No OneTemporary

File Metadata

Created
Wed, May 8, 4:42 AM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/3rdparty/ext_qt/0001-Use-fast-path-for-unsupported-mime-types.patch b/3rdparty/ext_qt/0001-Use-fast-path-for-unsupported-mime-types.patch
new file mode 100644
index 0000000000..3e72c541a4
--- /dev/null
+++ b/3rdparty/ext_qt/0001-Use-fast-path-for-unsupported-mime-types.patch
@@ -0,0 +1,33 @@
+From 6644f33d9c9be580f3277792e304d20c4b973cdd Mon Sep 17 00:00:00 2001
+From: Dmitry Kazakov <dimula73@gmail.com>
+Date: Wed, 19 Jun 2019 15:04:31 +0300
+Subject: [PATCH 01/17] Use fast path for unsupported mime types
+
+We don't need to request the entire image every time
+Windows asks for the list of supported MIME types. That
+can make graphical applications very slow (because the image
+might be quite big)
+
+Change-Id: I84223417661eceffa1362f8045c89e260b68e0a7
+---
+ src/plugins/platforms/windows/qwindowsmime.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp
+index 030d8d1e0f..b4f325736b 100644
+--- a/src/plugins/platforms/windows/qwindowsmime.cpp
++++ b/src/plugins/platforms/windows/qwindowsmime.cpp
+@@ -1082,6 +1082,10 @@ bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *p
+ bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
+ {
+ int cf = getCf(formatetc);
++
++ if (cf != CF_DIBV5 && cf != CF_DIB && cf != CF_PNG)
++ return false;
++
+ if (!mimeData->hasImage())
+ return false;
+ const QImage image = qvariant_cast<QImage>(mimeData->imageData());
+--
+2.20.1.windows.1
+
diff --git a/3rdparty/ext_qt/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch b/3rdparty/ext_qt/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch
deleted file mode 100644
index 13826a32da..0000000000
--- a/3rdparty/ext_qt/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From 9081571e58ceeaa4c623c83ecb41b04dfc7d90f9 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Tue, 21 Jun 2016 14:50:07 +0300
-Subject: [PATCH 2/4] Don't request the MIME image every time Windows asks for
- the list of supported types
-
-Change-Id: I05516d83dc4e0f192bc94f92cefc722f25dae4d4
----
- src/plugins/platforms/windows/qwindowsmime.cpp | 9 ++++++---
- 1 file changed, 6 insertions(+), 3 deletions(-)
-
-diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp
-index ff0dccb0d9..a702a2a90f 100644
---- a/src/plugins/platforms/windows/qwindowsmime.cpp
-+++ b/src/plugins/platforms/windows/qwindowsmime.cpp
-@@ -1082,12 +1082,15 @@ bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *p
- bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
- {
- int cf = getCf(formatetc);
-+
-+ if (cf != CF_DIBV5 && cf != CF_DIB && cf != CF_PNG)
-+ return false;
-+
- if (!mimeData->hasImage())
- return false;
-+
- const QImage image = qvariant_cast<QImage>(mimeData->imageData());
-- if (image.isNull())
-- return false;
-- return cf == CF_DIBV5 || (cf == CF_DIB) || cf == int(CF_PNG);
-+ return !image.isNull();
- }
-
- bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
---
-2.20.1.windows.1
-
diff --git a/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch b/3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch
similarity index 87%
rename from 3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch
rename to 3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch
index db93c33e36..aa09c1933d 100644
--- a/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch
+++ b/3rdparty/ext_qt/0002-Hack-always-return-we-support-DIBV5.patch
@@ -1,30 +1,30 @@
-From 6ce23924948d9697e07681bef44f0a47f9fd0d09 Mon Sep 17 00:00:00 2001
+From 835bb62519cc53976b8341c0d83a660674f66a92 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Tue, 21 Jun 2016 14:50:47 +0300
-Subject: [PATCH 3/4] Hack: always return we support DIBV5
+Subject: [PATCH 02/17] Hack: always return we support DIBV5
Asking for the entire image may be too expensive
Change-Id: I44c38fad73f1bb5859eb58b941054eeb6c3c6b66
---
src/plugins/platforms/windows/qwindowsmime.cpp | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp
-index a702a2a90f..03f8948a86 100644
+index b4f325736b..e2ae95d577 100644
--- a/src/plugins/platforms/windows/qwindowsmime.cpp
+++ b/src/plugins/platforms/windows/qwindowsmime.cpp
@@ -1055,9 +1055,7 @@ QVector<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, co
QVector<FORMATETC> formatetcs;
if (mimeData->hasImage() && mimeType == QLatin1String("application/x-qt-image")) {
//add DIBV5 if image has alpha channel. Do not add CF_PNG here as it will confuse MS Office (QTBUG47656).
- QImage image = qvariant_cast<QImage>(mimeData->imageData());
- if (!image.isNull() && image.hasAlphaChannel())
- formatetcs += setCf(CF_DIBV5);
+ formatetcs += setCf(CF_DIBV5);
formatetcs += setCf(CF_DIB);
}
if (!formatetcs.isEmpty())
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0003-Implement-openGL-surface-color-space-selection-in-An.patch b/3rdparty/ext_qt/0003-Implement-openGL-surface-color-space-selection-in-An.patch
new file mode 100644
index 0000000000..860d0ae15b
--- /dev/null
+++ b/3rdparty/ext_qt/0003-Implement-openGL-surface-color-space-selection-in-An.patch
@@ -0,0 +1,1200 @@
+From 721279a37e70459798928babacc2ccc8bf4fca3a Mon Sep 17 00:00:00 2001
+From: Dmitry Kazakov <dimula73@gmail.com>
+Date: Sat, 8 Dec 2018 15:35:43 +0300
+Subject: [PATCH 03/17] Implement openGL surface color space selection in Angle
+
+WARNING: this patch actually means that the library must be build on
+ the system with at least DXGI 1.4 (DirectX 12 API) present
+ in SDK. Mingw64 7.3 supports that.
+
+1) D3D11 implementation of angle now supports GL_RGB10_A2 format
+
+2) Technically, Angle's EGL implementation now supports the following
+ display extensions:
+ * EGL_KHR_gl_colorspace
+ * EGL_EXT_gl_colorspace_scrgb_linear
+ * EGL_EXT_gl_colorspace_bt2020_pq
+
+3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute,
+ which allows selection one of four color modes:
+ * Linear --- just pass-through data to GPU
+ * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system
+ becomes clever and automatically converts linear framebuffer
+ attachments to sRGB space, as per EGL_KHR_gl_colorspace definition.
+ It is not possible to select sRGB without this extra "feature".
+ * scRGB --- p709-g10 color space. This mode is the only mode
+ supported in f16-bit mode (and it is also not supported in other
+ bit depths).
+ * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode.
+
+5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode:
+ * because non-flip mode is considered deprecated and HDR is not
+ supported in it;
+ * because in flip-discard mode partial updates from
+ SwapChain11::present() are not supported and return an error,
+ which is never checked :)
+
+6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because
+ flip modes are not available on Windows 7 and such old systems.
+
+Notes:
+
+eglCreatePixmapSurface() is not implemented in Angle, so the support is
+not added.
+
+eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface()
+do not have support for color spaces according to the extension wording
+(and they are also not supported by Angle :) )
+
+Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6
+---
+ src/3rdparty/angle/src/common/platform.h | 14 +-
+ src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +-
+ src/3rdparty/angle/src/libANGLE/Caps.h | 9 +
+ .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +-
+ .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +-
+ .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 +
+ .../renderer/d3d/d3d11/Renderer11.cpp | 16 +-
+ .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +-
+ .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++-
+ .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +-
+ .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +-
+ .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +-
+ .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +-
+ .../angle/src/libANGLE/validationEGL.cpp | 53 ++
+ ...-surface-color-space-selection-in-An.patch | 596 ++++++++++++++++++
+ 15 files changed, 831 insertions(+), 20 deletions(-)
+ create mode 100644 src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch
+
+diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h
+index fb251da579..2e17994557 100644
+--- a/src/3rdparty/angle/src/common/platform.h
++++ b/src/3rdparty/angle/src/common/platform.h
+@@ -59,12 +59,14 @@
+ # endif
+
+ # if defined(ANGLE_ENABLE_D3D11)
+-#include <d3d10_1.h>
+-#include <d3d11.h>
+-#include <d3d11_3.h>
+-#include <d3dcompiler.h>
+-#include <dxgi.h>
+-#include <dxgi1_2.h>
++# include <d3d10_1.h>
++# include <d3d11.h>
++# include <dxgi.h>
++# include <d3d11_1.h>
++# include <d3d11_3.h>
++# include <dxgi1_2.h>
++# include <dxgi1_4.h> // WARNING: This is actually D3D12!
++# include <d3dcompiler.h>
+ # endif
+
+ #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11)
+diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp
+index 44da2bbe27..2088457458 100644
+--- a/src/3rdparty/angle/src/libANGLE/Caps.cpp
++++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp
+@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions()
+ displayTextureShareGroup(false),
+ createContextClientArrays(false),
+ programCacheControl(false),
+- robustResourceInitialization(false)
++ robustResourceInitialization(false),
++ colorspaceSRGB(false),
++ colorspaceSCRGBLinear(false),
++ colorspaceBt2020PQ(false)
+ {
+ }
+
+@@ -1146,6 +1149,9 @@ std::vector<std::string> DisplayExtensions::getStrings() const
+ InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings);
+ InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings);
+ InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings);
++ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings);
++ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings);
++ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings);
+ // TODO(jmadill): Enable this when complete.
+ //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings);
+ // clang-format on
+diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h
+index 64bdf97112..8157af5800 100644
+--- a/src/3rdparty/angle/src/libANGLE/Caps.h
++++ b/src/3rdparty/angle/src/libANGLE/Caps.h
+@@ -692,6 +692,15 @@ struct DisplayExtensions
+
+ // EGL_ANGLE_robust_resource_initialization
+ bool robustResourceInitialization;
++
++ // EGL_KHR_gl_colorspace
++ bool colorspaceSRGB;
++
++ // EGL_EXT_gl_colorspace_scrgb_linear
++ bool colorspaceSCRGBLinear;
++
++ // EGL_EXT_gl_colorspace_bt2020_pq
++ bool colorspaceBt2020PQ;
+ };
+
+ struct DeviceExtensions
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
+index dcc98f2ec6..b8ee635625 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
+@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize
+ GLenum backBufferFormat,
+ GLenum depthBufferFormat,
+ EGLint orientation,
+- EGLint samples) = 0;
++ EGLint samples,
++ EGLint colorSpace) = 0;
+ virtual egl::Error getD3DTextureInfo(const egl::Config *configuration,
+ IUnknown *d3dTexture,
+ EGLint *width,
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
+index 7657aef79e..efd4dd1a24 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
+@@ -22,6 +22,27 @@
+ namespace rx
+ {
+
++GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace)
++{
++ GLenum result = baseFormat;
++
++ /**
++ * If sRGB extension is supported, we should change the surface format
++ * to a specific one that does support automated gamma conversion.
++ *
++ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of
++ * textures in this format technically is not supported!
++ */
++ if (display->getExtensions().colorspaceSRGB &&
++ baseFormat == GL_RGBA8_OES &&
++ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR)
++ {
++ result = GL_SRGB8_ALPHA8;
++ }
++
++ return result;
++}
++
+ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
+ RendererD3D *renderer,
+ egl::Display *display,
+@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
+ mDisplay(display),
+ mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE),
+ mOrientation(static_cast<EGLint>(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))),
+- mRenderTargetFormat(state.config->renderTargetFormat),
++ mColorSpace(static_cast<EGLint>(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))),
++ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)),
+ mDepthStencilFormat(state.config->depthStencilFormat),
+ mSwapChain(nullptr),
+ mSwapIntervalDirty(true),
+@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display)
+
+ mSwapChain =
+ mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat,
+- mDepthStencilFormat, mOrientation, mState.config->samples);
++ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace);
+ if (!mSwapChain)
+ {
+ return egl::EglBadAlloc();
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
+index 01d2573244..ccb793d423 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
+@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl
+
+ bool mFixedSize;
+ GLint mOrientation;
++ EGLint mColorSpace;
+
+ GLenum mRenderTargetFormat;
+ GLenum mDepthStencilFormat;
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+index b0ef9abddc..f0e497b52f 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display)
+ mRenderer11DeviceCaps.supportsConstantBufferOffsets = false;
+ mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false;
+ mRenderer11DeviceCaps.supportsDXGI1_2 = false;
++ mRenderer11DeviceCaps.supportsDXGI1_4 = false;
+ mRenderer11DeviceCaps.B5G6R5support = 0;
+ mRenderer11DeviceCaps.B4G4R4A4support = 0;
+ mRenderer11DeviceCaps.B5G5R5A1support = 0;
+@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice()
+
+ // Gather stats on DXGI and D3D feature level
+ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2);
++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4);
+
+ ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel);
+
+@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps()
+ IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject<IDXGIAdapter2>(mDxgiAdapter);
+ mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr);
+ SafeRelease(dxgiAdapter2);
++
++ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject<IDXGIAdapter3>(mDxgiAdapter);
++ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr);
++ SafeRelease(dxgiAdapter3);
+ }
+
+ gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig(
+@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions
+
+ // All D3D feature levels support robust resource init
+ outExtensions->robustResourceInitialization = true;
++
++ // color space selection supported in DXGI 1.4 only
++ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4;
++ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4;
++ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4;
+ }
+
+ gl::Error Renderer11::flush()
+@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow,
+ GLenum backBufferFormat,
+ GLenum depthBufferFormat,
+ EGLint orientation,
+- EGLint samples)
++ EGLint samples,
++ EGLint colorSpace)
+ {
+ return new SwapChain11(this, GetAs<NativeWindow11>(nativeWindow), shareHandle, d3dTexture,
+- backBufferFormat, depthBufferFormat, orientation, samples);
++ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace);
+ }
+
+ void *Renderer11::getD3DDevice()
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+index a8c24e681b..3516bf779d 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps
+
+ D3D_FEATURE_LEVEL featureLevel;
+ bool supportsDXGI1_2; // Support for DXGI 1.2
++ bool supportsDXGI1_4; // Support for DXGI 1.4
+ bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView
+ bool supportsConstantBufferOffsets; // Support for Constant buffer offset
+ bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader
+@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D
+ GLenum backBufferFormat,
+ GLenum depthBufferFormat,
+ EGLint orientation,
+- EGLint samples) override;
++ EGLint samples,
++ EGLint colorSpace) override;
+ egl::Error getD3DTextureInfo(const egl::Config *configuration,
+ IUnknown *d3dTexture,
+ EGLint *width,
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
+index dcfd06484d..fc967b90d0 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
+@@ -18,6 +18,11 @@
+ #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"
+ #include "third_party/trace_event/trace_event.h"
+
++#if 0
++// used only for HDR metadata configuration options
++#include <dxgi1_5.h>
++#endif
++
+ // Precompiled shaders
+ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h"
+ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h"
+@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer,
+ GLenum backBufferFormat,
+ GLenum depthBufferFormat,
+ EGLint orientation,
+- EGLint samples)
++ EGLint samples,
++ EGLint colorSpace)
+ : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat),
+ mRenderer(renderer),
+ mWidth(-1),
+ mHeight(-1),
+ mOrientation(orientation),
++ mColorSpace(colorSpace),
+ mAppCreatedShareHandle(mShareHandle != nullptr),
+ mSwapInterval(0),
+ mPassThroughResourcesInit(false),
+@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context,
+ mSwapChain1 = d3d11::DynamicCastComObject<IDXGISwapChain1>(mSwapChain);
+ }
+
++ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4)
++ {
++ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject<IDXGISwapChain3>(mSwapChain);
++
++ printf("*** EGL colorSpace: 0x%X\n", mColorSpace);
++ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat);
++ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat());
++
++ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) {
++ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
++ switch (mColorSpace)
++ {
++ case EGL_GL_COLORSPACE_SRGB_KHR:
++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
++ break;
++ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
++ break;
++ case EGL_GL_COLORSPACE_BT2020_PQ_EXT:
++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
++ break;
++ default:
++ ASSERT(0 && "Unsupported colorspace requested");
++ }
++
++ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace);
++
++ UINT supported = 0;
++ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported);
++ ASSERT(SUCCEEDED(result));
++ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
++ SafeRelease(swapChain3);
++ return EGL_BAD_MATCH;
++ }
++
++ result = swapChain3->SetColorSpace1(nativeColorSpace);
++ ASSERT(SUCCEEDED(result));
++ }
++
++ SafeRelease(swapChain3);
++
++#if 0
++
++ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject<IDXGISwapChain4>(mSwapChain);
++
++ DXGI_HDR_METADATA_HDR10 md;
++ md.RedPrimary[0] = 0.680 * 50000;
++ md.RedPrimary[1] = 0.320 * 50000;
++ md.GreenPrimary[0] = 0.265 * 50000;
++ md.GreenPrimary[1] = 0.690 * 50000;
++ md.BluePrimary[0] = 0.150 * 50000;
++ md.BluePrimary[1] = 0.060 * 50000;
++ md.WhitePoint[0] = 0.3127 * 50000;
++ md.WhitePoint[1] = 0.3290 * 50000;
++ md.MaxMasteringLuminance = 1000 * 10000;
++ md.MinMasteringLuminance = 0.001 * 10000;
++ md.MaxContentLightLevel = 1000;
++ md.MaxFrameAverageLightLevel = 200;
++ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md);
++ printf("*** Result hdr 0x%X\n", result);
++ SafeRelease(swapChain4);
++#endif
++ }
++
+ ID3D11Texture2D *backbufferTex = nullptr;
+ result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
+ reinterpret_cast<LPVOID *>(&backbufferTex));
+ ASSERT(SUCCEEDED(result));
++
++ // TODO: recover rendering to sRGB
++ //
++ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc;
++ // offscreenRTVDesc.Format = getSwapChainNativeFormat();
++ //
++ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) {
++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) {
++ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
++ // }
++ //
++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) {
++ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
++ // }
++ // }
++ //
++ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format);
++
+ const auto &format =
+ d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps());
+ mBackBufferTexture.set(backbufferTex, format);
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
+index eca068210b..2a4b9ba274 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
+@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D
+ GLenum backBufferFormat,
+ GLenum depthBufferFormat,
+ EGLint orientation,
+- EGLint samples);
++ EGLint samples,
++ EGLint colorSpace);
+ ~SwapChain11() override;
+
+ EGLint resize(const gl::Context *context,
+@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D
+ EGLint mWidth;
+ EGLint mHeight;
+ const EGLint mOrientation;
++ EGLint mColorSpace;
+ bool mAppCreatedShareHandle;
+ unsigned int mSwapInterval;
+ bool mPassThroughResourcesInit;
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
+index 5394e3d3e7..af52c41d00 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
+@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
+
+ // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a
+ // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain.
++ //
++ // NOTE: in non-flip mode HDR rendering is not supported, so use it
++ // by default
+ IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject<IDXGIFactory2>(factory);
+ if (factory2 != nullptr)
+ {
+@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
+ swapChainDesc.SampleDesc.Quality = 0;
+ swapChainDesc.BufferUsage =
+ DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER;
+- swapChainDesc.BufferCount = 1;
++ swapChainDesc.BufferCount = 2;
+ swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
+- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
+ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
+ swapChainDesc.Flags = 0;
+ IDXGISwapChain1 *swapChain1 = nullptr;
+@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
+ }
+
+ DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
+- swapChainDesc.BufferCount = 1;
++ swapChainDesc.BufferCount = 2;
+ swapChainDesc.BufferDesc.Format = format;
+ swapChainDesc.BufferDesc.Width = width;
+ swapChainDesc.BufferDesc.Height = height;
+@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
+ swapChainDesc.SampleDesc.Count = samples;
+ swapChainDesc.SampleDesc.Quality = 0;
+ swapChainDesc.Windowed = TRUE;
++
++ /**
++ * NOTE1: in discard mode the swap chain doesn't support partial
++ * presentatiopn with Present1() call. Though it is not a big
++ * problem, because in case DXGI 1.2 is supported this code is
++ * unreachable.
++ *
++ * NOTE2: Flip modes are not supported on Windows 7 and the like,
++ * so use a legacy mode as a fallback
++ */
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
+
+ HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain);
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+index 75c6298868..58596169a8 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+@@ -718,8 +718,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow,
+ GLenum backBufferFormat,
+ GLenum depthBufferFormat,
+ EGLint orientation,
+- EGLint samples)
++ EGLint samples,
++ EGLint colorSpace)
+ {
++ UNUSED_VARIABLE(colorSpace);
+ return new SwapChain9(this, GetAs<NativeWindow9>(nativeWindow), shareHandle, d3dTexture,
+ backBufferFormat, depthBufferFormat, orientation);
+ }
+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
+index 9ddee45f0f..ce4bb201e5 100644
+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
+@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D
+ GLenum backBufferFormat,
+ GLenum depthBufferFormat,
+ EGLint orientation,
+- EGLint samples) override;
++ EGLint samples,
++ EGLint colorSpace) override;
+ egl::Error getD3DTextureInfo(const egl::Config *configuration,
+ IUnknown *d3dTexture,
+ EGLint *width,
+diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
+index 13a3a9e280..858d7ee929 100644
+--- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
++++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
+@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin
+ "either EGL_TRUE or EGL_FALSE.";
+ }
+ break;
++ case EGL_GL_COLORSPACE:
++
++ if (!displayExtensions.colorspaceSRGB)
++ {
++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
++ }
++
++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
++ {
++ if (!displayExtensions.colorspaceSCRGBLinear)
++ {
++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
++ }
++ }
++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
++ {
++ if (!displayExtensions.colorspaceBt2020PQ)
++ {
++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
++ }
++ }
++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
++ {
++ return EglBadAttribute() << "Unknown EGL color space requested";
++ }
++ break;
+
+ default:
+ return EglBadAttribute();
+@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri
+ }
+ break;
+
++ case EGL_GL_COLORSPACE:
++
++ if (!displayExtensions.colorspaceSRGB)
++ {
++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
++ }
++
++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
++ {
++ if (!displayExtensions.colorspaceSCRGBLinear)
++ {
++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
++ }
++ }
++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
++ {
++ if (!displayExtensions.colorspaceBt2020PQ)
++ {
++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
++ }
++ }
++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
++ {
++ return EglBadAttribute() << "Unknown EGL color space requested";
++ }
++ break;
++
+ default:
+ return EglBadAttribute();
+ }
+diff --git a/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch
+new file mode 100644
+index 0000000000..dfbe362690
+--- /dev/null
++++ b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch
+@@ -0,0 +1,596 @@
++From 05082a2affad3428e2ba4475a5c083e81a7730ab Mon Sep 17 00:00:00 2001
++From: Dmitry Kazakov <dimula73@gmail.com>
++Date: Sat, 8 Dec 2018 15:35:43 +0300
++Subject: [PATCH] Implement openGL surface color space selection in Angle
++
++WARNING: this patch actually means that the library must be build on
++ the system with at least DXGI 1.4 (DirectX 12 API) present
++ in SDK. Mingw64 7.3 supports that.
++
++1) D3D11 implementation of angle now supports GL_RGB10_A2 format
++
++2) Technically, Angle's EGL implementation now supports the following
++ display extensions:
++ * EGL_KHR_gl_colorspace
++ * EGL_EXT_gl_colorspace_scrgb_linear
++ * EGL_EXT_gl_colorspace_bt2020_pq
++
++3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute,
++ which allows selection one of four color modes:
++ * Linear --- just pass-through data to GPU
++ * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system
++ becomes clever and automatically converts linear framebuffer
++ attachments to sRGB space, as per EGL_KHR_gl_colorspace definition.
++ It is not possible to select sRGB without this extra "feature".
++ * scRGB --- p709-g10 color space. This mode is the only mode
++ supported in f16-bit mode (and it is also not supported in other
++ bit depths).
++ * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode.
++
++5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode:
++ * because non-flip mode is considered deprecated and HDR is not
++ supported in it;
++ * because in flip-discard mode partial updates from
++ SwapChain11::present() are not supported and return an error,
++ which is never checked :)
++
++6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because
++ flip modes are not available on Windows 7 and such old systems.
++
++Notes:
++
++eglCreatePixmapSurface() is not implemented in Angle, so the support is
++not added.
++
++eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface()
++do not have support for color spaces according to the extension wording
++(and they are also not supported by Angle :) )
++
++Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6
++---
++ src/3rdparty/angle/src/common/platform.h | 14 +--
++ src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +-
++ src/3rdparty/angle/src/libANGLE/Caps.h | 9 ++
++ .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +-
++ .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +++++-
++ .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 +
++ .../renderer/d3d/d3d11/Renderer11.cpp | 16 +++-
++ .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +-
++ .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++++++++++++++++++-
++ .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +-
++ .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +++-
++ .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +-
++ .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +-
++ .../angle/src/libANGLE/validationEGL.cpp | 53 +++++++++++
++ 14 files changed, 235 insertions(+), 20 deletions(-)
++
++diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h
++index fb251da579..2e17994557 100644
++--- a/src/3rdparty/angle/src/common/platform.h
+++++ b/src/3rdparty/angle/src/common/platform.h
++@@ -59,12 +59,14 @@
++ # endif
++
++ # if defined(ANGLE_ENABLE_D3D11)
++-#include <d3d10_1.h>
++-#include <d3d11.h>
++-#include <d3d11_3.h>
++-#include <d3dcompiler.h>
++-#include <dxgi.h>
++-#include <dxgi1_2.h>
+++# include <d3d10_1.h>
+++# include <d3d11.h>
+++# include <dxgi.h>
+++# include <d3d11_1.h>
+++# include <d3d11_3.h>
+++# include <dxgi1_2.h>
+++# include <dxgi1_4.h> // WARNING: This is actually D3D12!
+++# include <d3dcompiler.h>
++ # endif
++
++ #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11)
++diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp
++index 44da2bbe27..2088457458 100644
++--- a/src/3rdparty/angle/src/libANGLE/Caps.cpp
+++++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp
++@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions()
++ displayTextureShareGroup(false),
++ createContextClientArrays(false),
++ programCacheControl(false),
++- robustResourceInitialization(false)
+++ robustResourceInitialization(false),
+++ colorspaceSRGB(false),
+++ colorspaceSCRGBLinear(false),
+++ colorspaceBt2020PQ(false)
++ {
++ }
++
++@@ -1146,6 +1149,9 @@ std::vector<std::string> DisplayExtensions::getStrings() const
++ InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings);
++ InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings);
++ InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings);
+++ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings);
+++ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings);
+++ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings);
++ // TODO(jmadill): Enable this when complete.
++ //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings);
++ // clang-format on
++diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h
++index 64bdf97112..8157af5800 100644
++--- a/src/3rdparty/angle/src/libANGLE/Caps.h
+++++ b/src/3rdparty/angle/src/libANGLE/Caps.h
++@@ -692,6 +692,15 @@ struct DisplayExtensions
++
++ // EGL_ANGLE_robust_resource_initialization
++ bool robustResourceInitialization;
+++
+++ // EGL_KHR_gl_colorspace
+++ bool colorspaceSRGB;
+++
+++ // EGL_EXT_gl_colorspace_scrgb_linear
+++ bool colorspaceSCRGBLinear;
+++
+++ // EGL_EXT_gl_colorspace_bt2020_pq
+++ bool colorspaceBt2020PQ;
++ };
++
++ struct DeviceExtensions
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
++index dcc98f2ec6..b8ee635625 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
++@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize
++ GLenum backBufferFormat,
++ GLenum depthBufferFormat,
++ EGLint orientation,
++- EGLint samples) = 0;
+++ EGLint samples,
+++ EGLint colorSpace) = 0;
++ virtual egl::Error getD3DTextureInfo(const egl::Config *configuration,
++ IUnknown *d3dTexture,
++ EGLint *width,
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
++index 7657aef79e..efd4dd1a24 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
++@@ -22,6 +22,27 @@
++ namespace rx
++ {
++
+++GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace)
+++{
+++ GLenum result = baseFormat;
+++
+++ /**
+++ * If sRGB extension is supported, we should change the surface format
+++ * to a specific one that does support automated gamma conversion.
+++ *
+++ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of
+++ * textures in this format technically is not supported!
+++ */
+++ if (display->getExtensions().colorspaceSRGB &&
+++ baseFormat == GL_RGBA8_OES &&
+++ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR)
+++ {
+++ result = GL_SRGB8_ALPHA8;
+++ }
+++
+++ return result;
+++}
+++
++ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
++ RendererD3D *renderer,
++ egl::Display *display,
++@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
++ mDisplay(display),
++ mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE),
++ mOrientation(static_cast<EGLint>(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))),
++- mRenderTargetFormat(state.config->renderTargetFormat),
+++ mColorSpace(static_cast<EGLint>(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))),
+++ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)),
++ mDepthStencilFormat(state.config->depthStencilFormat),
++ mSwapChain(nullptr),
++ mSwapIntervalDirty(true),
++@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display)
++
++ mSwapChain =
++ mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat,
++- mDepthStencilFormat, mOrientation, mState.config->samples);
+++ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace);
++ if (!mSwapChain)
++ {
++ return egl::EglBadAlloc();
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
++index 01d2573244..ccb793d423 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
++@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl
++
++ bool mFixedSize;
++ GLint mOrientation;
+++ EGLint mColorSpace;
++
++ GLenum mRenderTargetFormat;
++ GLenum mDepthStencilFormat;
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
++index b0ef9abddc..f0e497b52f 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
++@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display)
++ mRenderer11DeviceCaps.supportsConstantBufferOffsets = false;
++ mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false;
++ mRenderer11DeviceCaps.supportsDXGI1_2 = false;
+++ mRenderer11DeviceCaps.supportsDXGI1_4 = false;
++ mRenderer11DeviceCaps.B5G6R5support = 0;
++ mRenderer11DeviceCaps.B4G4R4A4support = 0;
++ mRenderer11DeviceCaps.B5G5R5A1support = 0;
++@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice()
++
++ // Gather stats on DXGI and D3D feature level
++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2);
+++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4);
++
++ ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel);
++
++@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps()
++ IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject<IDXGIAdapter2>(mDxgiAdapter);
++ mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr);
++ SafeRelease(dxgiAdapter2);
+++
+++ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject<IDXGIAdapter3>(mDxgiAdapter);
+++ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr);
+++ SafeRelease(dxgiAdapter3);
++ }
++
++ gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig(
++@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions
++
++ // All D3D feature levels support robust resource init
++ outExtensions->robustResourceInitialization = true;
+++
+++ // color space selection supported in DXGI 1.4 only
+++ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4;
+++ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4;
+++ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4;
++ }
++
++ gl::Error Renderer11::flush()
++@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow,
++ GLenum backBufferFormat,
++ GLenum depthBufferFormat,
++ EGLint orientation,
++- EGLint samples)
+++ EGLint samples,
+++ EGLint colorSpace)
++ {
++ return new SwapChain11(this, GetAs<NativeWindow11>(nativeWindow), shareHandle, d3dTexture,
++- backBufferFormat, depthBufferFormat, orientation, samples);
+++ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace);
++ }
++
++ void *Renderer11::getD3DDevice()
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
++index a8c24e681b..3516bf779d 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
++@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps
++
++ D3D_FEATURE_LEVEL featureLevel;
++ bool supportsDXGI1_2; // Support for DXGI 1.2
+++ bool supportsDXGI1_4; // Support for DXGI 1.4
++ bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView
++ bool supportsConstantBufferOffsets; // Support for Constant buffer offset
++ bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader
++@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D
++ GLenum backBufferFormat,
++ GLenum depthBufferFormat,
++ EGLint orientation,
++- EGLint samples) override;
+++ EGLint samples,
+++ EGLint colorSpace) override;
++ egl::Error getD3DTextureInfo(const egl::Config *configuration,
++ IUnknown *d3dTexture,
++ EGLint *width,
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
++index dcfd06484d..fc967b90d0 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
++@@ -18,6 +18,11 @@
++ #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"
++ #include "third_party/trace_event/trace_event.h"
++
+++#if 0
+++// used only for HDR metadata configuration options
+++#include <dxgi1_5.h>
+++#endif
+++
++ // Precompiled shaders
++ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h"
++ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h"
++@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer,
++ GLenum backBufferFormat,
++ GLenum depthBufferFormat,
++ EGLint orientation,
++- EGLint samples)
+++ EGLint samples,
+++ EGLint colorSpace)
++ : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat),
++ mRenderer(renderer),
++ mWidth(-1),
++ mHeight(-1),
++ mOrientation(orientation),
+++ mColorSpace(colorSpace),
++ mAppCreatedShareHandle(mShareHandle != nullptr),
++ mSwapInterval(0),
++ mPassThroughResourcesInit(false),
++@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context,
++ mSwapChain1 = d3d11::DynamicCastComObject<IDXGISwapChain1>(mSwapChain);
++ }
++
+++ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4)
+++ {
+++ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject<IDXGISwapChain3>(mSwapChain);
+++
+++ printf("*** EGL colorSpace: 0x%X\n", mColorSpace);
+++ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat);
+++ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat());
+++
+++ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) {
+++ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+++ switch (mColorSpace)
+++ {
+++ case EGL_GL_COLORSPACE_SRGB_KHR:
+++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+++ break;
+++ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
+++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
+++ break;
+++ case EGL_GL_COLORSPACE_BT2020_PQ_EXT:
+++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+++ break;
+++ default:
+++ ASSERT(0 && "Unsupported colorspace requested");
+++ }
+++
+++ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace);
+++
+++ UINT supported = 0;
+++ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported);
+++ ASSERT(SUCCEEDED(result));
+++ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
+++ SafeRelease(swapChain3);
+++ return EGL_BAD_MATCH;
+++ }
+++
+++ result = swapChain3->SetColorSpace1(nativeColorSpace);
+++ ASSERT(SUCCEEDED(result));
+++ }
+++
+++ SafeRelease(swapChain3);
+++
+++#if 0
+++
+++ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject<IDXGISwapChain4>(mSwapChain);
+++
+++ DXGI_HDR_METADATA_HDR10 md;
+++ md.RedPrimary[0] = 0.680 * 50000;
+++ md.RedPrimary[1] = 0.320 * 50000;
+++ md.GreenPrimary[0] = 0.265 * 50000;
+++ md.GreenPrimary[1] = 0.690 * 50000;
+++ md.BluePrimary[0] = 0.150 * 50000;
+++ md.BluePrimary[1] = 0.060 * 50000;
+++ md.WhitePoint[0] = 0.3127 * 50000;
+++ md.WhitePoint[1] = 0.3290 * 50000;
+++ md.MaxMasteringLuminance = 1000 * 10000;
+++ md.MinMasteringLuminance = 0.001 * 10000;
+++ md.MaxContentLightLevel = 1000;
+++ md.MaxFrameAverageLightLevel = 200;
+++ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md);
+++ printf("*** Result hdr 0x%X\n", result);
+++ SafeRelease(swapChain4);
+++#endif
+++ }
+++
++ ID3D11Texture2D *backbufferTex = nullptr;
++ result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
++ reinterpret_cast<LPVOID *>(&backbufferTex));
++ ASSERT(SUCCEEDED(result));
+++
+++ // TODO: recover rendering to sRGB
+++ //
+++ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc;
+++ // offscreenRTVDesc.Format = getSwapChainNativeFormat();
+++ //
+++ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) {
+++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) {
+++ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+++ // }
+++ //
+++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) {
+++ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
+++ // }
+++ // }
+++ //
+++ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format);
+++
++ const auto &format =
++ d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps());
++ mBackBufferTexture.set(backbufferTex, format);
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
++index eca068210b..2a4b9ba274 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
++@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D
++ GLenum backBufferFormat,
++ GLenum depthBufferFormat,
++ EGLint orientation,
++- EGLint samples);
+++ EGLint samples,
+++ EGLint colorSpace);
++ ~SwapChain11() override;
++
++ EGLint resize(const gl::Context *context,
++@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D
++ EGLint mWidth;
++ EGLint mHeight;
++ const EGLint mOrientation;
+++ EGLint mColorSpace;
++ bool mAppCreatedShareHandle;
++ unsigned int mSwapInterval;
++ bool mPassThroughResourcesInit;
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
++index 5394e3d3e7..af52c41d00 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
++@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
++
++ // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a
++ // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain.
+++ //
+++ // NOTE: in non-flip mode HDR rendering is not supported, so use it
+++ // by default
++ IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject<IDXGIFactory2>(factory);
++ if (factory2 != nullptr)
++ {
++@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
++ swapChainDesc.SampleDesc.Quality = 0;
++ swapChainDesc.BufferUsage =
++ DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER;
++- swapChainDesc.BufferCount = 1;
+++ swapChainDesc.BufferCount = 2;
++ swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
++- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
+++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
++ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
++ swapChainDesc.Flags = 0;
++ IDXGISwapChain1 *swapChain1 = nullptr;
++@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
++ }
++
++ DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
++- swapChainDesc.BufferCount = 1;
+++ swapChainDesc.BufferCount = 2;
++ swapChainDesc.BufferDesc.Format = format;
++ swapChainDesc.BufferDesc.Width = width;
++ swapChainDesc.BufferDesc.Height = height;
++@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
++ swapChainDesc.SampleDesc.Count = samples;
++ swapChainDesc.SampleDesc.Quality = 0;
++ swapChainDesc.Windowed = TRUE;
+++
+++ /**
+++ * NOTE1: in discard mode the swap chain doesn't support partial
+++ * presentatiopn with Present1() call. Though it is not a big
+++ * problem, because in case DXGI 1.2 is supported this code is
+++ * unreachable.
+++ *
+++ * NOTE2: Flip modes are not supported on Windows 7 and the like,
+++ * so use a legacy mode as a fallback
+++ */
++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
++
++ HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain);
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
++index 75c6298868..58596169a8 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
++@@ -718,8 +718,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow,
++ GLenum backBufferFormat,
++ GLenum depthBufferFormat,
++ EGLint orientation,
++- EGLint samples)
+++ EGLint samples,
+++ EGLint colorSpace)
++ {
+++ UNUSED_VARIABLE(colorSpace);
++ return new SwapChain9(this, GetAs<NativeWindow9>(nativeWindow), shareHandle, d3dTexture,
++ backBufferFormat, depthBufferFormat, orientation);
++ }
++diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
++index 9ddee45f0f..ce4bb201e5 100644
++--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
+++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
++@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D
++ GLenum backBufferFormat,
++ GLenum depthBufferFormat,
++ EGLint orientation,
++- EGLint samples) override;
+++ EGLint samples,
+++ EGLint colorSpace) override;
++ egl::Error getD3DTextureInfo(const egl::Config *configuration,
++ IUnknown *d3dTexture,
++ EGLint *width,
++diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
++index 13a3a9e280..858d7ee929 100644
++--- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
+++++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
++@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin
++ "either EGL_TRUE or EGL_FALSE.";
++ }
++ break;
+++ case EGL_GL_COLORSPACE:
+++
+++ if (!displayExtensions.colorspaceSRGB)
+++ {
+++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
+++ }
+++
+++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
+++ {
+++ if (!displayExtensions.colorspaceSCRGBLinear)
+++ {
+++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
+++ }
+++ }
+++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
+++ {
+++ if (!displayExtensions.colorspaceBt2020PQ)
+++ {
+++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
+++ }
+++ }
+++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
+++ {
+++ return EglBadAttribute() << "Unknown EGL color space requested";
+++ }
+++ break;
++
++ default:
++ return EglBadAttribute();
++@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri
++ }
++ break;
++
+++ case EGL_GL_COLORSPACE:
+++
+++ if (!displayExtensions.colorspaceSRGB)
+++ {
+++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
+++ }
+++
+++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
+++ {
+++ if (!displayExtensions.colorspaceSCRGBLinear)
+++ {
+++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
+++ }
+++ }
+++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
+++ {
+++ if (!displayExtensions.colorspaceBt2020PQ)
+++ {
+++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
+++ }
+++ }
+++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
+++ {
+++ return EglBadAttribute() << "Unknown EGL color space requested";
+++ }
+++ break;
+++
++ default:
++ return EglBadAttribute();
++ }
++--
++2.20.1.windows.1
++
+--
+2.20.1.windows.1
+
diff --git a/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch b/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch
deleted file mode 100644
index b341b34d39..0000000000
--- a/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-From 4c1e4e693307c3169c6db488ad6bf6cff6aae864 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Mon, 11 Feb 2019 18:07:20 +0300
-Subject: [PATCH 4/4] Fix debug on openGL ES
-
-Change-Id: I08d1adf87b305c380a0f2177edf4ff9de109e4d6
----
- src/gui/opengl/qopengldebug.cpp | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/gui/opengl/qopengldebug.cpp b/src/gui/opengl/qopengldebug.cpp
-index 2e628a2bd5..9f1bb76869 100644
---- a/src/gui/opengl/qopengldebug.cpp
-+++ b/src/gui/opengl/qopengldebug.cpp
-@@ -1366,7 +1366,7 @@ bool QOpenGLDebugLogger::initialize()
-
- #define GET_DEBUG_PROC_ADDRESS(procName) \
- d->procName = reinterpret_cast< qt_ ## procName ## _t >( \
-- d->context->getProcAddress(#procName) \
-+ d->context->getProcAddress(d->context->isOpenGLES() ? (#procName "KHR") : (#procName)) \
- );
-
- GET_DEBUG_PROC_ADDRESS(glDebugMessageControl);
---
-2.20.1.windows.1
-
diff --git a/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch b/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch
new file mode 100644
index 0000000000..303174c533
--- /dev/null
+++ b/3rdparty/ext_qt/0004-Implement-color-space-selection-for-QSurfaceFormat.patch
@@ -0,0 +1,276 @@
+From 806fae9657a13559332796ade9cf1b302d014e46 Mon Sep 17 00:00:00 2001
+From: Dmitry Kazakov <dimula73@gmail.com>
+Date: Wed, 13 Feb 2019 16:56:11 +0300
+Subject: [PATCH 04/17] Implement color space selection for QSurfaceFormat
+
+With the patch one can select color space of openGL surface
+which is used a a root surface of the underlying native window.
+
+This feature is needed, e.g. when the user wants to render HDR
+content on screen. In such a case OS should be instructed about
+how to treat the graphical data in the application framebuffer.
+
+The easiest approach is to call QSurfaceFormat::setDefaultFormat()
+before creating the first application window. In such a case the
+root surface will (may) be in the requested format.
+
+Supported color spaces/formats:
+
+1) sRGB, SDR
+2) scRGB (Rec 709, gamma 1.0), HDR
+3) Rec 2020 PQ, HDR
+
+Please take into account that in real life the user should select
+proper bit depth for each color space, otherwise the system will
+refuse to create the surface:
+
+1) sRGB --- 8 bit or 10 bit
+2) scRGB --- 16 bit only
+3) Rec 2020 PQ --- 10 bit only
+
+Please note that color space selection is supported only on
+platforms with DXGI 1.4 and higher.
+
+Change-Id: I5f4945db9798d542f19c8ff1af1effa34f7745fd
+---
+ src/gui/kernel/qsurfaceformat.cpp | 11 ++++
+ src/gui/kernel/qsurfaceformat.h | 4 +-
+ src/gui/opengl/qopenglframebufferobject.cpp | 7 ++-
+ .../platforms/windows/qwindowseglcontext.cpp | 57 +++++++++++++++++--
+ .../platforms/windows/qwindowseglcontext.h | 6 +-
+ .../platforms/windows/qwindowsopenglcontext.h | 2 +-
+ .../platforms/windows/qwindowswindow.cpp | 8 ++-
+ 7 files changed, 83 insertions(+), 12 deletions(-)
+
+diff --git a/src/gui/kernel/qsurfaceformat.cpp b/src/gui/kernel/qsurfaceformat.cpp
+index 1a814ec21f..fc8b9c4f43 100644
+--- a/src/gui/kernel/qsurfaceformat.cpp
++++ b/src/gui/kernel/qsurfaceformat.cpp
+@@ -221,6 +221,17 @@ public:
+ set, the window will be created with an sRGB-capable default
+ framebuffer. Note that some platforms may return windows with a sRGB-capable
+ default framebuffer even when not requested explicitly.
++
++ \value scRGBColorSpace When \c{EGL_EXT_gl_colorspace_scrgb_linear}
++ is supported by the platform and this value is set, the window will
++ be created with an scRGB-capable default framebuffer. Note that some
++ platforms may return windows with a scRGB-capable default framebuffer
++ even when not requested explicitly. It usually happens when the application
++ requests 16-bit surface format.
++
++ \value bt2020PQColorSpace When \c{EGL_EXT_gl_colorspace_bt2020_pq}
++ is supported by the platform and this value is set, the window will
++ be created with an bt2020 PQ default framebuffer.
+ */
+
+ /*!
+diff --git a/src/gui/kernel/qsurfaceformat.h b/src/gui/kernel/qsurfaceformat.h
+index ed63eb8bbf..9ba6a29b7a 100644
+--- a/src/gui/kernel/qsurfaceformat.h
++++ b/src/gui/kernel/qsurfaceformat.h
+@@ -87,7 +87,9 @@ public:
+
+ enum ColorSpace {
+ DefaultColorSpace,
+- sRGBColorSpace
++ sRGBColorSpace,
++ scRGBColorSpace,
++ bt2020PQColorSpace
+ };
+ Q_ENUM(ColorSpace)
+
+diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp
+index cae3d516c4..ccdccb637a 100644
+--- a/src/gui/opengl/qopenglframebufferobject.cpp
++++ b/src/gui/opengl/qopenglframebufferobject.cpp
+@@ -545,10 +545,13 @@ void QOpenGLFramebufferObjectPrivate::initTexture(int idx)
+ ColorAttachment &color(colorAttachments[idx]);
+
+ GLuint pixelType = GL_UNSIGNED_BYTE;
+- if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10)
++ if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10) {
+ pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
+- else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16)
++ } else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16) {
+ pixelType = GL_UNSIGNED_SHORT;
++ } else if (color.internalFormat == GL_RGBA16F) {
++ pixelType = GL_HALF_FLOAT;
++ }
+
+ funcs.glTexImage2D(target, 0, color.internalFormat, color.size.width(), color.size.height(), 0,
+ GL_RGBA, pixelType, NULL);
+diff --git a/src/plugins/platforms/windows/qwindowseglcontext.cpp b/src/plugins/platforms/windows/qwindowseglcontext.cpp
+index 063e81150e..4cd745eac6 100644
+--- a/src/plugins/platforms/windows/qwindowseglcontext.cpp
++++ b/src/plugins/platforms/windows/qwindowseglcontext.cpp
+@@ -151,8 +151,9 @@ bool QWindowsLibEGL::init()
+ eglGetCurrentDisplay = RESOLVE((EGLDisplay (EGLAPIENTRY *)(void)), eglGetCurrentDisplay);
+ eglSwapBuffers = RESOLVE((EGLBoolean (EGLAPIENTRY *)(EGLDisplay , EGLSurface)), eglSwapBuffers);
+ eglGetProcAddress = RESOLVE((QFunctionPointer (EGLAPIENTRY * )(const char *)), eglGetProcAddress);
++ eglQueryString = RESOLVE((const char* (EGLAPIENTRY *)(EGLDisplay, EGLint)), eglQueryString);
+
+- if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress)
++ if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress || !eglQueryString)
+ return false;
+
+ eglGetPlatformDisplayEXT = nullptr;
+@@ -197,8 +198,15 @@ bool QWindowsLibGLESv2::init()
+ }
+
+ QWindowsEGLStaticContext::QWindowsEGLStaticContext(EGLDisplay display)
+- : m_display(display)
++ : m_display(display),
++ m_hasSRGBColorSpaceSupport(false),
++ m_hasSCRGBColorSpaceSupport(false),
++ m_hasBt2020PQColorSpaceSupport(false)
+ {
++ const char *eglExtensions = libEGL.eglQueryString(display, EGL_EXTENSIONS);
++ m_hasSRGBColorSpaceSupport = strstr(eglExtensions, "EGL_KHR_gl_colorspace") != nullptr;
++ m_hasSCRGBColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_scrgb_linear") != nullptr;
++ m_hasBt2020PQColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_bt2020_pq") != nullptr;
+ }
+
+ bool QWindowsEGLStaticContext::initializeAngle(QWindowsOpenGLTester::Renderers preferredType, HDC dc,
+@@ -297,11 +305,48 @@ QWindowsOpenGLContext *QWindowsEGLStaticContext::createContext(QOpenGLContext *c
+ return new QWindowsEGLContext(this, context->format(), context->shareHandle());
+ }
+
+-void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, int *err)
++void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig,
++ QSurfaceFormat::ColorSpace colorSpace, int *err)
+ {
+ *err = 0;
++
++ EGLint eglColorSpace = EGL_GL_COLORSPACE_LINEAR_KHR;
++ bool colorSpaceSupported = false;
++
++ switch (colorSpace) {
++ case QSurfaceFormat::DefaultColorSpace:
++ colorSpaceSupported = m_hasSRGBColorSpaceSupport;
++ break;
++ case QSurfaceFormat::sRGBColorSpace:
++ eglColorSpace = EGL_GL_COLORSPACE_SRGB_KHR;
++ colorSpaceSupported = m_hasSRGBColorSpaceSupport;
++ break;
++ case QSurfaceFormat::scRGBColorSpace:
++ eglColorSpace = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
++ colorSpaceSupported = m_hasSCRGBColorSpaceSupport;
++ break;
++ case QSurfaceFormat::bt2020PQColorSpace:
++ eglColorSpace = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
++ colorSpaceSupported = m_hasBt2020PQColorSpaceSupport;
++ break;
++ }
++
++ QVector<EGLint> attributes;
++
++ if (colorSpaceSupported) {
++ attributes << EGL_GL_COLORSPACE << eglColorSpace;
++ }
++
++ attributes << EGL_NONE;
++
++ if (!colorSpaceSupported && colorSpace != QSurfaceFormat::DefaultColorSpace) {
++ qWarning().nospace() << __FUNCTION__ << ": Requested color space is not supported by EGL implementation: " << colorSpace << " (egl: 0x" << hex << eglColorSpace << ")";
++ }
++
++
+ EGLSurface surface = libEGL.eglCreateWindowSurface(m_display, nativeConfig,
+- static_cast<EGLNativeWindowType>(nativeWindow), nullptr);
++ static_cast<EGLNativeWindowType>(nativeWindow),
++ attributes.constData());
+ if (surface == EGL_NO_SURFACE) {
+ *err = libEGL.eglGetError();
+ qWarning("%s: Could not create the EGL window surface: 0x%x", __FUNCTION__, *err);
+@@ -349,6 +394,7 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG
+ format.setSamples(sampleCount);
+ format.setStereo(false);
+ format.setSwapInterval(referenceFormat.swapInterval());
++ format.setColorSpace(referenceFormat.colorSpace());
+
+ // Clear the EGL error state because some of the above may
+ // have errored out because the attribute is not applicable
+@@ -378,7 +424,6 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG
+ \internal
+ \ingroup qt-lighthouse-win
+ */
+-
+ QWindowsEGLContext::QWindowsEGLContext(QWindowsEGLStaticContext *staticContext,
+ const QSurfaceFormat &format,
+ QPlatformOpenGLContext *share)
+@@ -483,6 +528,8 @@ bool QWindowsEGLContext::makeCurrent(QPlatformSurface *surface)
+ // Simulate context loss as the context is useless.
+ QWindowsEGLStaticContext::libEGL.eglDestroyContext(m_eglDisplay, m_eglContext);
+ m_eglContext = EGL_NO_CONTEXT;
++ } else if (err == EGL_BAD_MATCH) {
++ qCDebug(lcQpaGl) << "Got bad match in createWindowSurface() for context" << this << "Check color space configuration.";
+ }
+ return false;
+ }
+diff --git a/src/plugins/platforms/windows/qwindowseglcontext.h b/src/plugins/platforms/windows/qwindowseglcontext.h
+index 8a1e1ddae8..9f7742e6fb 100644
+--- a/src/plugins/platforms/windows/qwindowseglcontext.h
++++ b/src/plugins/platforms/windows/qwindowseglcontext.h
+@@ -80,6 +80,7 @@ struct QWindowsLibEGL
+ QFunctionPointer (EGLAPIENTRY *eglGetProcAddress)(const char *procname);
+
+ EGLDisplay (EGLAPIENTRY * eglGetPlatformDisplayEXT)(EGLenum platform, void *native_display, const EGLint *attrib_list);
++ const char* (EGLAPIENTRY * eglQueryString)(EGLDisplay dpy, EGLint name);
+
+ private:
+ #if !defined(QT_STATIC) || defined(QT_OPENGL_DYNAMIC)
+@@ -121,7 +122,7 @@ public:
+ void *moduleHandle() const override { return libGLESv2.moduleHandle(); }
+ QOpenGLContext::OpenGLModuleType moduleType() const override { return QOpenGLContext::LibGLES; }
+
+- void *createWindowSurface(void *nativeWindow, void *nativeConfig, int *err) override;
++ void *createWindowSurface(void *nativeWindow, void *nativeConfig, QSurfaceFormat::ColorSpace colorSpace, int *err) override;
+ void destroyWindowSurface(void *nativeSurface) override;
+
+ QSurfaceFormat formatFromConfig(EGLDisplay display, EGLConfig config, const QSurfaceFormat &referenceFormat);
+@@ -135,6 +136,9 @@ private:
+ EGLDisplay *display, EGLint *major, EGLint *minor);
+
+ const EGLDisplay m_display;
++ bool m_hasSRGBColorSpaceSupport;
++ bool m_hasSCRGBColorSpaceSupport;
++ bool m_hasBt2020PQColorSpaceSupport;
+ };
+
+ class QWindowsEGLContext : public QWindowsOpenGLContext
+diff --git a/src/plugins/platforms/windows/qwindowsopenglcontext.h b/src/plugins/platforms/windows/qwindowsopenglcontext.h
+index cc6d93d35e..61c0e28767 100644
+--- a/src/plugins/platforms/windows/qwindowsopenglcontext.h
++++ b/src/plugins/platforms/windows/qwindowsopenglcontext.h
+@@ -63,7 +63,7 @@ public:
+
+ // If the windowing system interface needs explicitly created window surfaces (like EGL),
+ // reimplement these.
+- virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, int * /*err*/) { return 0; }
++ virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, QSurfaceFormat::ColorSpace /*colorSpace*/, int * /*err*/) { return 0; }
+ virtual void destroyWindowSurface(void * /*nativeSurface*/) { }
+
+ protected:
+diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
+index 3bf424d0ac..7597fcb64d 100644
+--- a/src/plugins/platforms/windows/qwindowswindow.cpp
++++ b/src/plugins/platforms/windows/qwindowswindow.cpp
+@@ -2874,9 +2874,13 @@ void *QWindowsWindow::surface(void *nativeConfig, int *err)
+ return 0;
+ #endif
+ #ifndef QT_NO_OPENGL
++
++
++
+ if (!m_surface) {
+- if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext())
+- m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, err);
++ if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) {
++ m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, m_format.colorSpace(), err);
++ }
+ }
+
+ return m_surface;
+--
+2.20.1.windows.1
+
diff --git a/3rdparty/ext_qt/0005-Implement-color-conversion-for-the-backing-store-tex.patch b/3rdparty/ext_qt/0005-Implement-color-conversion-for-the-backing-store-tex.patch
new file mode 100644
index 0000000000..cd0dbb0bf4
--- /dev/null
+++ b/3rdparty/ext_qt/0005-Implement-color-conversion-for-the-backing-store-tex.patch
@@ -0,0 +1,627 @@
+From d9e2e0e62952dfcae6922307555e08ef5bce0c84 Mon Sep 17 00:00:00 2001
+From: Dmitry Kazakov <dimula73@gmail.com>
+Date: Thu, 22 Nov 2018 15:47:48 +0300
+Subject: [PATCH 05/17] Implement color conversion for the backing store
+ texture
+
+If the window surface is not in sRGB mode, then the backing store
+surface should be converted into the destinations color space.
+
+This patch adds the most popular color space transitions into
+QOpenGLTextureBlitter.
+
+The patch also implements QOpenGLWidget::setTextureColorSpace(),
+which notifies the compositor about the color space of non-native
+openGL widgets, so that the data could be converted correctly.
+
+TODO: should we implement the same for QOpenGLWindow::
+ setTextureColorSpace()?
+
+Note:
+The channels should be swizzled into RGBA *before* applying the
+color space conversion matrix. Otherwise the colors will be skewed,
+because the conversion function for R and B channels is not the same.
+
+Change-Id: Icbf599952c93cc04de417d0c3790a65282741655
+---
+ src/gui/opengl/qopengltextureblitter.cpp | 222 ++++++++++++++++--
+ src/gui/opengl/qopengltextureblitter.h | 12 +-
+ src/gui/painting/qplatformbackingstore.cpp | 16 +-
+ src/gui/painting/qplatformbackingstore.h | 4 +-
+ .../qopenglcompositorbackingstore.cpp | 2 +-
+ src/widgets/kernel/qopenglwidget.cpp | 45 +++-
+ src/widgets/kernel/qopenglwidget.h | 3 +
+ src/widgets/kernel/qwidget_p.h | 1 +
+ src/widgets/kernel/qwidgetbackingstore.cpp | 2 +-
+ 9 files changed, 281 insertions(+), 26 deletions(-)
+
+diff --git a/src/gui/opengl/qopengltextureblitter.cpp b/src/gui/opengl/qopengltextureblitter.cpp
+index b65df9dc82..5f6dbff292 100644
+--- a/src/gui/opengl/qopengltextureblitter.cpp
++++ b/src/gui/opengl/qopengltextureblitter.cpp
+@@ -131,14 +131,85 @@ static const char vertex_shader[] =
+ "}";
+
+ static const char fragment_shader[] =
+- "varying highp vec2 uv;"
+- "uniform sampler2D textureSampler;"
+- "uniform bool swizzle;"
+- "uniform highp float opacity;"
++ "varying highp vec2 uv;\n"
++ "uniform sampler2D textureSampler;\n"
++ "uniform bool swizzle;\n"
++ "uniform highp float opacity;\n"
++ "#if defined SCRGB_TO_SRGB\n"
++ "highp vec4 linearToSRGB(highp vec4 value)\n"
++ "{\n"
++ " bvec4 cutoff = lessThan(value, vec4(0.0031308));\n"
++ " const highp vec2 a1 = vec2(0.055, 0.0);\n"
++ " const highp vec2 c2 = vec2(1.055, 1.0);\n"
++ " const highp vec2 m3 = vec2(2.4, 1.0);\n"
++ " const highp vec2 c4 = vec2(12.92, 1.0);\n"
++ " highp vec4 higher = c2.xxxy * pow(value, 1.0 / m3.xxxy) - a1.xxxy;\n"
++ " highp vec4 lower = value * c4.xxxy;\n"
++ " return mix(higher, lower, vec4(cutoff));\n"
++ "}\n"
++ "#endif\n"
++ "#if defined SRGB_TO_SCRGB || defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n"
++ "highp vec4 sRgbToLinear(highp vec4 sRGB)\n"
++ "{\n"
++ " bvec4 cutoff = lessThan(sRGB, vec4(0.04045));\n"
++ " const highp vec2 a1 = vec2(0.055, 0.0);\n"
++ " const highp vec2 c2 = vec2(1.055, 1.0);\n"
++ " const highp vec2 m3 = vec2(2.4, 1.0);\n"
++ " const highp vec2 c4 = vec2(12.92, 1.0);\n"
++ " highp vec4 higher = pow((sRGB + a1.xxxy) / c2.xxxy, m3.xxxy);\n"
++ " highp vec4 lower = sRGB / c4.xxxy;\n"
++ " return mix(higher, lower, vec4(cutoff));\n"
++ "}\n"
++ "#endif\n"
++ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n"
++ "highp vec4 applySmpte2084Curve(highp vec4 L)\n"
++ "{"
++ " const highp vec2 m1 = vec2(2610.0 / 4096.0 / 4.0, 1.0);\n"
++ " const highp vec2 m2 = vec2(2523.0 / 4096.0 * 128.0, 1.0);\n"
++ " const highp vec2 a1 = vec2(3424.0 / 4096.0, 0.0);\n"
++ " const highp vec2 c2 = vec2(2413.0 / 4096.0 * 32.0, 1.0);\n"
++ " const highp vec2 c3 = vec2(2392.0 / 4096.0 * 32.0, 1.0);\n"
++ " const highp vec2 a4 = vec2(1.0, 0.0);\n"
++ " highp vec4 Lp = pow(L, m1.xxxy);\n"
++ " highp vec4 res = pow((a1.xxxy + c2.xxxy * Lp) / (a4.xxxy + c3.xxxy * Lp), m2.xxxy);\n"
++ " return res;"
++ "}\n"
++ "#endif\n"
++ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n"
++ "highp vec4 scRgbToBt2020pq(highp vec4 value)\n"
++ "{\n"
++ " const highp mat4 convMat = "
++ " mat4(0.627402, 0.069095, 0.016394, 0.0,"
++ " 0.329292, 0.919544, 0.088028, 0.0,"
++ " 0.043306, 0.011360, 0.895578, 0.0,"
++ " 0.0, 0.0, 0.0, 1.0);"
++ ""
++ " value = convMat * value;\n"
++ " return applySmpte2084Curve(0.008 * value);"
++ "}\n"
++ "#endif\n"
++ "#if defined SRGB_TO_BT2020PQ\n"
++ "highp vec4 sRgbToBt2020pq(highp vec4 value)\n"
++ "{\n"
++ " value = sRgbToLinear(value);"
++ " return scRgbToBt2020pq(value);"
++ "}\n"
++ "#endif\n"
++ "\n"
+ "void main() {"
+ " highp vec4 tmpFragColor = texture2D(textureSampler,uv);"
+- " tmpFragColor.a *= opacity;"
+- " gl_FragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;"
++ " tmpFragColor.a *= opacity;\n"
++ " tmpFragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;\n"
++ "#if defined SRGB_TO_SCRGB\n"
++ " tmpFragColor = sRgbToLinear(tmpFragColor);\n"
++ "#elif defined SRGB_TO_BT2020PQ\n"
++ " tmpFragColor = sRgbToBt2020pq(tmpFragColor);\n"
++ "#elif defined SCRGB_TO_BT2020PQ\n"
++ " tmpFragColor = scRgbToBt2020pq(tmpFragColor);\n"
++ "#elif defined SCRGB_TO_SRGB\n"
++ " tmpFragColor = linearToSRGB(tmpFragColor);\n"
++ "#endif\n"
++ " gl_FragColor = tmpFragColor;"
+ "}";
+
+ static const char fragment_shader_external_oes[] =
+@@ -187,6 +258,23 @@ private:
+ GLenum m_target;
+ };
+
++class ColorSpaceConversion : public QPair<QSurfaceFormat::ColorSpace, QSurfaceFormat::ColorSpace>
++{
++public:
++ ColorSpaceConversion() { };
++ ColorSpaceConversion(QSurfaceFormat::ColorSpace srcColorSpace,
++ QSurfaceFormat::ColorSpace dstColorSpace)
++ : QPair(srcColorSpace, dstColorSpace)
++ { }
++
++ QSurfaceFormat::ColorSpace source() const {
++ return first;
++ }
++ QSurfaceFormat::ColorSpace destination() const {
++ return second;
++ }
++};
++
+ class QOpenGLTextureBlitterPrivate
+ {
+ public:
+@@ -197,16 +285,29 @@ public:
+ };
+
+ enum ProgramIndex {
+- TEXTURE_2D,
+- TEXTURE_EXTERNAL_OES
++ TEXTURE_2D = 0,
++ TEXTURE_2D_SRGB_TO_SCRGB,
++ TEXTURE_2D_SCRGB_TO_SRGB,
++ TEXTURE_2D_SRGB_TO_BT2020PQ,
++ TEXTURE_2D_SCRGB_TO_BT2020PQ,
++ TEXTURE_EXTERNAL_OES,
++
++ PROGRAM_COUNT
+ };
+
+ QOpenGLTextureBlitterPrivate() :
+ swizzle(false),
+ opacity(1.0f),
+ vao(new QOpenGLVertexArrayObject),
+- currentTarget(TEXTURE_2D)
+- { }
++ currentTarget(GL_NONE),
++ colorSpaceConversion(0)
++ {
++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::DefaultColorSpace, QSurfaceFormat::DefaultColorSpace);
++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::scRGBColorSpace);
++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::sRGBColorSpace);
++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace);
++ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace);
++ }
+
+ bool buildProgram(ProgramIndex idx, const char *vs, const char *fs);
+
+@@ -214,6 +315,7 @@ public:
+ void blit(GLuint texture, const QMatrix4x4 &vertexTransform, QOpenGLTextureBlitter::Origin origin);
+
+ void prepareProgram(const QMatrix4x4 &vertexTransform);
++ int calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace);
+
+ QOpenGLBuffer vertexBuffer;
+ QOpenGLBuffer textureBuffer;
+@@ -239,18 +341,48 @@ public:
+ bool swizzle;
+ float opacity;
+ TextureMatrixUniform textureMatrixUniformState;
+- } programs[2];
++ } programs[PROGRAM_COUNT];
+ bool swizzle;
+ float opacity;
+ QScopedPointer<QOpenGLVertexArrayObject> vao;
+ GLenum currentTarget;
++
++ int colorSpaceConversion;
++ QVector<ColorSpaceConversion> supportedColorSpaceConversions;
+ };
+
+-static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target)
++int QOpenGLTextureBlitterPrivate::calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace)
++{
++ // TODO: auto-detect destination color space of the surface
++ // in case of default color space
++
++ // disable color management if at least one of the color
++ // spaces is declared as default
++ if (srcColorSpace == QSurfaceFormat::DefaultColorSpace ||
++ dstColorSpace == QSurfaceFormat::DefaultColorSpace) {
++
++ return 0;
++ }
++
++ // disable color management if source and destination color
++ // spaces are the same
++ if (srcColorSpace == dstColorSpace) {
++ return 0;
++ }
++
++ ColorSpaceConversion conversion(srcColorSpace, dstColorSpace);
++ return supportedColorSpaceConversions.indexOf(conversion);
++}
++
++static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target, int colorSpaceConversion)
+ {
+ switch (target) {
+- case GL_TEXTURE_2D:
+- return QOpenGLTextureBlitterPrivate::TEXTURE_2D;
++ case GL_TEXTURE_2D: {
++ QOpenGLTextureBlitterPrivate::ProgramIndex index =
++ QOpenGLTextureBlitterPrivate::ProgramIndex(
++ int(QOpenGLTextureBlitterPrivate::TEXTURE_2D) + colorSpaceConversion);
++ return index;
++ }
+ case GL_TEXTURE_EXTERNAL_OES:
+ return QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES;
+ default:
+@@ -261,7 +393,7 @@ static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GL
+
+ void QOpenGLTextureBlitterPrivate::prepareProgram(const QMatrix4x4 &vertexTransform)
+ {
+- Program *program = &programs[targetToProgramIndex(currentTarget)];
++ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)];
+
+ vertexBuffer.bind();
+ program->glProgram->setAttributeBuffer(program->vertexCoordAttribPos, GL_FLOAT, 0, 3, 0);
+@@ -293,7 +425,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture,
+ TextureBinder binder(currentTarget, texture);
+ prepareProgram(vertexTransform);
+
+- Program *program = &programs[targetToProgramIndex(currentTarget)];
++ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)];
+ program->glProgram->setUniformValue(program->textureTransformUniformPos, textureTransform);
+ program->textureMatrixUniformState = User;
+
+@@ -307,7 +439,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture,
+ TextureBinder binder(currentTarget, texture);
+ prepareProgram(vertexTransform);
+
+- Program *program = &programs[targetToProgramIndex(currentTarget)];
++ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)];
+ if (origin == QOpenGLTextureBlitter::OriginTopLeft) {
+ if (program->textureMatrixUniformState != IdentityFlipped) {
+ QMatrix3x3 flipped;
+@@ -408,6 +540,28 @@ bool QOpenGLTextureBlitter::create()
+ } else {
+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D, vertex_shader, fragment_shader))
+ return false;
++
++ // TODO: create non-default transformations on-demand
++ {
++ const QString shader = QString("#define SRGB_TO_SCRGB\n %1").arg(fragment_shader);
++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB, vertex_shader, shader.toLatin1().constData()))
++ return false;
++ }
++ {
++ const QString shader = QString("#define SCRGB_TO_SRGB\n %1").arg(fragment_shader);
++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_SRGB, vertex_shader, shader.toLatin1().constData()))
++ return false;
++ }
++ {
++ const QString shader = QString("#define SRGB_TO_BT2020PQ\n %1").arg(fragment_shader);
++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData()))
++ return false;
++ }
++ {
++ const QString shader = QString("#define SCRGB_TO_BT2020PQ\n %1").arg(fragment_shader);
++ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData()))
++ return false;
++ }
+ if (supportsExternalOESTarget())
+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES, vertex_shader, fragment_shader_external_oes))
+ return false;
+@@ -455,6 +609,8 @@ void QOpenGLTextureBlitter::destroy()
+ return;
+ Q_D(QOpenGLTextureBlitter);
+ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D].glProgram.reset();
++ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB].glProgram.reset();
++ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ].glProgram.reset();
+ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES].glProgram.reset();
+ d->vertexBuffer.destroy();
+ d->textureBuffer.destroy();
+@@ -484,15 +640,26 @@ bool QOpenGLTextureBlitter::supportsExternalOESTarget() const
+
+ \sa release(), blit()
+ */
+-void QOpenGLTextureBlitter::bind(GLenum target)
++void QOpenGLTextureBlitter::bind(GLenum target,
++ QSurfaceFormat::ColorSpace srcColorSpace,
++ QSurfaceFormat::ColorSpace dstColorSpace)
+ {
+ Q_D(QOpenGLTextureBlitter);
+
+ if (d->vao->isCreated())
+ d->vao->bind();
+
++ const int index = d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace);
++
++ if (index >= 0) {
++ d->colorSpaceConversion = index;
++ } else {
++ qWarning() << "QOpenGLTextureBlitter::bind(): color space conversion is not supported" << srcColorSpace << dstColorSpace;
++ d->colorSpaceConversion = 0; // noop conversion
++ }
++
+ d->currentTarget = target;
+- QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target)];
++ QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target, d->colorSpaceConversion)];
+ p->glProgram->bind();
+
+ d->vertexBuffer.bind();
+@@ -506,6 +673,21 @@ void QOpenGLTextureBlitter::bind(GLenum target)
+ d->textureBuffer.release();
+ }
+
++void QOpenGLTextureBlitter::rebind(GLenum target, QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace)
++{
++ Q_D(QOpenGLTextureBlitter);
++
++ if (d->vao->isCreated() &&
++ d->currentTarget == target &&
++ d->colorSpaceConversion == d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace)) {
++
++ // the blitter is already configured in the correct state, so just skip it
++ return;
++ }
++
++ bind(target, srcColorSpace, dstColorSpace);
++}
++
+ /*!
+ Unbinds the graphics resources used by the blitter.
+
+@@ -514,7 +696,7 @@ void QOpenGLTextureBlitter::bind(GLenum target)
+ void QOpenGLTextureBlitter::release()
+ {
+ Q_D(QOpenGLTextureBlitter);
+- d->programs[targetToProgramIndex(d->currentTarget)].glProgram->release();
++ d->programs[targetToProgramIndex(d->currentTarget, d->colorSpaceConversion)].glProgram->release();
+ if (d->vao->isCreated())
+ d->vao->release();
+ }
+diff --git a/src/gui/opengl/qopengltextureblitter.h b/src/gui/opengl/qopengltextureblitter.h
+index 2f7c6b1a0a..3c87e4e2b5 100644
+--- a/src/gui/opengl/qopengltextureblitter.h
++++ b/src/gui/opengl/qopengltextureblitter.h
+@@ -48,6 +48,9 @@
+ #include <QtGui/QMatrix3x3>
+ #include <QtGui/QMatrix4x4>
+
++// TODO: less includes!!!
++#include <QSurfaceFormat>
++
+ QT_BEGIN_NAMESPACE
+
+ class QOpenGLTextureBlitterPrivate;
+@@ -69,7 +72,14 @@ public:
+
+ bool supportsExternalOESTarget() const;
+
+- void bind(GLenum target = GL_TEXTURE_2D);
++ void bind(GLenum target = GL_TEXTURE_2D,
++ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace,
++ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace);
++
++ void rebind(GLenum target = GL_TEXTURE_2D,
++ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace,
++ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace);
++
+ void release();
+
+ void setRedBlueSwizzle(bool swizzle);
+diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp
+index c71d82546a..8dd96a66bd 100644
+--- a/src/gui/painting/qplatformbackingstore.cpp
++++ b/src/gui/painting/qplatformbackingstore.cpp
+@@ -132,6 +132,7 @@ struct QBackingstoreTextureInfo
+ QRect rect;
+ QRect clipRect;
+ QPlatformTextureList::Flags flags;
++ QSurfaceFormat::ColorSpace colorSpace;
+ };
+
+ Q_DECLARE_TYPEINFO(QBackingstoreTextureInfo, Q_MOVABLE_TYPE);
+@@ -181,6 +182,12 @@ QPlatformTextureList::Flags QPlatformTextureList::flags(int index) const
+ return d->textures.at(index).flags;
+ }
+
++QSurfaceFormat::ColorSpace QPlatformTextureList::colorSpace(int index) const
++{
++ Q_D(const QPlatformTextureList);
++ return d->textures.at(index).colorSpace;
++}
++
+ QRect QPlatformTextureList::geometry(int index) const
+ {
+ Q_D(const QPlatformTextureList);
+@@ -209,7 +216,7 @@ bool QPlatformTextureList::isLocked() const
+ }
+
+ void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const QRect &geometry,
+- const QRect &clipRect, Flags flags)
++ const QRect &clipRect, Flags flags, QSurfaceFormat::ColorSpace colorSpace)
+ {
+ Q_D(QPlatformTextureList);
+ QBackingstoreTextureInfo bi;
+@@ -218,6 +225,7 @@ void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const Q
+ bi.rect = geometry;
+ bi.clipRect = clipRect;
+ bi.flags = flags;
++ bi.colorSpace = colorSpace;
+ d->textures.append(bi);
+ }
+
+@@ -300,6 +308,7 @@ static void blitTextureForWidget(const QPlatformTextureList *textures, int idx,
+ if (srgb && canUseSrgb)
+ funcs->glEnable(GL_FRAMEBUFFER_SRGB);
+
++ blitter->rebind(GL_TEXTURE_2D, textures->colorSpace(idx), window->format().colorSpace());
+ blitter->blit(textures->textureId(idx), target, source);
+
+ if (srgb && canUseSrgb)
+@@ -433,6 +442,11 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
+ funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
+
+ if (textureId) {
++ // GUI texture is always in sRGB color space
++ d_ptr->blitter->rebind(GL_TEXTURE_2D,
++ QSurfaceFormat::sRGBColorSpace,
++ window->format().colorSpace());
++
+ if (d_ptr->needsSwizzle)
+ d_ptr->blitter->setRedBlueSwizzle(true);
+ // The backingstore is for the entire tlw.
+diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h
+index de5ba964dc..f8887bd4cd 100644
+--- a/src/gui/painting/qplatformbackingstore.h
++++ b/src/gui/painting/qplatformbackingstore.h
+@@ -95,11 +95,13 @@ public:
+ QRect clipRect(int index) const;
+ void *source(int index);
+ Flags flags(int index) const;
++ QSurfaceFormat::ColorSpace colorSpace(int index) const;
+ void lock(bool on);
+ bool isLocked() const;
+
+ void appendTexture(void *source, GLuint textureId, const QRect &geometry,
+- const QRect &clipRect = QRect(), Flags flags = 0);
++ const QRect &clipRect = QRect(), Flags flags = 0,
++ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::DefaultColorSpace);
+ void clear();
+
+ Q_SIGNALS:
+diff --git a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp
+index 40400e2a19..5d44e62455 100644
+--- a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp
++++ b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp
+@@ -230,7 +230,7 @@ void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegi
+ m_textures->clear();
+ for (int i = 0; i < textures->count(); ++i)
+ m_textures->appendTexture(textures->source(i), textures->textureId(i), textures->geometry(i),
+- textures->clipRect(i), textures->flags(i));
++ textures->clipRect(i), textures->flags(i), textures->colorSpace(i));
+
+ updateTexture();
+ m_textures->appendTexture(nullptr, m_bsTexture, window->geometry());
+diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp
+index 7aef74c507..787445a4d9 100644
+--- a/src/widgets/kernel/qopenglwidget.cpp
++++ b/src/widgets/kernel/qopenglwidget.cpp
+@@ -568,7 +568,8 @@ public:
+ updateBehavior(QOpenGLWidget::NoPartialUpdate),
+ requestedSamples(0),
+ inPaintGL(false),
+- textureFormat(0)
++ textureFormat(0),
++ textureColorSpace(QSurfaceFormat::DefaultColorSpace)
+ {
+ requestedFormat = QSurfaceFormat::defaultFormat();
+ }
+@@ -578,6 +579,7 @@ public:
+
+ GLuint textureId() const override;
+ QPlatformTextureList::Flags textureListFlags() override;
++ QSurfaceFormat::ColorSpace colorSpace() const override;
+
+ void initialize();
+ void invokeUserPaint();
+@@ -609,6 +611,7 @@ public:
+ int requestedSamples;
+ bool inPaintGL;
+ GLenum textureFormat;
++ QSurfaceFormat::ColorSpace textureColorSpace;
+ };
+
+ void QOpenGLWidgetPaintDevicePrivate::beginPaint()
+@@ -695,6 +698,11 @@ QPlatformTextureList::Flags QOpenGLWidgetPrivate::textureListFlags()
+ return flags;
+ }
+
++QSurfaceFormat::ColorSpace QOpenGLWidgetPrivate::colorSpace() const
++{
++ return textureColorSpace;
++}
++
+ void QOpenGLWidgetPrivate::reset()
+ {
+ Q_Q(QOpenGLWidget);
+@@ -1115,6 +1123,41 @@ void QOpenGLWidget::setTextureFormat(GLenum texFormat)
+ d->textureFormat = texFormat;
+ }
+
++/*!
++ \return the declared color space of the internal texture of the widget.
++
++ The texture's color space will be used when composing the widget
++ into the root window surface.
++
++ \note when the color space is set to QSurfaceFormat::DefaultColorSpace,
++ color conversion is effectively disabled.
++
++ \since 5.99
++ */
++QSurfaceFormat::ColorSpace QOpenGLWidget::textureColorSpace() const
++{
++ Q_D(const QOpenGLWidget);
++ return d->textureColorSpace;
++}
++
++/*!
++ Sets a custom color space for the internal texture of the widget
++
++ The color space of the texture will be compared against the color
++ space of the root surface and conversion will be performed if needed.
++
++ \note setting the color space to QSurfaceFormat::DefaultColorSpace will
++ effectively disable color conversion when composing this texture on
++ screen.
++
++ \since 5.99
++ */
++void QOpenGLWidget::setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace)
++{
++ Q_D(QOpenGLWidget);
++ d->textureColorSpace = colorSpace;
++}
++
+ /*!
+ \return the active internal texture format if the widget has already
+ initialized, the requested format if one was set but the widget has not yet
+diff --git a/src/widgets/kernel/qopenglwidget.h b/src/widgets/kernel/qopenglwidget.h
+index 9eb4a9ba5a..eff2d9796d 100644
+--- a/src/widgets/kernel/qopenglwidget.h
++++ b/src/widgets/kernel/qopenglwidget.h
+@@ -75,6 +75,9 @@ public:
+ GLenum textureFormat() const;
+ void setTextureFormat(GLenum texFormat);
+
++ QSurfaceFormat::ColorSpace textureColorSpace() const;
++ void setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace);
++
+ bool isValid() const;
+
+ void makeCurrent();
+diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
+index ae50624c04..67c43ca7a4 100644
+--- a/src/widgets/kernel/qwidget_p.h
++++ b/src/widgets/kernel/qwidget_p.h
+@@ -655,6 +655,7 @@ public:
+ ? QPlatformTextureList::StacksOnTop
+ : QPlatformTextureList::Flags(0);
+ }
++ virtual QSurfaceFormat::ColorSpace colorSpace() const { return QSurfaceFormat::DefaultColorSpace; }
+ virtual QImage grabFramebuffer() { return QImage(); }
+ virtual void beginBackingStorePainting() { }
+ virtual void endBackingStorePainting() { }
+diff --git a/src/widgets/kernel/qwidgetbackingstore.cpp b/src/widgets/kernel/qwidgetbackingstore.cpp
+index a32eb2a03b..db60338034 100644
+--- a/src/widgets/kernel/qwidgetbackingstore.cpp
++++ b/src/widgets/kernel/qwidgetbackingstore.cpp
+@@ -1007,7 +1007,7 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatfo
+ if (wd->renderToTexture) {
+ QPlatformTextureList::Flags flags = wd->textureListFlags();
+ const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
+- widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags);
++ widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags, wd->colorSpace());
+ }
+
+ for (int i = 0; i < wd->children.size(); ++i) {
+--
+2.20.1.windows.1
+
diff --git a/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch b/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch
deleted file mode 100644
index 60aee3488f..0000000000
--- a/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch
+++ /dev/null
@@ -1,3598 +0,0 @@
-From 3a16c206f86ddd97c4ef6c89bbeb444b7a16a89a Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Sat, 8 Dec 2018 15:35:43 +0300
-Subject: [PATCH 1/6] Implement openGL surface color space selection in Angle
-
-WARNING: this patch actually means that the library must be build on
- the system with at least DXGI 1.4 (DirectX 12 API) present
- in SDK. Mingw64 7.3 supports that.
-
-1) D3D11 implementation of angle now supports GL_RGB10_A2 format
-
-2) Technically, Angle's EGL implementation now supports the following
- display extensions:
- * EGL_KHR_gl_colorspace
- * EGL_EXT_gl_colorspace_scrgb_linear
- * EGL_EXT_gl_colorspace_bt2020_pq
-
-3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute,
- which allows selection one of four color modes:
- * Linear --- just pass-through data to GPU
- * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system
- becomes clever and automatically converts linear framebuffer
- attachments to sRGB space, as per EGL_KHR_gl_colorspace definition.
- It is not possible to select sRGB without this extra "feature".
- * scRGB --- p709-g10 color space. This mode is the only mode
- supported in f16-bit mode (and it is also not supported in other
- bit depths).
- * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode.
-
-5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode:
- * because non-flip mode is considered deprecated and HDR is not
- supported in it;
- * because in flip-discard mode partial updates from
- SwapChain11::present() are not supported and return an error,
- which is never checked :)
-
-6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because
- flip modes are not available on Windows 7 and such old systems.
-
-Notes:
-
-eglCreatePixmapSurface() is not implemented in Angle, so the support is
-not added.
-
-eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface()
-do not have support for color spaces according to the extension wording
-(and they are also not supported by Angle :) )
-
-Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6
----
- src/3rdparty/angle/src/common/platform.h | 14 +-
- src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +-
- src/3rdparty/angle/src/libANGLE/Caps.h | 9 +
- .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +-
- .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +-
- .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 +
- .../renderer/d3d/d3d11/Renderer11.cpp | 16 +-
- .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +-
- .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++-
- .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +-
- .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +-
- .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +-
- .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +-
- .../angle/src/libANGLE/validationEGL.cpp | 53 ++
- ...-surface-color-space-selection-in-An.patch | 596 ++++++++++++++++++
- 15 files changed, 831 insertions(+), 20 deletions(-)
- create mode 100644 src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch
-
-diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h
-index fb251da579..2e17994557 100644
---- a/src/3rdparty/angle/src/common/platform.h
-+++ b/src/3rdparty/angle/src/common/platform.h
-@@ -59,12 +59,14 @@
- # endif
-
- # if defined(ANGLE_ENABLE_D3D11)
--#include <d3d10_1.h>
--#include <d3d11.h>
--#include <d3d11_3.h>
--#include <d3dcompiler.h>
--#include <dxgi.h>
--#include <dxgi1_2.h>
-+# include <d3d10_1.h>
-+# include <d3d11.h>
-+# include <dxgi.h>
-+# include <d3d11_1.h>
-+# include <d3d11_3.h>
-+# include <dxgi1_2.h>
-+# include <dxgi1_4.h> // WARNING: This is actually D3D12!
-+# include <d3dcompiler.h>
- # endif
-
- #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11)
-diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp
-index 44da2bbe27..2088457458 100644
---- a/src/3rdparty/angle/src/libANGLE/Caps.cpp
-+++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp
-@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions()
- displayTextureShareGroup(false),
- createContextClientArrays(false),
- programCacheControl(false),
-- robustResourceInitialization(false)
-+ robustResourceInitialization(false),
-+ colorspaceSRGB(false),
-+ colorspaceSCRGBLinear(false),
-+ colorspaceBt2020PQ(false)
- {
- }
-
-@@ -1146,6 +1149,9 @@ std::vector<std::string> DisplayExtensions::getStrings() const
- InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings);
- InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings);
- InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings);
-+ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings);
-+ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings);
-+ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings);
- // TODO(jmadill): Enable this when complete.
- //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings);
- // clang-format on
-diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h
-index 64bdf97112..8157af5800 100644
---- a/src/3rdparty/angle/src/libANGLE/Caps.h
-+++ b/src/3rdparty/angle/src/libANGLE/Caps.h
-@@ -692,6 +692,15 @@ struct DisplayExtensions
-
- // EGL_ANGLE_robust_resource_initialization
- bool robustResourceInitialization;
-+
-+ // EGL_KHR_gl_colorspace
-+ bool colorspaceSRGB;
-+
-+ // EGL_EXT_gl_colorspace_scrgb_linear
-+ bool colorspaceSCRGBLinear;
-+
-+ // EGL_EXT_gl_colorspace_bt2020_pq
-+ bool colorspaceBt2020PQ;
- };
-
- struct DeviceExtensions
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
-index dcc98f2ec6..b8ee635625 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
-@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize
- GLenum backBufferFormat,
- GLenum depthBufferFormat,
- EGLint orientation,
-- EGLint samples) = 0;
-+ EGLint samples,
-+ EGLint colorSpace) = 0;
- virtual egl::Error getD3DTextureInfo(const egl::Config *configuration,
- IUnknown *d3dTexture,
- EGLint *width,
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
-index 7657aef79e..efd4dd1a24 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
-@@ -22,6 +22,27 @@
- namespace rx
- {
-
-+GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace)
-+{
-+ GLenum result = baseFormat;
-+
-+ /**
-+ * If sRGB extension is supported, we should change the surface format
-+ * to a specific one that does support automated gamma conversion.
-+ *
-+ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of
-+ * textures in this format technically is not supported!
-+ */
-+ if (display->getExtensions().colorspaceSRGB &&
-+ baseFormat == GL_RGBA8_OES &&
-+ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR)
-+ {
-+ result = GL_SRGB8_ALPHA8;
-+ }
-+
-+ return result;
-+}
-+
- SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
- RendererD3D *renderer,
- egl::Display *display,
-@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
- mDisplay(display),
- mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE),
- mOrientation(static_cast<EGLint>(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))),
-- mRenderTargetFormat(state.config->renderTargetFormat),
-+ mColorSpace(static_cast<EGLint>(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))),
-+ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)),
- mDepthStencilFormat(state.config->depthStencilFormat),
- mSwapChain(nullptr),
- mSwapIntervalDirty(true),
-@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display)
-
- mSwapChain =
- mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat,
-- mDepthStencilFormat, mOrientation, mState.config->samples);
-+ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace);
- if (!mSwapChain)
- {
- return egl::EglBadAlloc();
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
-index 01d2573244..ccb793d423 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
-@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl
-
- bool mFixedSize;
- GLint mOrientation;
-+ EGLint mColorSpace;
-
- GLenum mRenderTargetFormat;
- GLenum mDepthStencilFormat;
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
-index b0ef9abddc..f0e497b52f 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
-@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display)
- mRenderer11DeviceCaps.supportsConstantBufferOffsets = false;
- mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false;
- mRenderer11DeviceCaps.supportsDXGI1_2 = false;
-+ mRenderer11DeviceCaps.supportsDXGI1_4 = false;
- mRenderer11DeviceCaps.B5G6R5support = 0;
- mRenderer11DeviceCaps.B4G4R4A4support = 0;
- mRenderer11DeviceCaps.B5G5R5A1support = 0;
-@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice()
-
- // Gather stats on DXGI and D3D feature level
- ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2);
-+ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4);
-
- ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel);
-
-@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps()
- IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject<IDXGIAdapter2>(mDxgiAdapter);
- mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr);
- SafeRelease(dxgiAdapter2);
-+
-+ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject<IDXGIAdapter3>(mDxgiAdapter);
-+ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr);
-+ SafeRelease(dxgiAdapter3);
- }
-
- gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig(
-@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions
-
- // All D3D feature levels support robust resource init
- outExtensions->robustResourceInitialization = true;
-+
-+ // color space selection supported in DXGI 1.4 only
-+ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4;
-+ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4;
-+ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4;
- }
-
- gl::Error Renderer11::flush()
-@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow,
- GLenum backBufferFormat,
- GLenum depthBufferFormat,
- EGLint orientation,
-- EGLint samples)
-+ EGLint samples,
-+ EGLint colorSpace)
- {
- return new SwapChain11(this, GetAs<NativeWindow11>(nativeWindow), shareHandle, d3dTexture,
-- backBufferFormat, depthBufferFormat, orientation, samples);
-+ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace);
- }
-
- void *Renderer11::getD3DDevice()
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
-index a8c24e681b..3516bf779d 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
-@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps
-
- D3D_FEATURE_LEVEL featureLevel;
- bool supportsDXGI1_2; // Support for DXGI 1.2
-+ bool supportsDXGI1_4; // Support for DXGI 1.4
- bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView
- bool supportsConstantBufferOffsets; // Support for Constant buffer offset
- bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader
-@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D
- GLenum backBufferFormat,
- GLenum depthBufferFormat,
- EGLint orientation,
-- EGLint samples) override;
-+ EGLint samples,
-+ EGLint colorSpace) override;
- egl::Error getD3DTextureInfo(const egl::Config *configuration,
- IUnknown *d3dTexture,
- EGLint *width,
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
-index dcfd06484d..fc967b90d0 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
-@@ -18,6 +18,11 @@
- #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"
- #include "third_party/trace_event/trace_event.h"
-
-+#if 0
-+// used only for HDR metadata configuration options
-+#include <dxgi1_5.h>
-+#endif
-+
- // Precompiled shaders
- #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h"
- #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h"
-@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer,
- GLenum backBufferFormat,
- GLenum depthBufferFormat,
- EGLint orientation,
-- EGLint samples)
-+ EGLint samples,
-+ EGLint colorSpace)
- : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat),
- mRenderer(renderer),
- mWidth(-1),
- mHeight(-1),
- mOrientation(orientation),
-+ mColorSpace(colorSpace),
- mAppCreatedShareHandle(mShareHandle != nullptr),
- mSwapInterval(0),
- mPassThroughResourcesInit(false),
-@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context,
- mSwapChain1 = d3d11::DynamicCastComObject<IDXGISwapChain1>(mSwapChain);
- }
-
-+ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4)
-+ {
-+ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject<IDXGISwapChain3>(mSwapChain);
-+
-+ printf("*** EGL colorSpace: 0x%X\n", mColorSpace);
-+ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat);
-+ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat());
-+
-+ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) {
-+ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
-+ switch (mColorSpace)
-+ {
-+ case EGL_GL_COLORSPACE_SRGB_KHR:
-+ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
-+ break;
-+ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
-+ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
-+ break;
-+ case EGL_GL_COLORSPACE_BT2020_PQ_EXT:
-+ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
-+ break;
-+ default:
-+ ASSERT(0 && "Unsupported colorspace requested");
-+ }
-+
-+ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace);
-+
-+ UINT supported = 0;
-+ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported);
-+ ASSERT(SUCCEEDED(result));
-+ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
-+ SafeRelease(swapChain3);
-+ return EGL_BAD_MATCH;
-+ }
-+
-+ result = swapChain3->SetColorSpace1(nativeColorSpace);
-+ ASSERT(SUCCEEDED(result));
-+ }
-+
-+ SafeRelease(swapChain3);
-+
-+#if 0
-+
-+ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject<IDXGISwapChain4>(mSwapChain);
-+
-+ DXGI_HDR_METADATA_HDR10 md;
-+ md.RedPrimary[0] = 0.680 * 50000;
-+ md.RedPrimary[1] = 0.320 * 50000;
-+ md.GreenPrimary[0] = 0.265 * 50000;
-+ md.GreenPrimary[1] = 0.690 * 50000;
-+ md.BluePrimary[0] = 0.150 * 50000;
-+ md.BluePrimary[1] = 0.060 * 50000;
-+ md.WhitePoint[0] = 0.3127 * 50000;
-+ md.WhitePoint[1] = 0.3290 * 50000;
-+ md.MaxMasteringLuminance = 1000 * 10000;
-+ md.MinMasteringLuminance = 0.001 * 10000;
-+ md.MaxContentLightLevel = 1000;
-+ md.MaxFrameAverageLightLevel = 200;
-+ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md);
-+ printf("*** Result hdr 0x%X\n", result);
-+ SafeRelease(swapChain4);
-+#endif
-+ }
-+
- ID3D11Texture2D *backbufferTex = nullptr;
- result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
- reinterpret_cast<LPVOID *>(&backbufferTex));
- ASSERT(SUCCEEDED(result));
-+
-+ // TODO: recover rendering to sRGB
-+ //
-+ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc;
-+ // offscreenRTVDesc.Format = getSwapChainNativeFormat();
-+ //
-+ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) {
-+ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) {
-+ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
-+ // }
-+ //
-+ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) {
-+ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
-+ // }
-+ // }
-+ //
-+ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format);
-+
- const auto &format =
- d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps());
- mBackBufferTexture.set(backbufferTex, format);
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
-index eca068210b..2a4b9ba274 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
-@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D
- GLenum backBufferFormat,
- GLenum depthBufferFormat,
- EGLint orientation,
-- EGLint samples);
-+ EGLint samples,
-+ EGLint colorSpace);
- ~SwapChain11() override;
-
- EGLint resize(const gl::Context *context,
-@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D
- EGLint mWidth;
- EGLint mHeight;
- const EGLint mOrientation;
-+ EGLint mColorSpace;
- bool mAppCreatedShareHandle;
- unsigned int mSwapInterval;
- bool mPassThroughResourcesInit;
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
-index 5394e3d3e7..af52c41d00 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
-@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
-
- // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a
- // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain.
-+ //
-+ // NOTE: in non-flip mode HDR rendering is not supported, so use it
-+ // by default
- IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject<IDXGIFactory2>(factory);
- if (factory2 != nullptr)
- {
-@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
- swapChainDesc.SampleDesc.Quality = 0;
- swapChainDesc.BufferUsage =
- DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER;
-- swapChainDesc.BufferCount = 1;
-+ swapChainDesc.BufferCount = 2;
- swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
-- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
-+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
- swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
- swapChainDesc.Flags = 0;
- IDXGISwapChain1 *swapChain1 = nullptr;
-@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
- }
-
- DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
-- swapChainDesc.BufferCount = 1;
-+ swapChainDesc.BufferCount = 2;
- swapChainDesc.BufferDesc.Format = format;
- swapChainDesc.BufferDesc.Width = width;
- swapChainDesc.BufferDesc.Height = height;
-@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
- swapChainDesc.SampleDesc.Count = samples;
- swapChainDesc.SampleDesc.Quality = 0;
- swapChainDesc.Windowed = TRUE;
-+
-+ /**
-+ * NOTE1: in discard mode the swap chain doesn't support partial
-+ * presentatiopn with Present1() call. Though it is not a big
-+ * problem, because in case DXGI 1.2 is supported this code is
-+ * unreachable.
-+ *
-+ * NOTE2: Flip modes are not supported on Windows 7 and the like,
-+ * so use a legacy mode as a fallback
-+ */
- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
-
- HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain);
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
-index b583273641..8e158f6432 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
-@@ -719,8 +719,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow,
- GLenum backBufferFormat,
- GLenum depthBufferFormat,
- EGLint orientation,
-- EGLint samples)
-+ EGLint samples,
-+ EGLint colorSpace)
- {
-+ UNUSED_VARIABLE(colorSpace);
- return new SwapChain9(this, GetAs<NativeWindow9>(nativeWindow), shareHandle, d3dTexture,
- backBufferFormat, depthBufferFormat, orientation);
- }
-diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
-index 9ddee45f0f..ce4bb201e5 100644
---- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
-+++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
-@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D
- GLenum backBufferFormat,
- GLenum depthBufferFormat,
- EGLint orientation,
-- EGLint samples) override;
-+ EGLint samples,
-+ EGLint colorSpace) override;
- egl::Error getD3DTextureInfo(const egl::Config *configuration,
- IUnknown *d3dTexture,
- EGLint *width,
-diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
-index 13a3a9e280..858d7ee929 100644
---- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
-+++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
-@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin
- "either EGL_TRUE or EGL_FALSE.";
- }
- break;
-+ case EGL_GL_COLORSPACE:
-+
-+ if (!displayExtensions.colorspaceSRGB)
-+ {
-+ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
-+ }
-+
-+ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
-+ {
-+ if (!displayExtensions.colorspaceSCRGBLinear)
-+ {
-+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
-+ }
-+ }
-+ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
-+ {
-+ if (!displayExtensions.colorspaceBt2020PQ)
-+ {
-+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
-+ }
-+ }
-+ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
-+ {
-+ return EglBadAttribute() << "Unknown EGL color space requested";
-+ }
-+ break;
-
- default:
- return EglBadAttribute();
-@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri
- }
- break;
-
-+ case EGL_GL_COLORSPACE:
-+
-+ if (!displayExtensions.colorspaceSRGB)
-+ {
-+ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
-+ }
-+
-+ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
-+ {
-+ if (!displayExtensions.colorspaceSCRGBLinear)
-+ {
-+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
-+ }
-+ }
-+ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
-+ {
-+ if (!displayExtensions.colorspaceBt2020PQ)
-+ {
-+ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
-+ }
-+ }
-+ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
-+ {
-+ return EglBadAttribute() << "Unknown EGL color space requested";
-+ }
-+ break;
-+
- default:
- return EglBadAttribute();
- }
-diff --git a/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch
-new file mode 100644
-index 0000000000..dfbe362690
---- /dev/null
-+++ b/src/angle/patches/0013-Implement-openGL-surface-color-space-selection-in-An.patch
-@@ -0,0 +1,596 @@
-+From 05082a2affad3428e2ba4475a5c083e81a7730ab Mon Sep 17 00:00:00 2001
-+From: Dmitry Kazakov <dimula73@gmail.com>
-+Date: Sat, 8 Dec 2018 15:35:43 +0300
-+Subject: [PATCH] Implement openGL surface color space selection in Angle
-+
-+WARNING: this patch actually means that the library must be build on
-+ the system with at least DXGI 1.4 (DirectX 12 API) present
-+ in SDK. Mingw64 7.3 supports that.
-+
-+1) D3D11 implementation of angle now supports GL_RGB10_A2 format
-+
-+2) Technically, Angle's EGL implementation now supports the following
-+ display extensions:
-+ * EGL_KHR_gl_colorspace
-+ * EGL_EXT_gl_colorspace_scrgb_linear
-+ * EGL_EXT_gl_colorspace_bt2020_pq
-+
-+3) D3D11 implementation of angle now supports GL_COLOR_SPACE attribute,
-+ which allows selection one of four color modes:
-+ * Linear --- just pass-through data to GPU
-+ * sRGB --- p709-g22 color space. WARNING: in 8-bit mode the system
-+ becomes clever and automatically converts linear framebuffer
-+ attachments to sRGB space, as per EGL_KHR_gl_colorspace definition.
-+ It is not possible to select sRGB without this extra "feature".
-+ * scRGB --- p709-g10 color space. This mode is the only mode
-+ supported in f16-bit mode (and it is also not supported in other
-+ bit depths).
-+ * bt2020-pq --- p2020-pq color space. Supported only in 10-bit mode.
-+
-+5) SwapChain is now created in DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL mode:
-+ * because non-flip mode is considered deprecated and HDR is not
-+ supported in it;
-+ * because in flip-discard mode partial updates from
-+ SwapChain11::present() are not supported and return an error,
-+ which is never checked :)
-+
-+6) As a fallback, SwapChain uses old DXGI_SWAP_EFFECT_DISCARD, because
-+ flip modes are not available on Windows 7 and such old systems.
-+
-+Notes:
-+
-+eglCreatePixmapSurface() is not implemented in Angle, so the support is
-+not added.
-+
-+eglCreatePlatformWindowSurface() and eglCreatePlatformPixmapSurface()
-+do not have support for color spaces according to the extension wording
-+(and they are also not supported by Angle :) )
-+
-+Change-Id: I68204a5db6bbd7066a83a8d1d021ce76cd1cf6f6
-+---
-+ src/3rdparty/angle/src/common/platform.h | 14 +--
-+ src/3rdparty/angle/src/libANGLE/Caps.cpp | 8 +-
-+ src/3rdparty/angle/src/libANGLE/Caps.h | 9 ++
-+ .../src/libANGLE/renderer/d3d/RendererD3D.h | 3 +-
-+ .../src/libANGLE/renderer/d3d/SurfaceD3D.cpp | 26 +++++-
-+ .../src/libANGLE/renderer/d3d/SurfaceD3D.h | 1 +
-+ .../renderer/d3d/d3d11/Renderer11.cpp | 16 +++-
-+ .../libANGLE/renderer/d3d/d3d11/Renderer11.h | 4 +-
-+ .../renderer/d3d/d3d11/SwapChain11.cpp | 91 ++++++++++++++++++-
-+ .../libANGLE/renderer/d3d/d3d11/SwapChain11.h | 4 +-
-+ .../d3d/d3d11/win32/NativeWindow11Win32.cpp | 19 +++-
-+ .../libANGLE/renderer/d3d/d3d9/Renderer9.cpp | 4 +-
-+ .../libANGLE/renderer/d3d/d3d9/Renderer9.h | 3 +-
-+ .../angle/src/libANGLE/validationEGL.cpp | 53 +++++++++++
-+ 14 files changed, 235 insertions(+), 20 deletions(-)
-+
-+diff --git a/src/3rdparty/angle/src/common/platform.h b/src/3rdparty/angle/src/common/platform.h
-+index fb251da579..2e17994557 100644
-+--- a/src/3rdparty/angle/src/common/platform.h
-++++ b/src/3rdparty/angle/src/common/platform.h
-+@@ -59,12 +59,14 @@
-+ # endif
-+
-+ # if defined(ANGLE_ENABLE_D3D11)
-+-#include <d3d10_1.h>
-+-#include <d3d11.h>
-+-#include <d3d11_3.h>
-+-#include <d3dcompiler.h>
-+-#include <dxgi.h>
-+-#include <dxgi1_2.h>
-++# include <d3d10_1.h>
-++# include <d3d11.h>
-++# include <dxgi.h>
-++# include <d3d11_1.h>
-++# include <d3d11_3.h>
-++# include <dxgi1_2.h>
-++# include <dxgi1_4.h> // WARNING: This is actually D3D12!
-++# include <d3dcompiler.h>
-+ # endif
-+
-+ #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11)
-+diff --git a/src/3rdparty/angle/src/libANGLE/Caps.cpp b/src/3rdparty/angle/src/libANGLE/Caps.cpp
-+index 44da2bbe27..2088457458 100644
-+--- a/src/3rdparty/angle/src/libANGLE/Caps.cpp
-++++ b/src/3rdparty/angle/src/libANGLE/Caps.cpp
-+@@ -1101,7 +1101,10 @@ DisplayExtensions::DisplayExtensions()
-+ displayTextureShareGroup(false),
-+ createContextClientArrays(false),
-+ programCacheControl(false),
-+- robustResourceInitialization(false)
-++ robustResourceInitialization(false),
-++ colorspaceSRGB(false),
-++ colorspaceSCRGBLinear(false),
-++ colorspaceBt2020PQ(false)
-+ {
-+ }
-+
-+@@ -1146,6 +1149,9 @@ std::vector<std::string> DisplayExtensions::getStrings() const
-+ InsertExtensionString("EGL_ANGLE_create_context_client_arrays", createContextClientArrays, &extensionStrings);
-+ InsertExtensionString("EGL_ANGLE_program_cache_control", programCacheControl, &extensionStrings);
-+ InsertExtensionString("EGL_ANGLE_robust_resource_initialization", robustResourceInitialization, &extensionStrings);
-++ InsertExtensionString("EGL_KHR_gl_colorspace", colorspaceSRGB, &extensionStrings);
-++ InsertExtensionString("EGL_EXT_gl_colorspace_scrgb_linear", colorspaceSCRGBLinear, &extensionStrings);
-++ InsertExtensionString("EGL_EXT_gl_colorspace_bt2020_pq", colorspaceBt2020PQ, &extensionStrings);
-+ // TODO(jmadill): Enable this when complete.
-+ //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings);
-+ // clang-format on
-+diff --git a/src/3rdparty/angle/src/libANGLE/Caps.h b/src/3rdparty/angle/src/libANGLE/Caps.h
-+index 64bdf97112..8157af5800 100644
-+--- a/src/3rdparty/angle/src/libANGLE/Caps.h
-++++ b/src/3rdparty/angle/src/libANGLE/Caps.h
-+@@ -692,6 +692,15 @@ struct DisplayExtensions
-+
-+ // EGL_ANGLE_robust_resource_initialization
-+ bool robustResourceInitialization;
-++
-++ // EGL_KHR_gl_colorspace
-++ bool colorspaceSRGB;
-++
-++ // EGL_EXT_gl_colorspace_scrgb_linear
-++ bool colorspaceSCRGBLinear;
-++
-++ // EGL_EXT_gl_colorspace_bt2020_pq
-++ bool colorspaceBt2020PQ;
-+ };
-+
-+ struct DeviceExtensions
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
-+index dcc98f2ec6..b8ee635625 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/RendererD3D.h
-+@@ -130,7 +130,8 @@ class RendererD3D : public BufferFactoryD3D, public MultisampleTextureInitialize
-+ GLenum backBufferFormat,
-+ GLenum depthBufferFormat,
-+ EGLint orientation,
-+- EGLint samples) = 0;
-++ EGLint samples,
-++ EGLint colorSpace) = 0;
-+ virtual egl::Error getD3DTextureInfo(const egl::Config *configuration,
-+ IUnknown *d3dTexture,
-+ EGLint *width,
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
-+index 7657aef79e..efd4dd1a24 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
-+@@ -22,6 +22,27 @@
-+ namespace rx
-+ {
-+
-++GLenum renderTargetFormatFromColorSpace(egl::Display *display, GLenum baseFormat, EGLint colorSpace)
-++{
-++ GLenum result = baseFormat;
-++
-++ /**
-++ * If sRGB extension is supported, we should change the surface format
-++ * to a specific one that does support automated gamma conversion.
-++ *
-++ * TODO: openGL doesn't support BGRA-sRGB texture format, so creation of
-++ * textures in this format technically is not supported!
-++ */
-++ if (display->getExtensions().colorspaceSRGB &&
-++ baseFormat == GL_RGBA8_OES &&
-++ colorSpace == EGL_GL_COLORSPACE_SRGB_KHR)
-++ {
-++ result = GL_SRGB8_ALPHA8;
-++ }
-++
-++ return result;
-++}
-++
-+ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
-+ RendererD3D *renderer,
-+ egl::Display *display,
-+@@ -34,7 +55,8 @@ SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
-+ mDisplay(display),
-+ mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE),
-+ mOrientation(static_cast<EGLint>(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))),
-+- mRenderTargetFormat(state.config->renderTargetFormat),
-++ mColorSpace(static_cast<EGLint>(attribs.get(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR))),
-++ mRenderTargetFormat(renderTargetFormatFromColorSpace(display, state.config->renderTargetFormat, mColorSpace)),
-+ mDepthStencilFormat(state.config->depthStencilFormat),
-+ mSwapChain(nullptr),
-+ mSwapIntervalDirty(true),
-+@@ -148,7 +170,7 @@ egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display)
-+
-+ mSwapChain =
-+ mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat,
-+- mDepthStencilFormat, mOrientation, mState.config->samples);
-++ mDepthStencilFormat, mOrientation, mState.config->samples, mColorSpace);
-+ if (!mSwapChain)
-+ {
-+ return egl::EglBadAlloc();
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
-+index 01d2573244..ccb793d423 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
-+@@ -90,6 +90,7 @@ class SurfaceD3D : public SurfaceImpl
-+
-+ bool mFixedSize;
-+ GLint mOrientation;
-++ EGLint mColorSpace;
-+
-+ GLenum mRenderTargetFormat;
-+ GLenum mDepthStencilFormat;
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
-+index b0ef9abddc..f0e497b52f 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
-+@@ -465,6 +465,7 @@ Renderer11::Renderer11(egl::Display *display)
-+ mRenderer11DeviceCaps.supportsConstantBufferOffsets = false;
-+ mRenderer11DeviceCaps.supportsVpRtIndexWriteFromVertexShader = false;
-+ mRenderer11DeviceCaps.supportsDXGI1_2 = false;
-++ mRenderer11DeviceCaps.supportsDXGI1_4 = false;
-+ mRenderer11DeviceCaps.B5G6R5support = 0;
-+ mRenderer11DeviceCaps.B4G4R4A4support = 0;
-+ mRenderer11DeviceCaps.B5G5R5A1support = 0;
-+@@ -918,6 +919,7 @@ egl::Error Renderer11::initializeDevice()
-+
-+ // Gather stats on DXGI and D3D feature level
-+ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_2", mRenderer11DeviceCaps.supportsDXGI1_2);
-++ ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.SupportsDXGI1_4", mRenderer11DeviceCaps.supportsDXGI1_4);
-+
-+ ANGLEFeatureLevel angleFeatureLevel = GetANGLEFeatureLevel(mRenderer11DeviceCaps.featureLevel);
-+
-+@@ -1002,6 +1004,10 @@ void Renderer11::populateRenderer11DeviceCaps()
-+ IDXGIAdapter2 *dxgiAdapter2 = d3d11::DynamicCastComObject<IDXGIAdapter2>(mDxgiAdapter);
-+ mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr);
-+ SafeRelease(dxgiAdapter2);
-++
-++ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject<IDXGIAdapter3>(mDxgiAdapter);
-++ mRenderer11DeviceCaps.supportsDXGI1_4 = (dxgiAdapter3 != nullptr);
-++ SafeRelease(dxgiAdapter3);
-+ }
-+
-+ gl::SupportedSampleSet Renderer11::generateSampleSetForEGLConfig(
-+@@ -1241,6 +1247,11 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions
-+
-+ // All D3D feature levels support robust resource init
-+ outExtensions->robustResourceInitialization = true;
-++
-++ // color space selection supported in DXGI 1.4 only
-++ outExtensions->colorspaceSRGB = mRenderer11DeviceCaps.supportsDXGI1_4;
-++ outExtensions->colorspaceSCRGBLinear = mRenderer11DeviceCaps.supportsDXGI1_4;
-++ outExtensions->colorspaceBt2020PQ = mRenderer11DeviceCaps.supportsDXGI1_4;
-+ }
-+
-+ gl::Error Renderer11::flush()
-+@@ -1436,10 +1447,11 @@ SwapChainD3D *Renderer11::createSwapChain(NativeWindowD3D *nativeWindow,
-+ GLenum backBufferFormat,
-+ GLenum depthBufferFormat,
-+ EGLint orientation,
-+- EGLint samples)
-++ EGLint samples,
-++ EGLint colorSpace)
-+ {
-+ return new SwapChain11(this, GetAs<NativeWindow11>(nativeWindow), shareHandle, d3dTexture,
-+- backBufferFormat, depthBufferFormat, orientation, samples);
-++ backBufferFormat, depthBufferFormat, orientation, samples, colorSpace);
-+ }
-+
-+ void *Renderer11::getD3DDevice()
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
-+index a8c24e681b..3516bf779d 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
-+@@ -49,6 +49,7 @@ struct Renderer11DeviceCaps
-+
-+ D3D_FEATURE_LEVEL featureLevel;
-+ bool supportsDXGI1_2; // Support for DXGI 1.2
-++ bool supportsDXGI1_4; // Support for DXGI 1.4
-+ bool supportsClearView; // Support for ID3D11DeviceContext1::ClearView
-+ bool supportsConstantBufferOffsets; // Support for Constant buffer offset
-+ bool supportsVpRtIndexWriteFromVertexShader; // VP/RT can be selected in the Vertex Shader
-+@@ -138,7 +139,8 @@ class Renderer11 : public RendererD3D
-+ GLenum backBufferFormat,
-+ GLenum depthBufferFormat,
-+ EGLint orientation,
-+- EGLint samples) override;
-++ EGLint samples,
-++ EGLint colorSpace) override;
-+ egl::Error getD3DTextureInfo(const egl::Config *configuration,
-+ IUnknown *d3dTexture,
-+ EGLint *width,
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
-+index dcfd06484d..fc967b90d0 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
-+@@ -18,6 +18,11 @@
-+ #include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"
-+ #include "third_party/trace_event/trace_event.h"
-+
-++#if 0
-++// used only for HDR metadata configuration options
-++#include <dxgi1_5.h>
-++#endif
-++
-+ // Precompiled shaders
-+ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthrough2d11vs.h"
-+ #include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h"
-+@@ -56,12 +61,14 @@ SwapChain11::SwapChain11(Renderer11 *renderer,
-+ GLenum backBufferFormat,
-+ GLenum depthBufferFormat,
-+ EGLint orientation,
-+- EGLint samples)
-++ EGLint samples,
-++ EGLint colorSpace)
-+ : SwapChainD3D(shareHandle, d3dTexture, backBufferFormat, depthBufferFormat),
-+ mRenderer(renderer),
-+ mWidth(-1),
-+ mHeight(-1),
-+ mOrientation(orientation),
-++ mColorSpace(colorSpace),
-+ mAppCreatedShareHandle(mShareHandle != nullptr),
-+ mSwapInterval(0),
-+ mPassThroughResourcesInit(false),
-+@@ -620,10 +627,92 @@ EGLint SwapChain11::reset(const gl::Context *context,
-+ mSwapChain1 = d3d11::DynamicCastComObject<IDXGISwapChain1>(mSwapChain);
-+ }
-+
-++ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4)
-++ {
-++ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject<IDXGISwapChain3>(mSwapChain);
-++
-++ printf("*** EGL colorSpace: 0x%X\n", mColorSpace);
-++ printf("*** EGL format: 0x%X\n", mOffscreenRenderTargetFormat);
-++ printf("*** Native format: 0x%X\n", getSwapChainNativeFormat());
-++
-++ if (mColorSpace != EGL_GL_COLORSPACE_LINEAR_KHR) {
-++ DXGI_COLOR_SPACE_TYPE nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
-++ switch (mColorSpace)
-++ {
-++ case EGL_GL_COLORSPACE_SRGB_KHR:
-++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
-++ break;
-++ case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
-++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
-++ break;
-++ case EGL_GL_COLORSPACE_BT2020_PQ_EXT:
-++ nativeColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
-++ break;
-++ default:
-++ ASSERT(0 && "Unsupported colorspace requested");
-++ }
-++
-++ printf("*** Native colorSpace: 0x%X\n", nativeColorSpace);
-++
-++ UINT supported = 0;
-++ result = swapChain3->CheckColorSpaceSupport(nativeColorSpace, &supported);
-++ ASSERT(SUCCEEDED(result));
-++ if (!(supported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
-++ SafeRelease(swapChain3);
-++ return EGL_BAD_MATCH;
-++ }
-++
-++ result = swapChain3->SetColorSpace1(nativeColorSpace);
-++ ASSERT(SUCCEEDED(result));
-++ }
-++
-++ SafeRelease(swapChain3);
-++
-++#if 0
-++
-++ IDXGISwapChain4 *swapChain4 = d3d11::DynamicCastComObject<IDXGISwapChain4>(mSwapChain);
-++
-++ DXGI_HDR_METADATA_HDR10 md;
-++ md.RedPrimary[0] = 0.680 * 50000;
-++ md.RedPrimary[1] = 0.320 * 50000;
-++ md.GreenPrimary[0] = 0.265 * 50000;
-++ md.GreenPrimary[1] = 0.690 * 50000;
-++ md.BluePrimary[0] = 0.150 * 50000;
-++ md.BluePrimary[1] = 0.060 * 50000;
-++ md.WhitePoint[0] = 0.3127 * 50000;
-++ md.WhitePoint[1] = 0.3290 * 50000;
-++ md.MaxMasteringLuminance = 1000 * 10000;
-++ md.MinMasteringLuminance = 0.001 * 10000;
-++ md.MaxContentLightLevel = 1000;
-++ md.MaxFrameAverageLightLevel = 200;
-++ result = swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(md), &md);
-++ printf("*** Result hdr 0x%X\n", result);
-++ SafeRelease(swapChain4);
-++#endif
-++ }
-++
-+ ID3D11Texture2D *backbufferTex = nullptr;
-+ result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
-+ reinterpret_cast<LPVOID *>(&backbufferTex));
-+ ASSERT(SUCCEEDED(result));
-++
-++ // TODO: recover rendering to sRGB
-++ //
-++ // D3D11_RENDER_TARGET_VIEW_DESC offscreenRTVDesc;
-++ // offscreenRTVDesc.Format = getSwapChainNativeFormat();
-++ //
-++ // if (mColorSpace == EGL_GL_COLORSPACE_SRGB_KHR) {
-++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM) {
-++ // offscreenRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
-++ // }
-++ //
-++ // if (offscreenRTVDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM) {
-++ // offscreenRTVDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
-++ // }
-++ // }
-++ //
-++ // printf("*** Render target format: 0x%X\n", offscreenRTVDesc.Format);
-++
-+ const auto &format =
-+ d3d11::Format::Get(mOffscreenRenderTargetFormat, mRenderer->getRenderer11DeviceCaps());
-+ mBackBufferTexture.set(backbufferTex, format);
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
-+index eca068210b..2a4b9ba274 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
-+@@ -28,7 +28,8 @@ class SwapChain11 final : public SwapChainD3D
-+ GLenum backBufferFormat,
-+ GLenum depthBufferFormat,
-+ EGLint orientation,
-+- EGLint samples);
-++ EGLint samples,
-++ EGLint colorSpace);
-+ ~SwapChain11() override;
-+
-+ EGLint resize(const gl::Context *context,
-+@@ -93,6 +94,7 @@ class SwapChain11 final : public SwapChainD3D
-+ EGLint mWidth;
-+ EGLint mHeight;
-+ const EGLint mOrientation;
-++ EGLint mColorSpace;
-+ bool mAppCreatedShareHandle;
-+ unsigned int mSwapInterval;
-+ bool mPassThroughResourcesInit;
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
-+index 5394e3d3e7..af52c41d00 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
-+@@ -146,6 +146,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
-+
-+ // Use IDXGIFactory2::CreateSwapChainForHwnd if DXGI 1.2 is available to create a
-+ // DXGI_SWAP_EFFECT_SEQUENTIAL swap chain.
-++ //
-++ // NOTE: in non-flip mode HDR rendering is not supported, so use it
-++ // by default
-+ IDXGIFactory2 *factory2 = d3d11::DynamicCastComObject<IDXGIFactory2>(factory);
-+ if (factory2 != nullptr)
-+ {
-+@@ -158,9 +161,9 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
-+ swapChainDesc.SampleDesc.Quality = 0;
-+ swapChainDesc.BufferUsage =
-+ DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER;
-+- swapChainDesc.BufferCount = 1;
-++ swapChainDesc.BufferCount = 2;
-+ swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
-+- swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
-++ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
-+ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
-+ swapChainDesc.Flags = 0;
-+ IDXGISwapChain1 *swapChain1 = nullptr;
-+@@ -176,7 +179,7 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
-+ }
-+
-+ DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
-+- swapChainDesc.BufferCount = 1;
-++ swapChainDesc.BufferCount = 2;
-+ swapChainDesc.BufferDesc.Format = format;
-+ swapChainDesc.BufferDesc.Width = width;
-+ swapChainDesc.BufferDesc.Height = height;
-+@@ -191,6 +194,16 @@ HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
-+ swapChainDesc.SampleDesc.Count = samples;
-+ swapChainDesc.SampleDesc.Quality = 0;
-+ swapChainDesc.Windowed = TRUE;
-++
-++ /**
-++ * NOTE1: in discard mode the swap chain doesn't support partial
-++ * presentatiopn with Present1() call. Though it is not a big
-++ * problem, because in case DXGI 1.2 is supported this code is
-++ * unreachable.
-++ *
-++ * NOTE2: Flip modes are not supported on Windows 7 and the like,
-++ * so use a legacy mode as a fallback
-++ */
-+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
-+
-+ HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain);
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
-+index 75c6298868..58596169a8 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
-+@@ -718,8 +718,10 @@ SwapChainD3D *Renderer9::createSwapChain(NativeWindowD3D *nativeWindow,
-+ GLenum backBufferFormat,
-+ GLenum depthBufferFormat,
-+ EGLint orientation,
-+- EGLint samples)
-++ EGLint samples,
-++ EGLint colorSpace)
-+ {
-++ UNUSED_VARIABLE(colorSpace);
-+ return new SwapChain9(this, GetAs<NativeWindow9>(nativeWindow), shareHandle, d3dTexture,
-+ backBufferFormat, depthBufferFormat, orientation);
-+ }
-+diff --git a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
-+index 9ddee45f0f..ce4bb201e5 100644
-+--- a/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
-++++ b/src/3rdparty/angle/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
-+@@ -92,7 +92,8 @@ class Renderer9 : public RendererD3D
-+ GLenum backBufferFormat,
-+ GLenum depthBufferFormat,
-+ EGLint orientation,
-+- EGLint samples) override;
-++ EGLint samples,
-++ EGLint colorSpace) override;
-+ egl::Error getD3DTextureInfo(const egl::Config *configuration,
-+ IUnknown *d3dTexture,
-+ EGLint *width,
-+diff --git a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
-+index 13a3a9e280..858d7ee929 100644
-+--- a/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
-++++ b/src/3rdparty/angle/src/libANGLE/validationEGL.cpp
-+@@ -885,6 +885,32 @@ Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWin
-+ "either EGL_TRUE or EGL_FALSE.";
-+ }
-+ break;
-++ case EGL_GL_COLORSPACE:
-++
-++ if (!displayExtensions.colorspaceSRGB)
-++ {
-++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
-++ }
-++
-++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
-++ {
-++ if (!displayExtensions.colorspaceSCRGBLinear)
-++ {
-++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
-++ }
-++ }
-++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
-++ {
-++ if (!displayExtensions.colorspaceBt2020PQ)
-++ {
-++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
-++ }
-++ }
-++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
-++ {
-++ return EglBadAttribute() << "Unknown EGL color space requested";
-++ }
-++ break;
-+
-+ default:
-+ return EglBadAttribute();
-+@@ -977,6 +1003,33 @@ Error ValidateCreatePbufferSurface(Display *display, Config *config, const Attri
-+ }
-+ break;
-+
-++ case EGL_GL_COLORSPACE:
-++
-++ if (!displayExtensions.colorspaceSRGB)
-++ {
-++ return EglBadAttribute() << "EGL_KHR_gl_colorspace is not supported on this platform.";
-++ }
-++
-++ if (value == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT)
-++ {
-++ if (!displayExtensions.colorspaceSCRGBLinear)
-++ {
-++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_scrgb_linear is not supported on this platform.";
-++ }
-++ }
-++ else if (value == EGL_GL_COLORSPACE_BT2020_PQ_EXT)
-++ {
-++ if (!displayExtensions.colorspaceBt2020PQ)
-++ {
-++ return EglBadAttribute() << "EGL_EXT_gl_colorspace_bt2020_pq is not supported on this platform.";
-++ }
-++ }
-++ else if (value != EGL_GL_COLORSPACE_SRGB_KHR && value != EGL_GL_COLORSPACE_LINEAR_KHR)
-++ {
-++ return EglBadAttribute() << "Unknown EGL color space requested";
-++ }
-++ break;
-++
-+ default:
-+ return EglBadAttribute();
-+ }
-+--
-+2.20.1.windows.1
-+
---
-2.20.1.windows.1
-
-
-From 9442d48679b33ac8e7b3ef7e16b7b13097808f26 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Wed, 13 Feb 2019 16:56:11 +0300
-Subject: [PATCH 2/6] Implement color space selection for QSurfaceFormat
-
-With the patch one can select color space of openGL surface
-which is used a a root surface of the underlying native window.
-
-This feature is needed, e.g. when the user wants to render HDR
-content on screen. In such a case OS should be instructed about
-how to treat the graphical data in the application framebuffer.
-
-The easiest approach is to call QSurfaceFormat::setDefaultFormat()
-before creating the first application window. In such a case the
-root surface will (may) be in the requested format.
-
-Supported color spaces/formats:
-
-1) sRGB, SDR
-2) scRGB (Rec 709, gamma 1.0), HDR
-3) Rec 2020 PQ, HDR
-
-Please take into account that in real life the user should select
-proper bit depth for each color space, otherwise the system will
-refuse to create the surface:
-
-1) sRGB --- 8 bit or 10 bit
-2) scRGB --- 16 bit only
-3) Rec 2020 PQ --- 10 bit only
-
-Please note that color space selection is supported only on
-platforms with DXGI 1.4 and higher.
-
-Change-Id: I5f4945db9798d542f19c8ff1af1effa34f7745fd
----
- src/gui/kernel/qsurfaceformat.cpp | 11 ++++
- src/gui/kernel/qsurfaceformat.h | 4 +-
- src/gui/opengl/qopenglframebufferobject.cpp | 7 ++-
- .../platforms/windows/qwindowseglcontext.cpp | 57 +++++++++++++++++--
- .../platforms/windows/qwindowseglcontext.h | 6 +-
- .../platforms/windows/qwindowsopenglcontext.h | 2 +-
- .../platforms/windows/qwindowswindow.cpp | 8 ++-
- 7 files changed, 83 insertions(+), 12 deletions(-)
-
-diff --git a/src/gui/kernel/qsurfaceformat.cpp b/src/gui/kernel/qsurfaceformat.cpp
-index 1a814ec21f..fc8b9c4f43 100644
---- a/src/gui/kernel/qsurfaceformat.cpp
-+++ b/src/gui/kernel/qsurfaceformat.cpp
-@@ -221,6 +221,17 @@ public:
- set, the window will be created with an sRGB-capable default
- framebuffer. Note that some platforms may return windows with a sRGB-capable
- default framebuffer even when not requested explicitly.
-+
-+ \value scRGBColorSpace When \c{EGL_EXT_gl_colorspace_scrgb_linear}
-+ is supported by the platform and this value is set, the window will
-+ be created with an scRGB-capable default framebuffer. Note that some
-+ platforms may return windows with a scRGB-capable default framebuffer
-+ even when not requested explicitly. It usually happens when the application
-+ requests 16-bit surface format.
-+
-+ \value bt2020PQColorSpace When \c{EGL_EXT_gl_colorspace_bt2020_pq}
-+ is supported by the platform and this value is set, the window will
-+ be created with an bt2020 PQ default framebuffer.
- */
-
- /*!
-diff --git a/src/gui/kernel/qsurfaceformat.h b/src/gui/kernel/qsurfaceformat.h
-index ed63eb8bbf..9ba6a29b7a 100644
---- a/src/gui/kernel/qsurfaceformat.h
-+++ b/src/gui/kernel/qsurfaceformat.h
-@@ -87,7 +87,9 @@ public:
-
- enum ColorSpace {
- DefaultColorSpace,
-- sRGBColorSpace
-+ sRGBColorSpace,
-+ scRGBColorSpace,
-+ bt2020PQColorSpace
- };
- Q_ENUM(ColorSpace)
-
-diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp
-index cae3d516c4..ccdccb637a 100644
---- a/src/gui/opengl/qopenglframebufferobject.cpp
-+++ b/src/gui/opengl/qopenglframebufferobject.cpp
-@@ -545,10 +545,13 @@ void QOpenGLFramebufferObjectPrivate::initTexture(int idx)
- ColorAttachment &color(colorAttachments[idx]);
-
- GLuint pixelType = GL_UNSIGNED_BYTE;
-- if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10)
-+ if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10) {
- pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
-- else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16)
-+ } else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16) {
- pixelType = GL_UNSIGNED_SHORT;
-+ } else if (color.internalFormat == GL_RGBA16F) {
-+ pixelType = GL_HALF_FLOAT;
-+ }
-
- funcs.glTexImage2D(target, 0, color.internalFormat, color.size.width(), color.size.height(), 0,
- GL_RGBA, pixelType, NULL);
-diff --git a/src/plugins/platforms/windows/qwindowseglcontext.cpp b/src/plugins/platforms/windows/qwindowseglcontext.cpp
-index 063e81150e..4cd745eac6 100644
---- a/src/plugins/platforms/windows/qwindowseglcontext.cpp
-+++ b/src/plugins/platforms/windows/qwindowseglcontext.cpp
-@@ -151,8 +151,9 @@ bool QWindowsLibEGL::init()
- eglGetCurrentDisplay = RESOLVE((EGLDisplay (EGLAPIENTRY *)(void)), eglGetCurrentDisplay);
- eglSwapBuffers = RESOLVE((EGLBoolean (EGLAPIENTRY *)(EGLDisplay , EGLSurface)), eglSwapBuffers);
- eglGetProcAddress = RESOLVE((QFunctionPointer (EGLAPIENTRY * )(const char *)), eglGetProcAddress);
-+ eglQueryString = RESOLVE((const char* (EGLAPIENTRY *)(EGLDisplay, EGLint)), eglQueryString);
-
-- if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress)
-+ if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress || !eglQueryString)
- return false;
-
- eglGetPlatformDisplayEXT = nullptr;
-@@ -197,8 +198,15 @@ bool QWindowsLibGLESv2::init()
- }
-
- QWindowsEGLStaticContext::QWindowsEGLStaticContext(EGLDisplay display)
-- : m_display(display)
-+ : m_display(display),
-+ m_hasSRGBColorSpaceSupport(false),
-+ m_hasSCRGBColorSpaceSupport(false),
-+ m_hasBt2020PQColorSpaceSupport(false)
- {
-+ const char *eglExtensions = libEGL.eglQueryString(display, EGL_EXTENSIONS);
-+ m_hasSRGBColorSpaceSupport = strstr(eglExtensions, "EGL_KHR_gl_colorspace") != nullptr;
-+ m_hasSCRGBColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_scrgb_linear") != nullptr;
-+ m_hasBt2020PQColorSpaceSupport = strstr(eglExtensions, "EGL_EXT_gl_colorspace_bt2020_pq") != nullptr;
- }
-
- bool QWindowsEGLStaticContext::initializeAngle(QWindowsOpenGLTester::Renderers preferredType, HDC dc,
-@@ -297,11 +305,48 @@ QWindowsOpenGLContext *QWindowsEGLStaticContext::createContext(QOpenGLContext *c
- return new QWindowsEGLContext(this, context->format(), context->shareHandle());
- }
-
--void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, int *err)
-+void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig,
-+ QSurfaceFormat::ColorSpace colorSpace, int *err)
- {
- *err = 0;
-+
-+ EGLint eglColorSpace = EGL_GL_COLORSPACE_LINEAR_KHR;
-+ bool colorSpaceSupported = false;
-+
-+ switch (colorSpace) {
-+ case QSurfaceFormat::DefaultColorSpace:
-+ colorSpaceSupported = m_hasSRGBColorSpaceSupport;
-+ break;
-+ case QSurfaceFormat::sRGBColorSpace:
-+ eglColorSpace = EGL_GL_COLORSPACE_SRGB_KHR;
-+ colorSpaceSupported = m_hasSRGBColorSpaceSupport;
-+ break;
-+ case QSurfaceFormat::scRGBColorSpace:
-+ eglColorSpace = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
-+ colorSpaceSupported = m_hasSCRGBColorSpaceSupport;
-+ break;
-+ case QSurfaceFormat::bt2020PQColorSpace:
-+ eglColorSpace = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
-+ colorSpaceSupported = m_hasBt2020PQColorSpaceSupport;
-+ break;
-+ }
-+
-+ QVector<EGLint> attributes;
-+
-+ if (colorSpaceSupported) {
-+ attributes << EGL_GL_COLORSPACE << eglColorSpace;
-+ }
-+
-+ attributes << EGL_NONE;
-+
-+ if (!colorSpaceSupported && colorSpace != QSurfaceFormat::DefaultColorSpace) {
-+ qWarning().nospace() << __FUNCTION__ << ": Requested color space is not supported by EGL implementation: " << colorSpace << " (egl: 0x" << hex << eglColorSpace << ")";
-+ }
-+
-+
- EGLSurface surface = libEGL.eglCreateWindowSurface(m_display, nativeConfig,
-- static_cast<EGLNativeWindowType>(nativeWindow), nullptr);
-+ static_cast<EGLNativeWindowType>(nativeWindow),
-+ attributes.constData());
- if (surface == EGL_NO_SURFACE) {
- *err = libEGL.eglGetError();
- qWarning("%s: Could not create the EGL window surface: 0x%x", __FUNCTION__, *err);
-@@ -349,6 +394,7 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG
- format.setSamples(sampleCount);
- format.setStereo(false);
- format.setSwapInterval(referenceFormat.swapInterval());
-+ format.setColorSpace(referenceFormat.colorSpace());
-
- // Clear the EGL error state because some of the above may
- // have errored out because the attribute is not applicable
-@@ -378,7 +424,6 @@ QSurfaceFormat QWindowsEGLStaticContext::formatFromConfig(EGLDisplay display, EG
- \internal
- \ingroup qt-lighthouse-win
- */
--
- QWindowsEGLContext::QWindowsEGLContext(QWindowsEGLStaticContext *staticContext,
- const QSurfaceFormat &format,
- QPlatformOpenGLContext *share)
-@@ -483,6 +528,8 @@ bool QWindowsEGLContext::makeCurrent(QPlatformSurface *surface)
- // Simulate context loss as the context is useless.
- QWindowsEGLStaticContext::libEGL.eglDestroyContext(m_eglDisplay, m_eglContext);
- m_eglContext = EGL_NO_CONTEXT;
-+ } else if (err == EGL_BAD_MATCH) {
-+ qCDebug(lcQpaGl) << "Got bad match in createWindowSurface() for context" << this << "Check color space configuration.";
- }
- return false;
- }
-diff --git a/src/plugins/platforms/windows/qwindowseglcontext.h b/src/plugins/platforms/windows/qwindowseglcontext.h
-index 8a1e1ddae8..9f7742e6fb 100644
---- a/src/plugins/platforms/windows/qwindowseglcontext.h
-+++ b/src/plugins/platforms/windows/qwindowseglcontext.h
-@@ -80,6 +80,7 @@ struct QWindowsLibEGL
- QFunctionPointer (EGLAPIENTRY *eglGetProcAddress)(const char *procname);
-
- EGLDisplay (EGLAPIENTRY * eglGetPlatformDisplayEXT)(EGLenum platform, void *native_display, const EGLint *attrib_list);
-+ const char* (EGLAPIENTRY * eglQueryString)(EGLDisplay dpy, EGLint name);
-
- private:
- #if !defined(QT_STATIC) || defined(QT_OPENGL_DYNAMIC)
-@@ -121,7 +122,7 @@ public:
- void *moduleHandle() const override { return libGLESv2.moduleHandle(); }
- QOpenGLContext::OpenGLModuleType moduleType() const override { return QOpenGLContext::LibGLES; }
-
-- void *createWindowSurface(void *nativeWindow, void *nativeConfig, int *err) override;
-+ void *createWindowSurface(void *nativeWindow, void *nativeConfig, QSurfaceFormat::ColorSpace colorSpace, int *err) override;
- void destroyWindowSurface(void *nativeSurface) override;
-
- QSurfaceFormat formatFromConfig(EGLDisplay display, EGLConfig config, const QSurfaceFormat &referenceFormat);
-@@ -135,6 +136,9 @@ private:
- EGLDisplay *display, EGLint *major, EGLint *minor);
-
- const EGLDisplay m_display;
-+ bool m_hasSRGBColorSpaceSupport;
-+ bool m_hasSCRGBColorSpaceSupport;
-+ bool m_hasBt2020PQColorSpaceSupport;
- };
-
- class QWindowsEGLContext : public QWindowsOpenGLContext
-diff --git a/src/plugins/platforms/windows/qwindowsopenglcontext.h b/src/plugins/platforms/windows/qwindowsopenglcontext.h
-index cc6d93d35e..61c0e28767 100644
---- a/src/plugins/platforms/windows/qwindowsopenglcontext.h
-+++ b/src/plugins/platforms/windows/qwindowsopenglcontext.h
-@@ -63,7 +63,7 @@ public:
-
- // If the windowing system interface needs explicitly created window surfaces (like EGL),
- // reimplement these.
-- virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, int * /*err*/) { return 0; }
-+ virtual void *createWindowSurface(void * /*nativeWindow*/, void * /*nativeConfig*/, QSurfaceFormat::ColorSpace /*colorSpace*/, int * /*err*/) { return 0; }
- virtual void destroyWindowSurface(void * /*nativeSurface*/) { }
-
- protected:
-diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
-index c5d57be2ad..910d8dd209 100644
---- a/src/plugins/platforms/windows/qwindowswindow.cpp
-+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
-@@ -2738,9 +2738,13 @@ void *QWindowsWindow::surface(void *nativeConfig, int *err)
- return 0;
- #endif
- #ifndef QT_NO_OPENGL
-+
-+
-+
- if (!m_surface) {
-- if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext())
-- m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, err);
-+ if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext()) {
-+ m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, m_format.colorSpace(), err);
-+ }
- }
-
- return m_surface;
---
-2.20.1.windows.1
-
-
-From 2039f3cb4e267232160a844739e500e025b3818e Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Thu, 22 Nov 2018 15:47:48 +0300
-Subject: [PATCH 3/6] Implement color conversion for the backing store texture
-
-If the window surface is not in sRGB mode, then the backing store
-surface should be converted into the destinations color space.
-
-This patch adds the most popular color space transitions into
-QOpenGLTextureBlitter.
-
-The patch also implements QOpenGLWidget::setTextureColorSpace(),
-which notifies the compositor about the color space of non-native
-openGL widgets, so that the data could be converted correctly.
-
-TODO: should we implement the same for QOpenGLWindow::
- setTextureColorSpace()?
-
-Note:
-The channels should be swizzled into RGBA *before* applying the
-color space conversion matrix. Otherwise the colors will be skewed,
-because the conversion function for R and B channels is not the same.
-
-Change-Id: Icbf599952c93cc04de417d0c3790a65282741655
----
- src/gui/opengl/qopengltextureblitter.cpp | 222 ++++++++++++++++--
- src/gui/opengl/qopengltextureblitter.h | 12 +-
- src/gui/painting/qplatformbackingstore.cpp | 16 +-
- src/gui/painting/qplatformbackingstore.h | 4 +-
- .../qopenglcompositorbackingstore.cpp | 2 +-
- src/widgets/kernel/qopenglwidget.cpp | 45 +++-
- src/widgets/kernel/qopenglwidget.h | 3 +
- src/widgets/kernel/qwidget_p.h | 1 +
- src/widgets/kernel/qwidgetbackingstore.cpp | 2 +-
- 9 files changed, 281 insertions(+), 26 deletions(-)
-
-diff --git a/src/gui/opengl/qopengltextureblitter.cpp b/src/gui/opengl/qopengltextureblitter.cpp
-index b65df9dc82..5f6dbff292 100644
---- a/src/gui/opengl/qopengltextureblitter.cpp
-+++ b/src/gui/opengl/qopengltextureblitter.cpp
-@@ -131,14 +131,85 @@ static const char vertex_shader[] =
- "}";
-
- static const char fragment_shader[] =
-- "varying highp vec2 uv;"
-- "uniform sampler2D textureSampler;"
-- "uniform bool swizzle;"
-- "uniform highp float opacity;"
-+ "varying highp vec2 uv;\n"
-+ "uniform sampler2D textureSampler;\n"
-+ "uniform bool swizzle;\n"
-+ "uniform highp float opacity;\n"
-+ "#if defined SCRGB_TO_SRGB\n"
-+ "highp vec4 linearToSRGB(highp vec4 value)\n"
-+ "{\n"
-+ " bvec4 cutoff = lessThan(value, vec4(0.0031308));\n"
-+ " const highp vec2 a1 = vec2(0.055, 0.0);\n"
-+ " const highp vec2 c2 = vec2(1.055, 1.0);\n"
-+ " const highp vec2 m3 = vec2(2.4, 1.0);\n"
-+ " const highp vec2 c4 = vec2(12.92, 1.0);\n"
-+ " highp vec4 higher = c2.xxxy * pow(value, 1.0 / m3.xxxy) - a1.xxxy;\n"
-+ " highp vec4 lower = value * c4.xxxy;\n"
-+ " return mix(higher, lower, vec4(cutoff));\n"
-+ "}\n"
-+ "#endif\n"
-+ "#if defined SRGB_TO_SCRGB || defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n"
-+ "highp vec4 sRgbToLinear(highp vec4 sRGB)\n"
-+ "{\n"
-+ " bvec4 cutoff = lessThan(sRGB, vec4(0.04045));\n"
-+ " const highp vec2 a1 = vec2(0.055, 0.0);\n"
-+ " const highp vec2 c2 = vec2(1.055, 1.0);\n"
-+ " const highp vec2 m3 = vec2(2.4, 1.0);\n"
-+ " const highp vec2 c4 = vec2(12.92, 1.0);\n"
-+ " highp vec4 higher = pow((sRGB + a1.xxxy) / c2.xxxy, m3.xxxy);\n"
-+ " highp vec4 lower = sRGB / c4.xxxy;\n"
-+ " return mix(higher, lower, vec4(cutoff));\n"
-+ "}\n"
-+ "#endif\n"
-+ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n"
-+ "highp vec4 applySmpte2084Curve(highp vec4 L)\n"
-+ "{"
-+ " const highp vec2 m1 = vec2(2610.0 / 4096.0 / 4.0, 1.0);\n"
-+ " const highp vec2 m2 = vec2(2523.0 / 4096.0 * 128.0, 1.0);\n"
-+ " const highp vec2 a1 = vec2(3424.0 / 4096.0, 0.0);\n"
-+ " const highp vec2 c2 = vec2(2413.0 / 4096.0 * 32.0, 1.0);\n"
-+ " const highp vec2 c3 = vec2(2392.0 / 4096.0 * 32.0, 1.0);\n"
-+ " const highp vec2 a4 = vec2(1.0, 0.0);\n"
-+ " highp vec4 Lp = pow(L, m1.xxxy);\n"
-+ " highp vec4 res = pow((a1.xxxy + c2.xxxy * Lp) / (a4.xxxy + c3.xxxy * Lp), m2.xxxy);\n"
-+ " return res;"
-+ "}\n"
-+ "#endif\n"
-+ "#if defined SRGB_TO_BT2020PQ || defined SCRGB_TO_BT2020PQ\n"
-+ "highp vec4 scRgbToBt2020pq(highp vec4 value)\n"
-+ "{\n"
-+ " const highp mat4 convMat = "
-+ " mat4(0.627402, 0.069095, 0.016394, 0.0,"
-+ " 0.329292, 0.919544, 0.088028, 0.0,"
-+ " 0.043306, 0.011360, 0.895578, 0.0,"
-+ " 0.0, 0.0, 0.0, 1.0);"
-+ ""
-+ " value = convMat * value;\n"
-+ " return applySmpte2084Curve(0.008 * value);"
-+ "}\n"
-+ "#endif\n"
-+ "#if defined SRGB_TO_BT2020PQ\n"
-+ "highp vec4 sRgbToBt2020pq(highp vec4 value)\n"
-+ "{\n"
-+ " value = sRgbToLinear(value);"
-+ " return scRgbToBt2020pq(value);"
-+ "}\n"
-+ "#endif\n"
-+ "\n"
- "void main() {"
- " highp vec4 tmpFragColor = texture2D(textureSampler,uv);"
-- " tmpFragColor.a *= opacity;"
-- " gl_FragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;"
-+ " tmpFragColor.a *= opacity;\n"
-+ " tmpFragColor = swizzle ? tmpFragColor.bgra : tmpFragColor;\n"
-+ "#if defined SRGB_TO_SCRGB\n"
-+ " tmpFragColor = sRgbToLinear(tmpFragColor);\n"
-+ "#elif defined SRGB_TO_BT2020PQ\n"
-+ " tmpFragColor = sRgbToBt2020pq(tmpFragColor);\n"
-+ "#elif defined SCRGB_TO_BT2020PQ\n"
-+ " tmpFragColor = scRgbToBt2020pq(tmpFragColor);\n"
-+ "#elif defined SCRGB_TO_SRGB\n"
-+ " tmpFragColor = linearToSRGB(tmpFragColor);\n"
-+ "#endif\n"
-+ " gl_FragColor = tmpFragColor;"
- "}";
-
- static const char fragment_shader_external_oes[] =
-@@ -187,6 +258,23 @@ private:
- GLenum m_target;
- };
-
-+class ColorSpaceConversion : public QPair<QSurfaceFormat::ColorSpace, QSurfaceFormat::ColorSpace>
-+{
-+public:
-+ ColorSpaceConversion() { };
-+ ColorSpaceConversion(QSurfaceFormat::ColorSpace srcColorSpace,
-+ QSurfaceFormat::ColorSpace dstColorSpace)
-+ : QPair(srcColorSpace, dstColorSpace)
-+ { }
-+
-+ QSurfaceFormat::ColorSpace source() const {
-+ return first;
-+ }
-+ QSurfaceFormat::ColorSpace destination() const {
-+ return second;
-+ }
-+};
-+
- class QOpenGLTextureBlitterPrivate
- {
- public:
-@@ -197,16 +285,29 @@ public:
- };
-
- enum ProgramIndex {
-- TEXTURE_2D,
-- TEXTURE_EXTERNAL_OES
-+ TEXTURE_2D = 0,
-+ TEXTURE_2D_SRGB_TO_SCRGB,
-+ TEXTURE_2D_SCRGB_TO_SRGB,
-+ TEXTURE_2D_SRGB_TO_BT2020PQ,
-+ TEXTURE_2D_SCRGB_TO_BT2020PQ,
-+ TEXTURE_EXTERNAL_OES,
-+
-+ PROGRAM_COUNT
- };
-
- QOpenGLTextureBlitterPrivate() :
- swizzle(false),
- opacity(1.0f),
- vao(new QOpenGLVertexArrayObject),
-- currentTarget(TEXTURE_2D)
-- { }
-+ currentTarget(GL_NONE),
-+ colorSpaceConversion(0)
-+ {
-+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::DefaultColorSpace, QSurfaceFormat::DefaultColorSpace);
-+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::scRGBColorSpace);
-+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::sRGBColorSpace);
-+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::sRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace);
-+ supportedColorSpaceConversions << ColorSpaceConversion(QSurfaceFormat::scRGBColorSpace, QSurfaceFormat::bt2020PQColorSpace);
-+ }
-
- bool buildProgram(ProgramIndex idx, const char *vs, const char *fs);
-
-@@ -214,6 +315,7 @@ public:
- void blit(GLuint texture, const QMatrix4x4 &vertexTransform, QOpenGLTextureBlitter::Origin origin);
-
- void prepareProgram(const QMatrix4x4 &vertexTransform);
-+ int calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace);
-
- QOpenGLBuffer vertexBuffer;
- QOpenGLBuffer textureBuffer;
-@@ -239,18 +341,48 @@ public:
- bool swizzle;
- float opacity;
- TextureMatrixUniform textureMatrixUniformState;
-- } programs[2];
-+ } programs[PROGRAM_COUNT];
- bool swizzle;
- float opacity;
- QScopedPointer<QOpenGLVertexArrayObject> vao;
- GLenum currentTarget;
-+
-+ int colorSpaceConversion;
-+ QVector<ColorSpaceConversion> supportedColorSpaceConversions;
- };
-
--static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target)
-+int QOpenGLTextureBlitterPrivate::calcColorSpaceConversionIndex(QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace)
-+{
-+ // TODO: auto-detect destination color space of the surface
-+ // in case of default color space
-+
-+ // disable color management if at least one of the color
-+ // spaces is declared as default
-+ if (srcColorSpace == QSurfaceFormat::DefaultColorSpace ||
-+ dstColorSpace == QSurfaceFormat::DefaultColorSpace) {
-+
-+ return 0;
-+ }
-+
-+ // disable color management if source and destination color
-+ // spaces are the same
-+ if (srcColorSpace == dstColorSpace) {
-+ return 0;
-+ }
-+
-+ ColorSpaceConversion conversion(srcColorSpace, dstColorSpace);
-+ return supportedColorSpaceConversions.indexOf(conversion);
-+}
-+
-+static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GLenum target, int colorSpaceConversion)
- {
- switch (target) {
-- case GL_TEXTURE_2D:
-- return QOpenGLTextureBlitterPrivate::TEXTURE_2D;
-+ case GL_TEXTURE_2D: {
-+ QOpenGLTextureBlitterPrivate::ProgramIndex index =
-+ QOpenGLTextureBlitterPrivate::ProgramIndex(
-+ int(QOpenGLTextureBlitterPrivate::TEXTURE_2D) + colorSpaceConversion);
-+ return index;
-+ }
- case GL_TEXTURE_EXTERNAL_OES:
- return QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES;
- default:
-@@ -261,7 +393,7 @@ static inline QOpenGLTextureBlitterPrivate::ProgramIndex targetToProgramIndex(GL
-
- void QOpenGLTextureBlitterPrivate::prepareProgram(const QMatrix4x4 &vertexTransform)
- {
-- Program *program = &programs[targetToProgramIndex(currentTarget)];
-+ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)];
-
- vertexBuffer.bind();
- program->glProgram->setAttributeBuffer(program->vertexCoordAttribPos, GL_FLOAT, 0, 3, 0);
-@@ -293,7 +425,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture,
- TextureBinder binder(currentTarget, texture);
- prepareProgram(vertexTransform);
-
-- Program *program = &programs[targetToProgramIndex(currentTarget)];
-+ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)];
- program->glProgram->setUniformValue(program->textureTransformUniformPos, textureTransform);
- program->textureMatrixUniformState = User;
-
-@@ -307,7 +439,7 @@ void QOpenGLTextureBlitterPrivate::blit(GLuint texture,
- TextureBinder binder(currentTarget, texture);
- prepareProgram(vertexTransform);
-
-- Program *program = &programs[targetToProgramIndex(currentTarget)];
-+ Program *program = &programs[targetToProgramIndex(currentTarget, colorSpaceConversion)];
- if (origin == QOpenGLTextureBlitter::OriginTopLeft) {
- if (program->textureMatrixUniformState != IdentityFlipped) {
- QMatrix3x3 flipped;
-@@ -408,6 +540,28 @@ bool QOpenGLTextureBlitter::create()
- } else {
- if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D, vertex_shader, fragment_shader))
- return false;
-+
-+ // TODO: create non-default transformations on-demand
-+ {
-+ const QString shader = QString("#define SRGB_TO_SCRGB\n %1").arg(fragment_shader);
-+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB, vertex_shader, shader.toLatin1().constData()))
-+ return false;
-+ }
-+ {
-+ const QString shader = QString("#define SCRGB_TO_SRGB\n %1").arg(fragment_shader);
-+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_SRGB, vertex_shader, shader.toLatin1().constData()))
-+ return false;
-+ }
-+ {
-+ const QString shader = QString("#define SRGB_TO_BT2020PQ\n %1").arg(fragment_shader);
-+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData()))
-+ return false;
-+ }
-+ {
-+ const QString shader = QString("#define SCRGB_TO_BT2020PQ\n %1").arg(fragment_shader);
-+ if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_2D_SCRGB_TO_BT2020PQ, vertex_shader, shader.toLatin1().constData()))
-+ return false;
-+ }
- if (supportsExternalOESTarget())
- if (!d->buildProgram(QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES, vertex_shader, fragment_shader_external_oes))
- return false;
-@@ -455,6 +609,8 @@ void QOpenGLTextureBlitter::destroy()
- return;
- Q_D(QOpenGLTextureBlitter);
- d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D].glProgram.reset();
-+ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_SCRGB].glProgram.reset();
-+ d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_2D_SRGB_TO_BT2020PQ].glProgram.reset();
- d->programs[QOpenGLTextureBlitterPrivate::TEXTURE_EXTERNAL_OES].glProgram.reset();
- d->vertexBuffer.destroy();
- d->textureBuffer.destroy();
-@@ -484,15 +640,26 @@ bool QOpenGLTextureBlitter::supportsExternalOESTarget() const
-
- \sa release(), blit()
- */
--void QOpenGLTextureBlitter::bind(GLenum target)
-+void QOpenGLTextureBlitter::bind(GLenum target,
-+ QSurfaceFormat::ColorSpace srcColorSpace,
-+ QSurfaceFormat::ColorSpace dstColorSpace)
- {
- Q_D(QOpenGLTextureBlitter);
-
- if (d->vao->isCreated())
- d->vao->bind();
-
-+ const int index = d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace);
-+
-+ if (index >= 0) {
-+ d->colorSpaceConversion = index;
-+ } else {
-+ qWarning() << "QOpenGLTextureBlitter::bind(): color space conversion is not supported" << srcColorSpace << dstColorSpace;
-+ d->colorSpaceConversion = 0; // noop conversion
-+ }
-+
- d->currentTarget = target;
-- QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target)];
-+ QOpenGLTextureBlitterPrivate::Program *p = &d->programs[targetToProgramIndex(target, d->colorSpaceConversion)];
- p->glProgram->bind();
-
- d->vertexBuffer.bind();
-@@ -506,6 +673,21 @@ void QOpenGLTextureBlitter::bind(GLenum target)
- d->textureBuffer.release();
- }
-
-+void QOpenGLTextureBlitter::rebind(GLenum target, QSurfaceFormat::ColorSpace srcColorSpace, QSurfaceFormat::ColorSpace dstColorSpace)
-+{
-+ Q_D(QOpenGLTextureBlitter);
-+
-+ if (d->vao->isCreated() &&
-+ d->currentTarget == target &&
-+ d->colorSpaceConversion == d->calcColorSpaceConversionIndex(srcColorSpace, dstColorSpace)) {
-+
-+ // the blitter is already configured in the correct state, so just skip it
-+ return;
-+ }
-+
-+ bind(target, srcColorSpace, dstColorSpace);
-+}
-+
- /*!
- Unbinds the graphics resources used by the blitter.
-
-@@ -514,7 +696,7 @@ void QOpenGLTextureBlitter::bind(GLenum target)
- void QOpenGLTextureBlitter::release()
- {
- Q_D(QOpenGLTextureBlitter);
-- d->programs[targetToProgramIndex(d->currentTarget)].glProgram->release();
-+ d->programs[targetToProgramIndex(d->currentTarget, d->colorSpaceConversion)].glProgram->release();
- if (d->vao->isCreated())
- d->vao->release();
- }
-diff --git a/src/gui/opengl/qopengltextureblitter.h b/src/gui/opengl/qopengltextureblitter.h
-index 2f7c6b1a0a..3c87e4e2b5 100644
---- a/src/gui/opengl/qopengltextureblitter.h
-+++ b/src/gui/opengl/qopengltextureblitter.h
-@@ -48,6 +48,9 @@
- #include <QtGui/QMatrix3x3>
- #include <QtGui/QMatrix4x4>
-
-+// TODO: less includes!!!
-+#include <QSurfaceFormat>
-+
- QT_BEGIN_NAMESPACE
-
- class QOpenGLTextureBlitterPrivate;
-@@ -69,7 +72,14 @@ public:
-
- bool supportsExternalOESTarget() const;
-
-- void bind(GLenum target = GL_TEXTURE_2D);
-+ void bind(GLenum target = GL_TEXTURE_2D,
-+ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace,
-+ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace);
-+
-+ void rebind(GLenum target = GL_TEXTURE_2D,
-+ QSurfaceFormat::ColorSpace srcColorSpace = QSurfaceFormat::DefaultColorSpace,
-+ QSurfaceFormat::ColorSpace dstColorSpace = QSurfaceFormat::DefaultColorSpace);
-+
- void release();
-
- void setRedBlueSwizzle(bool swizzle);
-diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp
-index afb4613ba5..8541e582e9 100644
---- a/src/gui/painting/qplatformbackingstore.cpp
-+++ b/src/gui/painting/qplatformbackingstore.cpp
-@@ -132,6 +132,7 @@ struct QBackingstoreTextureInfo
- QRect rect;
- QRect clipRect;
- QPlatformTextureList::Flags flags;
-+ QSurfaceFormat::ColorSpace colorSpace;
- };
-
- Q_DECLARE_TYPEINFO(QBackingstoreTextureInfo, Q_MOVABLE_TYPE);
-@@ -181,6 +182,12 @@ QPlatformTextureList::Flags QPlatformTextureList::flags(int index) const
- return d->textures.at(index).flags;
- }
-
-+QSurfaceFormat::ColorSpace QPlatformTextureList::colorSpace(int index) const
-+{
-+ Q_D(const QPlatformTextureList);
-+ return d->textures.at(index).colorSpace;
-+}
-+
- QRect QPlatformTextureList::geometry(int index) const
- {
- Q_D(const QPlatformTextureList);
-@@ -209,7 +216,7 @@ bool QPlatformTextureList::isLocked() const
- }
-
- void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const QRect &geometry,
-- const QRect &clipRect, Flags flags)
-+ const QRect &clipRect, Flags flags, QSurfaceFormat::ColorSpace colorSpace)
- {
- Q_D(QPlatformTextureList);
- QBackingstoreTextureInfo bi;
-@@ -218,6 +225,7 @@ void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const Q
- bi.rect = geometry;
- bi.clipRect = clipRect;
- bi.flags = flags;
-+ bi.colorSpace = colorSpace;
- d->textures.append(bi);
- }
-
-@@ -300,6 +308,7 @@ static void blitTextureForWidget(const QPlatformTextureList *textures, int idx,
- if (srgb && canUseSrgb)
- funcs->glEnable(GL_FRAMEBUFFER_SRGB);
-
-+ blitter->rebind(GL_TEXTURE_2D, textures->colorSpace(idx), window->format().colorSpace());
- blitter->blit(textures->textureId(idx), target, source);
-
- if (srgb && canUseSrgb)
-@@ -433,6 +442,11 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &regi
- funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
-
- if (textureId) {
-+ // GUI texture is always in sRGB color space
-+ d_ptr->blitter->rebind(GL_TEXTURE_2D,
-+ QSurfaceFormat::sRGBColorSpace,
-+ window->format().colorSpace());
-+
- if (d_ptr->needsSwizzle)
- d_ptr->blitter->setRedBlueSwizzle(true);
- // The backingstore is for the entire tlw.
-diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h
-index de5ba964dc..f8887bd4cd 100644
---- a/src/gui/painting/qplatformbackingstore.h
-+++ b/src/gui/painting/qplatformbackingstore.h
-@@ -95,11 +95,13 @@ public:
- QRect clipRect(int index) const;
- void *source(int index);
- Flags flags(int index) const;
-+ QSurfaceFormat::ColorSpace colorSpace(int index) const;
- void lock(bool on);
- bool isLocked() const;
-
- void appendTexture(void *source, GLuint textureId, const QRect &geometry,
-- const QRect &clipRect = QRect(), Flags flags = 0);
-+ const QRect &clipRect = QRect(), Flags flags = 0,
-+ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::DefaultColorSpace);
- void clear();
-
- Q_SIGNALS:
-diff --git a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp
-index 40400e2a19..5d44e62455 100644
---- a/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp
-+++ b/src/platformsupport/platformcompositor/qopenglcompositorbackingstore.cpp
-@@ -230,7 +230,7 @@ void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegi
- m_textures->clear();
- for (int i = 0; i < textures->count(); ++i)
- m_textures->appendTexture(textures->source(i), textures->textureId(i), textures->geometry(i),
-- textures->clipRect(i), textures->flags(i));
-+ textures->clipRect(i), textures->flags(i), textures->colorSpace(i));
-
- updateTexture();
- m_textures->appendTexture(nullptr, m_bsTexture, window->geometry());
-diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp
-index 89f860150f..0abf707e01 100644
---- a/src/widgets/kernel/qopenglwidget.cpp
-+++ b/src/widgets/kernel/qopenglwidget.cpp
-@@ -568,7 +568,8 @@ public:
- updateBehavior(QOpenGLWidget::NoPartialUpdate),
- requestedSamples(0),
- inPaintGL(false),
-- textureFormat(0)
-+ textureFormat(0),
-+ textureColorSpace(QSurfaceFormat::DefaultColorSpace)
- {
- requestedFormat = QSurfaceFormat::defaultFormat();
- }
-@@ -578,6 +579,7 @@ public:
-
- GLuint textureId() const override;
- QPlatformTextureList::Flags textureListFlags() override;
-+ QSurfaceFormat::ColorSpace colorSpace() const override;
-
- void initialize();
- void invokeUserPaint();
-@@ -609,6 +611,7 @@ public:
- int requestedSamples;
- bool inPaintGL;
- GLenum textureFormat;
-+ QSurfaceFormat::ColorSpace textureColorSpace;
- };
-
- void QOpenGLWidgetPaintDevicePrivate::beginPaint()
-@@ -695,6 +698,11 @@ QPlatformTextureList::Flags QOpenGLWidgetPrivate::textureListFlags()
- return flags;
- }
-
-+QSurfaceFormat::ColorSpace QOpenGLWidgetPrivate::colorSpace() const
-+{
-+ return textureColorSpace;
-+}
-+
- void QOpenGLWidgetPrivate::reset()
- {
- Q_Q(QOpenGLWidget);
-@@ -1115,6 +1123,41 @@ void QOpenGLWidget::setTextureFormat(GLenum texFormat)
- d->textureFormat = texFormat;
- }
-
-+/*!
-+ \return the declared color space of the internal texture of the widget.
-+
-+ The texture's color space will be used when composing the widget
-+ into the root window surface.
-+
-+ \note when the color space is set to QSurfaceFormat::DefaultColorSpace,
-+ color conversion is effectively disabled.
-+
-+ \since 5.99
-+ */
-+QSurfaceFormat::ColorSpace QOpenGLWidget::textureColorSpace() const
-+{
-+ Q_D(const QOpenGLWidget);
-+ return d->textureColorSpace;
-+}
-+
-+/*!
-+ Sets a custom color space for the internal texture of the widget
-+
-+ The color space of the texture will be compared against the color
-+ space of the root surface and conversion will be performed if needed.
-+
-+ \note setting the color space to QSurfaceFormat::DefaultColorSpace will
-+ effectively disable color conversion when composing this texture on
-+ screen.
-+
-+ \since 5.99
-+ */
-+void QOpenGLWidget::setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace)
-+{
-+ Q_D(QOpenGLWidget);
-+ d->textureColorSpace = colorSpace;
-+}
-+
- /*!
- \return the active internal texture format if the widget has already
- initialized, the requested format if one was set but the widget has not yet
-diff --git a/src/widgets/kernel/qopenglwidget.h b/src/widgets/kernel/qopenglwidget.h
-index 9eb4a9ba5a..eff2d9796d 100644
---- a/src/widgets/kernel/qopenglwidget.h
-+++ b/src/widgets/kernel/qopenglwidget.h
-@@ -75,6 +75,9 @@ public:
- GLenum textureFormat() const;
- void setTextureFormat(GLenum texFormat);
-
-+ QSurfaceFormat::ColorSpace textureColorSpace() const;
-+ void setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace);
-+
- bool isValid() const;
-
- void makeCurrent();
-diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
-index 7e4ea2cc0c..1ff5af426b 100644
---- a/src/widgets/kernel/qwidget_p.h
-+++ b/src/widgets/kernel/qwidget_p.h
-@@ -655,6 +655,7 @@ public:
- ? QPlatformTextureList::StacksOnTop
- : QPlatformTextureList::Flags(0);
- }
-+ virtual QSurfaceFormat::ColorSpace colorSpace() const { return QSurfaceFormat::DefaultColorSpace; }
- virtual QImage grabFramebuffer() { return QImage(); }
- virtual void beginBackingStorePainting() { }
- virtual void endBackingStorePainting() { }
-diff --git a/src/widgets/kernel/qwidgetbackingstore.cpp b/src/widgets/kernel/qwidgetbackingstore.cpp
-index a32eb2a03b..db60338034 100644
---- a/src/widgets/kernel/qwidgetbackingstore.cpp
-+++ b/src/widgets/kernel/qwidgetbackingstore.cpp
-@@ -1007,7 +1007,7 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatfo
- if (wd->renderToTexture) {
- QPlatformTextureList::Flags flags = wd->textureListFlags();
- const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
-- widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags);
-+ widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags, wd->colorSpace());
- }
-
- for (int i = 0; i < wd->children.size(); ++i) {
---
-2.20.1.windows.1
-
-
-From 9ce6a1f443545d7615053dcfd642c0ab9ad86478 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Tue, 4 Dec 2018 20:11:34 +0300
-Subject: [PATCH 4/6] Return QScreen's HMONITOR handle via
- QPlatformNativeInterface
-
-It is needed to be able to fetch extra information about the display via
-DXGI interface.
-
-Change-Id: Id83982eb07ade157719e430d0abcc2613409a343
----
- .../windows/qwindowsnativeinterface.cpp | 16 ++++++++++++++++
- .../platforms/windows/qwindowsnativeinterface.h | 1 +
- src/plugins/platforms/windows/qwindowsscreen.cpp | 5 +++++
- src/plugins/platforms/windows/qwindowsscreen.h | 2 ++
- 4 files changed, 24 insertions(+)
-
-diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
-index 32eec322e8..05d6ac9201 100644
---- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
-+++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
-@@ -40,6 +40,7 @@
- #include "qwindowsnativeinterface.h"
- #include "qwindowsclipboard.h"
- #include "qwindowswindow.h"
-+#include "qwindowsscreen.h"
- #include "qwindowscontext.h"
- #include "qwindowscursor.h"
- #include "qwindowsopenglcontext.h"
-@@ -124,6 +125,21 @@ void *QWindowsNativeInterface::nativeResourceForWindow(const QByteArray &resourc
- return nullptr;
- }
-
-+void *QWindowsNativeInterface::nativeResourceForScreen(const QByteArray &resource, QScreen *screen)
-+{
-+ if (!screen || !screen->handle()) {
-+ qWarning("%s: '%s' requested for null screen or screen without handle.", __FUNCTION__, resource.constData());
-+ return 0;
-+ }
-+ QWindowsScreen *bs = static_cast<QWindowsScreen *>(screen->handle());
-+ int type = resourceType(resource);
-+ if (type == HandleType)
-+ return bs->handle();
-+
-+ qWarning("%s: Invalid key '%s' requested.", __FUNCTION__, resource.constData());
-+ return 0;
-+}
-+
- #ifndef QT_NO_CURSOR
- void *QWindowsNativeInterface::nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor)
- {
-diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.h b/src/plugins/platforms/windows/qwindowsnativeinterface.h
-index d085a4afb3..bd7cad01c1 100644
---- a/src/plugins/platforms/windows/qwindowsnativeinterface.h
-+++ b/src/plugins/platforms/windows/qwindowsnativeinterface.h
-@@ -73,6 +73,7 @@ public:
- void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) override;
- #endif
- void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override;
-+ void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override;
- #ifndef QT_NO_CURSOR
- void *nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor) override;
- #endif
-diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp
-index a161dc46e9..8a5f0e6577 100644
---- a/src/plugins/platforms/windows/qwindowsscreen.cpp
-+++ b/src/plugins/platforms/windows/qwindowsscreen.cpp
-@@ -321,6 +321,11 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
- }
- }
-
-+HMONITOR QWindowsScreen::handle() const
-+{
-+ return m_data.hMonitor;
-+}
-+
- QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry()
- {
- QRect result;
-diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h
-index 824bcb1ad6..33c9effa2a 100644
---- a/src/plugins/platforms/windows/qwindowsscreen.h
-+++ b/src/plugins/platforms/windows/qwindowsscreen.h
-@@ -104,6 +104,8 @@ public:
-
- inline void handleChanges(const QWindowsScreenData &newData);
-
-+ HMONITOR handle() const;
-+
- #ifndef QT_NO_CURSOR
- QPlatformCursor *cursor() const override { return m_cursor.data(); }
- const CursorPtr &cursorPtr() const { return m_cursor; }
---
-2.20.1.windows.1
-
-
-From df143f54ae6134edecd9f76818df3510a10dfcf1 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Sun, 10 Feb 2019 22:55:59 +0300
-Subject: [PATCH 5/6] Implement a manual test for checking is HDR features work
-
-Test plan:
-
-1) Run without arguments: `hdr-openglwidget.exe`
- It should show you three rectangles: the left one should be HDR'ly
- bright, the other ones should be SDR'ly dim and look exactly the same.
-
-3) Run in Bt. 2020 PQ mode: `hdr-openglwidget.exe --bt2020pq`
- The result should look exactly the same.
-
-4) Run in SDR sRGB mode: `hdr-openglwidget.exe --srgb`.
- All three images should look SDR'ly dim.
-
-NOTE:
-Please note that the current implementation of SDR compositing
-in QOpenGLTextureBlitter doesn't support user configuration for
-SDR brightness from the system. This API is available for UWP
-applications only. It means that when changing "SDR brightness"
-slider in Windows' settings, the brightness of our SDR widget
-will not change. More that that, it might even be different from
-the brightness of other SDR applications.
-
-Change-Id: Idccc790937c9061ec618ab21f6b71bd0620cd2cc
----
- .../hdr-qopenglwidget/KisGLImageF16.cpp | 131 +++++++++
- .../manual/hdr-qopenglwidget/KisGLImageF16.h | 68 +++++
- .../hdr-qopenglwidget/KisGLImageWidget.cpp | 252 ++++++++++++++++++
- .../hdr-qopenglwidget/KisGLImageWidget.h | 77 ++++++
- .../hdr-qopenglwidget/hdr-openglwidget.pro | 20 ++
- .../kis_gl_image_widget.frag | 23 ++
- .../hdr-qopenglwidget/kis_gl_image_widget.qrc | 6 +
- .../kis_gl_image_widget.vert | 17 ++
- tests/manual/hdr-qopenglwidget/main.cpp | 153 +++++++++++
- .../hdr-qopenglwidget/openglprobeutils.cpp | 139 ++++++++++
- .../hdr-qopenglwidget/openglprobeutils.h | 42 +++
- tests/manual/hdr-qopenglwidget/window.cpp | 219 +++++++++++++++
- tests/manual/hdr-qopenglwidget/window.h | 69 +++++
- tests/manual/manual.pro | 2 +-
- 14 files changed, 1217 insertions(+), 1 deletion(-)
- create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp
- create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.h
- create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp
- create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.h
- create mode 100644 tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro
- create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag
- create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc
- create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert
- create mode 100644 tests/manual/hdr-qopenglwidget/main.cpp
- create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.cpp
- create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.h
- create mode 100644 tests/manual/hdr-qopenglwidget/window.cpp
- create mode 100644 tests/manual/hdr-qopenglwidget/window.h
-
-diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp
-new file mode 100644
-index 0000000000..a84b676f5b
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp
-@@ -0,0 +1,131 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#include "KisGLImageF16.h"
-+
-+#include <QByteArray>
-+#include <QSize>
-+
-+struct KisGLImageF16::Private : public QSharedData
-+{
-+ QSize size;
-+ QByteArray data;
-+};
-+
-+KisGLImageF16::KisGLImageF16()
-+ : m_d(new Private)
-+{
-+}
-+
-+KisGLImageF16::KisGLImageF16(const QSize &size, bool clearPixels)
-+ : m_d(new Private)
-+{
-+ resize(size, clearPixels);
-+}
-+
-+KisGLImageF16::KisGLImageF16(int width, int height, bool clearPixels)
-+ : KisGLImageF16(QSize(width, height), clearPixels)
-+{
-+}
-+
-+KisGLImageF16::KisGLImageF16(const KisGLImageF16 &rhs)
-+ : m_d(rhs.m_d)
-+{
-+}
-+
-+KisGLImageF16 &KisGLImageF16::operator=(const KisGLImageF16 &rhs)
-+{
-+ m_d = rhs.m_d;
-+}
-+
-+bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs)
-+{
-+ return lhs.m_d == rhs.m_d;
-+}
-+
-+bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs)
-+{
-+ return !(lhs == rhs);
-+}
-+
-+KisGLImageF16::~KisGLImageF16()
-+{
-+}
-+
-+void KisGLImageF16::clearPixels()
-+{
-+ if (!m_d->data.isEmpty()) {
-+ m_d->data.fill(0);
-+ }
-+}
-+
-+void KisGLImageF16::resize(const QSize &size, bool clearPixels)
-+{
-+ const int pixelSize = 2 * 4;
-+
-+ m_d->size = size;
-+ m_d->data.resize(size.width() * size.height() * pixelSize);
-+
-+ if (clearPixels) {
-+ m_d->data.fill(0);
-+ }
-+}
-+
-+const qfloat16 *KisGLImageF16::constData() const
-+{
-+ Q_ASSERT(!m_d->data.isNull());
-+ return reinterpret_cast<const qfloat16*>(m_d->data.data());
-+}
-+
-+qfloat16 *KisGLImageF16::data()
-+{
-+ m_d->data.detach();
-+ Q_ASSERT(!m_d->data.isNull());
-+
-+ return reinterpret_cast<qfloat16*>(m_d->data.data());
-+}
-+
-+QSize KisGLImageF16::size() const
-+{
-+ return m_d->size;
-+}
-+
-+int KisGLImageF16::width() const
-+{
-+ return m_d->size.width();
-+}
-+
-+int KisGLImageF16::height() const
-+{
-+ return m_d->size.height();
-+}
-+
-+bool KisGLImageF16::isNull() const
-+{
-+ return m_d->data.isNull();
-+}
-diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.h b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h
-new file mode 100644
-index 0000000000..335e42ee68
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h
-@@ -0,0 +1,68 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#ifndef KISGLIMAGEF16_H
-+#define KISGLIMAGEF16_H
-+
-+#include <QSharedDataPointer>
-+#include <QFloat16>
-+
-+class QSize;
-+
-+class KisGLImageF16
-+{
-+public:
-+ KisGLImageF16();
-+ KisGLImageF16(const QSize &size, bool clearPixels = false);
-+ KisGLImageF16(int width, int height, bool clearPixels = false);
-+ KisGLImageF16(const KisGLImageF16 &rhs);
-+ KisGLImageF16& operator=(const KisGLImageF16 &rhs);
-+
-+ friend bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs);
-+ friend bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs);
-+
-+ ~KisGLImageF16();
-+
-+ void clearPixels();
-+ void resize(const QSize &size, bool clearPixels = false);
-+
-+ const qfloat16* constData() const;
-+ qfloat16* data();
-+
-+ QSize size() const;
-+ int width() const;
-+ int height() const;
-+
-+ bool isNull() const;
-+
-+private:
-+ struct Private;
-+ QSharedDataPointer<Private> m_d;
-+};
-+
-+#endif // KISGLIMAGEF16_H
-diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp
-new file mode 100644
-index 0000000000..da36ac1619
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp
-@@ -0,0 +1,252 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#include "KisGLImageWidget.h"
-+
-+#include <QPainter>
-+#include <QFile>
-+#include <QResizeEvent>
-+
-+#include "KisGLImageF16.h"
-+
-+namespace {
-+inline void rectToVertices(QVector3D* vertices, const QRectF &rc)
-+{
-+ vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f);
-+ vertices[1] = QVector3D(rc.left(), rc.top(), 0.f);
-+ vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f);
-+ vertices[3] = QVector3D(rc.left(), rc.top(), 0.f);
-+ vertices[4] = QVector3D(rc.right(), rc.top(), 0.f);
-+ vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f);
-+}
-+
-+inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc)
-+{
-+ texCoords[0] = QVector2D(rc.left(), rc.bottom());
-+ texCoords[1] = QVector2D(rc.left(), rc.top());
-+ texCoords[2] = QVector2D(rc.right(), rc.bottom());
-+ texCoords[3] = QVector2D(rc.left(), rc.top());
-+ texCoords[4] = QVector2D(rc.right(), rc.top());
-+ texCoords[5] = QVector2D(rc.right(), rc.bottom());
-+}
-+}
-+
-+KisGLImageWidget::KisGLImageWidget(QWidget *parent)
-+ : KisGLImageWidget(QSurfaceFormat::sRGBColorSpace, parent)
-+{
-+}
-+
-+KisGLImageWidget::KisGLImageWidget(QSurfaceFormat::ColorSpace colorSpace,
-+ QWidget *parent)
-+ : QOpenGLWidget(parent),
-+ m_texture(QOpenGLTexture::Target2D)
-+{
-+
-+ qDebug() << "Crating gl widget";
-+
-+ setTextureFormat(GL_RGBA16F);
-+ setTextureColorSpace(colorSpace);
-+
-+ setUpdateBehavior(QOpenGLWidget::NoPartialUpdate);
-+}
-+
-+void KisGLImageWidget::initializeGL()
-+{
-+ initializeOpenGLFunctions();
-+
-+ qDebug() << "Initialized with format:" << context()->format();
-+
-+ QFile vertexShaderFile(QString(":/") + "kis_gl_image_widget.vert");
-+ vertexShaderFile.open(QIODevice::ReadOnly);
-+ QString vertSource = vertexShaderFile.readAll();
-+
-+ QFile fragShaderFile(QString(":/") + "kis_gl_image_widget.frag");
-+ fragShaderFile.open(QIODevice::ReadOnly);
-+ QString fragSource = fragShaderFile.readAll();
-+
-+ if (context()->isOpenGLES()) {
-+ const char *versionHelper = "#define USE_OPENGLES\n";
-+ vertSource.prepend(versionHelper);
-+ fragSource.prepend(versionHelper);
-+
-+ const char *versionDefinition = "#version 100\n";
-+ vertSource.prepend(versionDefinition);
-+ fragSource.prepend(versionDefinition);
-+ } else {
-+ const char *versionDefinition = "#version 330 core\n";
-+ vertSource.prepend(versionDefinition);
-+ fragSource.prepend(versionDefinition);
-+ }
-+
-+ if (!m_shader.addShaderFromSourceCode(QOpenGLShader::Vertex, vertSource)) {
-+ qDebug() << "Could not add vertex code";
-+ return;
-+ }
-+
-+ if (!m_shader.addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource)) {
-+ qDebug() << "Could not add fragment code";
-+ return;
-+ }
-+
-+ if (!m_shader.link()) {
-+ qDebug() << "Could not link";
-+ return;
-+ }
-+
-+ if (!m_shader.bind()) {
-+ qDebug() << "Could not bind";
-+ return;
-+ }
-+
-+ m_shader.release();
-+
-+
-+ m_vao.create();
-+ m_vao.bind();
-+
-+ m_verticesBuffer.create();
-+ updateVerticesBuffer(this->rect());
-+
-+ QVector<QVector2D> textureVertices(6);
-+ rectToTexCoords(textureVertices.data(), QRect(0.0, 0.0, 1.0, 1.0));
-+
-+ m_textureVerticesBuffer.create();
-+ m_textureVerticesBuffer.bind();
-+ m_textureVerticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
-+ m_textureVerticesBuffer.allocate(2 * 3 * sizeof(QVector2D));
-+ m_verticesBuffer.write(0, textureVertices.data(), m_textureVerticesBuffer.size());
-+ m_textureVerticesBuffer.release();
-+
-+ m_vao.release();
-+
-+
-+ if (!m_sourceImage.isNull()) {
-+ loadImage(m_sourceImage);
-+ }
-+}
-+
-+void KisGLImageWidget::updateVerticesBuffer(const QRect &rect)
-+{
-+ if (!m_vao.isCreated() || !m_verticesBuffer.isCreated()) return;
-+
-+ QVector<QVector3D> vertices(6);
-+ rectToVertices(vertices.data(), rect);
-+
-+ m_verticesBuffer.bind();
-+ m_verticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
-+ m_verticesBuffer.allocate(2 * 3 * sizeof(QVector3D));
-+ m_verticesBuffer.write(0, vertices.data(), m_verticesBuffer.size());
-+ m_verticesBuffer.release();
-+}
-+
-+
-+void KisGLImageWidget::paintGL()
-+{
-+ const QColor bgColor = palette().background().color();
-+ glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0f);
-+ glClear(GL_COLOR_BUFFER_BIT);
-+
-+ if (!m_texture.isCreated()) return;
-+
-+ glViewport(0, 0, width(), height());
-+
-+ m_vao.bind();
-+ m_shader.bind();
-+
-+ {
-+ QMatrix4x4 projectionMatrix;
-+ projectionMatrix.setToIdentity();
-+ projectionMatrix.ortho(0, width(), height(), 0, -1, 1);
-+ QMatrix4x4 viewProjectionMatrix;
-+
-+ // use a QTransform to scale, translate, rotate your view
-+ QTransform transform; // TODO: noop!
-+ viewProjectionMatrix = projectionMatrix * QMatrix4x4(transform);
-+
-+ m_shader.setUniformValue("viewProjectionMatrix", viewProjectionMatrix);
-+ }
-+
-+ m_shader.enableAttributeArray("vertexPosition");
-+ m_verticesBuffer.bind();
-+ m_shader.setAttributeBuffer("vertexPosition", GL_FLOAT, 0, 3);
-+
-+ m_shader.enableAttributeArray("texturePosition");
-+ m_textureVerticesBuffer.bind();
-+ m_shader.setAttributeBuffer("texturePosition", GL_FLOAT, 0, 2);
-+
-+ glActiveTexture(GL_TEXTURE0);
-+ m_texture.bind();
-+
-+ // draw 2 triangles = 6 vertices starting at offset 0 in the buffer
-+ glDrawArrays(GL_TRIANGLES, 0, 6);
-+
-+ m_verticesBuffer.release();
-+ m_textureVerticesBuffer.release();
-+ m_texture.release();
-+ m_shader.release();
-+ m_vao.release();
-+}
-+
-+void KisGLImageWidget::loadImage(const KisGLImageF16 &image)
-+{
-+ if (m_sourceImage != image) {
-+ m_sourceImage = image;
-+ }
-+
-+ if (m_vao.isCreated()) {
-+ m_texture.setFormat(QOpenGLTexture::RGBA16F);
-+ m_texture.setSize(image.width(), image.height());
-+ m_texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::Float16);
-+ m_texture.setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
-+ m_texture.setMagnificationFilter(QOpenGLTexture::Linear);
-+ m_texture.setData(QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image.constData());
-+ updateGeometry();
-+ }
-+}
-+
-+void KisGLImageWidget::paintEvent(QPaintEvent *event)
-+{
-+ QOpenGLWidget::paintEvent(event);
-+}
-+
-+void KisGLImageWidget::resizeEvent(QResizeEvent *event)
-+{
-+ updateVerticesBuffer(QRect(QPoint(),event->size()));
-+ QOpenGLWidget::resizeEvent(event);
-+}
-+
-+QSize KisGLImageWidget::sizeHint() const
-+{
-+ return m_sourceImage.size();
-+}
-+
-+KisGLImageF16 KisGLImageWidget::image() const
-+{
-+ return m_sourceImage;
-+}
-+
-diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h
-new file mode 100644
-index 0000000000..e807064cb4
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h
-@@ -0,0 +1,77 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#ifndef KISGLIMAGEWIDGET_H
-+#define KISGLIMAGEWIDGET_H
-+
-+#include <QOpenGLWidget>
-+#include <QOpenGLFunctions>
-+#include <QOpenGLTexture>
-+#include <QOpenGLShaderProgram>
-+#include <QOpenGLVertexArrayObject>
-+#include <QOpenGLBuffer>
-+#include <QTransform>
-+#include <KisGLImageF16.h>
-+
-+
-+class KisGLImageWidget : public QOpenGLWidget, protected QOpenGLFunctions
-+{
-+ Q_OBJECT
-+public:
-+ KisGLImageWidget(QWidget *parent = nullptr);
-+ KisGLImageWidget(QSurfaceFormat::ColorSpace colorSpace,
-+ QWidget *parent = nullptr);
-+
-+ void initializeGL() override;
-+ void paintGL() override;
-+
-+ void loadImage(const KisGLImageF16 &image);
-+
-+ void paintEvent(QPaintEvent *event) override;
-+ void resizeEvent(QResizeEvent *event) override;
-+
-+ QSize sizeHint() const override;
-+
-+ KisGLImageF16 image() const;
-+
-+public Q_SLOTS:
-+
-+private:
-+ void updateVerticesBuffer(const QRect &rect);
-+
-+private:
-+ KisGLImageF16 m_sourceImage;
-+
-+ QOpenGLShaderProgram m_shader;
-+ QOpenGLVertexArrayObject m_vao;
-+ QOpenGLBuffer m_verticesBuffer;
-+ QOpenGLBuffer m_textureVerticesBuffer;
-+ QOpenGLTexture m_texture;
-+};
-+
-+#endif // KISGLIMAGEWIDGET_H
-diff --git a/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro
-new file mode 100644
-index 0000000000..b418e54b43
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro
-@@ -0,0 +1,20 @@
-+QT += widgets widgets-private gui-private core-private
-+
-+TARGET = hdr-openglwidget
-+TEMPLATE = app
-+
-+SOURCES += main.cpp \
-+ #hdr-openglwidget.cpp \
-+ openglprobeutils.cpp \
-+ KisGLImageWidget.cpp \
-+ KisGLImageF16.cpp \
-+ window.cpp
-+
-+HEADERS += \
-+#hdr-openglwidget.h \
-+ openglprobeutils.h \
-+ KisGLImageWidget.h \
-+ KisGLImageF16.h \
-+ window.h
-+
-+RESOURCES += kis_gl_image_widget.qrc
-diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag
-new file mode 100644
-index 0000000000..b57c657046
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag
-@@ -0,0 +1,23 @@
-+#ifndef USE_OPENGLES
-+#define INATTR in
-+#define OUTATTR out
-+#define DECLARE_OUT_VAR out vec4 f_fragColor;
-+#define OUT_VAR f_fragColor
-+#define highp
-+#define texture2D texture
-+#else
-+#define INATTR varying
-+#define DECLARE_OUT_VAR
-+#define OUT_VAR gl_FragColor
-+#endif
-+// vertices data
-+INATTR highp vec4 textureCoordinates;
-+uniform sampler2D f_tileTexture;
-+DECLARE_OUT_VAR
-+
-+void main()
-+{
-+ // get the fragment color from the tile texture
-+ highp vec4 color = texture2D(f_tileTexture, textureCoordinates.st);
-+ OUT_VAR = vec4(color);
-+}
-diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc
-new file mode 100644
-index 0000000000..ab5b5719a9
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc
-@@ -0,0 +1,6 @@
-+<RCC>
-+ <qresource prefix="/">
-+ <file>kis_gl_image_widget.frag</file>
-+ <file>kis_gl_image_widget.vert</file>
-+ </qresource>
-+</RCC>
-diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert
-new file mode 100644
-index 0000000000..9578f47945
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert
-@@ -0,0 +1,17 @@
-+#ifndef USE_OPENGLES
-+#define INATTR in
-+#define OUTATTR out
-+#define highp
-+#else
-+#define INATTR attribute
-+#define OUTATTR varying
-+#endif
-+uniform mat4 viewProjectionMatrix;
-+INATTR highp vec3 vertexPosition;
-+INATTR highp vec2 texturePosition;
-+OUTATTR highp vec4 textureCoordinates;
-+void main()
-+{
-+ textureCoordinates = vec4(texturePosition.x, texturePosition.y, 0.0, 1.0);
-+ gl_Position = viewProjectionMatrix * vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);
-+}
-diff --git a/tests/manual/hdr-qopenglwidget/main.cpp b/tests/manual/hdr-qopenglwidget/main.cpp
-new file mode 100644
-index 0000000000..e517ef8579
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/main.cpp
-@@ -0,0 +1,153 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2016 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#include <QApplication>
-+#include "window.h"
-+
-+#include "openglprobeutils.h"
-+#include <QDebug>
-+
-+QSurfaceFormat generateSurfaceFormat(QSurfaceFormat::RenderableType renderer,
-+ QSurfaceFormat::ColorSpace colorSpace,
-+ int bitDepth)
-+{
-+ QSurfaceFormat format;
-+#ifdef Q_OS_MACOS
-+ format.setVersion(3, 2);
-+ format.setProfile(QSurfaceFormat::CoreProfile);
-+#else
-+ format.setVersion(3, 0);
-+ format.setProfile(QSurfaceFormat::CoreProfile);
-+#endif
-+ format.setDepthBufferSize(24);
-+ format.setStencilBufferSize(8);
-+
-+ switch (bitDepth) {
-+ case 8:
-+ format.setRedBufferSize(8);
-+ format.setGreenBufferSize(8);
-+ format.setBlueBufferSize(8);
-+ format.setAlphaBufferSize(8);
-+ break;
-+ case 10:
-+ format.setRedBufferSize(10);
-+ format.setGreenBufferSize(10);
-+ format.setBlueBufferSize(10);
-+ format.setAlphaBufferSize(2);
-+ break;
-+ case 16:
-+ format.setRedBufferSize(16);
-+ format.setGreenBufferSize(16);
-+ format.setBlueBufferSize(16);
-+ format.setAlphaBufferSize(16);
-+ break;
-+ default:
-+ qFatal("Unsupported surface bit depth %d", bitDepth);
-+ }
-+
-+ format.setRenderableType(renderer);
-+ format.setColorSpace(colorSpace);
-+
-+ format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
-+ format.setSwapInterval(0); // Disable vertical refresh syncing
-+
-+ return format;
-+}
-+
-+int main(int argc, char *argv[])
-+{
-+ QVector<QSurfaceFormat> allFormats;
-+
-+ QVector<QSurfaceFormat::RenderableType> availableRenderers;
-+ availableRenderers << QSurfaceFormat::OpenGL;
-+ availableRenderers << QSurfaceFormat::OpenGLES;
-+
-+ for (QSurfaceFormat::RenderableType renderer : availableRenderers) {
-+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 8);
-+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 8);
-+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 10);
-+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 10);
-+ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::scRGBColorSpace, 16);
-+ }
-+
-+ for (QSurfaceFormat format : allFormats) {
-+ qDebug() << "Probing: " << format;
-+ bool result = OpenGLProbeUtils::probeFormat(format, true);
-+ qDebug() << " result:" << result;
-+ }
-+
-+
-+ if (argc > 1 && !strcmp(argv[1], "--sharecontext")) {
-+ qDebug("Requesting all contexts to share");
-+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
-+ }
-+
-+ QApplication a(argc, argv);
-+
-+ QSurfaceFormat::RenderableType renderer = QSurfaceFormat::OpenGLES;
-+ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::scRGBColorSpace;
-+ int bitDepth = 16;
-+
-+
-+ if (QCoreApplication::arguments().contains(QLatin1String("--scrgb"))) {
-+ colorSpace = QSurfaceFormat::scRGBColorSpace;
-+ bitDepth = 16;
-+ } else if (QCoreApplication::arguments().contains(QLatin1String("--bt2020pq"))) {
-+ colorSpace = QSurfaceFormat::bt2020PQColorSpace;
-+ bitDepth = 10;
-+ } else if (QCoreApplication::arguments().contains(QLatin1String("--srgb"))) {
-+ colorSpace = QSurfaceFormat::sRGBColorSpace;
-+ bitDepth = 8;
-+ }
-+
-+ if (QCoreApplication::arguments().contains(QLatin1String("--opengl"))) {
-+ renderer = QSurfaceFormat::OpenGL;
-+ } else if (QCoreApplication::arguments().contains(QLatin1String("--opengles"))) {
-+ renderer = QSurfaceFormat::OpenGLES;
-+ }
-+
-+ QSurfaceFormat format = generateSurfaceFormat(renderer, colorSpace, bitDepth);
-+
-+ if (QCoreApplication::arguments().contains(QLatin1String("--multisample"))) {
-+ format.setSamples(4);
-+ }
-+
-+ if (format.renderableType() == QSurfaceFormat::OpenGL) {
-+ QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true);
-+ } else if (format.renderableType() == QSurfaceFormat::OpenGLES) {
-+ QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
-+ }
-+
-+ qDebug() << "Requesting" << format.renderableType() << format;
-+ QSurfaceFormat::setDefaultFormat(format);
-+
-+ Window window;
-+ window.show();
-+
-+ return a.exec();
-+}
-diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp
-new file mode 100644
-index 0000000000..687cc08904
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp
-@@ -0,0 +1,139 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#include "openglprobeutils.h"
-+
-+#include <QApplication>
-+#include <QSurfaceFormat>
-+#include <QOpenGLContext>
-+#include <QDebug>
-+#include <QWindow>
-+
-+namespace OpenGLProbeUtils {
-+
-+namespace {
-+
-+struct AppAttributeSetter
-+{
-+ AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES)
-+ : m_attribute(attribute),
-+ m_oldValue(QCoreApplication::testAttribute(attribute))
-+ {
-+ QCoreApplication::setAttribute(attribute, useOpenGLES);
-+ }
-+
-+ ~AppAttributeSetter() {
-+ QCoreApplication::setAttribute(m_attribute, m_oldValue);
-+ }
-+
-+private:
-+ Qt::ApplicationAttribute m_attribute;
-+ bool m_oldValue = false;
-+};
-+
-+struct SurfaceFormatSetter
-+{
-+ SurfaceFormatSetter(const QSurfaceFormat &format)
-+ : m_oldFormat(QSurfaceFormat::defaultFormat())
-+ {
-+ QSurfaceFormat::setDefaultFormat(format);
-+ }
-+
-+ ~SurfaceFormatSetter() {
-+ QSurfaceFormat::setDefaultFormat(m_oldFormat);
-+ }
-+
-+private:
-+ QSurfaceFormat m_oldFormat;
-+};
-+
-+}
-+
-+bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs)
-+{
-+ return lhs == rhs ||
-+ ((lhs == QSurfaceFormat::DefaultColorSpace ||
-+ lhs == QSurfaceFormat::sRGBColorSpace) &&
-+ (rhs == QSurfaceFormat::DefaultColorSpace ||
-+ rhs == QSurfaceFormat::sRGBColorSpace));
-+}
-+
-+bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState)
-+{
-+ QScopedPointer<AppAttributeSetter> sharedContextSetter;
-+ QScopedPointer<AppAttributeSetter> glSetter;
-+ QScopedPointer<AppAttributeSetter> glesSetter;
-+ QScopedPointer<SurfaceFormatSetter> formatSetter;
-+ QScopedPointer<QApplication> application;
-+
-+ if (adjustGlobalState) {
-+ sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false));
-+
-+ if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) {
-+ glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES));
-+ glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES));
-+ }
-+
-+ formatSetter.reset(new SurfaceFormatSetter(format));
-+
-+ int argc = 1;
-+ QByteArray data("krita");
-+ char *argv = data.data();
-+ application.reset(new QApplication(argc, &argv));
-+ }
-+
-+ QWindow surface;
-+ surface.setFormat(format);
-+ surface.setSurfaceType(QSurface::OpenGLSurface);
-+ surface.create();
-+ QOpenGLContext context;
-+ context.setFormat(format);
-+
-+
-+ if (!context.create()) {
-+ qCritical() << "OpenGL context cannot be created";
-+ return false;
-+ }
-+ if (!context.isValid()) {
-+ qCritical() << "OpenGL context is not valid while checking Qt's OpenGL status";
-+ return false;
-+ }
-+ if (!context.makeCurrent(&surface)) {
-+ qCritical() << "OpenGL context cannot be made current";
-+ return false;
-+ }
-+
-+ if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) {
-+ qCritical() << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace();
-+ return false;
-+ }
-+
-+ return true;
-+}
-+
-+}
-diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.h b/tests/manual/hdr-qopenglwidget/openglprobeutils.h
-new file mode 100644
-index 0000000000..3b2f1ec3d0
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.h
-@@ -0,0 +1,42 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#ifndef OPENGLPROBEUTILS_H
-+#define OPENGLPROBEUTILS_H
-+
-+#include <QSurfaceFormat>
-+
-+namespace OpenGLProbeUtils
-+{
-+
-+bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs);
-+bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState);
-+
-+};
-+
-+#endif // OPENGLPROBEUTILS_H
-diff --git a/tests/manual/hdr-qopenglwidget/window.cpp b/tests/manual/hdr-qopenglwidget/window.cpp
-new file mode 100644
-index 0000000000..5729660a4f
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/window.cpp
-@@ -0,0 +1,219 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#include "window.h"
-+
-+#include "KisGLImageWidget.h"
-+#include "KisGLImageF16.h"
-+
-+#include <cmath>
-+
-+#include <QMenu>
-+#include <QMenuBar>
-+#include <QToolBar>
-+#include <QAction>
-+#include <QDebug>
-+
-+#include <QVBoxLayout>
-+#include <QHBoxLayout>
-+#include <QLabel>
-+
-+#include <cmath>
-+
-+
-+Window::Window()
-+{
-+ setWindowTitle("16 bit float QOpenGLWidget test");
-+ QMenu *menu = menuBar()->addMenu("File");
-+ QToolBar *tb = addToolBar("File");
-+
-+ m_quitAction = new QAction("Quit", this);
-+ connect(m_quitAction, SIGNAL(triggered(bool)), this, SLOT(close()));
-+ menu->addAction(m_quitAction);
-+ tb->addAction(m_quitAction);
-+
-+ QWidget *centralWidget = new QWidget(this);
-+ QVBoxLayout *layout = new QVBoxLayout(centralWidget);
-+
-+ QHBoxLayout *hLayout = new QHBoxLayout(centralWidget);
-+
-+ m_imageWidget = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this);
-+ m_imageWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
-+ hLayout->addWidget(m_imageWidget, 0, Qt::AlignLeft);
-+
-+ m_imageWidgetSdr = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this);
-+ m_imageWidgetSdr->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
-+ hLayout->addWidget(m_imageWidgetSdr, 0, Qt::AlignLeft);
-+
-+ QImage image(QSize(255,255), QImage::Format_ARGB32);
-+ image.fill(Qt::red);
-+
-+ QLabel *label = new QLabel(this);
-+ label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
-+ hLayout->addWidget(label, 0, Qt::AlignLeft);
-+
-+ m_imageWidget->loadImage(initializeImage(false));
-+ m_imageWidgetSdr->loadImage(initializeImage(true));
-+ label->setPixmap(QPixmap::fromImage(convertToQImage(m_imageWidget->image())));
-+
-+ layout->addLayout(hLayout);
-+
-+ m_lblContextInfo = new QLabel(this);
-+ layout->addWidget(m_lblContextInfo);
-+
-+ QLabel *lblNotes = new QLabel(this);
-+ lblNotes->setWordWrap(true);
-+ lblNotes->setText("* In SDR display mode all three images should look exactly the same\n"
-+ "* In HDR display mode: image 1 should look brighter than the others "
-+ "(it is HDR), images 2 and 3 should have exactly the same brightness and look");
-+
-+ layout->addWidget(lblNotes);
-+
-+ centralWidget->setLayout(layout);
-+ setCentralWidget(centralWidget);
-+}
-+
-+inline qfloat16 floatToFloat16(float x) {
-+ qfloat16 result;
-+ qFloatToFloat16(&result, &x, 1);
-+ return result;
-+}
-+
-+inline float float16ToFloat(qfloat16 x) {
-+ float result;
-+ qFloatFromFloat16(&result, &x, 1);
-+ return result;
-+}
-+
-+
-+KisGLImageF16 Window::initializeImage(bool cropRange) const
-+{
-+ const int size = 256;
-+ KisGLImageF16 image(size, size);
-+ image.clearPixels();
-+ qfloat16 *pixelPtr = image.data();
-+
-+ for (int y = 0; y < size; y++) {
-+ for (int x = 0; x < size; x++) {
-+ qfloat16 *pxl = reinterpret_cast<qfloat16*>(pixelPtr);
-+
-+ float hdrRedValue = 25.0f * std::pow(float(x) / size, 5.0f);
-+
-+ if (cropRange) {
-+ hdrRedValue = qMin(hdrRedValue, 1.0f);
-+ }
-+
-+ pxl[0] = floatToFloat16(hdrRedValue);
-+
-+ if (y > size / 2) {
-+ const float portion = (float(y) / size - 0.5f) * 2.0f;
-+ const float value = qMin(1.0f, 0.2f + 1.8f * portion);
-+
-+ pxl[1] = floatToFloat16(value);
-+ pxl[2] = floatToFloat16(value);
-+ } else {
-+ pxl[1] = floatToFloat16(0.2);
-+ pxl[2] = floatToFloat16(0.2);
-+ }
-+
-+ pxl[3] = floatToFloat16(1.0);
-+
-+ pixelPtr += 4;
-+ }
-+ }
-+
-+ return image;
-+}
-+
-+inline float linearToSRGB(float value)
-+{
-+ if (value <= 0.0f) {
-+ value = 0.0f;
-+ } else if (value < 0.0031308f) {
-+ value = value * 12.92f;
-+ } else if (value < 1.0f) {
-+ value = std::pow(value, 0.41666f) * 1.055f - 0.055f;
-+ } else {
-+ value = 1.0f;
-+ }
-+ return value;
-+}
-+
-+QImage Window::convertToQImage(const KisGLImageF16 &image) const
-+{
-+ const QSize size = image.size();
-+ const qfloat16 *pixelPtr = image.constData();
-+
-+ QImage qimage(size, QImage::Format_ARGB32);
-+ quint8 *qimagePixelPtr = qimage.bits();
-+
-+
-+ for (int y = 0; y < size.height(); y++) {
-+ for (int x = 0; x < size.width(); x++) {
-+ const qfloat16 *srcPxl = pixelPtr;
-+ quint8 *dstPxl = qimagePixelPtr;
-+
-+ auto convertChannel = [] (qfloat16 x) {
-+ float value = float16ToFloat(x);
-+ return quint8(linearToSRGB(value) * 255.0f);
-+ };
-+
-+ dstPxl[0] = convertChannel(srcPxl[2]);
-+ dstPxl[1] = convertChannel(srcPxl[1]);
-+ dstPxl[2] = convertChannel(srcPxl[0]);
-+ dstPxl[3] = convertChannel(srcPxl[3]);
-+
-+ pixelPtr += 4;
-+ qimagePixelPtr += 4;
-+ }
-+ }
-+
-+ return qimage;
-+}
-+
-+void Window::updateSurfaceInfo()
-+{
-+ const QSurfaceFormat format = m_imageWidget->context()->format();
-+
-+ m_lblContextInfo->setText(
-+ QString("renderer: %1\ncolorSpace: %2\n\n")
-+ .arg(format.renderableType() == QSurfaceFormat::OpenGL ? "openGL" : "openGL ES")
-+ .arg(format.colorSpace() == QSurfaceFormat::sRGBColorSpace ? "sRGB" :
-+ format.colorSpace() == QSurfaceFormat::scRGBColorSpace ? "scRGB" :
-+ format.colorSpace() == QSurfaceFormat::bt2020PQColorSpace ? "Bt. 2020 PQ" :
-+ "unknown"));
-+}
-+
-+void Window::showEvent(QShowEvent *ev)
-+{
-+ QMainWindow::showEvent(ev);
-+
-+ if (m_lblContextInfo->text().isEmpty()) {
-+ updateSurfaceInfo();
-+ }
-+}
-diff --git a/tests/manual/hdr-qopenglwidget/window.h b/tests/manual/hdr-qopenglwidget/window.h
-new file mode 100644
-index 0000000000..fd8e5c0393
---- /dev/null
-+++ b/tests/manual/hdr-qopenglwidget/window.h
-@@ -0,0 +1,69 @@
-+/****************************************************************************
-+**
-+** Copyright (C) 2019 The Qt Company Ltd.
-+** Contact: https://www.qt.io/licensing/
-+**
-+** This file is part of the test suite of the Qt Toolkit.
-+**
-+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-+** Commercial License Usage
-+** Licensees holding valid commercial Qt licenses may use this file in
-+** accordance with the commercial license agreement provided with the
-+** Software or, alternatively, in accordance with the terms contained in
-+** a written agreement between you and The Qt Company. For licensing terms
-+** and conditions see https://www.qt.io/terms-conditions. For further
-+** information use the contact form at https://www.qt.io/contact-us.
-+**
-+** GNU General Public License Usage
-+** Alternatively, this file may be used under the terms of the GNU
-+** General Public License version 3 as published by the Free Software
-+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-+** included in the packaging of this file. Please review the following
-+** information to ensure the GNU General Public License requirements will
-+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-+**
-+** $QT_END_LICENSE$
-+**
-+****************************************************************************/
-+
-+#ifndef WINDOW_H
-+#define WINDOW_H
-+
-+#include <QMainWindow>
-+
-+class QAction;
-+
-+class GLWidget;
-+class KisGLImageWidget;
-+class KisGLImageF16;
-+class QLabel;
-+
-+class Window : public QMainWindow
-+{
-+ Q_OBJECT
-+
-+public:
-+ Window();
-+
-+ void showEvent(QShowEvent *ev) override;
-+
-+public Q_SLOTS:
-+
-+
-+private:
-+ KisGLImageF16 initializeImage(bool cropRange) const;
-+ QImage convertToQImage(const KisGLImageF16 &image) const;
-+
-+ void updateSurfaceInfo();
-+
-+private:
-+ GLWidget *m_glWidget {0};
-+ QAction *m_openAction {0};
-+ QAction *m_quitAction {0};
-+ KisGLImageWidget *m_imageWidget;
-+ KisGLImageWidget *m_imageWidgetSdr;
-+ QLabel *m_lblContextInfo;
-+
-+};
-+
-+#endif
-diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
-index ab00a5ef60..b202ed0431 100644
---- a/tests/manual/manual.pro
-+++ b/tests/manual/manual.pro
-@@ -59,7 +59,7 @@ qtabbar
-
- qtConfig(opengl) {
- SUBDIRS += qopengltextureblitter
-- qtConfig(egl): SUBDIRS += qopenglcontext
-+ qtConfig(egl): SUBDIRS += qopenglcontext hdr-qopenglwidget
- }
-
- win32: SUBDIRS -= network_remote_stresstest network_stresstest
---
-2.20.1.windows.1
-
-
-From abd67442a2aec3ece2025e1154aa3c58599e2471 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Thu, 6 Dec 2018 16:16:27 +0300
-Subject: [PATCH 6/6] Fix notification of QDockWidget when it gets undocked
-
-Before the patch the notification was emitted only when the docker
-was attached to the panel or changed a position on it.
-
-Change-Id: Id3ffbd2018a8e68844d174328dd1c4ceb7fa01d3
----
- src/widgets/widgets/qdockwidget.cpp | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp
-index 6c871aae2c..19fc2d1677 100644
---- a/src/widgets/widgets/qdockwidget.cpp
-+++ b/src/widgets/widgets/qdockwidget.cpp
-@@ -1171,6 +1171,8 @@ void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect
- QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q);
- if (mwlayout)
- emit q->dockLocationChanged(mwlayout->dockWidgetArea(q));
-+ } else {
-+ emit q->dockLocationChanged(Qt::NoDockWidgetArea);
- }
- }
-
---
-2.20.1.windows.1
-
diff --git a/3rdparty/ext_qt/0006-Fix-swizzling-when-rendering-QPainter-on-QOpenGLWidg.patch b/3rdparty/ext_qt/0006-Fix-swizzling-when-rendering-QPainter-on-QOpenGLWidg.patch
deleted file mode 100644
index 967c848203..0000000000
--- a/3rdparty/ext_qt/0006-Fix-swizzling-when-rendering-QPainter-on-QOpenGLWidg.patch
+++ /dev/null
@@ -1,120 +0,0 @@
-From 9ef8cb9a95962864dc4bd506b9a2cdb462b0c908 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Thu, 4 Apr 2019 19:11:47 +0300
-Subject: [PATCH] Fix swizzling when rendering QPainter on QOpenGLWidget with
- Angle
-
-OpenGLES specification does not support GL_TEXTURE_SWIZZLE_RGBA,
-it supports only per-channel calls. And since Qt supports QpenGLES,
-it should use the latter approach only.
-
-The regression was introduced in Qt 5.12 by commit
-ede3791df8330ed8daae6667d025ad40219a9f5f
-
-Task-number: QTBUG-74968
----
- src/gui/opengl/qopengltexture.cpp | 12 ++++----
- src/gui/opengl/qopengltextureuploader.cpp | 36 ++++++++++-------------
- 2 files changed, 22 insertions(+), 26 deletions(-)
-
-diff --git a/src/gui/opengl/qopengltexture.cpp b/src/gui/opengl/qopengltexture.cpp
-index 5b7956d3..a33d4763 100644
---- a/src/gui/opengl/qopengltexture.cpp
-+++ b/src/gui/opengl/qopengltexture.cpp
-@@ -4015,12 +4015,12 @@ void QOpenGLTexture::setSwizzleMask(SwizzleValue r, SwizzleValue g,
- qWarning("QOpenGLTexture::setSwizzleMask() requires OpenGL >= 3.3");
- return;
- }
-- GLint swizzleMask[] = {GLint(r), GLint(g), GLint(b), GLint(a)};
-- d->swizzleMask[0] = r;
-- d->swizzleMask[1] = g;
-- d->swizzleMask[2] = b;
-- d->swizzleMask[3] = a;
-- d->texFuncs->glTextureParameteriv(d->textureId, d->target, d->bindingTarget, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
-+
-+ d->texFuncs->glTextureParameteri(d->textureId, d->target, d->bindingTarget, GL_TEXTURE_SWIZZLE_R, GLint(r));
-+ d->texFuncs->glTextureParameteri(d->textureId, d->target, d->bindingTarget, GL_TEXTURE_SWIZZLE_G, GLint(g));
-+ d->texFuncs->glTextureParameteri(d->textureId, d->target, d->bindingTarget, GL_TEXTURE_SWIZZLE_B, GLint(b));
-+ d->texFuncs->glTextureParameteri(d->textureId, d->target, d->bindingTarget, GL_TEXTURE_SWIZZLE_A, GLint(a));
-+
- return;
- }
- #else
-diff --git a/src/gui/opengl/qopengltextureuploader.cpp b/src/gui/opengl/qopengltextureuploader.cpp
-index 42e309b7..8d204ddc 100644
---- a/src/gui/opengl/qopengltextureuploader.cpp
-+++ b/src/gui/opengl/qopengltextureuploader.cpp
-@@ -77,10 +77,6 @@
- #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
- #endif
-
--#ifndef GL_TEXTURE_SWIZZLE_RGBA
--#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46
--#endif
--
- #ifndef GL_SRGB
- #define GL_SRGB 0x8C40
- #endif
-@@ -114,14 +110,10 @@ qsizetype QOpenGLTextureUploader::textureImage(GLenum target, const QImage &imag
- externalFormat = GL_BGRA;
- internalFormat = GL_RGBA;
- pixelType = GL_UNSIGNED_INT_8_8_8_8_REV;
-- } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle)) {
--#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
-- GLint swizzle[4] = { GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA };
-- funcs->glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
--#else
-- GLint swizzle[4] = { GL_GREEN, GL_BLUE, GL_ALPHA, GL_RED };
-- funcs->glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
--#endif
-+ } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle) && (isOpenGL12orBetter || isOpenGLES3orBetter)) {
-+ funcs->glTexParameteri(target, GL_TEXTURE_SWIZZLE_B, GL_RED);
-+ funcs->glTexParameteri(target, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
-+
- externalFormat = internalFormat = GL_RGBA;
- pixelType = GL_UNSIGNED_BYTE;
- } else {
-@@ -173,8 +165,8 @@ qsizetype QOpenGLTextureUploader::textureImage(GLenum target, const QImage &imag
- pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
- externalFormat = GL_RGBA;
- internalFormat = GL_RGB10_A2;
-- GLint swizzle[4] = { GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA };
-- funcs->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
- targetFormat = image.format();
- }
- break;
-@@ -231,9 +223,11 @@ qsizetype QOpenGLTextureUploader::textureImage(GLenum target, const QImage &imag
- externalFormat = internalFormat = GL_ALPHA;
- pixelType = GL_UNSIGNED_BYTE;
- targetFormat = image.format();
-- } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle)) {
-- GLint swizzle[4] = { GL_ZERO, GL_ZERO, GL_ZERO, GL_RED };
-- funcs->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
-+ } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle) && (isOpenGL12orBetter || isOpenGLES3orBetter)) {
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ALPHA);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_ZERO);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_ZERO);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ZERO);
- externalFormat = internalFormat = GL_RED;
- pixelType = GL_UNSIGNED_BYTE;
- targetFormat = image.format();
-@@ -251,9 +245,11 @@ qsizetype QOpenGLTextureUploader::textureImage(GLenum target, const QImage &imag
- externalFormat = internalFormat = GL_LUMINANCE;
- pixelType = GL_UNSIGNED_BYTE;
- targetFormat = image.format();
-- } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle)) {
-- GLint swizzle[4] = { GL_RED, GL_RED, GL_RED, GL_ONE };
-- funcs->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
-+ } else if (funcs->hasOpenGLExtension(QOpenGLExtensions::TextureSwizzle) && (isOpenGL12orBetter || isOpenGLES3orBetter)) {
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
-+ funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
- externalFormat = internalFormat = GL_RED;
- pixelType = GL_UNSIGNED_BYTE;
- targetFormat = image.format();
---
-2.20.1.windows.1
-
diff --git a/3rdparty/ext_qt/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch b/3rdparty/ext_qt/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch
new file mode 100644
index 0000000000..92edcd0c75
--- /dev/null
+++ b/3rdparty/ext_qt/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch
@@ -0,0 +1,95 @@
+From a1131ad9ab08c86eb32ca3f294690a698470bda1 Mon Sep 17 00:00:00 2001
+From: Dmitry Kazakov <dimula73@gmail.com>
+Date: Tue, 4 Dec 2018 20:11:34 +0300
+Subject: [PATCH 06/17] Return QScreen's HMONITOR handle via
+ QPlatformNativeInterface
+
+It is needed to be able to fetch extra information about the display via
+DXGI interface.
+
+Change-Id: Id83982eb07ade157719e430d0abcc2613409a343
+---
+ .../windows/qwindowsnativeinterface.cpp | 16 ++++++++++++++++
+ .../platforms/windows/qwindowsnativeinterface.h | 1 +
+ src/plugins/platforms/windows/qwindowsscreen.cpp | 5 +++++
+ src/plugins/platforms/windows/qwindowsscreen.h | 2 ++
+ 4 files changed, 24 insertions(+)
+
+diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
+index ed945ec4b1..1c5be44150 100644
+--- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
++++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
+@@ -40,6 +40,7 @@
+ #include "qwindowsnativeinterface.h"
+ #include "qwindowsclipboard.h"
+ #include "qwindowswindow.h"
++#include "qwindowsscreen.h"
+ #include "qwindowscontext.h"
+ #include "qwindowscursor.h"
+ #include "qwindowsopenglcontext.h"
+@@ -124,6 +125,21 @@ void *QWindowsNativeInterface::nativeResourceForWindow(const QByteArray &resourc
+ return nullptr;
+ }
+
++void *QWindowsNativeInterface::nativeResourceForScreen(const QByteArray &resource, QScreen *screen)
++{
++ if (!screen || !screen->handle()) {
++ qWarning("%s: '%s' requested for null screen or screen without handle.", __FUNCTION__, resource.constData());
++ return 0;
++ }
++ QWindowsScreen *bs = static_cast<QWindowsScreen *>(screen->handle());
++ int type = resourceType(resource);
++ if (type == HandleType)
++ return bs->handle();
++
++ qWarning("%s: Invalid key '%s' requested.", __FUNCTION__, resource.constData());
++ return 0;
++}
++
+ #ifndef QT_NO_CURSOR
+ void *QWindowsNativeInterface::nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor)
+ {
+diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.h b/src/plugins/platforms/windows/qwindowsnativeinterface.h
+index e6f8aae8fb..ce395dc5a4 100644
+--- a/src/plugins/platforms/windows/qwindowsnativeinterface.h
++++ b/src/plugins/platforms/windows/qwindowsnativeinterface.h
+@@ -74,6 +74,7 @@ public:
+ void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) override;
+ #endif
+ void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override;
++ void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen) override;
+ #ifndef QT_NO_CURSOR
+ void *nativeResourceForCursor(const QByteArray &resource, const QCursor &cursor) override;
+ #endif
+diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp
+index b28a113ce6..2f8850cbe0 100644
+--- a/src/plugins/platforms/windows/qwindowsscreen.cpp
++++ b/src/plugins/platforms/windows/qwindowsscreen.cpp
+@@ -323,6 +323,11 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
+ }
+ }
+
++HMONITOR QWindowsScreen::handle() const
++{
++ return m_data.hMonitor;
++}
++
+ QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry()
+ {
+ QRect result;
+diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h
+index 8ad012512e..3eb2d35b27 100644
+--- a/src/plugins/platforms/windows/qwindowsscreen.h
++++ b/src/plugins/platforms/windows/qwindowsscreen.h
+@@ -104,6 +104,8 @@ public:
+
+ inline void handleChanges(const QWindowsScreenData &newData);
+
++ HMONITOR handle() const;
++
+ #ifndef QT_NO_CURSOR
+ QPlatformCursor *cursor() const override { return m_cursor.data(); }
+ const CursorPtr &cursorPtr() const { return m_cursor; }
+--
+2.20.1.windows.1
+
diff --git a/3rdparty/ext_qt/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch b/3rdparty/ext_qt/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch
new file mode 100644
index 0000000000..9d2a59096a
--- /dev/null
+++ b/3rdparty/ext_qt/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch
@@ -0,0 +1,1368 @@
+From fc02647fc01c20f3bfcc53f38a060328b9e2ab90 Mon Sep 17 00:00:00 2001
+From: Dmitry Kazakov <dimula73@gmail.com>
+Date: Sun, 10 Feb 2019 22:55:59 +0300
+Subject: [PATCH 07/17] Implement a manual test for checking is HDR features
+ work
+
+Test plan:
+
+1) Run without arguments: `hdr-openglwidget.exe`
+ It should show you three rectangles: the left one should be HDR'ly
+ bright, the other ones should be SDR'ly dim and look exactly the same.
+
+3) Run in Bt. 2020 PQ mode: `hdr-openglwidget.exe --bt2020pq`
+ The result should look exactly the same.
+
+4) Run in SDR sRGB mode: `hdr-openglwidget.exe --srgb`.
+ All three images should look SDR'ly dim.
+
+NOTE:
+Please note that the current implementation of SDR compositing
+in QOpenGLTextureBlitter doesn't support user configuration for
+SDR brightness from the system. This API is available for UWP
+applications only. It means that when changing "SDR brightness"
+slider in Windows' settings, the brightness of our SDR widget
+will not change. More that that, it might even be different from
+the brightness of other SDR applications.
+
+Change-Id: Idccc790937c9061ec618ab21f6b71bd0620cd2cc
+---
+ .../hdr-qopenglwidget/KisGLImageF16.cpp | 131 +++++++++
+ .../manual/hdr-qopenglwidget/KisGLImageF16.h | 68 +++++
+ .../hdr-qopenglwidget/KisGLImageWidget.cpp | 252 ++++++++++++++++++
+ .../hdr-qopenglwidget/KisGLImageWidget.h | 77 ++++++
+ .../hdr-qopenglwidget/hdr-openglwidget.pro | 20 ++
+ .../kis_gl_image_widget.frag | 23 ++
+ .../hdr-qopenglwidget/kis_gl_image_widget.qrc | 6 +
+ .../kis_gl_image_widget.vert | 17 ++
+ tests/manual/hdr-qopenglwidget/main.cpp | 153 +++++++++++
+ .../hdr-qopenglwidget/openglprobeutils.cpp | 139 ++++++++++
+ .../hdr-qopenglwidget/openglprobeutils.h | 42 +++
+ tests/manual/hdr-qopenglwidget/window.cpp | 219 +++++++++++++++
+ tests/manual/hdr-qopenglwidget/window.h | 69 +++++
+ tests/manual/manual.pro | 2 +-
+ 14 files changed, 1217 insertions(+), 1 deletion(-)
+ create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp
+ create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageF16.h
+ create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp
+ create mode 100644 tests/manual/hdr-qopenglwidget/KisGLImageWidget.h
+ create mode 100644 tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro
+ create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag
+ create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc
+ create mode 100644 tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert
+ create mode 100644 tests/manual/hdr-qopenglwidget/main.cpp
+ create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.cpp
+ create mode 100644 tests/manual/hdr-qopenglwidget/openglprobeutils.h
+ create mode 100644 tests/manual/hdr-qopenglwidget/window.cpp
+ create mode 100644 tests/manual/hdr-qopenglwidget/window.h
+
+diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp
+new file mode 100644
+index 0000000000..a84b676f5b
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.cpp
+@@ -0,0 +1,131 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#include "KisGLImageF16.h"
++
++#include <QByteArray>
++#include <QSize>
++
++struct KisGLImageF16::Private : public QSharedData
++{
++ QSize size;
++ QByteArray data;
++};
++
++KisGLImageF16::KisGLImageF16()
++ : m_d(new Private)
++{
++}
++
++KisGLImageF16::KisGLImageF16(const QSize &size, bool clearPixels)
++ : m_d(new Private)
++{
++ resize(size, clearPixels);
++}
++
++KisGLImageF16::KisGLImageF16(int width, int height, bool clearPixels)
++ : KisGLImageF16(QSize(width, height), clearPixels)
++{
++}
++
++KisGLImageF16::KisGLImageF16(const KisGLImageF16 &rhs)
++ : m_d(rhs.m_d)
++{
++}
++
++KisGLImageF16 &KisGLImageF16::operator=(const KisGLImageF16 &rhs)
++{
++ m_d = rhs.m_d;
++}
++
++bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs)
++{
++ return lhs.m_d == rhs.m_d;
++}
++
++bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs)
++{
++ return !(lhs == rhs);
++}
++
++KisGLImageF16::~KisGLImageF16()
++{
++}
++
++void KisGLImageF16::clearPixels()
++{
++ if (!m_d->data.isEmpty()) {
++ m_d->data.fill(0);
++ }
++}
++
++void KisGLImageF16::resize(const QSize &size, bool clearPixels)
++{
++ const int pixelSize = 2 * 4;
++
++ m_d->size = size;
++ m_d->data.resize(size.width() * size.height() * pixelSize);
++
++ if (clearPixels) {
++ m_d->data.fill(0);
++ }
++}
++
++const qfloat16 *KisGLImageF16::constData() const
++{
++ Q_ASSERT(!m_d->data.isNull());
++ return reinterpret_cast<const qfloat16*>(m_d->data.data());
++}
++
++qfloat16 *KisGLImageF16::data()
++{
++ m_d->data.detach();
++ Q_ASSERT(!m_d->data.isNull());
++
++ return reinterpret_cast<qfloat16*>(m_d->data.data());
++}
++
++QSize KisGLImageF16::size() const
++{
++ return m_d->size;
++}
++
++int KisGLImageF16::width() const
++{
++ return m_d->size.width();
++}
++
++int KisGLImageF16::height() const
++{
++ return m_d->size.height();
++}
++
++bool KisGLImageF16::isNull() const
++{
++ return m_d->data.isNull();
++}
+diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageF16.h b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h
+new file mode 100644
+index 0000000000..335e42ee68
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/KisGLImageF16.h
+@@ -0,0 +1,68 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#ifndef KISGLIMAGEF16_H
++#define KISGLIMAGEF16_H
++
++#include <QSharedDataPointer>
++#include <QFloat16>
++
++class QSize;
++
++class KisGLImageF16
++{
++public:
++ KisGLImageF16();
++ KisGLImageF16(const QSize &size, bool clearPixels = false);
++ KisGLImageF16(int width, int height, bool clearPixels = false);
++ KisGLImageF16(const KisGLImageF16 &rhs);
++ KisGLImageF16& operator=(const KisGLImageF16 &rhs);
++
++ friend bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs);
++ friend bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs);
++
++ ~KisGLImageF16();
++
++ void clearPixels();
++ void resize(const QSize &size, bool clearPixels = false);
++
++ const qfloat16* constData() const;
++ qfloat16* data();
++
++ QSize size() const;
++ int width() const;
++ int height() const;
++
++ bool isNull() const;
++
++private:
++ struct Private;
++ QSharedDataPointer<Private> m_d;
++};
++
++#endif // KISGLIMAGEF16_H
+diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp
+new file mode 100644
+index 0000000000..da36ac1619
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.cpp
+@@ -0,0 +1,252 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#include "KisGLImageWidget.h"
++
++#include <QPainter>
++#include <QFile>
++#include <QResizeEvent>
++
++#include "KisGLImageF16.h"
++
++namespace {
++inline void rectToVertices(QVector3D* vertices, const QRectF &rc)
++{
++ vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f);
++ vertices[1] = QVector3D(rc.left(), rc.top(), 0.f);
++ vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f);
++ vertices[3] = QVector3D(rc.left(), rc.top(), 0.f);
++ vertices[4] = QVector3D(rc.right(), rc.top(), 0.f);
++ vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f);
++}
++
++inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc)
++{
++ texCoords[0] = QVector2D(rc.left(), rc.bottom());
++ texCoords[1] = QVector2D(rc.left(), rc.top());
++ texCoords[2] = QVector2D(rc.right(), rc.bottom());
++ texCoords[3] = QVector2D(rc.left(), rc.top());
++ texCoords[4] = QVector2D(rc.right(), rc.top());
++ texCoords[5] = QVector2D(rc.right(), rc.bottom());
++}
++}
++
++KisGLImageWidget::KisGLImageWidget(QWidget *parent)
++ : KisGLImageWidget(QSurfaceFormat::sRGBColorSpace, parent)
++{
++}
++
++KisGLImageWidget::KisGLImageWidget(QSurfaceFormat::ColorSpace colorSpace,
++ QWidget *parent)
++ : QOpenGLWidget(parent),
++ m_texture(QOpenGLTexture::Target2D)
++{
++
++ qDebug() << "Crating gl widget";
++
++ setTextureFormat(GL_RGBA16F);
++ setTextureColorSpace(colorSpace);
++
++ setUpdateBehavior(QOpenGLWidget::NoPartialUpdate);
++}
++
++void KisGLImageWidget::initializeGL()
++{
++ initializeOpenGLFunctions();
++
++ qDebug() << "Initialized with format:" << context()->format();
++
++ QFile vertexShaderFile(QString(":/") + "kis_gl_image_widget.vert");
++ vertexShaderFile.open(QIODevice::ReadOnly);
++ QString vertSource = vertexShaderFile.readAll();
++
++ QFile fragShaderFile(QString(":/") + "kis_gl_image_widget.frag");
++ fragShaderFile.open(QIODevice::ReadOnly);
++ QString fragSource = fragShaderFile.readAll();
++
++ if (context()->isOpenGLES()) {
++ const char *versionHelper = "#define USE_OPENGLES\n";
++ vertSource.prepend(versionHelper);
++ fragSource.prepend(versionHelper);
++
++ const char *versionDefinition = "#version 100\n";
++ vertSource.prepend(versionDefinition);
++ fragSource.prepend(versionDefinition);
++ } else {
++ const char *versionDefinition = "#version 330 core\n";
++ vertSource.prepend(versionDefinition);
++ fragSource.prepend(versionDefinition);
++ }
++
++ if (!m_shader.addShaderFromSourceCode(QOpenGLShader::Vertex, vertSource)) {
++ qDebug() << "Could not add vertex code";
++ return;
++ }
++
++ if (!m_shader.addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource)) {
++ qDebug() << "Could not add fragment code";
++ return;
++ }
++
++ if (!m_shader.link()) {
++ qDebug() << "Could not link";
++ return;
++ }
++
++ if (!m_shader.bind()) {
++ qDebug() << "Could not bind";
++ return;
++ }
++
++ m_shader.release();
++
++
++ m_vao.create();
++ m_vao.bind();
++
++ m_verticesBuffer.create();
++ updateVerticesBuffer(this->rect());
++
++ QVector<QVector2D> textureVertices(6);
++ rectToTexCoords(textureVertices.data(), QRect(0.0, 0.0, 1.0, 1.0));
++
++ m_textureVerticesBuffer.create();
++ m_textureVerticesBuffer.bind();
++ m_textureVerticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
++ m_textureVerticesBuffer.allocate(2 * 3 * sizeof(QVector2D));
++ m_verticesBuffer.write(0, textureVertices.data(), m_textureVerticesBuffer.size());
++ m_textureVerticesBuffer.release();
++
++ m_vao.release();
++
++
++ if (!m_sourceImage.isNull()) {
++ loadImage(m_sourceImage);
++ }
++}
++
++void KisGLImageWidget::updateVerticesBuffer(const QRect &rect)
++{
++ if (!m_vao.isCreated() || !m_verticesBuffer.isCreated()) return;
++
++ QVector<QVector3D> vertices(6);
++ rectToVertices(vertices.data(), rect);
++
++ m_verticesBuffer.bind();
++ m_verticesBuffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
++ m_verticesBuffer.allocate(2 * 3 * sizeof(QVector3D));
++ m_verticesBuffer.write(0, vertices.data(), m_verticesBuffer.size());
++ m_verticesBuffer.release();
++}
++
++
++void KisGLImageWidget::paintGL()
++{
++ const QColor bgColor = palette().background().color();
++ glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), 1.0f);
++ glClear(GL_COLOR_BUFFER_BIT);
++
++ if (!m_texture.isCreated()) return;
++
++ glViewport(0, 0, width(), height());
++
++ m_vao.bind();
++ m_shader.bind();
++
++ {
++ QMatrix4x4 projectionMatrix;
++ projectionMatrix.setToIdentity();
++ projectionMatrix.ortho(0, width(), height(), 0, -1, 1);
++ QMatrix4x4 viewProjectionMatrix;
++
++ // use a QTransform to scale, translate, rotate your view
++ QTransform transform; // TODO: noop!
++ viewProjectionMatrix = projectionMatrix * QMatrix4x4(transform);
++
++ m_shader.setUniformValue("viewProjectionMatrix", viewProjectionMatrix);
++ }
++
++ m_shader.enableAttributeArray("vertexPosition");
++ m_verticesBuffer.bind();
++ m_shader.setAttributeBuffer("vertexPosition", GL_FLOAT, 0, 3);
++
++ m_shader.enableAttributeArray("texturePosition");
++ m_textureVerticesBuffer.bind();
++ m_shader.setAttributeBuffer("texturePosition", GL_FLOAT, 0, 2);
++
++ glActiveTexture(GL_TEXTURE0);
++ m_texture.bind();
++
++ // draw 2 triangles = 6 vertices starting at offset 0 in the buffer
++ glDrawArrays(GL_TRIANGLES, 0, 6);
++
++ m_verticesBuffer.release();
++ m_textureVerticesBuffer.release();
++ m_texture.release();
++ m_shader.release();
++ m_vao.release();
++}
++
++void KisGLImageWidget::loadImage(const KisGLImageF16 &image)
++{
++ if (m_sourceImage != image) {
++ m_sourceImage = image;
++ }
++
++ if (m_vao.isCreated()) {
++ m_texture.setFormat(QOpenGLTexture::RGBA16F);
++ m_texture.setSize(image.width(), image.height());
++ m_texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::Float16);
++ m_texture.setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
++ m_texture.setMagnificationFilter(QOpenGLTexture::Linear);
++ m_texture.setData(QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image.constData());
++ updateGeometry();
++ }
++}
++
++void KisGLImageWidget::paintEvent(QPaintEvent *event)
++{
++ QOpenGLWidget::paintEvent(event);
++}
++
++void KisGLImageWidget::resizeEvent(QResizeEvent *event)
++{
++ updateVerticesBuffer(QRect(QPoint(),event->size()));
++ QOpenGLWidget::resizeEvent(event);
++}
++
++QSize KisGLImageWidget::sizeHint() const
++{
++ return m_sourceImage.size();
++}
++
++KisGLImageF16 KisGLImageWidget::image() const
++{
++ return m_sourceImage;
++}
++
+diff --git a/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h
+new file mode 100644
+index 0000000000..e807064cb4
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/KisGLImageWidget.h
+@@ -0,0 +1,77 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#ifndef KISGLIMAGEWIDGET_H
++#define KISGLIMAGEWIDGET_H
++
++#include <QOpenGLWidget>
++#include <QOpenGLFunctions>
++#include <QOpenGLTexture>
++#include <QOpenGLShaderProgram>
++#include <QOpenGLVertexArrayObject>
++#include <QOpenGLBuffer>
++#include <QTransform>
++#include <KisGLImageF16.h>
++
++
++class KisGLImageWidget : public QOpenGLWidget, protected QOpenGLFunctions
++{
++ Q_OBJECT
++public:
++ KisGLImageWidget(QWidget *parent = nullptr);
++ KisGLImageWidget(QSurfaceFormat::ColorSpace colorSpace,
++ QWidget *parent = nullptr);
++
++ void initializeGL() override;
++ void paintGL() override;
++
++ void loadImage(const KisGLImageF16 &image);
++
++ void paintEvent(QPaintEvent *event) override;
++ void resizeEvent(QResizeEvent *event) override;
++
++ QSize sizeHint() const override;
++
++ KisGLImageF16 image() const;
++
++public Q_SLOTS:
++
++private:
++ void updateVerticesBuffer(const QRect &rect);
++
++private:
++ KisGLImageF16 m_sourceImage;
++
++ QOpenGLShaderProgram m_shader;
++ QOpenGLVertexArrayObject m_vao;
++ QOpenGLBuffer m_verticesBuffer;
++ QOpenGLBuffer m_textureVerticesBuffer;
++ QOpenGLTexture m_texture;
++};
++
++#endif // KISGLIMAGEWIDGET_H
+diff --git a/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro
+new file mode 100644
+index 0000000000..b418e54b43
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/hdr-openglwidget.pro
+@@ -0,0 +1,20 @@
++QT += widgets widgets-private gui-private core-private
++
++TARGET = hdr-openglwidget
++TEMPLATE = app
++
++SOURCES += main.cpp \
++ #hdr-openglwidget.cpp \
++ openglprobeutils.cpp \
++ KisGLImageWidget.cpp \
++ KisGLImageF16.cpp \
++ window.cpp
++
++HEADERS += \
++#hdr-openglwidget.h \
++ openglprobeutils.h \
++ KisGLImageWidget.h \
++ KisGLImageF16.h \
++ window.h
++
++RESOURCES += kis_gl_image_widget.qrc
+diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag
+new file mode 100644
+index 0000000000..b57c657046
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.frag
+@@ -0,0 +1,23 @@
++#ifndef USE_OPENGLES
++#define INATTR in
++#define OUTATTR out
++#define DECLARE_OUT_VAR out vec4 f_fragColor;
++#define OUT_VAR f_fragColor
++#define highp
++#define texture2D texture
++#else
++#define INATTR varying
++#define DECLARE_OUT_VAR
++#define OUT_VAR gl_FragColor
++#endif
++// vertices data
++INATTR highp vec4 textureCoordinates;
++uniform sampler2D f_tileTexture;
++DECLARE_OUT_VAR
++
++void main()
++{
++ // get the fragment color from the tile texture
++ highp vec4 color = texture2D(f_tileTexture, textureCoordinates.st);
++ OUT_VAR = vec4(color);
++}
+diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc
+new file mode 100644
+index 0000000000..ab5b5719a9
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc
+@@ -0,0 +1,6 @@
++<RCC>
++ <qresource prefix="/">
++ <file>kis_gl_image_widget.frag</file>
++ <file>kis_gl_image_widget.vert</file>
++ </qresource>
++</RCC>
+diff --git a/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert
+new file mode 100644
+index 0000000000..9578f47945
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.vert
+@@ -0,0 +1,17 @@
++#ifndef USE_OPENGLES
++#define INATTR in
++#define OUTATTR out
++#define highp
++#else
++#define INATTR attribute
++#define OUTATTR varying
++#endif
++uniform mat4 viewProjectionMatrix;
++INATTR highp vec3 vertexPosition;
++INATTR highp vec2 texturePosition;
++OUTATTR highp vec4 textureCoordinates;
++void main()
++{
++ textureCoordinates = vec4(texturePosition.x, texturePosition.y, 0.0, 1.0);
++ gl_Position = viewProjectionMatrix * vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);
++}
+diff --git a/tests/manual/hdr-qopenglwidget/main.cpp b/tests/manual/hdr-qopenglwidget/main.cpp
+new file mode 100644
+index 0000000000..e517ef8579
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/main.cpp
+@@ -0,0 +1,153 @@
++/****************************************************************************
++**
++** Copyright (C) 2016 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#include <QApplication>
++#include "window.h"
++
++#include "openglprobeutils.h"
++#include <QDebug>
++
++QSurfaceFormat generateSurfaceFormat(QSurfaceFormat::RenderableType renderer,
++ QSurfaceFormat::ColorSpace colorSpace,
++ int bitDepth)
++{
++ QSurfaceFormat format;
++#ifdef Q_OS_MACOS
++ format.setVersion(3, 2);
++ format.setProfile(QSurfaceFormat::CoreProfile);
++#else
++ format.setVersion(3, 0);
++ format.setProfile(QSurfaceFormat::CoreProfile);
++#endif
++ format.setDepthBufferSize(24);
++ format.setStencilBufferSize(8);
++
++ switch (bitDepth) {
++ case 8:
++ format.setRedBufferSize(8);
++ format.setGreenBufferSize(8);
++ format.setBlueBufferSize(8);
++ format.setAlphaBufferSize(8);
++ break;
++ case 10:
++ format.setRedBufferSize(10);
++ format.setGreenBufferSize(10);
++ format.setBlueBufferSize(10);
++ format.setAlphaBufferSize(2);
++ break;
++ case 16:
++ format.setRedBufferSize(16);
++ format.setGreenBufferSize(16);
++ format.setBlueBufferSize(16);
++ format.setAlphaBufferSize(16);
++ break;
++ default:
++ qFatal("Unsupported surface bit depth %d", bitDepth);
++ }
++
++ format.setRenderableType(renderer);
++ format.setColorSpace(colorSpace);
++
++ format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
++ format.setSwapInterval(0); // Disable vertical refresh syncing
++
++ return format;
++}
++
++int main(int argc, char *argv[])
++{
++ QVector<QSurfaceFormat> allFormats;
++
++ QVector<QSurfaceFormat::RenderableType> availableRenderers;
++ availableRenderers << QSurfaceFormat::OpenGL;
++ availableRenderers << QSurfaceFormat::OpenGLES;
++
++ for (QSurfaceFormat::RenderableType renderer : availableRenderers) {
++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 8);
++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 8);
++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::sRGBColorSpace, 10);
++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::bt2020PQColorSpace, 10);
++ allFormats << generateSurfaceFormat(renderer, QSurfaceFormat::scRGBColorSpace, 16);
++ }
++
++ for (QSurfaceFormat format : allFormats) {
++ qDebug() << "Probing: " << format;
++ bool result = OpenGLProbeUtils::probeFormat(format, true);
++ qDebug() << " result:" << result;
++ }
++
++
++ if (argc > 1 && !strcmp(argv[1], "--sharecontext")) {
++ qDebug("Requesting all contexts to share");
++ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
++ }
++
++ QApplication a(argc, argv);
++
++ QSurfaceFormat::RenderableType renderer = QSurfaceFormat::OpenGLES;
++ QSurfaceFormat::ColorSpace colorSpace = QSurfaceFormat::scRGBColorSpace;
++ int bitDepth = 16;
++
++
++ if (QCoreApplication::arguments().contains(QLatin1String("--scrgb"))) {
++ colorSpace = QSurfaceFormat::scRGBColorSpace;
++ bitDepth = 16;
++ } else if (QCoreApplication::arguments().contains(QLatin1String("--bt2020pq"))) {
++ colorSpace = QSurfaceFormat::bt2020PQColorSpace;
++ bitDepth = 10;
++ } else if (QCoreApplication::arguments().contains(QLatin1String("--srgb"))) {
++ colorSpace = QSurfaceFormat::sRGBColorSpace;
++ bitDepth = 8;
++ }
++
++ if (QCoreApplication::arguments().contains(QLatin1String("--opengl"))) {
++ renderer = QSurfaceFormat::OpenGL;
++ } else if (QCoreApplication::arguments().contains(QLatin1String("--opengles"))) {
++ renderer = QSurfaceFormat::OpenGLES;
++ }
++
++ QSurfaceFormat format = generateSurfaceFormat(renderer, colorSpace, bitDepth);
++
++ if (QCoreApplication::arguments().contains(QLatin1String("--multisample"))) {
++ format.setSamples(4);
++ }
++
++ if (format.renderableType() == QSurfaceFormat::OpenGL) {
++ QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true);
++ } else if (format.renderableType() == QSurfaceFormat::OpenGLES) {
++ QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
++ }
++
++ qDebug() << "Requesting" << format.renderableType() << format;
++ QSurfaceFormat::setDefaultFormat(format);
++
++ Window window;
++ window.show();
++
++ return a.exec();
++}
+diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp
+new file mode 100644
+index 0000000000..687cc08904
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.cpp
+@@ -0,0 +1,139 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#include "openglprobeutils.h"
++
++#include <QApplication>
++#include <QSurfaceFormat>
++#include <QOpenGLContext>
++#include <QDebug>
++#include <QWindow>
++
++namespace OpenGLProbeUtils {
++
++namespace {
++
++struct AppAttributeSetter
++{
++ AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES)
++ : m_attribute(attribute),
++ m_oldValue(QCoreApplication::testAttribute(attribute))
++ {
++ QCoreApplication::setAttribute(attribute, useOpenGLES);
++ }
++
++ ~AppAttributeSetter() {
++ QCoreApplication::setAttribute(m_attribute, m_oldValue);
++ }
++
++private:
++ Qt::ApplicationAttribute m_attribute;
++ bool m_oldValue = false;
++};
++
++struct SurfaceFormatSetter
++{
++ SurfaceFormatSetter(const QSurfaceFormat &format)
++ : m_oldFormat(QSurfaceFormat::defaultFormat())
++ {
++ QSurfaceFormat::setDefaultFormat(format);
++ }
++
++ ~SurfaceFormatSetter() {
++ QSurfaceFormat::setDefaultFormat(m_oldFormat);
++ }
++
++private:
++ QSurfaceFormat m_oldFormat;
++};
++
++}
++
++bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs)
++{
++ return lhs == rhs ||
++ ((lhs == QSurfaceFormat::DefaultColorSpace ||
++ lhs == QSurfaceFormat::sRGBColorSpace) &&
++ (rhs == QSurfaceFormat::DefaultColorSpace ||
++ rhs == QSurfaceFormat::sRGBColorSpace));
++}
++
++bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState)
++{
++ QScopedPointer<AppAttributeSetter> sharedContextSetter;
++ QScopedPointer<AppAttributeSetter> glSetter;
++ QScopedPointer<AppAttributeSetter> glesSetter;
++ QScopedPointer<SurfaceFormatSetter> formatSetter;
++ QScopedPointer<QApplication> application;
++
++ if (adjustGlobalState) {
++ sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false));
++
++ if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) {
++ glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES));
++ glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES));
++ }
++
++ formatSetter.reset(new SurfaceFormatSetter(format));
++
++ int argc = 1;
++ QByteArray data("krita");
++ char *argv = data.data();
++ application.reset(new QApplication(argc, &argv));
++ }
++
++ QWindow surface;
++ surface.setFormat(format);
++ surface.setSurfaceType(QSurface::OpenGLSurface);
++ surface.create();
++ QOpenGLContext context;
++ context.setFormat(format);
++
++
++ if (!context.create()) {
++ qCritical() << "OpenGL context cannot be created";
++ return false;
++ }
++ if (!context.isValid()) {
++ qCritical() << "OpenGL context is not valid while checking Qt's OpenGL status";
++ return false;
++ }
++ if (!context.makeCurrent(&surface)) {
++ qCritical() << "OpenGL context cannot be made current";
++ return false;
++ }
++
++ if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) {
++ qCritical() << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace();
++ return false;
++ }
++
++ return true;
++}
++
++}
+diff --git a/tests/manual/hdr-qopenglwidget/openglprobeutils.h b/tests/manual/hdr-qopenglwidget/openglprobeutils.h
+new file mode 100644
+index 0000000000..3b2f1ec3d0
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/openglprobeutils.h
+@@ -0,0 +1,42 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#ifndef OPENGLPROBEUTILS_H
++#define OPENGLPROBEUTILS_H
++
++#include <QSurfaceFormat>
++
++namespace OpenGLProbeUtils
++{
++
++bool fuzzyCompareColorSpaces(const QSurfaceFormat::ColorSpace &lhs, const QSurfaceFormat::ColorSpace &rhs);
++bool probeFormat(const QSurfaceFormat &format, bool adjustGlobalState);
++
++};
++
++#endif // OPENGLPROBEUTILS_H
+diff --git a/tests/manual/hdr-qopenglwidget/window.cpp b/tests/manual/hdr-qopenglwidget/window.cpp
+new file mode 100644
+index 0000000000..5729660a4f
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/window.cpp
+@@ -0,0 +1,219 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#include "window.h"
++
++#include "KisGLImageWidget.h"
++#include "KisGLImageF16.h"
++
++#include <cmath>
++
++#include <QMenu>
++#include <QMenuBar>
++#include <QToolBar>
++#include <QAction>
++#include <QDebug>
++
++#include <QVBoxLayout>
++#include <QHBoxLayout>
++#include <QLabel>
++
++#include <cmath>
++
++
++Window::Window()
++{
++ setWindowTitle("16 bit float QOpenGLWidget test");
++ QMenu *menu = menuBar()->addMenu("File");
++ QToolBar *tb = addToolBar("File");
++
++ m_quitAction = new QAction("Quit", this);
++ connect(m_quitAction, SIGNAL(triggered(bool)), this, SLOT(close()));
++ menu->addAction(m_quitAction);
++ tb->addAction(m_quitAction);
++
++ QWidget *centralWidget = new QWidget(this);
++ QVBoxLayout *layout = new QVBoxLayout(centralWidget);
++
++ QHBoxLayout *hLayout = new QHBoxLayout(centralWidget);
++
++ m_imageWidget = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this);
++ m_imageWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
++ hLayout->addWidget(m_imageWidget, 0, Qt::AlignLeft);
++
++ m_imageWidgetSdr = new KisGLImageWidget(QSurfaceFormat::scRGBColorSpace, this);
++ m_imageWidgetSdr->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
++ hLayout->addWidget(m_imageWidgetSdr, 0, Qt::AlignLeft);
++
++ QImage image(QSize(255,255), QImage::Format_ARGB32);
++ image.fill(Qt::red);
++
++ QLabel *label = new QLabel(this);
++ label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
++ hLayout->addWidget(label, 0, Qt::AlignLeft);
++
++ m_imageWidget->loadImage(initializeImage(false));
++ m_imageWidgetSdr->loadImage(initializeImage(true));
++ label->setPixmap(QPixmap::fromImage(convertToQImage(m_imageWidget->image())));
++
++ layout->addLayout(hLayout);
++
++ m_lblContextInfo = new QLabel(this);
++ layout->addWidget(m_lblContextInfo);
++
++ QLabel *lblNotes = new QLabel(this);
++ lblNotes->setWordWrap(true);
++ lblNotes->setText("* In SDR display mode all three images should look exactly the same\n"
++ "* In HDR display mode: image 1 should look brighter than the others "
++ "(it is HDR), images 2 and 3 should have exactly the same brightness and look");
++
++ layout->addWidget(lblNotes);
++
++ centralWidget->setLayout(layout);
++ setCentralWidget(centralWidget);
++}
++
++inline qfloat16 floatToFloat16(float x) {
++ qfloat16 result;
++ qFloatToFloat16(&result, &x, 1);
++ return result;
++}
++
++inline float float16ToFloat(qfloat16 x) {
++ float result;
++ qFloatFromFloat16(&result, &x, 1);
++ return result;
++}
++
++
++KisGLImageF16 Window::initializeImage(bool cropRange) const
++{
++ const int size = 256;
++ KisGLImageF16 image(size, size);
++ image.clearPixels();
++ qfloat16 *pixelPtr = image.data();
++
++ for (int y = 0; y < size; y++) {
++ for (int x = 0; x < size; x++) {
++ qfloat16 *pxl = reinterpret_cast<qfloat16*>(pixelPtr);
++
++ float hdrRedValue = 25.0f * std::pow(float(x) / size, 5.0f);
++
++ if (cropRange) {
++ hdrRedValue = qMin(hdrRedValue, 1.0f);
++ }
++
++ pxl[0] = floatToFloat16(hdrRedValue);
++
++ if (y > size / 2) {
++ const float portion = (float(y) / size - 0.5f) * 2.0f;
++ const float value = qMin(1.0f, 0.2f + 1.8f * portion);
++
++ pxl[1] = floatToFloat16(value);
++ pxl[2] = floatToFloat16(value);
++ } else {
++ pxl[1] = floatToFloat16(0.2);
++ pxl[2] = floatToFloat16(0.2);
++ }
++
++ pxl[3] = floatToFloat16(1.0);
++
++ pixelPtr += 4;
++ }
++ }
++
++ return image;
++}
++
++inline float linearToSRGB(float value)
++{
++ if (value <= 0.0f) {
++ value = 0.0f;
++ } else if (value < 0.0031308f) {
++ value = value * 12.92f;
++ } else if (value < 1.0f) {
++ value = std::pow(value, 0.41666f) * 1.055f - 0.055f;
++ } else {
++ value = 1.0f;
++ }
++ return value;
++}
++
++QImage Window::convertToQImage(const KisGLImageF16 &image) const
++{
++ const QSize size = image.size();
++ const qfloat16 *pixelPtr = image.constData();
++
++ QImage qimage(size, QImage::Format_ARGB32);
++ quint8 *qimagePixelPtr = qimage.bits();
++
++
++ for (int y = 0; y < size.height(); y++) {
++ for (int x = 0; x < size.width(); x++) {
++ const qfloat16 *srcPxl = pixelPtr;
++ quint8 *dstPxl = qimagePixelPtr;
++
++ auto convertChannel = [] (qfloat16 x) {
++ float value = float16ToFloat(x);
++ return quint8(linearToSRGB(value) * 255.0f);
++ };
++
++ dstPxl[0] = convertChannel(srcPxl[2]);
++ dstPxl[1] = convertChannel(srcPxl[1]);
++ dstPxl[2] = convertChannel(srcPxl[0]);
++ dstPxl[3] = convertChannel(srcPxl[3]);
++
++ pixelPtr += 4;
++ qimagePixelPtr += 4;
++ }
++ }
++
++ return qimage;
++}
++
++void Window::updateSurfaceInfo()
++{
++ const QSurfaceFormat format = m_imageWidget->context()->format();
++
++ m_lblContextInfo->setText(
++ QString("renderer: %1\ncolorSpace: %2\n\n")
++ .arg(format.renderableType() == QSurfaceFormat::OpenGL ? "openGL" : "openGL ES")
++ .arg(format.colorSpace() == QSurfaceFormat::sRGBColorSpace ? "sRGB" :
++ format.colorSpace() == QSurfaceFormat::scRGBColorSpace ? "scRGB" :
++ format.colorSpace() == QSurfaceFormat::bt2020PQColorSpace ? "Bt. 2020 PQ" :
++ "unknown"));
++}
++
++void Window::showEvent(QShowEvent *ev)
++{
++ QMainWindow::showEvent(ev);
++
++ if (m_lblContextInfo->text().isEmpty()) {
++ updateSurfaceInfo();
++ }
++}
+diff --git a/tests/manual/hdr-qopenglwidget/window.h b/tests/manual/hdr-qopenglwidget/window.h
+new file mode 100644
+index 0000000000..fd8e5c0393
+--- /dev/null
++++ b/tests/manual/hdr-qopenglwidget/window.h
+@@ -0,0 +1,69 @@
++/****************************************************************************
++**
++** Copyright (C) 2019 The Qt Company Ltd.
++** Contact: https://www.qt.io/licensing/
++**
++** This file is part of the test suite of the Qt Toolkit.
++**
++** $QT_BEGIN_LICENSE:GPL-EXCEPT$
++** Commercial License Usage
++** Licensees holding valid commercial Qt licenses may use this file in
++** accordance with the commercial license agreement provided with the
++** Software or, alternatively, in accordance with the terms contained in
++** a written agreement between you and The Qt Company. For licensing terms
++** and conditions see https://www.qt.io/terms-conditions. For further
++** information use the contact form at https://www.qt.io/contact-us.
++**
++** GNU General Public License Usage
++** Alternatively, this file may be used under the terms of the GNU
++** General Public License version 3 as published by the Free Software
++** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
++** included in the packaging of this file. Please review the following
++** information to ensure the GNU General Public License requirements will
++** be met: https://www.gnu.org/licenses/gpl-3.0.html.
++**
++** $QT_END_LICENSE$
++**
++****************************************************************************/
++
++#ifndef WINDOW_H
++#define WINDOW_H
++
++#include <QMainWindow>
++
++class QAction;
++
++class GLWidget;
++class KisGLImageWidget;
++class KisGLImageF16;
++class QLabel;
++
++class Window : public QMainWindow
++{
++ Q_OBJECT
++
++public:
++ Window();
++
++ void showEvent(QShowEvent *ev) override;
++
++public Q_SLOTS:
++
++
++private:
++ KisGLImageF16 initializeImage(bool cropRange) const;
++ QImage convertToQImage(const KisGLImageF16 &image) const;
++
++ void updateSurfaceInfo();
++
++private:
++ GLWidget *m_glWidget {0};
++ QAction *m_openAction {0};
++ QAction *m_quitAction {0};
++ KisGLImageWidget *m_imageWidget;
++ KisGLImageWidget *m_imageWidgetSdr;
++ QLabel *m_lblContextInfo;
++
++};
++
++#endif
+diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
+index ab00a5ef60..b202ed0431 100644
+--- a/tests/manual/manual.pro
++++ b/tests/manual/manual.pro
+@@ -59,7 +59,7 @@ qtabbar
+
+ qtConfig(opengl) {
+ SUBDIRS += qopengltextureblitter
+- qtConfig(egl): SUBDIRS += qopenglcontext
++ qtConfig(egl): SUBDIRS += qopenglcontext hdr-qopenglwidget
+ }
+
+ win32: SUBDIRS -= network_remote_stresstest network_stresstest
+--
+2.20.1.windows.1
+
diff --git a/3rdparty/ext_qt/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch b/3rdparty/ext_qt/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch
new file mode 100644
index 0000000000..d51572c13f
--- /dev/null
+++ b/3rdparty/ext_qt/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch
@@ -0,0 +1,29 @@
+From d06dc5de61c3574dd97c5aaae07561d8fc29c604 Mon Sep 17 00:00:00 2001
+From: Dmitry Kazakov <dimula73@gmail.com>
+Date: Thu, 6 Dec 2018 16:16:27 +0300
+Subject: [PATCH 08/17] Fix notification of QDockWidget when it gets undocked
+
+Before the patch the notification was emitted only when the docker
+was attached to the panel or changed a position on it.
+
+Change-Id: Id3ffbd2018a8e68844d174328dd1c4ceb7fa01d3
+---
+ src/widgets/widgets/qdockwidget.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp
+index 6c871aae2c..19fc2d1677 100644
+--- a/src/widgets/widgets/qdockwidget.cpp
++++ b/src/widgets/widgets/qdockwidget.cpp
+@@ -1171,6 +1171,8 @@ void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect
+ QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(q);
+ if (mwlayout)
+ emit q->dockLocationChanged(mwlayout->dockWidgetArea(q));
++ } else {
++ emit q->dockLocationChanged(Qt::NoDockWidgetArea);
+ }
+ }
+
+--
+2.20.1.windows.1
+
diff --git a/3rdparty/ext_qt/0010-Fix-tablet-jitter-on-X11.patch b/3rdparty/ext_qt/0010-Fix-tablet-jitter-on-X11.patch
deleted file mode 100644
index 799c1eee3c..0000000000
--- a/3rdparty/ext_qt/0010-Fix-tablet-jitter-on-X11.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-From a05a69e789006bbf3fbfdd1998bc58af7b826b4b Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Sun, 10 Mar 2019 14:48:58 +0300
-Subject: [PATCH] Fix tablet jitter on X11
-
-We should get the correct stylus position from the valuators,
-not from the X11-provided global position. Global position is rounded
-to the nearest FP16 values, which is not enough for smooth painting.
-
-Change-Id: Ie701446b3586296bcb8fb09158f387ba6a7cbf07
----
- src/plugins/platforms/xcb/qxcbconnection_xi2.cpp | 26 ++++++++++++++++++++++++
- 1 file changed, 26 insertions(+)
-
-diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
-index 04ddd3c..20aca33 100644
---- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
-+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
-@@ -1208,6 +1208,11 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
- return handled;
- }
-
-+inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize)
-+{
-+ return screenMin + normValue * screenSize;
-+}
-+
- void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData)
- {
- auto *ev = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
-@@ -1221,6 +1226,15 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD
- double pressure = 0, rotation = 0, tangentialPressure = 0;
- int xTilt = 0, yTilt = 0;
-
-+ // Valuators' values are relative to the physical size of the current virtual
-+ // screen. Therefore we cannot use QScreen/QWindow geometry and should use
-+ // QPlatformWindow/QPlatformScreen instead.
-+ QRect physicalScreenArea;
-+ const QList<QPlatformScreen *> siblings = window->screen()->handle()->virtualSiblings();
-+ for (const QPlatformScreen *screen : siblings) {
-+ physicalScreenArea |= screen->geometry();
-+ }
-+
- for (QHash<int, TabletData::ValuatorClassInfo>::iterator it = tabletData->valuatorInfo.begin(),
- ite = tabletData->valuatorInfo.end(); it != ite; ++it) {
- int valuator = it.key();
-@@ -1228,6 +1242,18 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD
- xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal);
- double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
- switch (valuator) {
-+ case QXcbAtom::AbsX: {
-+ const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width());
-+ global.setX(value);
-+ local.setX(value - window->handle()->geometry().x());
-+ break;
-+ }
-+ case QXcbAtom::AbsY: {
-+ qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height());
-+ global.setY(value);
-+ local.setY(value - window->handle()->geometry().y());
-+ break;
-+ }
- case QXcbAtom::AbsPressure:
- pressure = normalizedValue;
- break;
---
-2.7.4
-
diff --git a/3rdparty/ext_qt/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch b/3rdparty/ext_qt/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch
deleted file mode 100644
index e304f7fff1..0000000000
--- a/3rdparty/ext_qt/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From 8b82ab988807cd424e2e54b29dea4cde8e687fab Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Sun, 10 Mar 2019 14:51:28 +0300
-Subject: [PATCH] Add an ID for recognition of UGEE tablets
-
-Change-Id: I2228ee9d53aa23a2d2cd9970a363d8424e744093
----
- src/plugins/platforms/xcb/qxcbconnection_xi2.cpp | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
-index 20aca33..411366f 100644
---- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
-+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
-@@ -240,6 +240,10 @@ void QXcbConnection::xi2SetupDevice(void *info, bool removeExisting)
- } else if (name.contains("uc-logic") && isTablet) {
- tabletData.pointerType = QTabletEvent::Pen;
- dbgType = QLatin1String("pen");
-+ } else if (name.contains("ugee")) {
-+ isTablet = true;
-+ tabletData.pointerType = QTabletEvent::Pen;
-+ dbgType = QLatin1String("pen");
- } else {
- isTablet = false;
- }
---
-2.7.4
-
diff --git a/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch b/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
index b5db94adc3..644c0cd236 100644
--- a/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
+++ b/3rdparty/ext_qt/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
@@ -1,39 +1,39 @@
-From a36e3651fe79bbbdea44d78e11a97705687cffa9 Mon Sep 17 00:00:00 2001
+From 0ae8de8731d39fd1e736c7d9012f87184299956e Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Mon, 11 Mar 2019 13:18:06 +0300
-Subject: [PATCH] Synthesize Enter/LeaveEvent for accepted QTabletEvent
+Subject: [PATCH 1/2] Synthesize Enter/LeaveEvent for accepted QTabletEvent
When the tablet event is accepted, then Qt doesn't synthesize a mouse
event, it means that QApplicationPrivate::sendMouseEvent() will not be
called, and, therefore, enter/leave events will not be dispatched.
The patch looks a bit hackish. Ideally, the synthesize should happen
in QGuiApplicationPrivate::processTabletEvent(), which takes the decision
about synthesizing mouse events. But there is not enough information
on this level: neither qt_last_mouse_receiver nor the receiver widget
are known at this stage.
Change-Id: Ifbad6284483ee282ad129db54606f5d0d9ddd633
---
src/widgets/kernel/qwidgetwindow.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp
-index e9b749d..983ab77 100644
+index fbc71cd0ea..25cb486915 100644
--- a/src/widgets/kernel/qwidgetwindow.cpp
+++ b/src/widgets/kernel/qwidgetwindow.cpp
-@@ -1053,6 +1053,11 @@ void QWidgetWindow::handleTabletEvent(QTabletEvent *event)
+@@ -1051,6 +1051,11 @@ void QWidgetWindow::handleTabletEvent(QTabletEvent *event)
event->setAccepted(ev.isAccepted());
}
+ if (event->isAccepted() && widget != qt_last_mouse_receiver) {
+ QApplicationPrivate::dispatchEnterLeave(widget, qt_last_mouse_receiver, event->globalPos());
+ qt_last_mouse_receiver = widget;
+ }
+
if (event->type() == QEvent::TabletRelease && event->buttons() == Qt::NoButton)
qt_tablet_target = 0;
}
--
-2.7.4
+2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch b/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch
index 641697c9b6..dd39f1b369 100644
--- a/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch
+++ b/3rdparty/ext_qt/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch
@@ -1,28 +1,28 @@
-From 6851994441cc48cf48853f009dd620e9cebd551a Mon Sep 17 00:00:00 2001
+From 82c789f75fc634ccbe5f1aad6d41895a6e6da17d Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Mon, 11 Mar 2019 16:17:17 +0300
-Subject: [PATCH] Poison Qt's headers with a mark about presence of Enter/Leave
- patch
+Subject: [PATCH 2/2] Poison Qt's headers with a mark about presence of
+ Enter/Leave patch
---
src/gui/kernel/qevent.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/gui/kernel/qevent.h b/src/gui/kernel/qevent.h
-index 2b1c6a6..69cafaa 100644
+index 2b1c6a6e31..69cafaaa29 100644
--- a/src/gui/kernel/qevent.h
+++ b/src/gui/kernel/qevent.h
@@ -242,6 +242,10 @@ protected:
};
#endif
+// a temporary mark to know if the patch has landed to Qt or not
+// https://codereview.qt-project.org/#/c/255384/
+#define QT_HAS_ENTER_LEAVE_PATCH
+
#if QT_CONFIG(tabletevent)
class Q_GUI_EXPORT QTabletEvent : public QInputEvent
{
--
-2.7.4
+2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch b/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
index 8bd52aa813..e9e0664c1e 100644
--- a/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
+++ b/3rdparty/ext_qt/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
@@ -1,50 +1,50 @@
-From bf5a6693654afedd05a36d9605fce95f69b70e23 Mon Sep 17 00:00:00 2001
+From 89b0f14299deac2ad0802f3addfbf487ba680720 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Mon, 11 Mar 2019 13:18:06 +0300
-Subject: [PATCH 20/22] Synthesize Enter/LeaveEvent for accepted QTabletEvent
+Subject: [PATCH 09/17] Synthesize Enter/LeaveEvent for accepted QTabletEvent
When the tablet event is accepted, then Qt doesn't synthesize a mouse
event, it means that QApplicationPrivate::sendMouseEvent() will not be
called, and, therefore, enter/leave events will not be dispatched.
The patch looks a bit hackish. Ideally, the synthesize should happen
in QGuiApplicationPrivate::processTabletEvent(), which takes the decision
about synthesizing mouse events. But there is not enough information
on this level: neither qt_last_mouse_receiver nor the receiver widget
are known at this stage.
On Windows and other platforms where there is a parallel stream of
mouse events synthesized by the platform, we shouldn't generate these
events manually.
Change-Id: Ifbad6284483ee282ad129db54606f5d0d9ddd633
---
src/widgets/kernel/qwidgetwindow.cpp | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp
-index e9b749d7c2..00c1e8e92f 100644
+index fbc71cd0ea..729a7f701a 100644
--- a/src/widgets/kernel/qwidgetwindow.cpp
+++ b/src/widgets/kernel/qwidgetwindow.cpp
-@@ -1053,6 +1053,18 @@ void QWidgetWindow::handleTabletEvent(QTabletEvent *event)
+@@ -1051,6 +1051,18 @@ void QWidgetWindow::handleTabletEvent(QTabletEvent *event)
event->setAccepted(ev.isAccepted());
}
+ /**
+ * Synthesize Enter/Leave events if it is requested by the system and user
+ */
+ if (widget != qt_last_mouse_receiver &&
+ event->isAccepted() &&
+ !QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse &&
+ qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)) {
+
+ QApplicationPrivate::dispatchEnterLeave(widget, qt_last_mouse_receiver, event->globalPos());
+ qt_last_mouse_receiver = widget;
+ }
+
if (event->type() == QEvent::TabletRelease && event->buttons() == Qt::NoButton)
qt_tablet_target = 0;
}
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0021-Fix-QTabletEvent-uniqueId-when-Qt-uses-WinInk.patch b/3rdparty/ext_qt/0021-Fix-QTabletEvent-uniqueId-when-Qt-uses-WinInk.patch
deleted file mode 100644
index 3486f7c6d5..0000000000
--- a/3rdparty/ext_qt/0021-Fix-QTabletEvent-uniqueId-when-Qt-uses-WinInk.patch
+++ /dev/null
@@ -1,78 +0,0 @@
-From a2d7fa13fadddf5a18e555694c0b6774e654d793 Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Thu, 28 Mar 2019 18:33:44 +0300
-Subject: [PATCH 21/22] Fix QTabletEvent::uniqueId() when Qt uses WinInk
-
-A new 'pointerId' is assigned to the stylus every time it enters
-tablet's proximity. But applications expect this ID be constant,
-at least during one application run. Therefore, it needs to use
-'sourceDevice' instead.
-
-Basically, WinInk doesn't have an ability to distinguich two
-different styluses connected to the same tablet. We cannot do
-anything about it, it is supported only in WinTab.
-
-Task-number: QTBUG-74700
-Change-Id: I8328f1e5102b037b370082e69e965ab68b487882
----
- .../platforms/windows/qwindowspointerhandler.cpp | 12 ++++++------
- 1 file changed, 6 insertions(+), 6 deletions(-)
-
-diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-index f1960f1585..f37bec935c 100644
---- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-@@ -531,7 +531,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
- if (!QWindowsContext::user32dll.getPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
- return false;
-
-- const quint32 pointerId = penInfo->pointerInfo.pointerId;
-+ const qint64 sourceDevice = (qint64)penInfo->pointerInfo.sourceDevice;
- const QPoint globalPos = QPoint(penInfo->pointerInfo.ptPixelLocation.x, penInfo->pointerInfo.ptPixelLocation.y);
- const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos);
- const QPointF hiResGlobalPos = QPointF(dRect.left + qreal(penInfo->pointerInfo.ptHimetricLocation.x - pRect.left)
-@@ -547,7 +547,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
-
- if (QWindowsContext::verbose > 1)
- qCDebug(lcQpaEvents).noquote().nospace() << showbase
-- << __FUNCTION__ << " pointerId=" << pointerId
-+ << __FUNCTION__ << " sourceDevice=" << sourceDevice
- << " globalPos=" << globalPos << " localPos=" << localPos << " hiResGlobalPos=" << hiResGlobalPos
- << " message=" << hex << msg.message
- << " flags=" << hex << penInfo->pointerInfo.pointerFlags;
-@@ -570,7 +570,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
-
- switch (msg.message) {
- case WM_POINTERENTER: {
-- QWindowSystemInterface::handleTabletEnterProximityEvent(device, type, pointerId);
-+ QWindowSystemInterface::handleTabletEnterProximityEvent(device, type, sourceDevice);
- m_windowUnderPointer = window;
- // The local coordinates may fall outside the window.
- // Wait until the next update to send the enter event.
-@@ -583,12 +583,12 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
- m_windowUnderPointer = nullptr;
- m_currentWindow = nullptr;
- }
-- QWindowSystemInterface::handleTabletLeaveProximityEvent(device, type, pointerId);
-+ QWindowSystemInterface::handleTabletLeaveProximityEvent(device, type, sourceDevice);
- break;
- case WM_POINTERDOWN:
- case WM_POINTERUP:
- case WM_POINTERUPDATE: {
-- QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(pointerId).target; // Pass to window that grabbed it.
-+ QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).target; // Pass to window that grabbed it.
- if (!target && m_windowUnderPointer)
- target = m_windowUnderPointer;
- if (!target)
-@@ -607,7 +607,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
-
- QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons,
- pressure, xTilt, yTilt, tangentialPressure, rotation, z,
-- pointerId, keyModifiers);
-+ sourceDevice, keyModifiers);
- return false; // Allow mouse messages to be generated.
- }
- }
---
-2.20.1.windows.1
-
diff --git a/3rdparty/ext_qt/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch b/3rdparty/ext_qt/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch
deleted file mode 100644
index cbcbe15471..0000000000
--- a/3rdparty/ext_qt/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From 0749bda24a02db33812978e167ffac356f74526b Mon Sep 17 00:00:00 2001
-From: Dmitry Kazakov <dimula73@gmail.com>
-Date: Sat, 30 Mar 2019 23:14:07 +0300
-Subject: [PATCH 22/22] Fix generation of Leave events when using tablet
- devices
-
-When both mouse and tablet events are handled by QWindowsPointerHandler,
-m_currentWindow variable is shared among the two event streams, therefore
-each stream should ensure it does euqivalent operations, when changing it.
-
-Here we should subscribe to the Leave events, when we emit Enter event
-from the inside of the tablet events flow. Without whis subscription,
-the cursor may stuck into "resize" state when crossing the window's
-frame multiple times.
-
-Change-Id: I88df4a42ae86243e10ecd4a4cedf87639c96d169
----
- src/plugins/platforms/windows/qwindowspointerhandler.cpp | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-index f37bec935c..190fb208d9 100644
---- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-@@ -597,7 +597,12 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
- if (m_needsEnterOnPointerUpdate) {
- m_needsEnterOnPointerUpdate = false;
- if (window != m_currentWindow) {
-+
-+ // make sure we subscribe to leave events for this window
-+ trackLeave(hwnd);
-+
- QWindowSystemInterface::handleEnterEvent(window, localPos, globalPos);
-+
- m_currentWindow = window;
- if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(target))
- wumPlatformWindow->applyCursor();
---
-2.20.1.windows.1
-
diff --git a/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch b/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch
index 8c650179f0..3bcbb7e59e 100644
--- a/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch
+++ b/3rdparty/ext_qt/0023-Implement-a-switch-for-tablet-API-on-Windows.patch
@@ -1,73 +1,73 @@
-From 880c9387d5e889e52e0db22f629443c1006988f0 Mon Sep 17 00:00:00 2001
+From d95b6e9a4029a46acbe5df4b068de8a646887430 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Wed, 3 Apr 2019 18:37:56 +0300
-Subject: [PATCH] Implement a switch for tablet API on Windows
+Subject: [PATCH 10/17] Implement a switch for tablet API on Windows
Qt has support for two tablet APIs: WinTab and Windows Pointer API.
The former one is used in professional graphical tablet devices,
like Wacom, Huion and etc. The latter is mostly used in two-in-one
convertible laptops, like Surface Pro. By default Qt prefers Windows
Pointer API, if it is available.
The problem is that some devices (e.g. Huion tablets) do not
support Windows Pointer API. More than that, even devices, which
support Pointer API, must limit their capabilities to fit it:
1) Winodws Pointer API doesn't support more than one stylus barrel
buttons, but all professional devices have at least two buttons.
2) Winodws Pointer API limits pressure resolution to 1024 levels,
but even entry-level Wacom devices have at least 2048 levels.
Professional-level devices have 4096 levels.
Therefore painting applications should be able to choose, which API
they prefer.
This patch implements a special application attribute
Qt::AA_MSWindowsUseWinTabAPI. Application should set it before creation
of QApplication to force selection of WinTab API.
When running, application can check currently running API by
testing this attribute.
---
src/corelib/global/qnamespace.h | 1 +
src/plugins/platforms/windows/qwindowsintegration.cpp | 8 +++++++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h
-index dec2c446..3ab99219 100644
+index dec2c44637..3ab9921986 100644
--- a/src/corelib/global/qnamespace.h
+++ b/src/corelib/global/qnamespace.h
@@ -525,6 +525,7 @@ public:
AA_DontShowShortcutsInContextMenus = 28,
AA_CompressTabletEvents = 29,
AA_DisableWindowContextHelpButton = 30, // ### Qt 6: remove me
+ AA_MSWindowsUseWinTabAPI = 31, // Win only
// Add new attributes before this line
AA_AttributeCount
diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp
-index 2c90b048..c415cf28 100644
+index 5c1fa00088..d2d12ff7e5 100644
--- a/src/plugins/platforms/windows/qwindowsintegration.cpp
+++ b/src/plugins/platforms/windows/qwindowsintegration.cpp
@@ -236,10 +236,16 @@ QWindowsIntegrationPrivate::QWindowsIntegrationPrivate(const QStringList &paramL
m_options = parseOptions(paramList, &tabletAbsoluteRange, &dpiAwareness);
QWindowsFontDatabase::setFontOptions(m_options);
+ if (QCoreApplication::testAttribute(Qt::AA_MSWindowsUseWinTabAPI)) {
+ m_options |= QWindowsIntegration::DontUseWMPointer;
+ }
+
if (m_context.initPointer(m_options)) {
QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents);
} else {
- m_context.initTablet(m_options);
+ if (m_context.initTablet(m_options))
+ QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI);
+
if (tabletAbsoluteRange >= 0)
m_context.setTabletAbsoluteRange(tabletAbsoluteRange);
}
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch b/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch
index 5822b87ee7..2cbec63601 100644
--- a/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch
+++ b/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch
@@ -1,137 +1,139 @@
-From f62b528cbc9e3ef75584839d995e4b7369ad3ff8 Mon Sep 17 00:00:00 2001
+From 8485c169f76c074256616a55308c1a57174ae064 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Sat, 13 Apr 2019 18:08:33 +0300
-Subject: [PATCH] Fetch stylus button remapping from WinTab driver
+Subject: [PATCH 11/17] Fetch stylus button remapping from WinTab driver
The user can remap the stylus buttons using tablet driver settings.
This information is available to the application via CSR_SYSBTNMAP
WinTab feature. We should fetch this information every time the
stylus gets into proximity, because the user can change these settings
on the fly.
+
+Change-Id: Idc839905c3485179d782814f78fa862fd4a99127
---
.../windows/qwindowstabletsupport.cpp | 72 ++++++++++++++++++-
.../platforms/windows/qwindowstabletsupport.h | 2 +
2 files changed, 73 insertions(+), 1 deletion(-)
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
-index fa209f09..31655101 100644
+index fa209f09c4..44b94d044d 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
@@ -435,6 +435,27 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L
m_currentDevice = m_devices.size();
m_devices.push_back(tabletInit(uniqueId, cursorType));
}
+
+ /**
+ * We should check button map for changes on every proximity event, not
+ * only during initialization phase.
+ *
+ * WARNING: in 2016 there were some Wacom table drivers, which could mess up
+ * button mapping if the remapped button was pressed, while the
+ * application **didn't have input focus**. This bug is somehow
+ * related to the fact that Wacom drivers allow user to configure
+ * per-application button-mappings. If the bug shows up again,
+ * just move this button-map fetching into initialization block.
+ *
+ * See https://bugs.kde.org/show_bug.cgi?id=359561
+ */
+ BYTE logicalButtons[32];
+ memset(logicalButtons, 0, 32);
+ m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_SYSBTNMAP, &logicalButtons);
+ m_devices[m_currentDevice].buttonsMap[0x1] = logicalButtons[0];
+ m_devices[m_currentDevice].buttonsMap[0x2] = logicalButtons[1];
+ m_devices[m_currentDevice].buttonsMap[0x4] = logicalButtons[2];
+
m_devices[m_currentDevice].currentPointerType = pointerType(currentCursor);
m_state = PenProximity;
qCDebug(lcQpaTablet) << "enter proximity for device #"
-@@ -446,6 +467,50 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L
+@@ -446,6 +467,52 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L
return true;
}
+Qt::MouseButton buttonValueToEnum(DWORD button,
+ const QWindowsTabletDeviceData &tdd) {
-+ const int leftButtonValue = 0x1;
-+ const int middleButtonValue = 0x2;
-+ const int rightButtonValue = 0x4;
-+ const int doubleClickButtonValue = 0x7;
++
++ enum : unsigned {
++ leftButtonValue = 0x1,
++ middleButtonValue = 0x2,
++ rightButtonValue = 0x4,
++ doubleClickButtonValue = 0x7
++ };
+
+ button = tdd.buttonsMap.value(button);
+
+ return button == leftButtonValue ? Qt::LeftButton :
+ button == rightButtonValue ? Qt::RightButton :
+ button == doubleClickButtonValue ? Qt::MiddleButton :
+ button == middleButtonValue ? Qt::MiddleButton :
+ button ? Qt::LeftButton /* fallback item */ :
+ Qt::NoButton;
+}
+
-+void convertTabletButtons(DWORD btnNew,
-+ Qt::MouseButtons *buttons,
-+ const QWindowsTabletDeviceData &tdd,
-+ const Qt::KeyboardModifiers keyboardModifiers) {
++Qt::MouseButtons convertTabletButtons(DWORD btnNew,
++ const QWindowsTabletDeviceData &tdd) {
+
-+ *buttons = Qt::NoButton;
-+ for (int i = 0; i < 3; i++) {
-+ int btn = 0x1 << i;
++ Qt::MouseButtons buttons = Qt::NoButton;
++ for (unsigned int i = 0; i < 3; i++) {
++ unsigned int btn = 0x1 << i;
+
+ if (btn & btnNew) {
+ Qt::MouseButton convertedButton =
+ buttonValueToEnum(btn, tdd);
+
-+ *buttons |= convertedButton;
++ buttons |= convertedButton;
+
+ /**
+ * If a button that is present in hardware input is
+ * mapped to a Qt::NoButton, it means that it is going
+ * to be eaten by the driver, for example by its
+ * "Pan/Scroll" feature. Therefore we shouldn't handle
+ * any of the events associated to it. We'll just return
+ * Qt::NoButtons here.
+ */
+ }
+ }
++ return buttons;
+}
+
bool QWindowsTabletSupport::translateTabletPacketEvent()
{
static PACKET localPacketBuf[TabletPacketQSize]; // our own tablet packet queue.
-@@ -552,9 +617,14 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
+@@ -552,9 +619,12 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
<< tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation;
}
-+ Qt::MouseButtons buttons;
-+ convertTabletButtons(packet.pkButtons, &buttons,
-+ m_devices.at(m_currentDevice),
-+ keyboardModifiers);
++ Qt::MouseButtons buttons =
++ convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice));
+
QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF,
currentDevice, currentPointer,
- static_cast<Qt::MouseButtons>(packet.pkButtons),
+ buttons,
pressureNew, tiltX, tiltY,
tangentialPressure, rotation, z,
uniqueId,
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.h b/src/plugins/platforms/windows/qwindowstabletsupport.h
-index d91701d6..5b1ddb52 100644
+index d91701d6a5..8f97982308 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.h
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.h
@@ -45,6 +45,7 @@
#include <QtCore/qvector.h>
#include <QtCore/qpoint.h>
-+#include <QtCore/qmap.h>
++#include <QtCore/qhash.h>
#include <wintab.h>
@@ -100,6 +101,7 @@ struct QWindowsTabletDeviceData
qint64 uniqueId = 0;
int currentDevice = 0;
int currentPointerType = 0;
-+ QMap<quint8, quint8> buttonsMap;
++ QHash<quint8, quint8> buttonsMap;
};
#ifndef QT_NO_DEBUG_STREAM
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch b/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch
index 20a410cb36..632429d3cb 100644
--- a/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch
+++ b/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch
@@ -1,28 +1,28 @@
-From 30f28be2a239d82c2d7f2d6a37df4acb300d7cef Mon Sep 17 00:00:00 2001
+From c5ee9030553cc37d92391b6f60e978eb27b15768 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Sat, 13 Apr 2019 20:29:14 +0300
-Subject: [PATCH] Disable tablet relative mode in Qt
+Subject: [PATCH 12/17] Disable tablet relative mode in Qt
---
src/plugins/platforms/windows/qwindowstabletsupport.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
-index 31655101..3e35b146 100644
+index 44b94d044d..6a9fe28e75 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
-@@ -562,6 +562,11 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
+@@ -564,6 +564,11 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
m_state = PenDown;
m_mode = (mouseLocation - globalPosF).manhattanLength() > m_absoluteRange
? MouseMode : PenMode;
+
+ // Krita doesn't support mouse mode. And this code may break
+ // normal painting, so we just disable it.
+ m_mode = PenMode;
+
qCDebug(lcQpaTablet) << __FUNCTION__ << "mode=" << m_mode << "pen:"
<< globalPosF << "mouse:" << mouseLocation;
}
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch b/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch
index 41da7dd0fa..1f053612c9 100644
--- a/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch
+++ b/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch
@@ -1,217 +1,219 @@
-From 087561675d1c1922265c3c52faeac61ab1bc2ecf Mon Sep 17 00:00:00 2001
+From 71466f90b0b554987a60e5c797d38d6c28f8ebef Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Sat, 13 Apr 2019 23:24:01 +0300
-Subject: [PATCH] Fetch mapped screen size from the Wintab driver
+Subject: [PATCH 13/17] Fetch mapped screen size from the Wintab driver
Some devices, like Microsoft Surface Pro 5, don't map tablet's
input range to the entire virtual screen area, but map it to
the primary display that has actual built-in tablet sensor.
In such cases we should fetch actualy mapped aread from Wintab's
lcSys{Org,Ext}{X,Y} fields and use it for cursor mapping.
If one wants to fall back to the old screen size detection method,
then an environment variable can be set:
QT_IGNORE_WINTAB_MAPPING=1
When the variable is set, the scaling is done via virtual desktop
area only.
If the tablet driver is broken (e.g. Microsoft SP5, when primary
display is set to an external monitor) the user might want to override
mapping completely. Then the following variable can be used:
QT_WINTAB_DESKTOP_RECT=x;y;width;height
+
+Change-Id: Idd8bcf0323ce0811d2ad8976eaed48ad13ac3af8
---
.../windows/qwindowstabletsupport.cpp | 89 ++++++++++++++++++-
.../platforms/windows/qwindowstabletsupport.h | 13 ++-
2 files changed, 99 insertions(+), 3 deletions(-)
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
-index 18ec05e4..6a1bf02b 100644
+index 6a9fe28e75..bfd0a9640b 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
@@ -53,6 +53,7 @@
#include <QtCore/qdebug.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qmath.h>
+#include <QtCore/qregularexpression.h>
#include <private/qguiapplication_p.h>
#include <QtCore/private/qsystemlibrary_p.h>
@@ -216,6 +217,10 @@ QWindowsTabletSupport::QWindowsTabletSupport(HWND window, HCTX context)
// Some tablets don't support tilt, check if it is possible,
if (QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_ORIENTATION, &orientation))
m_tiltSupport = orientation[0].axResolution && orientation[1].axResolution;
+
+ connect(qGuiApp, &QGuiApplication::primaryScreenChanged,
+ this, QWindowsTabletSupport::slotPrimaryScreenChanged);
+ slotScreenGeometryChanged();
}
QWindowsTabletSupport::~QWindowsTabletSupport()
@@ -394,6 +399,84 @@ QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(qint64 uniqueId, UINT
return result;
}
+void QWindowsTabletSupport::slotPrimaryScreenChanged(QScreen *screen)
+{
+ if (m_connectedScreen)
+ disconnect(m_connectedScreen, 0, this, 0);
+
+ m_connectedScreen = screen;
+
+ if (m_connectedScreen)
+ connect(m_connectedScreen, &QScreen::virtualGeometryChanged,
+ this, &QWindowsTabletSupport::slotScreenGeometryChanged);
+
+ slotScreenGeometryChanged();
+}
+
+void QWindowsTabletSupport::slotScreenGeometryChanged()
+{
+ /**
+ * Some Wintab implementations map the tablet area to the entire
+ * virtual screen, but others (e.g. Microsoft SP5) don't. They
+ * may input range to a single (built-in) screen. The logic is
+ * quite obvious: when the screen has integrated tablet device,
+ * one cannot map this tablet device to another display.
+ *
+ * For such devices, we should always request mapped area from
+ * lcSys{Org,Ext}{X,Y} fields and use it accordingly.
+ */
+
+ LOGCONTEXT lc;
+ QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lc);
+ m_wintabScreenGeometry = QRect(lc.lcSysOrgX, lc.lcSysOrgY, lc.lcSysExtX, lc.lcSysExtY);
+
+ qCDebug(lcQpaTablet) << "Updated tablet mapping: " << m_wintabScreenGeometry;
+ if (QGuiApplication::primaryScreen()) {
+ qCDebug(lcQpaTablet) << " real desktop geometry: " << QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle());
+ }
+}
+
+void QWindowsTabletSupport::updateEffectiveScreenGeometry()
+{
+ QRect customGeometry;
+ bool dontUseWintabDesktopRect = false;
+
+ const QString geometry = qEnvironmentVariable("QT_WINTAB_DESKTOP_RECT");
+ if (!geometry.isEmpty()) {
+ QString tmp = QString::fromLatin1("([+-]?\\d+);([+-]?\\d+);(\\d+);(\\d+)");
+
+ QRegularExpression rex(tmp);
+ QRegularExpressionMatch match = rex.match(geometry);
+
+ if (match.hasMatch()) {
+ customGeometry.setRect(match.captured(1).toInt(),
+ match.captured(2).toInt(),
+ match.captured(3).toInt(),
+ match.captured(4).toInt());
+
+ qCDebug(lcQpaTablet) << "apply QT_WINTAB_DESKTOP_RECT:" << customGeometry;
+ } else {
+ qCWarning(lcQpaTablet) << "failed to parse QT_WINTAB_DESKTOP_RECT:" << geometry;
+ }
+ }
+
+ if (qEnvironmentVariableIsSet("QT_IGNORE_WINTAB_MAPPING")) {
+ if (!customGeometry.isValid()) {
+ qCDebug(lcQpaTablet) << "fallback mapping is requested via QT_IGNORE_WINTAB_MAPPING";
+ } else {
+ qCWarning(lcQpaTablet) << "ignoring QT_IGNORE_WINTAB_MAPPING, because QT_WINTAB_DESKTOP_RECT is set";
+ }
+ dontUseWintabDesktopRect = true;
+ }
+
+ m_effectiveScreenGeometry =
+ !customGeometry.isValid() ?
+ (dontUseWintabDesktopRect ?
+ QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle()) :
+ m_wintabScreenGeometry) :
+ customGeometry;
+}
+
bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam)
{
PACKET proximityBuffer[1]; // we are only interested in the first packet in this case
@@ -421,6 +504,8 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L
if (!totalPacks)
return false;
+ updateEffectiveScreenGeometry();
+
const UINT currentCursor = proximityBuffer[0].pkCursor;
UINT physicalCursorId;
QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + currentCursor, CSR_PHYSID, &physicalCursorId);
-@@ -534,8 +619,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
+@@ -537,8 +622,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
// in which case we snap the position to the mouse position.
// It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext
// area is always the virtual desktop.
- const QRect virtualDesktopArea =
- QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle());
+
+ const QRect virtualDesktopArea = m_effectiveScreenGeometry;
if (QWindowsContext::verbose > 1) {
qCDebug(lcQpaTablet) << __FUNCTION__ << "processing" << packetCount
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.h b/src/plugins/platforms/windows/qwindowstabletsupport.h
-index 5b1ddb52..e8b0c01b 100644
+index 8f97982308..fe7e7815d6 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.h
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.h
@@ -45,7 +45,9 @@
#include <QtCore/qvector.h>
#include <QtCore/qpoint.h>
+#include <QtCore/qrect.h>
- #include <QtCore/qmap.h>
+ #include <QtCore/qhash.h>
+#include <QtCore/qobject.h>
#include <wintab.h>
@@ -56,6 +58,7 @@ QT_BEGIN_NAMESPACE
class QDebug;
class QWindow;
class QRect;
+class QScreen;
struct QWindowsWinTab32DLL
{
@@ -108,7 +111,7 @@ struct QWindowsTabletDeviceData
QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t);
#endif
-class QWindowsTabletSupport
+class QWindowsTabletSupport : public QObject
{
Q_DISABLE_COPY(QWindowsTabletSupport)
@@ -141,9 +144,14 @@ public:
int absoluteRange() const { return m_absoluteRange; }
void setAbsoluteRange(int a) { m_absoluteRange = a; }
+private Q_SLOTS:
+ void slotPrimaryScreenChanged(QScreen *screen);
+ void slotScreenGeometryChanged();
+
private:
unsigned options() const;
QWindowsTabletDeviceData tabletInit(qint64 uniqueId, UINT cursorType) const;
+ void updateEffectiveScreenGeometry();
static QWindowsWinTab32DLL m_winTab32DLL;
const HWND m_window;
@@ -154,6 +162,9 @@ private:
int m_currentDevice = -1;
Mode m_mode = PenMode;
State m_state = PenUp;
+ QScreen *m_connectedScreen = 0;
+ QRect m_wintabScreenGeometry;
+ QRect m_effectiveScreenGeometry;
};
QT_END_NAMESPACE
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch b/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch
index 6048d12aaf..69c2927f63 100644
--- a/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch
+++ b/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch
@@ -1,61 +1,79 @@
-From 8b82db980186cec2834d86f213b5fd1504ed8d0a Mon Sep 17 00:00:00 2001
+From dd25106c340d91d20432375a2a22b85779eddb18 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Wed, 17 Apr 2019 17:39:10 +0300
-Subject: [PATCH] Switch stylus pointer type when the tablet is in the tablet
- proximity
+Subject: [PATCH 14/17] Switch stylus pointer type when the tablet is in the
+ tablet proximity
Some convertible tablet devices have a special stylus button that
converts the stylus into an eraser. Such button can be pressed right
when the stylus is in tablet surface proximity, so we should check
that not only during proximity event handling, but also while parsing
normal wintab packets.
+Make sure that we don't switch tablet pointer type while any **mapped**
+stylus button is pressed. Pressing the "eraser" button is reported
+in pkButtons, but it maps to none by CSR_SYSBTNMAP
+
https://bugs.kde.org/show_bug.cgi?id=405747
+https://bugs.kde.org/show_bug.cgi?id=408454
---
- .../windows/qwindowstabletsupport.cpp | 23 ++++++++++++++++++-
- 1 file changed, 22 insertions(+), 1 deletion(-)
+ .../windows/qwindowstabletsupport.cpp | 29 ++++++++++++++++---
+ 1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
-index 6a1bf02b..d82b33c1 100644
+index bfd0a9640b..02455536fe 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
-@@ -603,7 +603,6 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
+@@ -606,7 +606,6 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
return false;
const int currentDevice = m_devices.at(m_currentDevice).currentDevice;
- const int currentPointer = m_devices.at(m_currentDevice).currentPointerType;
const qint64 uniqueId = m_devices.at(m_currentDevice).uniqueId;
// The tablet can be used in 2 different modes (reflected in enum Mode),
-@@ -633,6 +632,28 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
+@@ -636,6 +635,31 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
for (int i = 0; i < packetCount ; ++i) {
const PACKET &packet = localPacketBuf[i];
+ int currentPointer = m_devices.at(m_currentDevice).currentPointerType;
+
+ const int packetPointerType = pointerType(packet.pkCursor);
-+ if (!packet.pkButtons && packetPointerType != currentPointer) {
++ Qt::MouseButtons buttons =
++ convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice));
++
++ if (buttons == Qt::NoButton && packetPointerType != currentPointer) {
+
+ QWindowSystemInterface::handleTabletLeaveProximityEvent(packet.pkTime,
+ m_devices.at(m_currentDevice).currentDevice,
+ m_devices.at(m_currentDevice).currentPointerType,
+ m_devices.at(m_currentDevice).uniqueId);
+
+
+
+ m_devices[m_currentDevice].currentPointerType = packetPointerType;
+
+ QWindowSystemInterface::handleTabletEnterProximityEvent(packet.pkTime,
+ m_devices.at(m_currentDevice).currentDevice,
+ m_devices.at(m_currentDevice).currentPointerType,
+ m_devices.at(m_currentDevice).uniqueId);
+
+ currentPointer = packetPointerType;
+ }
+
const int z = currentDevice == QTabletEvent::FourDMouse ? int(packet.pkZ) : 0;
QPointF globalPosF =
+@@ -709,9 +733,6 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
+ << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation;
+ }
+
+- Qt::MouseButtons buttons =
+- convertTabletButtons(packet.pkButtons, m_devices.at(m_currentDevice));
+-
+ QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF,
+ currentDevice, currentPointer,
+ buttons,
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch b/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch
index 29da88c514..cda9644e3b 100644
--- a/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch
+++ b/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch
@@ -1,38 +1,40 @@
-From 7dd05b77fe213ca2feb125503eb10c2567a4faee Mon Sep 17 00:00:00 2001
+From f83799913665e06e98c743326eafd47d78087477 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Thu, 18 Apr 2019 15:42:17 +0300
-Subject: [PATCH] Fix updating tablet pressure resolution on every proximity
- enter event
+Subject: [PATCH 15/17] Fix updating tablet pressure resolution on every
+ proximity enter event
The user can switch pressure sensitivity level in the driver,
which will make our saved values invalid (this option is
provided by Wacom drivers for compatibility reasons, and
it can be adjusted on the fly)
See the bug: https://bugs.kde.org/show_bug.cgi?id=391054
+
+Change-Id: I6cfdff27eaf5a587bf714871f1495a7ea150c553
---
src/plugins/platforms/windows/qwindowstabletsupport.cpp | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
-index d82b33c1..3393b091 100644
+index 02455536fe..e9d10a2b03 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
@@ -519,6 +519,14 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L
if (m_currentDevice < 0) {
m_currentDevice = m_devices.size();
m_devices.push_back(tabletInit(uniqueId, cursorType));
+ } else {
+ /**
+ * The user can switch pressure sensitivity level in the driver,
+ * which will make our saved values invalid (this option is
+ * provided by Wacom drivers for compatibility reasons, and
+ * it can be adjusted on the fly)
+ */
+ m_devices[m_currentDevice] = tabletInit(uniqueId, cursorType);
}
/**
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0030-Windows-QPA-Make-the-expected-screen-be-in-sync-with.patch b/3rdparty/ext_qt/0030-Windows-QPA-Make-the-expected-screen-be-in-sync-with.patch
deleted file mode 100644
index cafef5f4d7..0000000000
--- a/3rdparty/ext_qt/0030-Windows-QPA-Make-the-expected-screen-be-in-sync-with.patch
+++ /dev/null
@@ -1,59 +0,0 @@
-From e553433c3dfa2664140a2ccf4b479821fc382e83 Mon Sep 17 00:00:00 2001
-From: Andy Shaw <andy.shaw@qt.io>
-Date: Fri, 21 Dec 2018 15:53:57 +0100
-Subject: [PATCH 30/36] Windows QPA: Make the expected screen be in sync with
- the geometry changes
-
-When the window moves to a new screen then we should ensure the screen
-is updated at that point with the new size so it can account for any
-scaling changes.
-
-This reverts f1ec81b543fe1d5090acff298e24faf10a7bac63.
-
-Change-Id: I2be3aab677c4677841a07beaaf373f498483b320
-Fixes: QTBUG-72504
-Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
----
- src/plugins/platforms/windows/qwindowswindow.cpp | 10 +++++-----
- 1 file changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
-index 910d8dd209..865874846e 100644
---- a/src/plugins/platforms/windows/qwindowswindow.cpp
-+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
-@@ -1756,15 +1756,12 @@ void QWindowsWindow::checkForScreenChanged()
-
- QPlatformScreen *currentScreen = screen();
- const auto &screenManager = QWindowsContext::instance()->screenManager();
-- // QTBUG-62971: When dragging a window by its border, detect by mouse position
-- // to prevent it from oscillating between screens when it resizes
-- const QWindowsScreen *newScreen = testFlag(ResizeMoveActive)
-- ? screenManager.screenAtDp(QWindowsCursor::mousePosition())
-- : screenManager.screenForHwnd(m_data.hwnd);
-+ const QWindowsScreen *newScreen = screenManager.screenForHwnd(m_data.hwnd);
- if (newScreen != nullptr && newScreen != currentScreen) {
- qCDebug(lcQpaWindows).noquote().nospace() << __FUNCTION__
- << ' ' << window() << " \"" << currentScreen->name()
- << "\"->\"" << newScreen->name() << '"';
-+ setFlag(SynchronousGeometryChangeEvent);
- QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->screen());
- }
- }
-@@ -1783,11 +1780,14 @@ void QWindowsWindow::handleGeometryChange()
- fireExpose(QRect(QPoint(0, 0), m_data.geometry.size()), true);
- }
-
-+ const bool wasSync = testFlag(SynchronousGeometryChangeEvent);
- checkForScreenChanged();
-
- if (testFlag(SynchronousGeometryChangeEvent))
- QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
-
-+ if (!wasSync)
-+ clearFlag(SynchronousGeometryChangeEvent);
- qCDebug(lcQpaEvents) << __FUNCTION__ << this << window() << m_data.geometry;
- }
-
---
-2.18.0.windows.1
-
diff --git a/3rdparty/ext_qt/0031-Compute-logical-DPI-on-a-per-screen-basis.patch b/3rdparty/ext_qt/0031-Compute-logical-DPI-on-a-per-screen-basis.patch
index a8d63f0d3f..a108f67e32 100644
--- a/3rdparty/ext_qt/0031-Compute-logical-DPI-on-a-per-screen-basis.patch
+++ b/3rdparty/ext_qt/0031-Compute-logical-DPI-on-a-per-screen-basis.patch
@@ -1,115 +1,115 @@
-From 676320297d7e5654cbe66fe4bd86125824e05840 Mon Sep 17 00:00:00 2001
+From 44d40db1bd8a101ee28414edd27f4fa7e73fbc97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= <morten.sorvig@qt.io>
Date: Mon, 25 Apr 2016 09:27:48 +0200
-Subject: [PATCH 31/36] Compute logical DPI on a per-screen basis
+Subject: [PATCH 22/27] Compute logical DPI on a per-screen basis
The logical DPI reported to applications is the platform screen
logical DPI divided by the platform screen scale factor.
Use the screen in question when calculating the DPI instead of
the values from the main screen.
QHighDpiScaling::logicalDpi now takes a QScreen pointer.
Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Task-number: QTBUG-53022
Change-Id: I0f62b5878c37e3488e9a8cc48aef183ff822d0c4
---
src/gui/kernel/qhighdpiscaling.cpp | 20 +++++++++-----------
src/gui/kernel/qhighdpiscaling_p.h | 2 +-
src/gui/kernel/qscreen.cpp | 6 +++---
3 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
-index 22e46e0851..541d4f12af 100644
+index 22e46e08..541d4f12 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -224,7 +224,6 @@ bool QHighDpiScaling::m_usePixelDensity = false; // use scale factor from platfo
bool QHighDpiScaling::m_pixelDensityScalingActive = false; // pixel density scale factor > 1
bool QHighDpiScaling::m_globalScalingActive = false; // global scale factor is active
bool QHighDpiScaling::m_screenFactorSet = false; // QHighDpiScaling::setScreenFactor has been used
-QDpi QHighDpiScaling::m_logicalDpi = QDpi(-1,-1); // The scaled logical DPI of the primary screen
/*
Initializes the QHighDpiScaling global variables. Called before the
@@ -312,14 +311,6 @@ void QHighDpiScaling::updateHighDpiScaling()
}
}
m_active = m_globalScalingActive || m_screenFactorSet || m_pixelDensityScalingActive;
-
- QScreen *primaryScreen = QGuiApplication::primaryScreen();
- if (!primaryScreen)
- return;
- QPlatformScreen *platformScreen = primaryScreen->handle();
- qreal sf = screenSubfactor(platformScreen);
- QDpi primaryDpi = platformScreen->logicalDpi();
- m_logicalDpi = QDpi(primaryDpi.first / sf, primaryDpi.second / sf);
}
/*
@@ -405,9 +396,16 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
return factor;
}
-QDpi QHighDpiScaling::logicalDpi()
+QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
{
- return m_logicalDpi;
+ // (Note: m_active test is performed at call site.)
+ if (!screen)
+ return QDpi(96, 96);
+
+ qreal platformScreenfactor = screenSubfactor(screen->handle());
+ QDpi platformScreenDpi = screen->handle()->logicalDpi();
+ return QDpi(platformScreenDpi.first / platformScreenfactor,
+ platformScreenDpi.second / platformScreenfactor);
}
qreal QHighDpiScaling::factor(const QScreen *screen)
diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h
-index 83fc9452c5..ecd9ed6515 100644
+index 83fc9452..ecd9ed65 100644
--- a/src/gui/kernel/qhighdpiscaling_p.h
+++ b/src/gui/kernel/qhighdpiscaling_p.h
@@ -85,7 +85,7 @@ public:
static QPoint origin(const QPlatformScreen *platformScreen);
static QPoint mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen);
static QPoint mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen);
- static QDpi logicalDpi();
+ static QDpi logicalDpi(const QScreen *screen);
private:
static qreal screenSubfactor(const QPlatformScreen *screen);
diff --git a/src/gui/kernel/qscreen.cpp b/src/gui/kernel/qscreen.cpp
-index f208eb02be..82ee62e6b4 100644
+index f208eb02..82ee62e6 100644
--- a/src/gui/kernel/qscreen.cpp
+++ b/src/gui/kernel/qscreen.cpp
@@ -279,7 +279,7 @@ qreal QScreen::logicalDotsPerInchX() const
{
Q_D(const QScreen);
if (QHighDpiScaling::isActive())
- return QHighDpiScaling::logicalDpi().first;
+ return QHighDpiScaling::logicalDpi(this).first;
return d->logicalDpi.first;
}
@@ -295,7 +295,7 @@ qreal QScreen::logicalDotsPerInchY() const
{
Q_D(const QScreen);
if (QHighDpiScaling::isActive())
- return QHighDpiScaling::logicalDpi().second;
+ return QHighDpiScaling::logicalDpi(this).second;
return d->logicalDpi.second;
}
@@ -314,7 +314,7 @@ qreal QScreen::logicalDotsPerInchY() const
qreal QScreen::logicalDotsPerInch() const
{
Q_D(const QScreen);
- QDpi dpi = QHighDpiScaling::isActive() ? QHighDpiScaling::logicalDpi() : d->logicalDpi;
+ QDpi dpi = QHighDpiScaling::isActive() ? QHighDpiScaling::logicalDpi(this) : d->logicalDpi;
return (dpi.first + dpi.second) * qreal(0.5);
}
--
-2.18.0.windows.1
+2.22.0.windows.1
diff --git a/3rdparty/ext_qt/0032-Update-Dpi-and-scale-factor-computation.patch b/3rdparty/ext_qt/0032-Update-Dpi-and-scale-factor-computation.patch
index f01ee778af..c7cae24762 100644
--- a/3rdparty/ext_qt/0032-Update-Dpi-and-scale-factor-computation.patch
+++ b/3rdparty/ext_qt/0032-Update-Dpi-and-scale-factor-computation.patch
@@ -1,554 +1,554 @@
-From 3ef20bcb114c316efb74a0a704e918731847620c Mon Sep 17 00:00:00 2001
+From 0735b61f7e0868ab2272838ef52fe34aed0dfe25 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= <morten.sorvig@qt.io>
Date: Mon, 25 Apr 2016 11:31:34 +0200
-Subject: [PATCH 32/36] Update Dpi and scale factor computation
+Subject: [PATCH 23/27] Update Dpi and scale factor computation
Remove pixelScale() in favor of logicalBaseDpi(). Compute scale factor
based on logical DPI and logical base DPI, or optionally based on the
physical DPI.
Add policies for running the scale factor and adjusting the logical
DPI reported to the application. The policies are set via environment
variables:
QT_SCALE_FACTOR_ROUNDING_POLICY=Round|Ceil|Floor|RoundPreferFloor|PassThrough
QT_DPI_ADJUSTMENT_POLICY=AdjustDpi|DontAdjustDpi|AdjustUpOnly
QT_USE_PHYSICAL_DPI=0|1
Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Task-number: QTBUG-53022
Change-Id: I4846f223186df665eb0a9c827eaef0a96d1f458f
---
src/gui/kernel/qhighdpiscaling.cpp | 234 ++++++++++++++++--
src/gui/kernel/qhighdpiscaling_p.h | 29 +++
src/gui/kernel/qplatformscreen.cpp | 14 ++
src/gui/kernel/qplatformscreen.h | 1 +
.../android/qandroidplatformscreen.cpp | 8 +-
.../android/qandroidplatformscreen.h | 2 +-
src/plugins/platforms/cocoa/qcocoascreen.h | 1 +
.../platforms/windows/qwindowsscreen.cpp | 9 -
.../platforms/windows/qwindowsscreen.h | 2 +-
src/plugins/platforms/xcb/qxcbscreen.cpp | 11 -
src/plugins/platforms/xcb/qxcbscreen.h | 3 +-
tests/manual/highdpi/highdpi.pro | 1 +
12 files changed, 264 insertions(+), 51 deletions(-)
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
-index 541d4f12af..ae531569ce 100644
+index 541d4f12..ae531569 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -44,6 +44,9 @@
#include "private/qscreen_p.h"
#include <QtCore/qdebug.h>
+#include <QtCore/qmetaobject.h>
+
+#include <algorithm>
QT_BEGIN_NAMESPACE
@@ -54,6 +57,18 @@ static const char legacyDevicePixelEnvVar[] = "QT_DEVICE_PIXEL_RATIO";
static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
static const char autoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR";
static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
+static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
+static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
+static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI";
+
+// Reads and interprets the given environment variable as a bool,
+// returns the default value if not set.
+static bool qEnvironmentVariableAsBool(const char *name, bool defaultValue)
+{
+ bool ok = false;
+ int value = qEnvironmentVariableIntValue(name, &ok);
+ return ok ? value > 0 : defaultValue;
+}
static inline qreal initialGlobalScaleFactor()
{
@@ -247,6 +262,191 @@ static inline bool usePixelDensity()
qgetenv(legacyDevicePixelEnvVar).compare("auto", Qt::CaseInsensitive) == 0);
}
+qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
+{
+ // Determine if physical DPI should be used
+ static bool usePhysicalDpi = qEnvironmentVariableAsBool(usePhysicalDpiEnvVar, false);
+
+ // Calculate scale factor beased on platform screen DPI values
+ qreal factor;
+ QDpi platformBaseDpi = screen->logicalBaseDpi();
+ if (usePhysicalDpi) {
+ qreal platformPhysicalDpi = screen->screen()->physicalDotsPerInch();
+ factor = qreal(platformPhysicalDpi) / qreal(platformBaseDpi.first);
+ } else {
+ QDpi platformLogicalDpi = screen->logicalDpi();
+ factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
+ }
+
+ return factor;
+}
+
+template <class EnumType>
+struct EnumLookup
+{
+ const char *name;
+ EnumType value;
+};
+
+template <class EnumType>
+static bool operator==(const EnumLookup<EnumType> &e1, const EnumLookup<EnumType> &e2)
+{
+ return qstricmp(e1.name, e2.name) == 0;
+}
+
+template <class EnumType>
+static QByteArray joinEnumValues(const EnumLookup<EnumType> *i1, const EnumLookup<EnumType> *i2)
+{
+ QByteArray result;
+ for (; i1 < i2; ++i1) {
+ if (!result.isEmpty())
+ result += QByteArrayLiteral(", ");
+ result += i1->name;
+ }
+ return result;
+}
+
+using ScaleFactorRoundingPolicyLookup = EnumLookup<QHighDpiScaling::HighDpiScaleFactorRoundingPolicy>;
+
+static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
+{
+ {"Round", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Round},
+ {"Ceil", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Ceil},
+ {"Floor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Floor},
+ {"RoundPreferFloor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
+ {"PassThrough", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::PassThrough}
+};
+
+static QHighDpiScaling::HighDpiScaleFactorRoundingPolicy
+ lookupScaleFactorRoundingPolicy(const QByteArray &v)
+{
+ auto end = std::end(scaleFactorRoundingPolicyLookup);
+ auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
+ ScaleFactorRoundingPolicyLookup{v.constData(), QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::NotSet});
+ return it != end ? it->value : QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::NotSet;
+}
+
+using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
+
+static const DpiAdjustmentPolicyLookup dpiAdjustmentPolicyLookup[] =
+{
+ {"AdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Enabled},
+ {"DontAdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Disabled},
+ {"AdjustUpOnly", QHighDpiScaling::DpiAdjustmentPolicy::UpOnly}
+};
+
+static QHighDpiScaling::DpiAdjustmentPolicy
+ lookupDpiAdjustmentPolicy(const QByteArray &v)
+{
+ auto end = std::end(dpiAdjustmentPolicyLookup);
+ auto it = std::find(std::begin(dpiAdjustmentPolicyLookup), end,
+ DpiAdjustmentPolicyLookup{v.constData(), QHighDpiScaling::DpiAdjustmentPolicy::NotSet});
+ return it != end ? it->value : QHighDpiScaling::DpiAdjustmentPolicy::NotSet;
+}
+
+qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
+{
+ // Apply scale factor rounding policy. Using mathematically correct rounding
+ // may not give the most desirable visual results, especially for
+ // critical fractions like .5. In general, rounding down results in visual
+ // sizes that are smaller than the ideal size, and opposite for rounding up.
+ // Rounding down is then preferable since "small UI" is a more acceptable
+ // high-DPI experience than "large UI".
+ static auto scaleFactorRoundingPolicy = HighDpiScaleFactorRoundingPolicy::NotSet;
+
+ // Determine rounding policy
+ if (scaleFactorRoundingPolicy == HighDpiScaleFactorRoundingPolicy::NotSet) {
+ // Check environment
+ if (qEnvironmentVariableIsSet(scaleFactorRoundingPolicyEnvVar)) {
+ QByteArray policyText = qgetenv(scaleFactorRoundingPolicyEnvVar);
+ auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText);
+ if (policyEnumValue != HighDpiScaleFactorRoundingPolicy::NotSet) {
+ scaleFactorRoundingPolicy = policyEnumValue;
+ } else {
+ auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup),
+ std::end(scaleFactorRoundingPolicyLookup));
+ qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.",
+ policyText.constData(), values.constData());
+ }
+ } else {
+ // Set default policy if no environment variable is set.
+ scaleFactorRoundingPolicy = HighDpiScaleFactorRoundingPolicy::RoundPreferFloor;
+ }
+ }
+
+ // Apply rounding policy.
+ qreal roundedFactor = rawFactor;
+ switch (scaleFactorRoundingPolicy) {
+ case HighDpiScaleFactorRoundingPolicy::Round:
+ roundedFactor = qRound(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::Ceil:
+ roundedFactor = qCeil(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::Floor:
+ roundedFactor = qFloor(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
+ // Round up for .75 and higher. This favors "small UI" over "large UI".
+ roundedFactor = rawFactor - qFloor(rawFactor) < 0.75
+ ? qFloor(rawFactor) : qCeil(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::PassThrough:
+ case HighDpiScaleFactorRoundingPolicy::NotSet:
+ break;
+ }
+
+ // Don't round down to to zero; clamp the minimum (rounded) factor to 1.
+ // This is not a common case but can happen if a display reports a very
+ // low DPI.
+ if (scaleFactorRoundingPolicy != HighDpiScaleFactorRoundingPolicy::PassThrough)
+ roundedFactor = qMax(roundedFactor, qreal(1));
+
+ return roundedFactor;
+}
+
+QDpi QHighDpiScaling::effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor)
+{
+ // Apply DPI adjustment policy, if needed. If enabled this will change
+ // the reported logical DPI to account for the difference between the
+ // rounded scale factor and the actual scale factor. The effect
+ // is that text size will be correct for the screen dpi, but may be (slightly)
+ // out of sync with the rest of the UI. The amount of out-of-synch-ness
+ // depends on how well user code handles a non-standard DPI values, but
+ // since the adjustment is small (typically +/- 48 max) this might be OK.
+ static auto dpiAdjustmentPolicy = DpiAdjustmentPolicy::NotSet;
+
+ // Determine adjustment policy.
+ if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::NotSet) {
+ if (qEnvironmentVariableIsSet(dpiAdjustmentPolicyEnvVar)) {
+ QByteArray policyText = qgetenv(dpiAdjustmentPolicyEnvVar);
+ auto policyEnumValue = lookupDpiAdjustmentPolicy(policyText);
+ if (policyEnumValue != DpiAdjustmentPolicy::NotSet) {
+ dpiAdjustmentPolicy = policyEnumValue;
+ } else {
+ auto values = joinEnumValues(std::begin(dpiAdjustmentPolicyLookup),
+ std::end(dpiAdjustmentPolicyLookup));
+ qWarning("Unknown DPI adjustment policy: %s. Supported values are: %s.",
+ policyText.constData(), values.constData());
+ }
+ }
+ if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::NotSet)
+ dpiAdjustmentPolicy = DpiAdjustmentPolicy::UpOnly;
+ }
+
+ // Apply adjustment policy.
+ const QDpi baseDpi = screen->logicalBaseDpi();
+ const qreal dpiAdjustmentFactor = rawFactor / roundedFactor;
+
+ // Return the base DPI for cases where there is no adjustment
+ if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled)
+ return baseDpi;
+ if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1)
+ return baseDpi;
+
+ return QDpi(baseDpi.first * dpiAdjustmentFactor, baseDpi.second * dpiAdjustmentFactor);
+}
+
void QHighDpiScaling::initHighDpiScaling()
{
// Determine if there is a global scale factor set.
@@ -257,8 +457,6 @@ void QHighDpiScaling::initHighDpiScaling()
m_pixelDensityScalingActive = false; //set in updateHighDpiScaling below
- // we update m_active in updateHighDpiScaling, but while we create the
- // screens, we have to assume that m_usePixelDensity implies scaling
m_active = m_globalScalingActive || m_usePixelDensity;
}
@@ -310,7 +508,7 @@ void QHighDpiScaling::updateHighDpiScaling()
++i;
}
}
- m_active = m_globalScalingActive || m_screenFactorSet || m_pixelDensityScalingActive;
+ m_active = m_globalScalingActive || m_usePixelDensity;
}
/*
@@ -371,22 +569,8 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
{
qreal factor = qreal(1.0);
if (screen) {
- if (m_usePixelDensity) {
- qreal pixelDensity = screen->pixelDensity();
-
- // Pixel density reported by the screen is sometimes not precise enough,
- // so recalculate it: divide px (physical pixels) by dp (device-independent pixels)
- // for both width and height, and then use the average if it is different from
- // the one initially reported by the screen
- QRect screenGeometry = screen->geometry();
- qreal wFactor = qreal(screenGeometry.width()) / qRound(screenGeometry.width() / pixelDensity);
- qreal hFactor = qreal(screenGeometry.height()) / qRound(screenGeometry.height() / pixelDensity);
- qreal averageDensity = (wFactor + hFactor) / 2;
- if (!qFuzzyCompare(pixelDensity, averageDensity))
- pixelDensity = averageDensity;
-
- factor *= pixelDensity;
- }
+ if (m_usePixelDensity)
+ factor *= roundScaleFactor(rawScaleFactor(screen));
if (m_screenFactorSet) {
QVariant screenFactor = screen->screen()->property(scaleFactorProperty);
if (screenFactor.isValid())
@@ -399,13 +583,15 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
{
// (Note: m_active test is performed at call site.)
- if (!screen)
+ if (!screen || !screen->handle())
return QDpi(96, 96);
- qreal platformScreenfactor = screenSubfactor(screen->handle());
- QDpi platformScreenDpi = screen->handle()->logicalDpi();
- return QDpi(platformScreenDpi.first / platformScreenfactor,
- platformScreenDpi.second / platformScreenfactor);
+ if (!m_usePixelDensity)
+ return screen->handle()->logicalDpi();
+
+ const qreal scaleFactor = rawScaleFactor(screen->handle());
+ const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
+ return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor);
}
qreal QHighDpiScaling::factor(const QScreen *screen)
diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h
-index ecd9ed6515..55bddfeb88 100644
+index ecd9ed65..55bddfeb 100644
--- a/src/gui/kernel/qhighdpiscaling_p.h
+++ b/src/gui/kernel/qhighdpiscaling_p.h
@@ -71,7 +71,33 @@ typedef QPair<qreal, qreal> QDpi;
#ifndef QT_NO_HIGHDPISCALING
class Q_GUI_EXPORT QHighDpiScaling {
+ Q_GADGET
public:
+ enum class HighDpiScaleFactorRoundingPolicy {
+ NotSet,
+ Round,
+ Ceil,
+ Floor,
+ RoundPreferFloor,
+ PassThrough
+ };
+ Q_ENUM(HighDpiScaleFactorRoundingPolicy)
+
+ enum class DpiAdjustmentPolicy {
+ NotSet,
+ Enabled,
+ Disabled,
+ UpOnly
+ };
+ Q_ENUM(DpiAdjustmentPolicy)
+
+ QHighDpiScaling() = delete;
+ ~QHighDpiScaling() = delete;
+ QHighDpiScaling(const QHighDpiScaling &) = delete;
+ QHighDpiScaling &operator=(const QHighDpiScaling &) = delete;
+ QHighDpiScaling(QHighDpiScaling &&) = delete;
+ QHighDpiScaling &operator=(QHighDpiScaling &&) = delete;
+
static void initHighDpiScaling();
static void updateHighDpiScaling();
static void setGlobalFactor(qreal factor);
@@ -88,6 +114,9 @@ public:
static QDpi logicalDpi(const QScreen *screen);
private:
+ static qreal rawScaleFactor(const QPlatformScreen *screen);
+ static qreal roundScaleFactor(qreal rawFactor);
+ static QDpi effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor);
static qreal screenSubfactor(const QPlatformScreen *screen);
static qreal m_factor;
diff --git a/src/gui/kernel/qplatformscreen.cpp b/src/gui/kernel/qplatformscreen.cpp
-index b7b312e89e..07a2231228 100644
+index 21ae75ba..ff76528a 100644
--- a/src/gui/kernel/qplatformscreen.cpp
+++ b/src/gui/kernel/qplatformscreen.cpp
-@@ -194,6 +194,20 @@ QDpi QPlatformScreen::logicalDpi() const
+@@ -197,6 +197,20 @@ QDpi QPlatformScreen::logicalDpi() const
25.4 * s.height() / ps.height());
}
+/*!
+ Reimplement to return the base logical DPI for the platform. This
+ DPI value should correspond to a standard-DPI (1x) display. The
+ default implementation returns 96.
+
+ QtGui will use this value (together with logicalDpi) to compute
+ the scale factor when high-DPI scaling is enabled:
+ factor = logicalDPI / baseDPI
+*/
+QDpi QPlatformScreen::logicalBaseDpi() const
+{
+ return QDpi(96, 96);
+}
+
/*!
Reimplement this function in subclass to return the device pixel ratio
for the screen. This is the ratio between physical pixels and the
diff --git a/src/gui/kernel/qplatformscreen.h b/src/gui/kernel/qplatformscreen.h
-index e9d64c8a29..63b5d5a4a7 100644
+index e9d64c8a..63b5d5a4 100644
--- a/src/gui/kernel/qplatformscreen.h
+++ b/src/gui/kernel/qplatformscreen.h
@@ -113,6 +113,7 @@ public:
virtual QSizeF physicalSize() const;
virtual QDpi logicalDpi() const;
+ virtual QDpi logicalBaseDpi() const;
virtual qreal devicePixelRatio() const;
virtual qreal pixelDensity() const;
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp
-index 7dc8bb8080..80757c2135 100644
+index 7dc8bb80..80757c21 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.cpp
+++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp
@@ -401,15 +401,17 @@ void QAndroidPlatformScreen::doRedraw()
m_dirtyRect = QRect();
}
+static const int androidLogicalDpi = 72;
+
QDpi QAndroidPlatformScreen::logicalDpi() const
{
- qreal lDpi = QtAndroid::scaledDensity() * 72;
+ qreal lDpi = QtAndroid::scaledDensity() * androidLogicalDpi;
return QDpi(lDpi, lDpi);
}
-qreal QAndroidPlatformScreen::pixelDensity() const
+QDpi QAndroidPlatformScreen::logicalBaseDpi() const
{
- return QtAndroid::pixelDensity();
+ return QDpi(androidLogicalDpi, androidLogicalDpi);
}
Qt::ScreenOrientation QAndroidPlatformScreen::orientation() const
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.h b/src/plugins/platforms/android/qandroidplatformscreen.h
-index f15aeae3fd..5dc158e351 100644
+index f15aeae3..5dc158e3 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.h
+++ b/src/plugins/platforms/android/qandroidplatformscreen.h
@@ -103,7 +103,7 @@ protected:
private:
QDpi logicalDpi() const override;
- qreal pixelDensity() const override;
+ QDpi logicalBaseDpi() const override;
Qt::ScreenOrientation orientation() const override;
Qt::ScreenOrientation nativeOrientation() const override;
void surfaceChanged(JNIEnv *env, jobject surface, int w, int h) override;
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h
-index 9ded98df32..a73b97c771 100644
+index 9ded98df..a73b97c7 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.h
+++ b/src/plugins/platforms/cocoa/qcocoascreen.h
@@ -64,6 +64,7 @@ public:
qreal devicePixelRatio() const override;
QSizeF physicalSize() const override { return m_physicalSize; }
QDpi logicalDpi() const override { return m_logicalDpi; }
+ QDpi logicalBaseDpi() const override { return m_logicalDpi; }
qreal refreshRate() const override { return m_refreshRate; }
QString name() const override { return m_name; }
QPlatformCursor *cursor() const override { return m_cursor; }
diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp
-index 8a5f0e6577..ebde684038 100644
+index b70b0bbe..e34a8e3c 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.cpp
+++ b/src/plugins/platforms/windows/qwindowsscreen.cpp
@@ -254,15 +254,6 @@ QWindow *QWindowsScreen::windowAt(const QPoint &screenPoint, unsigned flags)
return result;
}
-qreal QWindowsScreen::pixelDensity() const
-{
- // QTBUG-49195: Use logical DPI instead of physical DPI to calculate
- // the pixel density since it is reflects the Windows UI scaling.
- // High DPI auto scaling should be disabled when the user chooses
- // small fonts on a High DPI monitor, resulting in lower logical DPI.
- return qMax(1, qRound(logicalDpi().first / 96));
-}
-
/*!
\brief Determine siblings in a virtual desktop system.
diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h
-index 33c9effa2a..a7b1c64e29 100644
+index 33c9effa..a7b1c64e 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.h
+++ b/src/plugins/platforms/windows/qwindowsscreen.h
@@ -87,7 +87,7 @@ public:
QImage::Format format() const override { return m_data.format; }
QSizeF physicalSize() const override { return m_data.physicalSizeMM; }
QDpi logicalDpi() const override { return m_data.dpi; }
- qreal pixelDensity() const override;
+ QDpi logicalBaseDpi() const override { return QDpi(96, 96); };
qreal devicePixelRatio() const override { return 1.0; }
qreal refreshRate() const override { return m_data.refreshRateHz; }
QString name() const override { return m_data.name; }
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
-index 57dbdc9bec..9af0794d29 100644
+index 57dbdc9b..9af0794d 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
@@ -671,11 +671,6 @@ QDpi QXcbScreen::logicalDpi() const
return m_virtualDesktop->dpi();
}
-qreal QXcbScreen::pixelDensity() const
-{
- return m_pixelDensity;
-}
-
QPlatformCursor *QXcbScreen::cursor() const
{
return m_cursor;
@@ -739,12 +734,6 @@ void QXcbScreen::updateGeometry(const QRect &geometry, uint8_t rotation)
if (m_sizeMillimeters.isEmpty())
m_sizeMillimeters = sizeInMillimeters(geometry.size(), m_virtualDesktop->dpi());
- qreal dpi = geometry.width() / physicalSize().width() * qreal(25.4);
-
- // Use 128 as a reference DPI on small screens. This favors "small UI" over "large UI".
- qreal referenceDpi = physicalSize().width() <= 320 ? 128 : 96;
-
- m_pixelDensity = qMax(1, qRound(dpi/referenceDpi));
m_geometry = geometry;
m_availableGeometry = geometry & m_virtualDesktop->workArea();
QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), m_geometry, m_availableGeometry);
diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h
-index be6c45e415..3f619d71c2 100644
+index be6c45e4..3f619d71 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.h
+++ b/src/plugins/platforms/xcb/qxcbscreen.h
@@ -161,7 +161,7 @@ public:
QImage::Format format() const override;
QSizeF physicalSize() const override { return m_sizeMillimeters; }
QDpi logicalDpi() const override;
- qreal pixelDensity() const override;
+ QDpi logicalBaseDpi() const override { return QDpi(96, 96); };
QPlatformCursor *cursor() const override;
qreal refreshRate() const override { return m_refreshRate; }
Qt::ScreenOrientation orientation() const override { return m_orientation; }
@@ -226,7 +226,6 @@ private:
Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation;
QXcbCursor *m_cursor;
int m_refreshRate = 60;
- int m_pixelDensity = 1;
QEdidParser m_edid;
};
diff --git a/tests/manual/highdpi/highdpi.pro b/tests/manual/highdpi/highdpi.pro
-index 9db083cd82..2de8ed3bb5 100644
+index 9db083cd..2de8ed3b 100644
--- a/tests/manual/highdpi/highdpi.pro
+++ b/tests/manual/highdpi/highdpi.pro
@@ -15,3 +15,4 @@ HEADERS += \
RESOURCES += \
highdpi.qrc
+DEFINES += HAVE_SCREEN_BASE_DPI
--
-2.18.0.windows.1
+2.22.0.windows.1
diff --git a/3rdparty/ext_qt/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch b/3rdparty/ext_qt/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch
index ef703408b1..c79e0e10e1 100644
--- a/3rdparty/ext_qt/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch
+++ b/3rdparty/ext_qt/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch
@@ -1,133 +1,133 @@
-From 3f00c3cddb35c4f17100b2becbb99dec4e48b351 Mon Sep 17 00:00:00 2001
+From 4757dc87c9350e40621b633ae8ac5d8a1f5228b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= <morten.sorvig@qt.io>
Date: Thu, 2 Jun 2016 09:52:21 +0200
-Subject: [PATCH 33/36] Move QT_FONT_DPI to cross-platform code
+Subject: [PATCH 24/27] Move QT_FONT_DPI to cross-platform code
This makes it possible to test the effects of setting
Qt::AA_HighDpiScaling/QT_AUTO_SCREEN_SCALE_FACTOR, with different DPI
values on all platforms.
This also makes it possible to access the actual DPI values reported
by the OS/WS via the QPlatformScreen API.
A drawback is that there is no single place to check the environment
variable; currently done in three places. This may be
further simplified later on.
Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Task-number: QTBUG-53022
Change-Id: Idd6463219d3ae58fe0ab72c17686cce2eb9dbadd
---
src/gui/kernel/qhighdpiscaling.cpp | 4 ++--
src/gui/kernel/qplatformscreen.cpp | 8 ++++++++
src/gui/kernel/qplatformscreen.h | 2 ++
src/gui/kernel/qscreen.cpp | 7 +++++--
src/gui/kernel/qwindowsysteminterface.cpp | 4 ++--
src/plugins/platforms/xcb/qxcbscreen.cpp | 4 ----
6 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
-index ae531569ce..fc8084c45a 100644
+index ae531569..fc8084c4 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -274,7 +274,7 @@ qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
qreal platformPhysicalDpi = screen->screen()->physicalDotsPerInch();
factor = qreal(platformPhysicalDpi) / qreal(platformBaseDpi.first);
} else {
- QDpi platformLogicalDpi = screen->logicalDpi();
+ const QDpi platformLogicalDpi = QPlatformScreen::overrideDpi(screen->logicalDpi());
factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
}
@@ -587,7 +587,7 @@ QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
return QDpi(96, 96);
if (!m_usePixelDensity)
- return screen->handle()->logicalDpi();
+ return QPlatformScreen::overrideDpi(screen->handle()->logicalDpi());
const qreal scaleFactor = rawScaleFactor(screen->handle());
const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
diff --git a/src/gui/kernel/qplatformscreen.cpp b/src/gui/kernel/qplatformscreen.cpp
-index 07a2231228..54ec211f2c 100644
+index ff76528a..9e684c9f 100644
--- a/src/gui/kernel/qplatformscreen.cpp
+++ b/src/gui/kernel/qplatformscreen.cpp
-@@ -194,6 +194,14 @@ QDpi QPlatformScreen::logicalDpi() const
+@@ -197,6 +197,14 @@ QDpi QPlatformScreen::logicalDpi() const
25.4 * s.height() / ps.height());
}
+// Helper function for accessing the platform screen logical dpi
+// which accounts for QT_FONT_DPI.
+QPair<qreal, qreal> QPlatformScreen::overrideDpi(const QPair<qreal, qreal> &in)
+{
+ static const int overrideDpi = qEnvironmentVariableIntValue("QT_FONT_DPI");
+ return overrideDpi > 0 ? QDpi(overrideDpi, overrideDpi) : in;
+}
+
/*!
Reimplement to return the base logical DPI for the platform. This
DPI value should correspond to a standard-DPI (1x) display. The
diff --git a/src/gui/kernel/qplatformscreen.h b/src/gui/kernel/qplatformscreen.h
-index 63b5d5a4a7..32e6bf7ec7 100644
+index 63b5d5a4..32e6bf7e 100644
--- a/src/gui/kernel/qplatformscreen.h
+++ b/src/gui/kernel/qplatformscreen.h
@@ -159,6 +159,8 @@ public:
// The platform screen's geometry in device independent coordinates
QRect deviceIndependentGeometry() const;
+ static QDpi overrideDpi(const QDpi &in);
+
protected:
void resizeMaximizedWindows();
diff --git a/src/gui/kernel/qscreen.cpp b/src/gui/kernel/qscreen.cpp
-index 82ee62e6b4..b856435f67 100644
+index 82ee62e6..b856435f 100644
--- a/src/gui/kernel/qscreen.cpp
+++ b/src/gui/kernel/qscreen.cpp
@@ -84,8 +84,11 @@ void QScreenPrivate::setPlatformScreen(QPlatformScreen *screen)
platformScreen->d_func()->screen = q;
orientation = platformScreen->orientation();
geometry = platformScreen->deviceIndependentGeometry();
- availableGeometry = QHighDpi::fromNative(platformScreen->availableGeometry(), QHighDpiScaling::factor(platformScreen), geometry.topLeft());
- logicalDpi = platformScreen->logicalDpi();
+ availableGeometry = QHighDpi::fromNative(platformScreen->availableGeometry(),
+ QHighDpiScaling::factor(platformScreen), geometry.topLeft());
+
+ logicalDpi = QPlatformScreen::overrideDpi(platformScreen->logicalDpi());
+
refreshRate = platformScreen->refreshRate();
// safeguard ourselves against buggy platform behavior...
if (refreshRate < 1.0)
diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp
-index 5b32405f5e..0bedae1bb4 100644
+index b3b6167c..efc1d90e 100644
--- a/src/gui/kernel/qwindowsysteminterface.cpp
+++ b/src/gui/kernel/qwindowsysteminterface.cpp
-@@ -780,8 +780,8 @@ void QWindowSystemInterface::handleScreenGeometryChange(QScreen *screen, const Q
+@@ -860,8 +860,8 @@ void QWindowSystemInterface::handleScreenGeometryChange(QScreen *screen, const Q
void QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(QScreen *screen, qreal dpiX, qreal dpiY)
{
- QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInchEvent *e =
- new QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInchEvent(screen, dpiX, dpiY); // ### tja
+ const QDpi effectiveDpi = QPlatformScreen::overrideDpi(QDpi{dpiX, dpiY});
+ auto e = new QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInchEvent(screen, effectiveDpi.first, effectiveDpi.second);
QWindowSystemInterfacePrivate::handleWindowSystemEvent(e);
}
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
-index 9af0794d29..27ffcad902 100644
+index 9af0794d..27ffcad9 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
@@ -660,10 +660,6 @@ QImage::Format QXcbScreen::format() const
QDpi QXcbScreen::logicalDpi() const
{
- static const int overrideDpi = qEnvironmentVariableIntValue("QT_FONT_DPI");
- if (overrideDpi)
- return QDpi(overrideDpi, overrideDpi);
-
const int forcedDpi = m_virtualDesktop->forcedDpi();
if (forcedDpi > 0) {
return QDpi(forcedDpi, forcedDpi);
--
-2.18.0.windows.1
+2.22.0.windows.1
diff --git a/3rdparty/ext_qt/0034-Update-QT_SCREEN_SCALE_FACTORS.patch b/3rdparty/ext_qt/0034-Update-QT_SCREEN_SCALE_FACTORS.patch
index b78e67977c..e1090a3416 100644
--- a/3rdparty/ext_qt/0034-Update-QT_SCREEN_SCALE_FACTORS.patch
+++ b/3rdparty/ext_qt/0034-Update-QT_SCREEN_SCALE_FACTORS.patch
@@ -1,155 +1,155 @@
-From f58ea52d89d9230ab9d441d96f6884044c4c686d Mon Sep 17 00:00:00 2001
+From 3520905a803a22c2ed0fe496ce3209c70cfa4db6 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@qt.io>
Date: Wed, 27 Feb 2019 08:46:47 +0100
-Subject: [PATCH 34/36] Update QT_SCREEN_SCALE_FACTORS
+Subject: [PATCH 25/27] Update QT_SCREEN_SCALE_FACTORS
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Store name->factor associations in a global QHash instead of on the
QScreen object, making them survive QScreen object deletion, for
example on disconnect/ connect cycles.
Make factors set with QT_SCREEN_SCALE_FACTORS override the screen
factors computed from platform plugin DPI values. This matches the use
case for QT_SCREEN_SCALE_FACTORS (but not the general scale factors
combine by multiplication”principle)
Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Task-number: QTBUG-53022
Change-Id: I12771249314ab0c073e609d62f57ac0d18d3b6ce
---
src/gui/kernel/qhighdpiscaling.cpp | 62 ++++++++++++++++++++++--------
src/gui/kernel/qhighdpiscaling_p.h | 2 +-
2 files changed, 47 insertions(+), 17 deletions(-)
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
-index fc8084c45a..b1350599b7 100644
+index fc8084c4..b1350599 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -61,6 +61,12 @@ static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_
static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI";
+// Per-screen scale factors for named screens set with QT_SCREEN_SCALE_FACTORS
+// are stored here. Use a global hash to keep the factor across screen
+// disconnect/connect cycles where the screen object may be deleted.
+typedef QHash<QString, qreal> QScreenScaleFactorHash;
+Q_GLOBAL_STATIC(QScreenScaleFactorHash, qNamedScreenScaleFactors);
+
// Reads and interprets the given environment variable as a bool,
// returns the default value if not set.
static bool qEnvironmentVariableAsBool(const char *name, bool defaultValue)
@@ -478,20 +484,19 @@ void QHighDpiScaling::updateHighDpiScaling()
int i = 0;
const auto specs = qgetenv(screenFactorsEnvVar).split(';');
for (const QByteArray &spec : specs) {
- QScreen *screen = 0;
int equalsPos = spec.lastIndexOf('=');
- double factor = 0;
+ qreal factor = 0;
if (equalsPos > 0) {
// support "name=factor"
QByteArray name = spec.mid(0, equalsPos);
QByteArray f = spec.mid(equalsPos + 1);
bool ok;
factor = f.toDouble(&ok);
- if (ok) {
+ if (ok && factor > 0 ) {
const auto screens = QGuiApplication::screens();
for (QScreen *s : screens) {
if (s->name() == QString::fromLocal8Bit(name)) {
- screen = s;
+ setScreenFactor(s, factor);
break;
}
}
@@ -500,11 +505,11 @@ void QHighDpiScaling::updateHighDpiScaling()
// listing screens in order
bool ok;
factor = spec.toDouble(&ok);
- if (ok && i < QGuiApplication::screens().count())
- screen = QGuiApplication::screens().at(i);
+ if (ok && factor > 0 && i < QGuiApplication::screens().count()) {
+ QScreen *screen = QGuiApplication::screens().at(i);
+ setScreenFactor(screen, factor);
+ }
}
- if (screen)
- setScreenFactor(screen, factor);
++i;
}
}
@@ -540,7 +545,14 @@ void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor)
m_screenFactorSet = true;
m_active = true;
}
- screen->setProperty(scaleFactorProperty, QVariant(factor));
+
+ // Prefer associating the factor with screen name over the object
+ // since the screen object may be deleted on screen disconnects.
+ const QString name = screen->name();
+ if (!name.isEmpty())
+ qNamedScreenScaleFactors()->insert(name, factor);
+ else
+ screen->setProperty(scaleFactorProperty, QVariant(factor));
// hack to force re-evaluation of screen geometry
if (screen->handle())
@@ -568,15 +580,33 @@ QPoint QHighDpiScaling::mapPositionFromNative(const QPoint &pos, const QPlatform
qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
{
qreal factor = qreal(1.0);
- if (screen) {
- if (m_usePixelDensity)
- factor *= roundScaleFactor(rawScaleFactor(screen));
- if (m_screenFactorSet) {
- QVariant screenFactor = screen->screen()->property(scaleFactorProperty);
- if (screenFactor.isValid())
- factor *= screenFactor.toReal();
+ if (!screen)
+ return factor;
+
+ // Unlike the other code where factors are combined
+ // by multiplication, factors from QT_SCREEN_SCALE_FACTORS takes
+ // precedence over the factor computed from platform plugin
+ // DPI. The rationale is that the user is setting the factor
+ // to override erroneous DPI values.
+ bool screenPropertyUsed = false;
+ if (m_screenFactorSet) {
+ // Check if there is a factor set on the screen object or
+ // associated with the screen name. These are mutually
+ // exclusive, so checking order is not significant.
+ QVariant byIndex = screen->screen()->property(scaleFactorProperty);
+ auto byName = qNamedScreenScaleFactors()->find(screen->name());
+ if (byIndex.isValid()) {
+ screenPropertyUsed = true;
+ factor = byIndex.toReal();
+ } else if (byName != qNamedScreenScaleFactors()->end()) {
+ screenPropertyUsed = true;
+ factor = *byName;
}
}
+
+ if (!screenPropertyUsed && m_usePixelDensity)
+ factor = roundScaleFactor(rawScaleFactor(screen));
+
return factor;
}
diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h
-index 55bddfeb88..d3f71854a8 100644
+index 55bddfeb..d3f71854 100644
--- a/src/gui/kernel/qhighdpiscaling_p.h
+++ b/src/gui/kernel/qhighdpiscaling_p.h
@@ -101,7 +101,7 @@ public:
static void initHighDpiScaling();
static void updateHighDpiScaling();
static void setGlobalFactor(qreal factor);
- static void setScreenFactor(QScreen *window, qreal factor);
+ static void setScreenFactor(QScreen *screen, qreal factor);
static bool isActive() { return m_active; }
static qreal factor(const QWindow *window);
--
-2.18.0.windows.1
+2.22.0.windows.1
diff --git a/3rdparty/ext_qt/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch b/3rdparty/ext_qt/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch
index 980f3f1670..94af9e142c 100644
--- a/3rdparty/ext_qt/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch
+++ b/3rdparty/ext_qt/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch
@@ -1,101 +1,101 @@
-From 4fdc698603e5ac3bc0cf107fdf3880ba68dbfb02 Mon Sep 17 00:00:00 2001
+From f9f524b9cf964be802682e30571bf83d6cbf16b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= <morten.sorvig@qt.io>
Date: Thu, 10 Nov 2016 14:17:53 +0100
-Subject: [PATCH 35/36] Deprecate QT_AUTO_SCREEN_SCALE_FACTOR
+Subject: [PATCH 26/27] Deprecate QT_AUTO_SCREEN_SCALE_FACTOR
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replace by QT_ENABLE_HIGHDPI_SCALING.
QT_AUTO_SCREEN_SCALE_FACTOR has the usability problem that it mixes
enabling the high-DPI scaling mode with the method of getting screen
scale factors (“auto”). Due to this, it ends up with a slightly
strange name.
QT_ENABLE_HIGHDPI_SCALING matches the C++ option
(Qt::AA_EnableHighDPiScaling), and leaves the scale factor acquisition
method unspecified, possibly to be set by some other means (like
QT_SCREEN_SCALE_FACTORS).
Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Task-number: QTBUG-53022
Change-Id: I30033d91175a00db7837efc9c48c33396f5f0449
---
src/gui/kernel/qhighdpiscaling.cpp | 29 +++++++++++++++++++++++------
1 file changed, 23 insertions(+), 6 deletions(-)
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
-index b1350599b7..52c0665b5b 100644
+index b1350599..52c0665b 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -54,8 +54,10 @@ Q_LOGGING_CATEGORY(lcScaling, "qt.scaling");
#ifndef QT_NO_HIGHDPISCALING
static const char legacyDevicePixelEnvVar[] = "QT_DEVICE_PIXEL_RATIO";
+static const char legacyAutoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR";
+
+static const char enableHighDpiScalingEnvVar[] = "QT_ENABLE_HIGHDPI_SCALING";
static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
-static const char autoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR";
static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
@@ -88,17 +90,24 @@ static inline qreal initialGlobalScaleFactor()
result = f;
}
} else {
+ // Check for deprecated environment variables.
if (qEnvironmentVariableIsSet(legacyDevicePixelEnvVar)) {
qWarning("Warning: %s is deprecated. Instead use:\n"
" %s to enable platform plugin controlled per-screen factors.\n"
- " %s to set per-screen factors.\n"
+ " %s to set per-screen DPI.\n"
" %s to set the application global scale factor.",
- legacyDevicePixelEnvVar, autoScreenEnvVar, screenFactorsEnvVar, scaleFactorEnvVar);
+ legacyDevicePixelEnvVar, legacyAutoScreenEnvVar, screenFactorsEnvVar, scaleFactorEnvVar);
int dpr = qEnvironmentVariableIntValue(legacyDevicePixelEnvVar);
if (dpr > 0)
result = dpr;
}
+
+ if (qEnvironmentVariableIsSet(legacyAutoScreenEnvVar)) {
+ qWarning("Warning: %s is deprecated. Instead use:\n"
+ " %s to enable platform plugin controlled per-screen factors.",
+ legacyAutoScreenEnvVar, enableHighDpiScalingEnvVar);
+ }
}
return result;
}
@@ -256,16 +265,24 @@ static inline bool usePixelDensity()
// Determine if we should set a scale factor based on the pixel density
// reported by the platform plugin. There are several enablers and several
// disablers. A single disable may veto all other enablers.
+
+ // First, check of there is an explicit disable.
if (QCoreApplication::testAttribute(Qt::AA_DisableHighDpiScaling))
return false;
bool screenEnvValueOk;
- const int screenEnvValue = qEnvironmentVariableIntValue(autoScreenEnvVar, &screenEnvValueOk);
+ const int screenEnvValue = qEnvironmentVariableIntValue(legacyAutoScreenEnvVar, &screenEnvValueOk);
if (screenEnvValueOk && screenEnvValue < 1)
return false;
+ bool enableEnvValueOk;
+ const int enableEnvValue = qEnvironmentVariableIntValue(enableHighDpiScalingEnvVar, &enableEnvValueOk);
+ if (enableEnvValueOk && enableEnvValue < 1)
+ return false;
+
+ // Then return if there was an enable.
return QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling)
|| (screenEnvValueOk && screenEnvValue > 0)
- || (qEnvironmentVariableIsSet(legacyDevicePixelEnvVar) &&
- qgetenv(legacyDevicePixelEnvVar).compare("auto", Qt::CaseInsensitive) == 0);
+ || (enableEnvValueOk && enableEnvValue > 0)
+ || (qEnvironmentVariableIsSet(legacyDevicePixelEnvVar) && qgetenv(legacyDevicePixelEnvVar).toLower() == "auto");
}
qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
--
-2.18.0.windows.1
+2.22.0.windows.1
diff --git a/3rdparty/ext_qt/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch b/3rdparty/ext_qt/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch
index 74f2b666e7..e1789d0101 100644
--- a/3rdparty/ext_qt/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch
+++ b/3rdparty/ext_qt/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch
@@ -1,314 +1,314 @@
-From 8e4d9b59af6b64345dd2a23656cf157ad76614eb Mon Sep 17 00:00:00 2001
+From 8f8d32fd20b774f4857b557be665a243870edd8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= <morten.sorvig@qt.io>
Date: Sat, 7 Oct 2017 01:35:29 +0200
-Subject: [PATCH 36/36] Add high-DPI scale factor rounding policy C++ API
+Subject: [PATCH 27/27] Add high-DPI scale factor rounding policy C++ API
This API enables tuning of how Qt rounds fractional scale factors, and
corresponds to the QT_SCALE_FACTOR_ROUNDING_POLICY environment
variable
New API:
Qt::HighDPiScaleFactorRoundingPolicy
QGuiApplication::setHighDpiScaleFactorRoundingPolicy()
QGuiApplication::highDpiScaleFactorRoundingPolicy()
Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Task-number: QTBUG-53022
Change-Id: Ic360f26a173caa757e4ebde35ce08a6b74290b7d
---
src/corelib/global/qnamespace.h | 10 +++++++
src/corelib/global/qnamespace.qdoc | 22 ++++++++++++++
src/gui/kernel/qguiapplication.cpp | 44 ++++++++++++++++++++++++++++
src/gui/kernel/qguiapplication.h | 3 ++
src/gui/kernel/qguiapplication_p.h | 1 +
src/gui/kernel/qhighdpiscaling.cpp | 47 +++++++++++++++++-------------
src/gui/kernel/qhighdpiscaling_p.h | 10 -------
7 files changed, 106 insertions(+), 31 deletions(-)
diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h
-index 3ab9921986..fea3d4d8da 100644
+index 3ab99219..fea3d4d8 100644
--- a/src/corelib/global/qnamespace.h
+++ b/src/corelib/global/qnamespace.h
@@ -1728,6 +1728,15 @@ public:
ChecksumItuV41
};
+ enum class HighDpiScaleFactorRoundingPolicy {
+ NotSet,
+ Round,
+ Ceil,
+ Floor,
+ RoundPreferFloor,
+ PassThrough
+ };
+
#ifndef Q_QDOC
// NOTE: Generally, do not add QT_Q_ENUM if a corresponding Q_Q_FLAG exists.
QT_Q_ENUM(ScrollBarPolicy)
@@ -1813,6 +1822,7 @@ public:
QT_Q_ENUM(MouseEventSource)
QT_Q_FLAG(MouseEventFlag)
QT_Q_ENUM(ChecksumType)
+ QT_Q_ENUM(HighDpiScaleFactorRoundingPolicy)
QT_Q_ENUM(TabFocusBehavior)
#endif // Q_DOC
diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc
-index 5bba8c5fe5..a3ca70a74d 100644
+index 5bba8c5f..a3ca70a7 100644
--- a/src/corelib/global/qnamespace.qdoc
+++ b/src/corelib/global/qnamespace.qdoc
@@ -3252,3 +3252,25 @@
\value ChecksumItuV41 Checksum calculation based on ITU-V.41.
*/
+
+/*!
+ \enum Qt::HighDpiScaleFactorRoundingPolicy
+ \since 5.14
+
+ This enum describes the possible High-DPI scale factor rounding policies, which
+ decide how non-integer scale factors (such as Windows 150%) are handled.
+
+ The active policy is set by calling QGuiApplication::setHighDdpiScaleFactorRoundingPolicy() before
+ the application object is created, or by setting the QT_SCALE_FACTOR_ROUNDING_POLICY
+ environment variable.
+
+ \sa QGuiApplication::setHighDdpiScaleFactorRoundingPolicy()
+ \sa AA_EnableHighDpiScaling.
+
+ \omitvalue NotSet
+ \value Round Round up for .5 and above.
+ \value Ceil Always round up.
+ \value Floor Always round down.
+ \value RoundPreferFloor Round up for .75 and above.
+ \value PassThrough Don't round.
+*/
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp
-index fd01f8bb7b..3541c1ae59 100644
+index 4d61d9fd..681cbd85 100644
--- a/src/gui/kernel/qguiapplication.cpp
+++ b/src/gui/kernel/qguiapplication.cpp
@@ -146,6 +146,8 @@ QString QGuiApplicationPrivate::styleOverride;
Qt::ApplicationState QGuiApplicationPrivate::applicationState = Qt::ApplicationInactive;
+Qt::HighDpiScaleFactorRoundingPolicy QGuiApplicationPrivate::highDpiScaleFactorRoundingPolicy =
+ Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor;
bool QGuiApplicationPrivate::highDpiScalingUpdated = false;
QPointer<QWindow> QGuiApplicationPrivate::currentDragWindow;
@@ -677,6 +679,8 @@ QGuiApplication::~QGuiApplication()
QGuiApplicationPrivate::lastCursorPosition = {qInf(), qInf()};
QGuiApplicationPrivate::currentMousePressWindow = QGuiApplicationPrivate::currentMouseWindow = nullptr;
QGuiApplicationPrivate::applicationState = Qt::ApplicationInactive;
+ QGuiApplicationPrivate::highDpiScaleFactorRoundingPolicy =
+ Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor;
QGuiApplicationPrivate::highDpiScalingUpdated = false;
QGuiApplicationPrivate::currentDragWindow = nullptr;
QGuiApplicationPrivate::tabletDevicePoints.clear();
-@@ -3448,6 +3452,46 @@ Qt::ApplicationState QGuiApplication::applicationState()
+@@ -3449,6 +3453,46 @@ Qt::ApplicationState QGuiApplication::applicationState()
return QGuiApplicationPrivate::applicationState;
}
+/*!
+ \since 5.14
+
+ Sets the high-DPI scale factor rounding policy for the application. The
+ policy decides how non-integer scale factors (such as Windows 150%) are
+ handled, for applications that have AA_EnableHighDpiScaling enabled.
+
+ The two principal options are whether fractional scale factors should
+ be rounded to an integer or not. Keeping the scale factor as-is will
+ make the user interface size match the OS setting exactly, but may cause
+ painting errors, for example with the Windows style.
+
+ If rounding is wanted, then which type of rounding should be decided
+ next. Mathematically correct rounding is supported but may not give
+ the best visual results: Consider if you want to render 1.5x as 1x
+ ("small UI") or as 2x ("large UI"). See the Qt::HighDpiScaleFactorRoundingPolicy
+ enum for a complete list of all options.
+
+ This function must be called before creating the application object,
+ and can be overridden by setting the QT_SCALE_FACTOR_ROUNDING_POLICY
+ environment variable. The QGuiApplication::highDpiScaleFactorRoundingPolicy()
+ accessor will reflect the environment, if set.
+
+ The default value is Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor.
+*/
+void QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy policy)
+{
+ QGuiApplicationPrivate::highDpiScaleFactorRoundingPolicy = policy;
+}
+
+/*!
+ \since 5.14
+
+ Returns the high-DPI scale factor rounding policy.
+*/
+Qt::HighDpiScaleFactorRoundingPolicy QGuiApplication::highDpiScaleFactorRoundingPolicy()
+{
+ return QGuiApplicationPrivate::highDpiScaleFactorRoundingPolicy;
+}
+
/*!
\since 5.2
\fn void QGuiApplication::applicationStateChanged(Qt::ApplicationState state)
diff --git a/src/gui/kernel/qguiapplication.h b/src/gui/kernel/qguiapplication.h
-index 02dffef0fe..2814ba1d1b 100644
+index 02dffef0..2814ba1d 100644
--- a/src/gui/kernel/qguiapplication.h
+++ b/src/gui/kernel/qguiapplication.h
@@ -156,6 +156,9 @@ public:
static Qt::ApplicationState applicationState();
+ static void setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy policy);
+ static Qt::HighDpiScaleFactorRoundingPolicy highDpiScaleFactorRoundingPolicy();
+
static int exec();
bool notify(QObject *, QEvent *) override;
diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h
-index 042a36c31f..7962bb0177 100644
+index 042a36c3..7962bb01 100644
--- a/src/gui/kernel/qguiapplication_p.h
+++ b/src/gui/kernel/qguiapplication_p.h
@@ -216,6 +216,7 @@ public:
static QWindow *currentMouseWindow;
static QWindow *currentMousePressWindow;
static Qt::ApplicationState applicationState;
+ static Qt::HighDpiScaleFactorRoundingPolicy highDpiScaleFactorRoundingPolicy;
static bool highDpiScalingUpdated;
static QPointer<QWindow> currentDragWindow;
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
-index 52c0665b5b..29b6d7e7c2 100644
+index 52c0665b..29b6d7e7 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -329,24 +329,24 @@ static QByteArray joinEnumValues(const EnumLookup<EnumType> *i1, const EnumLooku
return result;
}
-using ScaleFactorRoundingPolicyLookup = EnumLookup<QHighDpiScaling::HighDpiScaleFactorRoundingPolicy>;
+using ScaleFactorRoundingPolicyLookup = EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy>;
static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
{
- {"Round", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Round},
- {"Ceil", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Ceil},
- {"Floor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Floor},
- {"RoundPreferFloor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
- {"PassThrough", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::PassThrough}
+ {"Round", Qt::HighDpiScaleFactorRoundingPolicy::Round},
+ {"Ceil", Qt::HighDpiScaleFactorRoundingPolicy::Ceil},
+ {"Floor", Qt::HighDpiScaleFactorRoundingPolicy::Floor},
+ {"RoundPreferFloor", Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
+ {"PassThrough", Qt::HighDpiScaleFactorRoundingPolicy::PassThrough}
};
-static QHighDpiScaling::HighDpiScaleFactorRoundingPolicy
+static Qt::HighDpiScaleFactorRoundingPolicy
lookupScaleFactorRoundingPolicy(const QByteArray &v)
{
auto end = std::end(scaleFactorRoundingPolicyLookup);
auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
- ScaleFactorRoundingPolicyLookup{v.constData(), QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::NotSet});
- return it != end ? it->value : QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::NotSet;
+ ScaleFactorRoundingPolicyLookup{v.constData(), Qt::HighDpiScaleFactorRoundingPolicy::NotSet});
+ return it != end ? it->value : Qt::HighDpiScaleFactorRoundingPolicy::NotSet;
}
using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
@@ -375,15 +375,15 @@ qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
// sizes that are smaller than the ideal size, and opposite for rounding up.
// Rounding down is then preferable since "small UI" is a more acceptable
// high-DPI experience than "large UI".
- static auto scaleFactorRoundingPolicy = HighDpiScaleFactorRoundingPolicy::NotSet;
+ static auto scaleFactorRoundingPolicy = Qt::HighDpiScaleFactorRoundingPolicy::NotSet;
// Determine rounding policy
- if (scaleFactorRoundingPolicy == HighDpiScaleFactorRoundingPolicy::NotSet) {
+ if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::NotSet) {
// Check environment
if (qEnvironmentVariableIsSet(scaleFactorRoundingPolicyEnvVar)) {
QByteArray policyText = qgetenv(scaleFactorRoundingPolicyEnvVar);
auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText);
- if (policyEnumValue != HighDpiScaleFactorRoundingPolicy::NotSet) {
+ if (policyEnumValue != Qt::HighDpiScaleFactorRoundingPolicy::NotSet) {
scaleFactorRoundingPolicy = policyEnumValue;
} else {
auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup),
@@ -391,38 +391,43 @@ qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.",
policyText.constData(), values.constData());
}
+ }
+
+ // Check application object if no environment value was set.
+ if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::NotSet) {
+ scaleFactorRoundingPolicy = QGuiApplication::highDpiScaleFactorRoundingPolicy();
} else {
- // Set default policy if no environment variable is set.
- scaleFactorRoundingPolicy = HighDpiScaleFactorRoundingPolicy::RoundPreferFloor;
+ // Make application setting reflect environment
+ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(scaleFactorRoundingPolicy);
}
}
// Apply rounding policy.
qreal roundedFactor = rawFactor;
switch (scaleFactorRoundingPolicy) {
- case HighDpiScaleFactorRoundingPolicy::Round:
+ case Qt::HighDpiScaleFactorRoundingPolicy::Round:
roundedFactor = qRound(rawFactor);
break;
- case HighDpiScaleFactorRoundingPolicy::Ceil:
+ case Qt::HighDpiScaleFactorRoundingPolicy::Ceil:
roundedFactor = qCeil(rawFactor);
break;
- case HighDpiScaleFactorRoundingPolicy::Floor:
+ case Qt::HighDpiScaleFactorRoundingPolicy::Floor:
roundedFactor = qFloor(rawFactor);
break;
- case HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
+ case Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
// Round up for .75 and higher. This favors "small UI" over "large UI".
roundedFactor = rawFactor - qFloor(rawFactor) < 0.75
? qFloor(rawFactor) : qCeil(rawFactor);
break;
- case HighDpiScaleFactorRoundingPolicy::PassThrough:
- case HighDpiScaleFactorRoundingPolicy::NotSet:
+ case Qt::HighDpiScaleFactorRoundingPolicy::PassThrough:
+ case Qt::HighDpiScaleFactorRoundingPolicy::NotSet:
break;
}
// Don't round down to to zero; clamp the minimum (rounded) factor to 1.
// This is not a common case but can happen if a display reports a very
// low DPI.
- if (scaleFactorRoundingPolicy != HighDpiScaleFactorRoundingPolicy::PassThrough)
+ if (scaleFactorRoundingPolicy != Qt::HighDpiScaleFactorRoundingPolicy::PassThrough)
roundedFactor = qMax(roundedFactor, qreal(1));
return roundedFactor;
diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h
-index d3f71854a8..ae361a9ddb 100644
+index d3f71854..ae361a9d 100644
--- a/src/gui/kernel/qhighdpiscaling_p.h
+++ b/src/gui/kernel/qhighdpiscaling_p.h
@@ -73,16 +73,6 @@ typedef QPair<qreal, qreal> QDpi;
class Q_GUI_EXPORT QHighDpiScaling {
Q_GADGET
public:
- enum class HighDpiScaleFactorRoundingPolicy {
- NotSet,
- Round,
- Ceil,
- Floor,
- RoundPreferFloor,
- PassThrough
- };
- Q_ENUM(HighDpiScaleFactorRoundingPolicy)
-
enum class DpiAdjustmentPolicy {
NotSet,
Enabled,
--
-2.18.0.windows.1
+2.22.0.windows.1
diff --git a/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch b/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch
index 8b79e1f5dd..64d3476751 100644
--- a/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch
+++ b/3rdparty/ext_qt/0050-Fix-using-tablet-on-QML-widgets.patch
@@ -1,52 +1,52 @@
-From bb58e3fda70d83f696f1183defc93e6501a69c55 Mon Sep 17 00:00:00 2001
+From 8281d9ff26581797bf489d937824f2142f97b027 Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Wed, 15 May 2019 19:39:44 +0300
-Subject: [PATCH 1/2] Fix using tablet on QML widgets
+Subject: [PATCH 2/3] Fix using tablet on QML widgets
In previous versions of Qt (wintab impeplementation) the events were
marked by Qt::MouseEventSynthesizedBySystem flag only when they were
synthesized from touch, not from tablet events. This is what
QWindowsTabletSupport does and what QQuickWindow expects (it
filters out all synthesized events). This patch recovers the old behavior
for the new QWindowsPointerHandler tablet API implementation.
See bug: https://bugs.kde.org/show_bug.cgi?id=406668
---
src/plugins/platforms/windows/qwindowspointerhandler.cpp | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-index 190fb208..07f4d41e 100644
+index fd3d711470..bf201b87e0 100644
--- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-@@ -627,14 +627,16 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
+@@ -648,14 +648,16 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
#endif
}
-static inline bool isMouseEventSynthesizedFromPenOrTouch()
+static inline bool isMouseEventSynthesizedFromPen()
{
// For details, see
// https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages
const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00;
const LONG_PTR MI_WP_SIGNATURE = 0xFF515700;
- return ((::GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE);
+ const quint64 extraInfo = ::GetMessageExtraInfo();
+
+ return ((extraInfo & SIGNATURE_MASK) == MI_WP_SIGNATURE) && (extraInfo & 0x80);
}
bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window,
-@@ -705,7 +707,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
+@@ -725,7 +727,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
}
Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
- if (isMouseEventSynthesizedFromPenOrTouch()) {
+ if (isMouseEventSynthesizedFromPen()) {
if (QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)
return false;
source = Qt::MouseEventSynthesizedBySystem;
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch b/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch
index 2dfecbda5c..342d4f044e 100644
--- a/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch
+++ b/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch
@@ -1,243 +1,251 @@
-From 9a2a150476d51d786e011e1ddde4bb77c7b9d146 Mon Sep 17 00:00:00 2001
+From c37ff0abfed81284721ec00665579b73107a38ca Mon Sep 17 00:00:00 2001
From: Dmitry Kazakov <dimula73@gmail.com>
Date: Wed, 15 May 2019 19:54:52 +0300
-Subject: [PATCH 2/2] Add workaround for handling table press correctly in
+Subject: [PATCH 3/3] Add workaround for handling table press correctly in
WinInk mode
Original problem: widgets do not get synthesized mouse-down and
mouse-press events until the stylus is released
Reason: if the app accepts the event, WndProc should report
that to the system (by returning true). This is the only way to
prevent Windows from starting some system-wide gestures, like
click+hold -> right button click. If we ignore the event, then
OS postpones all synthesized mouse events until the entire gesture
is completed.
The patch implements a "hackish" workaround for the original problem
by using the following rules:
1) All tablet-move events are ignored (without synthesized mouse events
OS doesn't generate any Enter/Leave events)
2) All not-accepted tablet press- and release-events and also reported as
ignored (without it D&D doesn't work).
3) All accepted tablet press- and release-events are reported as "accepted",
**but** we artificially synthesize mouse events for them.
-TODO: there are still two problems:
+TODO: there are still one problem:
1) Perhaps this synthesizeMouseEvent() is not needed at all. But we should
first check if Qt relies on these synthesized messages anywhere in the
code or not.
-2) If we still keep synthesizeMouseEvent(), then it should fetch actual
- tablet buttons from QGuiApplicationPrivate::tabletDevicePoint(). Right
- now it always synthesizes left-click, whatever the button was pressed/
- released.
-
See bug: https://bugs.kde.org/show_bug.cgi?id=406668
---
src/gui/kernel/qguiapplication.cpp | 1 +
src/gui/kernel/qwindowsysteminterface.cpp | 20 ++---
src/gui/kernel/qwindowsysteminterface.h | 8 +-
- .../windows/qwindowspointerhandler.cpp | 75 ++++++++++++++++++-
- 4 files changed, 86 insertions(+), 18 deletions(-)
+ .../windows/qwindowspointerhandler.cpp | 88 ++++++++++++++++++-
+ 4 files changed, 99 insertions(+), 18 deletions(-)
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp
-index 3541c1ae..681cbd85 100644
+index a67214bd9a..d431f37d35 100644
--- a/src/gui/kernel/qguiapplication.cpp
+++ b/src/gui/kernel/qguiapplication.cpp
-@@ -2536,6 +2536,7 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T
+@@ -2537,6 +2537,7 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T
tabletEvent.setTimestamp(e->timestamp);
QGuiApplication::sendSpontaneousEvent(window, &tabletEvent);
pointData.state = e->buttons;
+ e->eventAccepted = tabletEvent.isAccepted();
if (!tabletEvent.isAccepted()
&& !QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse
&& qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)) {
diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp
-index 0bedae1b..779a9ee4 100644
+index b0f2869128..b3b6167c9d 100644
--- a/src/gui/kernel/qwindowsysteminterface.cpp
+++ b/src/gui/kernel/qwindowsysteminterface.cpp
-@@ -869,7 +869,7 @@ void QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(boo
+@@ -949,7 +949,7 @@ void QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(boo
platformSynthesizesMouse = v;
}
-void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global,
+bool QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global,
int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers)
-@@ -880,36 +880,36 @@ void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp,
+@@ -960,36 +960,36 @@ void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp,
QHighDpi::fromNativePixels(global, window),
device, pointerType, buttons, pressure,
xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers);
- QWindowSystemInterfacePrivate::handleWindowSystemEvent(e);
+ return QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::SynchronousDelivery>(e);
}
-void QWindowSystemInterface::handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global,
+bool QWindowSystemInterface::handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global,
int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers)
{
ulong time = QWindowSystemInterfacePrivate::eventTime.elapsed();
- handleTabletEvent(window, time, local, global, device, pointerType, buttons, pressure,
+ return handleTabletEvent(window, time, local, global, device, pointerType, buttons, pressure,
xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers);
}
#if QT_DEPRECATED_SINCE(5, 10)
-void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global,
+bool QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global,
int device, int pointerType, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers)
{
- handleTabletEvent(window, timestamp, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure,
- xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers);
+ return handleTabletEvent(window, timestamp, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure,
+ xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers);
}
-void QWindowSystemInterface::handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global,
+bool QWindowSystemInterface::handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global,
int device, int pointerType, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers)
{
- handleTabletEvent(window, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure,
- xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers);
+ return handleTabletEvent(window, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure,
+ xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers);
}
#endif // QT_DEPRECATED_SINCE(5, 10)
diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h
-index 1dde9130..0ad89547 100644
+index bf98c33a1a..fdc5a2fb50 100644
--- a/src/gui/kernel/qwindowsysteminterface.h
+++ b/src/gui/kernel/qwindowsysteminterface.h
-@@ -243,20 +243,20 @@ public:
+@@ -247,20 +247,20 @@ public:
static void handleFileOpenEvent(const QString& fileName);
static void handleFileOpenEvent(const QUrl &url);
- static void handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global,
+ static bool handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global,
int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
- static void handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global,
+ static bool handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global,
int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
#if QT_DEPRECATED_SINCE(5, 10)
- QT_DEPRECATED static void handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global,
+ QT_DEPRECATED static bool handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global,
int device, int pointerType, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
- QT_DEPRECATED static void handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global,
+ QT_DEPRECATED static bool handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global,
int device, int pointerType, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-index 07f4d41e..58a5d19f 100644
+index bf201b87e0..60112f7610 100644
--- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
-@@ -518,6 +518,58 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
+@@ -541,6 +541,58 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
return false; // Allow mouse messages to be generated.
}
+void synthesizeMouseEvent(QEvent::Type type, Qt::MouseButton button, const POINTER_PEN_INFO &penInfo)
+{
+ // Update the cursor position
+ BOOL result = SetCursorPos(penInfo.pointerInfo.ptPixelLocationRaw.x, penInfo.pointerInfo.ptPixelLocationRaw.y);
+ if (!result) {
+ qCDebug(lcQpaEvents).noquote().nospace() << showbase
+ << __FUNCTION__ << "SetCursorPos failed, err" << GetLastError();
+ return;
+ }
+ // Send mousebutton down/up events. Windows stores the button state.
+ DWORD inputDataFlags = 0;
+ switch (type) {
+ case QEvent::TabletPress:
+ switch (button) {
+ case Qt::LeftButton:
+ inputDataFlags = MOUSEEVENTF_LEFTDOWN;
+ break;
+ case Qt::RightButton:
+ inputDataFlags = MOUSEEVENTF_RIGHTDOWN;
+ break;
+ default:
+ return;
+ }
+ break;
+ case QEvent::TabletRelease:
+ switch (button) {
+ case Qt::LeftButton:
+ inputDataFlags = MOUSEEVENTF_LEFTUP;
+ break;
+ case Qt::RightButton:
+ inputDataFlags = MOUSEEVENTF_RIGHTUP;
+ break;
+ default:
+ return;
+ }
+ break;
+ case QEvent::TabletMove:
+ default:
+ return;
+ }
+ INPUT inputData = {};
+ inputData.type = INPUT_MOUSE;
+ inputData.mi.dwFlags = inputDataFlags;
+ inputData.mi.dwExtraInfo = 0xFF515700 | 0x01; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320%28v=vs.85%29.aspx
+ UINT result2 = SendInput(1, &inputData, sizeof(inputData));
+ if (result2 != 1) {
+ qCDebug(lcQpaEvents).noquote().nospace() << showbase
+ << __FUNCTION__ << "SendInput failed, err" << GetLastError();
+ return;
+ }
+}
+
bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et,
MSG msg, PVOID vPenInfo)
{
-@@ -610,10 +662,25 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
+@@ -631,10 +683,38 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
}
const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
- QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons,
- pressure, xTilt, yTilt, tangentialPressure, rotation, z,
- sourceDevice, keyModifiers);
- return false; // Allow mouse messages to be generated.
++ const Qt::MouseButtons oldButtons = QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).state;
++
+ const bool accepted =
+ QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons,
+ pressure, xTilt, yTilt, tangentialPressure, rotation, z,
+ sourceDevice, keyModifiers);
+
-+ /**
-+ * TODO: right now we can fake-synthesize only left button clicks. For
-+ * synthesizing right button clicks we should fetch tablet state
-+ * from QGuiApplicationPrivate::tabletDevicePoint()
-+ */
-+ if (accepted && (msg.message == WM_POINTERDOWN || msg.message == WM_POINTERUP)) {
++ const Qt::MouseButtons changedButtons =
++ oldButtons ^ QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).state;
++
++ Qt::MouseButton pressedButton = Qt::NoButton;
++
++ const QVector<Qt::MouseButton> supportedButtons =
++ {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton};
++
++ for (Qt::MouseButton button : supportedButtons) {
++ if (changedButtons & button) {
++ pressedButton = button;
++ break;
++ }
++ }
++
++ if (accepted && pressedButton != Qt::NoButton &&
++ (msg.message == WM_POINTERDOWN || msg.message == WM_POINTERUP)) {
++
+ QEvent::Type type = (msg.message == WM_POINTERDOWN) ? QEvent::TabletPress : QEvent::TabletRelease;
-+ Qt::MouseButton button = Qt::LeftButton;
+
-+ synthesizeMouseEvent(type, button, *penInfo);
++ synthesizeMouseEvent(type, pressedButton, *penInfo);
+ return true;
+ } else {
+ return false; // Allow mouse messages to be generated by OS
+ }
}
}
return true;
--
2.20.1.windows.1
diff --git a/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch b/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch
similarity index 93%
rename from 3rdparty/ext_qt/set-has-border-in-full-screen-default.patch
rename to 3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch
index f818fd8439..30c0a19ef1 100644
--- a/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch
+++ b/3rdparty/ext_qt/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch
@@ -1,162 +1,163 @@
-From f377ab8da1acb279e5b23ab1d2ef4afabe5f481c Mon Sep 17 00:00:00 2001
+From 16a8ff15243a9875fa509f10d1cd9d01df9d4c6d Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@qt.io>
Date: Wed, 21 Nov 2018 09:06:50 +0100
-Subject: [PATCH] Windows: Add a default setting for hasBorderInFullScreen
+Subject: [PATCH 16/17] Windows: Add a default setting for
+ hasBorderInFullScreen
The hasBorderInFullScreen only has an effect when set before
the window is shown or switched to fullscreen. This is currently
not possible in the QML case since the window is only accessible
after all properties (including visibility) have been set.
Add a function to set a default value.
[ChangeLog][QtPlatformHeaders][QWindowsWindowFunctions] Add a default
setting for hasBorderInFullScreen
Task-number: QTBUG-47247
Task-number: QTBUG-71855
Change-Id: I3952e3f34bc4eb134cf1c5265b4489fc74112688
Reviewed-by: Andre de la Rocha <andre.rocha@qt.io>
Reviewed-by: Andy Shaw <andy.shaw@qt.io>
(cherry picked from commit 7264bf19dbc47b805bb7af5df584ce1aae081962)
---
.../qwindowswindowfunctions.h | 9 +++++
.../qwindowswindowfunctions.qdoc | 33 +++++++++++++++++++
.../windows/qwindowsnativeinterface.cpp | 2 ++
.../platforms/windows/qwindowswindow.cpp | 8 ++++-
.../platforms/windows/qwindowswindow.h | 2 ++
5 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h
index e51c2fde67..032dcafa6e 100644
--- a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h
+++ b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h
@@ -81,6 +81,15 @@ public:
func(window, border);
}
+ typedef void (*SetHasBorderInFullScreenDefault)(bool border);
+ static const QByteArray setHasBorderInFullScreenDefaultIdentifier() { return QByteArrayLiteral("WindowsSetHasBorderInFullScreenDefault"); }
+ static void setHasBorderInFullScreenDefault(bool border)
+ {
+ auto func = reinterpret_cast<SetHasBorderInFullScreenDefault>(QGuiApplication::platformFunction(setHasBorderInFullScreenDefaultIdentifier()));
+ if (func)
+ func(border);
+ }
+
typedef void (*SetWindowActivationBehaviorType)(WindowActivationBehavior);
static const QByteArray setWindowActivationBehaviorIdentifier() { return QByteArrayLiteral("WindowsSetWindowActivationBehavior"); }
diff --git a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc
index a52bbe061b..0c52cde753 100644
--- a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc
+++ b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc
@@ -93,7 +93,40 @@
is true then it will enable the WS_BORDER flag in full screen mode to enable other top level windows
inside the application to appear on top when required.
+ \note The setting must be applied before showing the window or switching it
+ to full screen. For QML, setHasBorderInFullScreenDefault() can be used to
+ set a default value.
+
+ See also \l [QtDoc] {Fullscreen OpenGL Based Windows}
+*/
+
+/*!
+ \typedef QWindowsWindowFunctions::SetHasBorderInFullScreenDefault
+ \since 5.13
+
+ This is the typedef for the function returned by QGuiApplication::platformFunction
+ when passed setHasBorderInFullScreenDefaultIdentifier.
+*/
+
+/*!
+ \fn QByteArray QWindowsWindowFunctions::setHasBorderInFullScreenDefaultIdentifier()
+ \since 5.13
+
+ This function returns the bytearray that can be used to query
+ QGuiApplication::platformFunction to retrieve the SetHasBorderInFullScreen function.
+*/
+
+/*!
+ \fn void QWindowsWindowFunctions::setHasBorderInFullScreenDefault(bool border)
+ \since 5.13
+
+ This is a convenience function that can be used directly instead of resolving
+ the function pointer. \a border will be relayed to the function retrieved by
+ QGuiApplication. When \a border is true, the WS_BORDER flag will be set
+ in full screen mode for all windows by default.
+
See also \l [QtDoc] {Fullscreen OpenGL Based Windows}
+ \sa setHasBorderInFullScreen()
*/
/*!
diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
-index 05d6ac9201..eeb1aa58a3 100644
+index 1c5be44150..42193baf46 100644
--- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
+++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp
@@ -293,6 +293,8 @@ QFunctionPointer QWindowsNativeInterface::platformFunction(const QByteArray &fun
return QFunctionPointer(QWindowsWindow::setTouchWindowTouchTypeStatic);
if (function == QWindowsWindowFunctions::setHasBorderInFullScreenIdentifier())
return QFunctionPointer(QWindowsWindow::setHasBorderInFullScreenStatic);
+ if (function == QWindowsWindowFunctions::setHasBorderInFullScreenDefaultIdentifier())
+ return QFunctionPointer(QWindowsWindow::setHasBorderInFullScreenDefault);
if (function == QWindowsWindowFunctions::setWindowActivationBehaviorIdentifier())
return QFunctionPointer(QWindowsNativeInterface::setWindowActivationBehavior);
if (function == QWindowsWindowFunctions::isTabletModeIdentifier())
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
-index 910d8dd209..9705eb7293 100644
+index 7597fcb64d..6949b9c669 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
-@@ -1180,6 +1180,7 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w,
+@@ -1256,6 +1256,7 @@ void QWindowCreationContext::applyToMinMaxInfo(MINMAXINFO *mmi) const
const char *QWindowsWindow::embeddedNativeParentHandleProperty = "_q_embedded_native_parent_handle";
const char *QWindowsWindow::hasBorderInFullScreenProperty = "_q_has_border_in_fullscreen";
+bool QWindowsWindow::m_borderInFullScreenDefault = false;
QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) :
QWindowsBaseWindow(aWindow),
-@@ -1217,7 +1218,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data)
+@@ -1293,7 +1294,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data)
if (aWindow->isTopLevel())
setWindowIcon(aWindow->icon());
- if (aWindow->property(hasBorderInFullScreenProperty).toBool())
+ if (m_borderInFullScreenDefault || aWindow->property(hasBorderInFullScreenProperty).toBool())
setFlag(HasBorderInFullScreen);
clearFlag(WithinCreate);
}
-@@ -2820,6 +2821,11 @@ void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border
+@@ -2956,6 +2957,11 @@ void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border
window->setProperty(hasBorderInFullScreenProperty, QVariant(border));
}
+void QWindowsWindow::setHasBorderInFullScreenDefault(bool border)
+{
+ m_borderInFullScreenDefault = border;
+}
+
void QWindowsWindow::setHasBorderInFullScreen(bool border)
{
if (testFlag(HasBorderInFullScreen) == border)
diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h
-index b9b398b67b..b07bd15d2a 100644
+index ce67e46df3..958564aa86 100644
--- a/src/plugins/platforms/windows/qwindowswindow.h
+++ b/src/plugins/platforms/windows/qwindowswindow.h
-@@ -341,6 +341,7 @@ public:
+@@ -340,6 +340,7 @@ public:
static void setTouchWindowTouchTypeStatic(QWindow *window, QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes);
void registerTouchWindow(QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes = QWindowsWindowFunctions::NormalTouch);
static void setHasBorderInFullScreenStatic(QWindow *window, bool border);
+ static void setHasBorderInFullScreenDefault(bool border);
void setHasBorderInFullScreen(bool border);
static QString formatWindowTitle(const QString &title);
-@@ -386,6 +387,7 @@ private:
+@@ -385,6 +386,7 @@ private:
// note: intentionally not using void * in order to avoid breaking x86
VkSurfaceKHR m_vkSurface = 0;
#endif
+ static bool m_borderInFullScreenDefault;
};
#ifndef QT_NO_DEBUG_STREAM
--
-2.18.0.windows.1
+2.20.1.windows.1
diff --git a/3rdparty/ext_qt/remove-fullscreen-border-hack.patch b/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch
similarity index 85%
rename from 3rdparty/ext_qt/remove-fullscreen-border-hack.patch
rename to 3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch
index e608287d62..86c5b667e9 100644
--- a/3rdparty/ext_qt/remove-fullscreen-border-hack.patch
+++ b/3rdparty/ext_qt/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch
@@ -1,51 +1,51 @@
-From c09f43f4c97ab9154d35d9388943b5f58125e457 Mon Sep 17 00:00:00 2001
+From f14fa33b2c5452be7df163973c26a0d1222696a2 Mon Sep 17 00:00:00 2001
From: Alvin Wong <alvin@alvinhc.com>
Date: Thu, 18 Apr 2019 18:59:58 +0800
-Subject: [PATCH] Hack to hide 1px border with OpenGL fullscreen hack
+Subject: [PATCH 17/17] Hack to hide 1px border with OpenGL fullscreen hack
Unfortunately can't hide all four sides because the bug returns. Now we
leave the bottom border visible, which is probably the most we can do.
Ref: https://bugreports.qt.io/browse/QTBUG-41309
---
src/plugins/platforms/windows/qwindowswindow.cpp | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
-index 9705eb7293..a25b11d305 100644
+index 6949b9c669..10de52d4d6 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
-@@ -1558,7 +1558,7 @@ void QWindowsWindow::show_sys() const
+@@ -1634,7 +1634,7 @@ void QWindowsWindow::show_sys() const
restoreMaximize = true;
} else {
updateTransientParent();
- if (state & Qt::WindowMaximized) {
+ if (state & Qt::WindowMaximized && !(state & Qt::WindowFullScreen)) {
sm = SW_SHOWMAXIMIZED;
// Windows will not behave correctly when we try to maximize a window which does not
// have minimize nor maximize buttons in the window frame. Windows would then ignore
-@@ -1995,7 +1995,7 @@ bool QWindowsWindow::isFullScreen_sys() const
+@@ -2124,7 +2124,7 @@ bool QWindowsWindow::isFullScreen_sys() const
return false;
QRect geometry = geometry_sys();
if (testFlag(HasBorderInFullScreen))
- geometry += QMargins(1, 1, 1, 1);
+ geometry += QMargins(0, 0, 0, 1);
QPlatformScreen *screen = screenForGeometry(geometry);
return screen && geometry == screen->geometry();
}
-@@ -2066,7 +2066,11 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
+@@ -2195,7 +2195,11 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE;
const bool wasSync = testFlag(SynchronousGeometryChangeEvent);
setFlag(SynchronousGeometryChangeEvent);
- SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf);
+ if (testFlag(HasBorderInFullScreen)) {
+ SetWindowPos(m_data.hwnd, HWND_TOP, r.left() - 1, r.top() - 1, r.width() + 2, r.height() + 1, swpf);
+ } else {
+ SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf);
+ }
if (!wasSync)
clearFlag(SynchronousGeometryChangeEvent);
clearFlag(MaximizeToFullScreen);
--
-2.18.0.windows.1
+2.20.1.windows.1
diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt
index 662e32136c..dd5f353076 100644
--- a/3rdparty/ext_qt/CMakeLists.txt
+++ b/3rdparty/ext_qt/CMakeLists.txt
@@ -1,285 +1,291 @@
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 -no-sql-sqlite -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 ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-disable-wintab.patch
+ 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
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Hack-always-return-we-support-DIBV5.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Fix-debug-on-openGL-ES.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-cumulative-patch-for-hdr.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Fix-swizzling-when-rendering-QPainter-on-QOpenGLWidg.patch
)
else()
- set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Hack-always-return-we-support-DIBV5.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Fix-debug-on-openGL-ES.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-cumulative-patch-for-hdr.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Fix-swizzling-when-rendering-QPainter-on-QOpenGLWidg.patch
+ 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}/0021-Fix-QTabletEvent-uniqueId-when-Qt-uses-WinInk.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.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}/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0050-Fix-using-tablet-on-QML-widgets.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
+ )
+
+ # Other patches
set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/set-has-border-in-full-screen-default.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/remove-fullscreen-border-hack.patch
+ 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}/0030-Windows-QPA-Make-the-expected-screen-be-in-sync-with.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
+# 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/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz
- URL_MD5 99c2eb46e533371798b4ca2d1458e065
+ URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz
+ URL_MD5 dda95b0239d13c5276834177af3a8588
PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
INSTALL_DIR ${EXTPREFIX_qt}
CONFIGURE_COMMAND <SOURCE_DIR>/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
CONFIGURE_COMMAND <SOURCE_DIR>/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/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz
- URL_MD5 99c2eb46e533371798b4ca2d1458e065
+ URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz
+ URL_MD5 dda95b0239d13c5276834177af3a8588
- PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0010-Fix-tablet-jitter-on-X11.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0011-Add-an-ID-for-recognition-of-UGEE-tablets.patch
- COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
+ 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
CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto'
CONFIGURE_COMMAND <SOURCE_DIR>/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 -b -d <SOURCE_DIR>/qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/mac_standardpaths_qtbug-61159.diff
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 -b -d <SOURCE_DIR>/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
)
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/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz
- URL_MD5 99c2eb46e533371798b4ca2d1458e065
+ URL https://download.qt.io/archive/qt/5.12/5.12.3/single/qt-everywhere-src-5.12.3.tar.xz
+ URL_MD5 38017e0ed88b9baba063bd723d9ca8b2
CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto'
INSTALL_DIR ${EXTPREFIX_qt}
CONFIGURE_COMMAND <SOURCE_DIR>/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/3rdparty/ext_qt/qt-no-motion-compression.diff b/3rdparty/ext_qt/qt-no-motion-compression.diff
deleted file mode 100644
index bac29953b9..0000000000
--- a/3rdparty/ext_qt/qt-no-motion-compression.diff
+++ /dev/null
@@ -1,20 +0,0 @@
-diff --git a/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp b/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp
-index 05c0224..8fa9345 100644
---- a/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp
-+++ b/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp
-@@ -1687,8 +1687,13 @@ void QXcbConnection::processXcbEvents()
- continue;
- }
-
-- if (compressEvent(event, i, eventqueue))
-- continue;
-+
-+ /**
-+ * (Krita) Do *NOT* compress any motion events! This is the
-+ * most weird thing one can do for a painting application!
-+ */
-+ // if (compressEvent(event, i, eventqueue))
-+ // continue;
-
- bool accepted = false;
- if (clipboard()->processIncr())
diff --git a/3rdparty/ext_qt/qtbase-configure.patch b/3rdparty/ext_qt/qtbase-configure.patch
deleted file mode 100644
index ee2a621bcc..0000000000
--- a/3rdparty/ext_qt/qtbase-configure.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- old/configure 2016-06-10 02:48:56.000000000 -0400
-+++ new/configure 2016-09-21 02:26:34.000000000 -0400
-@@ -548,7 +548,7 @@
- exit 2
- fi
-
-- if ! /usr/bin/xcrun -find xcrun >/dev/null 2>&1; then
-+ if ! /usr/bin/xcrun -find xcodebuild >/dev/null 2>&1; then
- echo >&2
- echo " Xcode not set up properly. You may need to confirm the license" >&2
- echo " agreement by running /usr/bin/xcodebuild without arguments." >&2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6a4ad80a3d..669a0d8f6c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,927 +1,927 @@
project(krita)
message(STATUS "Using CMake version: ${CMAKE_VERSION}")
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
-set(MIN_QT_VERSION 5.6.0)
+set(MIN_QT_VERSION 5.9.0)
set(MIN_FRAMEWORKS_VERSION 5.18.0)
if (POLICY CMP0002)
cmake_policy(SET CMP0002 OLD)
endif()
if (POLICY CMP0017)
cmake_policy(SET CMP0017 NEW)
endif ()
if (POLICY CMP0022)
cmake_policy(SET CMP0022 OLD)
endif ()
if (POLICY CMP0026)
cmake_policy(SET CMP0026 OLD)
endif()
if (POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif()
if (POLICY CMP0046)
cmake_policy(SET CMP0046 OLD)
endif ()
if (POLICY CMP0059)
cmake_policy(SET CMP0059 OLD)
endif()
if (POLICY CMP0063)
cmake_policy(SET CMP0063 OLD)
endif()
if (POLICY CMP0054)
cmake_policy(SET CMP0054 OLD)
endif()
if (POLICY CMP0064)
cmake_policy(SET CMP0064 OLD)
endif()
if (POLICY CMP0071)
cmake_policy(SET CMP0071 OLD)
endif()
if (APPLE)
set(APPLE_SUPPRESS_X11_WARNING TRUE)
set(KDE_SKIP_RPATH_SETTINGS TRUE)
set(CMAKE_MACOSX_RPATH 1)
set(BUILD_WITH_INSTALL_RPATH 1)
add_definitions(-mmacosx-version-min=10.11 -Wno-macro-redefined -Wno-deprecated-register)
endif()
if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32)
add_definitions(-Wno-suggest-override)
endif()
######################
#######################
## Constants defines ##
#######################
######################
# define common versions of Krita applications, used to generate kritaversion.h
# update these version for every release:
-set(KRITA_VERSION_STRING "4.2.0-beta")
+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 2)
+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_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 <Python.h>
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
)
# KConfig deprecated authorizeKAction. In order to be warning free,
# compile with the updated function when the dependency is new enough.
# Remove this (and the uses of the define) when the minimum KF5
# version is >= 5.24.0.
if (${KF5Config_VERSION} VERSION_LESS "5.24.0" )
message("Old KConfig (< 5.24.0) found.")
add_definitions(-DKCONFIG_BEFORE_5_24)
endif()
find_package(Qt5 ${MIN_QT_VERSION}
REQUIRED COMPONENTS
Core
Gui
Widgets
Xml
Network
PrintSupport
Svg
Test
Concurrent
)
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 <QCoreApplication>
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 <QSurfaceFormat>
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 <QGuiApplication>
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 <QtPlatformHeaders/QWindowsWindowFunctions>
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)
find_package(XCB COMPONENTS XCB ATOM)
set(HAVE_XCB ${XCB_FOUND})
else()
set(HAVE_DBUS FALSE)
set(HAVE_X11 FALSE)
set(HAVE_XCB FALSE)
endif()
add_definitions(
-DQT_USE_QSTRINGBUILDER
-DQT_STRICT_ITERATORS
-DQT_NO_SIGNALS_SLOTS_KEYWORDS
-DQT_NO_URL_CAST_FROM_STRING
)
if (${Qt5_VERSION} VERSION_GREATER "5.8.0" )
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900)
elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" )
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800)
elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" )
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700)
else()
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600)
endif()
add_definitions(-DTRANSLATION_DOMAIN=\"krita\")
#
# The reason for this mode is that the Debug mode disable inlining
#
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals")
endif()
option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug")
if (KRITA_DEVS)
set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE)
endif()
if(UNIX)
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m")
endif()
if(WIN32)
if(MSVC)
# C4522: 'class' : multiple assignment operators specified
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522")
endif()
endif()
# KDECompilerSettings adds the `--export-all-symbols` linker flag.
# We don't really need it.
if(MINGW)
string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}")
endif(MINGW)
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 "(<CMAKE_AR> [^ 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")
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 "-Wabi -ffp-contract=fast")
+ 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 "-Wabi -fabi-version=0 -ffp-contract=fast")
+ set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast")
if(NOT WIN32)
set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC")
endif()
endif()
#Handle Vc master
if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG)
AddCompilerFlag("-std=c++11" _ok)
if(NOT _ok)
AddCompilerFlag("-std=c++0x" _ok)
endif()
endif()
macro(ko_compile_for_all_implementations_no_scalar _objs _src)
vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2)
endmacro()
macro(ko_compile_for_all_implementations _objs _src)
vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2)
endmacro()
endif()
set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} )
add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS})
##
## Test endianness
##
include (TestBigEndian)
test_big_endian(CMAKE_WORDS_BIGENDIAN)
##
## Test for qt-poppler
##
find_package(Poppler COMPONENTS Qt5)
set_package_properties(Poppler PROPERTIES
DESCRIPTION "A PDF rendering library"
URL "http://poppler.freedesktop.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita PDF filter.")
##
## 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 <toplevel/foo.h> 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=<targetname> and -DANDROID_APK_DIR=<paths>")
endif()
diff --git a/README.md b/README.md
index da5c434615..756a660104 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,51 @@
![Picture](https://krita.org/wp-content/uploads/2019/04/krita-logo-2019.png)
| Jenkins CI Name | Master | Stable |
| --------------- | ------ | ------ |
| OpenSuse Qt 5.10 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.10/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.10/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.10)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.10/)|
| FreeBSD Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.12/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.12/)|
Krita is a free and open source digital painting application. It is for artists who want to create professional work from start to end. Krita is used by comic book artists, illustrators, concept artists, matte and texture painters and in the digital VFX industry.
If you are reading this on Github, be aware that this is just a mirror. Our real
code repository is provided by KDE: https://invent.kde.org/kde/krita
![Picture](https://krita.org/wp-content/uploads/2016/04/krita-30-screenshot.jpg)
### User Manual
https://docs.krita.org/en/user_manual.html
### Development Notes and Build Instructions
If you're building on Windows or OSX you'll need to build some third-party dependencies first. You should look at the README in the 3rdparty folder for directions.
If you're building on Linux, please follow David Revoy's Cat Guide: http://www.davidrevoy.com/article193/guide-building-krita-on-linux-for-cats
Other developer guides, notes and wiki:
-https://community.kde.org/Krita
+https://docs.krita.org/en/untranslatable_pages.html
Apidox:
https://api.kde.org/extragear-api/graphics-apidocs/krita/html/index.html
### Bugs and Wishes
https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1315444&product=krita&query_format=advanced
### Discussion Forum
http://forum.kde.org/viewforum.php?f=136
### IRC channel
Most of the developers hang out here. If you are interested in helping with the project this is a great place to start. Many of the developers based in Europe so they may be offline depending on when you join.
irc.freenode.net, #krita
### Project Website
http://www.krita.org
### License
Krita as a whole is licensed under the GNU Public License, Version 3. Individual files may have a different, but compatible license.
diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd
index 671bcb63a0..b65d701876 100644
--- a/build-tools/windows/build.cmd
+++ b/build-tools/windows/build.cmd
@@ -1,817 +1,818 @@
@echo off
setlocal enabledelayedexpansion
goto begin
:: Subroutines
:find_on_path out_variable file_name
set %1=%~f$PATH:2
goto :EOF
:get_dir_path out_variable file_path
set %1=%~dp2
goto :EOF
:get_full_path out_variable file_path
setlocal
set FULL_PATH=%~f2
if not exist "%FULL_PATH%" (
set FULL_PATH=
) else (
if exist "%FULL_PATH%\" (
set FULL_PATH=
)
)
endlocal & set "%1=%FULL_PATH%"
goto :EOF
:get_full_path_dir out_variable file_path
setlocal
set FULL_PATH=%~dp2
if not exist "%FULL_PATH%" (
set FULL_PATH=
)
endlocal & set "%1=%FULL_PATH%"
goto :EOF
:prompt_for_string out_variable prompt
set /p %1=%~2^>
goto :EOF
:prompt_for_positive_integer out_variable prompt
setlocal
call :prompt_for_string USER_INPUT "%~2"
if "%USER_INPUT%" == "" set USER_INPUT=0
set /a RESULT=%USER_INPUT%
if not %RESULT% GTR 0 (
set RESULT=
)
endlocal & set "%1=%RESULT%"
goto :EOF
:prompt_for_file out_variable prompt
setlocal
:prompt_for_file__retry
call :prompt_for_string USER_INPUT "%~2"
if "%USER_INPUT%" == "" (
endlocal
set %1=
goto :EOF
)
call :get_full_path RESULT "%USER_INPUT%"
if "%RESULT%" == "" (
echo Input does not point to valid file!
set USER_INPUT=
goto prompt_for_file__retry
)
endlocal & set "%1=%RESULT%"
goto :EOF
:prompt_for_dir out_variable prompt
setlocal
:prompt_for_dir__retry
call :prompt_for_string USER_INPUT "%~2"
if "%USER_INPUT%" == "" (
endlocal
set %1=
goto :EOF
)
call :get_full_path_dir RESULT "%USER_INPUT%\"
if "%RESULT%" == "" (
echo Input does not point to valid dir!
set USER_INPUT=
goto prompt_for_dir__retry
)
endlocal & set "%1=%RESULT%"
goto :EOF
:usage
echo Usage:
echo %~n0 [--no-interactive] [ OPTIONS ... ]
echo.
echo Basic options:
echo --no-interactive Run without interactive prompts
echo When not specified, the script will prompt
echo for some of the parameters.
echo --jobs ^<count^> Set parallel jobs count when building
echo Defaults to no. of logical cores
echo --skip-deps Skips (re)building of deps
echo --skip-krita Skips (re)building of Krita
echo --cmd Launch a cmd prompt instead of building.
echo The environment is set up like the build
echo environment with some helper command macros.
echo.
echo Path options:
echo --src-dir ^<dir_path^> Specify Krita source dir
echo If unspecified, this will be determined from
echo the script location.
echo --download-dir ^<dir_path^> Specify deps download dir
echo Can be omitted if --skip-deps is used
echo --deps-build-dir ^<dir_path^> Specify deps build dir
echo Can be omitted if --skip-deps is used
echo --deps-install-dir ^<dir_path^> Specify deps install dir
echo --krita-build-dir ^<dir_path^> Specify Krita build dir
echo Can be omitted if --skip-krita is used
echo --krita-install-dir ^<dir_path^> Specify Krita install dir
echo Can be omitted if --skip-krita is used
echo.
goto :EOF
:usage_and_exit
call :usage
exit /b
:usage_and_fail
call :usage
exit /b 100
:: ----------------------------
:begin
echo Krita build script for Windows
echo.
:: command-line args parsing
set ARG_NO_INTERACTIVE=
set ARG_JOBS=
set ARG_SKIP_DEPS=
set ARG_SKIP_KRITA=
set ARG_SRC_DIR=
set ARG_DOWNLOAD_DIR=
set ARG_DEPS_BUILD_DIR=
set ARG_DEPS_INSTALL_DIR=
set ARG_KRITA_BUILD_DIR=
set ARG_KRITA_INSTALL_DIR=
set ARG_CMD=
:args_parsing_loop
set CURRENT_MATCHED=
if not "%1" == "" (
if "%1" == "--no-interactive" (
set ARG_NO_INTERACTIVE=1
set CURRENT_MATCHED=1
)
if "%1" == "--jobs" (
if not "%ARG_JOBS%" == "" (
echo ERROR: Arg --jobs specified more than once 1>&2
echo.
goto usage_and_fail
)
set /a "ARG_JOBS=%2"
if not !ARG_JOBS! GTR 0 (
echo ERROR: Arg --jobs is not a positive integer 1>&2
echo.
goto usage_and_fail
)
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--skip-deps" (
set ARG_SKIP_DEPS=1
set CURRENT_MATCHED=1
)
if "%1" == "--skip-krita" (
set ARG_SKIP_KRITA=1
set CURRENT_MATCHED=1
)
if "%1" == "--src-dir" (
if not "%ARG_SRC_DIR%" == "" (
echo ERROR: Arg --src-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if not exist "%~f2\" (
echo ERROR: Arg --src-dir does not point to a directory 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_SRC_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--download-dir" (
if not "%ARG_DOWNLOAD_DIR%" == "" (
echo ERROR: Arg --download-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --download-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--deps-build-dir" (
if not "%ARG_DEPS_BUILD_DIR%" == "" (
echo ERROR: Arg --deps-build-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--deps-install-dir" (
if not "%ARG_DEPS_INSTALL_DIR%" == "" (
echo ERROR: Arg --deps-install-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--krita-build-dir" (
if not "%ARG_KRITA_BUILD_DIR%" == "" (
echo ERROR: Arg --krita-build-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--krita-install-dir" (
if not "%ARG_KRITA_INSTALL_DIR%" == "" (
echo ERROR: Arg --krita-install-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--cmd" (
set ARG_CMD=1
set CURRENT_MATCHED=1
)
if "%1" == "--help" (
goto usage_and_exit
)
if not "!CURRENT_MATCHED!" == "1" (
echo ERROR: Unknown option %1 1>&2
echo.
goto usage_and_fail
)
shift /1
goto args_parsing_loop
)
if "%ARG_NO_INTERACTIVE%" == "1" (
echo Non-interactive mode
) else (
echo Interactive mode
:: Trick to pause on exit
call :real_begin
pause
exit /b !ERRORLEVEL!
)
:real_begin
echo.
if "%ARG_SKIP_DEPS%" == "1" (
if "%ARG_SKIP_KRITA%" == "1" (
echo ERROR: You cannot skip both deps and Krita 1>&2
echo.
exit /b 102
)
echo Building of deps will be skipped.
) else (
if "%ARG_SKIP_KRITA%" == "1" (
echo Building of Krita will be skipped.
) else (
echo Both deps and Krita will be built.
)
)
:: Check environment config
if "%CMAKE_EXE%" == "" (
call :find_on_path CMAKE_EXE cmake.exe
if "!CMAKE_EXE!" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe"
)
if "!CMAKE_EXE!" == "" (
echo ERROR: CMake not found! 1>&2
exit /b 102
)
) else (
echo Found CMake on PATH: !CMAKE_EXE!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe"
if "!CMAKE_EXE!" == "" (
echo ERROR: CMake not found! 1>&2
exit /b 102
)
)
)
)
)
echo CMake: %CMAKE_EXE%
if "%SEVENZIP_EXE%" == "" (
call :find_on_path SEVENZIP_EXE 7z.exe
if "!SEVENZIP_EXE!" == "" (
set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe"
if "!SEVENZIP_EXE!" == "" (
set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe"
)
if "!SEVENZIP_EXE!" == "" (
echo 7-Zip not found
)
)
)
if "%SEVENZIP_EXE%" == "" (
echo 7-Zip: %SEVENZIP_EXE%
)
if "%MINGW_BIN_DIR%" == "" (
call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe
if "!MINGW_BIN_DIR_MAKE_EXE!" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64"
)
if "!MINGW_BIN_DIR_MAKE_EXE!" == "" (
echo ERROR: mingw-w64 not found! 1>&2
exit /b 102
)
call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!"
) else (
call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!"
echo Found mingw on PATH: !MINGW_BIN_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64"
if "!MINGW_BIN_DIR_MAKE_EXE!" == "" (
echo ERROR: mingw-w64 not found! 1>&2
exit /b 102
)
call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!"
)
)
)
)
echo mingw-w64: %MINGW_BIN_DIR%
if "%PYTHON_BIN_DIR%" == "" (
call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe
if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2"
)
if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" (
echo ERROR: Python not found! 1>&2
exit /b 102
)
call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!"
) else (
call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!"
echo Found Python on PATH: !PYTHON_BIN_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2"
if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" (
echo ERROR: Python not found! 1>&2
exit /b 102
)
call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!"
)
)
)
)
echo Python: %PYTHON_BIN_DIR%
if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check
if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10"
if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10"
if exist "%WindowsSdkDir%\" (
pushd "%WindowsSdkDir%"
if exist "bin\x64\fxc.exe" (
set HAVE_FXC_EXE=1
if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%"
) else (
for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do (
if exist "bin\%%a\x64\fxc.exe" (
set HAVE_FXC_EXE=1
if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%\bin\%%a\"
)
)
)
popd
)
set QT_ENABLE_DYNAMIC_OPENGL=ON
if not "%HAVE_FXC_EXE%" == "1" (
set WindowsSdkDir=
echo Windows SDK 10 with fxc.exe not found
echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support.
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
exit /b 102
)
)
set QT_ENABLE_DYNAMIC_OPENGL=OFF
) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir%
:skip_windows_sdk_dir_check
if not "%ARG_JOBS%" == "" (
set "PARALLEL_JOBS=%ARG_JOBS%"
)
if "%PARALLEL_JOBS%" == "" (
echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS%
echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs
set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS%
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs"
if "!PARALLEL_JOBS!" == "" (
echo ERROR: Invalid job count! 1>&2
exit /b 102
)
)
)
)
echo Parallel jobs count: %PARALLEL_JOBS%
if not "%ARG_SRC_DIR%" == "" (
set "KRITA_SRC_DIR=%ARG_SRC_DIR%"
)
if "%KRITA_SRC_DIR%" == "" (
:: Check whether this looks like to be in the source tree
set "_temp=%~dp0"
if "!_temp:~-21!" == "\build-tools\windows\" (
if exist "!_temp:~0,-21!\CMakeLists.txt" (
if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" (
set "KRITA_SRC_DIR=!_temp:~0,-21!\"
echo Script is running inside Krita src dir
)
)
)
)
if "%KRITA_SRC_DIR%" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir"
)
if "!KRITA_SRC_DIR!" == "" (
echo ERROR: Krita src dir not found! 1>&2
exit /b 102
)
)
echo Krita src: %KRITA_SRC_DIR%
if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check
if not "%ARG_DOWNLOAD_DIR%" == "" (
set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%"
)
if "%DEPS_DOWNLOAD_DIR%" == "" (
set DEPS_DOWNLOAD_DIR=%CD%\d\
echo Using default deps download dir: !DEPS_DOWNLOAD_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir"
)
)
if "!DEPS_DOWNLOAD_DIR!" == "" (
echo ERROR: Deps download dir not set! 1>&2
exit /b 102
)
)
echo Deps download dir: %DEPS_DOWNLOAD_DIR%
if not "%ARG_DEPS_BUILD_DIR%" == "" (
set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%"
)
if "%DEPS_BUILD_DIR%" == "" (
set DEPS_BUILD_DIR=%CD%\b_deps\
echo Using default deps build dir: !DEPS_BUILD_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir"
)
)
if "!DEPS_BUILD_DIR!" == "" (
echo ERROR: Deps build dir not set! 1>&2
exit /b 102
)
)
echo Deps build dir: %DEPS_BUILD_DIR%
:skip_deps_args_check
if not "%ARG_DEPS_INSTALL_DIR%" == "" (
set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%"
)
if "%DEPS_INSTALL_DIR%" == "" (
set DEPS_INSTALL_DIR=%CD%\i_deps\
echo Using default deps install dir: !DEPS_INSTALL_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir"
)
)
if "!DEPS_INSTALL_DIR!" == "" (
echo ERROR: Deps install dir not set! 1>&2
exit /b 102
)
)
echo Deps install dir: %DEPS_INSTALL_DIR%
if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check
if not "%ARG_KRITA_BUILD_DIR%" == "" (
set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%"
)
if "%KRITA_BUILD_DIR%" == "" (
set KRITA_BUILD_DIR=%CD%\b\
echo Using default Krita build dir: !KRITA_BUILD_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir"
)
)
if "!KRITA_BUILD_DIR!" == "" (
echo ERROR: Krita build dir not set! 1>&2
exit /b 102
)
)
echo Krita build dir: %KRITA_BUILD_DIR%
if not "%ARG_KRITA_INSTALL_DIR%" == "" (
set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%"
)
if "%KRITA_INSTALL_DIR%" == "" (
set KRITA_INSTALL_DIR=%CD%\i\
echo Using default Krita install dir: !KRITA_INSTALL_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir"
)
)
if "!KRITA_INSTALL_DIR!" == "" (
echo ERROR: Krita install dir not set! 1>&2
exit /b 102
)
)
echo Krita install dir: %KRITA_INSTALL_DIR%
:skip_krita_args_check
echo.
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is the above ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
exit /b 1
)
echo.
)
:: Initialize clean PATH
set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH%
echo Creating dirs...
if NOT "%ARG_SKIP_DEPS%" == "1" (
mkdir %DEPS_DOWNLOAD_DIR%
if errorlevel 1 (
if not exist "%DEPS_DOWNLOAD_DIR%\" (
echo ERROR: Cannot create deps download dir! 1>&2
exit /b 103
)
)
mkdir %DEPS_BUILD_DIR%
if errorlevel 1 (
if not exist "%DEPS_BUILD_DIR%\" (
echo ERROR: Cannot create deps build dir! 1>&2
exit /b 103
)
)
mkdir %DEPS_INSTALL_DIR%
if errorlevel 1 (
if not exist "%DEPS_INSTALL_DIR%\" (
echo ERROR: Cannot create deps install dir! 1>&2
exit /b 103
)
)
)
if NOT "%ARG_SKIP_KRITA%" == "1" (
mkdir %KRITA_BUILD_DIR%
if errorlevel 1 (
if not exist "%KRITA_BUILD_DIR%\" (
echo ERROR: Cannot create Krita build dir! 1>&2
exit /b 103
)
)
mkdir %KRITA_INSTALL_DIR%
if errorlevel 1 (
if not exist "%KRITA_INSTALL_DIR%\" (
echo ERROR: Cannot create Krita install dir! 1>&2
exit /b 103
)
)
)
echo.
set CMAKE_BUILD_TYPE=RelWithDebInfo
set QT_ENABLE_DEBUG_INFO=OFF
:: Paths for CMake
set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%"
set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %"
set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%"
set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %"
set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%"
set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %"
set PATH=%DEPS_INSTALL_DIR%\bin;%PATH%
if not "%GETTEXT_SEARCH_PATH%" == "" (
set PATH=!PATH!;!GETTEXT_SEARCH_PATH!
)
:: Prepare the CMake command lines
set CMDLINE_CMAKE_DEPS="%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^
-DSUBMAKE_JOBS=%PARALLEL_JOBS% ^
-DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^
-DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^
-DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^
-DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^
-G "MinGW Makefiles" ^
-DUSE_QT_TABLET_WINDOWS=ON ^
-DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE%
set CMDLINE_CMAKE_KRITA="%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^
-DBoost_DEBUG=OFF ^
-DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^
-DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^
-DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^
-DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^
-DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^
-DBUILD_TESTING=OFF ^
-DHAVE_MEMORY_LEAK_TRACKER=OFF ^
-DFOUNDATION_BUILD=ON ^
-DUSE_QT_TABLET_WINDOWS=ON ^
+ -DHIDE_SAFE_ASSERTS=ON ^
-Wno-dev ^
-G "MinGW Makefiles" ^
-DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE%
:: Launch CMD prompt if requested
if "%ARG_CMD%" == "1" (
doskey cmake-deps=cmd /c "pushd %DEPS_BUILD_DIR% && %CMDLINE_CMAKE_DEPS%"
doskey cmake-krita=cmd /c "pushd %KRITA_BUILD_DIR% && %CMDLINE_CMAKE_KRITA%"
doskey make-deps=cmd /c "pushd %DEPS_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target $*"
doskey make-krita=cmd /c "pushd %KRITA_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%"
echo.
title Krita build - %KRITA_SRC_DIR% ^(deps: %DEPS_BUILD_DIR%, krita: %KRITA_BUILD_DIR%^)
echo You're now in the build environment.
echo The following macros are available:
echo cmake-deps
echo -- Run CMake for the deps.
echo make-deps ^<deps target^>
echo -- Run build for the specified deps target. The target name should
echo include the `ext_` prefix, e.g. `ext_qt`.
echo cmake-krita
echo -- Run CMake for Krita.
echo make-krita
echo -- Run build for Krita's `install` target.
echo.
echo For more info, type `doskey /macros` to view the macro commands.
cmd /k
exit
)
if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps
pushd %DEPS_BUILD_DIR%
if errorlevel 1 (
echo ERROR: Cannot enter deps build dir! 1>&2
exit /b 104
)
echo Running CMake for deps...
@echo on
%CMDLINE_CMAKE_DEPS%
@if errorlevel 1 (
@echo ERROR: CMake configure failed! 1>&2
@exit /b 104
)
@echo off
echo.
set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost exiv2 fftw3 eigen3
set EXT_TARGETS=%EXT_TARGETS% ilmbase jpeg lcms2 ocio openexr png tiff gsl vc libraw
set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic
set EXT_TARGETS=%EXT_TARGETS% python sip pyqt
set EXT_TARGETS=%EXT_TARGETS% quazip
for %%a in (%EXT_TARGETS%) do (
echo Building ext_%%a...
"%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a
if errorlevel 1 (
echo ERROR: Building of ext_%%a failed! 1>&2
exit /b 105
)
)
echo.
echo ******** Built deps ********
popd
:skip_build_deps
if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita
pushd %KRITA_BUILD_DIR%
if errorlevel 1 (
echo ERROR: Cannot enter Krita build dir! 1>&2
exit /b 104
)
echo Running CMake for Krita...
@echo on
%CMDLINE_CMAKE_KRITA%
@if errorlevel 1 (
@echo ERROR: CMake configure failed! 1>&2
@exit /b 104
)
@echo off
echo.
echo Building Krita...
"%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%
if errorlevel 1 (
echo ERROR: Building of Krita failed! 1>&2
exit /b 105
)
echo.
echo ******** Built Krita ********
popd
:skip_build_krita
echo Krita build completed!
diff --git a/krita/CMakeLists.txt b/krita/CMakeLists.txt
index 48f5443177..e9b3b9d258 100644
--- a/krita/CMakeLists.txt
+++ b/krita/CMakeLists.txt
@@ -1,111 +1,113 @@
project(krita)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${Vc_INCLUDE_DIR}
)
add_subdirectory( dtd )
add_subdirectory( data )
add_subdirectory( integration )
+# Install the application icons following the the freedesktop icon theme spec
add_subdirectory( pics/app )
if (ANDROID)
include_directories (${Qt5AndroidExtras_INCLUDE_DIRS})
endif()
set(krita_SRCS main.cc)
-
+# Set the application icon on the application
if (NOT APPLE)
file(GLOB ICON_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/pics/app/*-apps-calligrakrita.png")
else()
set(ICON_SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/pics/app/16-apps-calligrakrita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/app/32-apps-calligrakrita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/app/48-apps-calligrakrita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/app/128-apps-calligrakrita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/app/256-apps-calligrakrita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/app/512-apps-calligrakrita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/app/1024-apps-calligrakrita.png"
)
endif()
ecm_add_app_icon(krita_SRCS ICONS ${ICON_SRCS})
+# Install the mimetype icons
ecm_install_icons(ICONS
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/16-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/22-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/32-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/48-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/64-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/128-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/256-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/512-mimetypes-application-x-krita.png"
"${CMAKE_CURRENT_SOURCE_DIR}/pics/mimetypes/1024-mimetypes-application-x-krita.png"
DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor)
# separate listing, both used by Krita and KritaSketch
set(krita_QRCS
${CMAKE_SOURCE_DIR}/krita/krita.qrc
${CMAKE_SOURCE_DIR}/krita/pics/Breeze-dark/breeze-dark-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/Breeze-light/breeze-light-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/layerbox/layerbox-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/layerbox/svg/layerbox-svg-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/layers/layers-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/misc-light/misc-light-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/misc-dark/misc-dark-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/paintops/paintops-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/tool_transform/tool-transform-icons.qrc
${CMAKE_SOURCE_DIR}/krita/pics/svg/svg-icons.qrc
${CMAKE_SOURCE_DIR}/libs/flake/flake.qrc
${CMAKE_SOURCE_DIR}/libs/widgets/kritawidgets.qrc
${CMAKE_SOURCE_DIR}/pics/icons.qrc
${CMAKE_SOURCE_DIR}/krita/data/aboutdata/aboutdata.qrc
${CMAKE_SOURCE_DIR}/krita/data/shaders/shaders.qrc
${CMAKE_SOURCE_DIR}/krita/data/cursors/cursors.qrc
CACHE INTERNAL "krita_QRCS"
)
qt5_add_resources(krita_SRCS ${krita_QRCS})
if (ANDROID)
add_library(krita SHARED ${krita_SRCS})
target_link_libraries(krita PRIVATE Qt5::AndroidExtras)
else()
add_executable(krita ${krita_SRCS})
endif()
target_link_libraries(krita
PRIVATE
kritaui
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::Xml
Qt5::Network
Qt5::PrintSupport
Qt5::Svg
Qt5::Concurrent)
if(HAVE_KCRASH)
target_link_libraries(krita PRIVATE KF5::Crash)
endif()
if (APPLE)
set_target_properties(krita PROPERTIES INSTALL_RPATH "@loader_path/../../../../lib;@loader_path/../lib;@loader_path/../Frameworks;@executable_path/../lib;@executable_path/../Frameworks")
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.template)
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.krita")
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Krita")
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_ICON_FILE "krita_SRCS.icns")
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_LONG_VERSION_STRING ${KRITA_VERSION_STRING})
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${KRITA_VERSION_STRING})
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_VERSION ${KRITA_VERSION_STRING})
set_target_properties(krita PROPERTIES MACOSX_BUNDLE_COPYRIGHT "GNU Public License, V2 or, at your option, any later version.")
endif ()
install(TARGETS krita ${INSTALL_TARGETS_DEFAULT_ARGS})
install(PROGRAMS org.kde.krita.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
install(FILES krita.action kritamenu.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions)
install(FILES org.kde.krita.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} )
install(DIRECTORY DESTINATION ${DATA_INSTALL_DIR}/krita/shortcuts)
diff --git a/krita/Info.plist.template b/krita/Info.plist.template
index 55691ff2ab..eb204d6a11 100644
--- a/krita/Info.plist.template
+++ b/krita/Info.plist.template
@@ -1,80 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>YES</string>
<key>NSHighResolutionMagnifyAllowed</key>
<string>NO</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>KRTA</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFile</key>
<string>kra_SRCS</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>kra</string>
<string>KRA</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>application/x-krita</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
</array>
<key>CFBundleTypeName</key>
<string>Krita File</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>ora</string>
<string>ORA</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>image/openraster</string>
</array>
<key>CFBundleTypeName</key>
<string>OpenRaster File</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
</array>
</dict>
</plist>
diff --git a/krita/data/aboutdata/credits.txt b/krita/data/aboutdata/credits.txt
index 5b9efe7042..82ec941b42 100644
--- a/krita/data/aboutdata/credits.txt
+++ b/krita/data/aboutdata/credits.txt
@@ -1,8 +1,10 @@
Peter Sikking:Project Vision
Ramon Miranda:Artist, Muses author, brush and preset creator
David Revoy:Brushes and Palettes
Ilya Portnov:MyPaint shade selector
Martin Renold:MyPaint shade selector
Saisho Kazuki:Japanese Animation template
Pasquale D'Antuono:Donor
+David Gowers:Dither patterns
+
diff --git a/krita/data/aboutdata/libraries.txt b/krita/data/aboutdata/libraries.txt
index 9f6f92996c..998cec61af 100644
--- a/krita/data/aboutdata/libraries.txt
+++ b/krita/data/aboutdata/libraries.txt
@@ -1,32 +1,33 @@
Boost,https://www.boost.org,Boost Software License - Version 1.0
DrMingw,https://github.com/jrfonseca/drmingw,LGPLv2.1
Eigen3,http://eigen.tuxfamily.org,MPL2
Exiv2,http://www.exiv2.org,GPLv2
Expat,https://github.com/libexpat/libexpat,MIT
-#ffmpeg,https://www.ffmpeg.org,LGPL2.1+
+ffmpeg,https://www.ffmpeg.org,LGPL2.1+
fftw3,http://www.fftw.org/,GPLv2+
fontconfig,https://www.freedesktop.org/wiki/Software/fontconfig/,BSD
freetype,https://www.freetype.org/,FTL or GPLv2
gettext,https://www.gnu.org/software/gettext/,GPLv3
giflib,http://giflib.sourceforge.net/,BSD
gmic,http://gmic.eu/,CeCILLv2.1
gmic-qt,http://gmic.eu/,GPLv3
GNU Scientific Library,http://www.gnu.org/software/gsl,GPLv3
-libheif,https://github.com/strukturag/libheif,LGPLv3
iconv,https://www.gnu.org/software/libiconv/,LGPLv2 or GPLv3
ilmbase,http://www.openexr.com,Modified BSD
+KDE Frameworks 5,https://www.kde.org,LGPLv2.1+
+libheif,https://github.com/strukturag/libheif,LGPLv3
libjpeg-turbo,http://www.libjpeg-turbo.org,BSD
-littlecms2,http://www.littlecms.com,MIT
+libpng,http://www.libpng.org,libpng license
libraw,http://www.libraw.org,LGPLv2.1 or CDDL 1.0
+libtiff,http://libtiff.org/,BSD
+littlecms2,http://www.littlecms.com,MIT
OpenColorIO,http://www.opencolorio.org,BSD
OpenEXR,http://www.openexr.com,Modified BSD
-libpng,http://www.libpng.org,libpng license
poppler,http://poppler.freedesktop.org,GPLv2 and GPLv3
PyQt,https://www.riverbankcomputing.com/software/pyqt/download5,GPLv3
Python,http://www.python.org,Python Software Foundation License v2
Qt,https://www.qt.io,GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3
+Quazip,https://github.com/stachenov/quazip,LBPLv2.1
SIP,https://www.riverbankcomputing.com/software/sip/download,GPLv3
-libtiff,http://libtiff.org/,BSD
Vc,https://github.com/VcDevel/Vc,BSD
zlib,http://www.zlib.net/,BSD
-KDE Frameworks 5,https://www.kde.org,LGPLv+
diff --git a/krita/data/patterns/CMakeLists.txt b/krita/data/patterns/CMakeLists.txt
index 09fb559736..6f5917648f 100644
--- a/krita/data/patterns/CMakeLists.txt
+++ b/krita/data/patterns/CMakeLists.txt
@@ -1,86 +1,117 @@
########### install files ###############
install(FILES
Abstract_lines.png
01_canvas.png
02_rough-canvas.png
02b_WoofTissue.png
03_default-paper.png
04_paper-C-grain.png
05_paper-torchon.png
06_hard-grain.png
08_bump-relief.png
09_drawed_crosshatched.png
09b_drawed-CrossedLines.png
10_drawed_dotted.png
11_drawed_furry.png
12_drawed_vertical.png
13_drawed_swirl.png
14_texture-rock.png
15_texture-rockB.png
16_texture-woody.png
17_texture-melt.png
18_texture-bark.png
18b_WaveFlex.png
19_texture-crackle.png
20_texture-vegetal.png
21_texture-chainmail.png
22_texture-reptile.png
23-dynamic-screentone-A.png
24-dynamic-screentone-B.png
25-dynamic-screentone-C.png
26_brush-marks.png
Cross01.pat
Cross02.pat
Cross03.pat
Cross04.pat
Cross05.pat
Cross06.pat
Cross07.pat
Crumpled_Paper.pat
Grid01.pat
Grid02.pat
Grid03.pat
Grid04.pat
Grid05.pat
HR_PastelPaper_02.pat
HR_Wall_Paper.pat
Maze_lines.png
Pattern01.pat
Pattern02.pat
Pattern03.pat
Pattern04.pat
Pattern05.pat
Pattern06.pat
Rough_Paper.png
Rough_Wall_With_Impasto.png
Sand_fine.png
Stars_Sized.png
Squares01.pat
Squares02.pat
Squares03.pat
Squares04.pat
Squares05.pat
Squares06.pat
Squares07.pat
Squares08.pat
Squares09.pat
Squares10.pat
Stripes02.pat
Stripes03.pat
Stripes04.pat
Stripes05.pat
Stripes06.pat
Stripes07.pat
Stripes08.pat
Stripes09.pat
Zigzag01.pat
Zigzag02.pat
Zigzag03.pat
Zigzag04.pat
fractal_pattern.pat
generic_paper1.pat
generic_paper2.pat
hexacolBW__2.pat
+DITH_0202_CLUS.pat
+DITH_0202_GEN_.pat
+DITH_0202_HORZ.pat
+DITH_0202_VERT.pat
+DITH_0404_ALT_.pat
+DITH_0404_BL22.pat
+DITH_0404_CLUS.pat
+DITH_0404_CURL.pat
+DITH_0404_DIAG.pat
+DITH_0404_ELL2.pat
+DITH_0404_ELL3.pat
+DITH_0404_ELLS.pat
+DITH_0404_GEN_.pat
+DITH_0404_HORZ.pat
+DITH_0404_SHUR.pat
+DITH_0404_SLIC.pat
+DITH_0404_VERT.pat
+DITH_0404_WAV2.pat
+DITH_0404_WAVE.pat
+DITH_0404_ZORO.pat
+DITH_0808_BL22.pat
+DITH_0808_BL22_v.pat
+DITH_0808_BUBL.pat
+DITH_0808_CIRC.pat
+DITH_0808_CLUS.pat
+DITH_0808_DIAM.pat
+DITH_0808_PANL.pat
+DITH_0808_SPOT.pat
+DITH_0808_SWRL.pat
+DITH_0808_WAVE.pat
+DITH_3232_CSTR.pat
DESTINATION ${DATA_INSTALL_DIR}/krita/patterns)
diff --git a/krita/data/patterns/DITH_0202_CLUS.pat b/krita/data/patterns/DITH_0202_CLUS.pat
new file mode 100644
index 0000000000..a490dd26ed
Binary files /dev/null and b/krita/data/patterns/DITH_0202_CLUS.pat differ
diff --git a/krita/data/patterns/DITH_0202_GEN_.pat b/krita/data/patterns/DITH_0202_GEN_.pat
new file mode 100644
index 0000000000..1957ce2b67
Binary files /dev/null and b/krita/data/patterns/DITH_0202_GEN_.pat differ
diff --git a/krita/data/patterns/DITH_0202_HORZ.pat b/krita/data/patterns/DITH_0202_HORZ.pat
new file mode 100644
index 0000000000..2168049001
Binary files /dev/null and b/krita/data/patterns/DITH_0202_HORZ.pat differ
diff --git a/krita/data/patterns/DITH_0202_VERT.pat b/krita/data/patterns/DITH_0202_VERT.pat
new file mode 100644
index 0000000000..7fcf9acac4
Binary files /dev/null and b/krita/data/patterns/DITH_0202_VERT.pat differ
diff --git a/krita/data/patterns/DITH_0404_ALT_.pat b/krita/data/patterns/DITH_0404_ALT_.pat
new file mode 100644
index 0000000000..03acba8794
Binary files /dev/null and b/krita/data/patterns/DITH_0404_ALT_.pat differ
diff --git a/krita/data/patterns/DITH_0404_BL22.pat b/krita/data/patterns/DITH_0404_BL22.pat
new file mode 100644
index 0000000000..8862a86b53
Binary files /dev/null and b/krita/data/patterns/DITH_0404_BL22.pat differ
diff --git a/krita/data/patterns/DITH_0404_CLUS.pat b/krita/data/patterns/DITH_0404_CLUS.pat
new file mode 100644
index 0000000000..2ee2d9e7a4
Binary files /dev/null and b/krita/data/patterns/DITH_0404_CLUS.pat differ
diff --git a/krita/data/patterns/DITH_0404_CURL.pat b/krita/data/patterns/DITH_0404_CURL.pat
new file mode 100644
index 0000000000..0ba3bd0570
Binary files /dev/null and b/krita/data/patterns/DITH_0404_CURL.pat differ
diff --git a/krita/data/patterns/DITH_0404_DIAG.pat b/krita/data/patterns/DITH_0404_DIAG.pat
new file mode 100644
index 0000000000..23d6efa933
Binary files /dev/null and b/krita/data/patterns/DITH_0404_DIAG.pat differ
diff --git a/krita/data/patterns/DITH_0404_ELL2.pat b/krita/data/patterns/DITH_0404_ELL2.pat
new file mode 100644
index 0000000000..72adb0a39e
Binary files /dev/null and b/krita/data/patterns/DITH_0404_ELL2.pat differ
diff --git a/krita/data/patterns/DITH_0404_ELL3.pat b/krita/data/patterns/DITH_0404_ELL3.pat
new file mode 100644
index 0000000000..502ed21949
Binary files /dev/null and b/krita/data/patterns/DITH_0404_ELL3.pat differ
diff --git a/krita/data/patterns/DITH_0404_ELLS.pat b/krita/data/patterns/DITH_0404_ELLS.pat
new file mode 100644
index 0000000000..0fa32df133
Binary files /dev/null and b/krita/data/patterns/DITH_0404_ELLS.pat differ
diff --git a/krita/data/patterns/DITH_0404_GEN_.pat b/krita/data/patterns/DITH_0404_GEN_.pat
new file mode 100644
index 0000000000..7dfe88bbdc
Binary files /dev/null and b/krita/data/patterns/DITH_0404_GEN_.pat differ
diff --git a/krita/data/patterns/DITH_0404_HORZ.pat b/krita/data/patterns/DITH_0404_HORZ.pat
new file mode 100644
index 0000000000..de52f84159
Binary files /dev/null and b/krita/data/patterns/DITH_0404_HORZ.pat differ
diff --git a/krita/data/patterns/DITH_0404_SHUR.pat b/krita/data/patterns/DITH_0404_SHUR.pat
new file mode 100644
index 0000000000..6f355da51c
Binary files /dev/null and b/krita/data/patterns/DITH_0404_SHUR.pat differ
diff --git a/krita/data/patterns/DITH_0404_SLIC.pat b/krita/data/patterns/DITH_0404_SLIC.pat
new file mode 100644
index 0000000000..73f5c74cd1
Binary files /dev/null and b/krita/data/patterns/DITH_0404_SLIC.pat differ
diff --git a/krita/data/patterns/DITH_0404_VERT.pat b/krita/data/patterns/DITH_0404_VERT.pat
new file mode 100644
index 0000000000..d086e36830
Binary files /dev/null and b/krita/data/patterns/DITH_0404_VERT.pat differ
diff --git a/krita/data/patterns/DITH_0404_WAV2.pat b/krita/data/patterns/DITH_0404_WAV2.pat
new file mode 100644
index 0000000000..a01d844e7b
Binary files /dev/null and b/krita/data/patterns/DITH_0404_WAV2.pat differ
diff --git a/krita/data/patterns/DITH_0404_WAVE.pat b/krita/data/patterns/DITH_0404_WAVE.pat
new file mode 100644
index 0000000000..cd9b5721a3
Binary files /dev/null and b/krita/data/patterns/DITH_0404_WAVE.pat differ
diff --git a/krita/data/patterns/DITH_0404_ZORO.pat b/krita/data/patterns/DITH_0404_ZORO.pat
new file mode 100644
index 0000000000..919002432d
Binary files /dev/null and b/krita/data/patterns/DITH_0404_ZORO.pat differ
diff --git a/krita/data/patterns/DITH_0808_BL22.pat b/krita/data/patterns/DITH_0808_BL22.pat
new file mode 100644
index 0000000000..2683b8a2f5
Binary files /dev/null and b/krita/data/patterns/DITH_0808_BL22.pat differ
diff --git a/krita/data/patterns/DITH_0808_BL22_v.pat b/krita/data/patterns/DITH_0808_BL22_v.pat
new file mode 100644
index 0000000000..ed86f75bf8
Binary files /dev/null and b/krita/data/patterns/DITH_0808_BL22_v.pat differ
diff --git a/krita/data/patterns/DITH_0808_BUBL.pat b/krita/data/patterns/DITH_0808_BUBL.pat
new file mode 100644
index 0000000000..cfe3d45ac9
Binary files /dev/null and b/krita/data/patterns/DITH_0808_BUBL.pat differ
diff --git a/krita/data/patterns/DITH_0808_CIRC.pat b/krita/data/patterns/DITH_0808_CIRC.pat
new file mode 100644
index 0000000000..b4d40d4650
Binary files /dev/null and b/krita/data/patterns/DITH_0808_CIRC.pat differ
diff --git a/krita/data/patterns/DITH_0808_CLUS.pat b/krita/data/patterns/DITH_0808_CLUS.pat
new file mode 100644
index 0000000000..a0c41af628
Binary files /dev/null and b/krita/data/patterns/DITH_0808_CLUS.pat differ
diff --git a/krita/data/patterns/DITH_0808_DIAM.pat b/krita/data/patterns/DITH_0808_DIAM.pat
new file mode 100644
index 0000000000..3e72373753
Binary files /dev/null and b/krita/data/patterns/DITH_0808_DIAM.pat differ
diff --git a/krita/data/patterns/DITH_0808_PANL.pat b/krita/data/patterns/DITH_0808_PANL.pat
new file mode 100644
index 0000000000..e84dfddede
Binary files /dev/null and b/krita/data/patterns/DITH_0808_PANL.pat differ
diff --git a/krita/data/patterns/DITH_0808_SPOT.pat b/krita/data/patterns/DITH_0808_SPOT.pat
new file mode 100644
index 0000000000..7eddc95cae
Binary files /dev/null and b/krita/data/patterns/DITH_0808_SPOT.pat differ
diff --git a/krita/data/patterns/DITH_0808_SWRL.pat b/krita/data/patterns/DITH_0808_SWRL.pat
new file mode 100644
index 0000000000..3a9abbdc12
Binary files /dev/null and b/krita/data/patterns/DITH_0808_SWRL.pat differ
diff --git a/krita/data/patterns/DITH_0808_WAVE.pat b/krita/data/patterns/DITH_0808_WAVE.pat
new file mode 100644
index 0000000000..f889ade6af
Binary files /dev/null and b/krita/data/patterns/DITH_0808_WAVE.pat differ
diff --git a/krita/data/patterns/DITH_3232_CSTR.pat b/krita/data/patterns/DITH_3232_CSTR.pat
new file mode 100644
index 0000000000..595932a911
Binary files /dev/null and b/krita/data/patterns/DITH_3232_CSTR.pat differ
diff --git a/krita/data/patterns/dith_license.txt b/krita/data/patterns/dith_license.txt
new file mode 100644
index 0000000000..05a4d72068
--- /dev/null
+++ b/krita/data/patterns/dith_license.txt
@@ -0,0 +1,465 @@
+"dithering/halftoning patterns" by David Gowers
+
+Files:
+
+DITH_0202_CLUS.pat
+DITH_0202_GEN_.pat
+DITH_0202_HORZ.pat
+DITH_0202_VERT.pat
+DITH_0404_ALT_.pat
+DITH_0404_BL22.pat
+DITH_0404_CLUS.pat
+DITH_0404_CURL.pat
+DITH_0404_DIAG.pat
+DITH_0404_ELL2.pat
+DITH_0404_ELL3.pat
+DITH_0404_ELLS.pat
+DITH_0404_GEN_.pat
+DITH_0404_HORZ.pat
+DITH_0404_SHUR.pat
+DITH_0404_SLIC.pat
+DITH_0404_VERT.pat
+DITH_0404_WAV2.pat
+DITH_0404_WAVE.pat
+DITH_0404_ZORO.pat
+DITH_0808_BL22.pat
+DITH_0808_BL22_v.pat
+DITH_0808_BUBL.pat
+DITH_0808_CIRC.pat
+DITH_0808_CLUS.pat
+DITH_0808_DIAM.pat
+DITH_0808_PANL.pat
+DITH_0808_SPOT.pat
+DITH_0808_SWRL.pat
+DITH_0808_WAVE.pat
+DITH_3232_CSTR.pat
+
+Licensed under CC-BY-SA:
+
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+
+ including for purposes of Section 3(b); and
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/krita/data/shortcuts/krita_default.shortcuts b/krita/data/shortcuts/krita_default.shortcuts
index 46f31367ec..e7ca0597dd 100644
--- a/krita/data/shortcuts/krita_default.shortcuts
+++ b/krita/data/shortcuts/krita_default.shortcuts
@@ -1,285 +1,286 @@
[Shortcuts]
activateNextLayer=PgUp
activatePreviousLayer=PgDown
add_blank_frame=none
add_duplicate_frame=none
add_new_adjustment_layer=none
add_new_clone_layer=none
add_new_file_layer=none
add_new_fill_layer=none
add_new_filter_mask=none
add_new_group_layer=none
add_new_paint_layer=none
add_new_selection_mask=none
add_new_shape_layer=none
add_new_transform_mask=none
add_new_transparency_mask=none
borderselection=none
BrushesAndStuff=none
brushslider2=none
brushslider3=none
canvassize=Ctrl+Alt+C
clear=Del
clones_array=none
colorrange=none
composite_actions=none
convert_selection_to_shape=none
convert_shapes_to_vector_selection=none
convert_to_filter_mask=none
convert_to_paint_layer=none
convert_to_selection_mask=none
convert_to_transparency_mask=none
convert_to_vector_selection=none
copy_merged=Ctrl+Shift+C
copy_selection_to_new_layer=Ctrl+Alt+J
copy_sharp=none
create_bundle=none
create_copy=none
create_template=none
cut_selection_to_new_layer=Ctrl+Shift+J
cut_sharp=none
decrease_brush_size=[
decrease_opacity=I
delete_keyframe=none
deselect=Ctrl+Shift+A
dual=none
duplicatelayer=Ctrl+J
edit_blacklist_cleanup=none
edit_copy=Ctrl+C
edit_cut=Ctrl+X
EditLayerMetaData=none
edit_paste=Ctrl+V
edit_redo=Ctrl+Shift+Z
edit_undo=Ctrl+Z
erase_action=E
featherselection=Shift+F6
file_close_all=Ctrl+Shift+W
file_close=Ctrl+W
file_documentinfo=none
file_export_animation=none
file_export_file=none
file_export_pdf=none
file_import_file=none
file_new=Ctrl+N
file_open=Ctrl+O
file_open_recent=none
file_print=Ctrl+P
file_print_preview=none
file_quit=Ctrl+Q
file_save_as=Ctrl+Shift+S
file_save=Ctrl+S
fill_selection_background_color=Backspace
fill_selection_background_color_opacity=Ctrl+Backspace
fill_selection_foreground_color_opacity=Ctrl+Shift+Backspace
fill_selection_foreground_color=Shift+Backspace
fill_selection_pattern=none
fill_selection_pattern_opacity=none
filter_apply_again=Ctrl+F
first_frame=none
flatten_image=Ctrl+Shift+E
flatten_layer=none
fullscreen=Ctrl+Shift+F
gmic=none
gradients=none
growselection=none
help_about_app=none
help_about_kde=none
help_contents=F1
help_report_bug=none
histogram=none
hmirror_action=none
image_color=none
imagecolorspaceconversion=none
image_properties=none
imagesize=Ctrl+Alt+I
imagesplit=none
import_layer_as_filter_mask=none
import_layer_as_paint_layer=none
import_layer_as_selection_mask=none
import_layer_as_transparency_mask=none
import_layer_from_file=none
import_resources=none
increase_brush_size=]
increase_opacity=O
invert=Ctrl+Shift+I
isolate_layer=none
krita_filter_autocontrast=none
krita_filter_blur=none
krita_filter_bottom edge detections=none
krita_filter_brightnesscontrast=none
krita_filter_burn=none
krita_filter_colorbalance=Ctrl+B
krita_filter_colortoalpha=none
krita_filter_colortransfer=none
krita_filter_desaturate=Ctrl+Shift+U
krita_filter_dodge=none
krita_filter_emboss all directions=none
krita_filter_emboss horizontal and vertical=none
krita_filter_emboss horizontal only=none
krita_filter_emboss laplascian=none
krita_filter_emboss=none
krita_filter_emboss vertical only=none
krita_filter_gaussian blur=none
krita_filter_gaussiannoisereducer=none
krita_filter_hsvadjustment=Ctrl+U
krita_filter_indexcolors=none
krita_filter_invert=Ctrl+I
krita_filter_left edge detections=none
krita_filter_lens blur=none
krita_filter_levels=Ctrl+L
krita_filter_maximize=none
krita_filter_mean removal=none
krita_filter_minimize=none
krita_filter_motion blur=none
krita_filter_noise=none
krita_filter_oilpaint=none
krita_filter_perchannel=Ctrl+M
krita_filter_phongbumpmap=none
krita_filter_pixelize=none
krita_filter_posterize=none
krita_filter_raindrops=none
krita_filter_randompick=none
krita_filter_right edge detections=none
krita_filter_roundcorners=none
krita_filter_sharpen=none
krita_filter_smalltiles=none
krita_filter_sobel=none
krita_filter_top edge detections=none
krita_filter_unsharp=none
krita_filter_waveletnoisereducer=none
krita_filter_wave=none
last_frame=none
layercolorspaceconversion=none
LayerGroupSwitcher/next=none
LayerGroupSwitcher/previous=none
layer_properties=none
layersize=none
layersplit=none
layer_style=none
lazy_frame=none
level_of_detail_mode=Shift+L
Macro_Open_Edit=none
Macro_Open_Play=none
mainToolBar=none
make_brush_color_darker=K
make_brush_color_lighter=L
manage_bundles=none
merge_layer=Ctrl+E
mirror_actions=none
mirror_canvas=M
mirrorImageHorizontal=none
mirrorImageVertical=none
mirrorNodeX=none
mirrorNodeY=none
move_layer_down=none
move_layer_up=none
next_favorite_preset=,
next_frame=none
next_keyframe=none
offsetimage=none
offsetlayer=none
open_resources_directory=none
options_configure_keybinding=none
options_configure=none
options_configure_toolbars=none
paintops=none
paste_at=none
paste_new=Ctrl+Shift+N
+paste_as_reference=Ctrl+Shift+R
patterns=none
preserve_alpha=none
previous_favorite_preset=.
previous_frame=none
previous_keyframe=none
previous_preset=/
rasterize_layer=none
Recording_Start_Recording_Macro=none
Recording_Stop_Recording_Macro=none
reload_preset_action=none
remove_layer=Shift+Delete
RenameCurrentLayer=F2
reselect=Ctrl+Shift+D
resizeimagetolayer=none
resizeimagetoselection=none
rotate_canvas_left=Ctrl+[
rotate_canvas_right=Ctrl+]
rotateImage180=none
rotateImageCCW90=none
rotateImageCW90=none
rotateimage=none
rotateLayer180=none
rotateLayerCCW90=none
rotateLayerCW90=none
rotatelayer=none
save_groups_as_images=none
save_incremental_backup=F4
save_incremental_version=Ctrl+Alt+S
save_node_as_image=none
select_all=Ctrl+A
Select Behind Blending Mode=Alt+Shift+Q
Select Clear Blending Mode=Alt+Shift+R
Select Color Blending Mode=Alt+Shift+C
Select Color Burn Blending Mode=Alt+Shift+B
Select Color Dodge Blending Mode=Alt+Shift+D
Select Darken Blending Mode=Alt+Shift+K
Select Difference Blending Mode=Alt+Shift+E
Select Dissolve Blending Mode=Alt+Shift+I
Select Exclusion Blending Mode=Alt+Shift+X
Select Hard Light Blending Mode=Alt+Shift+H
Select Hard Mix Blending Mode=Alt+Shift+L
Select Hue Blending Mode=Alt+Shift+U
selectionscale=none
Select Lighten Blending Mode=Alt+Shift+G
Select Linear Burn Blending Mode=Alt+Shift+A
Select Linear Dodge Blending Mode=Alt+Shift+W
Select Linear Light Blending Mode=Alt+Shift+J
Select Luminosity Blending Mode=Alt+Shift+Y
Select Multiply Blending Mode=Alt+Shift+M
Select Normal Blending Mode=Alt+Shift+N
select_opaque=none
selectopaque=none
Select Overlay Blending Mode=Alt+Shift+O
Select Pin Light Blending Mode=Alt+Shift+Z
Select Saturation Blending Mode=Alt+Shift+T
Select Screen Blending Mode=Alt+Shift+S
Select Soft Light Blending Mode=Alt+Shift+F
Select Vivid Light Blending Mode=Alt+Shift+V
separate=none
settings_active_author=none
shearimage=none
shearlayer=none
show_brush_presets=F6
show-global-selection-mask=none
show_in_timeline=none
showStatusBar=none
shrinkselection=none
smoothselection=none
split_alpha_into_mask=none
split_alpha_save_merged=none
split_alpha_write=none
stroke_shapes=none
tablet_debugger=Ctrl+Shift+T
toggle_display_selection=Ctrl+H
toggle_playback=none
toggle-selection-overlay-mode=none
trim_to_image=none
view_clear_perspective_grid=none
view_grid=Ctrl+Shift+'
view_newwindow=none
view_ruler=none
view_show_canvas_only=Tab
view_show_guides=none
view_snap_to_grid=Ctrl+Shift+;
view_toggle_assistant_previews=none
view_toggledockers=none
view_toggledockertitlebars=none
view_toggle_painting_assistants=none
view_toggle_perspective_grid=none
view_zoom_in=Ctrl++
view_zoom_out=Ctrl+-
vmirror_action=none
windows_cascade=none
windows_next=Ctrl+Tab
windows_previous=none
windows_tile=none
workspaces=none
zoom_to_100pct=Ctrl+0
diff --git a/krita/krita.action b/krita/krita.action
index c6b06cc039..d7ae56934d 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,3592 +1,3604 @@
<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Krita">
<Actions category="General">
<text>General</text>
<Action name="open_resources_directory">
<icon></icon>
<text>Open Resources Folder</text>
<whatsThis>Opens a file browser at the location Krita saves resources such as brushes to.</whatsThis>
<toolTip>Opens a file browser at the location Krita saves resources such as brushes to.</toolTip>
<iconText>Open Resources Folder</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_blacklist_cleanup">
<icon></icon>
<text>Cleanup removed files...</text>
<whatsThis></whatsThis>
<toolTip>Cleanup removed files</toolTip>
<iconText>Cleanup removed files</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_cascade">
<icon></icon>
<text>C&amp;ascade</text>
<whatsThis></whatsThis>
<toolTip>Cascade</toolTip>
<iconText>Cascade</iconText>
<activationFlags>10</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_tile">
<icon></icon>
<text>&amp;Tile</text>
<whatsThis></whatsThis>
<toolTip>Tile</toolTip>
<iconText>Tile</iconText>
<activationFlags>10</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_bundle">
<icon></icon>
<text>Create Resource Bundle...</text>
<whatsThis></whatsThis>
<toolTip>Create Resource Bundle</toolTip>
<iconText>Create Resource Bundle</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mainToolBar">
<icon></icon>
<text>Show File Toolbar</text>
<whatsThis></whatsThis>
<toolTip>Show File Toolbar</toolTip>
<iconText>Show File Toolbar</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_color_selector">
<icon></icon>
<text>Show color selector</text>
<whatsThis></whatsThis>
<toolTip>Show color selector</toolTip>
<iconText>Show color selector</iconText>
<shortcut>Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_mypaint_shade_selector">
<icon></icon>
<text>Show MyPaint shade selector</text>
<whatsThis></whatsThis>
<toolTip>Show MyPaint shade selector</toolTip>
<iconText>Show MyPaint shade selector</iconText>
<shortcut>Shift+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_minimal_shade_selector">
<icon></icon>
<text>Show minimal shade selector</text>
<whatsThis></whatsThis>
<toolTip>Show minimal shade selector</toolTip>
<iconText>Show minimal shade selector</iconText>
<shortcut>Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_color_history">
<icon></icon>
<text>Show color history</text>
<whatsThis></whatsThis>
<toolTip>Show color history</toolTip>
<iconText>Show color history</iconText>
<shortcut>H</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_common_colors">
<icon></icon>
<text>Show common colors</text>
<whatsThis></whatsThis>
<toolTip>Show common colors</toolTip>
<iconText>Show common colors</iconText>
<shortcut>U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_tool_options">
<icon></icon>
<text>Show Tool Options</text>
<whatsThis></whatsThis>
<toolTip>Show Tool Options</toolTip>
<iconText>Show Tool Options</iconText>
<shortcut>\</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_brush_editor">
<icon></icon>
<text>Show Brush Editor</text>
<whatsThis></whatsThis>
<toolTip>Show Brush Editor</toolTip>
<iconText>Show Brush Editor</iconText>
<shortcut>F5</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_brush_presets">
<icon></icon>
<text>Show Brush Presets</text>
<whatsThis></whatsThis>
<toolTip>Show Brush Presets</toolTip>
<iconText>Show Brush Presets</iconText>
<shortcut>F6</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="tablet_debugger">
<icon></icon>
<text>Toggle Tablet Debugger</text>
<whatsThis></whatsThis>
<toolTip>Toggle Tablet Debugger</toolTip>
<iconText>Toggle Tablet Debugger</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="buginfo">
<icon></icon>
<text>Show system information for bug reports.</text>
<whatsThis></whatsThis>
<toolTip>Show system information for bug reports.</toolTip>
<iconText>Show system information for bug reports.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rename_composition">
<icon></icon>
<text>Rename Composition...</text>
<whatsThis></whatsThis>
<toolTip>Rename Composition</toolTip>
<iconText>Rename Composition</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="update_composition">
<icon></icon>
<text>Update Composition</text>
<whatsThis></whatsThis>
<toolTip>Update Composition</toolTip>
<iconText>Update Composition</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ruler_pixel_multiple2">
<icon></icon>
<text>Use multiple of 2 for pixel scale</text>
<whatsThis>Use multiple of 2 for pixel scale</whatsThis>
<toolTip>Use multiple of 2 for pixel scale</toolTip>
<iconText>Use multiple of 2 for pixel scale</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="invert_selection">
<icon></icon>
<text>&amp;Invert Selection</text>
<whatsThis></whatsThis>
<toolTip>Invert current selection</toolTip>
<iconText>Invert Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut>Ctrl+Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Painting">
<text>Painting</text>
<Action name="make_brush_color_lighter">
<icon>lightness-increase</icon>
<text>Make brush color lighter</text>
<whatsThis></whatsThis>
<toolTip>Make brush color lighter</toolTip>
<iconText>Make brush color lighter</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_darker">
<icon>lightness-decrease</icon>
<text>Make brush color darker</text>
<whatsThis></whatsThis>
<toolTip>Make brush color darker</toolTip>
<iconText>Make brush color darker</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>K</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_saturated">
<icon></icon>
<text>Make brush color more saturated</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more saturated</toolTip>
<iconText>Make brush color more saturated</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_desaturated">
<icon></icon>
<text>Make brush color more desaturated</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more desaturated</toolTip>
<iconText>Make brush color more desaturated</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shift_brush_color_clockwise">
<icon></icon>
<text>Shift brush color hue clockwise</text>
<whatsThis></whatsThis>
<toolTip>Shift brush color hue clockwise</toolTip>
<iconText>Shift brush color hue clockwise</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shift_brush_color_counter_clockwise">
<icon></icon>
<text>Shift brush color hue counter-clockwise</text>
<whatsThis></whatsThis>
<toolTip>Shift brush color hue counter-clockwise</toolTip>
<iconText>Shift brush color hue counter-clockwise</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_redder">
<icon></icon>
<text>Make brush color more red</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more red</toolTip>
<iconText>Make brush color more red</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_greener">
<icon></icon>
<text>Make brush color more green</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more green</toolTip>
<iconText>Make brush color more green</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_bluer">
<icon></icon>
<text>Make brush color more blue</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more blue</toolTip>
<iconText>Make brush color more blue</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_yellower">
<icon></icon>
<text>Make brush color more yellow</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more yellow</toolTip>
<iconText>Make brush color more yellow</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="increase_opacity">
<icon>opacity-increase</icon>
<text>Increase opacity</text>
<whatsThis></whatsThis>
<toolTip>Increase opacity</toolTip>
<iconText>Increase opacity</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="decrease_opacity">
<icon>opacity-decrease</icon>
<text>Decrease opacity</text>
<whatsThis></whatsThis>
<toolTip>Decrease opacity</toolTip>
<iconText>Decrease opacity</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="erase_action">
<icon>draw-eraser</icon>
<text>Set eraser mode</text>
<whatsThis></whatsThis>
<toolTip>Set eraser mode</toolTip>
<iconText>Set eraser mode</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>E</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reload_preset_action">
<icon>view-refresh</icon>
<text>Reload Original Preset</text>
<whatsThis></whatsThis>
<toolTip>Reload Original Preset</toolTip>
<iconText>Reload Original Preset</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="preserve_alpha">
<icon>transparency-unlocked</icon>
<text>Preserve Alpha</text>
<whatsThis></whatsThis>
<toolTip>Preserve Alpha</toolTip>
<iconText>Preserve Alpha</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="disable_pressure">
<icon>transform_icons_penPressure</icon>
<text>Use Pen Pressure</text>
<whatsThis></whatsThis>
<toolTip>Use Pen Pressure</toolTip>
<iconText>Use Pen Pressure</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="hmirror_action">
<icon>symmetry-horizontal</icon>
<text>Horizontal Mirror Tool</text>
<whatsThis></whatsThis>
<toolTip>Horizontal Mirror Tool</toolTip>
<iconText>Horizontal Mirror Tool</iconText>
<activationFlags>0</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="vmirror_action">
<icon>symmetry-vertical</icon>
<text>Vertical Mirror Tool</text>
<whatsThis></whatsThis>
<toolTip>Vertical Mirror Tool</toolTip>
<iconText>Vertical Mirror Tool</iconText>
<activationFlags>0</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorX-hideDecorations">
<icon></icon>
<text>Hide Mirror X Line</text>
<whatsThis></whatsThis>
<toolTip>Hide Mirror X Line</toolTip>
<iconText>Hide Mirror X Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorY-hideDecorations">
<icon></icon>
<text>Hide Mirror Y Line</text>
<whatsThis></whatsThis>
<toolTip>Hide Mirror Y Line</toolTip>
<iconText>Hide Mirror Y Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorX-lock">
<icon></icon>
<text>Lock</text>
<whatsThis></whatsThis>
<toolTip>Lock X Line</toolTip>
<iconText>Lock X Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorY-lock">
<icon></icon>
<text>Lock Y Line</text>
<whatsThis></whatsThis>
<toolTip>Lock Y Line</toolTip>
<iconText>Lock Y Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorX-moveToCenter">
<icon></icon>
<text>Move to Canvas Center</text>
<whatsThis></whatsThis>
<toolTip>Move to Canvas Center X</toolTip>
<iconText>Move to Canvas Center X</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorY-moveToCenter">
<icon></icon>
<text>Move to Canvas Center Y</text>
<whatsThis></whatsThis>
<toolTip>Move to Canvas Center Y</toolTip>
<iconText>Move to Canvas Center Y</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle-selection-overlay-mode">
<icon></icon>
<text>&amp;Toggle Selection Display Mode</text>
<whatsThis></whatsThis>
<toolTip>Toggle Selection Display Mode</toolTip>
<iconText>Toggle Selection Display Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_favorite_preset">
<icon></icon>
<text>Next Favourite Preset</text>
<whatsThis></whatsThis>
<toolTip>Next Favourite Preset</toolTip>
<iconText>Next Favourite Preset</iconText>
<shortcut>,</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_favorite_preset">
<icon></icon>
<text>Previous Favourite Preset</text>
<whatsThis></whatsThis>
<toolTip>Previous Favourite Preset</toolTip>
<iconText>Previous Favourite Preset</iconText>
<shortcut>.</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_preset">
<icon>preset-switcher</icon>
<text>Switch to Previous Preset</text>
<whatsThis></whatsThis>
<toolTip>Switch to Previous Preset</toolTip>
<iconText>Switch to Previous Preset</iconText>
<shortcut>/</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="BrushesAndStuff">
<icon></icon>
<text>Hide Brushes and Stuff Toolbar</text>
<whatsThis></whatsThis>
<toolTip>Hide Brushes and Stuff Toolbar</toolTip>
<iconText>Hide Brushes and Stuff Toolbar</iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reset_fg_bg">
<icon></icon>
<text>Reset Foreground and Background Color</text>
<whatsThis></whatsThis>
<toolTip>Reset Foreground and Background Color</toolTip>
<iconText>Reset Foreground and Background Color</iconText>
<shortcut>D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_fg_bg">
<icon></icon>
<text>Swap Foreground and Background Color</text>
<whatsThis></whatsThis>
<toolTip>Swap Foreground and Background Color</toolTip>
<iconText>Swap Foreground and Background Color</iconText>
<shortcut>X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selection_tool_mode_add">
<icon></icon>
<text>Selection Mode: Add</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Add</toolTip>
<iconText>Selection Mode: Add</iconText>
<shortcut>A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selection_tool_mode_subtract">
<icon></icon>
<text>Selection Mode: Subtract</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Subtract</toolTip>
<iconText>Selection Mode: Subtract</iconText>
<shortcut>S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selection_tool_mode_intersect">
<icon></icon>
<text>Selection Mode: Intersect</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Intersect</toolTip>
<iconText>Selection Mode: Intersect</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selection_tool_mode_replace">
<icon></icon>
<text>Selection Mode: Replace</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Replace</toolTip>
<iconText>Selection Mode: Replace</iconText>
<shortcut>R</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_weighted_brush_smoothing">
<icon>smoothing-weighted</icon>
<text>Brush Smoothing: Weighted</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Weighted</toolTip>
<iconText>Brush Smoothing: Weighted</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_no_brush_smoothing">
<icon>smoothing-no</icon>
<text>Brush Smoothing: Disabled</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Disabled</toolTip>
<iconText>Brush Smoothing: Disabled</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_stabilizer_brush_smoothing">
<icon>smoothing-stabilizer</icon>
<text>Brush Smoothing: Stabilizer</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Stabilizer</toolTip>
<iconText>Brush Smoothing: Stabilizer</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="decrease_brush_size">
<icon>brushsize-decrease</icon>
<text>Decrease Brush Size</text>
<whatsThis></whatsThis>
<toolTip>Decrease Brush Size</toolTip>
<iconText>Decrease Brush Size</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>[</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_simple_brush_smoothing">
<icon>smoothing-basic</icon>
<text>Brush Smoothing: Basic</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Basic</toolTip>
<iconText>Brush Smoothing: Basic</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="increase_brush_size">
<icon>brushsize-increase</icon>
<text>Increase Brush Size</text>
<whatsThis></whatsThis>
<toolTip>Increase Brush Size</toolTip>
<iconText>Increase Brush Size</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>]</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_assistant">
<icon></icon>
<text>Toggle Assistant</text>
<whatsThis></whatsThis>
<toolTip>Toggle Assistant</toolTip>
<iconText>ToggleAssistant</iconText>
<shortcut>Ctrl+Shift+L</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="undo_polygon_selection">
<icon></icon>
<text>Undo Polygon Selection Points</text>
<whatsThis></whatsThis>
<toolTip>Undo Polygon Selection Points</toolTip>
<iconText>Undo Polygon Selection Points</iconText>
<shortcut>Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_foreground_color_opacity">
<icon></icon>
<text>Fill with Foreground Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color (Opacity)</toolTip>
<iconText>Fill with Foreground Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color_opacity">
<icon></icon>
<text>Fill with Background Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color (Opacity)</toolTip>
<iconText>Fill with Background Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern_opacity">
<icon></icon>
<text>Fill with Pattern (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern (Opacity)</toolTip>
<iconText>Fill with Pattern (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_selection_to_shape">
<icon></icon>
<text>Convert &amp;to Shape</text>
<whatsThis></whatsThis>
<toolTip>Convert to Shape</toolTip>
<iconText>Convert to Shape</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show-global-selection-mask">
<icon></icon>
<text>&amp;Show Global Selection Mask</text>
<whatsThis></whatsThis>
<toolTip>Shows global selection as a usual selection mask in &lt;interface>Layers&lt;/interface> docker</toolTip>
<iconText>Show Global Selection Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Filters">
<text>Filters</text>
<Action name="krita_filter_colortoalpha">
<icon>color-to-alpha</icon>
<text>&amp;Color to Alpha...</text>
<whatsThis></whatsThis>
<toolTip>Color to Alpha</toolTip>
<iconText>Color to Alpha</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_top edge detections">
<icon></icon>
<text>&amp;Top Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Top Edge Detection</toolTip>
<iconText>Top Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_indexcolors">
<icon></icon>
<text>&amp;Index Colors...</text>
<whatsThis></whatsThis>
<toolTip>Index Colors</toolTip>
<iconText>Index Colors</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss horizontal only">
<icon></icon>
<text>Emboss Horizontal &amp;Only</text>
<whatsThis></whatsThis>
<toolTip>Emboss Horizontal Only</toolTip>
<iconText>Emboss Horizontal Only</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_dodge">
<icon></icon>
<text>D&amp;odge</text>
<whatsThis></whatsThis>
<toolTip>Dodge</toolTip>
<iconText>Dodge</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_sharpen">
<icon></icon>
<text>&amp;Sharpen</text>
<whatsThis></whatsThis>
<toolTip>Sharpen</toolTip>
<iconText>Sharpen</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_burn">
<icon></icon>
<text>B&amp;urn</text>
<whatsThis></whatsThis>
<toolTip>Burn</toolTip>
<iconText>Burn</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_mean removal">
<icon></icon>
<text>&amp;Mean Removal</text>
<whatsThis></whatsThis>
<toolTip>Mean Removal</toolTip>
<iconText>Mean Removal</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_gaussian blur">
<icon></icon>
<text>&amp;Gaussian Blur...</text>
<whatsThis></whatsThis>
<toolTip>Gaussian Blur</toolTip>
<iconText>Gaussian Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss all directions">
<icon></icon>
<text>Emboss &amp;in All Directions</text>
<whatsThis></whatsThis>
<toolTip>Emboss in All Directions</toolTip>
<iconText>Emboss in All Directions</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_smalltiles">
<icon></icon>
<text>&amp;Small Tiles...</text>
<whatsThis></whatsThis>
<toolTip>Small Tiles</toolTip>
<iconText>Small Tiles</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_levels">
<icon></icon>
<text>&amp;Levels...</text>
<whatsThis></whatsThis>
<toolTip>Levels</toolTip>
<iconText>Levels</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_sobel">
<icon></icon>
<text>&amp;Sobel...</text>
<whatsThis></whatsThis>
<toolTip>Sobel</toolTip>
<iconText>Sobel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_wave">
<icon></icon>
<text>&amp;Wave...</text>
<whatsThis></whatsThis>
<toolTip>Wave</toolTip>
<iconText>Wave</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_motion blur">
<icon></icon>
<text>&amp;Motion Blur...</text>
<whatsThis></whatsThis>
<toolTip>Motion Blur</toolTip>
<iconText>Motion Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_invert">
<icon></icon>
<text>&amp;Invert</text>
<whatsThis></whatsThis>
<toolTip>Invert</toolTip>
<iconText>Invert</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_perchannel">
<icon></icon>
<text>&amp;Color Adjustment curves...</text>
<whatsThis></whatsThis>
<toolTip>Color Adjustment curves</toolTip>
<iconText>Color Adjustment curves</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_pixelize">
<icon></icon>
<text>Pi&amp;xelize...</text>
<whatsThis></whatsThis>
<toolTip>Pixelize</toolTip>
<iconText>Pixelize</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss laplascian">
<icon></icon>
<text>Emboss (&amp;Laplacian)</text>
<whatsThis></whatsThis>
<toolTip>Emboss (Laplacian)</toolTip>
<iconText>Emboss (Laplacian)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_left edge detections">
<icon></icon>
<text>&amp;Left Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Left Edge Detection</toolTip>
<iconText>Left Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_blur">
<icon></icon>
<text>&amp;Blur...</text>
<whatsThis></whatsThis>
<toolTip>Blur</toolTip>
<iconText>Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_raindrops">
<icon></icon>
<text>&amp;Raindrops...</text>
<whatsThis></whatsThis>
<toolTip>Raindrops</toolTip>
<iconText>Raindrops</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_bottom edge detections">
<icon></icon>
<text>&amp;Bottom Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Bottom Edge Detection</toolTip>
<iconText>Bottom Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_noise">
<icon></icon>
<text>&amp;Random Noise...</text>
<whatsThis></whatsThis>
<toolTip>Random Noise</toolTip>
<iconText>Random Noise</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_brightnesscontrast">
<icon></icon>
<text>&amp;Brightness/Contrast curve...</text>
<whatsThis></whatsThis>
<toolTip>Brightness/Contrast curve</toolTip>
<iconText>Brightness/Contrast curve</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_colorbalance">
<icon></icon>
<text>Colo&amp;r Balance...</text>
<whatsThis></whatsThis>
<toolTip>Color Balance</toolTip>
<iconText>Color Balance</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_phongbumpmap">
<icon></icon>
<text>&amp;Phong Bumpmap...</text>
<whatsThis></whatsThis>
<toolTip>Phong Bumpmap</toolTip>
<iconText>Phong Bumpmap</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_desaturate">
<icon></icon>
<text>&amp;Desaturate</text>
<whatsThis></whatsThis>
<toolTip>Desaturate</toolTip>
<iconText>Desaturate</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_colortransfer">
<icon></icon>
<text>Color &amp;Transfer...</text>
<whatsThis></whatsThis>
<toolTip>Color Transfer</toolTip>
<iconText>Color Transfer</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss vertical only">
<icon></icon>
<text>Emboss &amp;Vertical Only</text>
<whatsThis></whatsThis>
<toolTip>Emboss Vertical Only</toolTip>
<iconText>Emboss Vertical Only</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_lens blur">
<icon></icon>
<text>&amp;Lens Blur...</text>
<whatsThis></whatsThis>
<toolTip>Lens Blur</toolTip>
<iconText>Lens Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_minimize">
<icon></icon>
<text>M&amp;inimize Channel</text>
<whatsThis></whatsThis>
<toolTip>Minimize Channel</toolTip>
<iconText>Minimize Channel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_maximize">
<icon></icon>
<text>M&amp;aximize Channel</text>
<whatsThis></whatsThis>
<toolTip>Maximize Channel</toolTip>
<iconText>Maximize Channel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_oilpaint">
<icon></icon>
<text>&amp;Oilpaint...</text>
<whatsThis></whatsThis>
<toolTip>Oilpaint</toolTip>
<iconText>Oilpaint</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_right edge detections">
<icon></icon>
<text>&amp;Right Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Right Edge Detection</toolTip>
<iconText>Right Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_autocontrast">
<icon></icon>
<text>&amp;Auto Contrast</text>
<whatsThis></whatsThis>
<toolTip>Auto Contrast</toolTip>
<iconText>Auto Contrast</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_roundcorners">
<icon></icon>
<text>&amp;Round Corners...</text>
<whatsThis></whatsThis>
<toolTip>Round Corners</toolTip>
<iconText>Round Corners</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_unsharp">
<icon></icon>
<text>&amp;Unsharp Mask...</text>
<whatsThis></whatsThis>
<toolTip>Unsharp Mask</toolTip>
<iconText>Unsharp Mask</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss">
<icon></icon>
<text>&amp;Emboss with Variable Depth...</text>
<whatsThis></whatsThis>
<toolTip>Emboss with Variable Depth</toolTip>
<iconText>Emboss with Variable Depth</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss horizontal and vertical">
<icon></icon>
<text>Emboss &amp;Horizontal &amp;&amp; Vertical</text>
<whatsThis></whatsThis>
<toolTip>Emboss Horizontal &amp; Vertical</toolTip>
<iconText>Emboss Horizontal &amp; Vertical</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_randompick">
<icon></icon>
<text>Random &amp;Pick...</text>
<whatsThis></whatsThis>
<toolTip>Random Pick</toolTip>
<iconText>Random Pick</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_gaussiannoisereducer">
<icon></icon>
<text>&amp;Gaussian Noise Reduction...</text>
<whatsThis></whatsThis>
<toolTip>Gaussian Noise Reduction</toolTip>
<iconText>Gaussian Noise Reduction</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_posterize">
<icon></icon>
<text>&amp;Posterize...</text>
<whatsThis></whatsThis>
<toolTip>Posterize</toolTip>
<iconText>Posterize</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_waveletnoisereducer">
<icon></icon>
<text>&amp;Wavelet Noise Reducer...</text>
<whatsThis></whatsThis>
<toolTip>Wavelet Noise Reducer</toolTip>
<iconText>Wavelet Noise Reducer</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_hsvadjustment">
<icon></icon>
<text>&amp;HSV Adjustment...</text>
<whatsThis></whatsThis>
<toolTip>HSV Adjustment</toolTip>
<iconText>HSV Adjustment</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="tool-shortcuts">
<text>Tool Shortcuts</text>
<Action name="KritaShape/KisToolDyna">
<icon></icon>
<text>Dynamic Brush Tool</text>
<whatsThis></whatsThis>
<toolTip>Dynamic Brush Tool</toolTip>
<iconText>Dynamic Brush Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolCrop">
<icon></icon>
<text>Crop Tool</text>
<whatsThis></whatsThis>
<toolTip>Crop the image to an area</toolTip>
<iconText>Crop the image to an area</iconText>
<shortcut>C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPolygon">
<icon></icon>
<text>Polygon Tool</text>
<whatsThis></whatsThis>
<toolTip>Polygon Tool. Shift-mouseclick ends the polygon.</toolTip>
<iconText>Polygon Tool. Shift-mouseclick ends the polygon.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolRectangle">
<icon></icon>
<text>Rectangle Tool</text>
<whatsThis></whatsThis>
<toolTip>Rectangle Tool</toolTip>
<iconText>Rectangle Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolMultiBrush">
<icon></icon>
<text>Multibrush Tool</text>
<whatsThis></whatsThis>
<toolTip>Multibrush Tool</toolTip>
<iconText>Multibrush Tool</iconText>
<shortcut>Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolLazyBrush">
<icon></icon>
<text>Colorize Mask Tool</text>
<whatsThis></whatsThis>
<toolTip>Colorize Mask Tool</toolTip>
<iconText>Colorize Mask Tool</iconText>
<shortcut></shortcut>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolSmartPatch">
<icon></icon>
<text>Smart Patch Tool</text>
<whatsThis></whatsThis>
<toolTip>Smart Patch Tool</toolTip>
<iconText>Smart Patch Tool</iconText>
<shortcut></shortcut>
<statusTip></statusTip>
</Action>
<Action name="PanTool">
<icon></icon>
<text>Pan Tool</text>
<whatsThis></whatsThis>
<toolTip>Pan Tool</toolTip>
<iconText>Pan Tool</iconText>
<shortcut></shortcut>
<statusTip></statusTip>
</Action>
<Action name="InteractionTool">
<icon></icon>
<text>Select Shapes Tool</text>
<whatsThis></whatsThis>
<toolTip>Select Shapes Tool</toolTip>
<iconText>Select Shapes Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaSelected/KisToolColorPicker">
<icon></icon>
<text>Color Picker</text>
<whatsThis></whatsThis>
<toolTip>Select a color from the image or current layer</toolTip>
<iconText>Select a color from the image or current layer</iconText>
<shortcut>P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectOutline">
<icon></icon>
<text>Outline Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Outline Selection Tool</toolTip>
<iconText>Outline Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectPath">
<icon></icon>
<text>Bezier Curve Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Select a </toolTip>
<iconText>Bezier Curve Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectSimilar">
<icon></icon>
<text>Similar Color Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Select a </toolTip>
<iconText>Similar Color Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaFill/KisToolFill">
<icon></icon>
<text>Fill Tool</text>
<whatsThis></whatsThis>
<toolTip>Fill a contiguous area of color with a color, or fill a selection.</toolTip>
<iconText>Fill a contiguous area of color with a color, or fill a selection.</iconText>
<shortcut>F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolLine">
<icon></icon>
<text>Line Tool</text>
<whatsThis></whatsThis>
<toolTip>Line Tool</toolTip>
<iconText>Line Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPencil">
<icon></icon>
<text>Freehand Path Tool</text>
<whatsThis></whatsThis>
<toolTip>Freehand Path Tool</toolTip>
<iconText>Freehand Path Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPath">
<icon></icon>
<text>Bezier Curve Tool</text>
<whatsThis></whatsThis>
<toolTip>Bezier Curve Tool. Shift-mouseclick or double-click ends the curve.</toolTip>
<iconText>Bezier Curve Tool. Shift-mouseclick or double-click ends the curve.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolEllipse">
<icon></icon>
<text>Ellipse Tool</text>
<whatsThis></whatsThis>
<toolTip>Ellipse Tool</toolTip>
<iconText>Ellipse Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolBrush">
<icon></icon>
<text>Freehand Brush Tool</text>
<whatsThis></whatsThis>
<toolTip>Freehand Brush Tool</toolTip>
<iconText>Freehand Brush Tool</iconText>
<shortcut>B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="CreateShapesTool">
<icon></icon>
<text>Create object</text>
<whatsThis></whatsThis>
<toolTip>Create object</toolTip>
<iconText>Create object</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectElliptical">
<icon></icon>
<text>Elliptical Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Elliptical Selection Tool</toolTip>
<iconText>Elliptical Selection Tool</iconText>
<shortcut>J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectContiguous">
<icon></icon>
<text>Contiguous Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Contiguous Selection Tool</toolTip>
<iconText>Contiguous Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonPatternTool">
<icon></icon>
<text>Pattern editing</text>
<whatsThis></whatsThis>
<toolTip>Pattern editing</toolTip>
<iconText>Pattern editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ReviewTool">
<icon></icon>
<text>Review</text>
<whatsThis></whatsThis>
<toolTip>Review</toolTip>
<iconText>Review</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaFill/KisToolGradient">
<icon></icon>
<text>Draw a gradient.</text>
<whatsThis></whatsThis>
<toolTip>Draw a gradient.</toolTip>
<iconText>Draw a gradient.</iconText>
<shortcut>G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectPolygonal">
<icon></icon>
<text>Polygonal Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Polygonal Selection Tool</toolTip>
<iconText>Polygonal Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolMeasure">
<icon></icon>
<text>Measurement Tool</text>
<whatsThis></whatsThis>
<toolTip>Measure the distance between two points</toolTip>
<iconText>Measure the distance between two points</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectRectangular">
<icon></icon>
<text>Rectangular Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Rectangular Selection Tool</toolTip>
<iconText>Rectangular Selection Tool</iconText>
<shortcut>Ctrl+R</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaTransform/KisToolMove">
<icon></icon>
<text>Move Tool</text>
<whatsThis></whatsThis>
<toolTip>Move a layer</toolTip>
<iconText>Move a layer</iconText>
<shortcut>T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="VectorTool">
<icon></icon>
<text>Vector Image Tool</text>
<whatsThis></whatsThis>
<toolTip>Vector Image (EMF/WMF/SVM/SVG) tool</toolTip>
<iconText>Vector Image (EMF/WMF/SVM/SVG) tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonCalligraphyTool">
<icon></icon>
<text>Calligraphy</text>
<whatsThis></whatsThis>
<toolTip>Calligraphy</toolTip>
<iconText>Calligraphy</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="PathTool">
<icon></icon>
<text>Path editing</text>
<whatsThis></whatsThis>
<toolTip>Path editing</toolTip>
<iconText>Path editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ZoomTool">
<icon></icon>
<text>Zoom Tool</text>
<whatsThis></whatsThis>
<toolTip>Zoom Tool</toolTip>
<iconText>Zoom Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPolyline">
<icon></icon>
<text>Polyline Tool</text>
<whatsThis></whatsThis>
<toolTip>Polyline Tool. Shift-mouseclick ends the polyline.</toolTip>
<iconText>Polyline Tool. Shift-mouseclick ends the polyline.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolTransform">
<icon></icon>
<text>Transform Tool</text>
<whatsThis></whatsThis>
<toolTip>Transform a layer or a selection</toolTip>
<iconText>Transform a layer or a selection</iconText>
<shortcut>Ctrl+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisAssistantTool">
<icon></icon>
<text>Assistant Tool</text>
<whatsThis></whatsThis>
<toolTip>Assistant Tool</toolTip>
<iconText>Assistant Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonGradientTool">
<icon></icon>
<text>Gradient Editing Tool</text>
<whatsThis></whatsThis>
<toolTip>Gradient editing</toolTip>
<iconText>Gradient editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ToolReferenceImages">
<icon></icon>
<text>Reference Images Tool</text>
<whatsThis></whatsThis>
<toolTip>Reference Images Tool</toolTip>
<iconText>Reference Images Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Blending Modes">
<text>Blending Modes</text>
<!-- commented out in the code right now
<Action name="Next Blending Mode">
<icon></icon>
<text>Next Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Next Blending Mode</toolTip>
<iconText>Next Blending Mode</iconText>
<shortcut>Alt+Shift++</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Previous Blending Mode">
<icon></icon>
<text>Previous Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Previous Blending Mode</toolTip>
<iconText>Previous Blending Mode</iconText>
<shortcut>Alt+Shift+-</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
-->
<Action name="Select Normal Blending Mode">
<icon></icon>
<text>Select Normal Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Normal Blending Mode</toolTip>
<iconText>Select Normal Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Dissolve Blending Mode">
<icon></icon>
<text>Select Dissolve Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Dissolve Blending Mode</toolTip>
<iconText>Select Dissolve Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Behind Blending Mode">
<icon></icon>
<text>Select Behind Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Behind Blending Mode</toolTip>
<iconText>Select Behind Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Clear Blending Mode">
<icon></icon>
<text>Select Clear Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Clear Blending Mode</toolTip>
<iconText>Select Clear Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+R</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Darken Blending Mode">
<icon></icon>
<text>Select Darken Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Darken Blending Mode</toolTip>
<iconText>Select Darken Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+K</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Multiply Blending Mode">
<icon></icon>
<text>Select Multiply Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Multiply Blending Mode</toolTip>
<iconText>Select Multiply Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Burn Blending Mode">
<icon></icon>
<text>Select Color Burn Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Burn Blending Mode</toolTip>
<iconText>Select Color Burn Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Burn Blending Mode">
<icon></icon>
<text>Select Linear Burn Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Burn Blending Mode</toolTip>
<iconText>Select Linear Burn Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Lighten Blending Mode">
<icon></icon>
<text>Select Lighten Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Lighten Blending Mode</toolTip>
<iconText>Select Lighten Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Screen Blending Mode">
<icon></icon>
<text>Select Screen Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Screen Blending Mode</toolTip>
<iconText>Select Screen Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Dodge Blending Mode">
<icon></icon>
<text>Select Color Dodge Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Dodge Blending Mode</toolTip>
<iconText>Select Color Dodge Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Dodge Blending Mode">
<icon></icon>
<text>Select Linear Dodge Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Dodge Blending Mode</toolTip>
<iconText>Select Linear Dodge Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+W</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Overlay Blending Mode">
<icon></icon>
<text>Select Overlay Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Overlay Blending Mode</toolTip>
<iconText>Select Overlay Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Overlay Blending Mode">
<icon></icon>
<text>Select Hard Overlay Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Overlay Blending Mode</toolTip>
<iconText>Select Hard Overlay Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Soft Light Blending Mode">
<icon></icon>
<text>Select Soft Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Soft Light Blending Mode</toolTip>
<iconText>Select Soft Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Light Blending Mode">
<icon></icon>
<text>Select Hard Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Light Blending Mode</toolTip>
<iconText>Select Hard Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+H</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Vivid Light Blending Mode">
<icon></icon>
<text>Select Vivid Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Vivid Light Blending Mode</toolTip>
<iconText>Select Vivid Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Light Blending Mode">
<icon></icon>
<text>Select Linear Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Light Blending Mode</toolTip>
<iconText>Select Linear Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Pin Light Blending Mode">
<icon></icon>
<text>Select Pin Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Pin Light Blending Mode</toolTip>
<iconText>Select Pin Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Mix Blending Mode">
<icon></icon>
<text>Select Hard Mix Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Mix Blending Mode</toolTip>
<iconText>Select Hard Mix Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Difference Blending Mode">
<icon></icon>
<text>Select Difference Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Difference Blending Mode</toolTip>
<iconText>Select Difference Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Exclusion Blending Mode">
<icon></icon>
<text>Select Exclusion Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Exclusion Blending Mode</toolTip>
<iconText>Select Exclusion Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hue Blending Mode">
<icon></icon>
<text>Select Hue Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hue Blending Mode</toolTip>
<iconText>Select Hue Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Saturation Blending Mode">
<icon></icon>
<text>Select Saturation Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Saturation Blending Mode</toolTip>
<iconText>Select Saturation Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Blending Mode">
<icon></icon>
<text>Select Color Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Blending Mode</toolTip>
<iconText>Select Color Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Luminosity Blending Mode">
<icon></icon>
<text>Select Luminosity Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Luminosity Blending Mode</toolTip>
<iconText>Select Luminosity Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Y</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Animation">
<text>Animation</text>
<Action name="previous_frame">
<icon></icon>
<text>Previous frame</text>
<whatsThis></whatsThis>
<toolTip>Move to previous frame</toolTip>
<iconText>Move to previous frame</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_frame">
<icon></icon>
<text>Next frame</text>
<whatsThis></whatsThis>
<toolTip>Move to next frame</toolTip>
<iconText>Move to next frame</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_playback">
<icon></icon>
<text>Play / pause animation</text>
<whatsThis></whatsThis>
<toolTip>Play / pause animation</toolTip>
<iconText>Play / pause animation</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_blank_frame">
<icon>addblankframe</icon>
<text>Create Blank Frame</text>
<whatsThis></whatsThis>
<toolTip>Add blank frame</toolTip>
<iconText>Add blank frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_duplicate_frame">
<icon>addduplicateframe</icon>
<text>Create Duplicate Frame</text>
<whatsThis></whatsThis>
<toolTip>Add duplicate frame</toolTip>
<iconText>Add duplicate frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_onion_skin">
<icon></icon>
<text>Toggle onion skin</text>
<whatsThis></whatsThis>
<toolTip>Toggle onion skin</toolTip>
<iconText>Toggle onion skin</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_keyframe">
<icon></icon>
<text>Previous Keyframe</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_keyframe">
<icon></icon>
<text>Next Keyframe</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="first_frame">
<icon></icon>
<text>First Frame</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="last_frame">
<icon></icon>
<text>Last Frame</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="lazy_frame">
<icon></icon>
<text>Auto Frame Mode</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="drop_frames">
<icon></icon>
<text></text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_in_timeline">
<icon></icon>
<text>Show in Timeline</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_keyframe_left">
<icon></icon>
<text>Insert Keyframe Left</text>
<whatsThis></whatsThis>
<toolTip>Insert keyframes to the left of selection, moving the tail of animation to the right.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_keyframe_right">
<icon></icon>
<text>Insert Keyframe Right</text>
<whatsThis></whatsThis>
<toolTip>Insert keyframes to the right of selection, moving the tail of animation to the right.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_keyframes">
<icon></icon>
<text>Insert Multiple Keyframes</text>
<whatsThis></whatsThis>
<toolTip>Insert several keyframes based on user parameters.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_frames_and_pull">
<icon></icon>
<text>Remove Frame and Pull</text>
<whatsThis></whatsThis>
<toolTip>Remove keyframes moving the tail of animation to the left</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_frames">
<icon>deletekeyframe</icon>
<text>Remove Keyframe</text>
<whatsThis></whatsThis>
<toolTip>Remove keyframes without moving anything around</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_column_left">
<icon></icon>
<text>Insert Column Left</text>
<whatsThis></whatsThis>
<toolTip>Insert column to the left of selection, moving the tail of animation to the right</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_column_right">
<icon></icon>
<text>Insert Column Right</text>
<whatsThis></whatsThis>
<toolTip>Insert column to the right of selection, moving the tail of animation to the right</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_columns">
<icon></icon>
<text>Insert Multiple Columns</text>
<whatsThis></whatsThis>
<toolTip>Insert several columns based on user parameters.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_columns_and_pull">
<icon></icon>
<text>Remove Column and Pull</text>
<whatsThis></whatsThis>
<toolTip>Remove columns moving the tail of animation to the left</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_columns">
<icon></icon>
<text>Remove Column</text>
<whatsThis></whatsThis>
<toolTip>Remove columns without moving anything around</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_hold_frame">
<icon></icon>
<text>Insert Hold Frame</text>
<whatsThis></whatsThis>
<toolTip>Insert a hold frame after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_hold_frames">
<icon></icon>
<text>Insert Multiple Hold Frames</text>
<whatsThis></whatsThis>
<toolTip>Insert N hold frames after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_hold_frame">
<icon></icon>
<text>Remove Hold Frame</text>
<whatsThis></whatsThis>
<toolTip>Remove a hold frame after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_multiple_hold_frames">
<icon></icon>
<text>Remove Multiple Hold Frames</text>
<whatsThis></whatsThis>
<toolTip>Remove N hold frames after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_hold_column">
<icon></icon>
<text>Insert Hold Column</text>
<whatsThis></whatsThis>
<toolTip>Insert a hold column into the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_hold_columns">
<icon></icon>
<text>Insert Multiple Hold Columns</text>
<whatsThis></whatsThis>
<toolTip>Insert N hold columns into the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_hold_column">
<icon></icon>
<text>Remove Hold Column</text>
<whatsThis></whatsThis>
<toolTip>Remove a hold column from the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_multiple_hold_columns">
<icon></icon>
<text>Remove Multiple Hold Columns</text>
<whatsThis></whatsThis>
<toolTip>Remove N hold columns from the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_frames">
<icon></icon>
<text>Mirror Frames</text>
<whatsThis></whatsThis>
<toolTip>Mirror frames' position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_columns">
<icon></icon>
<text>Mirror Columns</text>
<whatsThis></whatsThis>
<toolTip>Mirror columns' position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_frames_to_clipboard">
<icon></icon>
<text>Copy to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Copy frames to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_frames_to_clipboard">
<icon></icon>
<text>Cut to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Cut frames to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_frames_from_clipboard">
<icon></icon>
<text>Paste from Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Paste frames from clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_columns_to_clipboard">
<icon></icon>
<text>Copy Columns to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Copy columns to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_columns_to_clipboard">
<icon></icon>
<text>Cut Columns to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Cut columns to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_columns_from_clipboard">
<icon></icon>
<text>Paste Columns from Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Paste columns from clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_start_time">
<icon></icon>
<text>Set Start Time</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_end_time">
<icon></icon>
<text>Set End Time</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="update_playback_range">
<icon></icon>
<text>Update Playback Range</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Layers">
<text>Layers</text>
<Action name="activateNextLayer">
<icon></icon>
<text>Activate next layer</text>
<whatsThis></whatsThis>
<toolTip>Activate next layer</toolTip>
<iconText>Activate next layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>PgUp</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="activatePreviousLayer">
<icon></icon>
<text>Activate previous layer</text>
<whatsThis></whatsThis>
<toolTip>Activate previous layer</toolTip>
<iconText>Activate previous layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>PgDown</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="switchToPreviouslyActiveNode">
<icon></icon>
<text>Activate previously selected layer</text>
<whatsThis></whatsThis>
<toolTip>Activate previously selected layer</toolTip>
<iconText>Activate previously selected layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>;</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_group_layer">
<icon>groupLayer</icon>
<text>&amp;Group Layer</text>
<whatsThis></whatsThis>
<toolTip>Group Layer</toolTip>
<iconText>Group Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_clone_layer">
<icon>cloneLayer</icon>
<text>&amp;Clone Layer</text>
<whatsThis></whatsThis>
<toolTip>Clone Layer</toolTip>
<iconText>Clone Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_shape_layer">
<icon>vectorLayer</icon>
<text>&amp;Vector Layer</text>
<whatsThis></whatsThis>
<toolTip>Vector Layer</toolTip>
<iconText>Vector Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_adjustment_layer">
<icon>filterLayer</icon>
<text>&amp;Filter Layer...</text>
<whatsThis></whatsThis>
<toolTip>Filter Layer</toolTip>
<iconText>Filter Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_fill_layer">
<icon>fillLayer</icon>
<text>&amp;Fill Layer...</text>
<whatsThis></whatsThis>
<toolTip>Fill Layer</toolTip>
<iconText>Fill Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_file_layer">
<icon>fileLayer</icon>
<text>&amp;File Layer...</text>
<whatsThis></whatsThis>
<toolTip>File Layer</toolTip>
<iconText>File Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_transparency_mask">
<icon>transparencyMask</icon>
<text>&amp;Transparency Mask</text>
<whatsThis></whatsThis>
<toolTip>Transparency Mask</toolTip>
<iconText>Transparency Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_filter_mask">
<icon>filterMask</icon>
<text>&amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>Filter Mask</toolTip>
<iconText>Filter Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_colorize_mask">
<icon>filterMask</icon>
<text>&amp;Colorize Mask</text>
<whatsThis></whatsThis>
<toolTip>Colorize Mask</toolTip>
<iconText>Colorize Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_transform_mask">
<icon>transformMask</icon>
<text>&amp;Transform Mask...</text>
<whatsThis></whatsThis>
<toolTip>Transform Mask</toolTip>
<iconText>Transform Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_selection_mask">
<icon>selectionMask</icon>
<text>&amp;Local Selection</text>
<whatsThis></whatsThis>
<toolTip>Local Selection</toolTip>
<iconText>Local Selection</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="isolate_layer">
<icon>view-filter</icon>
<text>&amp;Isolate Layer</text>
<whatsThis></whatsThis>
<toolTip>Isolate Layer</toolTip>
<iconText>Isolate Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_lock">
<icon>layer-locked</icon>
<text>&amp;Toggle layer lock</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer lock</toolTip>
<iconText>Toggle layer lock</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_visibility">
<icon>visible</icon>
<text>Toggle layer &amp;visibility</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer visibility</toolTip>
<iconText>Toggle layer visibility</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_alpha_lock">
<icon>transparency-locked</icon>
<text>Toggle layer &amp;alpha</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer alpha</toolTip>
<iconText>Toggle layer alpha</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_inherit_alpha">
<icon>transparency-enabled</icon>
<text>Toggle layer alpha &amp;inheritance</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer alpha inheritance</toolTip>
<iconText>Toggle layer alpha inheritance</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_paint_layer">
<icon>paintLayer</icon>
<text>&amp;Paint Layer</text>
<whatsThis></whatsThis>
<toolTip>Paint Layer</toolTip>
<iconText>Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Insert</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="new_from_visible">
<icon></icon>
<text>&amp;New Layer From Visible</text>
<whatsThis></whatsThis>
<toolTip>New layer from visible</toolTip>
<iconText>New layer from visible</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="duplicatelayer">
<icon>duplicatelayer</icon>
<text>&amp;Duplicate Layer or Mask</text>
<whatsThis></whatsThis>
<toolTip>Duplicate Layer or Mask</toolTip>
<iconText>Duplicate Layer or Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_selection_to_new_layer">
<icon></icon>
<text>&amp;Cut Selection to New Layer</text>
<whatsThis></whatsThis>
<toolTip>Cut Selection to New Layer</toolTip>
<iconText>Cut Selection to New Layer</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Shift+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_selection_to_new_layer">
<icon></icon>
<text>Copy &amp;Selection to New Layer</text>
<whatsThis></whatsThis>
<toolTip>Copy Selection to New Layer</toolTip>
<iconText>Copy Selection to New Layer</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_layer_clipboard">
<icon></icon>
<text>Copy Layer</text>
<whatsThis></whatsThis>
<toolTip>Copy layer to clipboard</toolTip>
<iconText>Copy layer to clipboard</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_layer_clipboard">
<icon></icon>
<text>Cut Layer</text>
<whatsThis></whatsThis>
<toolTip>Cut layer to clipboard</toolTip>
<iconText>Cut layer to clipboard</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_layer_from_clipboard">
<icon></icon>
<text>Paste Layer</text>
<whatsThis></whatsThis>
<toolTip>Paste layer from clipboard</toolTip>
<iconText>Paste layer from clipboard</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_quick_group">
<icon></icon>
<text>Quick Group</text>
<whatsThis></whatsThis>
<toolTip>Create a group layer containing selected layers</toolTip>
<iconText>Quick Group</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="quick_ungroup">
<icon></icon>
<text>Quick Ungroup</text>
<whatsThis></whatsThis>
<toolTip>Remove grouping of the layers or remove one layer out of the group</toolTip>
<iconText>Quick Ungroup</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_quick_clipping_group">
<icon></icon>
<text>Quick Clipping Group</text>
<whatsThis></whatsThis>
<toolTip>Group selected layers and add a layer with clipped alpha channel</toolTip>
<iconText>Quick Clipping Group</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_all_layers">
<icon></icon>
<text>All Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all layers</toolTip>
<iconText>Select all layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_visible_layers">
<icon></icon>
<text>Visible Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all visible layers</toolTip>
<iconText>Select all visible layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_locked_layers">
<icon></icon>
<text>Locked Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all locked layers</toolTip>
<iconText>Select all locked layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_invisible_layers">
<icon></icon>
<text>Invisible Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all invisible layers</toolTip>
<iconText>Select all invisible layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_unlocked_layers">
<icon></icon>
<text>Unlocked Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all unlocked layers</toolTip>
<iconText>Select all unlocked layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_node_as_image">
<icon>document-save</icon>
<text>&amp;Save Layer/Mask...</text>
<whatsThis></whatsThis>
<toolTip>Save Layer/Mask</toolTip>
<iconText>Save Layer/Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_vector_node_to_svg">
<icon>document-save</icon>
<text>Save Vector Layer as SVG...</text>
<whatsThis></whatsThis>
<toolTip>Save Vector Layer as SVG</toolTip>
<iconText>Save Vector Layer as SVG</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_groups_as_images">
<icon>document-save</icon>
<text>Save &amp;Group Layers...</text>
<whatsThis></whatsThis>
<toolTip>Save Group Layers</toolTip>
<iconText>Save Group Layers</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_group_to_animated">
<icon></icon>
<text>Convert group to &amp;animated layer</text>
<whatsThis></whatsThis>
<toolTip>Convert child layers into animation frames</toolTip>
<iconText>Convert child layers into animation frames</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_file_layer">
<icon>fileLayer</icon>
<text>to &amp;File Layer</text>
<whatsThis></whatsThis>
<toolTip>Saves out the layers into a new image and then references that image.</toolTip>
<iconText>Convert to File Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_from_file">
<icon></icon>
<text>I&amp;mport Layer...</text>
<whatsThis></whatsThis>
<toolTip>Import Layer</toolTip>
<iconText>Import Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_paint_layer">
<icon>paintLayer</icon>
<text>&amp;as Paint Layer...</text>
<whatsThis></whatsThis>
<toolTip>as Paint Layer</toolTip>
<iconText>as Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_transparency_mask">
<icon>transparencyMask</icon>
<text>as &amp;Transparency Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Transparency Mask</toolTip>
<iconText>as Transparency Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_filter_mask">
<icon>filterMask</icon>
<text>as &amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Filter Mask</toolTip>
<iconText>as Filter Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_selection_mask">
<icon>selectionMask</icon>
<text>as &amp;Selection Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Selection Mask</toolTip>
<iconText>as Selection Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_paint_layer">
<icon>paintLayer</icon>
<text>to &amp;Paint Layer</text>
<whatsThis></whatsThis>
<toolTip>to Paint Layer</toolTip>
<iconText>to Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_transparency_mask">
<icon>transparencyMask</icon>
<text>to &amp;Transparency Mask</text>
<whatsThis></whatsThis>
<toolTip>to Transparency Mask</toolTip>
<iconText>to Transparency Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_filter_mask">
<icon>filterMask</icon>
<text>to &amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>to Filter Mask</toolTip>
<iconText>to Filter Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_selection_mask">
<icon>selectionMask</icon>
<text>to &amp;Selection Mask</text>
<whatsThis></whatsThis>
<toolTip>to Selection Mask</toolTip>
<iconText>to Selection Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_into_mask">
<icon>transparencyMask</icon>
<text>&amp;Alpha into Mask</text>
<whatsThis></whatsThis>
<toolTip>Alpha into Mask</toolTip>
<iconText>Alpha into Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>10</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_write">
<icon>transparency-enabled</icon>
<text>&amp;Write as Alpha</text>
<whatsThis></whatsThis>
<toolTip>Write as Alpha</toolTip>
<iconText>Write as Alpha</iconText>
<activationFlags>1000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_save_merged">
<icon>document-save</icon>
<text>&amp;Save Merged...</text>
<whatsThis></whatsThis>
<toolTip>Save Merged</toolTip>
<iconText>Save Merged</iconText>
<activationFlags>1000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layersplit">
<icon>split-layer</icon>
<text>Split Layer...</text>
<whatsThis></whatsThis>
<toolTip>Split Layer</toolTip>
<iconText>Split Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="waveletdecompose">
<icon></icon>
<text>Wavelet Decompose ...</text>
<whatsThis></whatsThis>
<toolTip>Wavelet Decompose</toolTip>
<iconText>Wavelet Decompose</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorNodeX">
<icon>symmetry-horizontal</icon>
<text>Mirror Layer Hori&amp;zontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror Layer Horizontally</toolTip>
<iconText>Mirror Layer Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorNodeY">
<icon>symmetry-vertical</icon>
<text>Mirror Layer &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror Layer Vertically</toolTip>
<iconText>Mirror Layer Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotatelayer">
<icon></icon>
<text>&amp;Rotate Layer...</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer</toolTip>
<iconText>Rotate Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayerCW90">
<icon>object-rotate-right</icon>
<text>Rotate &amp;Layer 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 90° to the Right</toolTip>
<iconText>Rotate Layer 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayerCCW90">
<icon>object-rotate-left</icon>
<text>Rotate Layer &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 90° to the Left</toolTip>
<iconText>Rotate Layer 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayer180">
<icon></icon>
<text>Rotate Layer &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 180°</toolTip>
<iconText>Rotate Layer 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layersize">
<icon></icon>
<text>Scale &amp;Layer to new Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale Layer to new Size</toolTip>
<iconText>Scale Layer to new Size</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearlayer">
<icon></icon>
<text>&amp;Shear Layer...</text>
<whatsThis></whatsThis>
<toolTip>Shear Layer</toolTip>
<iconText>Shear Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorAllNodesX">
<icon>symmetry-horizontal</icon>
<text>Mirror All Layers Hori&amp;zontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror All Layers Horizontally</toolTip>
<iconText>Mirror All Layers Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorAllNodesY">
<icon>symmetry-vertical</icon>
<text>Mirror All Layers &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror All Layers Vertically</toolTip>
<iconText>Mirror All Layers Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayers">
<icon></icon>
<text>&amp;Rotate All Layers...</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers</toolTip>
<iconText>Rotate All Layers</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayersCW90">
<icon>object-rotate-right</icon>
<text>Rotate All &amp;Layers 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers 90° to the Right</toolTip>
<iconText>Rotate All Layers 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayersCCW90">
<icon>object-rotate-left</icon>
<text>Rotate All Layers &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers 90° to the Left</toolTip>
<iconText>Rotate All Layers 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayers180">
<icon></icon>
<text>Rotate All Layers &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers 180°</toolTip>
<iconText>Rotate All Layers 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="scaleAllLayers">
<icon></icon>
<text>Scale All &amp;Layers to new Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale All Layers to new Size</toolTip>
<iconText>Scale All Layers to new Size</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearAllLayers">
<icon></icon>
<text>&amp;Shear All Layers...</text>
<whatsThis></whatsThis>
<toolTip>Shear All Layers</toolTip>
<iconText>Shear All Layers</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="offsetlayer">
<icon></icon>
<text>&amp;Offset Layer...</text>
<whatsThis></whatsThis>
<toolTip>Offset Layer</toolTip>
<iconText>Offset Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="clones_array">
<icon></icon>
<text>Clones &amp;Array...</text>
<whatsThis></whatsThis>
<toolTip>Clones Array</toolTip>
<iconText>Clones Array</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="EditLayerMetaData">
<icon></icon>
<text>&amp;Edit metadata...</text>
<whatsThis></whatsThis>
<toolTip>Edit metadata</toolTip>
<iconText>Edit metadata</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="histogram">
<icon></icon>
<text>&amp;Histogram...</text>
<whatsThis></whatsThis>
<toolTip>Histogram</toolTip>
<iconText>Histogram</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layercolorspaceconversion">
<icon></icon>
<text>&amp;Convert Layer Color Space...</text>
<whatsThis></whatsThis>
<toolTip>Convert Layer Color Space</toolTip>
<iconText>Convert Layer Color Space</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="merge_layer">
<icon>merge-layer-below</icon>
<text>&amp;Merge with Layer Below</text>
<whatsThis></whatsThis>
<toolTip>Merge with Layer Below</toolTip>
<iconText>Merge with Layer Below</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="flatten_layer">
<icon></icon>
<text>&amp;Flatten Layer</text>
<whatsThis></whatsThis>
<toolTip>Flatten Layer</toolTip>
<iconText>Flatten Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rasterize_layer">
<icon></icon>
<text>Ras&amp;terize Layer</text>
<whatsThis></whatsThis>
<toolTip>Rasterize Layer</toolTip>
<iconText>Rasterize Layer</iconText>
<activationFlags>10000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="flatten_image">
<icon></icon>
<text>Flatten ima&amp;ge</text>
<whatsThis></whatsThis>
<toolTip>Flatten image</toolTip>
<iconText>Flatten image</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layer_style">
<icon></icon>
<text>La&amp;yer Style...</text>
<whatsThis></whatsThis>
<toolTip>Layer Style</toolTip>
<iconText>Layer Style</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="LayerGroupSwitcher/previous">
<icon></icon>
<text>Move into previous group</text>
<whatsThis></whatsThis>
<toolTip>Move into previous group</toolTip>
<iconText>Move into previous group</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="LayerGroupSwitcher/next">
<icon></icon>
<text>Move into next group</text>
<whatsThis></whatsThis>
<toolTip>Move into next group</toolTip>
<iconText>Move into next group</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="RenameCurrentLayer">
<icon></icon>
<text>Rename current layer</text>
<whatsThis></whatsThis>
<toolTip>Rename current layer</toolTip>
<iconText>Rename current layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>F2</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_layer">
<icon>deletelayer</icon>
<text>&amp;Remove Layer</text>
<whatsThis></whatsThis>
<toolTip>Remove Layer</toolTip>
<iconText>Remove Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Shift+Delete</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="move_layer_up">
<icon>arrowupblr</icon>
<text>Move Layer or Mask Up</text>
<whatsThis></whatsThis>
<toolTip>Move Layer or Mask Up</toolTip>
<iconText></iconText>
<shortcut>Ctrl+PgUp</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="move_layer_down">
<icon>arrowdown</icon>
<text>Move Layer or Mask Down</text>
<whatsThis></whatsThis>
<toolTip>Move Layer or Mask Down</toolTip>
<iconText></iconText>
<shortcut>Ctrl+PgDown</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layer_properties">
<icon>properties</icon>
<text>&amp;Properties...</text>
<whatsThis></whatsThis>
<toolTip>Properties</toolTip>
<iconText>Properties</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>F3</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
+ <Action name="set-copy-from">
+ <icon></icon>
+ <text>Set Copy F&amp;rom...</text>
+ <whatsThis></whatsThis>
+ <toolTip>Set the source for the selected clone layer(s).</toolTip>
+ <iconText>Set Copy From</iconText>
+ <activationFlags>1000</activationFlags>
+ <activationConditions>1</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
</Actions>
</ActionCollection>
diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui
index 80aa7f525c..c0f035efad 100644
--- a/krita/krita4.xmlgui
+++ b/krita/krita4.xmlgui
@@ -1,405 +1,406 @@
<?xml version="1.0"?>
<kpartgui xmlns="http://www.kde.org/standards/kxmlgui/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Krita"
-version="421"
+version="430"
xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0 http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd">
<MenuBar>
<Menu name="file">
<text>&amp;File</text>
<Action name="file_new"/>
<Action name="file_open"/>
<Action name="file_open_recent"/>
<Separator/>
<Action name="file_save"/>
<Action name="file_save_as"/>
<Action name="file_reload_file"/>
<Separator/>
<Action name="file_sessions"/>
<Separator/>
<Action name="file_import_file"/>
<Action name="file_export_file"/>
<Separator/>
<Action name="file_export_pdf"/>
<Separator/>
<Action name="file_import_animation"/>
<Action name="render_animation"/>
<Separator/>
<Action name="save_incremental_version"/>
<Action name="save_incremental_backup"/>
<Separator/>
<Action name="create_template"/>
<Action name="create_copy"/>
<!--Separator/>
<Action name="file_print"/>
<Action name="file_print_preview"/-->
<Separator/>
<Action name="file_documentinfo"/>
<Separator/>
<Action name="file_close"/>
<Action name="file_close_all"/>
<Action name="file_quit"/>
</Menu>
<Menu name="edit">
<text>&amp;Edit</text>
<Action name="edit_undo"/>
<Action name="edit_redo"/>
<Separator/>
<Action name="edit_cut"/>
<Action name="edit_copy"/>
<Action name="cut_sharp"/>
<Action name="copy_sharp"/>
<Action name="copy_merged"/>
<Action name="edit_paste"/>
<Action name="paste_at"/>
<Action name="paste_new"/>
+ <Action name="paste_as_reference"/>
<Action name="clear"/>
<Action name="fill_selection_foreground_color"/>
<Action name="fill_selection_background_color"/>
<Action name="fill_selection_pattern"/>
<Menu name="fill_special">
<text>Fill Special</text>
<Action name="fill_selection_foreground_color_opacity"/>
<Action name="fill_selection_background_color_opacity"/>
<Action name="fill_selection_pattern_opacity"/>
</Menu>
<Action name="stroke_shapes"/>
<Action name="stroke_selection"/>
<Action name="delete"/>
<Separator/>
<Action name="revert"/>
</Menu>
<Menu name="view">
<text>&amp;View</text>
<Action name="view_show_canvas_only"/>
<Action name="fullscreen"/>
<Action name="wrap_around_mode"/>
<Action name="level_of_detail_mode"/>
<Action name="softProof"/>
<Action name="gamutCheck"/>
<Separator/>
<Menu name="Canvas">
<text>&amp;Canvas</text>
<Action name="mirror_canvas"/>
<Separator/>
<Action name="zoom_to_100pct" />
<Action name="rotate_canvas_right" />
<Action name="rotate_canvas_left" />
<Action name="reset_canvas_rotation" />
<!-- TODO: Something is not right with the way zoom actions are hooked up. These are in the KoZoomController.
It seems they are not being properly placed in the view manager since the MDI changes were implemented
-->
<Action name="view_zoom_in"/>
<Action name="view_zoom_out"/>
</Menu>
<!-- TODO: None of these actions are showing. There names must have been changed to something with the MDI changes?...
<Action name="actual_pixels"/>
<Action name="actual_size"/>
<Action name="fit_to_canvas"/>
-->
<Separator/>
<Action name="view_ruler"/>
<Action name="rulers_track_mouse"/>
<Action name="view_show_guides"/>
<Action name="view_lock_guides"/>
<Action name="showStatusBar" />
<Separator/>
<Action name="view_grid"/>
<Action name="view_pixel_grid"/>
<Separator/>
<Menu name="SnapTo">
<text>&amp;Snap To</text>
<Action name="view_snap_to_guides"/>
<Action name="view_snap_to_grid"/>
<Action name="view_snap_to_pixel"/>
<Action name="view_snap_orthogonal" />
<Action name="view_snap_node" />
<Action name="view_snap_extension" />
<Action name="view_snap_intersection" />
<Action name="view_snap_bounding_box" />
<Action name="view_snap_image_bounds" />
<Action name="view_snap_image_center" />
</Menu>
<Separator/>
<Action name="view_toggle_painting_assistants"/>
<Action name="view_toggle_assistant_previews"/>
<Action name="view_toggle_reference_images"/>
<Separator/>
<Action name="view_palette_action_menu"/>
<Separator/>
<Action name="refresh_canvas"/>
</Menu>
<Menu name="Image">
<text>&amp;Image</text>
<Action name="image_properties"/>
<Action name="image_color"/>
<Action name="imagecolorspaceconversion"/>
<Action name="duplicate_image"/>
<Separator/>
<Action name="trim_to_image"/>
<Action name="resizeimagetolayer"/>
<Action name="resizeimagetoselection"/>
<Separator/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotateimage"/>
<Separator/>
<Action name="rotateImageCW90"/>
<Action name="rotateImageCCW90"/>
<Action name="rotateImage180"/>
</Menu>
<Action name="shearimage"/>
<Separator/>
<Action name="mirrorImageHorizontal"/>
<Action name="mirrorImageVertical"/>
<Separator/>
<Action name="imagesize"/>
<Action name="offsetimage"/>
<Action name="imageresolution"/>
<Action name="canvassize"/>
<Separator/>
<Action name="imagesplit"/>
<Action name="waveletdecompose"/>
<Action name="separate"/>
</Menu>
<Menu name="Layer">
<text>&amp;Layer</text>
<Action name="cut_layer_clipboard"/>
<Action name="copy_layer_clipboard"/>
<Action name="paste_layer_from_clipboard"/>
<Separator/>
<Menu name="LayerNew">
<text>New</text>
<Action name="add_new_paint_layer"/>
<Action name="new_from_visible"/>
<Action name="duplicatelayer"/>
<Separator/>
<Action name="cut_selection_to_new_layer"/>
<Action name="copy_selection_to_new_layer"/>
</Menu>
<Menu name="LayerImportExport">
<text>&amp;Import/Export</text>
<Action name="save_node_as_image"/>
<Action name="save_vector_node_to_svg"/>
<Action name="save_groups_as_images"/>
<Separator/>
<Action name="import_layer_from_file"/>
<Menu name="LayerImportAs">
<text>Import</text>
<Action name="import_layer_as_paint_layer"/>
<Action name="import_layer_as_transparency_mask"/>
<Action name="import_layer_as_filter_mask"/>
<Action name="import_layer_as_selection_mask"/>
</Menu>
</Menu>
<Menu name="LayerConvert">
<text>&amp;Convert</text>
<Action name="convert_to_paint_layer"/>
<Action name="convert_to_transparency_mask"/>
<Action name="convert_to_filter_mask"/>
<Action name="convert_to_selection_mask"/>
<Action name="convert_to_file_layer"/>
<Action name="convert_group_to_animated"/>
<Action name="layercolorspaceconversion"/>
</Menu>
<Separator/>
<Menu name="LayerSelect">
<text>&amp;Select</text>
<Action name="select_all_layers"/>
<Action name="select_visible_layers"/>
<Action name="select_invisible_layers"/>
<Action name="select_locked_layers"/>
<Action name="select_unlocked_layers"/>
</Menu>
<Menu name="LayerGroup">
<text>&amp;Group</text>
<Action name="create_quick_group"/>
<Action name="create_quick_clipping_group"/>
<Action name="quick_ungroup"/>
</Menu>
<Menu name="LayerTransform">
<text>&amp;Transform</text>
<Action name="mirrorNodeX"/>
<Action name="mirrorNodeY"/>
<Action name="layersize"/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotatelayer"/>
<Separator/>
<Action name="rotateLayerCW90"/>
<Action name="rotateLayerCCW90"/>
<Action name="rotateLayer180"/>
</Menu>
<Action name="shearlayer"/>
<Action name="offsetlayer"/>
</Menu>
<Menu name="LayerTransformAll">
<text>Transform &amp;All Layers</text>
<Action name="mirrorAllNodesX"/>
<Action name="mirrorAllNodesY"/>
<Action name="scaleAllLayers"/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotateAllLayers"/>
<Separator/>
<Action name="rotateAllLayersCW90"/>
<Action name="rotateAllLayersCCW90"/>
<Action name="rotateAllLayers180"/>
</Menu>
<Action name="shearAllLayers"/>
</Menu>
<Menu name="LayerSplitAlpha">
<text>S&amp;plit</text>
<Menu name="LayerSplitAlpha">
<text>S&amp;plit Alpha</text>
<Action name="split_alpha_into_mask"/>
<Action name="split_alpha_write"/>
<Action name="split_alpha_save_merged"/>
</Menu>
<Action name="layersplit"/>
<Action name="clones_array"/>
</Menu>
<Separator/>
<Action name="EditLayerMetaData"/>
<Action name="histogram"/>
<Separator/>
<Action name="merge_layer"/>
<Action name="flatten_layer"/>
<Action name="rasterize_layer"/>
<Action name="merge_all_shape_layers"/>
<Action name="flatten_image"/>
<Action name="merge_selected_layers"/>
<Separator/>
<Action name="layer_style"/>
</Menu>
<Menu name="Select">
<text>&amp;Select</text>
<Action name="select_all"/>
<Action name="deselect"/>
<Action name="reselect"/>
<Action name="invert_selection"/>
<Separator/>
<Action name="edit_selection"/>
<Action name="convert_to_vector_selection"/>
<Action name="convert_to_raster_selection"/>
<Action name="convert_shapes_to_vector_selection"/>
<Action name="convert_selection_to_shape"/>
<Separator/>
<Action name="feather"/>
<Action name="similar"/>
<Separator/>
<Action name="toggle_display_selection"/>
<Action name="show-global-selection-mask"/>
<Action name="selectionscale"/>
<Separator/>
<Action name="colorrange"/>
<Menu name="selectopaquemenu">
<text>Select &amp;Opaque</text>
<Action name="selectopaque"/>
<Separator/>
<Action name="selectopaque_add"/>
<Action name="selectopaque_subtract"/>
<Action name="selectopaque_intersect"/>
</Menu>
<Separator/>
<Action name="featherselection"/>
<Action name="growselection"/>
<Action name="shrinkselection"/>
<Action name="borderselection"/>
<Action name="smoothselection"/>
</Menu>
<Menu name="Filter">
<text>Filte&amp;r</text>
<Action name="filter_apply_again"/>
<Action name="filter_gallery"/>
<Separator/>
<Action name="adjust_filters"/>
<Action name="artistic_filters"/>
<Action name="blur_filters"/>
<Action name="color_filters"/>
<Action name="decor_filters"/>
<Action name="edge_filters"/>
<Action name="enhance_filters"/>
<Action name="emboss_filters"/>
<Action name="map_filters"/>
<Action name="nonphotorealistic_filters"/>
<Action name="other_filters"/>
<Separator/>
<Action name="QMic"/>
<Action name="QMicAgain"/>
</Menu>
<Menu name="tools">
<text>&amp;Tools</text>
<Menu name="scripts"><text>Scripts</text></Menu>
</Menu>
<Menu name="settings">
<text>Setti&amp;ngs</text>
<Action name="options_configure"/>
<Action name="manage_bundles"/>
<Separator/>
<Action name="options_configure_toolbars"/>
<Merge name="StandardToolBarMenuHandler" />
<Separator/>
<Action name="view_toggledockers"/>
<Action name="settings_dockers_menu"/>
<Separator/>
<Action name="theme_menu"/>
<Separator/>
<!-- `Configure Shortcuts` was moved into main configuration menu -->
<!-- <Action name="options_configure_keybinding"/> -->
<Separator/>
<Action name="switch_application_language"/>
<Action name="settings_active_author"/>
<Separator/>
</Menu>
<Action name="window"/>
<Separator/>
<Menu name="help">
<text>&amp;Help</text>
<Action name="help_contents"/>
<Action name="help_whats_this"/>
<Separator/>
<MergeLocal/>
<Action name="help_show_tip"/>
<Separator/>
<Action name="help_report_bug"/>
<Action name="buginfo"/>
<Separator/>
<Action name="help_about_app"/>
<Action name="help_about_kde"/>
</Menu>
</MenuBar>
<ToolBar name="mainToolBar" fullWidth="false" noMerge="1">
<Text>File</Text>
<Action name="file_new"/>
<Action name="file_open"/>
<Action name="file_save"/>
<Separator/>
<Action name="edit_undo"/>
<Action name="edit_redo"/>
</ToolBar>
<ToolBar name="BrushesAndStuff" position="top">
<Text>Brushes and Stuff</Text>
<Action name="gradients"/>
<Action name="patterns"/>
<Separator/>
<Action name="dual"/>
<Separator/>
<Action name="paintops"/>
<Action name="paintop_options"/>
<Action name="composite_actions"/>
<Action name="brushslider1"/>
<Action name="brushslider2"/>
<Separator/>
<Action name="mirror_actions"/>
<Action name="expanding_spacer_1"/>
<Action name="select_layout"/>
<Action name="workspaces"/>
</ToolBar>
</kpartgui>
diff --git a/krita/kritamenu.action b/krita/kritamenu.action
index 3aa7b9886e..5036540026 100644
--- a/krita/kritamenu.action
+++ b/krita/kritamenu.action
@@ -1,1817 +1,1829 @@
<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Menu">
<Actions category="File">
<text>File</text>
<Action name="file_new">
<icon>document-new</icon>
<text>&amp;New</text>
<whatsThis></whatsThis>
<toolTip>Create new document</toolTip>
<iconText>New</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_open">
<icon>document-open</icon>
<text>&amp;Open...</text>
<whatsThis></whatsThis>
<toolTip>Open an existing document</toolTip>
<iconText>Open</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_open_recent">
<icon>document-open-recent</icon>
<text>Open &amp;Recent</text>
<whatsThis></whatsThis>
<toolTip>Open a document which was recently opened</toolTip>
<iconText>Open Recent</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_save">
<icon>document-save</icon>
<text>&amp;Save</text>
<whatsThis></whatsThis>
<toolTip>Save</toolTip>
<iconText>Save</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_save_as">
<icon>document-save-as</icon>
<text>Save &amp;As...</text>
<whatsThis></whatsThis>
<toolTip>Save document under a new name</toolTip>
<iconText>Save As</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<!-- commented out in the code right now
<Action name="file_reload_file">
<icon></icon>
<text>Reload</text>
<whatsThis></whatsThis>
<toolTip>Reload</toolTip>
<iconText>Reload</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
-->
<Action name="file_sessions">
<icon></icon>
<text>Sessions...</text>
<whatsThis></whatsThis>
<toolTip>Open session manager</toolTip>
<iconText>Sessions</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_import_file">
<icon>document-import</icon>
<text>Open ex&amp;isting Document as Untitled Document...</text>
<whatsThis></whatsThis>
<toolTip>Open existing Document as Untitled Document</toolTip>
<iconText>Open existing Document as Untitled Document</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_export_file">
<icon>document-export</icon>
<text>E&amp;xport...</text>
<whatsThis></whatsThis>
<toolTip>Export</toolTip>
<iconText>Export</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_import_animation">
<icon></icon>
<text>Import animation frames...</text>
<whatsThis></whatsThis>
<toolTip>Import animation frames</toolTip>
<iconText>Import animation frames</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="render_animation">
<icon></icon>
<text>&amp;Render Animation...</text>
<whatsThis></whatsThis>
<toolTip>Render Animation to GIF, Image Sequence or Video</toolTip>
<iconText>Render Animation</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="render_animation_again">
<icon></icon>
<text>&amp;Render Animation Again</text>
<whatsThis></whatsThis>
<toolTip>Render Animation Again</toolTip>
<iconText>Render Animation</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_incremental_version">
<icon></icon>
<text>Save Incremental &amp;Version</text>
<whatsThis></whatsThis>
<toolTip>Save Incremental Version</toolTip>
<iconText>Save Incremental Version</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_incremental_backup">
<icon></icon>
<text>Save Incremental &amp;Backup</text>
<whatsThis></whatsThis>
<toolTip>Save Incremental Backup</toolTip>
<iconText>Save Incremental Backup</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>F4</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_template">
<icon></icon>
<text>&amp;Create Template From Image...</text>
<whatsThis></whatsThis>
<toolTip>Create Template From Image</toolTip>
<iconText>Create Template From Image</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_copy">
<icon></icon>
<text>Create Copy &amp;From Current Image</text>
<whatsThis></whatsThis>
<toolTip>Create Copy From Current Image</toolTip>
<iconText>Create Copy From Current Image</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_print">
<icon>document-print</icon>
<text>&amp;Print...</text>
<whatsThis></whatsThis>
<toolTip>Print document</toolTip>
<iconText>Print</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_print_preview">
<icon>document-print-preview</icon>
<text>Print Previe&amp;w</text>
<whatsThis></whatsThis>
<toolTip>Show a print preview of document</toolTip>
<iconText>Print Preview</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_documentinfo">
<icon>configure</icon>
<text>&amp;Document Information</text>
<whatsThis></whatsThis>
<toolTip>Document Information</toolTip>
<iconText>Document Information</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_close_all">
<icon></icon>
<text>&amp;Close All</text>
<whatsThis></whatsThis>
<toolTip>Close All</toolTip>
<iconText>Close All</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+W</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_close">
<icon></icon>
<text>C&amp;lose</text>
<whatsThis></whatsThis>
<toolTip>Close</toolTip>
<iconText>Close</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+W</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_quit">
<icon></icon>
<text>&amp;Quit</text>
<whatsThis></whatsThis>
<toolTip>Quit application</toolTip>
<iconText>Quit</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Edit">
<text>Edit</text>
<Action name="edit_undo">
<icon>edit-undo</icon>
<text>Undo</text>
<whatsThis></whatsThis>
<toolTip>Undo last action</toolTip>
<iconText>Undo</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_redo">
<icon>edit-redo</icon>
<text>Redo</text>
<whatsThis></whatsThis>
<toolTip>Redo last undone action</toolTip>
<iconText>Redo</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_cut">
<icon>edit-cut</icon>
<text>Cu&amp;t</text>
<whatsThis></whatsThis>
<toolTip>Cut selection to clipboard</toolTip>
<iconText>Cut</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_copy">
<icon>edit-copy</icon>
<text>&amp;Copy</text>
<whatsThis></whatsThis>
<toolTip>Copy selection to clipboard</toolTip>
<iconText>Copy</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_sharp">
<icon></icon>
<text>C&amp;opy (sharp)</text>
<whatsThis></whatsThis>
<toolTip>Copy (sharp)</toolTip>
<iconText>Copy (sharp)</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_sharp">
<icon></icon>
<text>Cut (&amp;sharp)</text>
<whatsThis></whatsThis>
<toolTip>Cut (sharp)</toolTip>
<iconText>Cut (sharp)</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_merged">
<icon></icon>
<text>Copy &amp;merged</text>
<whatsThis></whatsThis>
<toolTip>Copy merged</toolTip>
<iconText>Copy merged</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_paste">
<icon>edit-paste</icon>
<text>&amp;Paste</text>
<whatsThis></whatsThis>
<toolTip>Paste clipboard content</toolTip>
<iconText>Paste</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_at">
<icon></icon>
<text>Paste at Cursor</text>
<whatsThis></whatsThis>
<toolTip>Paste at cursor</toolTip>
<iconText>Paste at cursor</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_new">
<icon></icon>
<text>Paste into &amp;New Image</text>
<whatsThis></whatsThis>
<toolTip>Paste into New Image</toolTip>
<iconText>Paste into New Image</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
+ <Action name="paste_as_reference">
+ <icon></icon>
+ <text>Paste as R&amp;eference Image</text>
+ <whatsThis></whatsThis>
+ <toolTip>Paste as Reference Image</toolTip>
+ <iconText>Paste as Reference Image</iconText>
+ <activationFlags>1</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut>Ctrl+Shift+R</shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
<Action name="clear">
<icon>edit-clear</icon>
<text>C&amp;lear</text>
<whatsThis></whatsThis>
<toolTip>Clear</toolTip>
<iconText>Clear</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Del</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_foreground_color">
<icon></icon>
<text>&amp;Fill with Foreground Color</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color</toolTip>
<iconText>Fill with Foreground Color</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color">
<icon></icon>
<text>Fill &amp;with Background Color</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color</toolTip>
<iconText>Fill with Background Color</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern">
<icon></icon>
<text>F&amp;ill with Pattern</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern</toolTip>
<iconText>Fill with Pattern</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Actions category="EditFill">
<text>Fill Special</text>
<Action name="fill_selection_foreground_color_opacity">
<icon></icon>
<text>Fill with Foreground Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color (Opacity)</toolTip>
<iconText>Fill with Foreground Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color_opacity">
<icon></icon>
<text>Fill with Background Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color (Opacity)</toolTip>
<iconText>Fill with Background Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern_opacity">
<icon></icon>
<text>Fill with Pattern (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern (Opacity)</toolTip>
<iconText>Fill with Pattern (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Action name="stroke_shapes">
<icon></icon>
<text>Stro&amp;ke selected shapes</text>
<whatsThis></whatsThis>
<toolTip>Stroke selected shapes</toolTip>
<iconText>Stroke selected shapes</iconText>
<activationFlags>1000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="stroke_selection">
<icon></icon>
<text>Stroke Selec&amp;tion...</text>
<whatsThis></whatsThis>
<toolTip>Stroke selection</toolTip>
<iconText>Stroke selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="delete_keyframe">
<icon></icon>
<text>Delete keyframe</text>
<whatsThis></whatsThis>
<toolTip>Delete keyframe</toolTip>
<iconText>Delete keyframe</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Window">
<text>Window</text>
<Action name="view_newwindow">
<icon>window-new</icon>
<text>&amp;New Window</text>
<whatsThis></whatsThis>
<toolTip>New Window</toolTip>
<iconText>New Window</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_next">
<icon></icon>
<text>N&amp;ext</text>
<whatsThis></whatsThis>
<toolTip>Next</toolTip>
<iconText>Next</iconText>
<activationFlags>10</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_previous">
<icon></icon>
<text>Previous</text>
<whatsThis></whatsThis>
<toolTip>Previous</toolTip>
<iconText>Previous</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="View">
<text>View</text>
<Action name="view_show_canvas_only">
<icon>document-new</icon>
<text>&amp;Show Canvas Only</text>
<whatsThis></whatsThis>
<toolTip>Show just the canvas or the whole window</toolTip>
<iconText>Show Canvas Only</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Tab</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fullscreen">
<icon>view-fullscreen</icon>
<text>F&amp;ull Screen Mode</text>
<whatsThis></whatsThis>
<toolTip>Display the window in full screen</toolTip>
<iconText>Full Screen Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+F</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="wrap_around_mode">
<icon></icon>
<text>&amp;Wrap Around Mode</text>
<whatsThis></whatsThis>
<toolTip>Wrap Around Mode</toolTip>
<iconText>Wrap Around Mode</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="level_of_detail_mode">
<icon></icon>
<text>&amp;Instant Preview Mode</text>
<whatsThis></whatsThis>
<toolTip>Instant Preview Mode</toolTip>
<iconText>Instant Preview Mode</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Shift+L</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="softProof">
<icon></icon>
<text>Soft Proofing</text>
<whatsThis></whatsThis>
<toolTip>Turns on Soft Proofing</toolTip>
<iconText>Turns on Soft Proofing</iconText>
<shortcut>Ctrl+Y</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="gamutCheck">
<icon></icon>
<text>Out of Gamut Warnings</text>
<whatsThis></whatsThis>
<toolTip>Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.</toolTip>
<iconText>Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.</iconText>
<shortcut>Ctrl+Shift+Y</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_canvas">
<icon>mirror-view</icon>
<text>Mirror View</text>
<whatsThis></whatsThis>
<toolTip>Mirror View</toolTip>
<iconText>Mirror View</iconText>
<shortcut>M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="zoom_to_100pct">
<icon>zoom-original</icon>
<text>&amp;Reset zoom</text>
<whatsThis></whatsThis>
<toolTip>Reset zoom</toolTip>
<iconText>Reset zoom</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+0</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_zoom_in">
<icon>zoom-in</icon>
<text>Zoom &amp;In</text>
<whatsThis></whatsThis>
<toolTip>Zoom In</toolTip>
<iconText></iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl++</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_zoom_out">
<icon>zoom-out</icon>
<text>Zoom &amp;Out</text>
<whatsThis></whatsThis>
<toolTip>Zoom Out</toolTip>
<iconText></iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+-</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotate_canvas_right">
<icon>rotate-canvas-right</icon>
<text>Rotate &amp;Canvas Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Canvas Right</toolTip>
<iconText>Rotate Canvas Right</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+]</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotate_canvas_left">
<icon>rotate-canvas-left</icon>
<text>Rotate Canvas &amp;Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Canvas Left</toolTip>
<iconText>Rotate Canvas Left</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+[</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reset_canvas_rotation">
<icon>rotation-reset</icon>
<text>Reset Canvas Rotation</text>
<whatsThis></whatsThis>
<toolTip>Reset Canvas Rotation</toolTip>
<iconText>Reset Canvas Rotation</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_ruler">
<icon></icon>
<text>Show &amp;Rulers</text>
<whatsThis>The rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. &lt;p>Uncheck this to hide the rulers.&lt;/p></whatsThis>
<toolTip>Show Rulers</toolTip>
<iconText>Show Rulers</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rulers_track_mouse">
<icon></icon>
<text>Rulers Track Pointer</text>
<whatsThis>The rulers will track current mouse position and show it on screen. It can cause suptle performance slowdown</whatsThis>
<toolTip>Rulers Track Pointer</toolTip>
<iconText>Rulers Track Pointer</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_show_guides">
<icon></icon>
<text>Show Guides</text>
<whatsThis></whatsThis>
<toolTip>Show or hide guides</toolTip>
<iconText>Show Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_lock_guides">
<icon></icon>
<text>Lock Guides</text>
<whatsThis></whatsThis>
<toolTip>Lock or unlock guides</toolTip>
<iconText>Lock Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_to_guides">
<icon></icon>
<text>Snap to Guides</text>
<whatsThis></whatsThis>
<toolTip>Snap cursor to guides position</toolTip>
<iconText>Snap to Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="showStatusBar">
<icon></icon>
<text>Show Status &amp;Bar</text>
<whatsThis></whatsThis>
<toolTip>Show or hide the status bar</toolTip>
<iconText>Show Status Bar</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_pixel_grid">
<icon></icon>
<text>Show Pixel Grid</text>
<whatsThis></whatsThis>
<toolTip>Show Pixel Grid</toolTip>
<iconText>Show Pixel Grid</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1000</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_grid">
<icon>view-grid</icon>
<text>Show &amp;Grid</text>
<whatsThis></whatsThis>
<toolTip>Show Grid</toolTip>
<iconText>Show Grid</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+'</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_to_grid">
<icon></icon>
<text>Snap To Grid</text>
<whatsThis></whatsThis>
<toolTip>Snap To Grid</toolTip>
<iconText>Snap To Grid</iconText>
<activationFlags>1000</activationFlags>
<shortcut>Ctrl+Shift+;</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_snap_options_popup">
<icon></icon>
<text>Show Snap Options Popup</text>
<whatsThis></whatsThis>
<toolTip>Show Snap Options Popup</toolTip>
<iconText>Show Snap Options Popup</iconText>
<activationFlags>1000</activationFlags>
<shortcut>Shift+s</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_orthogonal">
<icon></icon>
<text>Snap Orthogonal</text>
<whatsThis></whatsThis>
<toolTip>Snap Orthogonal</toolTip>
<iconText>Snap Orthogonal</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_node">
<icon></icon>
<text>Snap Node</text>
<whatsThis></whatsThis>
<toolTip>Snap Node</toolTip>
<iconText>Snap Node</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_extension">
<icon></icon>
<text>Snap Extension</text>
<whatsThis></whatsThis>
<toolTip>Snap Extension</toolTip>
<iconText>Snap Extension</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_to_pixel">
<icon></icon>
<text>Snap Pixel</text>
<whatsThis></whatsThis>
<toolTip>Snap Pixel</toolTip>
<iconText>Snap Pixel</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_intersection">
<icon></icon>
<text>Snap Intersection</text>
<whatsThis></whatsThis>
<toolTip>Snap Intersection</toolTip>
<iconText>Snap Intersection</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_bounding_box">
<icon></icon>
<text>Snap Bounding Box</text>
<whatsThis></whatsThis>
<toolTip>Snap Bounding Box</toolTip>
<iconText>Snap Bounding Box</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_image_bounds">
<icon></icon>
<text>Snap Image Bounds</text>
<whatsThis></whatsThis>
<toolTip>Snap Image Bounds</toolTip>
<iconText>Snap Image Bounds</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_image_center">
<icon></icon>
<text>Snap Image Center</text>
<whatsThis></whatsThis>
<toolTip>Snap Image Center</toolTip>
<iconText>Snap Image Center</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggle_painting_assistants">
<icon></icon>
<text>S&amp;how Painting Assistants</text>
<whatsThis></whatsThis>
<toolTip>Show Painting Assistants</toolTip>
<iconText>Show Painting Assistants</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggle_assistant_previews">
<icon></icon>
<text>Show &amp;Assistant Previews</text>
<whatsThis></whatsThis>
<toolTip>Show Assistant Previews</toolTip>
<iconText>Show Assistant Previews</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggle_reference_images">
<icon></icon>
<text>S&amp;how Reference Images</text>
<whatsThis></whatsThis>
<toolTip>Show Reference Images</toolTip>
<iconText>Show Reference Images</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Image">
<text>Image</text>
<Action name="image_properties">
<icon>document-properties</icon>
<text>&amp;Properties...</text>
<whatsThis></whatsThis>
<toolTip>Properties</toolTip>
<iconText>Properties</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="image_color">
<icon>format-stroke-color</icon>
<text>&amp;Image Background Color and Transparency...</text>
<whatsThis></whatsThis>
<toolTip>Change the background color of the image</toolTip>
<iconText>Image Background Color and Transparency</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagecolorspaceconversion">
<icon></icon>
<text>&amp;Convert Image Color Space...</text>
<whatsThis></whatsThis>
<toolTip>Convert Image Color Space</toolTip>
<iconText>Convert Image Color Space</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="trim_to_image">
<icon>trim-to-image</icon>
<text>&amp;Trim to Image Size</text>
<whatsThis></whatsThis>
<toolTip>Trim to Image Size</toolTip>
<iconText>Trim to Image Size</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="resizeimagetolayer">
<icon></icon>
<text>Trim to Current &amp;Layer</text>
<whatsThis></whatsThis>
<toolTip>Trim to Current Layer</toolTip>
<iconText>Trim to Current Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="resizeimagetoselection">
<icon></icon>
<text>Trim to S&amp;election</text>
<whatsThis></whatsThis>
<toolTip>Trim to Selection</toolTip>
<iconText>Trim to Selection</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateimage">
<icon></icon>
<text>&amp;Rotate Image...</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image</toolTip>
<iconText>Rotate Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImageCW90">
<icon>object-rotate-right</icon>
<text>Rotate &amp;Image 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 90° to the Right</toolTip>
<iconText>Rotate Image 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImageCCW90">
<icon>object-rotate-left</icon>
<text>Rotate Image &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 90° to the Left</toolTip>
<iconText>Rotate Image 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImage180">
<icon></icon>
<text>Rotate Image &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 180°</toolTip>
<iconText>Rotate Image 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearimage">
<icon></icon>
<text>&amp;Shear Image...</text>
<whatsThis></whatsThis>
<toolTip>Shear Image</toolTip>
<iconText>Shear Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorImageHorizontal">
<icon>symmetry-horizontal</icon>
<text>&amp;Mirror Image Horizontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror Image Horizontally</toolTip>
<iconText>Mirror Image Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorImageVertical">
<icon>symmetry-vertical</icon>
<text>Mirror Image &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror Image Vertically</toolTip>
<iconText>Mirror Image Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagesize">
<icon></icon>
<text>Scale Image To &amp;New Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale Image To New Size</toolTip>
<iconText>Scale Image To New Size</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="offsetimage">
<icon></icon>
<text>&amp;Offset Image...</text>
<whatsThis></whatsThis>
<toolTip>Offset Image</toolTip>
<iconText>Offset Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="canvassize">
<icon></icon>
<text>R&amp;esize Canvas...</text>
<whatsThis></whatsThis>
<toolTip>Resize Canvas</toolTip>
<iconText>Resize Canvas</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagesplit">
<icon></icon>
<text>Im&amp;age Split </text>
<whatsThis></whatsThis>
<toolTip>Image Split</toolTip>
<iconText>Image Split</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="separate">
<icon></icon>
<text>Separate Ima&amp;ge...</text>
<whatsThis></whatsThis>
<toolTip>Separate Image</toolTip>
<iconText>Separate Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Select">
<text>Select</text>
<Action name="select_all">
<icon>edit-select-all</icon>
<text>Select &amp;All</text>
<whatsThis></whatsThis>
<toolTip>Select All</toolTip>
<iconText>Select All</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="deselect">
<icon>edit-select-all</icon>
<text>&amp;Deselect</text>
<whatsThis></whatsThis>
<toolTip>Deselect</toolTip>
<iconText>Deselect</iconText>
<activationFlags>1100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reselect">
<icon></icon>
<text>&amp;Reselect</text>
<whatsThis></whatsThis>
<toolTip>Reselect</toolTip>
<iconText>Reselect</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_vector_selection">
<icon></icon>
<text>&amp;Convert to Vector Selection</text>
<whatsThis></whatsThis>
<toolTip>Convert to Vector Selection</toolTip>
<iconText>Convert to Vector Selection</iconText>
<activationFlags>100000000000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_raster_selection">
<icon></icon>
<text>&amp;Convert to Raster Selection</text>
<whatsThis></whatsThis>
<toolTip>Convert to Raster Selection</toolTip>
<iconText>Convert to Raster Selection</iconText>
<activationFlags>10000000000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_selection">
<icon></icon>
<text>Edit Selection</text>
<whatsThis></whatsThis>
<toolTip>Edit Selection</toolTip>
<iconText>Edit Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_shapes_to_vector_selection">
<icon></icon>
<text>Convert Shapes to &amp;Vector Selection</text>
<whatsThis></whatsThis>
<toolTip>Convert Shapes to Vector Selection</toolTip>
<iconText>Convert Shapes to Vector Selection</iconText>
<activationFlags>1000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="featherselection">
<icon></icon>
<text>&amp;Feather Selection...</text>
<whatsThis></whatsThis>
<toolTip>Feather Selection</toolTip>
<iconText>Feather Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut>Shift+F6</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_display_selection">
<icon></icon>
<text>Dis&amp;play Selection</text>
<whatsThis></whatsThis>
<toolTip>Display Selection</toolTip>
<iconText>Display Selection</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+H</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectionscale">
<icon></icon>
<text>Sca&amp;le...</text>
<whatsThis></whatsThis>
<toolTip>Scale</toolTip>
<iconText>Scale</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="colorrange">
<icon></icon>
<text>S&amp;elect from Color Range...</text>
<whatsThis></whatsThis>
<toolTip>Select from Color Range</toolTip>
<iconText>Select from Color Range</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectopaque">
<icon></icon>
<text>Select &amp;Opaque (Replace)</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque</toolTip>
<iconText>Select Opaque</iconText>
- <activationFlags>10000</activationFlags>
+ <activationFlags>1</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectopaque_add">
<icon></icon>
<text>Select Opaque (&amp;Add)</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque (Add)</toolTip>
<iconText>Select Opaque (Add)</iconText>
- <activationFlags>10000</activationFlags>
+ <activationFlags>1</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectopaque_subtract">
<icon></icon>
<text>Select Opaque (&amp;Subtract)</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque (Subtract)</toolTip>
<iconText>Select Opaque (Subtract)</iconText>
- <activationFlags>10000</activationFlags>
+ <activationFlags>1</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectopaque_intersect">
<icon></icon>
<text>Select Opaque (&amp;Intersect)</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque (Intersect)</toolTip>
<iconText>Select Opaque (Intersect)</iconText>
- <activationFlags>10000</activationFlags>
+ <activationFlags>1</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="growselection">
<icon></icon>
<text>&amp;Grow Selection...</text>
<whatsThis></whatsThis>
<toolTip>Grow Selection</toolTip>
<iconText>Grow Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shrinkselection">
<icon></icon>
<text>S&amp;hrink Selection...</text>
<whatsThis></whatsThis>
<toolTip>Shrink Selection</toolTip>
<iconText>Shrink Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="borderselection">
<icon></icon>
<text>&amp;Border Selection...</text>
<whatsThis></whatsThis>
<toolTip>Border Selection</toolTip>
<iconText>Border Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="smoothselection">
<icon></icon>
<text>S&amp;mooth</text>
<whatsThis></whatsThis>
<toolTip>Smooth</toolTip>
<iconText>Smooth</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Filter">
<text>Filter</text>
<Action name="filter_apply_again">
<icon></icon>
<text>&amp;Apply Filter Again</text>
<whatsThis></whatsThis>
<toolTip>Apply Filter Again</toolTip>
<iconText>Apply Filter Again</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="adjust_filters">
<icon></icon>
<text>Adjust</text>
<whatsThis></whatsThis>
<toolTip>Adjust</toolTip>
<iconText>Adjust</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="artistic_filters">
<icon></icon>
<text>Artistic</text>
<whatsThis></whatsThis>
<toolTip>Artistic</toolTip>
<iconText>Artistic</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="blur_filters">
<icon></icon>
<text>Blur</text>
<whatsThis></whatsThis>
<toolTip>Blur</toolTip>
<iconText>Blur</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="color_filters">
<icon></icon>
<text>Colors</text>
<whatsThis></whatsThis>
<toolTip>Colors</toolTip>
<iconText>Colors</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edge_filters">
<icon></icon>
<text>Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Edge Detection</toolTip>
<iconText>Edge Detection</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="enhance_filters">
<icon></icon>
<text>Enhance</text>
<whatsThis></whatsThis>
<toolTip>Enhance</toolTip>
<iconText>Enhance</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="emboss_filters">
<icon></icon>
<text>Emboss</text>
<whatsThis></whatsThis>
<toolTip>Emboss</toolTip>
<iconText>Emboss</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="map_filters">
<icon></icon>
<text>Map</text>
<whatsThis></whatsThis>
<toolTip>Map</toolTip>
<iconText>Map</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="other_filters">
<icon></icon>
<text>Other</text>
<whatsThis></whatsThis>
<toolTip>Other</toolTip>
<iconText>Other</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="QMic">
<icon>gmic</icon>
<text>Start G'MIC-Qt</text>
<whatsThis></whatsThis>
<toolTip>Start G'Mic-Qt</toolTip>
<iconText>Start G'Mic-Qt</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="QMicAgain">
<icon>gmic</icon>
<text>Re-apply the last G'MIC filter</text>
<whatsThis></whatsThis>
<toolTip>Apply the last G'Mic-Qt action again</toolTip>
<iconText>Apply the last G'Mic-Qt action again</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Settings">
<text>Settings</text>
<Action name="options_configure">
<icon>configure</icon>
<text>&amp;Configure Krita...</text>
<whatsThis></whatsThis>
<toolTip>Configure Krita</toolTip>
<iconText>Configure Krita</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="manage_bundles">
<icon></icon>
<text>&amp;Manage Resources...</text>
<whatsThis></whatsThis>
<toolTip>Manage Resources</toolTip>
<iconText>Manage Resources</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="switch_application_language">
<icon>preferences-desktop-locale</icon>
<text>Switch Application &amp;Language...</text>
<whatsThis></whatsThis>
<toolTip>Switch Application Language</toolTip>
<iconText>Switch Application Language</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggledockers">
<icon></icon>
<text>&amp;Show Dockers</text>
<whatsThis></whatsThis>
<toolTip>Show Dockers</toolTip>
<iconText>Show Dockers</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="options_configure_toolbars">
<icon>configure</icon>
<text>Configure Tool&amp;bars...</text>
<whatsThis></whatsThis>
<toolTip>Configure Toolbars</toolTip>
<iconText>Configure Toolbars</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="settings_dockers_menu">
<icon></icon>
<text>Dockers</text>
<whatsThis></whatsThis>
<toolTip>Dockers</toolTip>
<iconText>Dockers</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="theme_menu">
<icon></icon>
<text>&amp;Themes</text>
<whatsThis></whatsThis>
<toolTip>Themes</toolTip>
<iconText>Themes</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="settings_active_author">
<icon>im-user</icon>
<text>Active Author Profile</text>
<whatsThis></whatsThis>
<toolTip>Active Author Profile</toolTip>
<iconText>Active Author Profile</iconText>
<shortcut></shortcut>
<isCheckable></isCheckable>
<statusTip></statusTip>
</Action>
<Action name="options_configure_keybinding">
<icon>configure-shortcuts</icon>
<text>Configure S&amp;hortcuts...</text>
<whatsThis></whatsThis>
<toolTip>Configure Shortcuts</toolTip>
<iconText>Configure Shortcuts</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="window">
<icon></icon>
<text>&amp;Window</text>
<whatsThis></whatsThis>
<toolTip>Window</toolTip>
<iconText>Window</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Help">
<text>Help</text>
<Action name="help_contents">
<icon>help-contents</icon>
<text>Krita &amp;Handbook</text>
<whatsThis></whatsThis>
<toolTip>Krita Handbook</toolTip>
<iconText>Krita Handbook</iconText>
<shortcut>F1</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_report_bug">
<icon>tools-report-bug</icon>
<text>&amp;Report Bug...</text>
<whatsThis></whatsThis>
<toolTip>Report Bug</toolTip>
<iconText>Report Bug</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_about_app">
<icon>calligrakrita</icon>
<text>&amp;About Krita</text>
<whatsThis></whatsThis>
<toolTip>About Krita</toolTip>
<iconText>About Krita</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_about_kde">
<icon>kde</icon>
<text>About &amp;KDE</text>
<whatsThis></whatsThis>
<toolTip>About KDE</toolTip>
<iconText>About KDE</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="BrushesAndStuff">
<text>Brushes and Stuff</text>
<Action name="gradients">
<icon></icon>
<text>&amp;Gradients</text>
<whatsThis></whatsThis>
<toolTip>Gradients</toolTip>
<iconText>Gradients</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="patterns">
<icon></icon>
<text>&amp;Patterns</text>
<whatsThis></whatsThis>
<toolTip>Patterns</toolTip>
<iconText>Patterns</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="dual">
<icon></icon>
<text>&amp;Color</text>
<whatsThis></whatsThis>
<toolTip>Color</toolTip>
<iconText>Color</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paintops">
<icon></icon>
<text>&amp;Painter's Tools</text>
<whatsThis></whatsThis>
<toolTip>Painter's Tools</toolTip>
<iconText>Painter's Tools</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="composite_actions">
<icon></icon>
<text>Brush composite</text>
<whatsThis></whatsThis>
<toolTip>Brush composite</toolTip>
<iconText>Brush composite</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider1">
<icon></icon>
<text>Brush option slider 1</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 1</toolTip>
<iconText>Brush option slider 1</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider2">
<icon></icon>
<text>Brush option slider 2</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 2</toolTip>
<iconText>Brush option slider 2</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider3">
<icon></icon>
<text>Brush option slider 3</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 3</toolTip>
<iconText>Brush option slider 3</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_actions">
<icon></icon>
<text>Mirror</text>
<whatsThis></whatsThis>
<toolTip>Mirror</toolTip>
<iconText>Mirror</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_layout">
<icon></icon>
<text>Layouts</text>
<whatsThis></whatsThis>
<toolTip>Select layout</toolTip>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="workspaces">
<icon></icon>
<text>Workspaces</text>
<whatsThis></whatsThis>
<toolTip>Workspaces</toolTip>
<iconText>Workspaces</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
diff --git a/krita/main.cc b/krita/main.cc
index 737b2a4472..5b977b2950 100644
--- a/krita/main.cc
+++ b/krita/main.cc
@@ -1,597 +1,599 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
#include <QString>
#include <QPixmap>
#include <kis_debug.h>
#include <QProcess>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <QDir>
#include <QDate>
#include <QLocale>
#include <QSettings>
#include <QByteArray>
#include <QMessageBox>
#include <QThread>
#if QT_VERSION >= 0x050900
#include <QOperatingSystemVersion>
#endif
#include <time.h>
#include <KisApplication.h>
#include <KoConfig.h>
#include <KoResourcePaths.h>
#include <kis_config.h>
#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 <opengl/kis_opengl.h>
#include "input/KisQtWidgetsTweaker.h"
#include <KisUsageLogger.h>
#include <kis_image_config.h>
#ifdef Q_OS_ANDROID
#include <QtAndroid>
#endif
#if defined Q_OS_WIN
#include "config_use_qt_tablet_windows.h"
#include <windows.h>
#ifndef USE_QT_TABLET_WINDOWS
#include <kis_tablet_support_win.h>
#include <kis_tablet_support_win8.h>
#else
#include <dialogs/KisDlgCustomTabletResolution.h>
#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 <QtPlatformHeaders/QWindowsWindowFunctions>
#endif
#include <QLibrary>
#endif
#if defined HAVE_KCRASH
#include <kcrash.h>
#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<BOOL (APIENTRY *)(const char *)>(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<pSetDisplayAutoRotationPreferences_t>(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*/)
{
KisPart *kisPart = KisPart::instance();
QList<QPointer<KisDocument>> list = kisPart->documents();
for (auto 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
__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);
#if QT_VERSION >= 0x050900
QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true);
#endif
#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();
- const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString();
- preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString);
-
#ifdef Q_OS_WIN
// Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP
// might get weird crashes atm.
+ const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString();
+ preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString);
qputenv("QT_ANGLE_PLATFORM", "d3d11");
+#else
+ const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString();
+ preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString);
#endif
const QSurfaceFormat format =
KisOpenGL::selectSurfaceFormat(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug);
if (format.renderableType() == QSurfaceFormat::OpenGLES) {
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
} else {
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true);
}
KisOpenGL::setDefaultSurfaceFormat(format);
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);
#ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT
if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) {
QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true);
}
#endif
KisUsageLogger::writeHeader();
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;
#if QT_VERSION >= 0x050900
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);
}
}
}
#endif
#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
if (!app.start(args)) {
return 1;
}
#if QT_VERSION >= 0x050700
app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false);
#endif
// Set up remote arguments.
QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)),
&app, SLOT(remoteArguments(QByteArray,QObject*)));
QObject::connect(&app, SIGNAL(fileOpenRequest(QString)),
&app, SLOT(fileOpenRequested(QString)));
// 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()));
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 1f6c2b2c4c..a07a6a1f1a 100644
--- a/krita/org.kde.krita.appdata.xml
+++ b/krita/org.kde.krita.appdata.xml
@@ -1,277 +1,338 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop">
<id>org.kde.krita</id>
<launchable type="desktop-id">org.kde.krita.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license>
<developer_name>Krita Foundation</developer_name>
<developer_name xml:lang="ca">Fundació Krita</developer_name>
<developer_name xml:lang="ca-valencia">Fundació Krita</developer_name>
<developer_name xml:lang="cs">Krita Foundation</developer_name>
<developer_name xml:lang="de">Krita Foundation</developer_name>
<developer_name xml:lang="en-GB">Krita Foundation</developer_name>
<developer_name xml:lang="es">Fundación Krita</developer_name>
<developer_name xml:lang="eu">Krita Fundazioa</developer_name>
<developer_name xml:lang="fi">Krita Foundation</developer_name>
<developer_name xml:lang="fr">La Fondation Krita</developer_name>
<developer_name xml:lang="gl">Fundación Krita</developer_name>
<developer_name xml:lang="id">Asas Krita</developer_name>
<developer_name xml:lang="it">Fondazione Krita</developer_name>
<developer_name xml:lang="ko">Krita Foundation</developer_name>
<developer_name xml:lang="nl">Krita Foundation</developer_name>
<developer_name xml:lang="nn">Krita Foundation</developer_name>
<developer_name xml:lang="pl">Fundacja Krity</developer_name>
<developer_name xml:lang="pt">Fundação do Krita</developer_name>
<developer_name xml:lang="pt-BR">Krita Foundation</developer_name>
<developer_name xml:lang="sv">Krita-stiftelsen</developer_name>
<developer_name xml:lang="tr">Krita Vakfı</developer_name>
<developer_name xml:lang="uk">Фундація Krita</developer_name>
<developer_name xml:lang="x-test">xxKrita Foundationxx</developer_name>
<developer_name xml:lang="zh-CN">Krita 基金会</developer_name>
<developer_name xml:lang="zh-TW">Krita 基金會</developer_name>
<update_contact>foundation@krita.org</update_contact>
<name>Krita</name>
<name xml:lang="ar">كريتا</name>
<name xml:lang="ca">Krita</name>
<name xml:lang="ca-valencia">Krita</name>
<name xml:lang="cs">Krita</name>
<name xml:lang="de">Krita</name>
<name xml:lang="el">Krita</name>
<name xml:lang="en-GB">Krita</name>
<name xml:lang="es">Krita</name>
<name xml:lang="eu">Krita</name>
<name xml:lang="fi">Krita</name>
<name xml:lang="fr">Krita</name>
<name xml:lang="gl">Krita</name>
<name xml:lang="id">Krita</name>
<name xml:lang="it">Krita</name>
<name xml:lang="ko">Krita</name>
<name xml:lang="nl">Krita</name>
<name xml:lang="nn">Krita</name>
<name xml:lang="pl">Krita</name>
<name xml:lang="pt">Krita</name>
<name xml:lang="pt-BR">Krita</name>
<name xml:lang="ru">Krita</name>
<name xml:lang="sk">Krita</name>
<name xml:lang="sv">Krita</name>
<name xml:lang="tr">Krita</name>
<name xml:lang="uk">Krita</name>
<name xml:lang="x-test">xxKritaxx</name>
<name xml:lang="zh-CN">Krita</name>
<name xml:lang="zh-TW">Krita</name>
<summary>Digital Painting, Creative Freedom</summary>
<summary xml:lang="ar">رسم رقميّ، حريّة إبداعيّة</summary>
<summary xml:lang="bs">Digitalno crtanje, kreativna sloboda</summary>
<summary xml:lang="ca">Dibuix digital, Llibertat creativa</summary>
<summary xml:lang="ca-valencia">Dibuix digital, Llibertat creativa</summary>
<summary xml:lang="cs">Digitální malování, svoboda tvorby</summary>
<summary xml:lang="da">Digital tegning, kunstnerisk frihed</summary>
<summary xml:lang="de">Digitales Malen, kreative Freiheit</summary>
<summary xml:lang="el">Ψηφιακή ζωγραφική, δημιουργική ελευθερία</summary>
<summary xml:lang="en-GB">Digital Painting, Creative Freedom</summary>
<summary xml:lang="es">Pintura digital, libertad creativa</summary>
<summary xml:lang="et">Digitaalne joonistamine, loominguline vabadus</summary>
<summary xml:lang="eu">Margolan digitala, sormen askatasuna</summary>
<summary xml:lang="fi">Digitaalimaalaus, luova vapaus</summary>
<summary xml:lang="fr">Peinture numérique, liberté créatrice</summary>
<summary xml:lang="gl">Debuxo dixital, liberdade creativa</summary>
<summary xml:lang="ia">Pictura digital, Libertate creative</summary>
<summary xml:lang="id">Pelukisan Digital, Kebebasan Berkreatif</summary>
<summary xml:lang="it">Pittura digitale, libertà creativa</summary>
<summary xml:lang="ko">디지털 페인팅, 자유로운 창의성</summary>
<summary xml:lang="nl">Digital Painting, Creative Freedom</summary>
<summary xml:lang="nn">Digital teikning – kreativ fridom</summary>
<summary xml:lang="pl">Cyfrowe malowanie, Wolność Twórcza</summary>
<summary xml:lang="pt">Pintura Digital, Liberdade Criativa</summary>
<summary xml:lang="pt-BR">Pintura digital, liberdade criativa</summary>
<summary xml:lang="ru">Цифровое рисование. Творческая свобода</summary>
<summary xml:lang="sk">Digitálne maľovanie, kreatívna sloboda</summary>
<summary xml:lang="sv">Digital målning, kreativ frihet</summary>
<summary xml:lang="tr">Sayısal Boyama, Yaratıcı Özgürlük</summary>
<summary xml:lang="uk">Цифрове малювання, творча свобода</summary>
<summary xml:lang="x-test">xxDigital Painting, Creative Freedomxx</summary>
<summary xml:lang="zh-CN">自由挥洒数字绘画的无限创意</summary>
<summary xml:lang="zh-TW">數位繪畫,創作自由</summary>
<description>
<p>Krita is the full-featured digital art studio.</p>
<p xml:lang="bs">Krita je potpuni digitalni umjetnički studio.</p>
- <p xml:lang="ca">Krita és l'estudi d'art digital ple de funcionalitats.</p>
- <p xml:lang="ca-valencia">Krita és l'estudi d'art digital ple de funcionalitats.</p>
+ <p xml:lang="ca">El Krita és l'estudi d'art digital ple de funcionalitats.</p>
+ <p xml:lang="ca-valencia">El Krita és l'estudi d'art digital ple de funcionalitats.</p>
<p xml:lang="de">Krita ist ein digitales Designstudio mit umfangreichen Funktionen.</p>
<p xml:lang="el">Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.</p>
<p xml:lang="en-GB">Krita is the full-featured digital art studio.</p>
<p xml:lang="es">Krita es un estudio de arte digital completo</p>
<p xml:lang="et">Krita on rohkete võimalustega digitaalkunstistuudio.</p>
<p xml:lang="eu">Krita arte lantegi digital osoa da.</p>
<p xml:lang="fi">Krita on täyspiirteinen digitaiteen ateljee.</p>
<p xml:lang="fr">Krita est le studio d'art numérique complet.</p>
<p xml:lang="gl">Krita é un estudio completo de arte dixital.</p>
<p xml:lang="ia">Krita es le studio de arte digital complete.</p>
<p xml:lang="id">Krita adalah studio seni digital yang penuh dengan fitur.</p>
<p xml:lang="it">Krita è uno studio d'arte digitale completo.</p>
<p xml:lang="ja">Krita は、フル機能を備えたデジタルなアートスタジオです。</p>
<p xml:lang="ko">Krita는 디지털 예술 스튜디오입니다.</p>
<p xml:lang="nl">Krita is de digitale kunststudio vol mogelijkheden.</p>
<p xml:lang="nn">Krita er ei funksjonsrik digital teiknestove.</p>
<p xml:lang="pl">Krita jest pełnowymiarowym, cyfrowym studiem artystycznym</p>
<p xml:lang="pt">O Krita é o estúdio de arte digital completo.</p>
<p xml:lang="pt-BR">O Krita é o estúdio de arte digital completo.</p>
<p xml:lang="ru">Krita — полнофункциональный инструмент для создания цифровой графики.</p>
<p xml:lang="sk">Krita je plne vybavené digitálne umelecké štúdio.</p>
<p xml:lang="sv">Krita är den fullfjädrade digitala konststudion.</p>
<p xml:lang="tr">Krita, tam özellikli dijital sanat stüdyosudur.</p>
<p xml:lang="uk">Krita — повноцінний комплекс для створення цифрових художніх творів.</p>
<p xml:lang="x-test">xxKrita is the full-featured digital art studio.xx</p>
<p xml:lang="zh-CN">Krita 是一款功能齐全的数字绘画工作室软件。</p>
<p xml:lang="zh-TW">Krita 是全功能的數位藝術工作室。</p>
<p>It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.</p>
<p xml:lang="bs">On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima</p>
<p xml:lang="ca">És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.</p>
<p xml:lang="ca-valencia">És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.</p>
<p xml:lang="el">Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.</p>
<p xml:lang="en-GB">It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.</p>
<p xml:lang="es">Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.</p>
<p xml:lang="et">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.</p>
<p xml:lang="eu">Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.</p>
<p xml:lang="fi">Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.</p>
<p xml:lang="fr">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.</p>
<p xml:lang="gl">Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.</p>
<p xml:lang="ia">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.</p>
<p xml:lang="id">Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.</p>
<p xml:lang="it">Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.</p>
<p xml:lang="ko">스케치, 페인팅을 위한 완벽한 도구이며, 생각에서부터 디지털 페인팅 파일을 만들어 낼 수 있는 종합적인 도구를 제공합니다.</p>
<p xml:lang="nl">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.</p>
<p xml:lang="nn">Passar perfekt for både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale målerifiler frå grunnen av.</p>
<p xml:lang="pl">Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.</p>
<p xml:lang="pt">É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.</p>
<p xml:lang="pt-BR">É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.</p>
<p xml:lang="ru">Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.</p>
<p xml:lang="sk">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.</p>
<p xml:lang="sv">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.</p>
<p xml:lang="tr">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.</p>
<p xml:lang="uk">Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.</p>
<p xml:lang="x-test">xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx</p>
<p xml:lang="zh-CN">它专门为数字绘画设计,为美术工作者提供了一个从起草、上色到完成作品等整个创作流程的完整解决方案。</p>
<p xml:lang="zh-TW">它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。</p>
<p>
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.
</p>
<p xml:lang="bs">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.</p>
<p xml:lang="ca">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.</p>
<p xml:lang="ca-valencia">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.</p>
<p xml:lang="el">Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,</p>
<p xml:lang="en-GB">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.</p>
<p xml:lang="es">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.</p>
<p xml:lang="et">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.</p>
<p xml:lang="eu">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.</p>
<p xml:lang="fi">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.</p>
<p xml:lang="fr">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.</p>
<p xml:lang="gl">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.</p>
<p xml:lang="ia">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.</p>
<p xml:lang="id">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.</p>
<p xml:lang="it">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.</p>
<p xml:lang="ja">コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。</p>
<p xml:lang="ko">Krita는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.</p>
<p xml:lang="nl">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.</p>
<p xml:lang="nn">Krita er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, som RGB og CMYK med 8- og 16-bits heiltals- eller flyttalskanalar.</p>
<p xml:lang="pl">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.</p>
<p xml:lang="pt">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.</p>
<p xml:lang="pt-BR">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.</p>
<p xml:lang="ru">Krita — отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.</p>
<p xml:lang="sk">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.</p>
<p xml:lang="sv">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.</p>
<p xml:lang="tr">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.</p>
<p xml:lang="uk">Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.</p>
<p xml:lang="x-test">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</p>
<p xml:lang="zh-CN">Krita 是绘制概念美术、漫画、纹理和电影布景的理想选择。Krita 支持多种色彩空间,如 8 位和 16 位整数及 16 位和 32 位浮点的 RGB 和 CMYK 颜色模型。</p>
<p xml:lang="zh-TW">Krita 是創造概念藝術、漫畫、彩現紋理和場景繪畫的絕佳選擇。Krita 在 8 位元和 16 位元整數色版,以及 16 位元和 32 位元浮點色板中支援 RGB 和 CMYK 等多種色彩空間。</p>
<p>Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.</p>
<p xml:lang="bs">Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.</p>
<p xml:lang="ca">Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.</p>
<p xml:lang="ca-valencia">Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.</p>
<p xml:lang="el">Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.</p>
<p xml:lang="en-GB">Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.</p>
<p xml:lang="es">Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.</p>
<p xml:lang="et">Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.</p>
<p xml:lang="eu">Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.</p>
<p xml:lang="fi">Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.</p>
<p xml:lang="fr">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.</p>
<p xml:lang="gl">Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.</p>
<p xml:lang="ia">Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.</p>
<p xml:lang="id">Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.</p>
<p xml:lang="it">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.</p>
<p xml:lang="ja">Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。</p>
<p xml:lang="ko">Krita의 고급 브러시 엔진, 다양한 필터, 여러 도움이 되는 기능으로 생산성을 즐겁게 향상시킬 수 있습니다.</p>
<p xml:lang="nl">Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.</p>
<p xml:lang="nn">Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.</p>
<p xml:lang="pl">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ą.</p>
<p xml:lang="pt">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.</p>
<p xml:lang="pt-BR">Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.</p>
<p xml:lang="ru">Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.</p>
<p xml:lang="sk">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.</p>
<p xml:lang="sv">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.</p>
<p xml:lang="tr">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.</p>
<p xml:lang="uk">Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.</p>
<p xml:lang="x-test">xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx</p>
<p xml:lang="zh-CN">Krita 具有功能强大的笔刷引擎、种类繁多的滤镜以及便于操作的交互设计,可让你尽情、高效地挥洒无限创意。</p>
<p xml:lang="zh-TW">使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。</p>
</description>
<url type="homepage">https://www.krita.org/</url>
- <url type="faq">https://krita.org/about/faq/</url>
+ <url type="faq">https://docs.krita.org/KritaFAQ.html</url>
<url type="donation">https://krita.org/support-us/donations/</url>
- <url type="help">https://docs.krita.org/Category:Tutorials</url>
+ <url type="help">https://docs.krita.org/</url>
+ <url type="bugtracker">https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html</url>
<screenshots>
<screenshot type="default">
+ <caption>Krita is a full-featured digital painting studio.</caption>
+ <caption xml:lang="ca">Krita és un estudi de pintura digital ple de funcionalitats.</caption>
+ <caption xml:lang="ca-valencia">Krita és un estudi de pintura digital ple de funcionalitats.</caption>
+ <caption xml:lang="es">Krita es un completo estudio de dibujo digital.</caption>
+ <caption xml:lang="id">Krita adalah studio pelukisan digital dengan fitur yang lengkap.</caption>
+ <caption xml:lang="it">Krita è uno studio d'arte digitale completo.</caption>
+ <caption xml:lang="ko">Krita는 다기능 디지털 예술 스튜디오입니다.</caption>
+ <caption xml:lang="nl">Krita is een digitale schilderstudio vol mogelijkheden.</caption>
+ <caption xml:lang="pl">Krita jest pełnowymiarowym, cyfrowym studiem artystycznym.</caption>
+ <caption xml:lang="pt">O Krita é um estúdio de arte digital completo.</caption>
+ <caption xml:lang="sv">Krita är en fullfjädrad digital konststudio.</caption>
+ <caption xml:lang="uk">Krita — повноцінний комплекс для цифрового малювання.</caption>
+ <caption xml:lang="x-test">xxKrita is a full-featured digital painting studio.xx</caption>
+ <caption xml:lang="zh-TW">Krita 是全功能的數位繪圖工作室。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png</image>
</screenshot>
<screenshot type="default">
+ <caption>The startup window now also gives you the latest news about Krita.</caption>
+ <caption xml:lang="ca">La finestra d'inici ara proporciona les darreres noticies quant al Krita.</caption>
+ <caption xml:lang="ca-valencia">La finestra d'inici ara proporciona les darreres noticies quant al Krita.</caption>
+ <caption xml:lang="es">La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita.</caption>
+ <caption xml:lang="id">Window pemulaian kini juga memberikan kamu kabar terbaru tentang Krita.</caption>
+ <caption xml:lang="it">La finestra di avvio ora fornisce anche le ultime novità su Krita.</caption>
+ <caption xml:lang="ko">시작 창에서 Krita의 최신 소식을 볼 수 있습니다.</caption>
+ <caption xml:lang="nl">Het opstartvenster geeft u nu ook you het laatste nieuws over Krita.</caption>
+ <caption xml:lang="pl">Okno początkowe teraz wyświetla wieści o Kricie.</caption>
+ <caption xml:lang="pt">A janela inicial agora também lhe dá as últimas notícias sobre o Krita.</caption>
+ <caption xml:lang="sv">Startfönstret ger nu också senaste nytt om Krita.</caption>
+ <caption xml:lang="uk">У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita.</caption>
+ <caption xml:lang="x-test">xxThe startup window now also gives you the latest news about Krita.xx</caption>
+ <caption xml:lang="zh-TW">開始視窗也提供給您關於 Krita 的最新消息。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png</image>
</screenshot>
<screenshot type="default">
+ <caption>There are over ten immensely powerful brush engines.</caption>
+ <caption xml:lang="ca">Hi ha uns deu motors de pinzell immensament potents.</caption>
+ <caption xml:lang="ca-valencia">Hi ha uns deu motors de pinzell immensament potents.</caption>
+ <caption xml:lang="es">Existen unos diez inmensamente potentes motores de pinceles.</caption>
+ <caption xml:lang="id">Ada lebih dari sepuluh mesin kuas yang sangat manjur.</caption>
+ <caption xml:lang="it">Ci sono oltre dieci motori di pennelli incredibilmente potenti.</caption>
+ <caption xml:lang="ko">10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다.</caption>
+ <caption xml:lang="nl">Er zijn meer dan tien immens krachtige penseelengines.</caption>
+ <caption xml:lang="pl">Istnieje ponad dziesięć zaawansowanych silników pędzli.</caption>
+ <caption xml:lang="pt">Existem mais de dez motores de pincéis extremamente poderosos.</caption>
+ <caption xml:lang="sv">Det finns mer än tio enormt kraftfulla penselgränssnitt.</caption>
+ <caption xml:lang="uk">У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів.</caption>
+ <caption xml:lang="x-test">xxThere are over ten immensely powerful brush engines.xx</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png</image>
</screenshot>
<screenshot type="default">
+ <caption>Create and use gamut masks to give your images a coherent feel.</caption>
+ <caption xml:lang="ca">Creeu i useu màscares de gamma per donar a les imatges una aparença coherent.</caption>
+ <caption xml:lang="ca-valencia">Creeu i useu màscares de gamma per donar a les imatges una aparença coherent.</caption>
+ <caption xml:lang="es">Cree y use gamas para proporcionar a sus imágenes un aspecto coherente.</caption>
+ <caption xml:lang="id">Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren.</caption>
+ <caption xml:lang="it">Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente.</caption>
+ <caption xml:lang="ko">색역 마스크를 만들고 사용할 수 있습니다.</caption>
+ <caption xml:lang="nl">Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven.</caption>
+ <caption xml:lang="pl">Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd.</caption>
+ <caption xml:lang="pt">Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente.</caption>
+ <caption xml:lang="sv">Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla.</caption>
+ <caption xml:lang="uk">Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду.</caption>
+ <caption xml:lang="x-test">xxCreate and use gamut masks to give your images a coherent feel.xx</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png</image>
</screenshot>
<screenshot type="default">
+ <caption>Into animation? Krita provides everything you need for traditional, hand-drawn animation.</caption>
+ <caption xml:lang="ca">Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional.</caption>
+ <caption xml:lang="ca-valencia">Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional.</caption>
+ <caption xml:lang="es">¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional.</caption>
+ <caption xml:lang="id">Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional.</caption>
+ <caption xml:lang="it">Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano.</caption>
+ <caption xml:lang="ko">애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다.</caption>
+ <caption xml:lang="nl">Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie.</caption>
+ <caption xml:lang="pl">Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji.</caption>
+ <caption xml:lang="pt">Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão.</caption>
+ <caption xml:lang="sv">Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar.</caption>
+ <caption xml:lang="uk">Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації.</caption>
+ <caption xml:lang="x-test">xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx</caption>
+ <caption xml:lang="zh-TW">想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png</image>
</screenshot>
<screenshot type="default">
+ <caption>If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.</caption>
+ <caption xml:lang="ca">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.</caption>
+ <caption xml:lang="ca-valencia">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.</caption>
+ <caption xml:lang="es">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.</caption>
+ <caption xml:lang="id">Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas.</caption>
+ <caption xml:lang="it">Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato.</caption>
+ <caption xml:lang="ko">디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오.</caption>
+ <caption xml:lang="nl">Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding.</caption>
+ <caption xml:lang="pl">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.</caption>
+ <caption xml:lang="pt">Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado.</caption>
+ <caption xml:lang="sv">Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok.</caption>
+ <caption xml:lang="uk">Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником.</caption>
+ <caption xml:lang="x-test">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</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png</image>
</screenshot>
</screenshots>
- <content_rating type="oars-1.0">
- <content_attribute id="violence-cartoon">none</content_attribute>
- <content_attribute id="violence-fantasy">none</content_attribute>
- <content_attribute id="violence-realistic">none</content_attribute>
- <content_attribute id="violence-bloodshed">none</content_attribute>
- <content_attribute id="violence-sexual">none</content_attribute>
- <content_attribute id="drugs-alcohol">none</content_attribute>
- <content_attribute id="drugs-narcotics">none</content_attribute>
- <content_attribute id="drugs-tobacco">none</content_attribute>
- <content_attribute id="sex-nudity">none</content_attribute>
- <content_attribute id="sex-themes">none</content_attribute>
- <content_attribute id="language-profanity">none</content_attribute>
- <content_attribute id="language-humor">none</content_attribute>
- <content_attribute id="language-discrimination">none</content_attribute>
- <content_attribute id="social-chat">none</content_attribute>
- <content_attribute id="social-info">none</content_attribute>
- <content_attribute id="social-audio">none</content_attribute>
- <content_attribute id="social-location">none</content_attribute>
- <content_attribute id="social-contacts">none</content_attribute>
- <content_attribute id="money-purchasing">none</content_attribute>
- <content_attribute id="money-gambling">none</content_attribute>
- </content_rating>
<categories>
<category>Graphics</category>
</categories>
<project_group>KDE</project_group>
<provides>
<binary>krita</binary>
<id>org.kde.krita.desktop</id>
</provides>
+ <content_rating type="oars-1.1"/>
<releases>
- <release date="2019-06-04" version="4.2.0.0 (BETA)"/>
+ <release date="2019-05-29" version="4.3.0.0"/>
</releases>
</component>
diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt
index c74d679c41..9bd57a43be 100644
--- a/libs/CMakeLists.txt
+++ b/libs/CMakeLists.txt
@@ -1,24 +1,23 @@
add_subdirectory( version )
add_subdirectory( global )
add_subdirectory( koplugin )
add_subdirectory( widgetutils )
add_subdirectory( widgets )
add_subdirectory( store )
add_subdirectory( odf )
add_subdirectory( flake )
add_subdirectory( basicflakes )
add_subdirectory( pigment )
add_subdirectory( command )
add_subdirectory( brush )
add_subdirectory( psd )
add_subdirectory( color )
add_subdirectory( image )
add_subdirectory( ui )
-add_subdirectory( vectorimage )
add_subdirectory( impex )
add_subdirectory( libkis )
if (NOT APPLE AND HAVE_QT_QUICK)
add_subdirectory( libqml )
endif()
add_subdirectory( metadata )
diff --git a/libs/brush/kis_auto_brush.cpp b/libs/brush/kis_auto_brush.cpp
index a16407c20d..72c445afa1 100644
--- a/libs/brush/kis_auto_brush.cpp
+++ b/libs/brush/kis_auto_brush.cpp
@@ -1,398 +1,398 @@
/*
* Copyright (c) 2004,2007-2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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 <compositeops/KoVcMultiArchBuildSupport.h> //MSVC requires that Vc come first
#include "kis_auto_brush.h"
#include <kis_debug.h>
#include <math.h>
#include <QRect>
#include <QDomElement>
#include <QtConcurrentMap>
#include <QByteArray>
#include <QBuffer>
#include <QFile>
#include <QFileInfo>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <kis_datamanager.h>
#include <kis_fixed_paint_device.h>
#include <kis_paint_device.h>
#include <brushengine/kis_paint_information.h>
#include <kis_mask_generator.h>
#include <kis_boundary.h>
#include <brushengine/kis_paintop_lod_limitations.h>
#include <kis_brush_mask_applicator_base.h>
#if defined(_WIN32) || defined(_WIN64)
#include <stdlib.h>
#define srand48 srand
inline double drand48()
{
return double(rand()) / RAND_MAX;
}
#endif
struct KisAutoBrush::Private {
Private()
: randomness(0), density(1.0), idealThreadCountCached(1) {}
Private(const Private &rhs)
: shape(rhs.shape->clone()),
randomness(rhs.randomness),
density(rhs.density),
idealThreadCountCached(rhs.idealThreadCountCached)
{
}
QScopedPointer<KisMaskGenerator> shape;
qreal randomness;
qreal density;
int idealThreadCountCached;
};
KisAutoBrush::KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density)
: KisBrush(),
d(new Private)
{
d->shape.reset(as);
d->randomness = randomness;
d->density = density;
d->idealThreadCountCached = QThread::idealThreadCount();
setBrushType(MASK);
setWidth(qMax(qreal(1.0), d->shape->width()));
setHeight(qMax(qreal(1.0), d->shape->height()));
QImage image = createBrushPreview();
setBrushTipImage(image);
// Set angle here so brush tip image is generated unrotated
setAngle(angle);
image = createBrushPreview();
setImage(image);
}
KisAutoBrush::~KisAutoBrush()
{
}
qreal KisAutoBrush::userEffectiveSize() const
{
return d->shape->diameter();
}
void KisAutoBrush::setUserEffectiveSize(qreal value)
{
d->shape->setDiameter(value);
}
KisAutoBrush::KisAutoBrush(const KisAutoBrush& rhs)
: KisBrush(rhs),
d(new Private(*rhs.d))
{
}
KisBrush* KisAutoBrush::clone() const
{
return new KisAutoBrush(*this);
}
/* It's difficult to predict the mask height when exaclty when there are
* more than 2 spikes, so we return an upperbound instead. */
static KisDabShape lieAboutDabShape(KisDabShape const& shape)
{
return KisDabShape(shape.scale(), 1.0, shape.rotation());
}
qint32 KisAutoBrush::maskHeight(KisDabShape const& shape,
qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
{
return KisBrush::maskHeight(
lieAboutDabShape(shape), subPixelX, subPixelY, info);
}
qint32 KisAutoBrush::maskWidth(KisDabShape const& shape,
qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
{
return KisBrush::maskWidth(
lieAboutDabShape(shape), subPixelX, subPixelY, info);
}
QSizeF KisAutoBrush::characteristicSize(KisDabShape const& shape) const
{
return KisBrush::characteristicSize(lieAboutDabShape(shape));
}
inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size)
{
/**
* This version of filling uses low granularity of data transfers
* (32-bit chunks) and internal processor's parallelism. It reaches
* 25% better performance in KisStrokeBenchmark in comparison to
* per-pixel memcpy version (tested on Sandy Bridge).
*/
int block1 = size / 8;
int block2 = size % 8;
quint32 *src = reinterpret_cast<quint32*>(color);
quint32 *dst = reinterpret_cast<quint32*>(buf);
// check whether all buffers are 4 bytes aligned
// (uncomment if experience some problems)
// Q_ASSERT(((qint64)src & 3) == 0);
// Q_ASSERT(((qint64)dst & 3) == 0);
for (int i = 0; i < block1; i++) {
*dst = *src;
*(dst + 1) = *src;
*(dst + 2) = *src;
*(dst + 3) = *src;
*(dst + 4) = *src;
*(dst + 5) = *src;
*(dst + 6) = *src;
*(dst + 7) = *src;
dst += 8;
}
for (int i = 0; i < block2; i++) {
*dst = *src;
dst++;
}
}
inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize)
{
/**
* This version uses internal processor's parallelism and gives
* 20% better performance in KisStrokeBenchmark in comparison to
* per-pixel memcpy version (tested on Sandy Bridge (+20%) and
* on Merom (+10%)).
*/
int block1 = size / 8;
int block2 = size % 8;
for (int i = 0; i < block1; i++) {
quint8 *d1 = buf;
quint8 *d2 = buf + pixelSize;
quint8 *d3 = buf + 2 * pixelSize;
quint8 *d4 = buf + 3 * pixelSize;
quint8 *d5 = buf + 4 * pixelSize;
quint8 *d6 = buf + 5 * pixelSize;
quint8 *d7 = buf + 6 * pixelSize;
quint8 *d8 = buf + 7 * pixelSize;
for (int j = 0; j < pixelSize; j++) {
*(d1 + j) = color[j];
*(d2 + j) = color[j];
*(d3 + j) = color[j];
*(d4 + j) = color[j];
*(d5 + j) = color[j];
*(d6 + j) = color[j];
*(d7 + j) = color[j];
*(d8 + j) = color[j];
}
buf += 8 * pixelSize;
}
for (int i = 0; i < block2; i++) {
memcpy(buf, color, pixelSize);
buf += pixelSize;
}
}
void KisAutoBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX , double subPixelY, qreal softnessFactor) const
{
Q_UNUSED(info);
// Generate the paint device from the mask
const KoColorSpace* cs = dst->colorSpace();
quint32 pixelSize = cs->pixelSize();
// mask dimension methods already includes KisBrush::angle()
int dstWidth = maskWidth(shape, subPixelX, subPixelY, info);
int dstHeight = maskHeight(shape, subPixelX, subPixelY, info);
QPointF hotSpot = this->hotSpot(shape, info);
// mask size and hotSpot function take the KisBrush rotation into account
qreal angle = shape.rotation() + KisBrush::angle();
// if there's coloring information, we merely change the alpha: in that case,
// the dab should be big enough!
if (coloringInformation) {
// new bounds. we don't care if there is some extra memory occcupied.
dst->setRect(QRect(0, 0, dstWidth, dstHeight));
dst->lazyGrowBufferWithoutInitialization();
}
else {
KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth &&
dst->bounds().height() >= dstHeight);
}
quint8* dabPointer = dst->data();
quint8* color = 0;
if (coloringInformation) {
if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) {
color = const_cast<quint8*>(coloringInformation->color());
}
}
double centerX = hotSpot.x() - 0.5 + subPixelX;
double centerY = hotSpot.y() - 0.5 + subPixelY;
+ d->shape->setSoftness(softnessFactor); // softness must be set first
d->shape->setScale(shape.scaleX(), shape.scaleY());
- d->shape->setSoftness(softnessFactor);
if (coloringInformation) {
if (color && pixelSize == 4) {
fillPixelOptimized_4bytes(color, dabPointer, dstWidth * dstHeight);
}
else if (color) {
fillPixelOptimized_general(color, dabPointer, dstWidth * dstHeight, pixelSize);
}
else {
for (int y = 0; y < dstHeight; y++) {
for (int x = 0; x < dstWidth; x++) {
memcpy(dabPointer, coloringInformation->color(), pixelSize);
coloringInformation->nextColumn();
dabPointer += pixelSize;
}
coloringInformation->nextRow();
}
}
}
MaskProcessingData data(dst, cs, d->randomness, d->density,
centerX, centerY,
angle);
KisBrushMaskApplicatorBase *applicator = d->shape->applicator();
applicator->initializeData(&data);
int jobs = d->idealThreadCountCached;
if (threadingAllowed() && dstHeight > 100 && jobs >= 4) {
int splitter = dstHeight / jobs;
QVector<QRect> rects;
for (int i = 0; i < jobs - 1; i++) {
rects << QRect(0, i * splitter, dstWidth, splitter);
}
rects << QRect(0, (jobs - 1)*splitter, dstWidth, dstHeight - (jobs - 1)*splitter);
OperatorWrapper wrapper(applicator);
QtConcurrent::blockingMap(rects, wrapper);
}
else {
QRect rect(0, 0, dstWidth, dstHeight);
applicator->process(rect);
}
}
void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const
{
QDomElement shapeElt = doc.createElement("MaskGenerator");
d->shape->toXML(doc, shapeElt);
e.appendChild(shapeElt);
e.setAttribute("type", "auto_brush");
e.setAttribute("spacing", QString::number(spacing()));
e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
e.setAttribute("angle", QString::number(KisBrush::angle()));
e.setAttribute("randomness", QString::number(d->randomness));
e.setAttribute("density", QString::number(d->density));
KisBrush::toXML(doc, e);
}
QImage KisAutoBrush::createBrushPreview()
{
int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation());
int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation());
KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0);
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
fdev->setRect(QRect(0, 0, width, height));
fdev->initialize();
mask(fdev, KoColor(Qt::black, fdev->colorSpace()), KisDabShape(), info);
return fdev->convertToQImage(0);
}
const KisMaskGenerator* KisAutoBrush::maskGenerator() const
{
return d->shape.data();
}
qreal KisAutoBrush::density() const
{
return d->density;
}
qreal KisAutoBrush::randomness() const
{
return d->randomness;
}
QPainterPath KisAutoBrush::outline() const
{
bool simpleOutline = (d->density < 1.0);
if (simpleOutline) {
QPainterPath path;
QRectF brushBoundingbox(0, 0, width(), height());
if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) {
path.addEllipse(brushBoundingbox);
}
else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE)
path.addRect(brushBoundingbox);
}
return path;
}
return KisBrush::boundary()->path();
}
void KisAutoBrush::lodLimitations(KisPaintopLodLimitations *l) const
{
KisBrush::lodLimitations(l);
if (!qFuzzyCompare(density(), 1.0)) {
l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0"));
}
if (!qFuzzyCompare(randomness(), 0.0)) {
l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0"));
}
}
diff --git a/libs/brush/kis_brushes_pipe.h b/libs/brush/kis_brushes_pipe.h
index 63f1cfd9e6..4bf4c90f0a 100644
--- a/libs/brush/kis_brushes_pipe.h
+++ b/libs/brush/kis_brushes_pipe.h
@@ -1,182 +1,195 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_BRUSHES_PIPE_H
#define __KIS_BRUSHES_PIPE_H
#include <kis_fixed_paint_device.h>
template<class BrushType>
class KisBrushesPipe
{
public:
KisBrushesPipe() {
}
KisBrushesPipe(const KisBrushesPipe &rhs) {
qDeleteAll(m_brushes);
m_brushes.clear();
Q_FOREACH (BrushType * brush, rhs.m_brushes) {
BrushType *clonedBrush = dynamic_cast<BrushType*>(brush->clone());
KIS_ASSERT_RECOVER(clonedBrush) {continue;}
m_brushes.append(clonedBrush);
}
}
virtual ~KisBrushesPipe() {
qDeleteAll(m_brushes);
}
virtual void clear() {
qDeleteAll(m_brushes);
m_brushes.clear();
}
BrushType* firstBrush() const {
return m_brushes.first();
}
BrushType* lastBrush() const {
return m_brushes.last();
}
BrushType* currentBrush(const KisPaintInformation& info) {
- return !m_brushes.isEmpty() ? m_brushes.at(chooseNextBrush(info)) : 0;
+ Q_UNUSED(info);
+ return !m_brushes.isEmpty() ? m_brushes.at(currentBrushIndex()) : 0;
}
int brushIndex(const KisPaintInformation& info) {
return chooseNextBrush(info);
}
qint32 maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) {
BrushType *brush = currentBrush(info);
return brush ? brush->maskWidth(shape, subPixelX, subPixelY, info) : 0;
}
qint32 maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) {
BrushType *brush = currentBrush(info);
return brush ? brush->maskHeight(shape, subPixelX, subPixelY, info) : 0;
}
void setAngle(qreal angle) {
Q_FOREACH (BrushType * brush, m_brushes) {
brush->setAngle(angle);
}
}
void setScale(qreal scale) {
Q_FOREACH (BrushType * brush, m_brushes) {
brush->setScale(scale);
}
}
void setSpacing(double spacing) {
Q_FOREACH (BrushType * brush, m_brushes) {
brush->setSpacing(spacing);
}
}
bool hasColor() const {
Q_FOREACH (BrushType * brush, m_brushes) {
if (brush->hasColor()) return true;
}
return false;
}
void notifyCachedDabPainted(const KisPaintInformation& info) {
updateBrushIndexes(info, -1);
}
void prepareForSeqNo(const KisPaintInformation& info, int seqNo) {
updateBrushIndexes(info, seqNo);
}
void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX , double subPixelY,
qreal softnessFactor) {
BrushType *brush = currentBrush(info);
if (!brush) return;
brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
notifyCachedDabPainted(info);
}
KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX, double subPixelY) {
BrushType *brush = currentBrush(info);
if (!brush) return 0;
KisFixedPaintDeviceSP device = brush->paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
notifyCachedDabPainted(info);
return device;
}
QVector<BrushType*> brushes() {
return m_brushes;
}
void testingSelectNextBrush(const KisPaintInformation& info) {
(void) chooseNextBrush(info);
notifyCachedDabPainted(info);
}
/**
* Is called by the paint op when a paintop starts a stroke. The
* brushes are shared among different strokes, so sometimes the
* brush should be reset.
*/
virtual void notifyStrokeStarted() = 0;
protected:
void addBrush(BrushType *brush) {
m_brushes.append(brush);
}
+ int sizeBrush() {
+ return m_brushes.size();
+ }
+
/**
- * Returns the index of the brush that corresponds to the current
+ * Returns the index of the next brush that corresponds to the current
* values of \p info. This method is called *before* the dab is
* actually painted.
*
+ */
+ virtual int chooseNextBrush(const KisPaintInformation& info) = 0;
+
+ /**
+ * Returns the current index of the brush
+ * This method is called *before* the dab is
+ * actually painted.
+ *
* The method is const, so no internal counters of the brush should
* change during its execution
*/
- virtual int chooseNextBrush(const KisPaintInformation& info) = 0;
+ virtual int currentBrushIndex() = 0;
/**
* Updates internal counters of the brush *after* a dab has been
* painted on the canvas. Some incremental switching of the brushes
* may me implemented in this method.
*
* If \p seqNo is equal or greater than zero, then incremental iteration is
* overridden by this seqNo value
*/
virtual void updateBrushIndexes(const KisPaintInformation& info, int seqNo) = 0;
protected:
QVector<BrushType*> m_brushes;
};
#endif /* __KIS_BRUSHES_PIPE_H */
diff --git a/libs/brush/kis_imagepipe_brush.cpp b/libs/brush/kis_imagepipe_brush.cpp
index 8595772ea1..626c365419 100644
--- a/libs/brush/kis_imagepipe_brush.cpp
+++ b/libs/brush/kis_imagepipe_brush.cpp
@@ -1,518 +1,539 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
*
* 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_imagepipe_brush.h"
#include "kis_pipebrush_parasite.h"
#include "kis_brushes_pipe.h"
class KisImageBrushesPipe : public KisBrushesPipe<KisGbrBrush>
{
public:
KisImageBrushesPipe()
- : m_isInitialized(false)
+ : m_currentBrushIndex(0)
+ , m_isInitialized(false)
{
}
/*
pre and post are split because:
21:12:20 < dmitryK> boud: i guess it was somehow related to the fact that the maskWidth/maskHeight should
correspond to the size of the mask returned by paintDevice()
21:13:33 < dmitryK> boud: the random stuff is called once per brush->paintDevice() call, after the device is
returned to the paint op, that is "preparing the randomness for the next call"
21:14:16 < dmitryK> boud: and brushesPipe->currentBrush() always returning the same brush for any particular
paintInfo.
*/
protected:
static int selectPre(KisParasite::SelectionMode mode,
int index, int rank,
const KisPaintInformation& info) {
qreal angle;
+ qreal velocity;
+ qreal capSpeed = 3;
switch (mode) {
case KisParasite::Constant:
case KisParasite::Incremental:
case KisParasite::Random:
break;
case KisParasite::Pressure:
index = static_cast<int>(info.pressure() * (rank - 1) + 0.5);
break;
case KisParasite::Angular:
- // + m_d->PI_2 to be compatible with the gimp
- angle = info.drawingAngle() + M_PI_2;
+ // + M_PI_2 + M_PI_4 to be compatible with the gimp
+ angle = info.drawingAngle() + M_PI_2 + M_PI_4;
angle = normalizeAngle(angle);
index = static_cast<int>(angle / (2.0 * M_PI) * rank);
break;
case KisParasite::TiltX:
index = qRound(info.xTilt() / 2.0 * rank) + rank / 2;
break;
case KisParasite::TiltY:
index = qRound(info.yTilt() / 2.0 * rank) + rank / 2;
break;
+ case KisParasite::Velocity:
+ // log is slow, but allows for nicer dab transition
+ velocity = log(info.drawingSpeed() + 1);
+ if (velocity > capSpeed) {
+ velocity = capSpeed;
+ }
+ velocity /= capSpeed;
+ velocity *= (rank - 1) + 0.5;
+ index = qRound(velocity);
+ break;
default:
warnImage << "Parasite" << mode << "is not implemented";
index = 0;
}
return index;
}
static int selectPost(KisParasite::SelectionMode mode,
int index, int rank,
const KisPaintInformation& info,
int seqNo) {
switch (mode) {
case KisParasite::Constant: break;
case KisParasite::Incremental:
index = (seqNo >= 0 ? seqNo : (index + 1)) % rank;
break;
case KisParasite::Random:
- index = info.randomSource()->generate(0, rank);
+ index = info.randomSource()->generate(0, rank-1);
break;
case KisParasite::Pressure:
case KisParasite::Angular:
break;
case KisParasite::TiltX:
case KisParasite::TiltY:
+ case KisParasite::Velocity:
break;
default:
warnImage << "Parasite" << mode << "is not implemented";
index = 0;
}
return index;
}
int chooseNextBrush(const KisPaintInformation& info) override {
quint32 brushIndex = 0;
if (!m_isInitialized) {
/**
* Reset all the indexes to the initial values and do the
* generation based on parameters.
*/
for (int i = 0; i < m_parasite.dim; i++) {
m_parasite.index[i] = 0;
}
updateBrushIndexes(info, 0);
m_isInitialized = true;
}
for (int i = 0; i < m_parasite.dim; i++) {
int index = selectPre(m_parasite.selection[i],
m_parasite.index[i],
m_parasite.rank[i], info);
brushIndex += m_parasite.brushesCount[i] * index;
}
- brushIndex %= m_brushes.size();
+ brushIndex %= (quint32)m_brushes.size();
+ m_currentBrushIndex = brushIndex;
return brushIndex;
}
+ int currentBrushIndex() override {
+ return m_currentBrushIndex;
+ }
+
void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override {
for (int i = 0; i < m_parasite.dim; i++) {
m_parasite.index[i] = selectPost(m_parasite.selection[i],
m_parasite.index[i],
m_parasite.rank[i],
info,
seqNo);
}
}
public:
using KisBrushesPipe<KisGbrBrush>::addBrush;
+ using KisBrushesPipe<KisGbrBrush>::sizeBrush;
void setParasite(const KisPipeBrushParasite& parasite) {
m_parasite = parasite;
}
const KisPipeBrushParasite& parasite() const {
return m_parasite;
}
void setUseColorAsMask(bool useColorAsMask) {
Q_FOREACH (KisGbrBrush * brush, m_brushes) {
brush->setUseColorAsMask(useColorAsMask);
}
}
void makeMaskImage() {
Q_FOREACH (KisGbrBrush * brush, m_brushes) {
brush->makeMaskImage();
}
}
bool saveToDevice(QIODevice* dev) const {
Q_FOREACH (KisGbrBrush * brush, m_brushes) {
if (!brush->saveToDevice(dev)) {
return false;
}
}
return true;
}
void notifyStrokeStarted() override {
m_isInitialized = false;
}
private:
KisPipeBrushParasite m_parasite;
+ int m_currentBrushIndex;
bool m_isInitialized;
};
struct KisImagePipeBrush::Private {
public:
KisImageBrushesPipe brushesPipe;
};
KisImagePipeBrush::KisImagePipeBrush(const QString& filename)
: KisGbrBrush(filename)
, m_d(new Private())
{
}
KisImagePipeBrush::KisImagePipeBrush(const QString& name, int w, int h,
QVector< QVector<KisPaintDevice*> > devices,
QVector<KisParasite::SelectionMode > modes)
: KisGbrBrush(QString())
, m_d(new Private())
{
Q_ASSERT(devices.count() == modes.count());
Q_ASSERT(devices.count() > 0);
Q_ASSERT(devices.count() < 2); // XXX Multidimensionals not supported yet, change to MaxDim!
setName(name);
KisPipeBrushParasite parasite;
parasite.dim = devices.count();
// XXX Change for multidim! :
parasite.ncells = devices.at(0).count();
parasite.rank[0] = parasite.ncells; // ### This can masquerade some bugs, be careful here in the future
parasite.selection[0] = modes.at(0);
// XXX needsmovement!
parasite.setBrushesCount();
setParasite(parasite);
setDevices(devices, w, h);
setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage());
}
KisImagePipeBrush::KisImagePipeBrush(const KisImagePipeBrush& rhs)
: KisGbrBrush(rhs),
m_d(new Private(*rhs.m_d))
{
}
KisImagePipeBrush::~KisImagePipeBrush()
{
delete m_d;
}
bool KisImagePipeBrush::load()
{
QFile file(filename());
file.open(QIODevice::ReadOnly);
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KisImagePipeBrush::loadFromDevice(QIODevice *dev)
{
QByteArray data = dev->readAll();
return initFromData(data);
}
bool KisImagePipeBrush::initFromData(const QByteArray &data)
{
if (data.size() == 0) return false;
// XXX: this doesn't correctly load the image pipe brushes yet.
// XXX: This stuff is in utf-8, too.
// The first line contains the name -- this means we look until we arrive at the first newline
QByteArray line1;
qint32 i = 0;
while (data[i] != '\n' && i < data.size()) {
line1.append(data[i]);
i++;
}
setName(QString::fromUtf8(line1, line1.size()));
i++; // Skip past the first newline
// The second line contains the number of brushes, separated by a space from the parasite
// XXX: This stuff is in utf-8, too.
QByteArray line2;
while (data[i] != '\n' && i < data.size()) {
line2.append(data[i]);
i++;
}
QString paramline = QString::fromUtf8(line2, line2.size());
qint32 numOfBrushes = paramline.left(paramline.indexOf(' ')).toUInt();
QString parasiteString = paramline.mid(paramline.indexOf(' ') + 1);
KisPipeBrushParasite parasite = KisPipeBrushParasite(parasiteString);
parasite.sanitize();
parasiteSelectionString = parasite.selectionMode; // selection mode to return to UI
m_d->brushesPipe.setParasite(parasite);
i++; // Skip past the second newline
- for (int brushIndex = 0;
+ for (int brushIndex = m_d->brushesPipe.sizeBrush();
brushIndex < numOfBrushes && i < data.size(); brushIndex++) {
KisGbrBrush* brush = new KisGbrBrush(name() + '_' + QString().setNum(brushIndex),
data,
i);
m_d->brushesPipe.addBrush(brush);
}
if (numOfBrushes > 0) {
setValid(true);
setSpacing(m_d->brushesPipe.lastBrush()->spacing());
setWidth(m_d->brushesPipe.firstBrush()->width());
setHeight(m_d->brushesPipe.firstBrush()->height());
setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage());
}
return true;
}
bool KisImagePipeBrush::save()
{
QFile file(filename());
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
bool ok = saveToDevice(&file);
file.close();
return ok;
}
bool KisImagePipeBrush::saveToDevice(QIODevice* dev) const
{
QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8
char const* name = utf8Name.data();
int len = qstrlen(name);
- if (m_d->brushesPipe.parasite().dim != 1) {
+ if (m_d->brushesPipe.parasite().dim >= KisPipeBrushParasite::MaxDim) {
warnImage << "Save to file for pipe brushes with dim != not yet supported!";
return false;
}
// Save this pipe brush: first the header, and then all individual brushes consecutively
// XXX: this needs some care for when we have > 1 dimension)
// Gimp Pipe Brush header format: Name\n<number of brushes> <parasite>\n
// The name\n
if (dev->write(name, len) == -1)
return false;
if (!dev->putChar('\n'))
return false;
// Write the parasite (also writes number of brushes)
if (!m_d->brushesPipe.parasite().saveToDevice(dev))
return false;
if (!dev->putChar('\n'))
return false;
KoResource::saveToDevice(dev);
// <gbr brushes>
return m_d->brushesPipe.saveToDevice(dev);
}
void KisImagePipeBrush::notifyStrokeStarted()
{
m_d->brushesPipe.notifyStrokeStarted();
}
void KisImagePipeBrush::notifyCachedDabPainted(const KisPaintInformation& info)
{
m_d->brushesPipe.notifyCachedDabPainted(info);
}
void KisImagePipeBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
{
m_d->brushesPipe.prepareForSeqNo(info, seqNo);
}
void KisImagePipeBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX , double subPixelY,
qreal softnessFactor) const
{
m_d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
}
QVector<KisGbrBrush *> KisImagePipeBrush::brushes() const
{
return m_d->brushesPipe.brushes();
}
KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice(
const KoColorSpace * colorSpace,
KisDabShape const& shape,
const KisPaintInformation& info, double subPixelX, double subPixelY) const
{
return m_d->brushesPipe.paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
}
enumBrushType KisImagePipeBrush::brushType() const
{
return !hasColor() || useColorAsMask() ? PIPE_MASK : PIPE_IMAGE;
}
QString KisImagePipeBrush::parasiteSelection()
{
return parasiteSelectionString;
}
bool KisImagePipeBrush::hasColor() const
{
return m_d->brushesPipe.hasColor();
}
void KisImagePipeBrush::makeMaskImage()
{
m_d->brushesPipe.makeMaskImage();
setUseColorAsMask(false);
}
void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask)
{
KisGbrBrush::setUseColorAsMask(useColorAsMask);
m_d->brushesPipe.setUseColorAsMask(useColorAsMask);
}
const KisBoundary* KisImagePipeBrush::boundary() const
{
KisGbrBrush *brush = m_d->brushesPipe.firstBrush();
Q_ASSERT(brush);
return brush->boundary();
}
bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info)
{
return (!m_d->brushesPipe.parasite().needsMovement || info.drawingDistance() >= 0.5);
}
KisBrush* KisImagePipeBrush::clone() const
{
return new KisImagePipeBrush(*this);
}
QString KisImagePipeBrush::defaultFileExtension() const
{
return QString(".gih");
}
quint32 KisImagePipeBrush::brushIndex(const KisPaintInformation& info) const
{
return m_d->brushesPipe.brushIndex(info);
}
qint32 KisImagePipeBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return m_d->brushesPipe.maskWidth(shape, subPixelX, subPixelY, info);
}
qint32 KisImagePipeBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return m_d->brushesPipe.maskHeight(shape, subPixelX, subPixelY, info);
}
void KisImagePipeBrush::setAngle(qreal _angle)
{
KisGbrBrush::setAngle(_angle);
m_d->brushesPipe.setAngle(_angle);
}
void KisImagePipeBrush::setScale(qreal _scale)
{
KisGbrBrush::setScale(_scale);
m_d->brushesPipe.setScale(_scale);
}
void KisImagePipeBrush::setSpacing(double _spacing)
{
KisGbrBrush::setSpacing(_spacing);
m_d->brushesPipe.setSpacing(_spacing);
}
void KisImagePipeBrush::setBrushType(enumBrushType type)
{
Q_UNUSED(type);
qFatal("FATAL: protected member setBrushType has no meaning for KisImagePipeBrush");
// brushType() is a function of hasColor() and useColorAsMask()
}
void KisImagePipeBrush::setHasColor(bool hasColor)
{
Q_UNUSED(hasColor);
qFatal("FATAL: protected member setHasColor has no meaning for KisImagePipeBrush");
// hasColor() is a function of the underlying brushes
}
KisGbrBrush* KisImagePipeBrush::testingGetCurrentBrush(const KisPaintInformation& info) const
{
return m_d->brushesPipe.currentBrush(info);
}
void KisImagePipeBrush::testingSelectNextBrush(const KisPaintInformation& info) const
{
return m_d->brushesPipe.testingSelectNextBrush(info);
}
const KisPipeBrushParasite& KisImagePipeBrush::parasite() const {
return m_d->brushesPipe.parasite();
}
void KisImagePipeBrush::setParasite(const KisPipeBrushParasite &parasite)
{
m_d->brushesPipe.setParasite(parasite);
}
void KisImagePipeBrush::setDevices(QVector<QVector<KisPaintDevice *> > devices, int w, int h)
{
for (int i = 0; i < devices.at(0).count(); i++) {
m_d->brushesPipe.addBrush(new KisGbrBrush(devices.at(0).at(i), 0, 0, w, h));
}
}
diff --git a/libs/brush/kis_pipebrush_parasite.cpp b/libs/brush/kis_pipebrush_parasite.cpp
index fe18e189f2..b1dcd1bf4a 100644
--- a/libs/brush/kis_pipebrush_parasite.cpp
+++ b/libs/brush/kis_pipebrush_parasite.cpp
@@ -1,179 +1,182 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
*
* 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_pipebrush_parasite.h"
KisPipeBrushParasite::KisPipeBrushParasite(const QString& source)
{
init();
needsMovement = false;
QRegExp basicSplitter(" ");
QRegExp parasiteSplitter(":");
QStringList parasites = source.split(basicSplitter, QString::SkipEmptyParts);
for (int i = 0; i < parasites.count(); i++) {
QStringList split = parasites.at(i).split(parasiteSplitter, QString::SkipEmptyParts);
if (split.count() != 2) {
warnImage << "Wrong count for this parasite key/value:" << parasites.at(i);
continue;
}
QString index = split.at(0);
if (index == "dim") {
dim = (split.at(1)).toInt();
if (dim < 1 || dim > MaxDim) {
dim = 1;
}
} else if (index.startsWith(QString("sel"))) {
int selIndex = index.mid(strlen("sel")).toInt();
if (selIndex >= 0 && selIndex < dim) {
selectionMode = split.at(1);
if (selectionMode == "incremental") {
selection[selIndex] = KisParasite::Incremental;
}
else if (selectionMode == "angular") {
selection[selIndex] = KisParasite::Angular;
needsMovement = true;
}
else if (selectionMode == "random") {
selection[selIndex] = KisParasite::Random;
}
else if (selectionMode == "pressure") {
selection[selIndex] = KisParasite::Pressure;
}
else if (selectionMode == "xtilt") {
selection[selIndex] = KisParasite::TiltX;
}
else if (selectionMode == "ytilt") {
selection[selIndex] = KisParasite::TiltY;
}
+ else if (selectionMode == "velocity") {
+ selection[selIndex] = KisParasite::Velocity;
+ }
else {
selection[selIndex] = KisParasite::Constant;
}
}
else {
warnImage << "Sel: wrong index: " << selIndex << "(dim = " << dim << ")";
}
}
else if (index.startsWith(QString("rank"))) {
int rankIndex = index.mid(strlen("rank")).toInt();
if (rankIndex < 0 || rankIndex > dim) {
warnImage << "Rankindex out of range: " << rankIndex;
continue;
}
rank[rankIndex] = (split.at(1)).toInt();
}
else if (index == "ncells") {
ncells = (split.at(1)).toInt();
if (ncells < 1) {
warnImage << "ncells out of range: " << ncells;
ncells = 1;
}
}
}
for (int i = 0; i < dim; i++) {
index[i] = 0;
}
setBrushesCount();
}
void KisPipeBrushParasite::init()
{
for (int i = 0; i < MaxDim; i++) {
rank[i] = index[i] = brushesCount[i] = 0;
selection[i] = KisParasite::Constant;
}
}
void KisPipeBrushParasite::sanitize()
{
for (int i = 0; i < dim; i++) {
// In the 2 listed cases, we'd divide by 0!
if (rank[i] == 0 &&
(selection[i] == KisParasite::Incremental
|| selection[i] == KisParasite::Angular)) {
warnImage << "PIPE brush has a wrong rank for its selection mode!";
selection[i] = KisParasite::Constant;
}
}
}
void KisPipeBrushParasite::setBrushesCount()
{
// I assume ncells is correct. If it isn't, complain to the parasite header.
if (rank[0] != 0) {
brushesCount[0] = ncells / rank[0];
}
else {
brushesCount[0] = ncells;
}
for (int i = 1; i < dim; i++) {
if (rank[i] == 0) {
brushesCount[i] = brushesCount[i - 1];
}
else {
brushesCount[i] = brushesCount[i - 1] / rank[i];
}
}
}
bool KisPipeBrushParasite::saveToDevice(QIODevice* dev) const
{
// write out something like
// <count> ncells:<count> dim:<dim> rank0:<rank0> sel0:<sel0> <...>
QTextStream stream(dev);
stream.setCodec("UTF-8");
// XXX: FIXME things like step, placement and so are not added (nor loaded, as a matter of fact)"
stream << ncells << " ncells:" << ncells << " dim:" << dim;
for (int i = 0; i < dim; i++) {
stream << " rank" << i << ":" << rank[i] << " sel" << i << ":";
switch (selection[i]) {
case KisParasite::Constant:
stream << "constant"; break;
case KisParasite::Incremental:
stream << "incremental"; break;
case KisParasite::Angular:
stream << "angular"; break;
case KisParasite::Velocity:
stream << "velocity"; break;
case KisParasite::Random:
stream << "random"; break;
case KisParasite::Pressure:
stream << "pressure"; break;
case KisParasite::TiltX:
stream << "xtilt"; break;
case KisParasite::TiltY:
stream << "ytilt"; break;
}
}
return true;
}
bool loadFromDevice(QIODevice */*dev*/)
{
// XXX: implement...
return true;
}
diff --git a/libs/brush/kis_text_brush.cpp b/libs/brush/kis_text_brush.cpp
index 6a73c97f01..a0812d6223 100644
--- a/libs/brush/kis_text_brush.cpp
+++ b/libs/brush/kis_text_brush.cpp
@@ -1,334 +1,339 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.h"
#include <QDomDocument>
#include <QDomElement>
#include <QFontMetrics>
#include <QPainter>
#include "kis_gbr_brush.h"
#include "kis_brushes_pipe.h"
#include <kis_dom_utils.h>
#include <kis_threaded_text_rendering_workaround.h>
#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
#include <QApplication>
#include <QWidget>
#include <QThread>
#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
class KisTextBrushesPipe : public KisBrushesPipe<KisGbrBrush>
{
public:
KisTextBrushesPipe() {
m_charIndex = 0;
m_currentBrushIndex = 0;
}
KisTextBrushesPipe(const KisTextBrushesPipe &rhs)
: KisBrushesPipe<KisGbrBrush>(), // no copy here!
m_text(rhs.m_text),
m_charIndex(rhs.m_charIndex),
m_currentBrushIndex(rhs.m_currentBrushIndex)
{
m_brushesMap.clear();
QMapIterator<QChar, KisGbrBrush*> iter(rhs.m_brushesMap);
while (iter.hasNext()) {
iter.next();
KisGbrBrush *brush = new KisGbrBrush(*iter.value());
m_brushesMap.insert(iter.key(), brush);
KisBrushesPipe<KisGbrBrush>::addBrush(brush);
}
}
void setText(const QString &text, const QFont &font) {
m_text = text;
m_charIndex = 0;
clear();
for (int i = 0; i < m_text.length(); i++) {
const QChar letter = m_text.at(i);
// skip letters that are already present in the brushes pipe
if (m_brushesMap.contains(letter)) continue;
QImage image = renderChar(letter, font);
KisGbrBrush *brush = new KisGbrBrush(image, letter);
brush->setSpacing(0.1); // support for letter spacing?
brush->makeMaskImage();
m_brushesMap.insert(letter, brush);
KisBrushesPipe<KisGbrBrush>::addBrush(brush);
}
}
static QImage renderChar(const QString& text, const QFont &font) {
#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
QWidget *focusWidget = qApp->focusWidget();
if (focusWidget) {
QThread *guiThread = focusWidget->thread();
if (guiThread != QThread::currentThread()) {
warnKrita << "WARNING: Rendering text in non-GUI thread!"
<< "That may lead to hangups and crashes on some"
<< "versions of X11/Qt!";
}
}
#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
QFontMetrics metric(font);
QRect rect = metric.boundingRect(text);
if (rect.isEmpty()) {
rect = QRect(0, 0, 1, 1); // paint at least something
}
QRect paintingRect = rect.translated(-rect.x(), -rect.y());
QImage renderedChar(paintingRect.size(), QImage::Format_ARGB32);
QPainter p;
p.begin(&renderedChar);
p.setFont(font);
p.fillRect(paintingRect, Qt::white);
p.setPen(Qt::black);
p.drawText(-rect.x(), -rect.y(), text);
p.end();
return renderedChar;
}
void clear() override {
m_brushesMap.clear();
KisBrushesPipe<KisGbrBrush>::clear();
}
KisGbrBrush* firstBrush() const {
Q_ASSERT(m_text.size() > 0);
Q_ASSERT(m_brushesMap.size() > 0);
return m_brushesMap.value(m_text.at(0));
}
void notifyStrokeStarted() override {
m_charIndex = 0;
updateBrushIndexesImpl();
}
protected:
int chooseNextBrush(const KisPaintInformation& info) override {
Q_UNUSED(info);
return m_currentBrushIndex;
}
+
+ int currentBrushIndex() override {
+ return m_currentBrushIndex;
+ }
+
void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override {
Q_UNUSED(info);
if (m_text.size()) {
m_charIndex = (seqNo >= 0 ? seqNo : (m_charIndex + 1)) % m_text.size();
} else {
m_charIndex = 0;
}
updateBrushIndexesImpl();
}
private:
void updateBrushIndexesImpl() {
if (m_text.isEmpty()) return;
if (m_charIndex >= m_text.size()) {
m_charIndex = 0;
}
QChar letter = m_text.at(m_charIndex);
Q_ASSERT(m_brushesMap.contains(letter));
m_currentBrushIndex = m_brushes.indexOf(m_brushesMap.value(letter));
}
private:
QMap<QChar, KisGbrBrush*> m_brushesMap;
QString m_text;
int m_charIndex;
int m_currentBrushIndex;
};
KisTextBrush::KisTextBrush()
: m_brushesPipe(new KisTextBrushesPipe())
{
setPipeMode(false);
}
KisTextBrush::KisTextBrush(const KisTextBrush &rhs)
: KisScalingSizeBrush(rhs),
m_font(rhs.m_font),
m_text(rhs.m_text),
m_brushesPipe(new KisTextBrushesPipe(*rhs.m_brushesPipe))
{
}
KisTextBrush::~KisTextBrush()
{
delete m_brushesPipe;
}
void KisTextBrush::setPipeMode(bool pipe)
{
setBrushType(pipe ? PIPE_MASK : MASK);
}
bool KisTextBrush::pipeMode() const
{
return brushType() == PIPE_MASK;
}
void KisTextBrush::setText(const QString& txt)
{
m_text = txt;
}
QString KisTextBrush::text(void) const
{
return m_text;
}
void KisTextBrush::setFont(const QFont& font)
{
m_font = font;
}
QFont KisTextBrush::font()
{
return m_font;
}
void KisTextBrush::notifyStrokeStarted()
{
m_brushesPipe->notifyStrokeStarted();
}
void KisTextBrush::notifyCachedDabPainted(const KisPaintInformation& info)
{
m_brushesPipe->notifyCachedDabPainted(info);
}
void KisTextBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
{
m_brushesPipe->prepareForSeqNo(info, seqNo);
}
void KisTextBrush::generateMaskAndApplyMaskOrCreateDab(
KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const
{
if (brushType() == MASK) {
KisBrush::generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
}
else { /* if (brushType() == PIPE_MASK)*/
m_brushesPipe->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
}
}
KisFixedPaintDeviceSP KisTextBrush::paintDevice(const KoColorSpace * colorSpace,
KisDabShape const& shape,
const KisPaintInformation& info, double subPixelX, double subPixelY) const
{
if (brushType() == MASK) {
return KisBrush::paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
}
else { /* if (brushType() == PIPE_MASK)*/
return m_brushesPipe->paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
}
}
void KisTextBrush::toXML(QDomDocument& doc, QDomElement& e) const
{
Q_UNUSED(doc);
e.setAttribute("type", "kis_text_brush");
e.setAttribute("spacing", KisDomUtils::toString(spacing()));
e.setAttribute("text", m_text);
e.setAttribute("font", m_font.toString());
e.setAttribute("pipe", (brushType() == PIPE_MASK) ? "true" : "false");
KisBrush::toXML(doc, e);
}
void KisTextBrush::updateBrush()
{
Q_ASSERT((brushType() == PIPE_MASK) || (brushType() == MASK));
if (brushType() == PIPE_MASK) {
m_brushesPipe->setText(m_text, m_font);
setBrushTipImage(m_brushesPipe->firstBrush()->brushTipImage());
}
else { /* if (brushType() == MASK)*/
setBrushTipImage(KisTextBrushesPipe::renderChar(m_text, m_font));
}
resetBoundary();
setValid(true);
}
quint32 KisTextBrush::brushIndex(const KisPaintInformation& info) const
{
return brushType() == MASK ? 0 : 1 + m_brushesPipe->brushIndex(info);
}
qint32 KisTextBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return brushType() == MASK ?
KisBrush::maskWidth(shape, subPixelX, subPixelY, info) :
m_brushesPipe->maskWidth(shape, subPixelX, subPixelY, info);
}
qint32 KisTextBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return brushType() == MASK ?
KisBrush::maskHeight(shape, subPixelX, subPixelY, info) :
m_brushesPipe->maskHeight(shape, subPixelX, subPixelY, info);
}
void KisTextBrush::setAngle(qreal _angle)
{
KisBrush::setAngle(_angle);
m_brushesPipe->setAngle(_angle);
}
void KisTextBrush::setScale(qreal _scale)
{
KisBrush::setScale(_scale);
m_brushesPipe->setScale(_scale);
}
void KisTextBrush::setSpacing(double _spacing)
{
KisBrush::setSpacing(_spacing);
m_brushesPipe->setSpacing(_spacing);
}
KisBrush* KisTextBrush::clone() const
{
return new KisTextBrush(*this);
}
diff --git a/libs/flake/KisGamutMaskViewConverter.cpp b/libs/flake/KisGamutMaskViewConverter.cpp
index 2135f61c2e..dd499dfe51 100644
--- a/libs/flake/KisGamutMaskViewConverter.cpp
+++ b/libs/flake/KisGamutMaskViewConverter.cpp
@@ -1,174 +1,175 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 "KisGamutMaskViewConverter.h"
#include <QPointF>
#include <QRectF>
#include <QSizeF>
#include <FlakeDebug.h>
//#define DEBUG_GAMUT_MASK_CONVERTER
KisGamutMaskViewConverter::KisGamutMaskViewConverter()
: m_viewSize(1.0)
, m_maskSize(QSizeF(1,1))
, m_maskResolution(1)
{
computeAndSetZoom();
}
KisGamutMaskViewConverter::~KisGamutMaskViewConverter()
{
}
QSize KisGamutMaskViewConverter::viewSize() const
{
return QSize(m_viewSize, m_viewSize);
}
QPointF KisGamutMaskViewConverter::documentToView(const QPointF &documentPoint) const
{
return QPointF(documentToViewX(documentPoint.x()), documentToViewY(documentPoint.y()));
}
QPointF KisGamutMaskViewConverter::viewToDocument(const QPointF &viewPoint) const
{
return QPointF(viewToDocumentX(viewPoint.x()), viewToDocumentY(viewPoint.y()));
}
QRectF KisGamutMaskViewConverter::documentToView(const QRectF &documentRect) const
{
return QRectF(documentToView(documentRect.topLeft()), documentToView(documentRect.size()));
}
QRectF KisGamutMaskViewConverter::viewToDocument(const QRectF &viewRect) const
{
return QRectF(viewToDocument(viewRect.topLeft()), viewToDocument(viewRect.size()));
}
QSizeF KisGamutMaskViewConverter::documentToView(const QSizeF &documentSize) const
{
return QSizeF(documentToViewX(documentSize.width()), documentToViewY(documentSize.height()));
}
QSizeF KisGamutMaskViewConverter::viewToDocument(const QSizeF &viewSize) const
{
return QSizeF(viewToDocumentX(viewSize.width()), viewToDocumentY(viewSize.height()));
}
qreal KisGamutMaskViewConverter::documentToViewX(qreal documentX) const
{
qreal translated = documentX * m_zoomLevel;
#ifdef DEBUG_GAMUT_MASK_CONVERTER
debugFlake << "KisGamutMaskViewConverter::DocumentToViewX: "
<< "documentX: " << documentX
<< " -> translated: " << translated;
#endif
return translated;
}
qreal KisGamutMaskViewConverter::documentToViewY(qreal documentY) const
{
qreal translated = documentY * m_zoomLevel;
#ifdef DEBUG_GAMUT_MASK_CONVERTER
debugFlake << "KisGamutMaskViewConverter::DocumentToViewY: "
<< "documentY: " << documentY
<< " -> translated: " << translated;
#endif
return translated;
}
qreal KisGamutMaskViewConverter::viewToDocumentX(qreal viewX) const
{
qreal translated = viewX / m_zoomLevel;
#ifdef DEBUG_GAMUT_MASK_CONVERTER
debugFlake << "KisGamutMaskViewConverter::viewToDocumentX: "
<< "viewX: " << viewX
<< " -> translated: " << translated;
#endif
return translated;
}
qreal KisGamutMaskViewConverter::viewToDocumentY(qreal viewY) const
{
qreal translated = viewY / m_zoomLevel;
#ifdef DEBUG_GAMUT_MASK_CONVERTER
debugFlake << "KisGamutMaskViewConverter::viewToDocumentY: "
<< "viewY: " << viewY
<< " -> translated: " << translated;
#endif
return translated;
}
void KisGamutMaskViewConverter::setZoom(qreal zoom)
{
if (qFuzzyCompare(zoom, qreal(0.0)) || qFuzzyCompare(zoom, qreal(1.0))) {
zoom = 1;
}
#ifdef DEBUG_GAMUT_MASK_CONVERTER
debugFlake << "KisGamutMaskViewConverter::setZoom: setting to " << zoom;
#endif
m_zoomLevel = zoom;
}
void KisGamutMaskViewConverter::zoom(qreal *zoomX, qreal *zoomY) const
{
*zoomX = m_zoomLevel;
*zoomY = m_zoomLevel;
}
void KisGamutMaskViewConverter::setViewSize(QSize viewSize)
{
m_viewSize = viewSize.width();
computeAndSetZoom();
}
void KisGamutMaskViewConverter::setMaskSize(QSizeF maskSize)
{
m_maskSize = maskSize;
m_maskResolution = maskSize.width();
computeAndSetZoom();
}
void KisGamutMaskViewConverter::computeAndSetZoom()
{
qreal zoom = m_viewSize / m_maskResolution;
#ifdef DEBUG_GAMUT_MASK_CONVERTER
debugFlake << "KisGamutMaskViewConverter::computeAndSetZoom: "
<< "m_viewSize: " << m_viewSize
<< " m_maskSize: " << m_maskResolution;
#endif
setZoom(zoom);
}
diff --git a/libs/flake/KisGamutMaskViewConverter.h b/libs/flake/KisGamutMaskViewConverter.h
index 73073efae8..6155528abf 100644
--- a/libs/flake/KisGamutMaskViewConverter.h
+++ b/libs/flake/KisGamutMaskViewConverter.h
@@ -1,70 +1,71 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 KISGAMUTMASKVIEWCONVERTER_H
#define KISGAMUTMASKVIEWCONVERTER_H
#include "kritaflake_export.h"
#include <QtGlobal>
#include <KoViewConverter.h>
#include <QSizeF>
class QPointF;
class QRectF;
/**
* @brief view convertor for gamut mask calculations and painting
*/
class KRITAFLAKE_EXPORT KisGamutMaskViewConverter : public KoViewConverter
{
public:
KisGamutMaskViewConverter();
~KisGamutMaskViewConverter();
QSize viewSize() const;
void setViewSize(QSize viewSize);
void setMaskSize(QSizeF maskSize);
QPointF documentToView(const QPointF &documentPoint) const override;
QPointF viewToDocument(const QPointF &viewPoint) const override;
QRectF documentToView(const QRectF &documentRect) const override;
QRectF viewToDocument(const QRectF &viewRect) const override;
QSizeF documentToView(const QSizeF& documentSize) const override;
QSizeF viewToDocument(const QSizeF& viewSize) const override;
qreal documentToViewX(qreal documentX) const override;
qreal documentToViewY(qreal documentY) const override;
qreal viewToDocumentX(qreal viewX) const override;
qreal viewToDocumentY(qreal viewY) const override;
void setZoom(qreal zoom) override;
void zoom(qreal *zoomX, qreal *zoomY) const override;
private:
void computeAndSetZoom();
qreal m_zoomLevel; // 1.0 is 100%
int m_viewSize;
QSizeF m_maskSize;
qreal m_maskResolution;
};
#endif // KISGAMUTMASKVIEWCONVERTER_H
diff --git a/libs/flake/KoCanvasController.cpp b/libs/flake/KoCanvasController.cpp
index f971fb633c..4ad3f5c26c 100644
--- a/libs/flake/KoCanvasController.cpp
+++ b/libs/flake/KoCanvasController.cpp
@@ -1,129 +1,129 @@
/* This file is part of the KDE project
*
* Copyright (C) 2010 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoCanvasController.h"
#include "KoToolManager.h"
#include <QSize>
#include <QPoint>
class Q_DECL_HIDDEN KoCanvasController::Private
{
public:
Private()
: margin(0)
, preferredCenterFractionX(0.5)
, preferredCenterFractionY(0.5)
, actionCollection(0)
{
}
int margin;
- QSize documentSize;
+ QSizeF documentSize;
QPoint documentOffset;
qreal preferredCenterFractionX;
qreal preferredCenterFractionY;
KActionCollection* actionCollection;
};
KoCanvasController::KoCanvasController(KActionCollection* actionCollection)
: d(new Private())
{
proxyObject = new KoCanvasControllerProxyObject(this);
d->actionCollection = actionCollection;
}
KoCanvasController::~KoCanvasController()
{
KoToolManager::instance()->removeCanvasController(this);
delete d;
delete proxyObject;
}
void KoCanvasController::setMargin(int margin)
{
d->margin = margin;
}
int KoCanvasController::margin() const
{
return d->margin;
}
KoCanvasBase* KoCanvasController::canvas() const
{
return 0;
}
-void KoCanvasController::setDocumentSize(const QSize &sz)
+void KoCanvasController::setDocumentSize(const QSizeF &sz)
{
d->documentSize = sz;
}
-QSize KoCanvasController::documentSize() const
+QSizeF KoCanvasController::documentSize() const
{
return d->documentSize;
}
void KoCanvasController::setPreferredCenterFractionX(qreal x)
{
d->preferredCenterFractionX = x;
}
qreal KoCanvasController::preferredCenterFractionX() const
{
return d->preferredCenterFractionX;
}
void KoCanvasController::setPreferredCenterFractionY(qreal y)
{
d->preferredCenterFractionY = y;
}
qreal KoCanvasController::preferredCenterFractionY() const
{
return d->preferredCenterFractionY;
}
void KoCanvasController::setDocumentOffset(QPoint &offset)
{
d->documentOffset = offset;
}
QPoint KoCanvasController::documentOffset() const
{
return d->documentOffset;
}
KoCanvasControllerProxyObject::KoCanvasControllerProxyObject(KoCanvasController *controller, QObject *parent)
: QObject(parent)
, m_canvasController(controller)
{
}
void KoCanvasControllerProxyObject::updateDocumentSize(const QSize &newSize, bool recalculateCenter)
{
m_canvasController->updateDocumentSize(newSize, recalculateCenter);
}
KActionCollection* KoCanvasController::actionCollection() const
{
return d->actionCollection;
}
diff --git a/libs/flake/KoCanvasController.h b/libs/flake/KoCanvasController.h
index 78c40e1b71..471a68b36b 100644
--- a/libs/flake/KoCanvasController.h
+++ b/libs/flake/KoCanvasController.h
@@ -1,482 +1,482 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2008 Thomas Zander <zander@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007-2008 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006-2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2009 Thorsten Zachmann <zachmann@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License 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 KOCANVASCONTROLLER_H
#define KOCANVASCONTROLLER_H
#include "kritaflake_export.h"
#include <QObject>
#include <QSize>
#include <QPoint>
#include <QPointF>
#include <QPointer>
class KActionCollection;
class QRect;
class QRectF;
class KoShape;
class KoCanvasBase;
class KoCanvasControllerProxyObject;
/**
* KoCanvasController is the base class for wrappers around your canvas
* that provides scrolling and zooming for your canvas.
*
* Flake does not provide a canvas, the application will have to
* implement a canvas themselves. You canvas can be QWidget-based
* or something we haven't invented yet -- as long the class that holds the canvas
* implements KoCanvasController, tools, scrolling and zooming will work.
*
* A KoCanvasController implementation acts as a decorator around the canvas widget
* and provides a way to scroll the canvas, allows the canvas to be centered
* in the viewArea and manages tool activation.
*
* <p>The using application can instantiate this class and add its
* canvas using the setCanvas() call. Which is designed so it can be
* called multiple times if you need to exchange one canvas
* widget for another, for instance, switching between a plain QWidget or a QOpenGLWidget.
*
* <p>There is _one_ KoCanvasController per canvas in your
* application.
*
* <p>The canvas widget is at most as big as the viewport of the scroll
* area, and when the view on the document is near its edges, smaller.
* In your canvas widget code, you can find the right place in your
* document in view coordinates (pixels) by adding the documentOffset
*/
class KRITAFLAKE_EXPORT KoCanvasController
{
public:
// proxy QObject: use this to connect to slots and signals.
QPointer<KoCanvasControllerProxyObject> proxyObject;
/**
* Constructor.
* @param actionCollection the action collection for this canvas
*/
explicit KoCanvasController(KActionCollection* actionCollection);
virtual ~KoCanvasController();
public:
/**
* Returns the current margin that is used to pad the canvas with.
* This value is read from the KConfig property "canvasmargin"
*/
virtual int margin() const;
/**
* Set the new margin to pad the canvas with.
*/
virtual void setMargin(int margin);
/**
* compatibility with QAbstractScrollArea
*/
virtual void scrollContentsBy(int dx, int dy) = 0;
/**
* @return the size of the viewport
*/
- virtual QSize viewportSize() const = 0;
+ virtual QSizeF viewportSize() const = 0;
/**
* Set the new canvas to be shown as a child
* Calling this will emit canvasRemoved() if there was a canvas before, and will emit
* canvasSet() with the new canvas.
* @param canvas the new canvas. The KoCanvasBase::canvas() will be called to retrieve the
* actual widget which will then be added as child of this one.
*/
virtual void setCanvas(KoCanvasBase *canvas) = 0;
/**
* Return the currently set canvas. The default implementation will return Null
* @return the currently set canvas
*/
virtual KoCanvasBase *canvas() const;
/**
* return the amount of pixels vertically visible of the child canvas.
* @return the amount of pixels vertically visible of the child canvas.
*/
virtual int visibleHeight() const = 0;
/**
* return the amount of pixels horizontally visible of the child canvas.
* @return the amount of pixels horizontally visible of the child canvas.
*/
virtual int visibleWidth() const = 0;
/**
* return the amount of pixels that are not visible on the left side of the canvas.
* The leftmost pixel that is shown is returned.
*/
virtual int canvasOffsetX() const = 0;
/**
* return the amount of pixels that are not visible on the top side of the canvas.
* The topmost pixel that is shown is returned.
*/
virtual int canvasOffsetY() const = 0;
/**
* @brief Scrolls the content of the canvas so that the given rect is visible.
*
* The rect is to be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the centerpoint of the rectangle is centered if possible.
*
* @param rect the rectangle to make visible
* @param smooth if true the viewport translation will make be just enough to ensure visibility, no more.
* @see KoViewConverter::documentToView()
*/
virtual void ensureVisible(const QRectF &rect, bool smooth = false) = 0;
/**
* @brief Scrolls the content of the canvas so that the given shape is visible.
*
* This is just a wrapper function of the above function.
*
* @param shape the shape to make visible
*/
virtual void ensureVisible(KoShape *shape) = 0;
/**
* @brief zooms in around the center.
*
* The center must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center becomes center if possible.
*
* @param center the position to zoom in on
*/
virtual void zoomIn(const QPoint &center) = 0;
/**
* @brief zooms out around the center.
*
* The center must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center becomes center if possible.
*
* @param center the position to zoom out around
*/
virtual void zoomOut(const QPoint &center) = 0;
/**
* @brief zooms around the center.
*
* The center must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center becomes center if possible.
*
* @param center the position to zoom around
* @param zoom the zoom to apply
*/
virtual void zoomBy(const QPoint &center, qreal zoom) = 0;
/**
* @brief zoom so that rect is exactly visible (as close as possible)
*
* The rect must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center of the rect becomes center if possible.
*
* @param rect the rect in view coordinates (pixels) that should fit the view afterwards
*/
virtual void zoomTo(const QRect &rect) = 0;
/**
* @brief repositions the scrollbars so previous center is once again center
*
* The previous center is cached from when the user uses the scrollbars or zoomTo
* are called. zoomTo is mostly used when a zoom tool of sorts have marked an area
* to zoom in on
*
* The success of this method is limited by the size of thing. But we try our best.
*/
virtual void recenterPreferred() = 0;
/**
* Sets the preferred center point in view coordinates (pixels).
* @param viewPoint the new preferred center
*/
virtual void setPreferredCenter(const QPointF &viewPoint) = 0;
/// Returns the currently set preferred center point in view coordinates (pixels)
virtual QPointF preferredCenter() const = 0;
/**
* Move the canvas over the x and y distance of the parameter distance
* @param distance the distance in view coordinates (pixels). A positive distance means moving the canvas up/left.
*/
virtual void pan(const QPoint &distance) = 0;
/**
* Move the canvas up. This behaves the same as \sa pan() with a positive y coordinate.
*/
virtual void panUp() = 0;
/**
* Move the canvas down. This behaves the same as \sa pan() with a negative y coordinate.
*/
virtual void panDown() = 0;
/**
* Move the canvas to the left. This behaves the same as \sa pan() with a positive x coordinate.
*/
virtual void panLeft() = 0;
/**
* Move the canvas to the right. This behaves the same as \sa pan() with a negative x coordinate.
*/
virtual void panRight() = 0;
/**
* Get the position of the scrollbar
*/
virtual QPoint scrollBarValue() const = 0;
/**
* Set the position of the scrollbar
* @param value the new values of the scroll bars
*/
virtual void setScrollBarValue(const QPoint &value) = 0;
/**
* Update the range of scroll bars
*/
virtual void resetScrollBars() = 0;
/**
* Called when the size of your document in view coordinates (pixels) changes, for instance when zooming.
*
* @param newSize the new size, in view coordinates (pixels), of the document.
* @param recalculateCenter if true the offset in the document we center on after calling
* recenterPreferred() will be recalculated for the new document size so the visual offset stays the same.
*/
- virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter) = 0;
+ virtual void updateDocumentSize(const QSizeF &sz, bool recalculateCenter) = 0;
/**
* Set mouse wheel to zoom behaviour
* @param zoom if true wheel will zoom instead of scroll, control modifier will scroll
*/
virtual void setZoomWithWheel(bool zoom) = 0;
/**
* Set scroll area to be bigger than actual document.
* It allows the user to move the corner of the document
* to e.g. the center of the screen
*
* @param factor the coefficient, defining how much we can scroll out,
* measured in parts of the widget size. Null value means vast
* scrolling is disabled.
*/
virtual void setVastScrolling(qreal factor) = 0;
/**
* Returns the action collection for the window
* @returns action collection for this window, can be 0
*/
KActionCollection* actionCollection() const;
QPoint documentOffset() const;
/**
* @return the current position of the cursor fetched from QCursor::pos() and
* converted into document coordinates
*/
virtual QPointF currentCursorPosition() const = 0;
protected:
- void setDocumentSize(const QSize &sz);
- QSize documentSize() const;
+ void setDocumentSize(const QSizeF &sz);
+ QSizeF documentSize() const;
void setPreferredCenterFractionX(qreal);
qreal preferredCenterFractionX() const;
void setPreferredCenterFractionY(qreal);
qreal preferredCenterFractionY() const;
void setDocumentOffset( QPoint &offset);
private:
class Private;
Private * const d;
};
/**
* Workaround class for the problem that Qt does not allow two QObject base classes.
* KoCanvasController can be implemented by for instance QWidgets, so it cannot be
* a QObject directly. The interface of this class should be considered public interface
* for KoCanvasController.
*/
class KRITAFLAKE_EXPORT KoCanvasControllerProxyObject : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(KoCanvasControllerProxyObject)
public:
explicit KoCanvasControllerProxyObject(KoCanvasController *canvasController, QObject *parent = 0);
public:
// Convenience methods to invoke the signals from subclasses
void emitCanvasRemoved(KoCanvasController *canvasController) { emit canvasRemoved(canvasController); }
void emitCanvasSet(KoCanvasController *canvasController) { emit canvasSet(canvasController); }
void emitCanvasOffsetXChanged(int offset) { emit canvasOffsetXChanged(offset); }
void emitCanvasOffsetYChanged(int offset) { emit canvasOffsetYChanged(offset); }
void emitCanvasMousePositionChanged(const QPoint &position) { emit canvasMousePositionChanged(position); }
void emitDocumentMousePositionChanged(const QPointF &position) { emit documentMousePositionChanged(position); }
void emitSizeChanged(const QSize &size) { emit sizeChanged(size); }
void emitMoveDocumentOffset(const QPoint &point) { emit moveDocumentOffset(point); }
void emitZoomRelative(const qreal factor, const QPointF &stillPoint) { emit zoomRelative(factor, stillPoint); }
// Convenience method to retrieve the canvas controller for who needs to use QPointer
KoCanvasController *canvasController() const { return m_canvasController; }
Q_SIGNALS:
/**
* Emitted when a previously added canvas is about to be removed.
* @param canvasController this object
*/
void canvasRemoved(KoCanvasController *canvasController);
/**
* Emitted when a canvas is set on this widget
* @param canvasController this object
*/
void canvasSet(KoCanvasController *canvasController);
/**
* Emitted when canvasOffsetX() changes
* @param offset the new canvas offset
*/
void canvasOffsetXChanged(int offset);
/**
* Emitted when canvasOffsetY() changes
* @param offset the new canvas offset
*/
void canvasOffsetYChanged(int offset);
/**
* Emitted when the cursor is moved over the canvas widget.
* @param position the position in view coordinates (pixels).
*/
void canvasMousePositionChanged(const QPoint &position);
/**
* Emitted when the cursor is moved over the canvas widget.
* @param position the position in document coordinates.
*
* Use \ref canvasMousePositionChanged to get the position
* in view coordinates.
*/
void documentMousePositionChanged(const QPointF &position);
/**
* Emitted when the entire controller size changes
* @param size the size in widget pixels.
*/
void sizeChanged(const QSize &size);
/**
* Emitted whenever the document is scrolled.
*
* @param point the new top-left point from which the document should
* be drawn.
*/
void moveDocumentOffset(const QPoint &point);
/**
* Emitted when zoomRelativeToPoint have calculated a factor by which
* the zoom should change and the point which should stand still
* on screen.
* Someone needs to connect to this and take action
*
* @param factor by how much the zoom needs to change.
* @param stillPoint the point which will not change its position
* in widget during the zooming. It is measured in
* view coordinate system *before* zoom.
*/
void zoomRelative(const qreal factor, const QPointF &stillPoint);
public Q_SLOTS:
/**
* Call this slot whenever the size of your document in view coordinates (pixels)
* changes, for instance when zooming.
* @param newSize the new size, in view coordinates (pixels), of the document.
* @param recalculateCenter if true the offset in the document we center on after calling
* recenterPreferred() will be recalculated for the new document size so the visual offset stays the same.
*/
void updateDocumentSize(const QSize &newSize, bool recalculateCenter = true);
private:
KoCanvasController *m_canvasController;
};
class KRITAFLAKE_EXPORT KoDummyCanvasController : public KoCanvasController {
public:
explicit KoDummyCanvasController(KActionCollection* actionCollection)
: KoCanvasController(actionCollection)
{}
~KoDummyCanvasController() override
{}
void scrollContentsBy(int /*dx*/, int /*dy*/) override {}
- QSize viewportSize() const override { return QSize(); }
+ QSizeF viewportSize() const override { return QSizeF(); }
void setCanvas(KoCanvasBase *canvas) override {Q_UNUSED(canvas)}
KoCanvasBase *canvas() const override {return 0;}
int visibleHeight() const override {return 0;}
int visibleWidth() const override {return 0;}
int canvasOffsetX() const override {return 0;}
int canvasOffsetY() const override {return 0;}
void ensureVisible(const QRectF &/*rect*/, bool /*smooth */ = false) override {}
void ensureVisible(KoShape *shape) override {Q_UNUSED(shape)}
void zoomIn(const QPoint &/*center*/) override {}
void zoomOut(const QPoint &/*center*/) override {}
void zoomBy(const QPoint &/*center*/, qreal /*zoom*/) override {}
void zoomTo(const QRect &/*rect*/) override {}
void recenterPreferred() override {}
void setPreferredCenter(const QPointF &/*viewPoint*/) override {}
QPointF preferredCenter() const override {return QPointF();}
void pan(const QPoint &/*distance*/) override {}
void panUp() override {}
void panDown() override {}
void panLeft() override {}
void panRight() override {}
QPoint scrollBarValue() const override {return QPoint();}
void setScrollBarValue(const QPoint &/*value*/) override {}
void resetScrollBars() override {}
- void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) override {}
+ void updateDocumentSize(const QSizeF &/*sz*/, bool /*recalculateCenter*/) override {}
void setZoomWithWheel(bool /*zoom*/) override {}
void setVastScrolling(qreal /*factor*/) override {}
QPointF currentCursorPosition() const override { return QPointF(); }
};
#endif
diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp
index 095058743b..3cda255127 100644
--- a/libs/flake/KoCanvasControllerWidget.cpp
+++ b/libs/flake/KoCanvasControllerWidget.cpp
@@ -1,622 +1,626 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2008-2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 Peter Simonsson <peter.simonsson@gmail.com>
* Copyright (C) 2006, 2009 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
*
* 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 "KoCanvasControllerWidget.h"
#include "KoCanvasControllerWidget_p.h"
#include "KoCanvasControllerWidgetViewport_p.h"
#include "KoShape.h"
#include "KoViewConverter.h"
#include "KoCanvasBase.h"
#include "KoCanvasObserverBase.h"
#include "KoCanvasSupervisor.h"
#include "KoToolManager_p.h"
#include <FlakeDebug.h>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QEvent>
#include <QDockWidget>
#include <QTimer>
#include <QPointer>
#include <QOpenGLWidget>
#include <math.h>
void KoCanvasControllerWidget::Private::setDocumentOffset()
{
// The margins scroll the canvas widget inside the viewport, not
// the document. The documentOffset is meant to be the value that
// the canvas must add to the update rect in its paint event, to
// compensate.
QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value());
q->proxyObject->emitMoveDocumentOffset(pt);
QWidget *canvasWidget = canvas->canvasWidget();
if (canvasWidget) {
// If it isn't an OpenGL canvas
if (qobject_cast<QOpenGLWidget*>(canvasWidget) == 0) {
QPoint diff = q->documentOffset() - pt;
canvasWidget->scroll(diff.x(), diff.y(), canvasWidget->rect());
}
}
q->setDocumentOffset(pt);
}
void KoCanvasControllerWidget::Private::resetScrollBars()
{
// The scrollbar value always points at the top-left corner of the
// bit of image we paint.
- int docH = q->documentSize().height() + q->margin();
- int docW = q->documentSize().width() + q->margin();
+ int docH = (int)q->documentSize().height() + q->margin();
+ int docW = (int)q->documentSize().width() + q->margin();
int drawH = viewportWidget->height();
int drawW = viewportWidget->width();
QScrollBar *hScroll = q->horizontalScrollBar();
QScrollBar *vScroll = q->verticalScrollBar();
int horizontalReserve = vastScrollingFactor * drawW;
int verticalReserve = vastScrollingFactor * drawH;
int xMin = -horizontalReserve;
int yMin = -verticalReserve;
int xMax = docW - drawW + horizontalReserve;
int yMax = docH - drawH + verticalReserve;
hScroll->setRange(xMin, xMax);
vScroll->setRange(yMin, yMax);
int fontheight = QFontMetrics(q->font()).height();
vScroll->setPageStep(drawH);
vScroll->setSingleStep(fontheight);
hScroll->setPageStep(drawW);
hScroll->setSingleStep(fontheight);
}
void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event)
{
if (!canvas) return;
if (!canvas->viewConverter()) return;
QPoint pointerPos;
QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
if (mouseEvent) {
pointerPos = mouseEvent->pos();
} else {
QTabletEvent *tabletEvent = dynamic_cast<QTabletEvent*>(event);
if (tabletEvent) {
pointerPos = tabletEvent->pos();
}
}
QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset();
QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos);
q->proxyObject->emitDocumentMousePositionChanged(documentPos);
q->proxyObject->emitCanvasMousePositionChanged(pointerPos);
}
#include <QTime>
void KoCanvasControllerWidget::Private::activate()
{
QWidget *parent = q;
while (parent->parentWidget()) {
parent = parent->parentWidget();
}
KoCanvasSupervisor *observerProvider = dynamic_cast<KoCanvasSupervisor*>(parent);
if (!observerProvider) {
return;
}
KoCanvasBase *canvas = q->canvas();
Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker);
if (observer) {
observer->setObservedCanvas(canvas);
}
}
}
void KoCanvasControllerWidget::Private::unsetCanvas()
{
QWidget *parent = q;
while (parent->parentWidget()) {
parent = parent->parentWidget();
}
KoCanvasSupervisor *observerProvider = dynamic_cast<KoCanvasSupervisor*>(parent);
if (!observerProvider) {
return;
}
Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker);
if (observer) {
if (observer->observedCanvas() == q->canvas()) {
observer->unsetObservedCanvas();
}
}
}
}
////////////
KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent)
: QAbstractScrollArea(parent)
, KoCanvasController(actionCollection)
, d(new Private(this))
{
// We need to set this as QDeclarativeView sets them a bit different from QAbstractScrollArea
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// And then our own Viewport
d->viewportWidget = new Viewport(this);
setViewport(d->viewportWidget);
d->viewportWidget->setFocusPolicy(Qt::NoFocus);
setFocusPolicy(Qt::NoFocus);
setFrameStyle(0);
//setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setAutoFillBackground(false);
/*
Fixes: apps starting at zero zoom.
Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose
to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the
widget means we no longer get rounding errors in zooming and we no longer end up with zero-zoom.
Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible
*/
setMinimumSize(QSize(50, 50));
setMouseTracking(true);
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX()));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY()));
connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX()));
connect(proxyObject, SIGNAL(moveDocumentOffset(QPoint)), d->viewportWidget, SLOT(documentOffsetMoved(QPoint)));
}
KoCanvasControllerWidget::~KoCanvasControllerWidget()
{
delete d;
}
void KoCanvasControllerWidget::activate()
{
d->activate();
}
void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy)
{
Q_UNUSED(dx);
Q_UNUSED(dy);
d->setDocumentOffset();
}
-QSize KoCanvasControllerWidget::viewportSize() const
+QSizeF KoCanvasControllerWidget::viewportSize() const
{
- return viewport()->size();
+ // Calculate viewport size aligned to device pixels to match KisOpenGLCanvas2.
+ qreal dpr = viewport()->devicePixelRatioF();
+ int viewportWidth = static_cast<int>(viewport()->width() * dpr);
+ int viewportHeight = static_cast<int>(viewport()->height() * dpr);
+ return QSizeF(viewportWidth / dpr, viewportHeight / dpr);
}
void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent)
{
proxyObject->emitSizeChanged(resizeEvent->size());
// XXX: When resizing, keep the area we're looking at now in the
// center of the resized view.
resetScrollBars();
d->setDocumentOffset();
}
void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas)
{
if (d->canvas) {
d->unsetCanvas();
proxyObject->emitCanvasRemoved(this);
d->canvas->setCanvasController(0);
d->canvas->canvasWidget()->removeEventFilter(this);
}
d->canvas = canvas;
if (d->canvas) {
d->canvas->setCanvasController(this);
changeCanvasWidget(d->canvas->canvasWidget());
proxyObject->emitCanvasSet(this);
QTimer::singleShot(0, this, SLOT(activate()));
setPreferredCenterFractionX(0);
setPreferredCenterFractionY(0);
}
}
KoCanvasBase* KoCanvasControllerWidget::canvas() const
{
if (d->canvas.isNull()) return 0;
return d->canvas;
}
void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget)
{
if (d->viewportWidget->canvas()) {
widget->setCursor(d->viewportWidget->canvas()->cursor());
d->viewportWidget->canvas()->removeEventFilter(this);
}
d->viewportWidget->setCanvas(widget);
setFocusProxy(d->canvas->canvasWidget());
}
int KoCanvasControllerWidget::visibleHeight() const
{
if (d->canvas == 0)
return 0;
QWidget *canvasWidget = canvas()->canvasWidget();
int height1;
if (canvasWidget == 0)
height1 = viewport()->height();
else
height1 = qMin(viewport()->height(), canvasWidget->height());
int height2 = height();
return qMin(height1, height2);
}
int KoCanvasControllerWidget::visibleWidth() const
{
if (d->canvas == 0)
return 0;
QWidget *canvasWidget = canvas()->canvasWidget();
int width1;
if (canvasWidget == 0)
width1 = viewport()->width();
else
width1 = qMin(viewport()->width(), canvasWidget->width());
int width2 = width();
return qMin(width1, width2);
}
int KoCanvasControllerWidget::canvasOffsetX() const
{
int offset = -horizontalScrollBar()->value();
if (d->canvas) {
offset += d->canvas->canvasWidget()->x() + frameWidth();
}
return offset;
}
int KoCanvasControllerWidget::canvasOffsetY() const
{
int offset = -verticalScrollBar()->value();
if (d->canvas) {
offset += d->canvas->canvasWidget()->y() + frameWidth();
}
return offset;
}
void KoCanvasControllerWidget::updateCanvasOffsetX()
{
proxyObject->emitCanvasOffsetXChanged(canvasOffsetX());
if (d->ignoreScrollSignals)
return;
setPreferredCenterFractionX((horizontalScrollBar()->value()
+ viewport()->width() / 2.0) / documentSize().width());
}
void KoCanvasControllerWidget::updateCanvasOffsetY()
{
proxyObject->emitCanvasOffsetYChanged(canvasOffsetY());
if (d->ignoreScrollSignals)
return;
setPreferredCenterFractionY((verticalScrollBar()->value()
+ verticalScrollBar()->pageStep() / 2.0) / documentSize().height());
}
void KoCanvasControllerWidget::ensureVisible(KoShape *shape)
{
Q_ASSERT(shape);
ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect()));
}
void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth)
{
QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight());
QRect viewRect = rect.toRect();
viewRect.translate(d->canvas->documentOrigin());
if (!viewRect.isValid() || currentVisible.contains(viewRect))
return; // its visible. Nothing to do.
// if we move, we move a little more so the amount of times we have to move is less.
int jumpWidth = smooth ? 0 : currentVisible.width() / 5;
int jumpHeight = smooth ? 0 : currentVisible.height() / 5;
if (!smooth && viewRect.width() + jumpWidth > currentVisible.width())
jumpWidth = 0;
if (!smooth && viewRect.height() + jumpHeight > currentVisible.height())
jumpHeight = 0;
int horizontalMove = 0;
if (currentVisible.width() <= viewRect.width()) // center view
horizontalMove = viewRect.center().x() - currentVisible.center().x();
else if (currentVisible.x() > viewRect.x()) // move left
horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth;
else if (currentVisible.right() < viewRect.right()) // move right
horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth);
int verticalMove = 0;
if (currentVisible.height() <= viewRect.height()) // center view
verticalMove = viewRect.center().y() - currentVisible.center().y();
if (currentVisible.y() > viewRect.y()) // move up
verticalMove = viewRect.y() - currentVisible.y() - jumpHeight;
else if (currentVisible.bottom() < viewRect.bottom()) // move down
verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight);
pan(QPoint(horizontalMove, verticalMove));
}
void KoCanvasControllerWidget::recenterPreferred()
{
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
QPointF center = preferredCenter();
// convert into a viewport based point
center.rx() += d->canvas->canvasWidget()->x() + frameWidth();
center.ry() += d->canvas->canvasWidget()->y() + frameWidth();
// scroll to a new center point
QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height());
setScrollBarValue(topLeft.toPoint());
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::zoomIn(const QPoint &center)
{
zoomBy(center, sqrt(2.0));
}
void KoCanvasControllerWidget::zoomOut(const QPoint &center)
{
zoomBy(center, sqrt(0.5));
}
void KoCanvasControllerWidget::zoomBy(const QPoint &center, qreal zoom)
{
setPreferredCenterFractionX(1.0 * center.x() / documentSize().width());
setPreferredCenterFractionY(1.0 * center.y() / documentSize().height());
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
proxyObject->emitZoomRelative(zoom, preferredCenter());
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::zoomTo(const QRect &viewRect)
{
qreal scale;
if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height())
scale = 1.0 * viewport()->height() / viewRect.height();
else
scale = 1.0 * viewport()->width() / viewRect.width();
zoomBy(viewRect.center(), scale);
}
-void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter)
+void KoCanvasControllerWidget::updateDocumentSize(const QSizeF &sz, bool recalculateCenter)
{
// Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates.
if (KoCanvasController::documentSize() == sz)
return;
if (!recalculateCenter) {
// assume the distance from the top stays equal and recalculate the center.
setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width());
setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height());
}
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
KoCanvasController::setDocumentSize(sz);
d->viewportWidget->setDocumentSize(sz);
resetScrollBars();
// Always emit the new offset.
updateCanvasOffsetX();
updateCanvasOffsetY();
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::setZoomWithWheel(bool zoom)
{
d->zoomWithWheel = zoom;
}
void KoCanvasControllerWidget::setVastScrolling(qreal factor)
{
d->vastScrollingFactor = factor;
}
QPointF KoCanvasControllerWidget::currentCursorPosition() const
{
QWidget *canvasWidget = d->canvas->canvasWidget();
const KoViewConverter *converter = d->canvas->viewConverter();
return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos());
}
void KoCanvasControllerWidget::pan(const QPoint &distance)
{
QPoint sourcePoint = scrollBarValue();
setScrollBarValue(sourcePoint + distance);
}
void KoCanvasControllerWidget::panUp()
{
pan(QPoint(0, verticalScrollBar()->singleStep()));
}
void KoCanvasControllerWidget::panDown()
{
pan(QPoint(0, -verticalScrollBar()->singleStep()));
}
void KoCanvasControllerWidget::panLeft()
{
pan(QPoint(horizontalScrollBar()->singleStep(), 0));
}
void KoCanvasControllerWidget::panRight()
{
pan(QPoint(-horizontalScrollBar()->singleStep(), 0));
}
void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint)
{
setPreferredCenterFractionX(viewPoint.x() / documentSize().width());
setPreferredCenterFractionY(viewPoint.y() / documentSize().height());
recenterPreferred();
}
QPointF KoCanvasControllerWidget::preferredCenter() const
{
QPointF center;
center.setX(preferredCenterFractionX() * documentSize().width());
center.setY(preferredCenterFractionY() * documentSize().height());
return center;
}
void KoCanvasControllerWidget::paintEvent(QPaintEvent *event)
{
QPainter gc(viewport());
d->viewportWidget->handlePaintEvent(gc, event);
}
void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event)
{
d->viewportWidget->handleDragEnterEvent(event);
}
void KoCanvasControllerWidget::dropEvent(QDropEvent *event)
{
d->viewportWidget->handleDropEvent(event);
}
void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event)
{
d->viewportWidget->handleDragMoveEvent(event);
}
void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event)
{
d->viewportWidget->handleDragLeaveEvent(event);
}
void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event)
{
if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) {
const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5);
zoomRelativeToPoint(event->pos(), zoomCoeff);
event->accept();
} else
QAbstractScrollArea::wheelEvent(event);
}
void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff)
{
const QPoint offset = scrollBarValue();
const QPoint mousePos(widgetPoint + offset);
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
proxyObject->emitZoomRelative(zoomCoeff, mousePos);
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
bool KoCanvasControllerWidget::focusNextPrevChild(bool)
{
// we always return false meaning the canvas takes keyboard focus, but never gives it away.
return false;
}
void KoCanvasControllerWidget::setMargin(int margin)
{
KoCanvasController::setMargin(margin);
Q_ASSERT(d->viewportWidget);
d->viewportWidget->setMargin(margin);
}
QPoint KoCanvasControllerWidget::scrollBarValue() const
{
QScrollBar * hBar = horizontalScrollBar();
QScrollBar * vBar = verticalScrollBar();
return QPoint(hBar->value(), vBar->value());
}
void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value)
{
QScrollBar * hBar = horizontalScrollBar();
QScrollBar * vBar = verticalScrollBar();
hBar->setValue(value.x());
vBar->setValue(value.y());
}
void KoCanvasControllerWidget::resetScrollBars()
{
d->resetScrollBars();
}
qreal KoCanvasControllerWidget::vastScrollingFactor() const
{
return d->vastScrollingFactor;
}
KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv()
{
return d;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoCanvasControllerWidget.cpp"
diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h
index f134fed94c..a74e5f109b 100644
--- a/libs/flake/KoCanvasControllerWidget.h
+++ b/libs/flake/KoCanvasControllerWidget.h
@@ -1,193 +1,193 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2008 Thomas Zander <zander@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007-2008 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006-2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2009 Thorsten Zachmann <zachmann@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License 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 KOCANVASCONTROLLERWIDGET_H
#define KOCANVASCONTROLLERWIDGET_H
#include "kritaflake_export.h"
#include <QAbstractScrollArea>
#include <QPointer>
#include "KoCanvasController.h"
class KoShape;
class KoCanvasBase;
/**
* KoCanvasController implementation for QWidget based canvases
*/
class KRITAFLAKE_EXPORT KoCanvasControllerWidget : public QAbstractScrollArea, public KoCanvasController
{
Q_OBJECT
public:
/**
* Constructor.
* @param actionCollection the action collection for this widget
* @param parent the parent this widget will belong to
*/
explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0);
~KoCanvasControllerWidget() override;
/**
* Reimplemented from QAbstractScrollArea.
*/
void scrollContentsBy(int dx, int dy) override;
- QSize viewportSize() const override;
+ QSizeF viewportSize() const override;
/// Reimplemented from KoCanvasController
/**
* Activate this canvascontroller
*/
virtual void activate();
void setCanvas(KoCanvasBase *canvas) override;
KoCanvasBase *canvas() const override;
/**
* Change the actual canvas widget used by the current canvas. This allows the canvas widget
* to be changed while keeping the current KoCanvasBase canvas and its associated resources as
* they are. This might be used, for example, to switch from a QWidget to a QOpenGLWidget canvas.
* @param widget the new canvas widget.
*/
virtual void changeCanvasWidget(QWidget *widget);
int visibleHeight() const override;
int visibleWidth() const override;
int canvasOffsetX() const override;
int canvasOffsetY() const override;
void ensureVisible(const QRectF &rect, bool smooth = false) override;
void ensureVisible(KoShape *shape) override;
/**
* will cause the toolOptionWidgetsChanged to be emitted and all
* listeners to be updated to the new widget.
*
* FIXME: This doesn't belong her and it does an
* inherits("KoView") so it too much tied to komain
*
* @param widgets the map of widgets
*/
void setToolOptionWidgets(const QList<QPointer<QWidget> > &widgets);
void zoomIn(const QPoint &center) override;
void zoomOut(const QPoint &center) override;
void zoomBy(const QPoint &center, qreal zoom) override;
void zoomTo(const QRect &rect) override;
/**
* Zoom document keeping point \p widgetPoint unchanged
* \param widgetPoint sticky point in widget pixels
* \param zoomCoeff the zoom
*/
virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff);
void recenterPreferred() override;
void setPreferredCenter(const QPointF &viewPoint) override;
/// Returns the currently set preferred center point in view coordinates (pixels)
QPointF preferredCenter() const override;
void pan(const QPoint &distance) override;
virtual void panUp() override;
virtual void panDown() override;
virtual void panLeft() override;
virtual void panRight() override;
void setMargin(int margin) override;
QPoint scrollBarValue() const override;
/**
* Used by KisCanvasController to correct the scrollbars position
* after the rotation.
*/
void setScrollBarValue(const QPoint &value) override;
- void updateDocumentSize(const QSize &sz, bool recalculateCenter = true) override;
+ void updateDocumentSize(const QSizeF &sz, bool recalculateCenter = true) override;
/**
* Set mouse wheel to zoom behaviour
* @param zoom if true wheel will zoom instead of scroll, control modifier will scroll
*/
void setZoomWithWheel(bool zoom) override;
void setVastScrolling(qreal factor) override;
QPointF currentCursorPosition() const override;
void resetScrollBars() override;
/**
* \internal
*/
class Private;
KoCanvasControllerWidget::Private *priv();
private Q_SLOTS:
/// Called by the horizontal scrollbar when its value changes
void updateCanvasOffsetX();
/// Called by the vertical scrollbar when its value changes
void updateCanvasOffsetY();
protected:
friend class KisZoomAndPanTest;
qreal vastScrollingFactor() const;
/// reimplemented from QWidget
void paintEvent(QPaintEvent *event) override;
/// reimplemented from QWidget
void resizeEvent(QResizeEvent *resizeEvent) override;
/// reimplemented from QWidget
void dragEnterEvent(QDragEnterEvent *event) override;
/// reimplemented from QWidget
void dropEvent(QDropEvent *event) override;
/// reimplemented from QWidget
void dragMoveEvent(QDragMoveEvent *event) override;
/// reimplemented from QWidget
void dragLeaveEvent(QDragLeaveEvent *event) override;
/// reimplemented from QWidget
void wheelEvent(QWheelEvent *event) override;
/// reimplemented from QWidget
bool focusNextPrevChild(bool next) override;
private:
Q_PRIVATE_SLOT(d, void activate())
Private * const d;
};
#endif
diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
index e64484997f..ef532a43bb 100644
--- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
+++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
@@ -1,332 +1,332 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoCanvasControllerWidgetViewport_p.h"
#include <limits.h>
#include <stdlib.h>
#include <QPainter>
#include <QDragEnterEvent>
#include <QMimeData>
#include <KoProperties.h>
#include <FlakeDebug.h>
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeFactoryBase.h" // for the SHAPE mimetypes
#include "KoShapeRegistry.h"
#include "KoShapeController.h"
#include "KoShapeManager.h"
#include "KoSelection.h"
#include "KoCanvasBase.h"
#include "KoShapeLayer.h"
#include "KoShapePaintingContext.h"
#include "KoToolProxy.h"
#include "KoCanvasControllerWidget.h"
#include "KoViewConverter.h"
#include "KoSvgPaste.h"
// ********** Viewport **********
Viewport::Viewport(KoCanvasControllerWidget *parent)
: QWidget(parent)
, m_draggedShape(0)
, m_canvas(0)
, m_documentOffset(QPoint(0, 0))
, m_margin(0)
{
setAutoFillBackground(true);
setAcceptDrops(true);
setMouseTracking(true);
m_parent = parent;
}
void Viewport::setCanvas(QWidget *canvas)
{
if (m_canvas) {
m_canvas->hide();
delete m_canvas;
}
m_canvas = canvas;
if (!canvas) return;
m_canvas->setParent(this);
m_canvas->show();
if (!m_canvas->minimumSize().isNull()) {
m_documentSize = m_canvas->minimumSize();
}
resetLayout();
}
-void Viewport::setDocumentSize(const QSize &size)
+void Viewport::setDocumentSize(const QSizeF &size)
{
m_documentSize = size;
resetLayout();
}
void Viewport::documentOffsetMoved(const QPoint &pt)
{
m_documentOffset = pt;
resetLayout();
}
void Viewport::handleDragEnterEvent(QDragEnterEvent *event)
{
// if not a canvas set then ignore this, makes it possible to assume
// we have a canvas in all the support methods.
if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) {
event->ignore();
return;
}
delete m_draggedShape;
m_draggedShape = 0;
// only allow dropping when active layer is editable
KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
KoShapeLayer *activeLayer = selection->activeLayer();
if (activeLayer && (!activeLayer->isShapeEditable() || activeLayer->isGeometryProtected())) {
event->ignore();
return;
}
const QMimeData *data = event->mimeData();
if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) ||
data->hasFormat(SHAPEID_MIMETYPE) ||
data->hasFormat("image/svg+xml"))
{
if (data->hasFormat("image/svg+xml")) {
KoCanvasBase *canvas = m_parent->canvas();
QSizeF fragmentSize;
QList<KoShape*> shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"),
canvas->shapeController()->documentRectInPixels(),
canvas->shapeController()->pixelsPerInch(),
&fragmentSize);
if (!shapes.isEmpty()) {
m_draggedShape = shapes[0];
}
}
else {
QByteArray itemData;
bool isTemplate = true;
if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) {
itemData = data->data(SHAPETEMPLATE_MIMETYPE);
}
else if (data->hasFormat(SHAPEID_MIMETYPE)) {
isTemplate = false;
itemData = data->data(SHAPEID_MIMETYPE);
}
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
QString id;
dataStream >> id;
QString properties;
if (isTemplate)
dataStream >> properties;
// and finally, there is a point.
QPointF offset;
dataStream >> offset;
// The rest of this method is mostly a copy paste from the KoCreateShapeStrategy
// So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave)
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id);
if (! factory) {
warnFlake << "Application requested a shape that is not registered '" <<
id << "', Ignoring";
event->ignore();
return;
}
if (isTemplate) {
KoProperties props;
props.load(properties);
m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager());
}
else {
m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager());
}
if (m_draggedShape->shapeId().isEmpty()) {
m_draggedShape->setShapeId(factory->id());
}
}
event->setDropAction(Qt::CopyAction);
event->accept();
Q_ASSERT(m_draggedShape);
if (!m_draggedShape) return;
// calculate maximum existing shape zIndex
int pasteZIndex = 0;
{
QList<KoShape*> allShapes = m_parent->canvas()->shapeManager()->topLevelShapes();
if (!allShapes.isEmpty()) {
std::sort(allShapes.begin(), allShapes.end(), KoShape::compareShapeZIndex);
pasteZIndex = qMin(int(KoShape::maxZIndex), allShapes.last()->zIndex() + 1);
}
}
m_draggedShape->setZIndex(pasteZIndex);
m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
m_parent->canvas()->shapeManager()->addShape(m_draggedShape);
} else {
event->ignore();
}
}
void Viewport::handleDropEvent(QDropEvent *event)
{
if (!m_draggedShape) {
m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos()));
return;
}
repaint(m_draggedShape);
m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc.
m_draggedShape->setPosition(QPointF(0, 0)); // always save position.
QPointF newPos = correctPosition(event->pos());
m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document.
m_draggedShape->setAbsolutePosition(newPos);
KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0);
if (cmd) {
m_parent->canvas()->addCommand(cmd);
KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
// repaint selection before selecting newly create shape
Q_FOREACH (KoShape * shape, selection->selectedShapes()) {
shape->update();
}
selection->deselectAll();
selection->select(m_draggedShape);
} else {
delete m_draggedShape;
}
m_draggedShape = 0;
}
QPointF Viewport::correctPosition(const QPoint &point) const
{
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y());
correctedPos += m_documentOffset;
return m_parent->canvas()->viewToDocument(correctedPos);
}
void Viewport::handleDragMoveEvent(QDragMoveEvent *event)
{
if (!m_draggedShape) {
m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos()));
return;
}
m_draggedShape->update();
repaint(m_draggedShape);
m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
m_draggedShape->update();
repaint(m_draggedShape);
}
void Viewport::repaint(KoShape *shape)
{
QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect();
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x());
rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y());
rect.adjust(-2, -2, 2, 2); // adjust for antialias
update(rect);
}
void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event)
{
if (m_draggedShape) {
repaint(m_draggedShape);
m_parent->canvas()->shapeManager()->remove(m_draggedShape);
delete m_draggedShape;
m_draggedShape = 0;
} else {
m_parent->canvas()->toolProxy()->dragLeaveEvent(event);
}
}
void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event)
{
Q_UNUSED(event);
if (m_draggedShape) {
const KoViewConverter *vc = m_parent->canvas()->viewConverter();
painter.save();
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
painter.translate(canvasWidget->x() - m_documentOffset.x(),
canvasWidget->y() - m_documentOffset.y());
QPointF offset = vc->documentToView(m_draggedShape->position());
painter.setOpacity(0.6);
painter.translate(offset.x(), offset.y());
painter.setRenderHint(QPainter::Antialiasing);
KoShapePaintingContext paintContext; //FIXME
m_draggedShape->paint(painter, *vc, paintContext);
painter.restore();
}
}
void Viewport::resetLayout()
{
// Determine the area we have to show
QRect viewRect(m_documentOffset, size());
const int viewH = viewRect.height();
const int viewW = viewRect.width();
if (m_canvas) {
QRect geom = QRect(0, 0, viewW, viewH);
if (m_canvas->geometry() != geom) {
m_canvas->setGeometry(geom);
m_canvas->update();
}
}
emit sizeChanged();
#if 0
debugFlake <<"View port geom:" << geometry();
if (m_canvas)
debugFlake <<"Canvas widget geom:" << m_canvas->geometry();
#endif
}
diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.h b/libs/flake/KoCanvasControllerWidgetViewport_p.h
index 5c56595b07..4ea532daaa 100644
--- a/libs/flake/KoCanvasControllerWidgetViewport_p.h
+++ b/libs/flake/KoCanvasControllerWidgetViewport_p.h
@@ -1,85 +1,85 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
*
* 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 KOCANVASCONTROLLERWIDGETVIEWPORT_P_H
#define KOCANVASCONTROLLERWIDGETVIEWPORT_P_H
#include <QWidget>
#include <QSize>
#include <QPoint>
class KoCanvasControllerWidget;
class KoShape;
class Viewport : public QWidget
{
Q_OBJECT
public:
explicit Viewport(KoCanvasControllerWidget *parent);
~Viewport() override {}
void setCanvas(QWidget *canvas);
QWidget *canvas() const {
return m_canvas;
}
- void setDocumentSize(const QSize &size);
+ void setDocumentSize(const QSizeF &size);
public Q_SLOTS:
void documentOffsetMoved(const QPoint &);
Q_SIGNALS:
void sizeChanged();
public:
void handleDragEnterEvent(QDragEnterEvent *event);
void handleDropEvent(QDropEvent *event);
void handleDragMoveEvent(QDragMoveEvent *event);
void handleDragLeaveEvent(QDragLeaveEvent *event);
void handlePaintEvent(QPainter &gc, QPaintEvent *event);
void setMargin(int margin) { m_margin = margin; resetLayout(); }
private:
QPointF correctPosition(const QPoint &point) const;
void repaint(KoShape *shape);
/**
Decides whether the containing canvas widget should be as
big as the viewport (i.e., no margins are visible) or whether
there are margins to be left blank, and then places the canvas
widget accordingly.
*/
void resetLayout();
private:
KoCanvasControllerWidget *m_parent;
KoShape *m_draggedShape;
QWidget *m_canvas;
- QSize m_documentSize; // Size in pixels of the document
+ QSizeF m_documentSize; // Size in pixels of the document
QPoint m_documentOffset; // Place where the canvas widget should
int m_margin; // The viewport margin around the document
};
#endif
diff --git a/libs/flake/KoDocumentResourceManager.cpp b/libs/flake/KoDocumentResourceManager.cpp
index 15c791ed51..553f3f8785 100644
--- a/libs/flake/KoDocumentResourceManager.cpp
+++ b/libs/flake/KoDocumentResourceManager.cpp
@@ -1,201 +1,214 @@
/*
Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
Copyright (C) 2007, 2010 Thomas Zander <zander@kde.org>
Copyright (c) 2008 Carlos Licea <carlos.licea@kdemail.net>
Copyright (c) 2011 Jan Hambrecht <jaham@gmx.net>
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 "KoDocumentResourceManager.h"
#include <QVariant>
#include <kundo2stack.h>
#include <FlakeDebug.h>
#include "KoShape.h"
#include "KoShapeController.h"
#include "KoResourceManager_p.h"
class Q_DECL_HIDDEN KoDocumentResourceManager::Private
{
public:
KoResourceManager manager;
};
KoDocumentResourceManager::KoDocumentResourceManager(QObject *parent)
: QObject(parent),
d(new Private())
{
connect(&d->manager, &KoResourceManager::resourceChanged,
this, &KoDocumentResourceManager::resourceChanged);
}
KoDocumentResourceManager::~KoDocumentResourceManager()
{
delete d;
}
void KoDocumentResourceManager::setResource(int key, const QVariant &value)
{
d->manager.setResource(key, value);
}
QVariant KoDocumentResourceManager::resource(int key) const
{
return d->manager.resource(key);
}
void KoDocumentResourceManager::setResource(int key, const KoColor &color)
{
QVariant v;
v.setValue(color);
setResource(key, v);
}
void KoDocumentResourceManager::setResource(int key, KoShape *shape)
{
QVariant v;
v.setValue(shape);
setResource(key, v);
}
void KoDocumentResourceManager::setResource(int key, const KoUnit &unit)
{
QVariant v;
v.setValue(unit);
setResource(key, v);
}
KoColor KoDocumentResourceManager::koColorResource(int key) const
{
return d->manager.koColorResource(key);
}
bool KoDocumentResourceManager::boolResource(int key) const
{
return d->manager.boolResource(key);
}
int KoDocumentResourceManager::intResource(int key) const
{
return d->manager.intResource(key);
}
QString KoDocumentResourceManager::stringResource(int key) const
{
return d->manager.stringResource(key);
}
QSizeF KoDocumentResourceManager::sizeResource(int key) const
{
return d->manager.sizeResource(key);
}
bool KoDocumentResourceManager::hasResource(int key) const
{
return d->manager.hasResource(key);
}
void KoDocumentResourceManager::clearResource(int key)
{
d->manager.clearResource(key);
}
KUndo2Stack *KoDocumentResourceManager::undoStack() const
{
if (!hasResource(UndoStack))
return 0;
return static_cast<KUndo2Stack*>(resource(UndoStack).value<void*>());
}
void KoDocumentResourceManager::setHandleRadius(int handleRadius)
{
// do not allow arbitrary small handles
if (handleRadius < 5)
handleRadius = 5;
setResource(HandleRadius, QVariant(handleRadius));
}
int KoDocumentResourceManager::handleRadius() const
{
if (hasResource(HandleRadius))
return intResource(HandleRadius);
return 5; // default value (and is used just about everywhere)
}
void KoDocumentResourceManager::setGrabSensitivity(int grabSensitivity)
{
// do not allow arbitrary small grab sensitivity
if (grabSensitivity < 5)
grabSensitivity = 5;
setResource(GrabSensitivity, QVariant(grabSensitivity));
}
int KoDocumentResourceManager::grabSensitivity() const
{
if (hasResource(GrabSensitivity))
return intResource(GrabSensitivity);
return 5; // default value (and is used just about everywhere)
}
void KoDocumentResourceManager::setUndoStack(KUndo2Stack *undoStack)
{
QVariant variant;
variant.setValue<void*>(undoStack);
setResource(UndoStack, variant);
}
KoImageCollection *KoDocumentResourceManager::imageCollection() const
{
if (!hasResource(ImageCollection))
return 0;
return static_cast<KoImageCollection*>(resource(ImageCollection).value<void*>());
}
void KoDocumentResourceManager::setImageCollection(KoImageCollection *ic)
{
QVariant variant;
variant.setValue<void*>(ic);
setResource(ImageCollection, variant);
}
KoDocumentBase *KoDocumentResourceManager::odfDocument() const
{
if (!hasResource(OdfDocument))
return 0;
return static_cast<KoDocumentBase*>(resource(OdfDocument).value<void*>());
}
void KoDocumentResourceManager::setOdfDocument(KoDocumentBase *currentDocument)
{
QVariant variant;
variant.setValue<void*>(currentDocument);
setResource(OdfDocument, variant);
}
-KoShapeController *KoDocumentResourceManager::shapeController() const
+qreal KoDocumentResourceManager::documentResolution() const
{
- if (!hasResource(ShapeController))
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(hasResource(DocumentResolution), 72.0);
+ return resource(DocumentResolution).toReal();
+}
+
+QRectF KoDocumentResourceManager::documentRectInPixels() const
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(hasResource(DocumentRectInPixels), QRectF(0,0, 777, 666));
+ return resource(DocumentRectInPixels).toRectF();
+}
+
+KoShapeController *KoDocumentResourceManager::globalShapeController() const
+{
+ if (!hasResource(GlobalShapeController))
return 0;
- return resource(ShapeController).value<KoShapeController *>();
+
+ return resource(GlobalShapeController).value<KoShapeController *>();
}
-void KoDocumentResourceManager::setShapeController(KoShapeController *shapeController)
+void KoDocumentResourceManager::setGlobalShapeController(KoShapeController *shapeController)
{
QVariant variant;
variant.setValue<KoShapeController *>(shapeController);
- setResource(ShapeController, variant);
+ setResource(GlobalShapeController, variant);
}
diff --git a/libs/flake/KoDocumentResourceManager.h b/libs/flake/KoDocumentResourceManager.h
index a68befc906..7c44583f58 100644
--- a/libs/flake/KoDocumentResourceManager.h
+++ b/libs/flake/KoDocumentResourceManager.h
@@ -1,253 +1,263 @@
/*
Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
Copyright (C) 2007, 2009, 2010 Thomas Zander <zander@kde.org>
Copyright (c) 2008 Carlos Licea <carlos.licea@kdemail.net>
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 KO_DOCUMENTRESOURCEMANAGER_H
#define KO_DOCUMENTRESOURCEMANAGER_H
#include <QObject>
#include "kritaflake_export.h"
class KoShape;
class KUndo2Stack;
class KoImageCollection;
class KoDocumentBase;
class KoShapeController;
class KoColor;
class KoUnit;
class QVariant;
class QSizeF;
/**
* The KoResourceManager contains a set of per-canvas <i>or</i> per-document
* properties, like current foreground color, current background
* color and more. All tools belonging to the current canvas are
* notified when a Resource changes (is set).
*
* The properties come from the KoDocumentResourceManager::DocumentResource
* See KoShapeController::resourceManager
*
* The manager can contain all sorts of variable types and there are accessors
* for the most common ones. All variables are always stored inside a QVariant
* instance internally and you can always just use the resource() method to get
* that directly.
* The way to store arbitrary data objects that are stored as pointers you can use
* the following code snippets;
* @code
* QVariant variant;
* variant.setValue<void*>(textShapeData->document());
* resourceManager->setResource(KoText::CurrentTextDocument, variant);
* // and get it out again.
* QVariant var = resourceManager->resource(KoText::CurrentTextDocument);
* document = static_cast<QTextDocument*>(var.value<void*>());
* @endcode
*/
class KRITAFLAKE_EXPORT KoDocumentResourceManager : public QObject
{
Q_OBJECT
public:
/**
* This enum holds identifiers to the resources that can be stored in here.
*/
enum DocumentResource {
UndoStack, ///< The document-wide undo stack (KUndo2Stack)
ImageCollection, ///< The KoImageCollection for the document
OdfDocument, ///< The document this canvas shows (KoDocumentBase)
HandleRadius, ///< The handle radius used for drawing handles of any kind
GrabSensitivity, ///< The grab sensitivity used for grabbing handles of any kind
MarkerCollection, ///< The collection holding all markers
- ShapeController, ///< The KoShapeController for the document
+ GlobalShapeController, ///< The KoShapeController for the document
+ DocumentResolution, ///< Pixels-per-inch resoluton of the document
+ DocumentRectInPixels, ///< Bounds of the document in pixels
KarbonStart = 1000, ///< Base number for Karbon specific values.
KexiStart = 2000, ///< Base number for Kexi specific values.
FlowStart = 3000, ///< Base number for Flow specific values.
PlanStart = 4000, ///< Base number for Plan specific values.
StageStart = 5000, ///< Base number for Stage specific values.
KritaStart = 6000, ///< Base number for Krita specific values.
SheetsStart = 7000, ///< Base number for Sheets specific values.
WordsStart = 8000, ///< Base number for Words specific values.
KoPageAppStart = 9000, ///< Base number for KoPageApp specific values.
KoTextStart = 10000 ///< Base number for KoText specific values.
};
/**
* Constructor.
* @param parent the parent QObject, used for memory management.
*/
explicit KoDocumentResourceManager(QObject *parent = 0);
~KoDocumentResourceManager() override;
/**
* Set a resource of any type.
* @param key the integer key
* @param value the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, const QVariant &value);
/**
* Set a resource of type KoColor.
* @param key the integer key
* @param color the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, const KoColor &color);
/**
* Set a resource of type KoShape*.
* @param key the integer key
* @param id the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, KoShape *shape);
/**
* Set a resource of type KoUnit
* @param key the integer key
* @param id the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, const KoUnit &unit);
/**
* Returns a qvariant containing the specified resource or a standard one if the
* specified resource does not exist.
* @param key the key
* @see KoDocumentResourceManager::DocumentResource
*/
QVariant resource(int key) const;
/**
* Return the resource determined by param key as a boolean.
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
bool boolResource(int key) const;
/**
* Return the resource determined by param key as an integer.
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
int intResource(int key) const;
/**
* Return the resource determined by param key as a KoColor.
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
KoColor koColorResource(int key) const;
/**
* Return the resource determined by param key as a pointer to a KoShape.
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
KoShape *koShapeResource(int key) const;
/**
* Return the resource determined by param key as a QString .
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
QString stringResource(int key) const;
/**
* Return the resource determined by param key as a QSizeF.
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
QSizeF sizeResource(int key) const;
/**
* Return the resource determined by param key as a KoUnit.
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
KoUnit unitResource(int key) const;
/**
* Returns true if there is a resource set with the requested key.
* @param key the identifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
bool hasResource(int key) const;
/**
* Remove the resource with @p key from the provider.
* @param key the key that will be used to remove the resource
* There will be a signal emitted with a variable that will return true on QVariable::isNull();
* @see KoDocumentResourceManager::DocumentResource
*/
void clearResource(int key);
/**
* Tools that provide a handle for controlling the content that the tool can edit can
* use this property to alter the radius that a circular handle should have on screen.
* @param handleSize the radius in pixels.
*/
void setHandleRadius(int handleSize);
/// Returns the actual handle radius
int handleRadius() const;
/**
* Tools that are used to grab handles or similar with the mouse
* should use this value to determine if the mouse is near enough
* @param grabSensitivity the grab sensitivity in pixels
*/
void setGrabSensitivity(int grabSensitivity);
/// Returns the actual grab sensitivity
int grabSensitivity() const;
KUndo2Stack *undoStack() const;
void setUndoStack(KUndo2Stack *undoStack);
KoImageCollection *imageCollection() const;
void setImageCollection(KoImageCollection *ic);
KoDocumentBase *odfDocument() const;
void setOdfDocument(KoDocumentBase *currentDocument);
- KoShapeController *shapeController() const;
- void setShapeController(KoShapeController *shapeController);
+ qreal documentResolution() const;
+ QRectF documentRectInPixels() const;
+
+ /**
+ * TODO: remove these methods after legacy ODF text shape is removed.
+ * New code must use documentResolution() and documentRectInPixels()
+ * instead.
+ */
+ Q_DECL_DEPRECATED KoShapeController *globalShapeController() const;
+ Q_DECL_DEPRECATED void setGlobalShapeController(KoShapeController *globalShapeController);
Q_SIGNALS:
/**
* This signal is emitted every time a resource is set that is either
* new or different from the previous set value.
* @param key the identifying key for the resource
* @param value the variants new value.
* @see KoDocumentResourceManager::DocumentResource
*/
void resourceChanged(int key, const QVariant &value);
private:
KoDocumentResourceManager(const KoDocumentResourceManager&);
KoDocumentResourceManager& operator=(const KoDocumentResourceManager&);
class Private;
Private *const d;
};
#endif
diff --git a/libs/flake/KoInputDevice.h b/libs/flake/KoInputDevice.h
index 540adba5e2..e6f86a5a06 100644
--- a/libs/flake/KoInputDevice.h
+++ b/libs/flake/KoInputDevice.h
@@ -1,110 +1,113 @@
/*
* Copyright (c) 2006 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2007 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License 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 KO_INPUT_DEVICE_H_
#define KO_INPUT_DEVICE_H_
#include "kritaflake_export.h"
#include <QHash>
#include <QTabletEvent>
#include <QDebug>
/**
* This class represents an input device.
* A user can manipulate flake-shapes using a large variety of input devices. This ranges from
* a mouse to a paintbrush-like tool connected to a tablet. */
class KRITAFLAKE_EXPORT KoInputDevice
{
public:
/**
* Copy constructor.
*/
KoInputDevice(const KoInputDevice &other);
/**
* Constructor for a tablet.
* Create a new input device with one of the many types that the tablet can have.
* @param device the device as found on a QTabletEvent
* @param pointer the pointer as found on a QTabletEvent
* @param uniqueTabletId the uniqueId as found on a QTabletEvent
*/
explicit KoInputDevice(QTabletEvent::TabletDevice device, QTabletEvent::PointerType pointer, qint64 uniqueTabletId = -1);
/**
* Constructor for the mouse as input device.
*/
KoInputDevice();
~KoInputDevice();
/**
* Return the tablet device used
*/
QTabletEvent::TabletDevice device() const;
/**
* Return the pointer used
*/
QTabletEvent::PointerType pointer() const;
/**
- * Return the unique tablet id as registered by QTabletEvents.
+ * Return the unique tablet id as registered by QTabletEvents. Note that this
+ * id can change randomly, so it's not dependable.
+ *
+ * See https://bugs.kde.org/show_bug.cgi?id=407659
*/
qint64 uniqueTabletId() const;
/**
* Return if this is a mouse device.
*/
bool isMouse() const;
/// equal
bool operator==(const KoInputDevice&) const;
/// not equal
bool operator!=(const KoInputDevice&) const;
/// assignment
KoInputDevice & operator=(const KoInputDevice &);
static KoInputDevice invalid(); ///< invalid input device
static KoInputDevice mouse(); ///< Standard mouse
static KoInputDevice stylus(); ///< Wacom style/pen
static KoInputDevice eraser(); ///< Wacom eraser
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoInputDevice)
KRITAFLAKE_EXPORT QDebug operator<<(QDebug debug, const KoInputDevice &device);
inline uint qHash(const KoInputDevice &key)
{
return qHash(QString(":%1:%2:%3:%4")
.arg(key.device())
.arg(key.pointer())
.arg(key.uniqueTabletId())
.arg(key.isMouse()));
}
#endif
diff --git a/libs/flake/KoSelection.cpp b/libs/flake/KoSelection.cpp
index 706ea4582f..94f96360dd 100644
--- a/libs/flake/KoSelection.cpp
+++ b/libs/flake/KoSelection.cpp
@@ -1,263 +1,263 @@
/* This file is part of the KDE project
Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2006-2007,2009 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License 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 "KoSelection.h"
#include "KoSelection_p.h"
#include "KoShapeContainer.h"
#include "KoShapeGroup.h"
#include "KoPointerEvent.h"
#include "KoShapePaintingContext.h"
#include "kis_algebra_2d.h"
#include "krita_container_utils.h"
#include <QPainter>
#include "kis_debug.h"
-
-KoSelection::KoSelection()
- : KoShape(new KoSelectionPrivate(this))
+KoSelection::KoSelection(QObject *parent)
+ : QObject(parent),
+ KoShape(new KoSelectionPrivate(this))
{
Q_D(KoSelection);
connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged()));
}
KoSelection::~KoSelection()
{
}
void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
Q_UNUSED(paintcontext);
}
void KoSelection::setSize(const QSizeF &size)
{
Q_UNUSED(size);
qWarning() << "WARNING: KoSelection::setSize() should never be used!";
}
QSizeF KoSelection::size() const
{
return outlineRect().size();
}
QRectF KoSelection::outlineRect() const
{
QPolygonF globalPolygon;
Q_FOREACH (KoShape *shape, selectedVisibleShapes()) {
globalPolygon = globalPolygon.united(
shape->absoluteTransformation(0).map(QPolygonF(shape->outlineRect())));
}
const QPolygonF localPolygon = transformation().inverted().map(globalPolygon);
return localPolygon.boundingRect();
}
QRectF KoSelection::boundingRect() const
{
return KoShape::boundingRect(selectedVisibleShapes());
}
void KoSelection::select(KoShape *shape)
{
Q_D(KoSelection);
KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this);
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
if (!shape->isSelectable() || !shape->isVisible()) {
return;
}
// check recursively
if (isSelected(shape)) {
return;
}
// find the topmost parent to select
while (KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(shape->parent())) {
shape = parentGroup;
}
d->selectedShapes << shape;
shape->addShapeChangeListener(this);
if (d->selectedShapes.size() == 1) {
setTransformation(shape->absoluteTransformation(0));
} else {
setTransformation(QTransform());
}
d->selectionChangedCompressor.start();
}
void KoSelection::deselect(KoShape *shape)
{
Q_D(KoSelection);
if (!d->selectedShapes.contains(shape))
return;
d->selectedShapes.removeAll(shape);
shape->removeShapeChangeListener(this);
if (d->selectedShapes.size() == 1) {
setTransformation(d->selectedShapes.first()->absoluteTransformation(0));
}
d->selectionChangedCompressor.start();
}
void KoSelection::deselectAll()
{
Q_D(KoSelection);
if (d->selectedShapes.isEmpty())
return;
Q_FOREACH (KoShape *shape, d->selectedShapes) {
shape->removeShapeChangeListener(this);
}
// reset the transformation matrix of the selection
setTransformation(QTransform());
d->selectedShapes.clear();
d->selectionChangedCompressor.start();
}
int KoSelection::count() const
{
Q_D(const KoSelection);
return d->selectedShapes.size();
}
bool KoSelection::hitTest(const QPointF &position) const
{
Q_D(const KoSelection);
Q_FOREACH (KoShape *shape, d->selectedShapes) {
if (shape->isVisible()) continue;
if (shape->hitTest(position)) return true;
}
return false;
}
const QList<KoShape*> KoSelection::selectedShapes() const
{
Q_D(const KoSelection);
return d->selectedShapes;
}
const QList<KoShape *> KoSelection::selectedVisibleShapes() const
{
QList<KoShape*> shapes = selectedShapes();
KritaUtils::filterContainer (shapes, [](KoShape *shape) {
return shape->isVisible();
});
return shapes;
}
const QList<KoShape *> KoSelection::selectedEditableShapes() const
{
QList<KoShape*> shapes = selectedShapes();
KritaUtils::filterContainer (shapes, [](KoShape *shape) {
return shape->isShapeEditable();
});
return shapes;
}
const QList<KoShape *> KoSelection::selectedEditableShapesAndDelegates() const
{
QList<KoShape*> shapes;
Q_FOREACH (KoShape *shape, selectedShapes()) {
QSet<KoShape *> delegates = shape->toolDelegates();
if (delegates.isEmpty()) {
shapes.append(shape);
} else {
Q_FOREACH (KoShape *delegatedShape, delegates) {
shapes.append(delegatedShape);
}
}
}
return shapes;
}
bool KoSelection::isSelected(const KoShape *shape) const
{
Q_D(const KoSelection);
if (shape == this)
return true;
const KoShape *tmpShape = shape;
while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()) {
tmpShape = tmpShape->parent();
}
return tmpShape;
}
KoShape *KoSelection::firstSelectedShape() const
{
Q_D(const KoSelection);
return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0;
}
void KoSelection::setActiveLayer(KoShapeLayer *layer)
{
Q_D(KoSelection);
d->activeLayer = layer;
emit currentLayerChanged(layer);
}
KoShapeLayer* KoSelection::activeLayer() const
{
Q_D(const KoSelection);
return d->activeLayer;
}
void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
{
Q_UNUSED(shape);
if (type == KoShape::Deleted) {
deselect(shape);
// HACK ALERT: the caller will also remove the listener, which was
// removed in deselect(), so re-add it here
shape->addShapeChangeListener(this);
}
}
void KoSelection::saveOdf(KoShapeSavingContext &) const
{
}
bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &)
{
return true;
}
diff --git a/libs/flake/KoSelection.h b/libs/flake/KoSelection.h
index b3e86b1729..b1122477e3 100644
--- a/libs/flake/KoSelection.h
+++ b/libs/flake/KoSelection.h
@@ -1,165 +1,165 @@
/* This file is part of the KDE project
Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007,2009 Thomas Zander <zander@kde.org>
Copyright (C) 2006,2007 Jan Hambrecht <jaham@gmx.net>
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 KOSELECTION_H
#define KOSELECTION_H
#include <QObject>
#include "KoShape.h"
#include "KoFlake.h"
#include "kritaflake_export.h"
class KoViewConverter;
class KoShapeLayer;
class KoSelectionPrivate;
/**
* A selection is a shape that contains a number of references
* to shapes. That means that a selection can be manipulated in
* the same way as a single shape.
*
* Note that a single shape can be selected in one view, and not in
* another, and that in a single view, more than one selection can be
* present. So selections should not be seen as singletons, or as
* something completely transient.
*
* A selection, however, should not be selectable. We need to think
* a little about the interaction here.
*/
class KRITAFLAKE_EXPORT KoSelection : public QObject, public KoShape, public KoShape::ShapeChangeListener
{
Q_OBJECT
public:
- KoSelection();
+ KoSelection(QObject *parent = 0);
~KoSelection() override;
void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
void setSize(const QSizeF &size) override;
QSizeF size() const override;
QRectF outlineRect() const override;
QRectF boundingRect() const override;
/**
* Adds a shape to the selection.
*
* If the shape is a KoShapeGroup all of its child shapes are automatically added
* to the selection.
* If the shape has no parent or is not a KoShapeGroup, only the given shape is
* added to the selection.
* If the given shape is a child of a KoShapeGroup and recursive selection is enabled
* the all parents and their child shapes up to the toplevel KoShapeGroup are added to
* the selection.
*
* @param shape the shape to add to the selection
*/
void select(KoShape *shape);
/**
* Removes a selected shape.
*
* If the shape is a KoShapeGroup all of its child shapes are automatically removed
* from the selection.
* If the shape has no parent or is not a KoShapeGroup, only the given shape is
* removed from the selection.
* If the given shape is a child of a KoShapeGroup and recursive selection is enabled
* the all parents and their child shape up to the toplevel KoShapeGroup are removed
* from the selection.
*
* @param shape the shape to remove from the selection
*/
void deselect(KoShape *shape);
/// clear the selections list
void deselectAll();
/**
* Return the list of selected shapes
* @return the list of selected shapes
*/
const QList<KoShape*> selectedShapes() const;
/**
* Same as selectedShapes() but only for shapes in visible state. Used by
* the algorithms that draw shapes on the image
*/
const QList<KoShape*> selectedVisibleShapes() const;
/**
* Same as selectedShapes() but only for editable shapes. Used by
* the algorithms that modify the image
*/
const QList<KoShape*> selectedEditableShapes() const;
/**
* Same as selectedEditableShapes() but also includes shapes delegates.
* Used for
*/
const QList<KoShape*> selectedEditableShapesAndDelegates() const;
/**
* Return the first selected shape, or 0 if there is nothing selected.
*/
KoShape *firstSelectedShape() const;
/// return true if the shape is selected
bool isSelected(const KoShape *shape) const;
/// return the selection count, i.e. the number of all selected shapes
int count() const;
bool hitTest(const QPointF &position) const override;
/**
* Sets the currently active layer.
* @param layer the new active layer
*/
void setActiveLayer(KoShapeLayer *layer);
/**
* Returns a currently active layer.
*
* @return the currently active layer, or zero if there is none
*/
KoShapeLayer *activeLayer() const;
void notifyShapeChanged(ChangeType type, KoShape *shape) override;
Q_SIGNALS:
/// emitted when the selection is changed
void selectionChanged();
/// emitted when the current layer is changed
void currentLayerChanged(const KoShapeLayer *layer);
private:
void saveOdf(KoShapeSavingContext &) const override;
bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override;
Q_DECLARE_PRIVATE_D(KoShape::d_ptr, KoSelection)
};
#endif
diff --git a/libs/flake/KoSelection_p.h b/libs/flake/KoSelection_p.h
index affef2b052..136b75d5a2 100644
--- a/libs/flake/KoSelection_p.h
+++ b/libs/flake/KoSelection_p.h
@@ -1,44 +1,44 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License 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 KOSELECTIONPRIVATE_H
#define KOSELECTIONPRIVATE_H
#include "KoShape_p.h"
-#include "kis_signal_compressor.h"
+#include "kis_thread_safe_signal_compressor.h"
class KoShapeGroup;
class KoSelectionPrivate : public KoShapePrivate
{
public:
explicit KoSelectionPrivate(KoSelection *parent)
: KoShapePrivate(parent),
activeLayer(0),
selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE)
{}
QList<KoShape*> selectedShapes;
KoShapeLayer *activeLayer;
- KisSignalCompressor selectionChangedCompressor;
+ KisThreadSafeSignalCompressor selectionChangedCompressor;
Q_DECLARE_PUBLIC(KoSelection)
};
#endif
diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp
index 9bfb42aab3..cd16569232 100644
--- a/libs/flake/KoShapeController.cpp
+++ b/libs/flake/KoShapeController.cpp
@@ -1,215 +1,212 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
*
* 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 "KoShapeController.h"
#include "KoShapeControllerBase.h"
#include "KoShapeRegistry.h"
#include "KoDocumentResourceManager.h"
#include "KoShapeManager.h"
#include "KoShapeLayer.h"
#include "KoSelection.h"
#include "commands/KoShapeCreateCommand.h"
#include "commands/KoShapeDeleteCommand.h"
#include "commands/KoShapeConnectionChangeCommand.h"
#include "KoCanvasBase.h"
#include "KoShapeConfigWidgetBase.h"
#include "KoShapeFactoryBase.h"
#include "KoShape.h"
#include "KoConnectionShape.h"
#include <KoUnit.h>
#include <QObject>
#include <kpagedialog.h>
#include <klocalizedstring.h>
class KoShapeController::Private
{
public:
Private()
: canvas(0),
shapeController(0)
{
}
KoCanvasBase *canvas;
KoShapeControllerBase *shapeController;
KUndo2Command* addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape, KUndo2Command *parent) {
if (canvas) {
if (showDialog && !shape->shapeId().isEmpty()) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId());
Q_ASSERT(factory);
qint16 z = 0;
Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) {
z = qMax(z, sh->zIndex());
}
shape->setZIndex(z + 1);
// show config dialog.
KPageDialog *dialog = new KPageDialog(canvas->canvasWidget());
dialog->setWindowTitle(i18n("%1 Options", factory->name()));
int pageCount = 0;
QList<KoShapeConfigWidgetBase*> widgets;
Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) {
if (! panel->showOnShapeCreate())
continue;
panel->open(shape);
panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept()));
widgets.append(panel);
panel->setResourceManager(canvas->resourceManager());
panel->setUnit(canvas->unit());
QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle();
dialog->addPage(panel, title);
pageCount ++;
}
if (pageCount > 0) {
if (pageCount > 1)
dialog->setFaceType(KPageDialog::Tabbed);
if (dialog->exec() != KPageDialog::Accepted) {
delete dialog;
return 0;
}
Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets)
widget->save();
}
delete dialog;
}
}
return addShapesDirect({shape}, parentShape, parent);
}
KUndo2Command* addShapesDirect(const QList<KoShape*> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return new KoShapeCreateCommand(shapeController, shapes, parentShape, parent);
}
void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast<KoConnectionShape*>(dependee);
if (connection) {
if (shape == connection->firstShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle,
shape, connection->firstConnectionId(), 0, -1, parentCmd);
} else if (shape == connection->secondShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle,
shape, connection->secondConnectionId(), 0, -1, parentCmd);
}
}
}
}
};
KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeControllerBase *shapeController)
: d(new Private())
{
d->canvas = canvas;
d->shapeController = shapeController;
- if (shapeController) {
- shapeController->resourceManager()->setShapeController(this);
- }
}
KoShapeController::~KoShapeController()
{
delete d;
}
void KoShapeController::reset()
{
d->canvas = 0;
d->shapeController = 0;
}
KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return d->addShape(shape, true, parentShape, parent);
}
KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return d->addShapesDirect({shape}, parentShape, parent);
}
KUndo2Command *KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KoShapeContainer *parentShape, KUndo2Command *parent)
{
return d->addShapesDirect(shapes, parentShape, parent);
}
KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shape, parent);
QList<KoShape*> shapes;
shapes.append(shape);
d->shapeController->shapesRemoved(shapes, cmd);
// detach shape from any attached connection shapes
d->handleAttachedConnections(shape, cmd);
return cmd;
}
KUndo2Command* KoShapeController::removeShapes(const QList<KoShape*> &shapes, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shapes, parent);
d->shapeController->shapesRemoved(shapes, cmd);
foreach (KoShape *shape, shapes) {
d->handleAttachedConnections(shape, cmd);
}
return cmd;
}
void KoShapeController::setShapeControllerBase(KoShapeControllerBase *shapeController)
{
d->shapeController = shapeController;
}
QRectF KoShapeController::documentRectInPixels() const
{
return d->shapeController ? d->shapeController->documentRectInPixels() : QRectF(0,0,1920,1080);
}
qreal KoShapeController::pixelsPerInch() const
{
return d->shapeController ? d->shapeController->pixelsPerInch() : 72.0;
}
QRectF KoShapeController::documentRect() const
{
return d->shapeController ? d->shapeController->documentRect() : documentRectInPixels();
}
KoDocumentResourceManager *KoShapeController::resourceManager() const
{
if (!d->shapeController) {
qWarning() << "THIS IS NOT GOOD!";
return 0;
}
return d->shapeController->resourceManager();
}
KoShapeControllerBase *KoShapeController::documentBase() const
{
return d->shapeController;
}
diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp
index 98337cfbe6..d4f3a2e26d 100644
--- a/libs/flake/KoShapeManager.cpp
+++ b/libs/flake/KoShapeManager.cpp
@@ -1,623 +1,634 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
Copyright (C) 2009-2010 Jan Hambrecht <jaham@gmx.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoShapeManager.h"
#include "KoShapeManager_p.h"
#include "KoSelection.h"
#include "KoToolManager.h"
#include "KoPointerEvent.h"
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoCanvasBase.h"
#include "KoShapeContainer.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeGroup.h"
#include "KoToolProxy.h"
#include "KoShapeShadow.h"
#include "KoShapeLayer.h"
#include "KoFilterEffect.h"
#include "KoFilterEffectStack.h"
#include "KoFilterEffectRenderContext.h"
#include "KoShapeBackground.h"
#include <KoRTree.h>
#include "KoClipPath.h"
#include "KoClipMaskPainter.h"
#include "KoShapePaintingContext.h"
#include "KoViewConverter.h"
#include "KisQPainterStateSaver.h"
#include "KoSvgTextChunkShape.h"
#include "KoSvgTextShape.h"
+#include <QApplication>
#include <QPainter>
#include <QTimer>
#include <FlakeDebug.h>
#include "kis_painting_tweaks.h"
bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape)
{
// FIXME: make more general!
return !dynamic_cast<KoShapeGroup*>(shape) &&
!dynamic_cast<KoShapeLayer*>(shape) &&
!(dynamic_cast<KoSvgTextChunkShape*>(shape) && !dynamic_cast<KoSvgTextShape*>(shape));
}
void KoShapeManager::Private::updateTree()
{
// for detecting collisions between shapes.
DetectCollision detector;
bool selectionModified = false;
bool anyModified = false;
Q_FOREACH (KoShape *shape, aggregate4update) {
if (shapeIndexesBeforeUpdate.contains(shape))
detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
selectionModified = selectionModified || selection->isSelected(shape);
anyModified = true;
}
foreach (KoShape *shape, aggregate4update) {
if (!shapeUsedInRenderingTree(shape)) continue;
tree.remove(shape);
QRectF br(shape->boundingRect());
tree.insert(br, shape);
}
// do it again to see which shapes we intersect with _after_ moving.
foreach (KoShape *shape, aggregate4update) {
detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
}
aggregate4update.clear();
shapeIndexesBeforeUpdate.clear();
detector.fireSignals();
if (selectionModified) {
emit q->selectionContentChanged();
}
if (anyModified) {
emit q->contentChanged();
}
}
void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
QList<KoShape*> shapes = group->shapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape *child, shapes) {
// we paint recursively here, so we do not have to check recursively for visibility
if (!child->isVisible(false))
continue;
KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(child);
if (childGroup) {
paintGroup(childGroup, painter, converter, paintContext);
} else {
painter.save();
KoShapeManager::renderSingleShape(child, painter, converter, paintContext);
painter.restore();
}
}
}
KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList<KoShape *> &shapes)
: d(new Private(this, canvas))
{
Q_ASSERT(d->canvas); // not optional.
connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
setShapes(shapes);
+
+ /**
+ * Shape manager uses signal compressors with timers, therefore
+ * it might handle queued signals, therefore it should belong
+ * to the GUI thread.
+ */
+ this->moveToThread(qApp->thread());
}
KoShapeManager::KoShapeManager(KoCanvasBase *canvas)
: d(new Private(this, canvas))
{
Q_ASSERT(d->canvas); // not optional.
connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
+
+ // see a comment in another constructor
+ this->moveToThread(qApp->thread());
}
void KoShapeManager::Private::unlinkFromShapesRecursively(const QList<KoShape*> &shapes)
{
Q_FOREACH (KoShape *shape, shapes) {
shape->priv()->removeShapeManager(q);
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
unlinkFromShapesRecursively(container->shapes());
}
}
}
KoShapeManager::~KoShapeManager()
{
d->unlinkFromShapesRecursively(d->shapes);
d->shapes.clear();
delete d;
}
void KoShapeManager::setShapes(const QList<KoShape *> &shapes, Repaint repaint)
{
//clear selection
d->selection->deselectAll();
d->unlinkFromShapesRecursively(d->shapes);
d->aggregate4update.clear();
d->tree.clear();
d->shapes.clear();
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape, repaint);
}
}
void KoShapeManager::addShape(KoShape *shape, Repaint repaint)
{
if (d->shapes.contains(shape))
return;
shape->priv()->addShapeManager(this);
d->shapes.append(shape);
if (d->shapeUsedInRenderingTree(shape)) {
QRectF br(shape->boundingRect());
d->tree.insert(br, shape);
}
if (repaint == PaintShapeOnAdd) {
shape->update();
}
// add the children of a KoShapeContainer
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
foreach (KoShape *containerShape, container->shapes()) {
addShape(containerShape, repaint);
}
}
Private::DetectCollision detector;
detector.detect(d->tree, shape, shape->zIndex());
detector.fireSignals();
}
void KoShapeManager::remove(KoShape *shape)
{
Private::DetectCollision detector;
detector.detect(d->tree, shape, shape->zIndex());
detector.fireSignals();
shape->update();
shape->priv()->removeShapeManager(this);
d->selection->deselect(shape);
d->aggregate4update.remove(shape);
if (d->shapeUsedInRenderingTree(shape)) {
d->tree.remove(shape);
}
d->shapes.removeAll(shape);
// remove the children of a KoShapeContainer
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
foreach (KoShape *containerShape, container->shapes()) {
remove(containerShape);
}
}
}
KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q)
: q(_q)
{
}
void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape)
{
q->d->selection->deselect(shape);
q->d->aggregate4update.remove(shape);
// we cannot access RTTI of the semi-destructed shape, so just
// unlink it lazily
if (q->d->tree.contains(shape)) {
q->d->tree.remove(shape);
}
q->d->shapes.removeAll(shape);
}
KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface()
{
return &d->shapeInterface;
}
void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint)
{
d->updateTree();
painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off.
painter.setBrush(Qt::NoBrush);
QList<KoShape*> unsortedShapes;
if (painter.hasClipping()) {
QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter));
unsortedShapes = d->tree.intersects(rect);
} else {
unsortedShapes = shapes();
warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!";
}
// filter all hidden shapes from the list
// also filter shapes with a parent which has filter effects applied
QList<KoShape*> sortedShapes;
foreach (KoShape *shape, unsortedShapes) {
if (!shape->isVisible())
continue;
bool addShapeToList = true;
// check if one of the shapes ancestors have filter effects
KoShapeContainer *parent = shape->parent();
while (parent) {
// parent must be part of the shape manager to be taken into account
if (!d->shapes.contains(parent))
break;
if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) {
addShapeToList = false;
break;
}
parent = parent->parent();
}
if (addShapeToList) {
sortedShapes.append(shape);
} else if (parent) {
sortedShapes.append(parent);
}
}
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
foreach (KoShape *shape, sortedShapes) {
renderSingleShape(shape, painter, converter, paintContext);
}
#ifdef CALLIGRA_RTREE_DEBUG
// paint tree
qreal zx = 0;
qreal zy = 0;
converter.zoom(&zx, &zy);
painter.save();
painter.scale(zx, zy);
d->tree.paint(painter);
painter.restore();
#endif
if (! forPrint) {
KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
d->selection->paint(painter, converter, paintContext);
}
}
void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
KisQPainterStateSaver saver(&painter);
// apply shape clipping
KoClipPath::applyClipping(shape, painter, converter);
// apply transformation
painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform());
// paint the shape
paintShape(shape, painter, converter, paintContext);
}
void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
qreal transparency = shape->transparency(true);
if (transparency > 0.0) {
painter.setOpacity(1.0-transparency);
}
if (shape->shadow()) {
painter.save();
shape->shadow()->paint(shape, painter, converter);
painter.restore();
}
if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) {
QScopedPointer<KoClipMaskPainter> clipMaskPainter;
QPainter *shapePainter = &painter;
KoClipMask *clipMask = shape->clipMask();
if (clipMask) {
clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect()));
shapePainter = clipMaskPainter->shapePainter();
}
/**
* We expect the shape to save/restore the painter's state itself. Such design was not
* not always here, so we need a period of sanity checks to ensure all the shapes are
* ported correctly.
*/
const QTransform sanityCheckTransformSaved = shapePainter->transform();
shape->paint(*shapePainter, converter, paintContext);
shape->paintStroke(*shapePainter, converter, paintContext);
KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) {
shapePainter->setTransform(sanityCheckTransformSaved);
}
if (clipMask) {
shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape);
clipMaskPainter->renderOnGlobalPainter();
}
} else {
// TODO: clipping mask is not implemented for this case!
// There are filter effects, then we need to prerender the shape on an image, to filter it
QRectF shapeBound(QPointF(), shape->size());
// First step, compute the rectangle used for the image
QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound);
// convert clip region to view coordinates
QRectF zoomedClipRegion = converter.documentToView(clipRegion);
// determine the offset of the clipping rect from the shapes origin
QPointF clippingOffset = zoomedClipRegion.topLeft();
// Initialize the buffer image
QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
sourceGraphic.fill(qRgba(0,0,0,0));
QHash<QString, QImage> imageBuffers;
QSet<QString> requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs();
if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) {
// Init the buffer painter
QPainter imagePainter(&sourceGraphic);
imagePainter.translate(-1.0f*clippingOffset);
imagePainter.setPen(Qt::NoPen);
imagePainter.setBrush(Qt::NoBrush);
imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
// Paint the shape on the image
KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
if (group) {
// the childrens matrix contains the groups matrix as well
// so we have to compensate for that before painting the children
imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true);
Private::paintGroup(group, imagePainter, converter, paintContext);
} else {
imagePainter.save();
shape->paint(imagePainter, converter, paintContext);
shape->paintStroke(imagePainter, converter, paintContext);
imagePainter.restore();
imagePainter.end();
}
}
if (requiredStdInputs.contains("SourceAlpha")) {
QImage sourceAlpha = sourceGraphic;
sourceAlpha.fill(qRgba(0,0,0,255));
sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel());
imageBuffers.insert("SourceAlpha", sourceAlpha);
}
if (requiredStdInputs.contains("FillPaint")) {
QImage fillPaint = sourceGraphic;
if (shape->background()) {
QPainter fillPainter(&fillPaint);
QPainterPath fillPath;
fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1));
shape->background()->paint(fillPainter, converter, paintContext, fillPath);
} else {
fillPaint.fill(qRgba(0,0,0,0));
}
imageBuffers.insert("FillPaint", fillPaint);
}
imageBuffers.insert("SourceGraphic", sourceGraphic);
imageBuffers.insert(QString(), sourceGraphic);
KoFilterEffectRenderContext renderContext(converter);
renderContext.setShapeBoundingBox(shapeBound);
QImage result;
QList<KoFilterEffect*> filterEffects = shape->filterEffectStack()->filterEffects();
// Filter
foreach (KoFilterEffect *filterEffect, filterEffects) {
QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound);
filterRegion = converter.documentToView(filterRegion);
QRect subRegion = filterRegion.translated(-clippingOffset).toRect();
// set current filter region
renderContext.setFilterRegion(subRegion & sourceGraphic.rect());
if (filterEffect->maximalInputCount() <= 1) {
QList<QString> inputs = filterEffect->inputs();
QString input = inputs.count() ? inputs.first() : QString();
// get input image from image buffers and apply the filter effect
QImage image = imageBuffers.value(input);
if (!image.isNull()) {
result = filterEffect->processImage(imageBuffers.value(input), renderContext);
}
} else {
QList<QImage> inputImages;
Q_FOREACH (const QString &input, filterEffect->inputs()) {
QImage image = imageBuffers.value(input);
if (!image.isNull())
inputImages.append(imageBuffers.value(input));
}
// apply the filter effect
if (filterEffect->inputs().count() == inputImages.count())
result = filterEffect->processImages(inputImages, renderContext);
}
// store result of effect
imageBuffers.insert(filterEffect->output(), result);
}
KoFilterEffect *lastEffect = filterEffects.last();
// Paint the result
painter.save();
painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output()));
painter.restore();
}
}
KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes)
{
d->updateTree();
QList<KoShape*> sortedShapes(d->tree.contains(position));
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
KoShape *firstUnselectedShape = 0;
for (int count = sortedShapes.count() - 1; count >= 0; count--) {
KoShape *shape = sortedShapes.at(count);
if (omitHiddenShapes && ! shape->isVisible())
continue;
if (! shape->hitTest(position))
continue;
switch (selection) {
case KoFlake::ShapeOnTop:
if (shape->isSelectable())
return shape;
break;
case KoFlake::Selected:
if (d->selection->isSelected(shape))
return shape;
break;
case KoFlake::Unselected:
if (! d->selection->isSelected(shape))
return shape;
break;
case KoFlake::NextUnselected:
// we want an unselected shape
if (d->selection->isSelected(shape))
continue;
// memorize the first unselected shape
if (! firstUnselectedShape)
firstUnselectedShape = shape;
// check if the shape above is selected
if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1)))
return shape;
break;
}
}
// if we want the next unselected below a selected but there was none selected,
// return the first found unselected shape
if (selection == KoFlake::NextUnselected && firstUnselectedShape)
return firstUnselectedShape;
if (d->selection->hitTest(position))
return d->selection;
return 0; // missed everything
}
QList<KoShape *> KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode)
{
d->updateTree();
QList<KoShape*> shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect));
for (int count = shapes.count() - 1; count >= 0; count--) {
KoShape *shape = shapes.at(count);
if (omitHiddenShapes && !shape->isVisible()) {
shapes.removeAt(count);
} else {
const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline());
if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) {
shapes.removeAt(count);
} else if (containedMode) {
QPainterPath containingPath;
containingPath.addRect(rect);
if (!containingPath.contains(outline)) {
shapes.removeAt(count);
}
}
}
}
return shapes;
}
void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles)
{
d->canvas->updateCanvas(rect);
if (selectionHandles && d->selection->isSelected(shape)) {
if (d->canvas->toolProxy())
d->canvas->toolProxy()->repaintDecorations();
}
}
void KoShapeManager::notifyShapeChanged(KoShape *shape)
{
Q_ASSERT(shape);
if (d->aggregate4update.contains(shape)) {
return;
}
const bool wasEmpty = d->aggregate4update.isEmpty();
d->aggregate4update.insert(shape);
d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
Q_FOREACH (KoShape *child, container->shapes())
notifyShapeChanged(child);
}
if (wasEmpty && !d->aggregate4update.isEmpty()) {
d->updateTreeCompressor.start();
}
}
QList<KoShape*> KoShapeManager::shapes() const
{
return d->shapes;
}
QList<KoShape*> KoShapeManager::topLevelShapes() const
{
QList<KoShape*> shapes;
// get all toplevel shapes
Q_FOREACH (KoShape *shape, d->shapes) {
if (!shape->parent() || dynamic_cast<KoShapeLayer*>(shape->parent())) {
shapes.append(shape);
}
}
return shapes;
}
KoSelection *KoShapeManager::selection() const
{
return d->selection;
}
KoCanvasBase *KoShapeManager::canvas()
{
return d->canvas;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoShapeManager.cpp"
diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h
index 654ace744c..484bf36084 100644
--- a/libs/flake/KoShapeManager_p.h
+++ b/libs/flake/KoShapeManager_p.h
@@ -1,124 +1,124 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
Copyright (C) 2009-2010 Jan Hambrecht <jaham@gmx.net>
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 KoShapeManager_p_h
#define KoShapeManager_p_h
#include "KoSelection.h"
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeContainer.h"
#include "KoShapeManager.h"
#include <KoRTree.h>
#include "kis_thread_safe_signal_compressor.h"
class KoCanvasBase;
class KoShapeGroup;
class KoShapePaintingContext;
class QPainter;
class Q_DECL_HIDDEN KoShapeManager::Private
{
public:
Private(KoShapeManager *shapeManager, KoCanvasBase *c)
- : selection(new KoSelection()),
+ : selection(new KoSelection(shapeManager)),
canvas(c),
tree(4, 2),
q(shapeManager),
shapeInterface(shapeManager),
updateTreeCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
{
connect(&updateTreeCompressor, SIGNAL(timeout()), q, SLOT(updateTree()));
}
~Private() {
delete selection;
}
/**
* Update the tree when there are shapes in m_aggregate4update. This is done so not all
* updates to the tree are done when they are asked for but when they are needed.
*/
void updateTree();
/**
* Returns whether the shape should be added to the RTree for collision and ROI
* detection.
*/
bool shapeUsedInRenderingTree(KoShape *shape);
/**
* Recursively detach the shapes from this shape manager
*/
void unlinkFromShapesRecursively(const QList<KoShape *> &shapes);
/**
* Recursively paints the given group shape to the specified painter
* This is needed for filter effects on group shapes where the filter effect
* applies to all the children of the group shape at once
*/
static void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
class DetectCollision
{
public:
DetectCollision() {}
void detect(KoRTree<KoShape *> &tree, KoShape *s, int prevZIndex) {
Q_FOREACH (KoShape *shape, tree.intersects(s->boundingRect())) {
bool isChild = false;
KoShapeContainer *parent = s->parent();
while (parent && !isChild) {
if (parent == shape)
isChild = true;
parent = parent->parent();
}
if (isChild)
continue;
if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex())
// Moving a shape will only make it collide with shapes below it.
continue;
if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape))
shapesWithCollisionDetection.append(shape);
}
}
void fireSignals() {
Q_FOREACH (KoShape *shape, shapesWithCollisionDetection)
shape->priv()->shapeChanged(KoShape::CollisionDetected);
}
private:
QList<KoShape*> shapesWithCollisionDetection;
};
QList<KoShape *> shapes;
KoSelection *selection;
KoCanvasBase *canvas;
KoRTree<KoShape *> tree;
QSet<KoShape *> aggregate4update;
QHash<KoShape*, int> shapeIndexesBeforeUpdate;
KoShapeManager *q;
KoShapeManager::ShapeInterface shapeInterface;
KisThreadSafeSignalCompressor updateTreeCompressor;
};
#endif
diff --git a/libs/flake/KoShapeRegistry.cpp b/libs/flake/KoShapeRegistry.cpp
index 5ce3a3b143..d70d101726 100644
--- a/libs/flake/KoShapeRegistry.cpp
+++ b/libs/flake/KoShapeRegistry.cpp
@@ -1,575 +1,575 @@
/* This file is part of the KDE project
* Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006,2008-2010 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 Inge Wallin <inge@lysator.liu.se>
*
* 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.
*/
// Own
#include "KoShapeRegistry.h"
#include "KoSvgTextShape.h"
#include "KoPathShapeFactory.h"
#include "KoConnectionShapeFactory.h"
#include "KoShapeLoadingContext.h"
#include "KoShapeSavingContext.h"
#include "KoShapeGroup.h"
#include "KoShapeLayer.h"
#include "SvgShapeFactory.h"
#include <KoPluginLoader.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoOdfLoadingContext.h>
#include <KoStyleStack.h>
#include <QString>
#include <QHash>
#include <QMultiMap>
#include <QPainter>
#include <QGlobalStatic>
#include <FlakeDebug.h>
Q_GLOBAL_STATIC(KoShapeRegistry, s_instance)
class Q_DECL_HIDDEN KoShapeRegistry::Private
{
public:
void insertFactory(KoShapeFactoryBase *factory);
void init(KoShapeRegistry *q);
KoShape *createShapeInternal(const KoXmlElement &fullElement, KoShapeLoadingContext &context, const KoXmlElement &element) const;
// Map namespace,tagname to priority:factory
QHash<QPair<QString, QString>, QMultiMap<int, KoShapeFactoryBase*> > factoryMap;
};
KoShapeRegistry::KoShapeRegistry()
: d(new Private())
{
}
KoShapeRegistry::~KoShapeRegistry()
{
qDeleteAll(doubleEntries());
qDeleteAll(values());
delete d;
}
void KoShapeRegistry::Private::init(KoShapeRegistry *q)
{
KoPluginLoader::PluginsConfig config;
config.whiteList = "FlakePlugins";
config.blacklist = "FlakePluginsDisabled";
config.group = "calligra";
KoPluginLoader::instance()->load(QString::fromLatin1("Calligra/Flake"),
QString::fromLatin1("[X-Flake-PluginVersion] == 28"),
config);
config.whiteList = "ShapePlugins";
config.blacklist = "ShapePluginsDisabled";
KoPluginLoader::instance()->load(QString::fromLatin1("Calligra/Shape"),
QString::fromLatin1("[X-Flake-PluginVersion] == 28"),
config);
// Also add our hard-coded basic shapes
q->add(new KoSvgTextShapeFactory());
q->add(new KoPathShapeFactory(QStringList()));
q->add(new KoConnectionShapeFactory());
// As long as there is no shape dealing with embedded svg images
// we add the svg shape factory here by default
q->add(new SvgShapeFactory);
// Now all shape factories are registered with us, determine their
// associated odf tagname & priority and prepare ourselves for
// loading ODF.
QList<KoShapeFactoryBase*> factories = q->values();
for (int i = 0; i < factories.size(); ++i) {
insertFactory(factories[i]);
}
}
KoShapeRegistry* KoShapeRegistry::instance()
{
if (!s_instance.exists()) {
s_instance->d->init(s_instance);
}
return s_instance;
}
void KoShapeRegistry::addFactory(KoShapeFactoryBase * factory)
{
add(factory);
d->insertFactory(factory);
}
void KoShapeRegistry::Private::insertFactory(KoShapeFactoryBase *factory)
{
const QList<QPair<QString, QStringList> > odfElements(factory->odfElements());
if (odfElements.isEmpty()) {
debugFlake << "Shape factory" << factory->id() << " does not have OdfNamespace defined, ignoring";
}
else {
int priority = factory->loadingPriority();
for (QList<QPair<QString, QStringList> >::const_iterator it(odfElements.begin()); it != odfElements.end(); ++it) {
foreach (const QString &elementName, (*it).second) {
QPair<QString, QString> p((*it).first, elementName);
QMultiMap<int, KoShapeFactoryBase*> & priorityMap = factoryMap[p];
priorityMap.insert(priority, factory);
debugFlake << "Inserting factory" << factory->id() << " for"
<< p << " with priority "
<< priority << " into factoryMap making "
<< priorityMap.size() << " entries. ";
}
}
}
}
#include <KoXmlWriter.h>
#include <QBuffer>
#include <KoStore.h>
#include <boost/optional.hpp>
namespace {
struct ObjectEntry {
ObjectEntry()
{
}
ObjectEntry(const ObjectEntry &rhs)
: objectXmlContents(rhs.objectXmlContents),
objectName(rhs.objectName),
isDir(rhs.isDir)
{
}
~ObjectEntry()
{
}
QByteArray objectXmlContents; // the XML tree in the object
QString objectName; // object name in the frame without "./"
// This is extracted from objectXmlContents.
bool isDir = false;
};
// A FileEntry is used to store information about embedded files
// inside (i.e. referred to by) an object.
struct FileEntry {
FileEntry() {}
FileEntry(const FileEntry &rhs)
: path(rhs.path),
mimeType(rhs.mimeType),
isDir(rhs.isDir),
contents(rhs.contents)
{
}
QString path; // Normalized filename, i.e. without "./".
QString mimeType;
bool isDir;
QByteArray contents;
};
QByteArray loadFile(const QString &fileName, KoShapeLoadingContext &context)
{
// Can't load a file which is a directory, return an invalid QByteArray
if (fileName.endsWith('/'))
return QByteArray();
KoStore *store = context.odfLoadingContext().store();
QByteArray fileContent;
if (!store->open(fileName)) {
store->close();
return QByteArray();
}
int fileSize = store->size();
fileContent = store->read(fileSize);
store->close();
//debugFlake << "File content: " << fileContent;
return fileContent;
}
boost::optional<FileEntry> storeFile(const QString &fileName, KoShapeLoadingContext &context)
{
debugFlake << "Saving file: " << fileName;
boost::optional<FileEntry> result;
QByteArray fileContent = loadFile(fileName, context);
if (!fileContent.isNull()) {
// Actually store the file in the list.
FileEntry entry;
entry.path = fileName;
if (entry.path.startsWith(QLatin1String("./"))) {
entry.path.remove(0, 2);
}
entry.mimeType = context.odfLoadingContext().mimeTypeForPath(entry.path);
entry.isDir = false;
entry.contents = fileContent;
result = entry;
}
return result;
}
void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer,
ObjectEntry *object, QHash<QString, QString> &unknownNamespaces)
{
// Start the element;
// keep the name in a QByteArray so that it stays valid until end element is called.
const QByteArray name(el.nodeName().toLatin1());
writer.startElement(name.constData());
// Child elements
// Loop through all the child elements of the draw:frame.
KoXmlNode n = el.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (n.isElement()) {
storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces);
}
else if (n.isText()) {
writer.addTextNode(n.toText().data()/*.toUtf8()*/);
}
}
// End the element
writer.endElement();
}
QVector<ObjectEntry> storeObjects(const KoXmlElement &element)
{
QVector<ObjectEntry> result;
// Loop through all the child elements of the draw:frame and save them.
KoXmlNode n = element.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
debugFlake << "In draw:frame, node =" << n.nodeName();
// This disregards #text, but that's not in the spec anyway so
// it doesn't need to be saved.
if (!n.isElement())
continue;
KoXmlElement el = n.toElement();
ObjectEntry object;
QByteArray contentsTmp;
QBuffer buffer(&contentsTmp); // the member
KoXmlWriter writer(&buffer);
// 1. Find out the objectName
// Save the normalized filename, i.e. without a starting "./".
// An empty string is saved if no name is found.
QString name = el.attributeNS(KoXmlNS::xlink, "href", QString());
if (name.startsWith(QLatin1String("./")))
name.remove(0, 2);
object.objectName = name;
// 2. Copy the XML code.
QHash<QString, QString> unknownNamespaces;
storeXmlRecursive(el, writer, &object, unknownNamespaces);
object.objectXmlContents = contentsTmp;
// 3, 4: the isDir and manifestEntry members are not set here,
// but initialize them anyway. .
object.isDir = false; // Has to be initialized to something.
result.append(object);
}
return result;
}
}
#include <svg/SvgShapeFactory.h>
#include "kis_debug.h"
#include <QMimeDatabase>
#include <KoUnit.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeController.h>
#include <KoShapeGroupCommand.h>
KoShape * KoShapeRegistry::createShapeFromOdf(const KoXmlElement & e, KoShapeLoadingContext & context) const
{
debugFlake << "Going to check for" << e.namespaceURI() << ":" << e.tagName();
KoShape * shape = 0;
// Handle the case where the element is a draw:frame differently from other cases.
if (e.tagName() == "frame" && e.namespaceURI() == KoXmlNS::draw) {
// If the element is in a frame, the frame is already added by the
// application and we only want to create a shape from the
// embedded element. The very first shape we create is accepted.
//
// FIXME: we might want to have some code to determine which is
// the "best" of the creatable shapes.
if (e.hasChildNodes()) {
// if we don't ignore white spaces it can be that the first child is not a element so look for the first element
KoXmlNode node = e.firstChild();
KoXmlElement element;
while (!node.isNull() && element.isNull()) {
element = node.toElement();
node = node.nextSibling();
}
if (!element.isNull()) {
// Check for draw:object
if (element.tagName() == "object" && element.namespaceURI() == KoXmlNS::draw && element.hasChildNodes()) {
// Loop through the elements and find the first one
// that is handled by any shape.
KoXmlNode n = element.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (n.isElement()) {
debugFlake << "trying for element " << n.toElement().tagName();
shape = d->createShapeInternal(e, context, n.toElement());
break;
}
}
if (shape)
debugFlake << "Found a shape for draw:object";
else
debugFlake << "Found NO shape shape for draw:object";
}
else {
// If not draw:object, e.g draw:image or draw:plugin
shape = d->createShapeInternal(e, context, element);
}
}
if (shape) {
debugFlake << "A shape supporting the requested type was found.";
}
else {
// If none of the registered shapes could handle the frame
// contents, try to fetch SVG it from an embedded link
const KoXmlElement &frameElement = e;
const int frameZIndex = SvgShapeFactory::calculateZIndex(frameElement, context);
QList<KoShape*> resultShapes;
QVector<ObjectEntry> objects = storeObjects(frameElement);
Q_FOREACH (const ObjectEntry &object, objects) {
if (object.objectName.isEmpty()) continue;
boost::optional<FileEntry> file = storeFile(object.objectName, context);
if (file && !file->contents.isEmpty()) {
QMimeDatabase db;
QMimeType mime = db.mimeTypeForData(file->contents);
const int zIndex = SvgShapeFactory::calculateZIndex(element, context);
if (mime.inherits("image/svg+xml")) {
KoXmlDocument xmlDoc;
int line, col;
QString errormessage;
const bool parsed = xmlDoc.setContent(file->contents, &errormessage, &line, &col);
if (!parsed) continue;
- const QRectF bounds = context.documentResourceManager()->shapeController()->documentRectInPixels();
+ const QRectF bounds = context.documentResourceManager()->documentRectInPixels();
// WARNING: Krita 3.x expects all the embedded objects to
// be loaded in default resolution of 72.0 ppi.
// Don't change it to the correct data in the image,
// it will change back compatibility (and this code will
// be deprecated some time soon
// UPDATE (DK): There is actually no difference in what resolution we
// load these shapes, because they will be scaled into
// the bounds of the parent odf-frame
const qreal pixelsPerInch = 72.0;
const qreal forcedFontSizeResolution = 72.0;
QPointF pos;
pos.setX(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "x", QString::number(bounds.x()))));
pos.setY(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "y", QString::number(bounds.y()))));
QSizeF size;
size.setWidth(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "width", QString::number(bounds.width()))));
size.setHeight(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "height", QString::number(bounds.height()))));
KoShape *shape = SvgShapeFactory::createShapeFromSvgDirect(xmlDoc.documentElement(),
QRectF(pos, size),
pixelsPerInch,
forcedFontSizeResolution,
zIndex,
context);
if (shape) {
// NOTE: here we are expected to stretch the internal to the bounds of
// the frame! Sounds weird, but it is what Krita 3.x did.
const QRectF shapeRect = shape->absoluteOutlineRect(0);
const QPointF offset = shapeRect.topLeft();
const QSizeF fragmentSize = shapeRect.size();
if (fragmentSize.isValid()) {
/**
* Yes, you see what you see. The previous versions of Krita used
* QSvgRenderer to render the object, which allegedly truncated the
* object on sides. Even though we don't use pre-rendering now,
* we should still reproduce the old way...
*/
const QSizeF newSize = QSizeF(int(size.width()), int(size.height()));
shape->applyAbsoluteTransformation(
QTransform::fromTranslate(-offset.x(), -offset.y()) *
QTransform::fromScale(
newSize.width() / fragmentSize.width(),
newSize.height() / fragmentSize.height()) *
QTransform::fromTranslate(pos.x(), pos.y()));
resultShapes.append(shape);
}
}
} else {
// TODO: implement raster images?
}
}
}
if (resultShapes.size() == 1) {
shape = resultShapes.takeLast();
} else if (resultShapes.size() > 1) {
KoShapeGroup *groupShape = new KoShapeGroup;
KoShapeGroupCommand cmd(groupShape, resultShapes);
cmd.redo();
groupShape->setZIndex(frameZIndex);
shape = groupShape;
}
}
}
}
// Hardwire the group shape into the loading as it should not appear
// in the shape selector
else if (e.localName() == "g" && e.namespaceURI() == KoXmlNS::draw) {
KoShapeGroup * group = new KoShapeGroup();
context.odfLoadingContext().styleStack().save();
bool loaded = group->loadOdf(e, context);
context.odfLoadingContext().styleStack().restore();
if (loaded) {
shape = group;
}
else {
delete group;
}
} else {
shape = d->createShapeInternal(e, context, e);
}
if (shape) {
context.shapeLoaded(shape);
}
return shape;
}
KoShape *KoShapeRegistry::Private::createShapeInternal(const KoXmlElement &fullElement,
KoShapeLoadingContext &context,
const KoXmlElement &element) const
{
// Pair of namespace, tagname
QPair<QString, QString> p = QPair<QString, QString>(element.namespaceURI(), element.tagName());
// Remove duplicate lookup.
if (!factoryMap.contains(p))
return 0;
QMultiMap<int, KoShapeFactoryBase*> priorityMap = factoryMap.value(p);
QList<KoShapeFactoryBase*> factories = priorityMap.values();
#ifndef NDEBUG
debugFlake << "Supported factories for=" << p;
foreach (KoShapeFactoryBase *f, factories)
debugFlake << f->id() << f->name();
#endif
// Loop through all shape factories. If any of them supports this
// element, then we let the factory create a shape from it. This
// may fail because the element itself is too generic to draw any
// real conclusions from it - we actually have to try to load it.
// An example of this is the draw:image element which have
// potentially hundreds of different image formats to support,
// including vector formats.
//
// If it succeeds, then we use this shape, if it fails, then just
// try the next.
//
// Higher numbers are more specific, map is sorted by keys.
for (int i = factories.size() - 1; i >= 0; --i) {
KoShapeFactoryBase * factory = factories[i];
if (factory->supports(element, context)) {
KoShape *shape = factory->createShapeFromOdf(fullElement, context);
if (shape) {
debugFlake << "Shape found for factory " << factory->id() << factory->name();
// we return the top-level most shape as that's the one that we'll have to
// add to the KoShapeManager for painting later (and also to avoid memory leaks)
// but don't go past a KoShapeLayer as KoShape adds those from the context
// during loading and those are already added.
while (shape->parent() && dynamic_cast<KoShapeLayer*>(shape->parent()) == 0)
shape = shape->parent();
return shape;
}
// Maybe a shape with a lower priority can load our
// element, but this attempt has failed.
}
else {
debugFlake << "No support for" << p << "by" << factory->id();
}
}
return 0;
}
QList<KoShapeFactoryBase*> KoShapeRegistry::factoriesForElement(const QString &nameSpace, const QString &elementName)
{
// Pair of namespace, tagname
QPair<QString, QString> p = QPair<QString, QString>(nameSpace, elementName);
QMultiMap<int, KoShapeFactoryBase*> priorityMap = d->factoryMap.value(p);
QList<KoShapeFactoryBase*> shapeFactories;
// sort list by priority
Q_FOREACH (KoShapeFactoryBase *f, priorityMap.values()) {
shapeFactories.prepend(f);
}
return shapeFactories;
}
diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp
index 66a04bc0c9..956caf2479 100644
--- a/libs/flake/KoToolManager.cpp
+++ b/libs/flake/KoToolManager.cpp
@@ -1,975 +1,976 @@
/* This file is part of the KDE project
*
* Copyright (c) 2005-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2006-2008 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
*
* 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.
*/
// flake
#include "KoToolManager.h"
#include "KoToolManager_p.h"
#include "KoToolRegistry.h"
#include "KoToolProxy.h"
#include "KoToolProxy_p.h"
#include "KoSelection.h"
#include "KoCanvasController.h"
#include "KoCanvasControllerWidget.h"
#include "KoShape.h"
#include "KoShapeLayer.h"
#include "KoShapeRegistry.h"
#include "KoShapeManager.h"
#include "KoSelectedShapesProxy.h"
#include "KoCanvasBase.h"
#include "KoInputDeviceHandlerRegistry.h"
#include "KoInputDeviceHandlerEvent.h"
#include "KoPointerEvent.h"
#include "tools/KoCreateShapesTool.h"
#include "tools/KoZoomTool.h"
#include "kis_action_registry.h"
#include "KoToolFactoryBase.h"
#include "kis_assert.h"
#include <krita_container_utils.h>
// Qt + kde
#include <QWidget>
#include <QEvent>
#include <QWheelEvent>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QTabletEvent>
#include <QKeyEvent>
#include <QVBoxLayout>
#include <QStringList>
#include <QApplication>
#include <kactioncollection.h>
#include <kactioncategory.h>
#include <FlakeDebug.h>
#include <QAction>
#include <klocalizedstring.h>
#include <QKeySequence>
#include <QStack>
#include <QLabel>
#include <QGlobalStatic>
Q_GLOBAL_STATIC(KoToolManager, s_instance)
class CanvasData
{
public:
CanvasData(KoCanvasController *cc, const KoInputDevice &id)
: activeTool(0),
canvas(cc),
inputDevice(id),
dummyToolWidget(0),
dummyToolLabel(0)
{
}
~CanvasData()
{
// the dummy tool widget does not necessarily have a parent and we create it, so we delete it.
delete dummyToolWidget;
}
void activateToolActions()
{
toolActions.clear();
disabledGlobalActions.clear();
KActionCollection *windowActionCollection = canvas->actionCollection();
if (!windowActionCollection) {
qWarning() << "We haven't got an action collection";
return;
}
QStringList globalActions;
QMap<QKeySequence, QStringList> shortcutMap;
//qDebug() << "................... activating tool" << activeToolId;
Q_FOREACH(QAction *action, windowActionCollection->actions()) {
//qDebug() << "Action" << action->objectName() << "shortcuts" << action->shortcuts();
if (action->property("tool_action").isValid()) {
QStringList tools = action->property("tool_action").toStringList();
//qDebug() << "\tassociated with" << tools;
if (tools.contains(activeToolId)) {
//qDebug() << "\t\tenabling";
action->setEnabled(true);
toolActions << action->objectName();
}
else {
action->setDisabled(true);
}
}
else {
globalActions << action->objectName();
}
Q_FOREACH(QKeySequence keySequence, action->shortcuts()) {
// After loading a custom shortcut profile, shortcuts can be defined as an empty string, which is not an empty shortcut
if (keySequence.toString() != "") {
if (shortcutMap.contains(keySequence)) {
shortcutMap[keySequence].append(action->objectName());
}
else {
shortcutMap[keySequence] = QStringList() << action->objectName();
}
}
}
}
// Make sure the tool's actions override the global actions that aren't associated with the tool.
Q_FOREACH(const QKeySequence &k, shortcutMap.keys()) {
if (shortcutMap[k].size() > 1) {
QStringList actions = shortcutMap[k];
//qDebug() << k << actions;
bool toolActionFound = false;
Q_FOREACH(const QString &action, actions) {
if (toolActions.contains(action)) {
toolActionFound = true;
}
}
Q_FOREACH(const QString &action, actions) {
if (toolActionFound && globalActions.contains(action)) {
//qDebug() << "\tdisabling global action" << action;
windowActionCollection->action(action)->setEnabled(false);
disabledGlobalActions << action;
}
}
//qDebug() << k << shortcutMap[k];
}
}
windowActionCollection->readSettings(); // The shortcuts might have been configured in the meantime.
}
void deactivateToolActions()
{
if (!activeTool)
return;
//qDebug() << "............... deactivating previous tool because activating" << activeToolId;
KActionCollection *windowActionCollection = canvas->actionCollection();
Q_FOREACH(const QString &action, toolActions) {
//qDebug() << "disabling" << action;
windowActionCollection->action(action)->setDisabled(true);
}
Q_FOREACH(const QString &action, disabledGlobalActions) {
//qDebug() << "enabling" << action;
windowActionCollection->action(action)->setEnabled(true);
}
}
KoToolBase *activeTool; // active Tool
QString activeToolId; // the id of the active Tool
QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to.
QHash<QString, KoToolBase*> allTools; // all the tools that are created for this canvas.
QStack<QString> stack; // stack of temporary tools
KoCanvasController *const canvas;
const KoInputDevice inputDevice;
QWidget *dummyToolWidget; // the widget shown in the toolDocker.
QLabel *dummyToolLabel;
QStringList toolActions;
QStringList disabledGlobalActions;
};
// ******** KoToolManager **********
KoToolManager::KoToolManager()
: QObject(),
d(new Private(this))
{
connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*,QWidget*)),
this, SLOT(movedFocus(QWidget*,QWidget*)));
}
KoToolManager::~KoToolManager()
{
delete d;
}
QList<KoToolAction*> KoToolManager::toolActionList() const
{
QList<KoToolAction*> answer;
answer.reserve(d->tools.count());
Q_FOREACH (ToolHelper *tool, d->tools) {
if (tool->id() == KoCreateShapesTool_ID)
continue; // don't show this one.
answer.append(tool->toolAction());
}
return answer;
}
void KoToolManager::requestToolActivation(KoCanvasController * controller)
{
if (d->canvasses.contains(controller)) {
QString activeToolId = d->canvasses.value(controller).first()->activeToolId;
Q_FOREACH (ToolHelper * th, d->tools) {
if (th->id() == activeToolId) {
d->toolActivated(th);
break;
}
}
}
}
KoInputDevice KoToolManager::currentInputDevice() const
{
return d->inputDevice;
}
void KoToolManager::registerToolActions(KActionCollection *ac, KoCanvasController *controller)
{
Q_ASSERT(controller);
Q_ASSERT(ac);
d->setup();
if (!d->canvasses.contains(controller)) {
return;
}
// Actions used to switch tools via shortcuts
Q_FOREACH (ToolHelper * th, d->tools) {
if (ac->action(th->id())) {
continue;
}
ShortcutToolAction* action = th->createShortcutToolAction(ac);
ac->addCategorizedAction(th->id(), action, "tool-shortcuts");
}
}
void KoToolManager::addController(KoCanvasController *controller)
{
Q_ASSERT(controller);
if (d->canvasses.contains(controller))
return;
d->setup();
d->attachCanvas(controller);
connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*)));
connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
}
void KoToolManager::removeCanvasController(KoCanvasController *controller)
{
Q_ASSERT(controller);
disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
d->detachCanvas(controller);
}
void KoToolManager::attemptCanvasControllerRemoval(QObject* controller)
{
KoCanvasControllerProxyObject* controllerActual = qobject_cast<KoCanvasControllerProxyObject*>(controller);
if (controllerActual) {
removeCanvasController(controllerActual->canvasController());
}
}
void KoToolManager::switchToolRequested(const QString & id)
{
Q_ASSERT(d->canvasData);
if (!d->canvasData) return;
while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack
d->canvasData->stack.pop();
d->switchTool(id, false);
}
void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id)
{
if (!d->canvasData) return;
d->switchInputDevice(id);
}
void KoToolManager::switchToolTemporaryRequested(const QString &id)
{
d->switchTool(id, true);
}
void KoToolManager::switchBackRequested()
{
if (!d->canvasData) return;
if (d->canvasData->stack.isEmpty()) {
// default to changing to the interactionTool
d->switchTool(KoInteractionTool_ID, false);
return;
}
d->switchTool(d->canvasData->stack.pop(), false);
}
KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas) {
KoCreateShapesTool *createTool = dynamic_cast<KoCreateShapesTool*>
(d->canvasData->allTools.value(KoCreateShapesTool_ID));
Q_ASSERT(createTool /* ID changed? */);
return createTool;
}
}
Q_ASSERT(0); // this should not happen
return 0;
}
KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas)
return d->canvasData->allTools.value(id);
}
return 0;
}
KoCanvasController *KoToolManager::activeCanvasController() const
{
if (! d->canvasData) return 0;
return d->canvasData->canvas;
}
QString KoToolManager::preferredToolForSelection(const QList<KoShape*> &shapes)
{
QSet<QString> shapeTypes;
Q_FOREACH (KoShape *shape, shapes) {
shapeTypes << shape->shapeId();
}
//KritaUtils::makeContainerUnique(types);
QString toolType = KoInteractionTool_ID;
int prio = INT_MAX;
Q_FOREACH (ToolHelper *helper, d->tools) {
if (helper->id() == KoCreateShapesTool_ID) continue;
if (helper->priority() >= prio)
continue;
bool toolWillWork = false;
foreach (const QString &type, shapeTypes) {
if (helper->activationShapeId().split(',').contains(type)) {
toolWillWork = true;
break;
}
}
if (toolWillWork) {
toolType = helper->id();
prio = helper->priority();
}
}
return toolType;
}
QPair<QString, KoToolBase*> KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool)
{
// XXX: maybe this method should go into the private class?
QHash<QString, KoToolBase*> origHash;
if (d->canvasses.contains(controller)) {
origHash = d->canvasses.value(controller).first()->allTools;
}
if (origHash.contains(tool->id())) {
return QPair<QString, KoToolBase*>(tool->id(), origHash.value(tool->id()));
}
debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority();
KoToolBase *tl = tool->createTool(controller->canvas());
if (tl) {
d->uniqueToolIds.insert(tl, tool->uniqueId());
tl->setObjectName(tool->id());
}
KoZoomTool *zoomTool = dynamic_cast<KoZoomTool*>(tl);
if (zoomTool) {
zoomTool->setCanvasController(controller);
}
return QPair<QString, KoToolBase*>(tool->id(), tl);
}
void KoToolManager::initializeCurrentToolForCanvas()
{
d->postSwitchTool(false);
}
KoToolManager* KoToolManager::instance()
{
return s_instance;
}
QString KoToolManager::activeToolId() const
{
if (!d->canvasData) return QString();
return d->canvasData->activeToolId;
}
KoToolManager::Private *KoToolManager::priv()
{
return d;
}
/**** KoToolManager::Private ****/
KoToolManager::Private::Private(KoToolManager *qq)
: q(qq),
canvasData(0),
layerExplicitlyDisabled(false)
{
}
KoToolManager::Private::~Private()
{
qDeleteAll(tools);
}
// helper method.
CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device)
{
QHash<QString, KoToolBase*> toolsHash;
Q_FOREACH (ToolHelper *tool, tools) {
QPair<QString, KoToolBase*> toolPair = q->createTools(controller, tool);
if (toolPair.second) { // only if a real tool was created
toolsHash.insert(toolPair.first, toolPair.second);
}
}
KoCreateShapesTool *createShapesTool = dynamic_cast<KoCreateShapesTool*>(toolsHash.value(KoCreateShapesTool_ID));
KIS_ASSERT(createShapesTool);
QString id = KoShapeRegistry::instance()->keys()[0];
createShapesTool->setShapeId(id);
CanvasData *cd = new CanvasData(controller, device);
cd->allTools = toolsHash;
return cd;
}
void KoToolManager::Private::setup()
{
if (tools.size() > 0)
return;
KoShapeRegistry::instance();
KoToolRegistry *registry = KoToolRegistry::instance();
Q_FOREACH (const QString & id, registry->keys()) {
ToolHelper *t = new ToolHelper(registry->value(id));
tools.append(t);
}
// connect to all tools so we can hear their button-clicks
Q_FOREACH (ToolHelper *tool, tools)
connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*)));
// load pluggable input devices
KoInputDeviceHandlerRegistry::instance();
}
void KoToolManager::Private::connectActiveTool()
{
if (canvasData->activeTool) {
connect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)),
q, SLOT(updateCursor(QCursor)));
connect(canvasData->activeTool, SIGNAL(activateTool(QString)),
q, SLOT(switchToolRequested(QString)));
connect(canvasData->activeTool, SIGNAL(activateTemporary(QString)),
q, SLOT(switchToolTemporaryRequested(QString)));
connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
connect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)),
q, SIGNAL(changedStatusText(QString)));
}
// we expect the tool to emit a cursor on activation.
updateCursor(Qt::ForbiddenCursor);
}
void KoToolManager::Private::disconnectActiveTool()
{
if (canvasData->activeTool) {
canvasData->deactivateToolActions();
// repaint the decorations before we deactivate the tool as it might deleted
// data needed for the repaint
emit q->aboutToChangeTool(canvasData->canvas);
canvasData->activeTool->deactivate();
disconnect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)),
q, SLOT(updateCursor(QCursor)));
disconnect(canvasData->activeTool, SIGNAL(activateTool(QString)),
q, SLOT(switchToolRequested(QString)));
disconnect(canvasData->activeTool, SIGNAL(activateTemporary(QString)),
q, SLOT(switchToolTemporaryRequested(QString)));
disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)),
q, SIGNAL(changedStatusText(QString)));
}
// emit a empty status text to clear status text from last active tool
emit q->changedStatusText(QString());
}
void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary)
{
Q_ASSERT(tool);
if (canvasData == 0)
return;
if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID)
return;
disconnectActiveTool();
canvasData->activeTool = tool;
connectActiveTool();
postSwitchTool(temporary);
}
void KoToolManager::Private::switchTool(const QString &id, bool temporary)
{
Q_ASSERT(canvasData);
if (!canvasData) return;
if (canvasData->activeTool && temporary)
canvasData->stack.push(canvasData->activeToolId);
canvasData->activeToolId = id;
KoToolBase *tool = canvasData->allTools.value(id);
if (! tool) {
return;
}
Q_FOREACH (ToolHelper *th, tools) {
if (th->id() == id) {
canvasData->activationShapeId = th->activationShapeId();
break;
}
}
switchTool(tool, temporary);
}
void KoToolManager::Private::postSwitchTool(bool temporary)
{
#ifndef NDEBUG
int canvasCount = 1;
Q_FOREACH (QList<CanvasData*> list, canvasses) {
bool first = true;
Q_FOREACH (CanvasData *data, list) {
if (first) {
debugFlake << "Canvas" << canvasCount++;
}
debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : "");
first = false;
}
}
#endif
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase::ToolActivation toolActivation;
if (temporary)
toolActivation = KoToolBase::TemporaryActivation;
else
toolActivation = KoToolBase::DefaultActivation;
QSet<KoShape*> shapesToOperateOn;
if (canvasData->activeTool
&& canvasData->activeTool->canvas()
&& canvasData->activeTool->canvas()->shapeManager()) {
KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection();
Q_ASSERT(selection);
shapesToOperateOn = QSet<KoShape*>::fromList(selection->selectedEditableShapesAndDelegates());
}
if (canvasData->canvas->canvas()) {
// Caller of postSwitchTool expect this to be called to update the selected tool
updateToolForProxy();
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
KoCanvasBase *canvas = canvasData->canvas->canvas();
canvas->updateInputMethodInfo();
} else {
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
}
QList<QPointer<QWidget> > optionWidgetList = canvasData->activeTool->optionWidgets();
if (optionWidgetList.empty()) { // no option widget.
QWidget *toolWidget;
QString title;
Q_FOREACH (ToolHelper *tool, tools) {
if (tool->id() == canvasData->activeTool->toolId()) {
title = tool->toolTip();
break;
}
}
toolWidget = canvasData->dummyToolWidget;
if (toolWidget == 0) {
toolWidget = new QWidget();
toolWidget->setObjectName("DummyToolWidget");
QVBoxLayout *layout = new QVBoxLayout(toolWidget);
layout->setMargin(3);
canvasData->dummyToolLabel = new QLabel(toolWidget);
layout->addWidget(canvasData->dummyToolLabel);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
toolWidget->setLayout(layout);
canvasData->dummyToolWidget = toolWidget;
}
canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title));
optionWidgetList.append(toolWidget);
}
// Activate the actions for the currently active tool
canvasData->activateToolActions();
emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool));
emit q->toolOptionWidgetsChanged(canvasData->canvas, optionWidgetList);
}
void KoToolManager::Private::switchCanvasData(CanvasData *cd)
{
Q_ASSERT(cd);
KoCanvasBase *oldCanvas = 0;
KoInputDevice oldInputDevice;
if (canvasData) {
oldCanvas = canvasData->canvas->canvas();
oldInputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
disconnectActiveTool();
}
KoToolProxy *proxy = proxies.value(oldCanvas);
Q_ASSERT(proxy);
proxy->setActiveTool(0);
}
canvasData = cd;
inputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
connectActiveTool();
postSwitchTool(false);
}
if (oldInputDevice != canvasData->inputDevice) {
emit q->inputDeviceChanged(canvasData->inputDevice);
}
if (oldCanvas != canvasData->canvas->canvas()) {
emit q->changedCanvas(canvasData->canvas->canvas());
}
}
void KoToolManager::Private::toolActivated(ToolHelper *tool)
{
Q_ASSERT(tool);
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase *t = canvasData->allTools.value(tool->id());
Q_ASSERT(t);
canvasData->activeToolId = tool->id();
canvasData->activationShapeId = tool->activationShapeId();
switchTool(t, false);
}
void KoToolManager::Private::detachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
// check if we are removing the active canvas controller
if (canvasData && canvasData->canvas == controller) {
KoCanvasController *newCanvas = 0;
// try to find another canvas controller beside the one we are removing
Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
if (canvas != controller) {
// yay found one
newCanvas = canvas;
break;
}
}
if (newCanvas) {
switchCanvasData(canvasses.value(newCanvas).first());
} else {
+ disconnectActiveTool();
emit q->toolOptionWidgetsChanged(controller, QList<QPointer<QWidget> >());
// as a last resort just set a blank one
canvasData = 0;
}
}
KoToolProxy *proxy = proxies.value(controller->canvas());
if (proxy)
proxy->setActiveTool(0);
QList<KoToolBase *> tools;
Q_FOREACH (CanvasData *canvasData, canvasses.value(controller)) {
Q_FOREACH (KoToolBase *tool, canvasData->allTools) {
if (! tools.contains(tool)) {
tools.append(tool);
}
}
delete canvasData;
}
Q_FOREACH (KoToolBase *tool, tools) {
uniqueToolIds.remove(tool);
delete tool;
}
canvasses.remove(controller);
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::attachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse());
// switch to new canvas as the active one.
switchCanvasData(cd);
inputDevice = cd->inputDevice;
QList<CanvasData*> canvasses_;
canvasses_.append(cd);
canvasses[controller] = canvasses_;
KoToolProxy *tp = proxies[controller->canvas()];
if (tp)
tp->priv()->setCanvasController(controller);
if (cd->activeTool == 0) {
// no active tool, so we activate the highest priority main tool
int highestPriority = INT_MAX;
ToolHelper * helper = 0;
Q_FOREACH (ToolHelper * th, tools) {
if (th->section() == KoToolFactoryBase::mainToolType()) {
if (th->priority() < highestPriority) {
highestPriority = qMin(highestPriority, th->priority());
helper = th;
}
}
}
if (helper)
toolActivated(helper);
}
Connector *connector = new Connector(controller->canvas()->shapeManager());
connect(connector, SIGNAL(selectionChanged(QList<KoShape*>)), q,
SLOT(selectionChanged(QList<KoShape*>)));
connect(controller->canvas()->selectedShapesProxy(),
SIGNAL(currentLayerChanged(const KoShapeLayer*)),
q, SLOT(currentLayerChanged(const KoShapeLayer*)));
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to)
{
Q_UNUSED(from);
// no canvas anyway or no focus set anyway?
if (!canvasData || to == 0) {
return;
}
// Check if this app is about QWidget-based KoCanvasControllerWidget canvasses
// XXX: Focus handling for non-qwidget based canvases!
KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(canvasData->canvas);
if (!canvasControllerWidget) {
return;
}
// canvasWidget is set as focusproxy for KoCanvasControllerWidget,
// so all focus checks are to be done against canvasWidget objects
// focus returned to current canvas?
if (to == canvasData->canvas->canvas()->canvasWidget()) {
// nothing to do
return;
}
// if the 'to' is one of our canvasWidgets, then switch.
// for code simplicity the current canvas will be checked again,
// but would have been caught already in the lines above, so no issue
KoCanvasController *newCanvas = 0;
Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
if (canvas->canvas()->canvasWidget() == to) {
newCanvas = canvas;
break;
}
}
// none of our canvasWidgets got focus?
if (newCanvas == 0) {
return;
}
// switch to canvasdata matching inputdevice used last with this app instance
Q_FOREACH (CanvasData *data, canvasses.value(newCanvas)) {
if (data->inputDevice == inputDevice) {
switchCanvasData(data);
return;
}
}
// if no such inputDevice for this canvas, then simply fallback to first one
switchCanvasData(canvasses.value(newCanvas).first());
}
void KoToolManager::Private::updateCursor(const QCursor &cursor)
{
Q_ASSERT(canvasData);
Q_ASSERT(canvasData->canvas);
Q_ASSERT(canvasData->canvas->canvas());
canvasData->canvas->canvas()->setCursor(cursor);
}
void KoToolManager::Private::selectionChanged(const QList<KoShape*> &shapes)
{
QList<QString> types;
Q_FOREACH (KoShape *shape, shapes) {
QSet<KoShape*> delegates = shape->toolDelegates();
if (delegates.isEmpty()) { // no delegates, just the orig shape
delegates << shape;
}
foreach (KoShape *shape2, delegates) {
Q_ASSERT(shape2);
if (! types.contains(shape2->shapeId())) {
types.append(shape2->shapeId());
}
}
}
// check if there is still a shape selected the active tool can work on
// there needs to be at least one shape for a tool without an activationShapeId
// to work
// if not change the current tool to the default tool
const QStringList activationShapeIds = canvasData->activationShapeId.split(',');
if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0)
&& !activationShapeIds.contains("flake/always")
&& !activationShapeIds.contains("flake/edit")) {
bool currentToolWorks = false;
foreach (const QString &type, types) {
if (activationShapeIds.contains(type)) {
currentToolWorks = true;
break;
}
}
if (!currentToolWorks) {
switchTool(KoInteractionTool_ID, false);
}
}
emit q->toolCodesSelected(types);
}
void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer)
{
emit q->currentLayerChanged(canvasData->canvas, layer);
layerExplicitlyDisabled = layer && !layer->isShapeEditable();
updateToolForProxy();
debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled;
}
void KoToolManager::Private::updateToolForProxy()
{
KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas());
if(!proxy) return;
bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always"));
proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0);
}
void KoToolManager::Private::switchInputDevice(const KoInputDevice &device)
{
Q_ASSERT(canvasData);
if (!canvasData) return;
if (inputDevice == device) return;
if (inputDevice.isMouse() && device.isMouse()) return;
if (device.isMouse() && !inputDevice.isMouse()) {
// we never switch back to mouse from a tablet input device, so the user can use the
// mouse to edit the settings for a tool activated by a tablet. See bugs
// https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501.
// We do continue to switch between tablet devices, thought.
return;
}
QList<CanvasData*> items = canvasses[canvasData->canvas];
// search for a canvasdata object for the current input device
Q_FOREACH (CanvasData *cd, items) {
if (cd->inputDevice == device) {
switchCanvasData(cd);
if (!canvasData->activeTool) {
switchTool(KoInteractionTool_ID, false);
}
return;
}
}
// still here? That means we need to create a new CanvasData instance with the current InputDevice.
CanvasData *cd = createCanvasData(canvasData->canvas, device);
// switch to new canvas as the active one.
QString oldTool = canvasData->activeToolId;
items.append(cd);
canvasses[cd->canvas] = items;
switchCanvasData(cd);
q->switchToolRequested(oldTool);
}
void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas)
{
proxies.insert(canvas, proxy);
Q_FOREACH (KoCanvasController *controller, canvasses.keys()) {
if (controller->canvas() == canvas) {
proxy->priv()->setCanvasController(controller);
break;
}
}
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoToolManager.cpp"
diff --git a/libs/flake/resources/KoGamutMask.cpp b/libs/flake/resources/KoGamutMask.cpp
index d295438271..d7da355b0d 100644
--- a/libs/flake/resources/KoGamutMask.cpp
+++ b/libs/flake/resources/KoGamutMask.cpp
@@ -1,426 +1,428 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 "KoGamutMask.h"
#include <cstring>
#include <QVector>
#include <QString>
#include <QFile>
#include <QList>
#include <QDomDocument>
#include <QDomElement>
#include <QByteArray>
#include <QBuffer>
+#include <QScopedPointer>
#include <FlakeDebug.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoDocumentResourceManager.h>
#include <SvgParser.h>
#include <SvgWriter.h>
#include <KoShape.h>
#include <KisGamutMaskViewConverter.h>
#include <kis_assert.h>
KoGamutMaskShape::KoGamutMaskShape(KoShape* shape)
: m_maskShape(shape)
, m_shapePaintingContext(KoShapePaintingContext())
{
}
KoGamutMaskShape::KoGamutMaskShape() {};
KoGamutMaskShape::~KoGamutMaskShape() {};
KoShape* KoGamutMaskShape::koShape()
{
return m_maskShape;
}
bool KoGamutMaskShape::coordIsClear(const QPointF& coord, const KoViewConverter& viewConverter, int maskRotation) const
{
// apply mask rotation to coord
const KisGamutMaskViewConverter& converter = dynamic_cast<const KisGamutMaskViewConverter&>(viewConverter);
QPointF centerPoint(converter.viewSize().width()*0.5, converter.viewSize().height()*0.5);
QTransform rotationTransform;
rotationTransform.translate(centerPoint.x(), centerPoint.y());
rotationTransform.rotate(-maskRotation);
rotationTransform.translate(-centerPoint.x(), -centerPoint.y());
QPointF rotatedCoord = rotationTransform.map(coord);
QPointF translatedPoint = viewConverter.viewToDocument(rotatedCoord);
bool isClear = m_maskShape->hitTest(translatedPoint);
return isClear;
}
void KoGamutMaskShape::paint(QPainter &painter, const KoViewConverter& viewConverter, int maskRotation)
{
painter.save();
// apply mask rotation before drawing
QPointF centerPoint(painter.viewport().width()*0.5, painter.viewport().height()*0.5);
painter.translate(centerPoint);
painter.rotate(maskRotation);
painter.translate(-centerPoint);
painter.setTransform(m_maskShape->absoluteTransformation(&viewConverter) * painter.transform());
m_maskShape->paint(painter, viewConverter, m_shapePaintingContext);
painter.restore();
}
void KoGamutMaskShape::paintStroke(QPainter &painter, const KoViewConverter &viewConverter, int maskRotation)
{
painter.save();
// apply mask rotation before drawing
QPointF centerPoint(painter.viewport().width()*0.5, painter.viewport().height()*0.5);
painter.translate(centerPoint);
painter.rotate(maskRotation);
painter.translate(-centerPoint);
painter.setTransform(m_maskShape->absoluteTransformation(&viewConverter) * painter.transform());
m_maskShape->paintStroke(painter, viewConverter, m_shapePaintingContext);
painter.restore();
}
struct Q_DECL_HIDDEN KoGamutMask::Private {
QString name;
QString title;
QString description;
QByteArray data;
QVector<KoGamutMaskShape*> maskShapes;
QVector<KoGamutMaskShape*> previewShapes;
QSizeF maskSize;
int rotation;
};
KoGamutMask::KoGamutMask(const QString& filename)
: KoResource(filename)
, d(new Private())
{
d->maskSize = QSizeF(144.0,144.0);
setRotation(0);
}
KoGamutMask::KoGamutMask()
: KoResource(QString())
, d(new Private())
{
d->maskSize = QSizeF(144.0,144.0);
setRotation(0);
}
KoGamutMask::KoGamutMask(KoGamutMask* rhs)
: QObject(0)
, KoResource(QString())
, d(new Private())
{
setFilename(rhs->filename());
setTitle(rhs->title());
setDescription(rhs->description());
d->maskSize = rhs->d->maskSize;
QList<KoShape*> newShapes;
for(KoShape* sh: rhs->koShapes()) {
newShapes.append(sh->cloneShape());
}
setMaskShapes(newShapes);
setValid(true);
}
bool KoGamutMask::coordIsClear(const QPointF& coord, KoViewConverter &viewConverter, bool preview)
{
QVector<KoGamutMaskShape*>* shapeVector;
if (preview && !d->previewShapes.isEmpty()) {
shapeVector = &d->previewShapes;
} else {
shapeVector = &d->maskShapes;
}
for(KoGamutMaskShape* shape: *shapeVector) {
if (shape->coordIsClear(coord, viewConverter, rotation()) == true) {
return true;
}
}
return false;
}
void KoGamutMask::paint(QPainter &painter, KoViewConverter& viewConverter, bool preview)
{
QVector<KoGamutMaskShape*>* shapeVector;
if (preview && !d->previewShapes.isEmpty()) {
shapeVector = &d->previewShapes;
} else {
shapeVector = &d->maskShapes;
}
for(KoGamutMaskShape* shape: *shapeVector) {
shape->paint(painter, viewConverter, rotation());
}
}
void KoGamutMask::paintStroke(QPainter &painter, KoViewConverter &viewConverter, bool preview)
{
QVector<KoGamutMaskShape*>* shapeVector;
if (preview && !d->previewShapes.isEmpty()) {
shapeVector = &d->previewShapes;
} else {
shapeVector = &d->maskShapes;
}
for(KoGamutMaskShape* shape: *shapeVector) {
shape->paintStroke(painter, viewConverter, rotation());
}
}
bool KoGamutMask::load()
{
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnFlake << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KoGamutMask::loadFromDevice(QIODevice *dev)
{
if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
d->data = dev->readAll();
// TODO: test
KIS_ASSERT_RECOVER_RETURN_VALUE(d->data.size() != 0, false);
if (filename().isNull()) {
warnFlake << "Cannot load gamut mask" << name() << "there is no filename set";
return false;
}
if (d->data.isNull()) {
QFile file(filename());
if (file.size() == 0) {
warnFlake << "Cannot load gamut mask" << name() << "there is no data available";
return false;
}
file.open(QIODevice::ReadOnly);
d->data = file.readAll();
file.close();
}
QBuffer buf(&d->data);
buf.open(QBuffer::ReadOnly);
- KoStore* store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-gamutmask", KoStore::Zip));
+ QScopedPointer<KoStore> store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-gamutmask", KoStore::Zip));
if (!store || store->bad()) return false;
bool storeOpened = store->open("gamutmask.svg");
if (!storeOpened) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
KoXmlDocument xmlDocument = SvgParser::createDocumentFromSvg(ba, &errorMsg, &errorLine, &errorColumn);
if (xmlDocument.isNull()) {
errorFlake << "Parsing error in " << filename() << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
errorFlake << "Parsing error in the main document at line" << errorLine
<< ", column" << errorColumn << endl
<< "Error message: " << errorMsg;
return false;
}
KoDocumentResourceManager manager;
SvgParser parser(&manager);
parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
QSizeF fragmentSize;
QList<KoShape*> shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize);
d->maskSize = fragmentSize;
d->title = parser.documentTitle();
setName(d->title);
d->description = parser.documentDescription();
setMaskShapes(shapes);
if (store->open("preview.png")) {
- KoStoreDevice previewDev(store);
+ KoStoreDevice previewDev(store.data());
previewDev.open(QIODevice::ReadOnly);
QImage preview = QImage();
preview.load(&previewDev, "PNG");
setImage(preview);
(void)store->close();
}
buf.close();
setValid(true);
return true;
}
void KoGamutMask::setMaskShapes(QList<KoShape*> shapes)
{
setMaskShapesToVector(shapes, d->maskShapes);
}
bool KoGamutMask::save()
{
QFile file(filename());
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
saveToDevice(&file);
file.close();
return true;
}
QList<KoShape*> KoGamutMask::koShapes() const
{
QList<KoShape*> shapes;
for(KoGamutMaskShape* maskShape: d->maskShapes) {
shapes.append(maskShape->koShape());
}
return shapes;
}
bool KoGamutMask::saveToDevice(QIODevice *dev) const
{
KoStore* store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-gamutmask", KoStore::Zip));
if (!store || store->bad()) return false;
QList<KoShape*> shapes = koShapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
if (!store->open("gamutmask.svg")) {
return false;
}
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::WriteOnly);
SvgWriter writer(shapes);
writer.setDocumentTitle(d->title);
writer.setDocumentDescription(d->description);
writer.save(storeDev, d->maskSize);
if (!store->close()) { return false; }
if (!store->open("preview.png")) {
return false;
}
KoStoreDevice previewDev(store);
previewDev.open(QIODevice::WriteOnly);
image().save(&previewDev, "PNG");
if (!store->close()) { return false; }
return store->finalize();
}
QString KoGamutMask::title()
{
return d->title;
}
void KoGamutMask::setTitle(QString title)
{
d->title = title;
setName(title);
}
QString KoGamutMask::description()
{
return d->description;
}
void KoGamutMask::setDescription(QString description)
{
d->description = description;
}
int KoGamutMask::rotation()
{
return d->rotation;
}
void KoGamutMask::setRotation(int rotation)
{
d->rotation = rotation;
}
QSizeF KoGamutMask::maskSize()
{
return d->maskSize;
}
void KoGamutMask::setPreviewMaskShapes(QList<KoShape*> shapes)
{
setMaskShapesToVector(shapes, d->previewShapes);
}
void KoGamutMask::setMaskShapesToVector(QList<KoShape *> shapes, QVector<KoGamutMaskShape *> &targetVector)
{
targetVector.clear();
for(KoShape* sh: shapes) {
KoGamutMaskShape* maskShape = new KoGamutMaskShape(sh);
targetVector.append(maskShape);
}
}
// clean up when ending mask preview
void KoGamutMask::clearPreview()
{
d->previewShapes.clear();
}
diff --git a/libs/flake/resources/KoGamutMask.h b/libs/flake/resources/KoGamutMask.h
index a530b83fd8..bcaa2edb3a 100644
--- a/libs/flake/resources/KoGamutMask.h
+++ b/libs/flake/resources/KoGamutMask.h
@@ -1,99 +1,100 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 KOGAMUTMASK_H
#define KOGAMUTMASK_H
#include <QPainter>
#include <QString>
#include <QVector>
#include <cmath>
#include <FlakeDebug.h>
#include <resources/KoResource.h>
#include <KoShape.h>
#include <KisGamutMaskViewConverter.h>
#include <KoShapePaintingContext.h>
class KoViewConverter;
class KoGamutMaskShape
{
public:
KoGamutMaskShape(KoShape* shape);
KoGamutMaskShape();
~KoGamutMaskShape();
bool coordIsClear(const QPointF& coord, const KoViewConverter& viewConverter, int maskRotation) const;
QPainterPath outline();
void paint(QPainter &painter, const KoViewConverter& viewConverter, int maskRotation);
void paintStroke(QPainter &painter, const KoViewConverter& viewConverter, int maskRotation);
KoShape* koShape();
private:
KoShape* m_maskShape;
KoShapePaintingContext m_shapePaintingContext;
};
/**
* @brief The resource type for gamut masks used by the artistic color selector
*/
class KRITAFLAKE_EXPORT KoGamutMask : public QObject, public KoResource
{
Q_OBJECT
public:
KoGamutMask(const QString &filename);
KoGamutMask();
KoGamutMask(KoGamutMask *rhs);
bool coordIsClear(const QPointF& coord, KoViewConverter& viewConverter, bool preview);
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
void paint(QPainter &painter, KoViewConverter& viewConverter, bool preview);
void paintStroke(QPainter &painter, KoViewConverter& viewConverter, bool preview);
QString title();
void setTitle(QString title);
QString description();
void setDescription(QString description);
int rotation();
void setRotation(int rotation);
QSizeF maskSize();
void setMaskShapes(QList<KoShape*> shapes);
void setPreviewMaskShapes(QList<KoShape*> shapes);
QList<KoShape*> koShapes() const;
void clearPreview();
private:
void setMaskShapesToVector(QList<KoShape*> shapes, QVector<KoGamutMaskShape*>& targetVector);
struct Private;
Private* const d;
};
#endif // KOGAMUTMASK_H
diff --git a/libs/flake/svg/SvgGraphicContext.cpp b/libs/flake/svg/SvgGraphicContext.cpp
index e9fe97506f..aa3c1fdcec 100644
--- a/libs/flake/svg/SvgGraphicContext.cpp
+++ b/libs/flake/svg/SvgGraphicContext.cpp
@@ -1,62 +1,70 @@
/* This file is part of the KDE project
* Copyright (C) 2003,2005 Rob Buis <buis@kde.org>
* Copyright (C) 2007,2009 Jan Hambrecht <jaham@gmx.net>
*
* 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 "SvgGraphicContext.h"
#include "kis_pointer_utils.h"
SvgGraphicsContext::SvgGraphicsContext()
: stroke(toQShared(new KoShapeStroke()))
, textProperties(KoSvgTextProperties::defaultProperties())
{
stroke->setLineStyle(Qt::NoPen, QVector<qreal>()); // default is no stroke
stroke->setLineWidth(1.0);
stroke->setCapStyle(Qt::FlatCap);
stroke->setJoinStyle(Qt::MiterJoin);
}
+SvgGraphicsContext::SvgGraphicsContext(const SvgGraphicsContext &gc)
+ : stroke(toQShared(new KoShapeStroke(*(gc.stroke.data()))))
+{
+ KoShapeStrokeSP newStroke = stroke;
+ *this = gc;
+ this->stroke = newStroke;
+}
+
void SvgGraphicsContext::workaroundClearInheritedFillProperties()
{
/**
* HACK ALERT: according to SVG patterns, clip paths and clip masks
* must not inherit any properties from the referencing element.
* We still don't support it, therefore we reset only fill/stroke
* properties to avoid cyclic fill inheritance, which may cause
* infinite recursion.
*/
strokeType = None;
stroke = toQShared(new KoShapeStroke());
stroke->setLineStyle(Qt::NoPen, QVector<qreal>()); // default is no stroke
stroke->setLineWidth(1.0);
stroke->setCapStyle(Qt::FlatCap);
stroke->setJoinStyle(Qt::MiterJoin);
fillType = Solid;
fillRule = Qt::WindingFill;
fillColor = QColor(Qt::black); // default is black fill as per svg spec
opacity = 1.0;
currentColor = Qt::black;
}
diff --git a/libs/flake/svg/SvgGraphicContext.h b/libs/flake/svg/SvgGraphicContext.h
index 5e8c12f869..d2badbfb26 100644
--- a/libs/flake/svg/SvgGraphicContext.h
+++ b/libs/flake/svg/SvgGraphicContext.h
@@ -1,84 +1,86 @@
/* This file is part of the KDE project
* Copyright (C) 2003,2005 Rob Buis <buis@kde.org>
* Copyright (C) 2007,2009 Jan Hambrecht <jaham@gmx.net>
*
* 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 SVGGRAPHICCONTEXT_H
#define SVGGRAPHICCONTEXT_H
#include "kritaflake_export.h"
#include <KoShapeStroke.h>
#include <QFont>
#include <QTransform>
#include <text/KoSvgTextProperties.h>
class KRITAFLAKE_EXPORT SvgGraphicsContext
{
public:
// Fill/stroke styles
enum StyleType {
None, ///< no style
Solid, ///< solid style
Complex ///< gradient or pattern style
};
SvgGraphicsContext();
+ SvgGraphicsContext(const SvgGraphicsContext &gc);
+
void workaroundClearInheritedFillProperties();
StyleType fillType {Solid}; ///< the current fill type
Qt::FillRule fillRule {Qt::WindingFill}; ///< the current fill rule
QColor fillColor {QColor(Qt::black)}; ///< the current fill color. Default is black fill as per svg spec
QString fillId; ///< the current fill id (used for gradient/pattern fills)
StyleType strokeType {None};///< the current stroke type
QString strokeId; ///< the current stroke id (used for gradient strokes)
KoShapeStrokeSP stroke; ///< the current stroke
QString filterId; ///< the current filter id
QString clipPathId; ///< the current clip path id
QString clipMaskId; ///< the current clip mask id
Qt::FillRule clipRule {Qt::WindingFill}; ///< the current clip rule
qreal opacity {1.0}; ///< the shapes opacity
QTransform matrix; ///< the current transformation matrix
QFont font; ///< the current font
QStringList fontFamiliesList; ///< the full list of all the families to search glyphs in
QColor currentColor {Qt::black}; ///< the current color
QString xmlBaseDir; ///< the current base directory (used for loading external content)
bool preserveWhitespace {false}; ///< preserve whitespace in element text
QRectF currentBoundingBox; ///< the current bound box used for bounding box units
bool forcePercentage {false}; ///< force parsing coordinates/length as percentages of currentBoundbox
QTransform viewboxTransform; ///< view box transformation
bool display {true}; ///< controls display of shape
bool visible {true}; ///< controls visibility of the shape (inherited)
bool isResolutionFrame {false};
qreal pixelsPerInch {72.0}; ///< controls the resolution of the image raster
qreal forcedFontSizeCoeff {1.0}; ///< workaround for a Krita 3.3 odf-based files that use different resolution for font size. No workaround by default
QString markerStartId;
QString markerMidId;
QString markerEndId;
bool autoFillMarkers {false};
KoSvgTextProperties textProperties;
};
#endif // SVGGRAPHICCONTEXT_H
diff --git a/libs/flake/svg/SvgLoadingContext.cpp b/libs/flake/svg/SvgLoadingContext.cpp
index bf38bec314..c956eafc6b 100644
--- a/libs/flake/svg/SvgLoadingContext.cpp
+++ b/libs/flake/svg/SvgLoadingContext.cpp
@@ -1,302 +1,303 @@
/* This file is part of the KDE project
* Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
*
* 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 "SvgLoadingContext.h"
#include <QStack>
#include <QFileInfo>
#include <QDir>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceEngine.h>
#include <KoColorProfile.h>
#include <KoDocumentResourceManager.h>
#include <FlakeDebug.h>
#include "SvgGraphicContext.h"
#include "SvgUtil.h"
#include "SvgCssHelper.h"
#include "SvgStyleParser.h"
#include "kis_debug.h"
class Q_DECL_HIDDEN SvgLoadingContext::Private
{
public:
Private()
: zIndex(0), styleParser(0)
{
}
~Private()
{
if (! gcStack.isEmpty() && !gcStack.top()->isResolutionFrame) {
// Resolution frame is usually the first and is not removed.
warnFlake << "the context stack is not empty (current count" << gcStack.size() << ", expected 0)";
}
qDeleteAll(gcStack);
gcStack.clear();
delete styleParser;
}
QStack<SvgGraphicsContext*> gcStack;
QString initialXmlBaseDir;
int zIndex;
KoDocumentResourceManager *documentResourceManager;
QHash<QString, KoShape*> loadedShapes;
QHash<QString, KoXmlElement> definitions;
QHash<QString, const KoColorProfile*> profiles;
SvgCssHelper cssStyles;
SvgStyleParser *styleParser;
FileFetcherFunc fileFetcher;
};
SvgLoadingContext::SvgLoadingContext(KoDocumentResourceManager *documentResourceManager)
: d(new Private())
{
d->documentResourceManager = documentResourceManager;
d->styleParser = new SvgStyleParser(*this);
Q_ASSERT(d->documentResourceManager);
}
SvgLoadingContext::~SvgLoadingContext()
{
delete d;
}
SvgGraphicsContext *SvgLoadingContext::currentGC() const
{
if (d->gcStack.isEmpty())
return 0;
return d->gcStack.top();
}
#include "parsers/SvgTransformParser.h"
SvgGraphicsContext *SvgLoadingContext::pushGraphicsContext(const KoXmlElement &element, bool inherit)
{
- SvgGraphicsContext *gc = new SvgGraphicsContext;
-
+ SvgGraphicsContext *gc;
// copy data from current context
if (! d->gcStack.isEmpty() && inherit) {
- *gc = *(d->gcStack.top());
+ gc = new SvgGraphicsContext(*d->gcStack.top());
+ } else {
+ gc = new SvgGraphicsContext();
}
gc->textProperties.resetNonInheritableToDefault(); // some of the text properties are not inherited
gc->filterId.clear(); // filters are not inherited
gc->clipPathId.clear(); // clip paths are not inherited
gc->clipMaskId.clear(); // clip masks are not inherited
gc->display = true; // display is not inherited
gc->opacity = 1.0; // opacity is not inherited
if (!element.isNull()) {
if (element.hasAttribute("transform")) {
SvgTransformParser p(element.attribute("transform"));
if (p.isValid()) {
QTransform mat = p.transform();
gc->matrix = mat * gc->matrix;
}
}
if (element.hasAttribute("xml:base"))
gc->xmlBaseDir = element.attribute("xml:base");
if (element.hasAttribute("xml:space"))
gc->preserveWhitespace = element.attribute("xml:space") == "preserve";
}
d->gcStack.push(gc);
return gc;
}
void SvgLoadingContext::popGraphicsContext()
{
delete(d->gcStack.pop());
}
void SvgLoadingContext::setInitialXmlBaseDir(const QString &baseDir)
{
d->initialXmlBaseDir = baseDir;
}
QString SvgLoadingContext::xmlBaseDir() const
{
SvgGraphicsContext *gc = currentGC();
return (gc && !gc->xmlBaseDir.isEmpty()) ? gc->xmlBaseDir : d->initialXmlBaseDir;
}
QString SvgLoadingContext::absoluteFilePath(const QString &href)
{
QFileInfo info(href);
if (! info.isRelative())
return href;
SvgGraphicsContext *gc = currentGC();
if (!gc)
return d->initialXmlBaseDir;
QString baseDir = d->initialXmlBaseDir;
if (! gc->xmlBaseDir.isEmpty())
baseDir = absoluteFilePath(gc->xmlBaseDir);
QFileInfo pathInfo(QFileInfo(baseDir).filePath());
QString relFile = href;
while (relFile.startsWith(QLatin1String("../"))) {
relFile.remove(0, 3);
pathInfo.setFile(pathInfo.dir(), QString());
}
QString absFile = pathInfo.absolutePath() + '/' + relFile;
return absFile;
}
QString SvgLoadingContext::relativeFilePath(const QString &href)
{
const SvgGraphicsContext *gc = currentGC();
if (!gc) return href;
QString result = href;
if (!gc->xmlBaseDir.isEmpty()) {
result = gc->xmlBaseDir + QDir::separator() + href;
} else if (!d->initialXmlBaseDir.isEmpty()) {
result = d->initialXmlBaseDir + QDir::separator() + href;
}
return QDir::cleanPath(result);
}
int SvgLoadingContext::nextZIndex()
{
return d->zIndex++;
}
KoImageCollection* SvgLoadingContext::imageCollection()
{
return d->documentResourceManager->imageCollection();
}
void SvgLoadingContext::registerShape(const QString &id, KoShape *shape)
{
if (!id.isEmpty())
d->loadedShapes.insert(id, shape);
}
KoShape* SvgLoadingContext::shapeById(const QString &id)
{
return d->loadedShapes.value(id);
}
void SvgLoadingContext::addDefinition(const KoXmlElement &element)
{
const QString id = element.attribute("id");
if (id.isEmpty() || d->definitions.contains(id))
return;
d->definitions.insert(id, element);
}
KoXmlElement SvgLoadingContext::definition(const QString &id) const
{
return d->definitions.value(id);
}
bool SvgLoadingContext::hasDefinition(const QString &id) const
{
return d->definitions.contains(id);
}
void SvgLoadingContext::addStyleSheet(const KoXmlElement &styleSheet)
{
d->cssStyles.parseStylesheet(styleSheet);
}
QStringList SvgLoadingContext::matchingCssStyles(const KoXmlElement &element) const
{
return d->cssStyles.matchStyles(element);
}
SvgStyleParser &SvgLoadingContext::styleParser()
{
return *d->styleParser;
}
void SvgLoadingContext::parseProfile(const KoXmlElement &element)
{
const QString href = element.attribute("xlink:href");
const QByteArray uniqueId = QByteArray::fromHex(element.attribute("local").toLatin1());
const QString name = element.attribute("name");
if (element.attribute("rendering-intent", "auto") != "auto") {
// WARNING: Krita does *not* treat rendering intents attributes of the profile!
debugFlake << "WARNING: we do *not* treat rendering intents attributes of the profile!";
}
if (d->profiles.contains(name)) {
debugFlake << "Profile already in the map!" << ppVar(name);
return;
}
const KoColorProfile *profile =
KoColorSpaceRegistry::instance()->profileByUniqueId(uniqueId);
if (!profile && d->fileFetcher) {
KoColorSpaceEngine *engine = KoColorSpaceEngineRegistry::instance()->get("icc");
KIS_ASSERT(engine);
if (engine) {
const QString fileName = relativeFilePath(href);
const QByteArray profileData = d->fileFetcher(fileName);
if (!profileData.isEmpty()) {
profile = engine->addProfile(profileData);
if (profile->uniqueId() != uniqueId) {
debugFlake << "WARNING: ProfileID of the attached profile doesn't match the one mentioned in SVG element";
debugFlake << " " << ppVar(profile->uniqueId().toHex());
debugFlake << " " << ppVar(uniqueId.toHex());
}
} else {
debugFlake << "WARNING: couldn't fetch the ICCprofile file!" << fileName;
}
}
}
if (profile) {
d->profiles.insert(name, profile);
} else {
debugFlake << "WARNING: couldn't load SVG profile" << ppVar(name) << ppVar(href) << ppVar(uniqueId);
}
}
bool SvgLoadingContext::isRootContext() const
{
KIS_ASSERT(!d->gcStack.isEmpty());
return d->gcStack.size() == 1;
}
void SvgLoadingContext::setFileFetcher(SvgLoadingContext::FileFetcherFunc func)
{
d->fileFetcher = func;
}
QByteArray SvgLoadingContext::fetchExternalFile(const QString &url)
{
return d->fileFetcher ? d->fileFetcher(url) : QByteArray();
}
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp
index f25eb16d1b..94db99a8dc 100644
--- a/libs/flake/svg/SvgParser.cpp
+++ b/libs/flake/svg/SvgParser.cpp
@@ -1,1930 +1,1933 @@
/* This file is part of the KDE project
* Copyright (C) 2002-2005,2007 Rob Buis <buis@kde.org>
* Copyright (C) 2002-2004 Nicolas Goutte <nicolasg@snafu.de>
* Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
* Copyright (C) 2005-2009 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2005,2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2007 Inge Wallin <inge@lysator.liu.se>
* Copyright (C) 2007-2008,2010 Thorsten Zachmann <zachmann@kde.org>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License 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 "SvgParser.h"
#include <cmath>
#include <FlakeDebug.h>
#include <QColor>
#include <QPainter>
#include <QDir>
#include <KoShape.h>
#include <KoShapeRegistry.h>
#include <KoShapeFactoryBase.h>
#include <KoShapeGroup.h>
#include <KoPathShape.h>
#include <KoDocumentResourceManager.h>
#include <KoPathShapeLoader.h>
#include <commands/KoShapeGroupCommand.h>
#include <commands/KoShapeUngroupCommand.h>
#include <KoXmlReader.h>
#include <KoImageCollection.h>
#include <KoColorBackground.h>
#include <KoGradientBackground.h>
#include <KoPatternBackground.h>
#include <KoFilterEffectRegistry.h>
#include <KoFilterEffect.h>
#include "KoFilterEffectStack.h"
#include "KoFilterEffectLoadingContext.h"
#include <KoClipPath.h>
#include <KoClipMask.h>
#include <KoXmlNS.h>
#include <QXmlSimpleReader>
#include "SvgUtil.h"
#include "SvgShape.h"
#include "SvgGraphicContext.h"
#include "SvgFilterHelper.h"
#include "SvgGradientHelper.h"
#include "SvgClipPathHelper.h"
#include "parsers/SvgTransformParser.h"
#include "kis_pointer_utils.h"
#include <KoVectorPatternBackground.h>
#include <KoMarker.h>
#include <text/KoSvgTextShape.h>
#include <text/KoSvgTextChunkShape.h>
#include "kis_dom_utils.h"
#include "kis_algebra_2d.h"
#include "kis_debug.h"
#include "kis_global.h"
#include <algorithm>
struct SvgParser::DeferredUseStore {
struct El {
El(const KoXmlElement* ue, const QString& key) :
m_useElement(ue), m_key(key) {
}
const KoXmlElement* m_useElement;
QString m_key;
};
DeferredUseStore(SvgParser* p) :
m_parse(p) {
}
void add(const KoXmlElement* useE, const QString& key) {
m_uses.push_back(El(useE, key));
}
bool empty() const {
return m_uses.empty();
}
void checkPendingUse(const KoXmlElement &b, QList<KoShape*>& shapes) {
KoShape* shape = 0;
const QString id = b.attribute("id");
if (id.isEmpty())
return;
// debugFlake << "Checking id: " << id;
auto i = std::partition(m_uses.begin(), m_uses.end(),
[&](const El& e) -> bool {return e.m_key != id;});
while (i != m_uses.end()) {
const El& el = m_uses.back();
if (m_parse->m_context.hasDefinition(el.m_key)) {
// debugFlake << "Found pending use for id: " << el.m_key;
shape = m_parse->resolveUse(*(el.m_useElement), el.m_key);
if (shape) {
shapes.append(shape);
}
}
m_uses.pop_back();
}
}
~DeferredUseStore() {
while (!m_uses.empty()) {
const El& el = m_uses.back();
debugFlake << "WARNING: could not find path in <use xlink:href=\"#xxxxx\" expression. Losing data here. Key:"
<< el.m_key;
m_uses.pop_back();
}
}
SvgParser* m_parse;
std::vector<El> m_uses;
};
SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager)
: m_context(documentResourceManager)
, m_documentResourceManager(documentResourceManager)
{
}
SvgParser::~SvgParser()
{
qDeleteAll(m_symbols);
}
KoXmlDocument SvgParser::createDocumentFromSvg(QIODevice *device, QString *errorMsg, int *errorLine, int *errorColumn)
{
QXmlInputSource source(device);
return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn);
}
KoXmlDocument SvgParser::createDocumentFromSvg(const QByteArray &data, QString *errorMsg, int *errorLine, int *errorColumn)
{
QXmlInputSource source;
source.setData(data);
return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn);
}
KoXmlDocument SvgParser::createDocumentFromSvg(const QString &data, QString *errorMsg, int *errorLine, int *errorColumn)
{
QXmlInputSource source;
source.setData(data);
return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn);
}
KoXmlDocument SvgParser::createDocumentFromSvg(QXmlInputSource *source, QString *errorMsg, int *errorLine, int *errorColumn)
{
// we should read all spaces to parse text node correctly
QXmlSimpleReader reader;
reader.setFeature("http://qt-project.org/xml/features/report-whitespace-only-CharData", true);
reader.setFeature("http://xml.org/sax/features/namespaces", false);
reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
QDomDocument doc;
if (!doc.setContent(source, &reader, errorMsg, errorLine, errorColumn)) {
return QDomDocument();
}
return doc;
}
void SvgParser::setXmlBaseDir(const QString &baseDir)
{
m_context.setInitialXmlBaseDir(baseDir);
setFileFetcher(
[this](const QString &name) {
const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name;
QFile file(fileName);
if (!file.exists()) {
return QByteArray();
}
file.open(QIODevice::ReadOnly);
return file.readAll();
});
}
void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
{
KIS_ASSERT(!m_context.currentGC());
m_context.pushGraphicsContext();
m_context.currentGC()->isResolutionFrame = true;
m_context.currentGC()->pixelsPerInch = pixelsPerInch;
const qreal scale = 72.0 / pixelsPerInch;
const QTransform t = QTransform::fromScale(scale, scale);
m_context.currentGC()->currentBoundingBox = boundsInPixels;
m_context.currentGC()->matrix = t;
}
void SvgParser::setForcedFontSizeResolution(qreal value)
{
if (qFuzzyCompare(value, 0.0)) return;
m_context.currentGC()->forcedFontSizeCoeff = 72.0 / value;
}
QList<KoShape*> SvgParser::shapes() const
{
return m_shapes;
}
QVector<KoSvgSymbol *> SvgParser::takeSymbols()
{
QVector<KoSvgSymbol*> symbols = m_symbols;
m_symbols.clear();
return symbols;
}
// Helper functions
// ---------------------------------------------------------------------------------------
SvgGradientHelper* SvgParser::findGradient(const QString &id)
{
SvgGradientHelper *result = 0;
// check if gradient was already parsed, and return it
if (m_gradients.contains(id)) {
result = &m_gradients[ id ];
}
// check if gradient was stored for later parsing
if (!result && m_context.hasDefinition(id)) {
const KoXmlElement &e = m_context.definition(id);
if (e.tagName().contains("Gradient")) {
result = parseGradient(m_context.definition(id));
}
}
return result;
}
QSharedPointer<KoVectorPatternBackground> SvgParser::findPattern(const QString &id, const KoShape *shape)
{
QSharedPointer<KoVectorPatternBackground> result;
// check if gradient was stored for later parsing
if (m_context.hasDefinition(id)) {
const KoXmlElement &e = m_context.definition(id);
if (e.tagName() == "pattern") {
result = parsePattern(m_context.definition(id), shape);
}
}
return result;
}
SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href)
{
// check if filter was already parsed, and return it
if (m_filters.contains(id))
return &m_filters[ id ];
// check if filter was stored for later parsing
if (!m_context.hasDefinition(id))
return 0;
const KoXmlElement &e = m_context.definition(id);
if (KoXml::childNodesCount(e) == 0) {
QString mhref = e.attribute("xlink:href").mid(1);
if (m_context.hasDefinition(mhref))
return findFilter(mhref, id);
else
return 0;
} else {
// ok parse filter now
if (! parseFilter(m_context.definition(id), m_context.definition(href)))
return 0;
}
// return successfully parsed filter or 0
QString n;
if (href.isEmpty())
n = id;
else
n = href;
if (m_filters.contains(n))
return &m_filters[ n ];
else
return 0;
}
SvgClipPathHelper* SvgParser::findClipPath(const QString &id)
{
return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0;
}
// Parsing functions
// ---------------------------------------------------------------------------------------
qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox)
{
return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox);
}
qreal SvgParser::parseUnitX(const QString &unit)
{
return SvgUtil::parseUnitX(m_context.currentGC(), unit);
}
qreal SvgParser::parseUnitY(const QString &unit)
{
return SvgUtil::parseUnitY(m_context.currentGC(), unit);
}
qreal SvgParser::parseUnitXY(const QString &unit)
{
return SvgUtil::parseUnitXY(m_context.currentGC(), unit);
}
qreal SvgParser::parseAngular(const QString &unit)
{
return SvgUtil::parseUnitAngular(m_context.currentGC(), unit);
}
SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e)
{
// IMPROVEMENTS:
// - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again.
// - A gradient inherits attributes it does not have from the referencing gradient.
// - Gradients with no color stops have no fill or stroke.
// - Gradients with one color stop have a solid color.
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc) return 0;
SvgGradientHelper gradHelper;
QString gradientId = e.attribute("id");
if (gradientId.isEmpty()) return 0;
// check if we have this gradient already parsed
// copy existing gradient if it exists
if (m_gradients.contains(gradientId)) {
return &m_gradients[gradientId];
}
if (e.hasAttribute("xlink:href")) {
// strip the '#' symbol
QString href = e.attribute("xlink:href").mid(1);
if (!href.isEmpty()) {
// copy the referenced gradient if found
SvgGradientHelper *pGrad = findGradient(href);
if (pGrad) {
gradHelper = *pGrad;
}
}
}
const QGradientStops defaultStops = gradHelper.gradient()->stops();
if (e.attribute("gradientUnits") == "userSpaceOnUse") {
gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse);
}
m_context.pushGraphicsContext(e);
uploadStyleToContext(e);
if (e.tagName() == "linearGradient") {
QLinearGradient *g = new QLinearGradient();
if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")),
SvgUtil::fromPercentage(e.attribute("y1", "0%"))));
g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")),
SvgUtil::fromPercentage(e.attribute("y2", "0%"))));
} else {
g->setStart(QPointF(parseUnitX(e.attribute("x1")),
parseUnitY(e.attribute("y1"))));
g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")),
parseUnitY(e.attribute("y2"))));
}
gradHelper.setGradient(g);
} else if (e.tagName() == "radialGradient") {
QRadialGradient *g = new QRadialGradient();
if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")),
SvgUtil::fromPercentage(e.attribute("cy", "50%"))));
g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%")));
g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")),
SvgUtil::fromPercentage(e.attribute("fy", "50%"))));
} else {
g->setCenter(QPointF(parseUnitX(e.attribute("cx")),
parseUnitY(e.attribute("cy"))));
g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")),
parseUnitY(e.attribute("fy"))));
g->setRadius(parseUnitXY(e.attribute("r")));
}
gradHelper.setGradient(g);
} else {
debugFlake << "WARNING: Failed to parse gradient with tag" << e.tagName();
}
// handle spread method
QGradient::Spread spreadMethod = QGradient::PadSpread;
QString spreadMethodStr = e.attribute("spreadMethod");
if (!spreadMethodStr.isEmpty()) {
if (spreadMethodStr == "reflect") {
spreadMethod = QGradient::ReflectSpread;
} else if (spreadMethodStr == "repeat") {
spreadMethod = QGradient::RepeatSpread;
}
}
gradHelper.setSpreadMode(spreadMethod);
// Parse the color stops.
m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops);
if (e.hasAttribute("gradientTransform")) {
SvgTransformParser p(e.attribute("gradientTransform"));
if (p.isValid()) {
gradHelper.setTransform(p.transform());
}
}
m_context.popGraphicsContext();
m_gradients.insert(gradientId, gradHelper);
return &m_gradients[gradientId];
}
inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
{
QTransform result =
patternTransform *
QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) *
patternTransform.inverted();
KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate);
return QPointF(result.dx(), result.dy());
}
QSharedPointer<KoVectorPatternBackground> SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape)
{
/**
* Unlike the gradient parsing function, this method is called every time we
* *reference* the pattern, not when we define it. Therefore we can already
* use the coordinate system of the destination.
*/
QSharedPointer<KoVectorPatternBackground> pattHelper;
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc) return pattHelper;
const QString patternId = e.attribute("id");
if (patternId.isEmpty()) return pattHelper;
pattHelper = toQShared(new KoVectorPatternBackground);
if (e.hasAttribute("xlink:href")) {
// strip the '#' symbol
QString href = e.attribute("xlink:href").mid(1);
if (!href.isEmpty() &&href != patternId) {
// copy the referenced pattern if found
QSharedPointer<KoVectorPatternBackground> pPatt = findPattern(href, shape);
if (pPatt) {
pattHelper = pPatt;
}
}
}
pattHelper->setReferenceCoordinates(
KoFlake::coordinatesFromString(e.attribute("patternUnits"),
pattHelper->referenceCoordinates()));
pattHelper->setContentCoordinates(
KoFlake::coordinatesFromString(e.attribute("patternContentUnits"),
pattHelper->contentCoordinates()));
if (e.hasAttribute("patternTransform")) {
SvgTransformParser p(e.attribute("patternTransform"));
if (p.isValid()) {
pattHelper->setPatternTransform(p.transform());
}
}
if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) {
QRectF referenceRect(
SvgUtil::fromPercentage(e.attribute("x", "0%")),
SvgUtil::fromPercentage(e.attribute("y", "0%")),
SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why!
SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why!
pattHelper->setReferenceRect(referenceRect);
} else {
QRectF referenceRect(
parseUnitX(e.attribute("x", "0")),
parseUnitY(e.attribute("y", "0")),
parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why!
parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why!
pattHelper->setReferenceRect(referenceRect);
}
/**
* In Krita shapes X,Y coordinates are baked into the shape global transform, but
* the pattern should be painted in "user" coordinates. Therefore, we should handle
* this offfset separately.
*
* TODO: Please also note that this offset is different from extraShapeOffset(),
* because A.inverted() * B != A * B.inverted(). I'm not sure which variant is
* correct (DK)
*/
const QTransform dstShapeTransform = shape->absoluteTransformation(0);
const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted();
KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate);
const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy());
m_context.pushGraphicsContext(e);
gc = m_context.currentGC();
gc->workaroundClearInheritedFillProperties(); // HACK!
// start building shape tree from scratch
gc->matrix = QTransform();
const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/;
const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
// WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes!
// although we expect the pattern be reusable, but it is not so!
// WARNING2: the pattern shapes are stored in *User* coordinate system, although
// the "official" content system might be either OBB or User. It means that
// this baked transform should be stripped before writing the shapes back
// into SVG
if (e.hasAttribute("viewBox")) {
gc->currentBoundingBox =
pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ?
relativeToShape.mapRect(pattHelper->referenceRect()) :
pattHelper->referenceRect();
applyViewBoxTransform(e);
pattHelper->setContentCoordinates(pattHelper->referenceCoordinates());
} else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) {
gc->matrix = relativeToShape * gc->matrix;
}
// We do *not* apply patternTransform here! Here we only bake the untransformed
// version of the shape. The transformed one will be done in the very end while rendering.
QList<KoShape*> patternShapes = parseContainer(e);
if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) {
// In Krita we normalize the shapes, bake this transform into the pattern shapes
const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
Q_FOREACH (KoShape *shape, patternShapes) {
shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y()));
}
}
if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) {
// In Krita we normalize the shapes, bake this transform into reference rect
// NOTE: this is possible *only* when pattern transform is not perspective
// (which is always true for SVG)
const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
QRectF ref = pattHelper->referenceRect();
ref.translate(offset);
pattHelper->setReferenceRect(ref);
}
m_context.popGraphicsContext();
gc = m_context.currentGC();
if (!patternShapes.isEmpty()) {
pattHelper->setShapes(patternShapes);
}
return pattHelper;
}
bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy)
{
SvgFilterHelper filter;
// Use the filter that is referencing, or if there isn't one, the original filter
KoXmlElement b;
if (!referencedBy.isNull())
b = referencedBy;
else
b = e;
// check if we are referencing another filter
if (e.hasAttribute("xlink:href")) {
QString href = e.attribute("xlink:href").mid(1);
if (! href.isEmpty()) {
// copy the referenced filter if found
SvgFilterHelper *refFilter = findFilter(href);
if (refFilter)
filter = *refFilter;
}
} else {
filter.setContent(b);
}
if (b.attribute("filterUnits") == "userSpaceOnUse")
filter.setFilterUnits(KoFlake::UserSpaceOnUse);
if (b.attribute("primitiveUnits") == "objectBoundingBox")
filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox);
// parse filter region rectangle
if (filter.filterUnits() == KoFlake::UserSpaceOnUse) {
filter.setPosition(QPointF(parseUnitX(b.attribute("x")),
parseUnitY(b.attribute("y"))));
filter.setSize(QSizeF(parseUnitX(b.attribute("width")),
parseUnitY(b.attribute("height"))));
} else {
// x, y, width, height are in percentages of the object referencing the filter
// so we just parse the percentages
filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")),
SvgUtil::fromPercentage(b.attribute("y", "-0.1"))));
filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")),
SvgUtil::fromPercentage(b.attribute("height", "1.2"))));
}
m_filters.insert(b.attribute("id"), filter);
return true;
}
bool SvgParser::parseMarker(const KoXmlElement &e)
{
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
QScopedPointer<KoMarker> marker(new KoMarker());
marker->setCoordinateSystem(
KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth")));
marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")),
parseUnitY(e.attribute("refY"))));
marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")),
parseUnitY(e.attribute("markerHeight", "3"))));
const QString orientation = e.attribute("orient", "0");
if (orientation == "auto") {
marker->setAutoOrientation(true);
} else {
marker->setExplicitOrientation(parseAngular(orientation));
}
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e, false);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize());
KoShape *markerShape = parseGroup(e);
m_context.popGraphicsContext();
if (!markerShape) return false;
marker->setShapes({markerShape});
m_markers.insert(id, QExplicitlySharedDataPointer<KoMarker>(marker.take()));
return true;
}
bool SvgParser::parseSymbol(const KoXmlElement &e)
{
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
QScopedPointer<KoSvgSymbol> svgSymbol(new KoSvgSymbol());
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e, false);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0);
QString title = e.firstChildElement("title").toElement().text();
QScopedPointer<KoShape> symbolShape(parseGroup(e));
m_context.popGraphicsContext();
if (!symbolShape) return false;
svgSymbol->shape = symbolShape.take();
svgSymbol->title = title;
svgSymbol->id = id;
if (title.isEmpty()) svgSymbol->title = id;
if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) {
debugFlake << "Symbol" << id << "seems to be empty, discarding";
return false;
}
m_symbols << svgSymbol.take();
return true;
}
bool SvgParser::parseClipPath(const KoXmlElement &e)
{
SvgClipPathHelper clipPath;
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
clipPath.setClipPathUnits(
KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse));
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK!
KoShape *clipShape = parseGroup(e);
m_context.popGraphicsContext();
if (!clipShape) return false;
clipPath.setShapes({clipShape});
m_clipPaths.insert(id, clipPath);
return true;
}
bool SvgParser::parseClipMask(const KoXmlElement &e)
{
QSharedPointer<KoClipMask> clipMask(new KoClipMask);
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox));
clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse));
QRectF maskRect;
if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) {
maskRect.setRect(
SvgUtil::fromPercentage(e.attribute("x", "-10%")),
SvgUtil::fromPercentage(e.attribute("y", "-10%")),
SvgUtil::fromPercentage(e.attribute("width", "120%")),
SvgUtil::fromPercentage(e.attribute("height", "120%")));
} else {
maskRect.setRect(
parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case,
parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us...
parseUnitX(e.attribute("width", "120%")),
parseUnitY(e.attribute("height", "120%")));
}
clipMask->setMaskRect(maskRect);
// ensure that the clip mask is loaded in local coordinates system
m_context.pushGraphicsContext(e);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK!
KoShape *clipShape = parseGroup(e);
m_context.popGraphicsContext();
if (!clipShape) return false;
clipMask->setShapes({clipShape});
m_clipMasks.insert(id, clipMask);
return true;
}
void SvgParser::uploadStyleToContext(const KoXmlElement &e)
{
SvgStyles styles = m_context.styleParser().collectStyles(e);
m_context.styleParser().parseFont(styles);
m_context.styleParser().parseStyle(styles);
}
void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
if (!shape) return;
applyCurrentBasicStyle(shape);
if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape)) {
applyMarkers(pathShape);
}
applyFilter(shape);
applyClipping(shape, shapeToOriginalUserCoordinates);
applyMaskClipping(shape, shapeToOriginalUserCoordinates);
}
void SvgParser::applyCurrentBasicStyle(KoShape *shape)
{
if (!shape) return;
SvgGraphicsContext *gc = m_context.currentGC();
KIS_ASSERT(gc);
if (!dynamic_cast<KoShapeGroup*>(shape)) {
applyFillStyle(shape);
applyStrokeStyle(shape);
}
if (!gc->display || !gc->visible) {
/**
* WARNING: here is a small inconsistency with the standard:
* in the standard, 'display' is not inherited, but in
* flake it is!
*
* NOTE: though the standard says: "A value of 'display:none' indicates
* that the given element and ***its children*** shall not be
* rendered directly". Therefore, using setVisible(false) is fully
* legitimate here (DK 29.11.16).
*/
shape->setVisible(false);
}
shape->setTransparency(1.0 - gc->opacity);
}
void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates)
{
applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates);
}
void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
m_context.styleParser().parseStyle(styles);
if (!obj)
return;
if (!dynamic_cast<KoShapeGroup*>(obj)) {
applyFillStyle(obj);
applyStrokeStyle(obj);
}
if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(obj)) {
applyMarkers(pathShape);
}
applyFilter(obj);
applyClipping(obj, shapeToOriginalUserCoordinates);
applyMaskClipping(obj, shapeToOriginalUserCoordinates);
if (!gc->display || !gc->visible) {
obj->setVisible(false);
}
obj->setTransparency(1.0 - gc->opacity);
}
QGradient* prepareGradientForShape(const SvgGradientHelper *gradient,
const KoShape *shape,
const SvgGraphicsContext *gc,
QTransform *transform)
{
QGradient *resultGradient = 0;
KIS_ASSERT(transform);
if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
resultGradient = KoFlake::cloneGradient(gradient->gradient());
*transform = gradient->transform();
} else {
if (gradient->gradient()->type() == QGradient::LinearGradient) {
/**
* Create a converted gradient that looks the same, but linked to the
* bounding rect of the shape, so it would be transformed with the shape
*/
const QRectF boundingRect = shape->outline().boundingRect();
const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
const QTransform relativeToUser =
relativeToShape * shape->transformation() * gc->matrix.inverted();
const QTransform userToRelative = relativeToUser.inverted();
const QLinearGradient *o = static_cast<const QLinearGradient*>(gradient->gradient());
QLinearGradient *g = new QLinearGradient();
g->setStart(userToRelative.map(o->start()));
g->setFinalStop(userToRelative.map(o->finalStop()));
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setStops(o->stops());
g->setSpread(o->spread());
resultGradient = g;
*transform = relativeToUser * gradient->transform() * userToRelative;
} else if (gradient->gradient()->type() == QGradient::RadialGradient) {
// For radial and conical gradients such conversion is not possible
resultGradient = KoFlake::cloneGradient(gradient->gradient());
*transform = gradient->transform() * gc->matrix * shape->transformation().inverted();
const QRectF outlineRect = shape->outlineRect();
if (outlineRect.isEmpty()) return resultGradient;
/**
* If shape outline rect is valid, convert the gradient into OBB mode by
* doing some magic conversions: we compensate non-uniform size of the shape
* by applying an additional pre-transform
*/
QRadialGradient *rgradient = static_cast<QRadialGradient*>(resultGradient);
const qreal maxDimension = KisAlgebra2D::maxDimension(outlineRect);
const QRectF uniformSize(outlineRect.topLeft(), QSizeF(maxDimension, maxDimension));
const QTransform uniformizeTransform =
QTransform::fromTranslate(-outlineRect.x(), -outlineRect.y()) *
QTransform::fromScale(maxDimension / shape->outlineRect().width(),
maxDimension / shape->outlineRect().height()) *
QTransform::fromTranslate(outlineRect.x(), outlineRect.y());
const QPointF centerLocal = transform->map(rgradient->center());
const QPointF focalLocal = transform->map(rgradient->focalPoint());
const QPointF centerOBB = KisAlgebra2D::absoluteToRelative(centerLocal, uniformSize);
const QPointF focalOBB = KisAlgebra2D::absoluteToRelative(focalLocal, uniformSize);
rgradient->setCenter(centerOBB);
rgradient->setFocalPoint(focalOBB);
const qreal centerRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->centerRadius(), uniformSize);
const qreal focalRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->focalRadius(), uniformSize);
rgradient->setCenterRadius(centerRadiusOBB);
rgradient->setFocalRadius(focalRadiusOBB);
rgradient->setCoordinateMode(QGradient::ObjectBoundingMode);
// Warning: should it really be pre-multiplication?
*transform = uniformizeTransform * gradient->transform();
}
}
return resultGradient;
}
void SvgParser::applyFillStyle(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->fillType == SvgGraphicsContext::None) {
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
} else if (gc->fillType == SvgGraphicsContext::Solid) {
shape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(gc->fillColor)));
} else if (gc->fillType == SvgGraphicsContext::Complex) {
// try to find referenced gradient
SvgGradientHelper *gradient = findGradient(gc->fillId);
if (gradient) {
QTransform transform;
QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
if (result) {
QSharedPointer<KoGradientBackground> bg;
bg = toQShared(new KoGradientBackground(result));
bg->setTransform(transform);
shape->setBackground(bg);
}
} else {
QSharedPointer<KoVectorPatternBackground> pattern =
findPattern(gc->fillId, shape);
if (pattern) {
shape->setBackground(pattern);
} else {
// no referenced fill found, use fallback color
shape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(gc->fillColor)));
}
}
}
KoPathShape *path = dynamic_cast<KoPathShape*>(shape);
if (path)
path->setFillRule(gc->fillRule);
}
void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
{
const double lineWidth = srcStroke->lineWidth();
QVector<qreal> dashes = srcStroke->lineDashes();
// apply line width to dashes and dash offset
if (dashes.count() && lineWidth > 0.0) {
const double dashOffset = srcStroke->dashOffset();
QVector<qreal> dashes = srcStroke->lineDashes();
for (int i = 0; i < dashes.count(); ++i) {
dashes[i] /= lineWidth;
}
dstStroke->setLineStyle(Qt::CustomDashLine, dashes);
dstStroke->setDashOffset(dashOffset / lineWidth);
} else {
dstStroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
}
}
void SvgParser::applyStrokeStyle(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->strokeType == SvgGraphicsContext::None) {
shape->setStroke(KoShapeStrokeModelSP());
} else if (gc->strokeType == SvgGraphicsContext::Solid) {
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
} else if (gc->strokeType == SvgGraphicsContext::Complex) {
// try to find referenced gradient
SvgGradientHelper *gradient = findGradient(gc->strokeId);
if (gradient) {
QTransform transform;
QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
if (result) {
QBrush brush = *result;
delete result;
brush.setTransform(transform);
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
stroke->setLineBrush(brush);
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
}
} else {
// no referenced stroke found, use fallback color
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
}
}
}
void SvgParser::applyFilter(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->filterId.isEmpty())
return;
SvgFilterHelper *filter = findFilter(gc->filterId);
if (! filter)
return;
KoXmlElement content = filter->content();
// parse filter region
QRectF bound(shape->position(), shape->size());
// work on bounding box without viewbox transformation applied
// so user space coordinates of bounding box and filter region match up
bound = gc->viewboxTransform.inverted().mapRect(bound);
QRectF filterRegion(filter->position(bound), filter->size(bound));
// convert filter region to boundingbox units
QRectF objectFilterRegion;
objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound));
objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound));
KoFilterEffectLoadingContext context(m_context.xmlBaseDir());
context.setShapeBoundingBox(bound);
// enable units conversion
context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse);
context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse);
KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance();
KoFilterEffectStack *filterStack = 0;
QSet<QString> stdInputs;
stdInputs << "SourceGraphic" << "SourceAlpha";
stdInputs << "BackgroundImage" << "BackgroundAlpha";
stdInputs << "FillPaint" << "StrokePaint";
QMap<QString, KoFilterEffect*> inputs;
// create the filter effects and add them to the shape
for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement primitive = n.toElement();
KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context);
if (!filterEffect) {
debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet";
continue;
}
const QString input = primitive.attribute("in");
if (!input.isEmpty()) {
filterEffect->setInput(0, input);
}
const QString output = primitive.attribute("result");
if (!output.isEmpty()) {
filterEffect->setOutput(output);
}
QRectF subRegion;
// parse subregion
if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) {
const QString xa = primitive.attribute("x");
const QString ya = primitive.attribute("y");
const QString wa = primitive.attribute("width");
const QString ha = primitive.attribute("height");
if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) {
bool hasStdInput = false;
bool isFirstEffect = filterStack == 0;
// check if one of the inputs is a standard input
Q_FOREACH (const QString &input, filterEffect->inputs()) {
if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) {
hasStdInput = true;
break;
}
}
if (hasStdInput || primitive.tagName() == "feImage") {
// default to 0%, 0%, 100%, 100%
subRegion.setTopLeft(QPointF(0, 0));
subRegion.setSize(QSizeF(1, 1));
} else {
// defaults to bounding rect of all referenced nodes
Q_FOREACH (const QString &input, filterEffect->inputs()) {
if (!inputs.contains(input))
continue;
KoFilterEffect *inputFilter = inputs[input];
if (inputFilter)
subRegion |= inputFilter->filterRect();
}
}
} else {
const qreal x = parseUnitX(xa);
const qreal y = parseUnitY(ya);
const qreal w = parseUnitX(wa);
const qreal h = parseUnitY(ha);
subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound));
subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound));
}
} else {
// x, y, width, height are in percentages of the object referencing the filter
// so we just parse the percentages
const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0"));
const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0"));
const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1"));
const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1"));
subRegion = QRectF(QPointF(x, y), QSizeF(w, h));
}
filterEffect->setFilterRect(subRegion);
if (!filterStack)
filterStack = new KoFilterEffectStack();
filterStack->appendFilterEffect(filterEffect);
inputs[filterEffect->output()] = filterEffect;
}
if (filterStack) {
filterStack->setClipRect(objectFilterRegion);
shape->setFilterEffectStack(filterStack);
}
}
void SvgParser::applyMarkers(KoPathShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) {
shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker);
}
if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) {
shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker);
}
if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker);
}
shape->setAutoFillMarkers(gc->autoFillMarkers);
}
void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->clipPathId.isEmpty())
return;
SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId);
if (!clipPath || clipPath->isEmpty())
return;
QList<KoShape*> shapes;
Q_FOREACH (KoShape *item, clipPath->shapes()) {
KoShape *clonedShape = item->cloneShape();
KIS_ASSERT_RECOVER(clonedShape) { continue; }
shapes.append(clonedShape);
}
if (!shapeToOriginalUserCoordinates.isNull()) {
const QTransform t =
QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
shapeToOriginalUserCoordinates.y());
Q_FOREACH(KoShape *s, shapes) {
s->applyAbsoluteTransformation(t);
}
}
KoClipPath *clipPathObject = new KoClipPath(shapes,
clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ?
KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse);
shape->setClipPath(clipPathObject);
}
void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
if (gc->clipMaskId.isEmpty())
return;
QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
if (!originalClipMask || originalClipMask->isEmpty()) return;
KoClipMask *clipMask = originalClipMask->clone();
clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
shape->setClipMask(clipMask);
}
KoShape* SvgParser::parseUse(const KoXmlElement &e, DeferredUseStore* deferredUseStore)
{
QString href = e.attribute("xlink:href");
if (href.isEmpty())
return 0;
QString key = href.mid(1);
const bool gotDef = m_context.hasDefinition(key);
if (gotDef) {
return resolveUse(e, key);
} else if (deferredUseStore) {
deferredUseStore->add(&e, key);
return 0;
}
debugFlake << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: "
<< key;
return 0;
}
KoShape* SvgParser::resolveUse(const KoXmlElement &e, const QString& key)
{
KoShape *result = 0;
SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
// TODO: parse 'width' and 'height' as well
gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
const KoXmlElement &referencedElement = m_context.definition(key);
result = parseGroup(e, referencedElement);
m_context.popGraphicsContext();
return result;
}
void SvgParser::addToGroup(QList<KoShape*> shapes, KoShapeContainer *group)
{
m_shapes += shapes;
if (!group || shapes.isEmpty())
return;
// not normalized
KoShapeGroupCommand cmd(group, shapes, false);
cmd.redo();
}
QList<KoShape*> SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize)
{
// check if we are the root svg element
const bool isRootSvg = m_context.isRootContext();
// parse 'transform' field if preset
SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
applyStyle(0, e, QPointF());
const QString w = e.attribute("width");
const QString h = e.attribute("height");
qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
if (w.isEmpty() || h.isEmpty()) {
QRectF viewRect;
QTransform viewTransform_unused;
QRectF fakeBoundingRect(0.0, 0.0, 1.0, 1.0);
if (SvgUtil::parseViewBox(e, fakeBoundingRect,
&viewRect, &viewTransform_unused)) {
QSizeF estimatedSize = viewRect.size();
if (estimatedSize.isValid()) {
if (!w.isEmpty()) {
estimatedSize = QSizeF(width, width * estimatedSize.height() / estimatedSize.width());
} else if (!h.isEmpty()) {
estimatedSize = QSizeF(height * estimatedSize.width() / estimatedSize.height(), height);
}
width = estimatedSize.width();
height = estimatedSize.height();
}
}
}
QSizeF svgFragmentSize(QSizeF(width, height));
if (fragmentSize) {
*fragmentSize = svgFragmentSize;
}
gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
if (!isRootSvg) {
// x and y attribute has no meaning for outermost svg elements
const qreal x = parseUnit(e.attribute("x", "0"));
const qreal y = parseUnit(e.attribute("y", "0"));
QTransform move = QTransform::fromTranslate(x, y);
gc->matrix = move * gc->matrix;
}
applyViewBoxTransform(e);
QList<KoShape*> shapes;
// First find the metadata
for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement b = n.toElement();
if (b.isNull())
continue;
if (b.tagName() == "title") {
m_documentTitle = b.text().trimmed();
}
else if (b.tagName() == "desc") {
m_documentDescription = b.text().trimmed();
}
else if (b.tagName() == "metadata") {
// TODO: parse the metadata
}
}
// SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy
// and as mother makes them -- if mother is inkscape.
if (gc->currentBoundingBox.normalized().isValid()) {
shapes = parseContainer(e);
}
m_context.popGraphicsContext();
return shapes;
}
void SvgParser::applyViewBoxTransform(const KoXmlElement &element)
{
SvgGraphicsContext *gc = m_context.currentGC();
QRectF viewRect = gc->currentBoundingBox;
QTransform viewTransform;
if (SvgUtil::parseViewBox(element, gc->currentBoundingBox,
&viewRect, &viewTransform)) {
gc->matrix = viewTransform * gc->matrix;
gc->currentBoundingBox = viewRect;
}
}
QList<QExplicitlySharedDataPointer<KoMarker> > SvgParser::knownMarkers() const
{
return m_markers.values();
}
QString SvgParser::documentTitle() const
{
return m_documentTitle;
}
QString SvgParser::documentDescription() const
{
return m_documentDescription;
}
void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func)
{
m_context.setFileFetcher(func);
}
inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
{
const QTransform shapeToOriginalUserCoordinates =
shape->absoluteTransformation(0).inverted() *
coordinateSystemOnLoading;
KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate);
return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy());
}
KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom)
{
m_context.pushGraphicsContext(b);
KoShapeGroup *group = new KoShapeGroup();
group->setZIndex(m_context.nextZIndex());
// groups should also have their own coordinate system!
group->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix);
uploadStyleToContext(b);
QList<KoShape*> childShapes;
if (!overrideChildrenFrom.isNull()) {
// we upload styles from both: <use> and <defs>
uploadStyleToContext(overrideChildrenFrom);
childShapes = parseSingleElement(overrideChildrenFrom, 0);
} else {
childShapes = parseContainer(b);
}
// handle id
applyId(b.attribute("id"), group);
addToGroup(childShapes, group);
applyCurrentStyle(group, extraOffset); // apply style to this group after size is set
m_context.popGraphicsContext();
return group;
}
KoShape* SvgParser::parseTextNode(const KoXmlText &e)
{
QScopedPointer<KoSvgTextChunkShape> textChunk(new KoSvgTextChunkShape());
textChunk->setZIndex(m_context.nextZIndex());
if (!textChunk->loadSvgTextNode(e, m_context)) {
return 0;
}
textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix);
applyCurrentBasicStyle(textChunk.data()); // apply style to this group after size is set
return textChunk.take();
}
KoXmlText getTheOnlyTextChild(const KoXmlElement &e)
{
KoXmlNode firstChild = e.firstChild();
return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ?
firstChild.toText() : KoXmlText();
}
KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *mergeIntoShape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan", 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_isInsideTextSubtree || e.tagName() == "text", 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0);
KoSvgTextShape *rootTextShape = 0;
if (e.tagName() == "text") {
// XXX: Shapes need to be created by their factories
if (mergeIntoShape) {
rootTextShape = mergeIntoShape;
} else {
rootTextShape = new KoSvgTextShape();
const QString useRichText = e.attribute("krita:useRichText", "true");
rootTextShape->setRichTextPreferred(useRichText != "false");
}
}
if (rootTextShape) {
m_isInsideTextSubtree = true;
}
m_context.pushGraphicsContext(e);
uploadStyleToContext(e);
KoSvgTextChunkShape *textChunk = rootTextShape ? rootTextShape : new KoSvgTextChunkShape();
- textChunk->setZIndex(m_context.nextZIndex());
+
+ if (!mergeIntoShape) {
+ textChunk->setZIndex(m_context.nextZIndex());
+ }
textChunk->loadSvg(e, m_context);
// 1) apply transformation only in case we are not overriding the shape!
// 2) the transformation should be applied *before* the shape is added to the group!
if (!mergeIntoShape) {
// groups should also have their own coordinate system!
textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(textChunk, m_context.currentGC()->matrix);
// handle id
applyId(e.attribute("id"), textChunk);
applyCurrentStyle(textChunk, extraOffset); // apply style to this group after size is set
} else {
m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation(0);
applyCurrentBasicStyle(textChunk);
}
KoXmlText onlyTextChild = getTheOnlyTextChild(e);
if (!onlyTextChild.isNull()) {
textChunk->loadSvgTextNode(onlyTextChild, m_context);
} else {
QList<KoShape*> childShapes = parseContainer(e, true);
addToGroup(childShapes, textChunk);
}
m_context.popGraphicsContext();
textChunk->normalizeCharTransformations();
if (rootTextShape) {
textChunk->simplifyFillStrokeInheritance();
m_isInsideTextSubtree = false;
rootTextShape->relayout();
}
return textChunk;
}
QList<KoShape*> SvgParser::parseContainer(const KoXmlElement &e, bool parseTextNodes)
{
QList<KoShape*> shapes;
// are we parsing a switch container
bool isSwitch = e.tagName() == "switch";
DeferredUseStore deferredUseStore(this);
for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement b = n.toElement();
if (b.isNull()) {
if (parseTextNodes && n.isText()) {
KoShape *shape = parseTextNode(n.toText());
if (shape) {
shapes += shape;
}
}
continue;
}
if (isSwitch) {
// if we are parsing a switch check the requiredFeatures, requiredExtensions
// and systemLanguage attributes
// TODO: evaluate feature list
if (b.hasAttribute("requiredFeatures")) {
continue;
}
if (b.hasAttribute("requiredExtensions")) {
// we do not support any extensions
continue;
}
if (b.hasAttribute("systemLanguage")) {
// not implemented yet
}
}
QList<KoShape*> currentShapes = parseSingleElement(b, &deferredUseStore);
shapes.append(currentShapes);
// if we are parsing a switch, stop after the first supported element
if (isSwitch && !currentShapes.isEmpty())
break;
}
return shapes;
}
void SvgParser::parseDefsElement(const KoXmlElement &e)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs");
parseSingleElement(e);
}
QList<KoShape*> SvgParser::parseSingleElement(const KoXmlElement &b, DeferredUseStore* deferredUseStore)
{
QList<KoShape*> shapes;
// save definition for later instantiation with 'use'
m_context.addDefinition(b);
if (deferredUseStore) {
deferredUseStore->checkPendingUse(b, shapes);
}
if (b.tagName() == "svg") {
shapes += parseSvg(b);
} else if (b.tagName() == "g" || b.tagName() == "a") {
// treat svg link <a> as group so we don't miss its child elements
shapes += parseGroup(b);
} else if (b.tagName() == "switch") {
m_context.pushGraphicsContext(b);
shapes += parseContainer(b);
m_context.popGraphicsContext();
} else if (b.tagName() == "defs") {
if (KoXml::childNodesCount(b) > 0) {
/**
* WARNING: 'defs' are basically 'display:none' style, therefore they should not play
* any role in shapes outline calculation. But setVisible(false) shapes do!
* Should be fixed in the future!
*/
KoShape *defsShape = parseGroup(b);
defsShape->setVisible(false);
m_defsShapes << defsShape; // TODO: where to delete the shape!?
}
} else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
} else if (b.tagName() == "pattern") {
} else if (b.tagName() == "filter") {
parseFilter(b);
} else if (b.tagName() == "clipPath") {
parseClipPath(b);
} else if (b.tagName() == "mask") {
parseClipMask(b);
} else if (b.tagName() == "marker") {
parseMarker(b);
} else if (b.tagName() == "symbol") {
parseSymbol(b);
} else if (b.tagName() == "style") {
m_context.addStyleSheet(b);
} else if (b.tagName() == "text" ||
b.tagName() == "tspan") {
shapes += parseTextElement(b);
} else if (b.tagName() == "rect" ||
b.tagName() == "ellipse" ||
b.tagName() == "circle" ||
b.tagName() == "line" ||
b.tagName() == "polyline" ||
b.tagName() == "polygon" ||
b.tagName() == "path" ||
b.tagName() == "image") {
KoShape *shape = createObjectDirect(b);
if (shape)
shapes.append(shape);
} else if (b.tagName() == "use") {
KoShape* s = parseUse(b, deferredUseStore);
if (s) {
shapes += s;
}
} else if (b.tagName() == "color-profile") {
m_context.parseProfile(b);
} else {
// this is an unknown element, so try to load it anyway
// there might be a shape that handles that element
KoShape *shape = createObject(b);
if (shape) {
shapes.append(shape);
}
}
return shapes;
}
// Creating functions
// ---------------------------------------------------------------------------------------
KoShape * SvgParser::createPath(const KoXmlElement &element)
{
KoShape *obj = 0;
if (element.tagName() == "line") {
KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
if (path) {
double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1"));
double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1"));
double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2"));
double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2"));
path->clear();
path->moveTo(QPointF(x1, y1));
path->lineTo(QPointF(x2, y2));
path->normalize();
obj = path;
}
} else if (element.tagName() == "polyline" || element.tagName() == "polygon") {
KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
if (path) {
path->clear();
bool bFirst = true;
QStringList pointList = SvgUtil::simplifyList(element.attribute("points"));
for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) {
QPointF point;
point.setX(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it)));
++it;
if (it == pointList.end())
break;
point.setY(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it)));
if (bFirst) {
path->moveTo(point);
bFirst = false;
} else
path->lineTo(point);
}
if (element.tagName() == "polygon")
path->close();
path->setPosition(path->normalize());
obj = path;
}
} else if (element.tagName() == "path") {
KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
if (path) {
path->clear();
KoPathShapeLoader loader(path);
loader.parseSvg(element.attribute("d"), true);
path->setPosition(path->normalize());
QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()),
SvgUtil::fromUserSpace(path->position().y()));
QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()),
SvgUtil::fromUserSpace(path->size().height()));
path->setSize(newSize);
path->setPosition(newPosition);
obj = path;
}
}
return obj;
}
KoShape * SvgParser::createObjectDirect(const KoXmlElement &b)
{
m_context.pushGraphicsContext(b);
uploadStyleToContext(b);
KoShape *obj = createShapeFromElement(b, m_context);
if (obj) {
obj->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
applyCurrentStyle(obj, extraOffset);
// handle id
applyId(b.attribute("id"), obj);
obj->setZIndex(m_context.nextZIndex());
}
m_context.popGraphicsContext();
return obj;
}
KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style)
{
m_context.pushGraphicsContext(b);
KoShape *obj = createShapeFromElement(b, m_context);
if (obj) {
obj->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style;
m_context.styleParser().parseFont(objStyle);
applyStyle(obj, objStyle, extraOffset);
// handle id
applyId(b.attribute("id"), obj);
obj->setZIndex(m_context.nextZIndex());
}
m_context.popGraphicsContext();
return obj;
}
KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context)
{
KoShape *object = 0;
const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element);
QList<KoShapeFactoryBase*> factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName);
foreach (KoShapeFactoryBase *f, factories) {
KoShape *shape = f->createDefaultShape(m_documentResourceManager);
if (!shape)
continue;
SvgShape *svgShape = dynamic_cast<SvgShape*>(shape);
if (!svgShape) {
delete shape;
continue;
}
// reset transformation that might come from the default shape
shape->setTransformation(QTransform());
// reset border
KoShapeStrokeModelSP oldStroke = shape->stroke();
shape->setStroke(KoShapeStrokeModelSP());
// reset fill
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
if (!svgShape->loadSvg(element, context)) {
delete shape;
continue;
}
object = shape;
break;
}
if (!object) {
object = createPath(element);
}
return object;
}
KoShape *SvgParser::createShape(const QString &shapeID)
{
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID);
if (!factory) {
debugFlake << "Could not find factory for shape id" << shapeID;
return 0;
}
KoShape *shape = factory->createDefaultShape(m_documentResourceManager);
if (!shape) {
debugFlake << "Could not create Default shape for shape id" << shapeID;
return 0;
}
if (shape->shapeId().isEmpty()) {
shape->setShapeId(factory->id());
}
// reset transformation that might come from the default shape
shape->setTransformation(QTransform());
// reset border
// ??? KoShapeStrokeModelSP oldStroke = shape->stroke();
shape->setStroke(KoShapeStrokeModelSP());
// reset fill
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
return shape;
}
void SvgParser::applyId(const QString &id, KoShape *shape)
{
if (id.isEmpty())
return;
shape->setName(id);
m_context.registerShape(id, shape);
}
diff --git a/libs/flake/tests/TestKoShapeRegistry.cpp b/libs/flake/tests/TestKoShapeRegistry.cpp
index 9352fbe33f..8fc3a4c4dd 100644
--- a/libs/flake/tests/TestKoShapeRegistry.cpp
+++ b/libs/flake/tests/TestKoShapeRegistry.cpp
@@ -1,213 +1,213 @@
/* This file is part of the KDE project
* Copyright (c) 2007 Boudewijn Rempt (boud@valdyas.org)
*
* 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 "TestKoShapeRegistry.h"
#include <QTest>
#include <QBuffer>
#include <QFile>
#include <QDateTime>
#include <QProcess>
#include <QString>
#include <QTextStream>
#include <QtXml>
#include <KoOdfLoadingContext.h>
#include <KoOdfStylesReader.h>
#include "KoShapeRegistry.h"
#include "KoShape.h"
#include "KoPathShape.h"
#include "KoShapeLoadingContext.h"
#include <KoXmlReader.h>
#include <FlakeDebug.h>
#include "kis_debug.h"
#include <sdk/tests/kistest.h>
void TestKoShapeRegistry::testGetKoShapeRegistryInstance()
{
KoShapeRegistry * registry = KoShapeRegistry::instance();
QVERIFY(registry != 0);
}
void TestKoShapeRegistry::testCreateShapes()
{
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream.setCodec("UTF-8");
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:calligra=\"http://www.calligra.org/2005/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">";
xmlstream << "<office:body>";
xmlstream << "<office:text>";
xmlstream << "<draw:path svg:d=\"M0,0L100,100\"></draw:path>";
xmlstream << "</office:text>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement contentElement = doc.documentElement();
KoXmlElement bodyElement = contentElement.firstChild().toElement();
KoShapeRegistry * registry = KoShapeRegistry::instance();
// XXX: When loading is implemented, these no doubt have to be
// sensibly filled.
KoOdfStylesReader stylesReader;
KoOdfLoadingContext odfContext(stylesReader, 0);
KoShapeLoadingContext shapeContext(odfContext, 0);
KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext);
QVERIFY(shape == 0);
KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement();
shape = registry->createShapeFromOdf(pathElement, shapeContext);
QVERIFY(shape != 0);
QVERIFY(shape->shapeId() == KoPathShapeId);
}
void TestKoShapeRegistry::testCreateFramedShapes()
{
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream.setCodec("UTF-8");
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:calligra=\"http://www.calligra.org/2005/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">";
xmlstream << "<office:body>";
xmlstream << "<office:text>";
xmlstream << "<draw:path svg:d=\"M0,0L100,100\"></draw:path>";
xmlstream << "</office:text>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement contentElement = doc.documentElement();
KoXmlElement bodyElement = contentElement.firstChild().toElement();
KoShapeRegistry * registry = KoShapeRegistry::instance();
// XXX: When loading is implemented, these no doubt have to be
// sensibly filled.
KoOdfStylesReader stylesReader;
KoOdfLoadingContext odfContext(stylesReader, 0);
KoShapeLoadingContext shapeContext(odfContext, 0);
KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext);
QVERIFY(shape == 0);
KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement();
shape = registry->createShapeFromOdf(pathElement, shapeContext);
QVERIFY(shape != 0);
QVERIFY(shape->shapeId() == KoPathShapeId);
}
#include <KoStore.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeController.h>
#include <MockShapes.h>
#include "../../sdk/tests/qimage_test_util.h"
void TestKoShapeRegistry::testFramedSvgShapes()
{
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream.setCodec("UTF-8");
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:calligra=\"http://www.calligra.org/2005/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">";
xmlstream << "<office:body>";
xmlstream << "<office:text>";
xmlstream << "<draw:frame xml:id=\"shape-1\" draw:z-index=\"2\" draw:id=\"shape-1\" draw:layer=\"\" svg:width=\"226pt\" svg:height=\"141pt\" svg:x=\"83pt\" svg:y=\"41pt\">";
xmlstream << " <draw:image xlink:type=\"simple\" draw:z-index=\"3\" xlink:show=\"embed\" xlink:actuate=\"onLoad\" xlink:href=\"VectorImages/Image1\"/>";
xmlstream << "</draw:frame>";
xmlstream << "</office:text>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement contentElement = doc.documentElement();
KoXmlElement bodyElement = contentElement.firstChild().toElement();
KoShapeRegistry * registry = KoShapeRegistry::instance();
// XXX: When loading is implemented, these no doubt have to be
// sensibly filled.
KoOdfStylesReader stylesReader;
const QString resourcesBlob = TestUtil::fetchDataFileLazy("odf_frame_resource_store.zip");
QScopedPointer<KoStore> store(KoStore::createStore(resourcesBlob, KoStore::Read, "krita", KoStore::Zip));
QScopedPointer<KoDocumentResourceManager> resourceManager(new KoDocumentResourceManager());
QScopedPointer<MockShapeController> document(new MockShapeController());
QScopedPointer<MockCanvas> canvas(new MockCanvas(document.data()));
QScopedPointer<KoShapeController> shapeController(new KoShapeController(canvas.data(), document.data()));
- resourceManager->setShapeController(shapeController.data());
+ resourceManager->setGlobalShapeController(shapeController.data());
KoOdfLoadingContext odfContext(stylesReader, store.data());
KoShapeLoadingContext shapeContext(odfContext, resourceManager.data());
KoXmlElement frameElement = bodyElement.firstChild().firstChild().toElement();
QCOMPARE(frameElement.tagName(), QString("frame"));
KoShape *shape = registry->createShapeFromOdf(frameElement, shapeContext);
QVERIFY(shape);
QCOMPARE(shape->absoluteOutlineRect(0), QRectF(83, 41, 226,141));
}
KISTEST_MAIN(TestKoShapeRegistry)
diff --git a/libs/flake/text/KoSvgTextShape.cpp b/libs/flake/text/KoSvgTextShape.cpp
index 38eb89fcbb..6ad42a467a 100644
--- a/libs/flake/text/KoSvgTextShape.cpp
+++ b/libs/flake/text/KoSvgTextShape.cpp
@@ -1,636 +1,634 @@
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KoSvgTextShape.h"
#include <QTextLayout>
#include <klocalizedstring.h>
#include "KoSvgText.h"
#include "KoSvgTextProperties.h"
#include <KoShapeContainer_p.h>
#include <text/KoSvgTextChunkShape_p.h>
#include <text/KoSvgTextShapeMarkupConverter.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeController.h>
#include "kis_debug.h"
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoShapeLoadingContext.h>
#include <KoOdfLoadingContext.h>
#include <KoIcon.h>
#include <KoProperties.h>
#include <KoColorBackground.h>
#include <SvgLoadingContext.h>
#include <SvgGraphicContext.h>
#include <SvgUtil.h>
#include <QApplication>
#include <QThread>
#include <vector>
#include <memory>
#include <QPainter>
#include <boost/optional.hpp>
#include <text/KoSvgTextChunkShapeLayoutInterface.h>
#include <FlakeDebug.h>
class KoSvgTextShapePrivate : public KoSvgTextChunkShapePrivate
{
KoSvgTextShapePrivate(KoSvgTextShape *_q)
: KoSvgTextChunkShapePrivate(_q)
{
}
KoSvgTextShapePrivate(const KoSvgTextShapePrivate &rhs, KoSvgTextShape *q)
: KoSvgTextChunkShapePrivate(rhs, q)
{
}
std::vector<std::unique_ptr<QTextLayout>> cachedLayouts;
std::vector<QPointF> cachedLayoutsOffsets;
QThread *cachedLayoutsWorkingThread = 0;
void clearAssociatedOutlines(KoShape *rootShape);
Q_DECLARE_PUBLIC(KoSvgTextShape)
};
KoSvgTextShape::KoSvgTextShape()
: KoSvgTextChunkShape(new KoSvgTextShapePrivate(this))
{
setShapeId(KoSvgTextShape_SHAPEID);
}
KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs)
: KoSvgTextChunkShape(new KoSvgTextShapePrivate(*rhs.d_func(), this))
{
setShapeId(KoSvgTextShape_SHAPEID);
// QTextLayout has no copy-ctor, so just relayout everything!
relayout();
}
KoSvgTextShape::~KoSvgTextShape()
{
}
KoShape *KoSvgTextShape::cloneShape() const
{
return new KoSvgTextShape(*this);
}
void KoSvgTextShape::shapeChanged(ChangeType type, KoShape *shape)
{
KoSvgTextChunkShape::shapeChanged(type, shape);
if (type == StrokeChanged || type == BackgroundChanged || type == ContentChanged) {
relayout();
}
}
void KoSvgTextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
Q_D(KoSvgTextShape);
Q_UNUSED(paintContext);
/**
* HACK ALERT:
* QTextLayout should only be accessed from the thread it has been created in.
* If the cached layout has been created in a different thread, we should just
* recreate the layouts in the current thread to be able to render them.
*/
if (QThread::currentThread() != d->cachedLayoutsWorkingThread) {
relayout();
}
applyConversion(painter, converter);
for (int i = 0; i < (int)d->cachedLayouts.size(); i++) {
d->cachedLayouts[i]->draw(&painter, d->cachedLayoutsOffsets[i]);
}
/**
* HACK ALERT:
* The layouts of non-gui threads must be destroyed in the same thread
* they have been created. Because the thread might be restarted in the
* meantime or just destroyed, meaning that the per-thread freetype data
* will not be available.
*/
if (QThread::currentThread() != qApp->thread()) {
d->cachedLayouts.clear();
d->cachedLayoutsOffsets.clear();
d->cachedLayoutsWorkingThread = 0;
}
}
void KoSvgTextShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
Q_UNUSED(paintContext);
// do nothing! everything is painted in paintComponent()
}
QPainterPath KoSvgTextShape::textOutline()
{
Q_D(KoSvgTextShape);
QPainterPath result;
result.setFillRule(Qt::WindingFill);
for (int i = 0; i < (int)d->cachedLayouts.size(); i++) {
const QPointF layoutOffset = d->cachedLayoutsOffsets[i];
const QTextLayout *layout = d->cachedLayouts[i].get();
for (int j = 0; j < layout->lineCount(); j++) {
QTextLine line = layout->lineAt(j);
Q_FOREACH (const QGlyphRun &run, line.glyphRuns()) {
const QVector<quint32> indexes = run.glyphIndexes();
const QVector<QPointF> positions = run.positions();
const QRawFont font = run.rawFont();
KIS_SAFE_ASSERT_RECOVER(indexes.size() == positions.size()) { continue; }
for (int k = 0; k < indexes.size(); k++) {
QPainterPath glyph = font.pathForGlyph(indexes[k]);
glyph.translate(positions[k] + layoutOffset);
result += glyph;
}
const qreal thickness = font.lineThickness();
const QRectF runBounds = run.boundingRect();
if (run.overline()) {
// the offset is calculated to be consistent with the way how Qt renders the text
const qreal y = line.y();
QRectF overlineBlob(runBounds.x(), y, runBounds.width(), thickness);
overlineBlob.translate(layoutOffset);
QPainterPath path;
path.addRect(overlineBlob);
// don't use direct addRect, because it doesn't care about Qt::WindingFill
result += path;
}
if (run.strikeOut()) {
// the offset is calculated to be consistent with the way how Qt renders the text
const qreal y = line.y() + 0.5 * line.height();
QRectF strikeThroughBlob(runBounds.x(), y, runBounds.width(), thickness);
strikeThroughBlob.translate(layoutOffset);
QPainterPath path;
path.addRect(strikeThroughBlob);
// don't use direct addRect, because it doesn't care about Qt::WindingFill
result += path;
}
if (run.underline()) {
const qreal y = line.y() + line.ascent() + font.underlinePosition();
QRectF underlineBlob(runBounds.x(), y, runBounds.width(), thickness);
underlineBlob.translate(layoutOffset);
QPainterPath path;
path.addRect(underlineBlob);
// don't use direct addRect, because it doesn't care about Qt::WindingFill
result += path;
}
}
}
}
return result;
}
void KoSvgTextShape::resetTextShape()
{
KoSvgTextChunkShape::resetTextShape();
relayout();
}
struct TextChunk {
QString text;
QVector<QTextLayout::FormatRange> formats;
Qt::LayoutDirection direction = Qt::LeftToRight;
Qt::Alignment alignment = Qt::AlignLeading;
struct SubChunkOffset {
QPointF offset;
int start = 0;
};
QVector<SubChunkOffset> offsets;
boost::optional<qreal> xStartPos;
boost::optional<qreal> yStartPos;
QPointF applyStartPosOverride(const QPointF &pos) const {
QPointF result = pos;
if (xStartPos) {
result.rx() = *xStartPos;
}
if (yStartPos) {
result.ry() = *yStartPos;
}
return result;
}
};
QVector<TextChunk> mergeIntoChunks(const QVector<KoSvgTextChunkShapeLayoutInterface::SubChunk> &subChunks)
{
QVector<TextChunk> chunks;
for (auto it = subChunks.begin(); it != subChunks.end(); ++it) {
if (it->transformation.startsNewChunk() || it == subChunks.begin()) {
TextChunk newChunk = TextChunk();
newChunk.direction = it->format.layoutDirection();
newChunk.alignment = it->format.calculateAlignment();
newChunk.xStartPos = it->transformation.xPos;
newChunk.yStartPos = it->transformation.yPos;
chunks.append(newChunk);
}
TextChunk &currentChunk = chunks.last();
if (it->transformation.hasRelativeOffset()) {
TextChunk::SubChunkOffset o;
o.start = currentChunk.text.size();
o.offset = it->transformation.relativeOffset();
KIS_SAFE_ASSERT_RECOVER_NOOP(!o.offset.isNull());
currentChunk.offsets.append(o);
}
QTextLayout::FormatRange formatRange;
formatRange.start = currentChunk.text.size();
formatRange.length = it->text.size();
formatRange.format = it->format;
currentChunk.formats.append(formatRange);
currentChunk.text += it->text;
}
return chunks;
}
/**
* Qt's QTextLayout has a weird trait, it doesn't count space characters as
* distinct characters in QTextLayout::setNumColumns(), that is, if we want to
* position a block of text that starts with a space character in a specific
* position, QTextLayout will drop this space and will move the text to the left.
*
* That is why we have a special wrapper object that ensures that no spaces are
* dropped and their horizontal advance parameter is taken into account.
*/
struct LayoutChunkWrapper
{
LayoutChunkWrapper(QTextLayout *layout)
: m_layout(layout)
{
}
QPointF addTextChunk(int startPos, int length, const QPointF &textChunkStartPos)
{
QPointF currentTextPos = textChunkStartPos;
const int lastPos = startPos + length - 1;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(startPos == m_addedChars, currentTextPos);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastPos < m_layout->text().size(), currentTextPos);
QTextLine line;
std::swap(line, m_danglingLine);
if (!line.isValid()) {
line = m_layout->createLine();
}
// skip all the space characters that were not included into the Qt's text line
const int currentLineStart = line.isValid() ? line.textStart() : startPos + length;
while (startPos < currentLineStart && startPos <= lastPos) {
currentTextPos.rx() += skipSpaceCharacter(startPos);
startPos++;
}
if (startPos <= lastPos) {
// defines the number of columns to look for glyphs
const int numChars = lastPos - startPos + 1;
// Tabs break the normal column flow
// grow to avoid missing glyphs
int charOffset = 0;
while (line.textLength() < numChars) {
line.setNumColumns(numChars + charOffset);
charOffset++;
}
line.setPosition(currentTextPos - QPointF(0, line.ascent()));
currentTextPos.rx() += line.horizontalAdvance();
// skip all the space characters that were not included into the Qt's text line
for (int i = line.textStart() + line.textLength(); i < lastPos; i++) {
currentTextPos.rx() += skipSpaceCharacter(i);
}
} else {
// keep the created but unused line for future use
std::swap(line, m_danglingLine);
}
m_addedChars += length;
return currentTextPos;
}
private:
qreal skipSpaceCharacter(int pos) {
const QTextCharFormat format =
formatForPos(pos, m_layout->formats());
const QChar skippedChar = m_layout->text()[pos];
KIS_SAFE_ASSERT_RECOVER_NOOP(skippedChar.isSpace() || !skippedChar.isPrint());
QFontMetrics metrics(format.font());
return metrics.width(skippedChar);
}
static QTextCharFormat formatForPos(int pos, const QVector<QTextLayout::FormatRange> &formats)
{
Q_FOREACH (const QTextLayout::FormatRange &range, formats) {
if (pos >= range.start && pos < range.start + range.length) {
return range.format;
}
}
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "pos should be within the bounds of the layouted text");
return QTextCharFormat();
}
private:
int m_addedChars = 0;
QTextLayout *m_layout;
QTextLine m_danglingLine;
};
void KoSvgTextShape::relayout()
{
Q_D(KoSvgTextShape);
d->cachedLayouts.clear();
d->cachedLayoutsOffsets.clear();
d->cachedLayoutsWorkingThread = QThread::currentThread();
QPointF currentTextPos;
QVector<TextChunk> textChunks = mergeIntoChunks(layoutInterface()->collectSubChunks());
Q_FOREACH (const TextChunk &chunk, textChunks) {
std::unique_ptr<QTextLayout> layout(new QTextLayout());
QTextOption option;
// WARNING: never activate this option! It breaks the RTL text layout!
//option.setFlags(QTextOption::ShowTabsAndSpaces);
option.setWrapMode(QTextOption::WrapAnywhere);
option.setUseDesignMetrics(true); // TODO: investigate if it is needed?
option.setTextDirection(chunk.direction);
layout->setText(chunk.text);
layout->setTextOption(option);
layout->setFormats(chunk.formats);
layout->setCacheEnabled(true);
layout->beginLayout();
currentTextPos = chunk.applyStartPosOverride(currentTextPos);
const QPointF anchorPointPos = currentTextPos;
int lastSubChunkStart = 0;
QPointF lastSubChunkOffset;
LayoutChunkWrapper wrapper(layout.get());
for (int i = 0; i <= chunk.offsets.size(); i++) {
const bool isFinalPass = i == chunk.offsets.size();
const int length =
!isFinalPass ?
chunk.offsets[i].start - lastSubChunkStart :
chunk.text.size() - lastSubChunkStart;
if (length > 0) {
currentTextPos += lastSubChunkOffset;
currentTextPos = wrapper.addTextChunk(lastSubChunkStart,
length,
currentTextPos);
}
if (!isFinalPass) {
lastSubChunkOffset = chunk.offsets[i].offset;
lastSubChunkStart = chunk.offsets[i].start;
}
}
layout->endLayout();
QPointF diff;
if (chunk.alignment & Qt::AlignTrailing || chunk.alignment & Qt::AlignHCenter) {
if (chunk.alignment & Qt::AlignTrailing) {
diff = currentTextPos - anchorPointPos;
} else if (chunk.alignment & Qt::AlignHCenter) {
diff = 0.5 * (currentTextPos - anchorPointPos);
}
// TODO: fix after t2b text implemented
diff.ry() = 0;
}
d->cachedLayouts.push_back(std::move(layout));
d->cachedLayoutsOffsets.push_back(-diff);
}
d->clearAssociatedOutlines(this);
for (int i = 0; i < int(d->cachedLayouts.size()); i++) {
const QTextLayout &layout = *d->cachedLayouts[i];
const QPointF layoutOffset = d->cachedLayoutsOffsets[i];
using namespace KoSvgText;
Q_FOREACH (const QTextLayout::FormatRange &range, layout.formats()) {
const KoSvgCharChunkFormat &format =
static_cast<const KoSvgCharChunkFormat&>(range.format);
AssociatedShapeWrapper wrapper = format.associatedShapeWrapper();
const int rangeStart = range.start;
const int safeRangeLength = range.length > 0 ? range.length : layout.text().size() - rangeStart;
if (safeRangeLength <= 0) continue;
const int rangeEnd = range.start + safeRangeLength - 1;
const int firstLineIndex = layout.lineForTextPosition(rangeStart).lineNumber();
const int lastLineIndex = layout.lineForTextPosition(rangeEnd).lineNumber();
for (int i = firstLineIndex; i <= lastLineIndex; i++) {
const QTextLine line = layout.lineAt(i);
// It might happen that the range contains only one (or two)
// symbol that is a whitespace symbol. In such a case we should
// just skip this (invalid) line.
if (!line.isValid()) continue;
const int posStart = qMax(line.textStart(), rangeStart);
const int posEnd = qMin(line.textStart() + line.textLength() - 1, rangeEnd);
const QList<QGlyphRun> glyphRuns = line.glyphRuns(posStart, posEnd - posStart + 1);
Q_FOREACH (const QGlyphRun &run, glyphRuns) {
const QPointF firstPosition = run.positions().first();
const quint32 firstGlyphIndex = run.glyphIndexes().first();
const QPointF lastPosition = run.positions().last();
const quint32 lastGlyphIndex = run.glyphIndexes().last();
const QRawFont rawFont = run.rawFont();
const QRectF firstGlyphRect = rawFont.boundingRect(firstGlyphIndex).translated(firstPosition);
const QRectF lastGlyphRect = rawFont.boundingRect(lastGlyphIndex).translated(lastPosition);
QRectF rect = run.boundingRect();
/**
* HACK ALERT: there is a bug in a way how Qt calculates boundingRect()
* of the glyph run. It doesn't care about left and right bearings
* of the border chars in the run, therefore it becomes cropped.
*
* Here we just add a half-char width margin to both sides
* of the glyph run to make sure the glyphs are fully painted.
*
* BUG: 389528
* BUG: 392068
*/
rect.setLeft(qMin(rect.left(), lastGlyphRect.left()) - 0.5 * firstGlyphRect.width());
rect.setRight(qMax(rect.right(), lastGlyphRect.right()) + 0.5 * lastGlyphRect.width());
wrapper.addCharacterRect(rect.translated(layoutOffset));
}
}
}
}
}
void KoSvgTextShapePrivate::clearAssociatedOutlines(KoShape *rootShape)
{
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(rootShape);
KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape);
chunkShape->layoutInterface()->clearAssociatedOutline();
Q_FOREACH (KoShape *child, chunkShape->shapes()) {
clearAssociatedOutlines(child);
}
}
bool KoSvgTextShape::isRootTextNode() const
{
return true;
}
KoSvgTextShapeFactory::KoSvgTextShapeFactory()
: KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18n("Text"))
{
setToolTip(i18n("SVG Text Shape"));
setIconName(koIconNameCStr("x-shape-text"));
setLoadingPriority(5);
setXmlElementNames(KoXmlNS::svg, QStringList("text"));
KoShapeTemplate t;
t.name = i18n("SVG Text");
t.iconName = koIconName("x-shape-text");
t.toolTip = i18n("SVG Text Shape");
addTemplate(t);
}
KoShape *KoSvgTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const
{
debugFlake << "Create default svg text shape";
KoSvgTextShape *shape = new KoSvgTextShape();
shape->setShapeId(KoSvgTextShape_SHAPEID);
KoSvgTextShapeMarkupConverter converter(shape);
converter.convertFromSvg("<text>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</text>",
"<defs/>",
QRectF(0, 0, 200, 60),
- documentResources->shapeController()->pixelsPerInch());
+ documentResources->documentResolution());
debugFlake << converter.errors() << converter.warnings();
return shape;
}
KoShape *KoSvgTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const
{
KoSvgTextShape *shape = new KoSvgTextShape();
shape->setShapeId(KoSvgTextShape_SHAPEID);
QString svgText = params->stringProperty("svgText", "<text>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</text>");
QString defs = params->stringProperty("defs" , "<defs/>");
QRectF shapeRect = QRectF(0, 0, 200, 60);
QVariant rect = params->property("shapeRect");
if (rect.type()==QVariant::RectF) {
shapeRect = rect.toRectF();
}
- KoShapeController *controller = documentResources->shapeController();
-
KoSvgTextShapeMarkupConverter converter(shape);
converter.convertFromSvg(svgText,
defs,
shapeRect,
- controller ? controller->pixelsPerInch() : 72);
+ documentResources->documentResolution());
shape->setPosition(shapeRect.topLeft());
return shape;
}
bool KoSvgTextShapeFactory::supports(const KoXmlElement &/*e*/, KoShapeLoadingContext &/*context*/) const
{
return false;
}
diff --git a/libs/flake/text/KoSvgTextShapeMarkupConverter.cpp b/libs/flake/text/KoSvgTextShapeMarkupConverter.cpp
index b4443e012e..45c1d087b1 100644
--- a/libs/flake/text/KoSvgTextShapeMarkupConverter.cpp
+++ b/libs/flake/text/KoSvgTextShapeMarkupConverter.cpp
@@ -1,1253 +1,1255 @@
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KoSvgTextShapeMarkupConverter.h"
#include "klocalizedstring.h"
#include "kis_assert.h"
#include "kis_debug.h"
#include <QBuffer>
#include <QStringList>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QTextBlock>
#include <QTextLayout>
#include <QTextLine>
#include <QStack>
#include <KoSvgTextShape.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoDocumentResourceManager.h>
#include <SvgParser.h>
#include <SvgWriter.h>
#include <SvgUtil.h>
#include <SvgSavingContext.h>
#include <SvgGraphicContext.h>
#include <html/HtmlSavingContext.h>
#include <html/HtmlWriter.h>
#include "kis_dom_utils.h"
#include <boost/optional.hpp>
#include <FlakeDebug.h>
struct KoSvgTextShapeMarkupConverter::Private {
Private(KoSvgTextShape *_shape) : shape(_shape) {}
KoSvgTextShape *shape;
QStringList errors;
QStringList warnings;
void clearErrors() {
errors.clear();
warnings.clear();
}
};
KoSvgTextShapeMarkupConverter::KoSvgTextShapeMarkupConverter(KoSvgTextShape *shape)
: d(new Private(shape))
{
}
KoSvgTextShapeMarkupConverter::~KoSvgTextShapeMarkupConverter()
{
}
bool KoSvgTextShapeMarkupConverter::convertToSvg(QString *svgText, QString *stylesText)
{
d->clearErrors();
QBuffer shapesBuffer;
QBuffer stylesBuffer;
shapesBuffer.open(QIODevice::WriteOnly);
stylesBuffer.open(QIODevice::WriteOnly);
{
SvgSavingContext savingContext(shapesBuffer, stylesBuffer);
savingContext.setStrippedTextMode(true);
SvgWriter writer({d->shape});
writer.saveDetached(savingContext);
}
shapesBuffer.close();
stylesBuffer.close();
*svgText = QString::fromUtf8(shapesBuffer.data());
*stylesText = QString::fromUtf8(stylesBuffer.data());
return true;
}
bool KoSvgTextShapeMarkupConverter::convertFromSvg(const QString &svgText, const QString &stylesText,
const QRectF &boundsInPixels, qreal pixelsPerInch)
{
debugFlake << "convertFromSvg. text:" << svgText << "styles:" << stylesText << "bounds:" << boundsInPixels << "ppi:" << pixelsPerInch;
d->clearErrors();
QString errorMessage;
int errorLine = 0;
int errorColumn = 0;
const QString fullText = QString("<svg>\n%1\n%2\n</svg>\n").arg(stylesText).arg(svgText);
KoXmlDocument doc = SvgParser::createDocumentFromSvg(fullText, &errorMessage, &errorLine, &errorColumn);
if (doc.isNull()) {
d->errors << QString("line %1, col %2: %3").arg(errorLine).arg(errorColumn).arg(errorMessage);
return false;
}
d->shape->resetTextShape();
KoDocumentResourceManager resourceManager;
SvgParser parser(&resourceManager);
parser.setResolution(boundsInPixels, pixelsPerInch);
KoXmlElement root = doc.documentElement();
KoXmlNode node = root.firstChild();
bool textNodeFound = false;
for (; !node.isNull(); node = node.nextSibling()) {
KoXmlElement el = node.toElement();
if (el.isNull()) continue;
if (el.tagName() == "defs") {
parser.parseDefsElement(el);
}
else if (el.tagName() == "text") {
if (textNodeFound) {
d->errors << i18n("More than one 'text' node found!");
return false;
}
KoShape *shape = parser.parseTextElement(el, d->shape);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape == d->shape, false);
textNodeFound = true;
break;
} else {
d->errors << i18n("Unknown node of type \'%1\' found!", el.tagName());
return false;
}
}
if (!textNodeFound) {
d->errors << i18n("No \'text\' node found!");
return false;
}
return true;
}
bool KoSvgTextShapeMarkupConverter::convertToHtml(QString *htmlText)
{
d->clearErrors();
QBuffer shapesBuffer;
shapesBuffer.open(QIODevice::WriteOnly);
{
HtmlWriter writer({d->shape});
if (!writer.save(shapesBuffer)) {
d->errors = writer.errors();
d->warnings = writer.warnings();
return false;
}
}
shapesBuffer.close();
*htmlText = QString(shapesBuffer.data());
debugFlake << "\t\t" << *htmlText;
return true;
}
bool KoSvgTextShapeMarkupConverter::convertFromHtml(const QString &htmlText, QString *svgText, QString *styles)
{
debugFlake << ">>>>>>>>>>>" << htmlText;
QBuffer svgBuffer;
svgBuffer.open(QIODevice::WriteOnly);
QXmlStreamReader htmlReader(htmlText);
QXmlStreamWriter svgWriter(&svgBuffer);
svgWriter.setAutoFormatting(true);
QStringRef elementName;
bool newLine = false;
int lineCount = 0;
QString bodyEm = "1em";
QString em;
QString p("p");
//previous style string is for keeping formatting proper on linebreaks and appendstyle is for specific tags
QString previousStyleString;
QString appendStyle;
while (!htmlReader.atEnd()) {
QXmlStreamReader::TokenType token = htmlReader.readNext();
switch (token) {
case QXmlStreamReader::StartElement:
{
newLine = false;
if (htmlReader.name() == "br") {
debugFlake << "\tdoing br";
svgWriter.writeEndElement();
elementName = QStringRef(&p);
em = bodyEm;
appendStyle = previousStyleString;
}
else {
elementName = htmlReader.name();
em = "";
}
if (elementName == "body") {
debugFlake << "\tstart Element" << elementName;
svgWriter.writeStartElement("text");
appendStyle = QString();
}
else if (elementName == "p") {
// new line
debugFlake << "\t\tstart Element" << elementName;
svgWriter.writeStartElement("tspan");
newLine = true;
if (em.isEmpty()) {
em = bodyEm;
appendStyle = QString();
}
lineCount++;
}
else if (elementName == "span") {
debugFlake << "\tstart Element" << elementName;
svgWriter.writeStartElement("tspan");
appendStyle = QString();
}
else if (elementName == "b" || elementName == "strong") {
debugFlake << "\tstart Element" << elementName;
svgWriter.writeStartElement("tspan");
appendStyle = "font-weight:700;";
}
else if (elementName == "i" || elementName == "em") {
debugFlake << "\tstart Element" << elementName;
svgWriter.writeStartElement("tspan");
appendStyle = "font-style:italic;";
}
else if (elementName == "u") {
debugFlake << "\tstart Element" << elementName;
svgWriter.writeStartElement("tspan");
appendStyle = "text-decoration:underline";
}
QXmlStreamAttributes attributes = htmlReader.attributes();
QString textAlign;
if (attributes.hasAttribute("align")) {
textAlign = attributes.value("align").toString();
}
if (attributes.hasAttribute("style")) {
QString filteredStyles;
- QStringList svgStyles = QString("font-family font-size font-weight font-variant word-spacing text-decoration font-style font-size-adjust font-stretch direction").split(" ");
+ QStringList svgStyles = QString("font-family font-size font-weight font-variant word-spacing text-decoration font-style font-size-adjust font-stretch direction letter-spacing").split(" ");
QStringList styles = attributes.value("style").toString().split(";");
for(int i=0; i<styles.size(); i++) {
QStringList style = QString(styles.at(i)).split(":");
debugFlake<<style.at(0);
if (svgStyles.contains(QString(style.at(0)).trimmed())) {
filteredStyles.append(styles.at(i)+";");
}
if (QString(style.at(0)).trimmed() == "color") {
filteredStyles.append(" fill:"+style.at(1)+";");
}
if (QString(style.at(0)).trimmed() == "text-align") {
textAlign = QString(style.at(1)).trimmed();
}
if (QString(style.at(0)).trimmed() == "line-height"){
if (style.at(1).contains("%")) {
double percentage = QString(style.at(1)).remove("%").toDouble();
em = QString::number(percentage/100.0)+"em";
} else if(style.at(1).contains("em")) {
em = style.at(1);
} else if(style.at(1).contains("px")) {
em = style.at(1);
}
if (elementName == "body") {
bodyEm = em;
}
}
}
if (textAlign == "center") {
filteredStyles.append(" text-anchor:middle;");
} else if (textAlign == "right") {
filteredStyles.append(" text-anchor:end;");
} else if (textAlign == "left"){
filteredStyles.append(" text-anchor:start;");
}
filteredStyles.append(appendStyle);
if (!filteredStyles.isEmpty()) {
svgWriter.writeAttribute("style", filteredStyles);
previousStyleString = filteredStyles;
}
}
if (newLine && lineCount > 1) {
debugFlake << "\t\tAdvancing to the next line";
svgWriter.writeAttribute("x", "0");
svgWriter.writeAttribute("dy", em);
}
break;
}
case QXmlStreamReader::EndElement:
{
if (htmlReader.name() == "br") break;
if (elementName == "p" || elementName == "span" || elementName == "body") {
debugFlake << "\tEndElement" << htmlReader.name() << "(" << elementName << ")";
svgWriter.writeEndElement();
}
break;
}
case QXmlStreamReader::Characters:
{
if (elementName == "style") {
*styles = htmlReader.text().toString();
}
else {
if (!htmlReader.isWhitespace()) {
debugFlake << "\tCharacters:" << htmlReader.text();
svgWriter.writeCharacters(htmlReader.text().toString());
}
}
break;
}
default:
;
}
}
if (htmlReader.hasError()) {
d->errors << htmlReader.errorString();
return false;
}
if (svgWriter.hasError()) {
d->errors << i18n("Unknown error writing SVG text element");
return false;
}
*svgText = QString::fromUtf8(svgBuffer.data());
return true;
}
void postCorrectBlockHeight(QTextDocument *doc,
qreal currLineAscent,
qreal prevLineAscent,
qreal prevLineDescent,
int prevBlockCursorPosition,
qreal currentBlockAbsoluteLineOffset)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(prevBlockCursorPosition >= 0);
QTextCursor postCorrectionCursor(doc);
postCorrectionCursor.setPosition(prevBlockCursorPosition);
if (!postCorrectionCursor.isNull()) {
const qreal relativeLineHeight =
((currentBlockAbsoluteLineOffset - currLineAscent + prevLineAscent) /
(prevLineAscent + prevLineDescent)) * 100.0;
QTextBlockFormat format = postCorrectionCursor.blockFormat();
format.setLineHeight(relativeLineHeight, QTextBlockFormat::ProportionalHeight);
postCorrectionCursor.setBlockFormat(format);
postCorrectionCursor = QTextCursor();
}
}
QTextFormat findMostCommonFormat(const QList<QTextFormat> &allFormats)
{
QTextCharFormat mostCommonFormat;
QSet<int> propertyIds;
/**
* Get all existing property ids
*/
Q_FOREACH (const QTextFormat &format, allFormats) {
const QMap<int, QVariant> formatProperties = format.properties();
Q_FOREACH (int id, formatProperties.keys()) {
propertyIds.insert(id);
}
}
/**
* Filter out properties that do not exist in some formats. Otherwise, the
* global format may override the default value used in these formats
* (and yes, we do not have access to the default values to use them
* in difference calculation algorithm
*/
Q_FOREACH (const QTextFormat &format, allFormats) {
for (auto it = propertyIds.begin(); it != propertyIds.end();) {
if (!format.hasProperty(*it)) {
it = propertyIds.erase(it);
} else {
++it;
}
}
if (propertyIds.isEmpty()) break;
}
if (!propertyIds.isEmpty()) {
QMap<int, QMap<QVariant, int>> propertyFrequency;
/**
* Calculate the frequency of values used in *all* the formats
*/
Q_FOREACH (const QTextFormat &format, allFormats) {
const QMap<int, QVariant> formatProperties = format.properties();
Q_FOREACH (int id, propertyIds) {
KIS_SAFE_ASSERT_RECOVER_BREAK(formatProperties.contains(id));
propertyFrequency[id][formatProperties.value(id)]++;
}
}
/**
* Add the most popular property value to the set of most common properties
*/
for (auto it = propertyFrequency.constBegin(); it != propertyFrequency.constEnd(); ++it) {
const int id = it.key();
const QMap<QVariant, int> allValues = it.value();
int maxCount = 0;
QVariant maxValue;
for (auto valIt = allValues.constBegin(); valIt != allValues.constEnd(); ++valIt) {
if (valIt.value() > maxCount) {
maxCount = valIt.value();
maxValue = valIt.key();
}
}
KIS_SAFE_ASSERT_RECOVER_BREAK(maxCount > 0);
mostCommonFormat.setProperty(id, maxValue);
}
}
return mostCommonFormat;
}
qreal calcLineWidth(const QTextBlock &block)
{
const QString blockText = block.text();
QTextLayout lineLayout;
lineLayout.setText(blockText);
lineLayout.setFont(block.charFormat().font());
lineLayout.setFormats(block.textFormats());
lineLayout.setTextOption(block.layout()->textOption());
lineLayout.beginLayout();
QTextLine fullLine = lineLayout.createLine();
if (!fullLine.isValid()) {
fullLine.setNumColumns(blockText.size());
}
lineLayout.endLayout();
return lineLayout.boundingRect().width();
}
bool KoSvgTextShapeMarkupConverter::convertDocumentToSvg(const QTextDocument *doc, QString *svgText)
{
QBuffer svgBuffer;
svgBuffer.open(QIODevice::WriteOnly);
QXmlStreamWriter svgWriter(&svgBuffer);
// disable auto-formatting to avoid axtra spaces appearing here and there
svgWriter.setAutoFormatting(false);
qreal maxParagraphWidth = 0.0;
QTextCharFormat mostCommonCharFormat;
QTextBlockFormat mostCommonBlockFormat;
struct LineInfo {
LineInfo() {}
LineInfo(QTextBlock _block, int _numSkippedLines)
: block(_block), numSkippedLines(_numSkippedLines)
{}
QTextBlock block;
int numSkippedLines = 0;
};
QVector<LineInfo> lineInfoList;
{
QTextBlock block = doc->begin();
QList<QTextFormat> allCharFormats;
QList<QTextFormat> allBlockFormats;
int numSequentialEmptyLines = 0;
while (block.isValid()) {
if (!block.text().trimmed().isEmpty()) {
lineInfoList.append(LineInfo(block, numSequentialEmptyLines));
numSequentialEmptyLines = 0;
maxParagraphWidth = qMax(maxParagraphWidth, calcLineWidth(block));
allBlockFormats.append(block.blockFormat());
Q_FOREACH (const QTextLayout::FormatRange &range, block.textFormats()) {
QTextFormat format = range.format;
allCharFormats.append(format);
}
} else {
numSequentialEmptyLines++;
}
block = block.next();
}
mostCommonCharFormat = findMostCommonFormat(allCharFormats).toCharFormat();
mostCommonBlockFormat = findMostCommonFormat(allBlockFormats).toBlockFormat();
}
//Okay, now the actual writing.
QTextBlock block = doc->begin();
Q_UNUSED(block);
svgWriter.writeStartElement("text");
{
const QString commonTextStyle = style(mostCommonCharFormat, mostCommonBlockFormat);
if (!commonTextStyle.isEmpty()) {
svgWriter.writeAttribute("style", commonTextStyle);
}
}
int prevBlockRelativeLineSpacing = mostCommonBlockFormat.lineHeight();
int prevBlockLineType = mostCommonBlockFormat.lineHeightType();
qreal prevBlockAscent = 0.0;
qreal prevBlockDescent= 0.0;
Q_FOREACH (const LineInfo &info, lineInfoList) {
QTextBlock block = info.block;
const QTextBlockFormat blockFormatDiff = formatDifference(block.blockFormat(), mostCommonBlockFormat).toBlockFormat();
QTextCharFormat blockCharFormatDiff = QTextCharFormat();
const QVector<QTextLayout::FormatRange> formats = block.textFormats();
if (formats.size()==1) {
blockCharFormatDiff = formatDifference(formats.at(0).format, mostCommonCharFormat).toCharFormat();
}
const QTextLayout *layout = block.layout();
const QTextLine line = layout->lineAt(0);
svgWriter.writeStartElement("tspan");
const QString text = block.text();
/**
* Mindblowing part: QTextEdit uses a hi-end algorithm for auto-estimation for the text
* directionality, so the user expects his text being saved to SVG with the same
* directionality. Just emulate behavior of direction="auto", which is not supported by
* SVG 1.1
*
* BUG: 392064
*/
bool isRightToLeft = false;
for (int i = 0; i < text.size(); i++) {
const QChar ch = text[i];
if (ch.direction() == QChar::DirR || ch.direction() == QChar::DirAL) {
isRightToLeft = true;
break;
} else if (ch.direction() == QChar::DirL) {
break;
}
}
if (isRightToLeft) {
svgWriter.writeAttribute("direction", "rtl");
svgWriter.writeAttribute("unicode-bidi", "embed");
}
{
const QString blockStyleString = style(blockCharFormatDiff, blockFormatDiff);
if (!blockStyleString.isEmpty()) {
svgWriter.writeAttribute("style", blockStyleString);
}
}
/**
* The alignment rule will be inverted while rendering the text in the text shape
* (according to the standard the alignment is defined not by "left" or "right",
* but by "start" and "end", which inverts for rtl text)
*/
Qt::Alignment blockAlignment = block.blockFormat().alignment();
if (isRightToLeft) {
if (blockAlignment & Qt::AlignLeft) {
blockAlignment &= ~Qt::AlignLeft;
blockAlignment |= Qt::AlignRight;
} else if (blockAlignment & Qt::AlignRight) {
blockAlignment &= ~Qt::AlignRight;
blockAlignment |= Qt::AlignLeft;
}
}
if (blockAlignment & Qt::AlignHCenter) {
svgWriter.writeAttribute("x", KisDomUtils::toString(0.5 * maxParagraphWidth) + "pt");
} else if (blockAlignment & Qt::AlignRight) {
svgWriter.writeAttribute("x", KisDomUtils::toString(maxParagraphWidth) + "pt");
} else {
svgWriter.writeAttribute("x", "0");
}
if (block.blockNumber() > 0) {
const qreal lineHeightPt =
line.ascent() - prevBlockAscent +
(prevBlockAscent + prevBlockDescent) * qreal(prevBlockRelativeLineSpacing) / 100.0;
const qreal currentLineSpacing = (info.numSkippedLines + 1) * lineHeightPt;
svgWriter.writeAttribute("dy", KisDomUtils::toString(currentLineSpacing) + "pt");
}
prevBlockRelativeLineSpacing =
blockFormatDiff.hasProperty(QTextFormat::LineHeight) ?
blockFormatDiff.lineHeight() :
mostCommonBlockFormat.lineHeight();
prevBlockLineType =
blockFormatDiff.hasProperty(QTextFormat::LineHeightType) ?
blockFormatDiff.lineHeightType() :
mostCommonBlockFormat.lineHeightType();
if (prevBlockLineType == QTextBlockFormat::SingleHeight) {
//single line will set lineHeight to 100%
prevBlockRelativeLineSpacing = 100;
}
prevBlockAscent = line.ascent();
prevBlockDescent = line.descent();
if (formats.size()>1) {
QStringList texts;
QVector<QTextCharFormat> charFormats;
for (int f=0; f<formats.size(); f++) {
QString chunk;
for (int c = 0; c<formats.at(f).length; c++) {
chunk.append(text.at(formats.at(f).start+c));
}
texts.append(chunk);
charFormats.append(formats.at(f).format);
}
for (int c = 0; c<texts.size(); c++) {
QTextCharFormat diff = formatDifference(charFormats.at(c), mostCommonCharFormat).toCharFormat();
const QString subStyle = style(diff, QTextBlockFormat(), mostCommonCharFormat);
if (!subStyle.isEmpty()) {
svgWriter.writeStartElement("tspan");
svgWriter.writeAttribute("style", subStyle);
svgWriter.writeCharacters(texts.at(c));
svgWriter.writeEndElement();
} else {
svgWriter.writeCharacters(texts.at(c));
}
}
} else {
svgWriter.writeCharacters(text);
//check format against
}
svgWriter.writeEndElement();
}
svgWriter.writeEndElement();//text root element.
if (svgWriter.hasError()) {
d->errors << i18n("Unknown error writing SVG text element");
return false;
}
*svgText = QString::fromUtf8(svgBuffer.data()).trimmed();
return true;
}
void parseTextAttributes(const QXmlStreamAttributes &elementAttributes,
QTextCharFormat &charFormat,
QTextBlockFormat &blockFormat)
{
QString styleString;
// we convert all the presentation attributes into styles
QString presentationAttributes;
for (int a = 0; a < elementAttributes.size(); a++) {
if (elementAttributes.at(a).name() != "style") {
presentationAttributes
.append(elementAttributes.at(a).name().toString())
.append(":")
.append(elementAttributes.at(a).value().toString())
.append(";");
}
}
if (presentationAttributes.endsWith(";")) {
presentationAttributes.chop(1);
}
if (elementAttributes.hasAttribute("style")) {
styleString = elementAttributes.value("style").toString();
if (styleString.endsWith(";")) {
styleString.chop(1);
}
}
if (!styleString.isEmpty() || !presentationAttributes.isEmpty()) {
//add attributes to parse them as part of the style.
styleString.append(";")
.append(presentationAttributes);
QStringList styles = styleString.split(";");
QVector<QTextFormat> formats = KoSvgTextShapeMarkupConverter::stylesFromString(styles, charFormat, blockFormat);
charFormat = formats.at(0).toCharFormat();
blockFormat = formats.at(1).toBlockFormat();
}
}
bool KoSvgTextShapeMarkupConverter::convertSvgToDocument(const QString &svgText, QTextDocument *doc)
{
QXmlStreamReader svgReader(svgText.trimmed());
doc->clear();
QTextCursor cursor(doc);
struct BlockFormatRecord {
BlockFormatRecord() {}
BlockFormatRecord(QTextBlockFormat _blockFormat,
QTextCharFormat _charFormat)
: blockFormat(_blockFormat),
charFormat(_charFormat)
{}
QTextBlockFormat blockFormat;
QTextCharFormat charFormat;
};
QStack<BlockFormatRecord> formatStack;
formatStack.push(BlockFormatRecord(QTextBlockFormat(), QTextCharFormat()));
qreal currBlockAbsoluteLineOffset = 0.0;
int prevBlockCursorPosition = -1;
qreal prevLineDescent = 0.0;
qreal prevLineAscent = 0.0;
boost::optional<qreal> previousBlockAbsoluteXOffset = boost::none;
while (!svgReader.atEnd()) {
QXmlStreamReader::TokenType token = svgReader.readNext();
switch (token) {
case QXmlStreamReader::StartElement:
{
bool newBlock = false;
QTextBlockFormat newBlockFormat;
QTextCharFormat newCharFormat;
qreal absoluteLineOffset = 1.0;
// fetch format of the parent block and make it default
if (formatStack.size() >= 2) {
newBlockFormat = formatStack[formatStack.size() - 2].blockFormat;
newCharFormat = formatStack[formatStack.size() - 2].charFormat;
}
{
const QXmlStreamAttributes elementAttributes = svgReader.attributes();
parseTextAttributes(elementAttributes, newCharFormat, newBlockFormat);
// mnemonic for a newline is (dy != 0 && x == 0)
boost::optional<qreal> blockAbsoluteXOffset = boost::none;
if (elementAttributes.hasAttribute("x")) {
QString xString = elementAttributes.value("x").toString();
if (xString.contains("pt")) {
xString = xString.remove("pt").trimmed();
}
blockAbsoluteXOffset = KisDomUtils::toDouble(xString);
}
if (previousBlockAbsoluteXOffset &&
blockAbsoluteXOffset &&
qFuzzyCompare(*previousBlockAbsoluteXOffset, *blockAbsoluteXOffset) &&
svgReader.name() != "text" &&
elementAttributes.hasAttribute("dy")) {
QString dyString = elementAttributes.value("dy").toString();
if (dyString.contains("pt")) {
dyString = dyString.remove("pt").trimmed();
}
KIS_SAFE_ASSERT_RECOVER_NOOP(formatStack.isEmpty() == (svgReader.name() == "text"));
absoluteLineOffset = KisDomUtils::toDouble(dyString);
newBlock = absoluteLineOffset > 0;
}
if (elementAttributes.hasAttribute("x")) {
previousBlockAbsoluteXOffset = blockAbsoluteXOffset;
}
}
//hack
doc->setTextWidth(100);
doc->setTextWidth(-1);
if (newBlock && absoluteLineOffset > 0) {
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!formatStack.isEmpty(), false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cursor.block().layout()->lineCount() > 0, false);
QTextLine line = cursor.block().layout()->lineAt(0);
if (prevBlockCursorPosition >= 0) {
postCorrectBlockHeight(doc, line.ascent(), prevLineAscent, prevLineDescent,
prevBlockCursorPosition, currBlockAbsoluteLineOffset);
}
prevBlockCursorPosition = cursor.position();
prevLineAscent = line.ascent();
prevLineDescent = line.descent();
currBlockAbsoluteLineOffset = absoluteLineOffset;
cursor.insertBlock();
cursor.setCharFormat(formatStack.top().charFormat);
cursor.setBlockFormat(formatStack.top().blockFormat);
}
cursor.mergeCharFormat(newCharFormat);
cursor.mergeBlockFormat(newBlockFormat);
formatStack.push(BlockFormatRecord(cursor.blockFormat(), cursor.charFormat()));
break;
}
case QXmlStreamReader::EndElement:
{
if (svgReader.name() != "text") {
formatStack.pop();
KIS_SAFE_ASSERT_RECOVER(!formatStack.isEmpty()) { break; }
cursor.setCharFormat(formatStack.top().charFormat);
cursor.setBlockFormat(formatStack.top().blockFormat);
}
break;
}
case QXmlStreamReader::Characters:
{
if (!svgReader.isWhitespace()) {
cursor.insertText(svgReader.text().toString());
}
break;
}
default:
break;
}
}
if (prevBlockCursorPosition >= 0) {
QTextLine line = cursor.block().layout()->lineAt(0);
postCorrectBlockHeight(doc, line.ascent(), prevLineAscent, prevLineDescent,
prevBlockCursorPosition, currBlockAbsoluteLineOffset);
}
if (svgReader.hasError()) {
d->errors << svgReader.errorString();
return false;
}
doc->setModified(false);
return true;
}
QStringList KoSvgTextShapeMarkupConverter::errors() const
{
return d->errors;
}
QStringList KoSvgTextShapeMarkupConverter::warnings() const
{
return d->warnings;
}
QString KoSvgTextShapeMarkupConverter::style(QTextCharFormat format,
QTextBlockFormat blockFormat,
QTextCharFormat mostCommon)
{
QStringList style;
for(int i=0; i<format.properties().size(); i++) {
QString c;
int propertyId = format.properties().keys().at(i);
if (propertyId == QTextCharFormat::FontFamily) {
c.append("font-family").append(":")
.append(format.properties()[propertyId].toString());
}
if (propertyId == QTextCharFormat::FontPointSize) {
c.append("font-size").append(":")
.append(format.properties()[propertyId].toString()+"pt");
style.append(c);
c.clear();
QFontMetricsF metrics(format.fontFamily());
qreal xRatio = metrics.xHeight()/metrics.height();
c.append("font-size-adjust").append(":").append(QString::number(xRatio));
}
if (propertyId == QTextCharFormat::FontPixelSize) {
c.append("font-size").append(":")
.append(format.properties()[propertyId].toString()+"px");
}
if (propertyId == QTextCharFormat::FontWeight) {
//8 comes from QTextDocument...
c.append("font-weight").append(":")
.append(QString::number(format.properties()[propertyId].toInt()*8));
}
if (propertyId == QTextCharFormat::FontItalic) {
QString val = "italic";
if (!format.fontItalic()) {
val = "normal";
}
c.append("font-style").append(":")
.append(val);
}
if (propertyId == QTextCharFormat::FontCapitalization) {
QString val;
if (format.fontCapitalization() == QFont::SmallCaps){
c.append("font-variant").append(":")
.append("small-caps");
} else if (format.fontCapitalization() == QFont::AllUppercase) {
c.append("text-transform").append(":")
.append("uppercase");
} else if (format.fontCapitalization() == QFont::AllLowercase) {
c.append("text-transform").append(":")
.append("lowercase");
} else if (format.fontCapitalization() == QFont::Capitalize) {
c.append("text-transform").append(":")
.append("capitalize");
}
}
if (propertyId == QTextCharFormat::FontStretch) {
c.append("font-stretch").append(":")
.append(format.properties()[propertyId].toString());
}
if (propertyId == QTextCharFormat::FontKerning) {
QString val = "normal";
if(!format.fontKerning()) {
val = "0";
}
c.append("kerning").append(":")
.append(val);
}
if (propertyId == QTextCharFormat::FontWordSpacing) {
c.append("word-spacing").append(":")
.append(QString::number(format.fontWordSpacing()));
}
if (propertyId == QTextCharFormat::FontLetterSpacing) {
QString val;
if (format.fontLetterSpacingType()==QFont::AbsoluteSpacing) {
val = QString::number(format.fontLetterSpacing());
} else {
val = QString::number(((format.fontLetterSpacing()/100)*format.fontPointSize()));
}
c.append("letter-spacing").append(":")
.append(val);
}
if (propertyId == QTextCharFormat::TextOutline) {
if (format.textOutline().color() != mostCommon.textOutline().color()) {
c.append("stroke").append(":")
.append(format.textOutline().color().name());
style.append(c);
c.clear();
}
if (format.textOutline().width() != mostCommon.textOutline().width()) {
c.append("stroke-width").append(":")
.append(QString::number(format.textOutline().width()));
}
}
if (propertyId == QTextCharFormat::TextVerticalAlignment) {
QString val = "baseline";
if (format.verticalAlignment() == QTextCharFormat::AlignSubScript) {
val = QLatin1String("sub");
}
else if (format.verticalAlignment() == QTextCharFormat::AlignSuperScript) {
val = QLatin1String("super");
}
c.append("baseline-shift").append(":").append(val);
}
//we might need a better check than 'isn't black'
if (propertyId == QTextCharFormat::ForegroundBrush) {
QString c;
c.append("fill").append(":")
.append(format.foreground().color().name());
if (!c.isEmpty()) {
style.append(c);
}
}
if (!c.isEmpty()) {
style.append(c);
}
}
if (format.underlineStyle() != QTextCharFormat::SpellCheckUnderline) {
if(format.underlineStyle() != mostCommon.underlineStyle()){
QStringList values;
QString c;
if (format.fontUnderline()) {
values.append("underline");
}
if (format.fontOverline()) {
values.append("overline");
}
if (format.fontStrikeOut()) {
values.append("line-through");
}
if (values.isEmpty()) {
values.append("none");
}
c.append("text-decoration").append(":")
.append(values.join(" "));
if (!values.isEmpty()) {
style.append(c);
}
}
}
if (blockFormat.hasProperty(QTextBlockFormat::BlockAlignment)) {
// TODO: Alignment works incorrectly! The offsets should be calculated
// according to the shape width/height!
QString c;
QString val;
if (blockFormat.alignment()==Qt::AlignRight) {
val = "end";
} else if (blockFormat.alignment()==Qt::AlignCenter) {
val = "middle";
} else {
val = "start";
}
c.append("text-anchor").append(":")
.append(val);
if (!c.isEmpty()) {
style.append(c);
}
}
return style.join("; ");
}
QVector<QTextFormat> KoSvgTextShapeMarkupConverter::stylesFromString(QStringList styles, QTextCharFormat currentCharFormat, QTextBlockFormat currentBlockFormat)
{
Q_UNUSED(currentBlockFormat);
QVector<QTextFormat> formats;
QTextCharFormat charFormat;
charFormat.setTextOutline(currentCharFormat.textOutline());
QTextBlockFormat blockFormat;
SvgGraphicsContext *context = new SvgGraphicsContext();
for (int i=0; i<styles.size(); i++) {
if (!styles.at(i).isEmpty()){
QStringList style = styles.at(i).split(":");
QString property = style.at(0).trimmed();
QString value = style.at(1).trimmed();
if (property == "font-family") {
charFormat.setFontFamily(value);
}
if (property == "font-size") {
qreal val = SvgUtil::parseUnitX(context, value);
charFormat.setFontPointSize(val);
}
if (property == "font-variant") {
if (value=="small-caps") {
charFormat.setFontCapitalization(QFont::SmallCaps);
} else {
charFormat.setFontCapitalization(QFont::MixedCase);
}
}
if (property == "font-style") {
if (value=="italic" || value=="oblique") {
charFormat.setFontItalic(true);
} else {
charFormat.setFontItalic(false);
}
}
if (property == "font-stretch") {
charFormat.setFontStretch(value.toInt());
}
if (property == "font-weight") {
charFormat.setFontWeight(value.toInt()/8);
}
if (property == "text-decoration") {
charFormat.setFontUnderline(false);
charFormat.setFontOverline(false);
charFormat.setFontStrikeOut(false);
QStringList values = value.split(" ");
if (values.contains("line-through")) {
charFormat.setFontStrikeOut(true);
}
if (values.contains("overline")) {
charFormat.setFontOverline(true);
}
if(values.contains("underline")){
charFormat.setFontUnderline(true);
}
}
if (property == "text-transform") {
if (value == "uppercase") {
charFormat.setFontCapitalization(QFont::AllUppercase);
} else if (value == "lowercase") {
charFormat.setFontCapitalization(QFont::AllLowercase);
} else if (value == "capitalize") {
charFormat.setFontCapitalization(QFont::Capitalize);
} else{
charFormat.setFontCapitalization(QFont::MixedCase);
}
}
if (property == "letter-spacing") {
qreal val = SvgUtil::parseUnitX(context, value);
charFormat.setFontLetterSpacingType(QFont::AbsoluteSpacing);
charFormat.setFontLetterSpacing(val);
}
if (property == "word-spacing") {
qreal val = SvgUtil::parseUnitX(context, value);
charFormat.setFontWordSpacing(val);
}
if (property == "kerning") {
if (value=="normal") {
charFormat.setFontKerning(true);
} else {
qreal val = SvgUtil::parseUnitX(context, value);
charFormat.setFontKerning(false);
charFormat.setFontLetterSpacingType(QFont::AbsoluteSpacing);
charFormat.setFontLetterSpacing(charFormat.fontLetterSpacing() + val);
}
}
if (property == "stroke") {
QPen pen = charFormat.textOutline();
QColor color;
color.setNamedColor(value);
pen.setColor(color);
charFormat.setTextOutline(pen);
}
if (property == "stroke-width") {
QPen pen = charFormat.textOutline();
pen.setWidth(value.toInt());
charFormat.setTextOutline(pen);
}
if (property == "fill") {
QColor color;
color.setNamedColor(value);
charFormat.setForeground(color);
}
if (property == "text-anchor") {
if (value == "end") {
blockFormat.setAlignment(Qt::AlignRight);
} else if (value == "middle") {
blockFormat.setAlignment(Qt::AlignCenter);
} else {
blockFormat.setAlignment(Qt::AlignLeft);
}
}
if (property == "baseline-shift") {
if (value == "super") {
charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
} else if (value == "sub") {
charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
} else {
charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal);
}
}
}
}
formats.append(charFormat);
formats.append(blockFormat);
return formats;
}
QTextFormat KoSvgTextShapeMarkupConverter::formatDifference(QTextFormat test, QTextFormat reference)
{
//copied from QTextDocument.cpp
QTextFormat diff = test;
//props should proly compare itself to the main text format...
const QMap<int, QVariant> props = reference.properties();
for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
it != end; ++it)
if (it.value() == test.property(it.key())) {
// Some props must not be removed as default state gets in the way.
if (it.key() == 0x2023) { // TextUnderlineStyle
continue;
+ } else if (it.key() == 0x2033) { // FontLetterSpacingType
+ continue;
}
diff.clearProperty(it.key());
}
return diff;
}
diff --git a/libs/flake/tools/KoZoomToolWidget.cpp b/libs/flake/tools/KoZoomToolWidget.cpp
index b818316b29..43822fdf96 100644
--- a/libs/flake/tools/KoZoomToolWidget.cpp
+++ b/libs/flake/tools/KoZoomToolWidget.cpp
@@ -1,89 +1,48 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Martin Pfeiffer <hubipete@gmx.net>
*
* 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 "KoZoomToolWidget.h"
#include <QPainter>
#include <QMouseEvent>
#include <KoIcon.h>
#include "KoZoomTool.h"
KoZoomToolWidget::KoZoomToolWidget(KoZoomTool* tool, QWidget* parent)
- : QWidget(parent), m_tool(tool)
+ : QWidget(parent)
+ , m_tool(tool)
{
setupUi(this);
- m_dirtyThumbnail = true;
- birdEyeLabel->installEventFilter(this);
- birdEyeLabel->hide(); //remove this when coding on the birdEyeLabel
zoomInButton->setIcon(koIcon("zoom-in"));
zoomOutButton->setIcon(koIcon("zoom-out"));
connect(zoomInButton, SIGNAL(toggled(bool)), this, SLOT(changeZoomMode()));
connect(zoomOutButton, SIGNAL(toggled(bool)), this, SLOT(changeZoomMode()));
zoomInButton->click();
}
KoZoomToolWidget::~KoZoomToolWidget()
{
}
-void KoZoomToolWidget::paintBirdEye()
-{
- QPainter p;
- if (m_dirtyThumbnail) {
- m_thumbnail = QPixmap(birdEyeLabel->size());
-// m_thumbnail.fill(birdEyeLabel->palette().dark().color());
- p.begin(&m_thumbnail);
- // TODO fill in code to paint a thumbnail of the current document
- p.end();
- m_dirtyThumbnail = false;
- }
-
- p.begin(birdEyeLabel);
- p.drawPixmap(0, 0, m_thumbnail);
-// p.drawRect(m_birdEyeRect);
- p.end();
-}
-
-bool KoZoomToolWidget::eventFilter(QObject* object, QEvent* event)
-{
- if (object == birdEyeLabel) {
- if (event->type() == QEvent::Paint) {
- paintBirdEye();
- return true;
- } else if (event->type() == QEvent::MouseMove) {
- QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
- if (mouseEvent->buttons() | Qt::LeftButton) {
- // m_tool->canvasController()->pan
- // TODO implement panning
- }
- return true;
- } else if (event->type() == QEvent::Resize) {
- m_dirtyThumbnail = true;
- } else
- return false;
- }
- return QWidget::eventFilter(object, event);
-}
-
void KoZoomToolWidget::changeZoomMode()
{
m_tool->setZoomInMode(zoomInButton->isChecked());
}
diff --git a/libs/flake/tools/KoZoomToolWidget.h b/libs/flake/tools/KoZoomToolWidget.h
index aff3e89019..41a5fe4feb 100644
--- a/libs/flake/tools/KoZoomToolWidget.h
+++ b/libs/flake/tools/KoZoomToolWidget.h
@@ -1,50 +1,41 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Martin Pfeiffer <hubipete@gmx.net>
*
* 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 KOZOOMTOOLWIDGET_H
#define KOZOOMTOOLWIDGET_H
#include <QWidget>
#include <QPixmap>
#include "ui_KoZoomToolWidget.h"
class KoZoomTool;
class KoZoomToolWidget : public QWidget, Ui::ZoomToolWidget
{
Q_OBJECT
public:
explicit KoZoomToolWidget(KoZoomTool* tool, QWidget *parent = 0);
~KoZoomToolWidget() override;
-protected:
- bool eventFilter(QObject *object, QEvent *event) override;
-
private Q_SLOTS:
void changeZoomMode();
-
private:
- void paintBirdEye();
-
- bool m_dirtyThumbnail;
- QRect m_birdEyeRect;
- QPixmap m_thumbnail;
KoZoomTool *m_tool;
};
#endif
diff --git a/libs/flake/tools/KoZoomToolWidget.ui b/libs/flake/tools/KoZoomToolWidget.ui
index f8d6fde19a..873553084a 100644
--- a/libs/flake/tools/KoZoomToolWidget.ui
+++ b/libs/flake/tools/KoZoomToolWidget.ui
@@ -1,58 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ZoomToolWidget</class>
<widget class="QWidget" name="ZoomToolWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>212</width>
<height>261</height>
</rect>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QRadioButton" name="zoomInButton">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
- <string>Zoom In</string>
+ <string>&amp;Zoom In</string>
</property>
</widget>
</item>
- <item row="0" column="1">
- <widget class="QRadioButton" name="zoomOutButton">
- <property name="focusPolicy">
- <enum>Qt::NoFocus</enum>
- </property>
- <property name="text">
- <string>Zoom Out</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0" colspan="2">
- <widget class="QLabel" name="birdEyeLabel">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
+ <item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
+ <item row="0" column="1">
+ <widget class="QRadioButton" name="zoomOutButton">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Zoo&amp;m Out</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
diff --git a/libs/global/kis_shared_ptr.h b/libs/global/kis_shared_ptr.h
index c75e1cc671..b9f1c93602 100644
--- a/libs/global/kis_shared_ptr.h
+++ b/libs/global/kis_shared_ptr.h
@@ -1,526 +1,526 @@
/*
* Copyright (c) 2005 Frerich Raabe <raabe@kde.org>
* Copyright (c) 2006,2010 Cyrille Berger <cberger@cberger.net>
*
* 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_SHAREDPTR_H
#define KIS_SHAREDPTR_H
#include <QtGlobal>
#include <kis_debug.h>
#include "kis_memory_leak_tracker.h"
template<class T>
class KisWeakSharedPtr;
/**
* KisSharedPtr is a shared pointer similar to KSharedPtr and
* boost::shared_ptr. The difference with KSharedPtr is that our
* constructor is not explicit.
*
* A shared pointer is a wrapper around a real pointer. The shared
* pointer keeps a reference count, and when the reference count drops
* to 0 the contained pointer is deleted. You can use the shared
* pointer just as you would use a real pointer.
*
* See also also item 28 and 29 of More Effective C++ and
* http://bugs.kde.org/show_bug.cgi?id=52261 as well as
* http://www.boost.org/libs/smart_ptr/shared_ptr.htm.
*
* Advantage of KisSharedPtr over boost pointer or QSharedPointer?
*
* The difference with boost share pointer is that in
* boost::shared_ptr, the counter is kept inside the smart pointer,
* meaning that you should never never remove the pointer from its
* smart pointer object, because if you do that, and somewhere in the
* code, the pointer is put back in a smart pointer, then you have two
* counters, and when one reach zero, then the object gets deleted
* while some other code thinks the pointer is still valid.
*
* Disadvantage of KisSharedPtr compared to boost pointer?
*
* KisSharedPtr requires the class to inherits KisShared.
*
- * Difference with QSharedDataPointer
+ * Difference with QSharedPointer
*
- * QSharedDataPointer and KisSharedPtr are very similar, but
- * QSharedDataPointer has an explicit constructor which makes it more
- * painful to use in some constructions. And QSharedDataPointer
+ * QSharedPointer and KisSharedPtr are very similar, but
+ * QSharedPointer has an explicit constructor which makes it more
+ * painful to use in some constructions. And QSharedPointer
* doesn't offer a weak pointer.
*/
template<class T>
class KisSharedPtr
{
friend class KisWeakSharedPtr<T>;
public:
/**
* Creates a null pointer.
*/
inline KisSharedPtr()
: d(0) { }
/**
* Creates a new pointer.
* @param p the pointer
*/
inline KisSharedPtr(T* p)
: d(p) {
ref();
}
inline KisSharedPtr(const KisWeakSharedPtr<T>& o);
// Free the pointer and set it to new value
void attach(T* p);
// Free the pointer
void clear();
/**
* Copies a pointer.
* @param o the pointer to copy
*/
inline KisSharedPtr<T>(const KisSharedPtr<T>& o)
: d(o.d) {
ref();
}
/**
* Dereferences the object that this pointer points to. If it was
* the last reference, the object will be deleted.
*/
inline ~KisSharedPtr() {
deref();
}
inline KisSharedPtr<T>& operator= (const KisSharedPtr& o) {
attach(o.d);
return *this;
}
inline bool operator== (const T* p) const {
return (d == p);
}
inline bool operator!= (const T* p) const {
return (d != p);
}
inline bool operator== (const KisSharedPtr& o) const {
return (d == o.d);
}
inline bool operator!= (const KisSharedPtr& o) const {
return (d != o.d);
}
inline KisSharedPtr<T>& operator= (T* p) {
attach(p);
return *this;
}
inline operator const T*() const {
return d;
}
template< class T2> inline operator KisSharedPtr<T2>() const {
return KisSharedPtr<T2>(d);
}
/**
* @return the contained pointer. If you delete the contained
* pointer, you will make KisSharedPtr very unhappy. It is
* perfectly save to put the contained pointer in another
* KisSharedPtr, though.
*/
inline T* data() {
return d;
}
/**
* @return the pointer
*/
inline const T* data() const {
return d;
}
/**
* @return a const pointer to the shared object.
*/
inline const T* constData() const {
return d;
}
inline const T& operator*() const {
Q_ASSERT(d);
return *d;
}
inline T& operator*() {
Q_ASSERT(d);
return *d;
}
inline const T* operator->() const {
Q_ASSERT(d);
return d;
}
inline T* operator->() {
Q_ASSERT(d);
return d;
}
/**
* @return true if the pointer is null
*/
inline bool isNull() const {
return (d == 0);
}
inline static void ref(const KisSharedPtr<T>* sp, T* t)
{
#ifndef HAVE_MEMORY_LEAK_TRACKER
Q_UNUSED(sp);
#else
KisMemoryLeakTracker::instance()->reference(t, sp);
#endif
if (t) {
t->ref();
}
}
inline static bool deref(const KisSharedPtr<T>* sp, T* t)
{
#ifndef HAVE_MEMORY_LEAK_TRACKER
Q_UNUSED(sp);
#else
KisMemoryLeakTracker::instance()->dereference(t, sp);
#endif
if (t && !t->deref()) {
delete t;
return false;
}
return true;
}
private:
inline void ref() const
{
ref(this, d);
}
inline void deref() const
{
bool v = deref(this, d);
#ifndef NDEBUG
if (!v) {
d = 0;
}
#else
Q_UNUSED(v);
#endif
}
private:
mutable T* d;
};
/**
* A weak shared ptr is an ordinary shared ptr, with two differences:
* it doesn't delete the contained pointer if the refcount drops to
* zero and it doesn't prevent the contained pointer from being
* deleted if the last strong shared pointer goes out of scope.
*/
template<class T>
class KisWeakSharedPtr
{
friend class KisSharedPtr<T>;
public:
/**
* Creates a null pointer.
*/
inline KisWeakSharedPtr()
: d(0), weakReference(0) { }
/**
* Creates a new pointer.
* @param p the pointer
*/
inline KisWeakSharedPtr(T* p) {
load(p);
}
inline KisWeakSharedPtr<T>(const KisSharedPtr<T>& o) {
load(o.d);
}
/**
* Copies a pointer.
* @param o the pointer to copy
*/
inline KisWeakSharedPtr<T>(const KisWeakSharedPtr<T>& o) {
if (o.isConsistent()) {
load(o.d);
}
else {
d = 0;
weakReference = 0;
}
}
inline ~KisWeakSharedPtr() {
detach();
}
inline KisWeakSharedPtr<T>& operator= (const KisWeakSharedPtr& o) {
attach(o);
return *this;
}
inline bool operator== (const T* p) const {
return (d == p);
}
inline bool operator!= (const T* p) const {
return (d != p);
}
inline bool operator== (const KisWeakSharedPtr& o) const {
return (d == o.d);
}
inline bool operator!= (const KisWeakSharedPtr& o) const {
return (d != o.d);
}
inline KisWeakSharedPtr<T>& operator= (T* p) {
attach(p);
return *this;
}
template< class T2> inline operator KisWeakSharedPtr<T2>() const {
return KisWeakSharedPtr<T2>(d);
}
/**
* Note that if you use this function, the pointer might be destroyed
* if KisSharedPtr pointing to this pointer are deleted, resulting in
* a segmentation fault. Use with care.
* @return a const pointer to the shared object.
*/
inline T* data() {
if (!isConsistent()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @see data()
*/
inline const T* data() const {
if (!isConsistent()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @see data()
*/
inline const T* constData() const {
if (!isConsistent()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @see data()
*/
inline operator const T*() const {
/**
* This operator is used in boolean expressions where we check
* for pointer consistency, so return 0 instead of asserting.
*/
return isConsistent() ? d : 0;
}
inline const T& operator*() const {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return *d;
}
inline T& operator*() {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return *d;
}
inline const T* operator->() const {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
inline T* operator->() {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @return true if the pointer is null
*/
inline bool isNull() const {
return (d == 0);
}
/**
* @return true if the weak pointer points to a valid pointer
* and false if the data has been deleted or is null
*/
inline bool isValid() const {
Q_ASSERT(!d || weakReference);
return d && weakReference && isOdd((int)*weakReference);
}
/**
* @brief toStrongRef returns a KisSharedPtr which may be dereferenced.
*
* Weak pointers should only be used to track ownership but never be used as pointers.
* This has historically not been the case, but in new API this function should be used
* instead of directly using a weak pointer as pointer.
* @return a KisSharedPtr, which may be null
*/
inline KisSharedPtr<T> toStrongRef() const {
return KisSharedPtr<T>(*this);
}
private:
static const qint32 WEAK_REF = 2;
static inline bool isOdd(const qint32 &x) {
return x & 0x01;
}
inline bool isConsistent() const {
Q_ASSERT(!d || weakReference);
return !d || (weakReference && isOdd((int)*weakReference));
}
void load(T* newValue) {
d = newValue;
if (d) {
weakReference = d->sharedWeakReference();
weakReference->fetchAndAddOrdered(WEAK_REF);
}
else {
weakReference = 0;
}
}
// see note in kis_shared.cc
inline void attach(T* newValue) {
detach();
load(newValue);
}
inline void attach(const KisWeakSharedPtr& o) {
detach();
if (o.isConsistent()) {
load(o.d);
}
else {
d = 0;
weakReference = 0;
}
}
// see note in kis_shared.cc
void detach() {
d = 0;
if (weakReference &&
weakReference->fetchAndAddOrdered(-WEAK_REF) <= WEAK_REF) {
// sanity check:
Q_ASSERT((int)*weakReference == 0);
delete weakReference;
weakReference = 0;
}
}
mutable T* d;
QAtomicInt *weakReference;
};
template <class T>
Q_INLINE_TEMPLATE KisSharedPtr<T>::KisSharedPtr(const KisWeakSharedPtr<T>& o)
: d(o.d)
{
if (o.isValid()) {
ref();
/**
* Thread safety:
* Is the object we have just referenced still valid?
*/
Q_ASSERT(o.isConsistent());
}
else {
d = 0;
}
}
template <class T>
Q_INLINE_TEMPLATE void KisSharedPtr<T>::attach(T* p)
{
if (d != p) {
ref(this, p);
T* old = d;
d = p;
deref(this, old);
}
}
template <class T>
Q_INLINE_TEMPLATE void KisSharedPtr<T>::clear()
{
attach((T*)0);
}
#endif
diff --git a/libs/global/kis_signal_auto_connection.h b/libs/global/kis_signal_auto_connection.h
index 89205ab67c..f618af22a2 100644
--- a/libs/global/kis_signal_auto_connection.h
+++ b/libs/global/kis_signal_auto_connection.h
@@ -1,137 +1,131 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_SIGNAL_AUTO_CONNECTOR_H
#define __KIS_SIGNAL_AUTO_CONNECTOR_H
#include <QObject>
#include <QPointer>
#include <QVector>
-
/**
* A special wrapper class that represents a connection between two QObject objects.
* It creates the connection on the construction and disconnects it on destruction.
*
* WARNING: never use QScopedPointer::reset() for updating the
* connection like:
*
* QScopedPointer<KisSignalAutoConnection> conn;
* ...
* void Something::setCanvas(KoCanvasBase * canvas) {
* conn.reset(new KisSignalAutoConnection(...));
* }
*
* The object stored in a scoped pointer will be destructed *after*
* the new object created which will cause you object to become
* disconnected.
*
* Instead use two-stage updates:
* conn.reset();
* conn.reset(new KisSignalAutoConnection(...));
*/
class KisSignalAutoConnection
{
public:
/**
* Creates a connection object and starts the requested connection
*/
- inline KisSignalAutoConnection(const QObject *sender, const char *signal,
- const QObject *receiver, const char *method,
+ template<class Sender, class Signal, class Receiver, class Method>
+ inline KisSignalAutoConnection(Sender sender, Signal signal,
+ Receiver receiver, Method method,
Qt::ConnectionType type = Qt::AutoConnection)
- : m_sender(const_cast<QObject*>(sender)),
- m_signal(signal),
- m_receiver(const_cast<QObject*>(receiver)),
- m_method(method)
+ : m_connection(QObject::connect(sender, signal, receiver, method, type))
{
- QObject::connect(m_sender, m_signal, m_receiver, m_method, type);
}
+
inline ~KisSignalAutoConnection()
{
- if (!m_sender.isNull() && !m_receiver.isNull()) {
- QObject::disconnect(m_sender, m_signal, m_receiver, m_method);
- }
+ QObject::disconnect(m_connection);
}
private:
KisSignalAutoConnection(const KisSignalAutoConnection &rhs);
private:
- QPointer<QObject> m_sender;
- const char *m_signal;
- QPointer<QObject> m_receiver;
- const char *m_method;
+ QMetaObject::Connection m_connection;
};
typedef QSharedPointer<KisSignalAutoConnection> KisSignalAutoConnectionSP;
/**
* A class to store multiple connections and to be able to stop all of
* them at once. It is handy when you need to reconnect some other
* object to the current manager. Then you just call
* connectionsStore.clear() and then call addConnection() again to
* recreate them.
*/
class KisSignalAutoConnectionsStore
{
public:
/**
* Connects \p sender to \p receiver with a connection of type \p type.
* The connection is saved into the store so can be reset later with clear()
*
* \see addUniqueConnection()
*/
- inline void addConnection(const QObject *sender, const char *signal,
- const QObject *receiver, const char *method,
+ template<class Sender, class Signal, class Receiver, class Method>
+ inline void addConnection(Sender sender, Signal signal,
+ Receiver receiver, Method method,
Qt::ConnectionType type = Qt::AutoConnection)
{
m_connections.append(KisSignalAutoConnectionSP(
new KisSignalAutoConnection(sender, signal,
receiver, method, type)));
}
/**
* Convenience override for addConnection() that creates a unique connection
*
* \see addConnection()
*/
- inline void addUniqueConnection(const QObject *sender, const char *signal,
- const QObject *receiver, const char *method)
+ template<class Sender, class Signal, class Receiver, class Method>
+ inline void addUniqueConnection(Sender sender, Signal signal,
+ Receiver receiver, Method method)
{
m_connections.append(KisSignalAutoConnectionSP(
new KisSignalAutoConnection(sender, signal,
receiver, method, Qt::UniqueConnection)));
}
/**
* Disconnects all the stored connections and removes them from the store
*/
inline void clear() {
m_connections.clear();
}
inline bool isEmpty() {
return m_connections.isEmpty();
}
private:
QVector<KisSignalAutoConnectionSP> m_connections;
};
#endif /* __KIS_SIGNAL_AUTO_CONNECTOR_H */
diff --git a/libs/global/kis_signal_compressor.h b/libs/global/kis_signal_compressor.h
index ff2ff7af18..2c03f7fea9 100644
--- a/libs/global/kis_signal_compressor.h
+++ b/libs/global/kis_signal_compressor.h
@@ -1,93 +1,94 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_SIGNAL_COMPRESSOR_H
#define __KIS_SIGNAL_COMPRESSOR_H
#include <QObject>
#include "kritaglobal_export.h"
class KisRelaxedTimer;
/**
* Sets a timer to delay or throttle activation of a Qt slot. One example of
* where this is used is to limit the amount of expensive redraw activity on the
* canvas.
*
* There are three behaviors to choose from.
*
* POSTPONE resets the timer after each call. Therefore if the calls are made
* quickly enough, the timer will never be activated.
*
* FIRST_ACTIVE_POSTPONE_NEXT emits the first signal and postpones all
* the other actions the other action like in POSTPONE. This mode is
* used e.g. in move/remove layer functionality. If you remove a
* single layer, you'll see the result immediately. But if you want to
* remove multiple layers, you should wait until all the actions are
* finished.
*
* FIRST_ACTIVE emits the timeout() event immediately and sets a timer of
* duration \p delay. If the compressor is triggered during this time, it will
* wait until the end of the delay period to fire the signal. Further events are
* ignored until the timer elapses. Think of it as a queue with size 1, and
* where the leading element is popped every \p delay ms.
*
* FIRST_INACTIVE emits the timeout() event at the end of a timer of duration \p
* delay ms. The compressor becomes inactive and all events are ignored until
* the timer has elapsed.
*
* The current implementation allows the timeout() to be delayed by up to 2 times
* \p delay in certain situations (for details see cpp file).
*/
class KRITAGLOBAL_EXPORT KisSignalCompressor : public QObject
{
Q_OBJECT
public:
enum Mode {
POSTPONE, /* Calling start() resets the timer to \p delay ms */
FIRST_ACTIVE_POSTPONE_NEXT, /* emits the first signal and postpones all the next ones */
FIRST_ACTIVE, /* Emit timeout() signal immediately. Throttle further timeout() to rate of one per \p delay ms */
FIRST_INACTIVE, /* Set a timer \p delay ms, emit timeout() when it elapses. Ignore all events meanwhile. */
UNDEFINED /* KisSignalCompressor is created without an explicit mode */
};
public:
KisSignalCompressor();
KisSignalCompressor(int delay, Mode mode, QObject *parent = 0);
bool isActive() const;
void setMode(Mode mode);
- void setDelay(int delay);
+
public Q_SLOTS:
+ void setDelay(int delay);
void start();
void stop();
private Q_SLOTS:
void slotTimerExpired();
Q_SIGNALS:
void timeout();
private:
KisRelaxedTimer *m_timer;
Mode m_mode;
bool m_gotSignals;
};
#endif /* __KIS_SIGNAL_COMPRESSOR_H */
diff --git a/libs/global/kis_thread_safe_signal_compressor.cpp b/libs/global/kis_thread_safe_signal_compressor.cpp
index d1a19417e4..cf546e09bc 100644
--- a/libs/global/kis_thread_safe_signal_compressor.cpp
+++ b/libs/global/kis_thread_safe_signal_compressor.cpp
@@ -1,49 +1,55 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_thread_safe_signal_compressor.h"
#include <QApplication>
KisThreadSafeSignalCompressor::KisThreadSafeSignalCompressor(int delay, KisSignalCompressor::Mode mode)
: m_compressor(new KisSignalCompressor(delay, mode, this))
{
connect(this, SIGNAL(internalRequestSignal()), m_compressor, SLOT(start()), Qt::AutoConnection);
connect(this, SIGNAL(internalStopSignal()), m_compressor, SLOT(stop()), Qt::AutoConnection);
+ connect(this, SIGNAL(internalSetDelay(int)), m_compressor, SLOT(setDelay(int)), Qt::AutoConnection);
connect(m_compressor, SIGNAL(timeout()), SIGNAL(timeout()));
// due to this line the object *must not* be deleted explicitly!
this->setObjectName("KisThreadSafeSignalCompressor");
this->moveToThread(QApplication::instance()->thread());
}
bool KisThreadSafeSignalCompressor::isActive() const
{
return m_compressor->isActive();
}
+void KisThreadSafeSignalCompressor::setDelay(int delay)
+{
+ emit internalSetDelay(delay);
+}
+
void KisThreadSafeSignalCompressor::start()
{
emit internalRequestSignal();
}
void KisThreadSafeSignalCompressor::stop()
{
emit internalStopSignal();
}
diff --git a/libs/global/kis_thread_safe_signal_compressor.h b/libs/global/kis_thread_safe_signal_compressor.h
index c7f1efce04..052e721920 100644
--- a/libs/global/kis_thread_safe_signal_compressor.h
+++ b/libs/global/kis_thread_safe_signal_compressor.h
@@ -1,60 +1,62 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_THREAD_SAFE_SIGNAL_COMPRESSOR_H
#define __KIS_THREAD_SAFE_SIGNAL_COMPRESSOR_H
#include <QObject>
#include "kritaglobal_export.h"
#include "kis_signal_compressor.h"
/**
* A special class which works exactly like KisSignalCompressor, but
* supports calling \p start() method from within the context of
* another thread. If it happens, it posts a message to Qt's event
* loop and the \p start() signal is delivered when event loop gets
* executes again.
*
* WARNING: After creation this object moves itself into the main
* thread, so one must *not* delete it explicitly. Use
* deleteLater() instead. Moving into another thread is
* another reason why it cannot have parent QObject.
*/
class KRITAGLOBAL_EXPORT KisThreadSafeSignalCompressor : public QObject
{
Q_OBJECT
public:
KisThreadSafeSignalCompressor(int delay, KisSignalCompressor::Mode mode);
bool isActive() const;
public Q_SLOTS:
+ void setDelay(int delay);
void start();
void stop();
Q_SIGNALS:
void timeout();
void internalRequestSignal();
void internalStopSignal();
+ void internalSetDelay(int delay);
private:
KisSignalCompressor *m_compressor;
};
#endif /* __KIS_THREAD_SAFE_SIGNAL_COMPRESSOR_H */
diff --git a/libs/global/tests/CMakeLists.txt b/libs/global/tests/CMakeLists.txt
index 22d062e090..ca8e3eff8f 100644
--- a/libs/global/tests/CMakeLists.txt
+++ b/libs/global/tests/CMakeLists.txt
@@ -1,8 +1,9 @@
include(ECMAddTests)
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
ecm_add_tests(KisSharedThreadPoolAdapterTest.cpp
+ KisSignalAutoConnectionTest.cpp
NAME_PREFIX libs-global-
LINK_LIBRARIES kritaglobal Qt5::Test)
diff --git a/libs/global/tests/KisSignalAutoConnectionTest.cpp b/libs/global/tests/KisSignalAutoConnectionTest.cpp
new file mode 100644
index 0000000000..45fcf2ed5f
--- /dev/null
+++ b/libs/global/tests/KisSignalAutoConnectionTest.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "KisSignalAutoConnectionTest.h"
+
+#include <kis_signal_auto_connection.h>
+
+void KisSignalAutoConnectionTest::testMacroConnection()
+{
+ QScopedPointer<TestClass> test1(new TestClass());
+ QScopedPointer<TestClass> test2(new TestClass());
+ KisSignalAutoConnectionsStore conn;
+ conn.addConnection(test1.data(), SIGNAL(sigTest1()), test2.data(), SLOT(slotTest1()));
+ emit test1->sigTest1();
+ QVERIFY(test2->m_test1Called);
+ test2->m_test1Called = false;
+ conn.clear();
+ emit test1->sigTest1();
+ QVERIFY(test2->m_test1Called == false);
+}
+
+void KisSignalAutoConnectionTest::testMemberFunctionConnection()
+{
+ QScopedPointer<TestClass> test1(new TestClass());
+ QScopedPointer<TestClass> test2(new TestClass());
+ KisSignalAutoConnectionsStore conn;
+ conn.addConnection(test1.data(), &TestClass::sigTest1, test2.data(), &TestClass::slotTest1);
+ emit test1->sigTest1();
+ QVERIFY(test2->m_test1Called);
+ test2->m_test1Called = false;
+ conn.clear();
+ emit test1->sigTest1();
+ QVERIFY(test2->m_test1Called == false);
+}
+
+void KisSignalAutoConnectionTest::testOverloadConnection()
+{
+ QScopedPointer<TestClass> test1(new TestClass());
+ QScopedPointer<TestClass> test2(new TestClass());
+ KisSignalAutoConnectionsStore conn;
+ conn.addConnection(test1.data(), QOverload<const QString &, const QString &>::of(&TestClass::sigTest2),
+ test2.data(), QOverload<const QString &, const QString &>::of(&TestClass::slotTest2));
+ conn.addConnection(test1.data(), SIGNAL(sigTest2(int)), test2.data(), SLOT(slotTest2(int)));
+ emit test1->sigTest2("foo", "bar");
+ QVERIFY(test2->m_str1 == "foo");
+ QVERIFY(test2->m_str2 == "bar");
+ emit test1->sigTest2(5);
+ QVERIFY(test2->m_number == 5);
+ conn.clear();
+ emit test1->sigTest2("1", "2");
+ QVERIFY(test2->m_str1 == "foo");
+ QVERIFY(test2->m_str2 == "bar");
+ conn.addConnection(test1.data(), SIGNAL(sigTest2(const QString &, const QString &)),
+ test2.data(), SLOT(slotTest2(const QString &)));
+ emit test1->sigTest2("3", "4");
+ QVERIFY(test2->m_str1 == "3");
+ QVERIFY(test2->m_str2 == "");
+}
+
+void KisSignalAutoConnectionTest::testSignalToSignalConnection()
+{
+ QScopedPointer<TestClass> test1(new TestClass());
+ QScopedPointer<TestClass> test2(new TestClass());
+ KisSignalAutoConnectionsStore conn;
+ conn.addConnection(test1.data(), QOverload<int>::of(&TestClass::sigTest2),
+ test2.data(), QOverload<int>::of(&TestClass::sigTest2));
+ conn.addConnection(test2.data(), SIGNAL(sigTest2(int)), test2.data(), SLOT(slotTest2(int)));
+ emit test1->sigTest2(10);
+ QVERIFY(test2->m_number == 10);
+ conn.clear();
+ conn.addConnection(test1.data(), SIGNAL(sigTest2(int)), test2.data(), SIGNAL(sigTest2(int)));
+ conn.addConnection(test2.data(), QOverload<int>::of(&TestClass::sigTest2),
+ test2.data(), QOverload<int>::of(&TestClass::slotTest2));
+ emit test1->sigTest2(50);
+ QVERIFY(test2->m_number == 50);
+}
+
+void KisSignalAutoConnectionTest::testDestroyedObject()
+{
+ QScopedPointer<TestClass> test1(new TestClass());
+ QScopedPointer<TestClass> test2(new TestClass());
+ KisSignalAutoConnectionsStore conn;
+ conn.addConnection(test1.data(), QOverload<int>::of(&TestClass::sigTest2),
+ test2.data(), QOverload<int>::of(&TestClass::slotTest2));
+ emit test1->sigTest2(10);
+ QVERIFY(test2->m_number == 10);
+ test2.reset(0);
+ conn.clear();
+}
+
+TestClass::TestClass(QObject *parent)
+ : QObject(parent)
+ , m_test1Called(false)
+ , m_str1()
+ , m_str2()
+ , m_number(0)
+{
+}
+
+TestClass::~TestClass()
+{
+}
+
+void TestClass::slotTest1()
+{
+ m_test1Called = true;
+}
+
+void TestClass::slotTest2(const QString &arg1, const QString &arg2)
+{
+ m_str1 = arg1;
+ m_str2 = arg2;
+}
+
+void TestClass::slotTest2(const QString &arg)
+{
+ m_str1 = arg;
+ m_str2 = QString();
+}
+
+void TestClass::slotTest2(int arg)
+{
+ m_number = arg;
+}
+
+QTEST_MAIN(KisSignalAutoConnectionTest)
diff --git a/libs/global/tests/KisSignalAutoConnectionTest.h b/libs/global/tests/KisSignalAutoConnectionTest.h
new file mode 100644
index 0000000000..9c5068dfbd
--- /dev/null
+++ b/libs/global/tests/KisSignalAutoConnectionTest.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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_SIGNAL_AUTO_CONNECTION_TEST_H_
+#define KIS_SIGNAL_AUTO_CONNECTION_TEST_H_
+
+#include <QtTest>
+
+class KisSignalAutoConnectionTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testMacroConnection();
+ void testMemberFunctionConnection();
+ void testOverloadConnection();
+ void testSignalToSignalConnection();
+ void testDestroyedObject();
+};
+
+class TestClass : public QObject
+{
+ Q_OBJECT
+public:
+ TestClass(QObject *parent = 0);
+ ~TestClass() override;
+
+Q_SIGNALS:
+ void sigTest1();
+ void sigTest2(const QString &arg1, const QString &arg2);
+ void sigTest2(int arg);
+
+public Q_SLOTS:
+ void slotTest1();
+ void slotTest2(const QString &arg1, const QString &arg2);
+ void slotTest2(const QString &arg);
+ void slotTest2(int arg);
+
+public:
+ bool m_test1Called;
+ QString m_str1;
+ QString m_str2;
+ int m_number;
+};
+
+#endif // KIS_SIGNAL_AUTO_CONNECTION_TEST_H_
diff --git a/libs/image/floodfill/kis_fill_interval_map.cpp b/libs/image/floodfill/kis_fill_interval_map.cpp
index 3b93468ac5..05ea720160 100644
--- a/libs/image/floodfill/kis_fill_interval_map.cpp
+++ b/libs/image/floodfill/kis_fill_interval_map.cpp
@@ -1,166 +1,174 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_fill_interval_map.h"
#include "kis_fill_interval_map_p.h"
#include "kis_fill_sanity_checks.h"
KisFillIntervalMap::KisFillIntervalMap()
: m_d(new Private)
{
}
KisFillIntervalMap::~KisFillIntervalMap()
{
}
void KisFillIntervalMap::insertInterval(const KisFillInterval &interval)
{
Private::GlobalMap::iterator rowMap = m_d->map.find(interval.row);
if (rowMap == m_d->map.end()) {
rowMap = m_d->map.insert(interval.row, Private::LineIntervalMap());
}
rowMap->insert(interval.start, interval);
}
void KisFillIntervalMap::cropInterval(KisFillInterval *interval)
{
Private::IteratorRange range;
range = m_d->findFirstIntersectingInterval(*interval);
Private::LineIntervalMap::iterator it = range.beginIt;
while (interval->isValid() && it != range.endIt) {
bool needsIncrement = true;
if (it->start <= interval->start && it->end >= interval->start) {
int savedIntervalStart = interval->start;
interval->start = it->end + 1;
/**
* It might happen that we need to split a backward
* interval into two pieces
*/
if (it->end > interval->end) {
KisFillInterval newInterval(interval->end + 1, it->end, it->row);
range.rowMapIt->insert(newInterval.start, newInterval);
}
it->end = savedIntervalStart - 1;
/**
* It might also happen that the backward interval is
* fully eaten by the forward interval. This is possible
* only in case the BW-interval was generated by the
* strictly adjacent FW-interval, that is (it->start ==
* interval->start)
*/
if (!it->isValid()) {
it = range.rowMapIt->erase(it);
needsIncrement = false;
}
} else if (it->start <= interval->end && it->end >= interval->end) {
int savedIntervalEnd = interval->end;
interval->end = it->start - 1;
it->start = savedIntervalEnd + 1;
/**
* The BW-interval is eaten by the FW-interval. See a
* comment above
*/
if (!it->isValid()) {
it = range.rowMapIt->erase(it);
needsIncrement = false;
}
} else if (it->start > interval->end) {
break;
}
#ifdef ENABLE_FILL_SANITY_CHECKS
else if (it->start > interval->start && it->end < interval->end) {
SANITY_ASSERT_MSG(0, "FATAL: The backward interval cannot fully reside inside the forward interval");
- it->invalidate();
interval->invalidate();
+ it->invalidate();
+ it = range.rowMapIt->erase(it);
+ needsIncrement = false;
}
- SANITY_ASSERT_MSG(it == range.endIt || it->isValid(), "FATAL: The backward interval cannot become invalid during the crop action");
-
+ // The code above should have removed the invalidated backward interval,
+ // just verify that
+ KIS_SAFE_ASSERT_RECOVER((it == range.endIt || it->isValid()) &&
+ "FATAL: The backward interval cannot become "
+ "invalid during the crop action") {
+ it = range.rowMapIt->erase(it);
+ needsIncrement = false;
+ }
#endif /* ENABLE_FILL_SANITY_CHECKS */
if (needsIncrement) {
it++;
}
}
}
KisFillIntervalMap::Private::IteratorRange
KisFillIntervalMap::Private::findFirstIntersectingInterval(const KisFillInterval &interval)
{
Private::GlobalMap::iterator rowMap = map.find(interval.row);
if (rowMap == map.end()) {
return IteratorRange();
}
LineIntervalMap::iterator it = rowMap->begin();
LineIntervalMap::iterator end = rowMap->end();
while(it != end) {
if (it->end < interval.start) {
++it;
} else if (it->start > interval.end) {
it = end;
break;
} else {
break;
}
}
return IteratorRange(it, end, rowMap);
}
QStack<KisFillInterval> KisFillIntervalMap::fetchAllIntervals(int rowCorrection) const
{
QStack<KisFillInterval> intervals;
Private::GlobalMap::const_iterator rowMapIt = m_d->map.constBegin();
Private::GlobalMap::const_iterator rowMapEndIt = m_d->map.constEnd();
while (rowMapIt != rowMapEndIt) {
Private::LineIntervalMap::const_iterator it = rowMapIt->constBegin();
Private::LineIntervalMap::const_iterator end = rowMapIt->constEnd();
while(it != end) {
KisFillInterval interval = *it;
interval.row += rowCorrection;
intervals.append(interval);
++it;
}
++rowMapIt;
}
return intervals;
}
void KisFillIntervalMap::clear()
{
m_d->map.clear();
}
diff --git a/libs/image/floodfill/kis_fill_sanity_checks.h b/libs/image/floodfill/kis_fill_sanity_checks.h
index 49d47efb12..2de42c9bda 100644
--- a/libs/image/floodfill/kis_fill_sanity_checks.h
+++ b/libs/image/floodfill/kis_fill_sanity_checks.h
@@ -1,41 +1,41 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_FILL_SANITY_CHECKS_H
#define __KIS_FILL_SANITY_CHECKS_H
#define ENABLE_FILL_SANITY_CHECKS
//#define ENABLE_CHECKS_FOR_TESTING
#ifdef ENABLE_FILL_SANITY_CHECKS
#ifdef ENABLE_CHECKS_FOR_TESTING
#include <stdexcept>
#define SANITY_ASSERT_MSG(cond, msg) ((!(cond)) ? throw std::invalid_argument(msg) : qt_noop())
#else
-#define SANITY_ASSERT_MSG(cond, msg) Q_ASSERT((cond))
+#define SANITY_ASSERT_MSG(cond, msg) KIS_SAFE_ASSERT_RECOVER_NOOP((cond))
#endif /* ENABLE_CHECKS_FOR_TESTING */
#else
#define SANITY_ASSERT_MSG(cond, msg)
#endif /* ENABLE_FILL_SANITY_CHECKS */
#endif /* __KIS_FILL_SANITY_CHECKS_H */
diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp
index db91b1ce07..750d827c09 100644
--- a/libs/image/floodfill/kis_scanline_fill.cpp
+++ b/libs/image/floodfill/kis_scanline_fill.cpp
@@ -1,728 +1,732 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_scanline_fill.h"
#include <KoAlwaysInline.h>
#include <QStack>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include "kis_image.h"
#include "kis_fill_interval_map.h"
#include "kis_pixel_selection.h"
#include "kis_random_accessor_ng.h"
#include "kis_fill_sanity_checks.h"
template <class BaseClass>
class CopyToSelection : public BaseClass
{
public:
typedef KisRandomConstAccessorSP SourceAccessorType;
SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) {
return device->createRandomConstAccessorNG(0, 0);
}
public:
void setDestinationSelection(KisPaintDeviceSP pixelSelection) {
m_pixelSelection = pixelSelection;
m_it = m_pixelSelection->createRandomAccessorNG(0,0);
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(dstPtr);
m_it->moveTo(x, y);
*m_it->rawData() = opacity;
}
private:
KisPaintDeviceSP m_pixelSelection;
KisRandomAccessorSP m_it;
};
template <class BaseClass>
class FillWithColor : public BaseClass
{
public:
typedef KisRandomAccessorSP SourceAccessorType;
SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) {
return device->createRandomAccessorNG(0, 0);
}
public:
FillWithColor() : m_pixelSize(0) {}
void setFillColor(const KoColor &sourceColor) {
m_sourceColor = sourceColor;
m_pixelSize = sourceColor.colorSpace()->pixelSize();
m_data = m_sourceColor.data();
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(x);
Q_UNUSED(y);
if (opacity == MAX_SELECTED) {
memcpy(dstPtr, m_data, m_pixelSize);
}
}
private:
KoColor m_sourceColor;
const quint8 *m_data;
int m_pixelSize;
};
template <class BaseClass>
class FillWithColorExternal : public BaseClass
{
public:
typedef KisRandomConstAccessorSP SourceAccessorType;
SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) {
return device->createRandomConstAccessorNG(0, 0);
}
public:
void setDestinationDevice(KisPaintDeviceSP device) {
m_externalDevice = device;
m_it = m_externalDevice->createRandomAccessorNG(0,0);
}
void setFillColor(const KoColor &sourceColor) {
m_sourceColor = sourceColor;
m_pixelSize = sourceColor.colorSpace()->pixelSize();
m_data = m_sourceColor.data();
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(dstPtr);
m_it->moveTo(x, y);
if (opacity == MAX_SELECTED) {
memcpy(m_it->rawData(), m_data, m_pixelSize);
}
}
private:
KisPaintDeviceSP m_externalDevice;
KisRandomAccessorSP m_it;
KoColor m_sourceColor;
const quint8 *m_data;
int m_pixelSize;
};
class DifferencePolicySlow
{
public:
ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) {
m_colorSpace = device->colorSpace();
m_srcPixel = srcPixel;
m_srcPixelPtr = m_srcPixel.data();
m_threshold = threshold;
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
if (m_threshold == 1) {
if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) {
return 0;
}
return quint8_MAX;
}
else {
return m_colorSpace->difference(m_srcPixelPtr, pixelPtr);
}
}
private:
const KoColorSpace *m_colorSpace;
KoColor m_srcPixel;
const quint8 *m_srcPixelPtr;
int m_threshold;
};
template <typename SrcPixelType>
class DifferencePolicyOptimized
{
typedef SrcPixelType HashKeyType;
typedef QHash<HashKeyType, quint8> HashType;
public:
ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) {
m_colorSpace = device->colorSpace();
m_srcPixel = srcPixel;
m_srcPixelPtr = m_srcPixel.data();
m_threshold = threshold;
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
HashKeyType key = *reinterpret_cast<HashKeyType*>(pixelPtr);
quint8 result;
typename HashType::iterator it = m_differences.find(key);
if (it != m_differences.end()) {
result = *it;
} else {
if (m_threshold == 1) {
if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) {
result = 0;
}
else {
result = quint8_MAX;
}
}
else {
result = m_colorSpace->difference(m_srcPixelPtr, pixelPtr);
}
m_differences.insert(key, result);
}
return result;
}
private:
HashType m_differences;
const KoColorSpace *m_colorSpace;
KoColor m_srcPixel;
const quint8 *m_srcPixelPtr;
int m_threshold;
};
template <bool useSmoothSelection,
class DifferencePolicy,
template <class> class PixelFiller>
class SelectionPolicy : public PixelFiller<DifferencePolicy>
{
public:
typename PixelFiller<DifferencePolicy>::SourceAccessorType m_srcIt;
public:
SelectionPolicy(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold)
: m_threshold(threshold)
{
this->initDifferences(device, srcPixel, threshold);
m_srcIt = this->createSourceDeviceAccessor(device);
}
ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) {
quint8 diff = this->calculateDifference(pixelPtr);
if (!useSmoothSelection) {
return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED;
} else {
quint8 selectionValue = qMax(0, m_threshold - diff);
quint8 result = MIN_SELECTED;
if (selectionValue > 0) {
qreal selectionNorm = qreal(selectionValue) / m_threshold;
result = MAX_SELECTED * selectionNorm;
}
return result;
}
}
private:
int m_threshold;
};
class IsNonNullPolicySlow
{
public:
ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) {
Q_UNUSED(srcPixel);
m_pixelSize = device->pixelSize();
m_testPixel.resize(m_pixelSize);
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
if (memcmp(m_testPixel.data(), pixelPtr, m_pixelSize) == 0) {
return 0;
}
return quint8_MAX;
}
private:
int m_pixelSize;
QByteArray m_testPixel;
};
template <typename SrcPixelType>
class IsNonNullPolicyOptimized
{
public:
ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) {
Q_UNUSED(device);
Q_UNUSED(srcPixel);
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
SrcPixelType *pixel = reinterpret_cast<SrcPixelType*>(pixelPtr);
return *pixel == 0;
}
};
class GroupSplitPolicy
{
public:
typedef KisRandomAccessorSP SourceAccessorType;
SourceAccessorType m_srcIt;
public:
GroupSplitPolicy(KisPaintDeviceSP scribbleDevice,
KisPaintDeviceSP groupMapDevice,
qint32 groupIndex,
quint8 referenceValue, int threshold)
: m_threshold(threshold),
m_groupIndex(groupIndex),
m_referenceValue(referenceValue)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0);
m_srcIt = scribbleDevice->createRandomAccessorNG(0,0);
m_groupMapIt = groupMapDevice->createRandomAccessorNG(0,0);
}
ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) {
// TODO: either threshold should always be null, or there should be a special
// case for *pixelPtr == 0, which is different from all the other groups,
// whatever the threshold is
int diff = qAbs(int(*pixelPtr) - m_referenceValue);
return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED;
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(opacity);
// erase the scribble
*dstPtr = 0;
// write group index into the map
m_groupMapIt->moveTo(x, y);
qint32 *groupMapPtr = reinterpret_cast<qint32*>(m_groupMapIt->rawData());
if (*groupMapPtr != 0) {
dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0);
*groupMapPtr = m_groupIndex;
}
private:
int m_threshold;
qint32 m_groupIndex;
quint8 m_referenceValue;
KisRandomAccessorSP m_groupMapIt;
};
struct Q_DECL_HIDDEN KisScanlineFill::Private
{
KisPaintDeviceSP device;
KisRandomAccessorSP it;
QPoint startPoint;
QRect boundingRect;
int threshold;
int rowIncrement;
KisFillIntervalMap backwardMap;
QStack<KisFillInterval> forwardStack;
inline void swapDirection() {
rowIncrement *= -1;
- SANITY_ASSERT_MSG(forwardStack.isEmpty(),
- "FATAL: the forward stack must be empty "
- "on a direction swap");
+ KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() &&
+ "FATAL: the forward stack must be empty "
+ "on a direction swap");
forwardStack = QStack<KisFillInterval>(backwardMap.fetchAllIntervals(rowIncrement));
backwardMap.clear();
}
};
KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect)
: m_d(new Private)
{
m_d->device = device;
m_d->it = device->createRandomAccessorNG(startPoint.x(), startPoint.y());
m_d->startPoint = startPoint;
m_d->boundingRect = boundingRect;
m_d->rowIncrement = 1;
m_d->threshold = 0;
}
KisScanlineFill::~KisScanlineFill()
{
}
void KisScanlineFill::setThreshold(int threshold)
{
m_d->threshold = threshold;
}
template <class T>
void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy)
{
int x;
int endX;
int columnIncrement;
int *intervalBorder;
int *backwardIntervalBorder;
KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow);
if (extendRight) {
x = currentInterval->end;
endX = m_d->boundingRect.right();
if (x >= endX) return;
columnIncrement = 1;
intervalBorder = &currentInterval->end;
backwardInterval.start = currentInterval->end + 1;
backwardIntervalBorder = &backwardInterval.end;
} else {
x = currentInterval->start;
endX = m_d->boundingRect.left();
if (x <= endX) return;
columnIncrement = -1;
intervalBorder = &currentInterval->start;
backwardInterval.end = currentInterval->start - 1;
backwardIntervalBorder = &backwardInterval.start;
}
do {
x += columnIncrement;
pixelPolicy.m_srcIt->moveTo(x, srcRow);
quint8 *pixelPtr = const_cast<quint8*>(pixelPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast
quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr);
if (opacity) {
*intervalBorder = x;
*backwardIntervalBorder = x;
pixelPolicy.fillPixel(pixelPtr, opacity, x, srcRow);
} else {
break;
}
} while (x != endX);
if (backwardInterval.isValid()) {
m_d->backwardMap.insertInterval(backwardInterval);
}
}
template <class T>
void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy)
{
m_d->backwardMap.cropInterval(&interval);
if (!interval.isValid()) return;
int firstX = interval.start;
int lastX = interval.end;
int x = firstX;
int row = interval.row;
int nextRow = row + rowIncrement;
KisFillInterval currentForwardInterval;
int numPixelsLeft = 0;
quint8 *dataPtr = 0;
const int pixelSize = m_d->device->pixelSize();
while(x <= lastX) {
// a bit of optimzation for not calling slow random accessor
// methods too often
if (numPixelsLeft <= 0) {
pixelPolicy.m_srcIt->moveTo(x, row);
numPixelsLeft = pixelPolicy.m_srcIt->numContiguousColumns(x) - 1;
dataPtr = const_cast<quint8*>(pixelPolicy.m_srcIt->rawDataConst());
} else {
numPixelsLeft--;
dataPtr += pixelSize;
}
quint8 *pixelPtr = dataPtr;
quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr);
if (opacity) {
if (!currentForwardInterval.isValid()) {
currentForwardInterval.start = x;
currentForwardInterval.end = x;
currentForwardInterval.row = nextRow;
} else {
currentForwardInterval.end = x;
}
pixelPolicy.fillPixel(pixelPtr, opacity, x, row);
if (x == firstX) {
extendedPass(&currentForwardInterval, row, false, pixelPolicy);
}
if (x == lastX) {
extendedPass(&currentForwardInterval, row, true, pixelPolicy);
}
} else {
if (currentForwardInterval.isValid()) {
m_d->forwardStack.push(currentForwardInterval);
currentForwardInterval.invalidate();
}
}
x++;
}
if (currentForwardInterval.isValid()) {
m_d->forwardStack.push(currentForwardInterval);
}
}
template <class T>
void KisScanlineFill::runImpl(T &pixelPolicy)
{
KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty());
KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y());
m_d->forwardStack.push(startInterval);
/**
* In the end of the first pass we should add an interval
* containing the starting pixel, but directed into the opposite
* direction. We cannot do it in the very beginning because the
* intervals are offset by 1 pixel during every swap operation.
*/
bool firstPass = true;
while (!m_d->forwardStack.isEmpty()) {
while (!m_d->forwardStack.isEmpty()) {
KisFillInterval interval = m_d->forwardStack.pop();
if (interval.row > m_d->boundingRect.bottom() ||
interval.row < m_d->boundingRect.top()) {
continue;
}
processLine(interval, m_d->rowIncrement, pixelPolicy);
}
m_d->swapDirection();
if (firstPass) {
startInterval.row--;
m_d->forwardStack.push(startInterval);
firstPass = false;
}
}
}
-void KisScanlineFill::fillColor(const KoColor &fillColor)
+void KisScanlineFill::fillColor(const KoColor &originalFillColor)
{
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace());
+ KoColor fillColor(originalFillColor);
+ fillColor.convertTo(m_d->device->colorSpace());
const int pixelSize = m_d->device->pixelSize();
if (pixelSize == 1) {
SelectionPolicy<false, DifferencePolicyOptimized<quint8>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<false, DifferencePolicyOptimized<quint16>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<false, DifferencePolicyOptimized<quint32>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<false, DifferencePolicyOptimized<quint64>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else {
SelectionPolicy<false, DifferencePolicySlow, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
}
}
-void KisScanlineFill::fillColor(const KoColor &fillColor, KisPaintDeviceSP externalDevice)
+void KisScanlineFill::fillColor(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice)
{
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace());
+ KoColor fillColor(originalFillColor);
+ fillColor.convertTo(m_d->device->colorSpace());
const int pixelSize = m_d->device->pixelSize();
if (pixelSize == 1) {
SelectionPolicy<false, DifferencePolicyOptimized<quint8>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<false, DifferencePolicyOptimized<quint16>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<false, DifferencePolicyOptimized<quint32>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<false, DifferencePolicyOptimized<quint64>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else {
SelectionPolicy<false, DifferencePolicySlow, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
}
}
void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection)
{
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace());
const int pixelSize = m_d->device->pixelSize();
if (pixelSize == 1) {
SelectionPolicy<true, DifferencePolicyOptimized<quint8>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<true, DifferencePolicyOptimized<quint16>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<true, DifferencePolicyOptimized<quint32>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<true, DifferencePolicyOptimized<quint64>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else {
SelectionPolicy<true, DifferencePolicySlow, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
}
}
void KisScanlineFill::clearNonZeroComponent()
{
const int pixelSize = m_d->device->pixelSize();
KoColor srcColor(Qt::transparent, m_d->device->colorSpace());
if (pixelSize == 1) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint8>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint16>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint32>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint64>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else {
SelectionPolicy<false, IsNonNullPolicySlow, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
}
}
void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1);
KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4);
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
const quint8 referenceValue = *it->rawDataConst();
GroupSplitPolicy policy(m_d->device, groupMapDevice, groupIndex, referenceValue, m_d->threshold);
runImpl(policy);
}
void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval)
{
KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace());
KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace());
SelectionPolicy<false, DifferencePolicyOptimized<quint32>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
processLine(processInterval, 1, policy);
}
QVector<KisFillInterval> KisScanlineFill::testingGetForwardIntervals() const
{
return QVector<KisFillInterval>(m_d->forwardStack);
}
KisFillIntervalMap* KisScanlineFill::testingGetBackwardIntervals() const
{
return &m_d->backwardMap;
}
diff --git a/libs/image/generator/kis_generator_layer.cpp b/libs/image/generator/kis_generator_layer.cpp
index 1f06bee4c6..e4cb63bf13 100644
--- a/libs/image/generator/kis_generator_layer.cpp
+++ b/libs/image/generator/kis_generator_layer.cpp
@@ -1,190 +1,189 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_generator_layer.h"
#include <klocalizedstring.h>
#include "kis_debug.h"
#include <KoIcon.h>
#include <kis_icon.h>
#include "kis_selection.h"
#include "filter/kis_filter_configuration.h"
#include "kis_processing_information.h"
#include "generator/kis_generator_registry.h"
#include "generator/kis_generator.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_thread_safe_signal_compressor.h"
#include "kis_recalculate_generator_layer_job.h"
#define UPDATE_DELAY 100 /*ms */
struct Q_DECL_HIDDEN KisGeneratorLayer::Private
{
Private()
: updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::FIRST_INACTIVE)
{
}
KisThreadSafeSignalCompressor updateSignalCompressor;
QRect preparedRect;
KisFilterConfigurationSP preparedForFilter;
};
KisGeneratorLayer::KisGeneratorLayer(KisImageWSP image,
const QString &name,
KisFilterConfigurationSP kfc,
KisSelectionSP selection)
: KisSelectionBasedLayer(image, name, selection, kfc, true),
m_d(new Private)
{
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
- update();
}
KisGeneratorLayer::KisGeneratorLayer(const KisGeneratorLayer& rhs)
: KisSelectionBasedLayer(rhs),
m_d(new Private)
{
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
}
KisGeneratorLayer::~KisGeneratorLayer()
{
}
void KisGeneratorLayer::setFilter(KisFilterConfigurationSP filterConfig)
{
KisSelectionBasedLayer::setFilter(filterConfig);
m_d->preparedRect = QRect();
update();
}
void KisGeneratorLayer::slotDelayedStaticUpdate()
{
/**
* The mask might have been deleted from the layers stack in the
* meanwhile. Just ignore the updates in the case.
*/
KisLayerSP parentLayer(qobject_cast<KisLayer*>(parent().data()));
if (!parentLayer) return;
KisImageSP image = parentLayer->image();
if (image) {
image->addSpontaneousJob(new KisRecalculateGeneratorLayerJob(KisGeneratorLayerSP(this)));
}
}
void KisGeneratorLayer::update()
{
KisImageSP image = this->image().toStrongRef();
const QRect updateRect = extent() | image->bounds();
KisFilterConfigurationSP filterConfig = filter();
KIS_SAFE_ASSERT_RECOVER_RETURN(filterConfig);
if (filterConfig != m_d->preparedForFilter) {
resetCache();
}
const QRegion processRegion(QRegion(updateRect) - m_d->preparedRect);
if (processRegion.isEmpty()) return;
KisGeneratorSP f = KisGeneratorRegistry::instance()->value(filterConfig->name());
KIS_SAFE_ASSERT_RECOVER_RETURN(f);
KisPaintDeviceSP originalDevice = original();
QVector<QRect> dirtyRegion;
Q_FOREACH (const QRect &rc, processRegion.rects()) {
KisProcessingInformation dstCfg(originalDevice,
rc.topLeft(),
KisSelectionSP());
f->generate(dstCfg, rc.size(), filterConfig.data());
dirtyRegion << rc;
}
m_d->preparedRect = updateRect;
m_d->preparedForFilter = filterConfig;
// HACK ALERT!!!
// this avoids cyclic loop with KisRecalculateGeneratorLayerJob::run()
KisSelectionBasedLayer::setDirty(dirtyRegion);
}
bool KisGeneratorLayer::accept(KisNodeVisitor & v)
{
return v.visit(this);
}
void KisGeneratorLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
QIcon KisGeneratorLayer::icon() const
{
return KisIconUtils::loadIcon("fillLayer");
}
KisBaseNode::PropertyList KisGeneratorLayer::sectionModelProperties() const
{
KisFilterConfigurationSP filterConfig = filter();
KisBaseNode::PropertyList l = KisLayer::sectionModelProperties();
l << KisBaseNode::Property(KoID("generator", i18n("Generator")),
KisGeneratorRegistry::instance()->value(filterConfig->name())->name());
return l;
}
void KisGeneratorLayer::setX(qint32 x)
{
KisSelectionBasedLayer::setX(x);
m_d->preparedRect = QRect();
m_d->updateSignalCompressor.start();
}
void KisGeneratorLayer::setY(qint32 y)
{
KisSelectionBasedLayer::setY(y);
m_d->preparedRect = QRect();
m_d->updateSignalCompressor.start();
}
void KisGeneratorLayer::resetCache()
{
KisSelectionBasedLayer::resetCache();
m_d->preparedRect = QRect();
m_d->updateSignalCompressor.start();
}
void KisGeneratorLayer::setDirty(const QVector<QRect> &rects)
{
KisSelectionBasedLayer::setDirty(rects);
m_d->updateSignalCompressor.start();
}
diff --git a/libs/image/kis_async_merger.cpp b/libs/image/kis_async_merger.cpp
index 3ebb857e2e..c0790e8986 100644
--- a/libs/image/kis_async_merger.cpp
+++ b/libs/image/kis_async_merger.cpp
@@ -1,380 +1,380 @@
/* Copyright (c) Dmitry Kazakov <dimula73@gmail.com>, 2009
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_async_merger.h"
#include <kis_debug.h>
#include <QBitArray>
#include <KoChannelInfo.h>
#include <KoCompositeOpRegistry.h>
#include "kis_node_visitor.h"
#include "kis_painter.h"
#include "kis_layer.h"
#include "kis_group_layer.h"
#include "kis_adjustment_layer.h"
#include "generator/kis_generator_layer.h"
#include "kis_external_layer_iface.h"
#include "kis_paint_layer.h"
#include "filter/kis_filter.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "kis_selection.h"
#include "kis_clone_layer.h"
#include "kis_processing_information.h"
#include "kis_busy_progress_indicator.h"
#include "kis_merge_walker.h"
#include "kis_refresh_subtree_walker.h"
#include "kis_abstract_projection_plane.h"
//#define DEBUG_MERGER
#ifdef DEBUG_MERGER
#define DEBUG_NODE_ACTION(message, type, leaf, rect) \
qDebug() << message << type << ":" << leaf->node()->name() << rect
#else
#define DEBUG_NODE_ACTION(message, type, leaf, rect)
#endif
class KisUpdateOriginalVisitor : public KisNodeVisitor
{
public:
KisUpdateOriginalVisitor(const QRect &updateRect, KisPaintDeviceSP projection, const QRect &cropRect)
: m_updateRect(updateRect),
m_cropRect(cropRect),
m_projection(projection)
{
}
~KisUpdateOriginalVisitor() override {
}
public:
using KisNodeVisitor::visit;
bool visit(KisAdjustmentLayer* layer) override {
if (!layer->visible()) return true;
if (!m_projection) {
warnImage << "ObligeChild mechanism has been activated for "
"an adjustment layer! Do nothing...";
layer->original()->clear();
return true;
}
const QRect originalUpdateRect =
layer->projectionPlane()->needRectForOriginal(m_updateRect);
KisPaintDeviceSP originalDevice = layer->original();
originalDevice->clear(originalUpdateRect);
const QRect applyRect = originalUpdateRect & m_projection->extent();
// If the intersection of the updaterect and the projection extent is
// null, we are finish here.
if(applyRect.isNull()) return true;
KisFilterConfigurationSP filterConfig = layer->filter();
if (!filterConfig) {
/**
* When an adjustment layer is just created, it may have no
* filter inside. Then the layer has work as a pass-through
* node. Just copy the merged data to the layer's original.
*/
KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect);
return true;
}
KisSelectionSP selection = layer->fetchComposedInternalSelection(applyRect);
const QRect filterRect = selection ? applyRect & selection->selectedRect() : applyRect;
KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name());
if (!filter) return false;
KisPaintDeviceSP dstDevice = originalDevice;
if (selection) {
dstDevice = new KisPaintDevice(originalDevice->colorSpace());
}
if (!filterRect.isEmpty()) {
KIS_ASSERT_RECOVER_NOOP(layer->busyProgressIndicator());
layer->busyProgressIndicator()->update();
// We do not create a transaction here, as srcDevice != dstDevice
filter->process(m_projection, dstDevice, 0, filterRect, filterConfig.data(), 0);
}
if (selection) {
KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect);
KisPainter::copyAreaOptimized(filterRect.topLeft(), dstDevice, originalDevice, filterRect, selection);
}
return true;
}
bool visit(KisExternalLayer*) override {
return true;
}
bool visit(KisGeneratorLayer*) override {
return true;
}
bool visit(KisPaintLayer*) override {
return true;
}
bool visit(KisGroupLayer*) override {
return true;
}
bool visit(KisCloneLayer *layer) override {
QRect emptyRect;
KisRefreshSubtreeWalker walker(emptyRect);
KisAsyncMerger merger;
KisLayerSP srcLayer = layer->copyFrom();
QRect srcRect = m_updateRect.translated(-layer->x(), -layer->y());
QRegion prepareRegion(srcRect);
prepareRegion -= m_cropRect;
/**
* If a clone has complicated masks, we should prepare additional
* source area to ensure the rect is prepared.
*/
QRect needRectOnSource = layer->needRectOnSourceForMasks(srcRect);
if (!needRectOnSource.isEmpty()) {
prepareRegion += needRectOnSource;
}
Q_FOREACH (const QRect &rect, prepareRegion.rects()) {
walker.collectRects(srcLayer, rect);
merger.startMerge(walker, false);
}
return true;
}
bool visit(KisNode*) override {
return true;
}
bool visit(KisFilterMask*) override {
return true;
}
bool visit(KisTransformMask*) override {
return true;
}
bool visit(KisTransparencyMask*) override {
return true;
}
bool visit(KisSelectionMask*) override {
return true;
}
bool visit(KisColorizeMask*) override {
return true;
}
private:
QRect m_updateRect;
QRect m_cropRect;
KisPaintDeviceSP m_projection;
};
/*********************************************************************/
/* KisAsyncMerger */
/*********************************************************************/
void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
KisMergeWalker::LeafStack &leafStack = walker.leafStack();
const bool useTempProjections = walker.needRectVaries();
while(!leafStack.isEmpty()) {
KisMergeWalker::JobItem item = leafStack.pop();
KisProjectionLeafSP currentLeaf = item.m_leaf;
/**
* In some unidentified cases teh nodes might be removed
* while the updates are still running. We have no proof
* of it yet, so just add a safety assert here.
*/
KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf);
KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->node());
// All the masks should be filtered by the walkers
KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->isLayer());
QRect applyRect = item.m_applyRect;
if (currentLeaf->isRoot()) {
currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
continue;
}
if(item.m_position & KisMergeWalker::N_EXTRA) {
// The type of layers that will not go to projection.
DEBUG_NODE_ACTION("Updating", "N_EXTRA", currentLeaf, applyRect);
KisUpdateOriginalVisitor originalVisitor(applyRect,
m_currentProjection,
walker.cropRect());
currentLeaf->accept(originalVisitor);
currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node());
continue;
}
if (!m_currentProjection) {
setupProjection(currentLeaf, applyRect, useTempProjections);
}
KisUpdateOriginalVisitor originalVisitor(applyRect,
m_currentProjection,
walker.cropRect());
if(item.m_position & KisMergeWalker::N_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentLeaf, applyRect);
- if (currentLeaf->visible()) {
+ if (currentLeaf->visible() || currentLeaf->hasClones()) {
currentLeaf->accept(originalVisitor);
currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
}
}
else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentLeaf, applyRect);
if(currentLeaf->dependsOnLowerNodes()) {
- if (currentLeaf->visible()) {
+ if (currentLeaf->visible() || currentLeaf->hasClones()) {
currentLeaf->accept(originalVisitor);
currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node());
}
}
}
else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentLeaf, applyRect);
- if (currentLeaf->visible()) {
+ if (currentLeaf->visible() || currentLeaf->hasClones()) {
currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
}
}
else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ {
DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentLeaf, applyRect);
/* nothing to do */
}
compositeWithProjection(currentLeaf, applyRect);
if(item.m_position & KisMergeWalker::N_TOPMOST) {
writeProjection(currentLeaf, useTempProjections, applyRect);
resetProjection();
}
// FIXME: remove it from the inner loop and/or change to a warning!
Q_ASSERT(currentLeaf->projection()->defaultBounds()->currentLevelOfDetail() ==
walker.levelOfDetail());
}
if(notifyClones) {
doNotifyClones(walker);
}
if(m_currentProjection) {
warnImage << "BUG: The walker hasn't reached the root layer!";
warnImage << " Start node:" << walker.startNode() << "Requested rect:" << walker.requestedRect();
warnImage << " An inconsistency in the walkers occurred!";
warnImage << " Please report a bug describing how you got this message.";
// reset projection to avoid artifacts in next merges and allow people to work further
resetProjection();
}
}
void KisAsyncMerger::resetProjection() {
m_currentProjection = 0;
m_finalProjection = 0;
}
void KisAsyncMerger::setupProjection(KisProjectionLeafSP currentLeaf, const QRect& rect, bool useTempProjection) {
KisPaintDeviceSP parentOriginal = currentLeaf->parent()->original();
if (parentOriginal != currentLeaf->projection()) {
if (useTempProjection) {
if(!m_cachedPaintDevice)
m_cachedPaintDevice = new KisPaintDevice(parentOriginal->colorSpace());
m_currentProjection = m_cachedPaintDevice;
m_currentProjection->prepareClone(parentOriginal);
m_finalProjection = parentOriginal;
}
else {
parentOriginal->clear(rect);
m_finalProjection = m_currentProjection = parentOriginal;
}
}
else {
/**
* It happened so that our parent uses our own projection as
* its original. It means obligeChild mechanism works.
* We won't initialise m_currentProjection. This will cause
* writeProjection() and compositeWithProjection() do nothing
* when called.
*/
/* NOP */
}
}
void KisAsyncMerger::writeProjection(KisProjectionLeafSP topmostLeaf, bool useTempProjection, const QRect &rect) {
Q_UNUSED(useTempProjection);
Q_UNUSED(topmostLeaf);
if (!m_currentProjection) return;
if(m_currentProjection != m_finalProjection) {
KisPainter::copyAreaOptimized(rect.topLeft(), m_currentProjection, m_finalProjection, rect);
}
DEBUG_NODE_ACTION("Writing projection", "", topmostLeaf->parent(), rect);
}
bool KisAsyncMerger::compositeWithProjection(KisProjectionLeafSP leaf, const QRect &rect) {
if (!m_currentProjection) return true;
if (!leaf->visible()) return true;
KisPainter gc(m_currentProjection);
leaf->projectionPlane()->apply(&gc, rect);
DEBUG_NODE_ACTION("Compositing projection", "", leaf, rect);
return true;
}
void KisAsyncMerger::doNotifyClones(KisBaseRectsWalker &walker) {
KisBaseRectsWalker::CloneNotificationsVector &vector =
walker.cloneNotifications();
KisBaseRectsWalker::CloneNotificationsVector::iterator it;
for(it = vector.begin(); it != vector.end(); ++it) {
(*it).notify();
}
}
diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp
index bc86280496..fa20321ca8 100644
--- a/libs/image/kis_base_node.cpp
+++ b/libs/image/kis_base_node.cpp
@@ -1,441 +1,446 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_base_node.h"
#include <klocalizedstring.h>
#include <kis_image.h>
#include <kis_icon.h>
#include <KoProperties.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include "kis_paint_device.h"
#include "kis_layer_properties_icons.h"
#include "kis_scalar_keyframe_channel.h"
struct Q_DECL_HIDDEN KisBaseNode::Private
{
QString compositeOp;
KoProperties properties;
KisBaseNode::Property hack_visible; //HACK
QUuid id;
QMap<QString, KisKeyframeChannel*> keyframeChannels;
QScopedPointer<KisScalarKeyframeChannel> opacityChannel;
bool systemLocked;
bool collapsed;
bool supportsLodMoves;
bool animated;
bool useInTimeline;
KisImageWSP image;
Private(KisImageWSP image)
: id(QUuid::createUuid())
, systemLocked(false)
, collapsed(false)
, supportsLodMoves(false)
, animated(false)
, useInTimeline(false)
, image(image)
{
}
Private(const Private &rhs)
: compositeOp(rhs.compositeOp),
id(QUuid::createUuid()),
systemLocked(false),
collapsed(rhs.collapsed),
supportsLodMoves(rhs.supportsLodMoves),
animated(rhs.animated),
useInTimeline(rhs.useInTimeline),
image(rhs.image)
{
QMapIterator<QString, QVariant> iter = rhs.properties.propertyIterator();
while (iter.hasNext()) {
iter.next();
properties.setProperty(iter.key(), iter.value());
}
}
};
KisBaseNode::KisBaseNode(KisImageWSP image)
: m_d(new Private(image))
{
/**
* Be cautious! These two calls are vital to warm-up KoProperties.
* We use it and its QMap in a threaded environment. This is not
* officially supported by Qt, but our environment guarantees, that
* there will be the only writer and several readers. Whilst the
* value of the QMap is boolean and there are no implicit-sharing
* calls provocated, it is safe to work with it in such an
* environment.
*/
setVisible(true, true);
setUserLocked(false);
setCollapsed(false);
setSupportsLodMoves(true);
m_d->compositeOp = COMPOSITE_OVER;
}
KisBaseNode::KisBaseNode(const KisBaseNode & rhs)
: QObject()
, KisShared()
, m_d(new Private(*rhs.m_d))
{
if (rhs.m_d->opacityChannel) {
m_d->opacityChannel.reset(new KisScalarKeyframeChannel(*rhs.m_d->opacityChannel, 0));
m_d->keyframeChannels.insert(m_d->opacityChannel->id(), m_d->opacityChannel.data());
}
}
KisBaseNode::~KisBaseNode()
{
delete m_d;
}
KisPaintDeviceSP KisBaseNode::colorPickSourceDevice() const
{
return projection();
}
quint8 KisBaseNode::opacity() const
{
if (m_d->opacityChannel) {
qreal value = m_d->opacityChannel->currentValue();
if (!qIsNaN(value)) {
return value;
}
}
return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8);
}
void KisBaseNode::setOpacity(quint8 val)
{
if (m_d->opacityChannel) {
KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe();
if (activeKeyframe) {
m_d->opacityChannel->setScalarValue(activeKeyframe, val);
}
}
if (opacity() == val) return;
setNodeProperty("opacity", val);
baseNodeInvalidateAllFramesCallback();
}
quint8 KisBaseNode::percentOpacity() const
{
return int(float(opacity() * 100) / 255 + 0.5);
}
void KisBaseNode::setPercentOpacity(quint8 val)
{
setOpacity(int(float(val * 255) / 100 + 0.5));
}
const QString& KisBaseNode::compositeOpId() const
{
return m_d->compositeOp;
}
void KisBaseNode::setCompositeOpId(const QString& compositeOp)
{
if (m_d->compositeOp == compositeOp) return;
m_d->compositeOp = compositeOp;
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const
{
KisBaseNode::PropertyList l;
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis);
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked());
return l;
}
void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
{
setVisible(properties.at(0).state.toBool());
m_d->hack_visible = properties.at(0);
setUserLocked(properties.at(1).state.toBool());
}
const KoProperties & KisBaseNode::nodeProperties() const
{
return m_d->properties;
}
void KisBaseNode::setNodeProperty(const QString & name, const QVariant & value)
{
m_d->properties.setProperty(name, value);
baseNodeChangedCallback();
}
void KisBaseNode::mergeNodeProperties(const KoProperties & properties)
{
QMapIterator<QString, QVariant> iter = properties.propertyIterator();
while (iter.hasNext()) {
iter.next();
m_d->properties.setProperty(iter.key(), iter.value());
}
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
bool KisBaseNode::check(const KoProperties & properties) const
{
QMapIterator<QString, QVariant> iter = properties.propertyIterator();
while (iter.hasNext()) {
iter.next();
if (m_d->properties.contains(iter.key())) {
if (m_d->properties.value(iter.key()) != iter.value())
return false;
}
}
return true;
}
QImage KisBaseNode::createThumbnail(qint32 w, qint32 h)
{
try {
QImage image(w, h, QImage::Format_ARGB32);
image.fill(0);
return image;
} catch (const std::bad_alloc&) {
return QImage();
}
}
QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time)
{
Q_UNUSED(time)
return createThumbnail(w, h);
}
bool KisBaseNode::visible(bool recursive) const
{
bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
KisBaseNodeSP parentNode = parentCallback();
return recursive && isVisible && parentNode ?
parentNode->visible(recursive) : isVisible;
}
void KisBaseNode::setVisible(bool visible, bool loading)
{
const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
if (!loading && isVisible == visible) return;
m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible);
notifyParentVisibilityChanged(visible);
if (!loading) {
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
}
bool KisBaseNode::userLocked() const
{
return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false);
}
void KisBaseNode::setUserLocked(bool locked)
{
const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true);
if (isLocked == locked) return;
m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked);
baseNodeChangedCallback();
}
bool KisBaseNode::isEditable(bool checkVisibility) const
{
bool editable = true;
if (checkVisibility) {
editable = (visible(false) && !userLocked());
}
else {
editable = (!userLocked());
}
if (editable) {
KisBaseNodeSP parentNode = parentCallback();
if (parentNode && parentNode != this) {
editable = parentNode->isEditable(checkVisibility);
}
}
return editable;
}
bool KisBaseNode::hasEditablePaintDevice() const
{
return paintDevice() && isEditable();
}
void KisBaseNode::setCollapsed(bool collapsed)
{
m_d->collapsed = collapsed;
}
bool KisBaseNode::collapsed() const
{
return m_d->collapsed;
}
void KisBaseNode::setColorLabelIndex(int index)
{
const int currentLabel = colorLabelIndex();
if (currentLabel == index) return;
m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index);
baseNodeChangedCallback();
}
int KisBaseNode::colorLabelIndex() const
{
return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0);
}
QUuid KisBaseNode::uuid() const
{
return m_d->id;
}
void KisBaseNode::setUuid(const QUuid& id)
{
m_d->id = id;
baseNodeChangedCallback();
}
bool KisBaseNode::supportsLodMoves() const
{
return m_d->supportsLodMoves;
}
void KisBaseNode::setImage(KisImageWSP image)
{
m_d->image = image;
}
KisImageWSP KisBaseNode::image() const
{
return m_d->image;
}
+bool KisBaseNode::isFakeNode() const
+{
+ return false;
+}
+
void KisBaseNode::setSupportsLodMoves(bool value)
{
m_d->supportsLodMoves = value;
}
QMap<QString, KisKeyframeChannel*> KisBaseNode::keyframeChannels() const
{
return m_d->keyframeChannels;
}
KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const
{
QMap<QString, KisKeyframeChannel*>::const_iterator i = m_d->keyframeChannels.constFind(id);
if (i == m_d->keyframeChannels.constEnd()) {
return 0;
}
return i.value();
}
KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create)
{
KisKeyframeChannel *channel = getKeyframeChannel(id);
if (!channel && create) {
channel = requestKeyframeChannel(id);
if (channel) {
addKeyframeChannel(channel);
}
}
return channel;
}
bool KisBaseNode::isAnimated() const
{
return m_d->animated;
}
void KisBaseNode::enableAnimation()
{
m_d->animated = true;
baseNodeChangedCallback();
}
bool KisBaseNode::useInTimeline() const
{
return m_d->useInTimeline;
}
void KisBaseNode::setUseInTimeline(bool value)
{
if (value == m_d->useInTimeline) return;
m_d->useInTimeline = value;
baseNodeChangedCallback();
}
void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel)
{
m_d->keyframeChannels.insert(channel->id(), channel);
emit keyframeChannelAdded(channel);
}
KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::Opacity.id()) {
Q_ASSERT(m_d->opacityChannel.isNull());
KisPaintDeviceSP device = original();
if (device) {
KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel(
KisKeyframeChannel::Opacity,
0, 255,
device->defaultBounds(),
KisKeyframe::Linear
);
m_d->opacityChannel.reset(channel);
return channel;
}
}
return 0;
}
diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h
index c2e485e2f4..9d1df1d164 100644
--- a/libs/image/kis_base_node.h
+++ b/libs/image/kis_base_node.h
@@ -1,572 +1,579 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_BASE_NODE_H
#define _KIS_BASE_NODE_H
#include <QObject>
#include <QIcon>
#include <QUuid>
#include <QString>
#include <KoID.h>
#include "kis_shared.h"
#include "kis_paint_device.h"
#include "kis_processing_visitor.h" // included, not forward declared for msvc
class KoProperties;
class KoColorSpace;
class KoCompositeOp;
class KisNodeVisitor;
class KisUndoAdapter;
class KisKeyframeChannel;
#include "kritaimage_export.h"
/**
* A KisBaseNode is the base class for all components of an image:
* nodes, layers masks, selections. A node has a number of properties,
* can be represented as a thumbnail and knows what to do when it gets
* a certain paint device to process. A KisBaseNode does not know
* anything about its peers. You should not directly inherit from a
* KisBaseNode; inherit from KisNode instead.
*/
class KRITAIMAGE_EXPORT KisBaseNode : public QObject, public KisShared
{
Q_OBJECT
public:
/**
* Describes a property of a document section.
*
* FIXME: using a QList instead of QMap and not having an untranslated identifier,
* either enum or string, forces applications to rely on the order of properties
* or to compare the translated strings. This makes it hard to robustly extend the
* properties of document section items.
*/
struct Property
{
QString id;
/** i18n-ed name, suitable for displaying */
QString name;
/** Whether the property is a boolean (e.g. locked, visible) which can be toggled directly from the widget itself. */
bool isMutable;
/** Provide these if the property isMutable. */
QIcon onIcon;
QIcon offIcon;
/** If the property isMutable, provide a boolean. Otherwise, a string suitable for displaying. */
QVariant state;
/** If the property is mutable, specifies whether it can be put into stasis. When a property
is in stasis, a new state is created, and the old one is stored in stateInStasis. When
stasis ends, the old value is restored and the new one discarded */
bool canHaveStasis;
/** If the property isMutable and canHaveStasis, indicate whether it is in stasis or not */
bool isInStasis;
/** If the property isMutable and canHaveStasis, provide this value to store the property's
state while in stasis */
bool stateInStasis;
bool operator==(const Property &rhs) const {
return rhs.name == name && rhs.state == state;
}
Property(): isMutable( false ) { }
/// Constructor for a mutable property.
Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn )
: id(n.id()), name( n.name() ), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( false ) { }
/** Constructor for a mutable property accepting stasis */
Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn,
bool _isInStasis, bool _stateInStasis )
: id(n.id()), name(n.name()), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ),
canHaveStasis( true ), isInStasis( _isInStasis ), stateInStasis( _stateInStasis ) { }
/// Constructor for a nonmutable property.
Property( const KoID &n, const QString &s )
: id(n.id()), name(n.name()), isMutable( false ), state( s ) { }
};
/** Return this type for PropertiesRole. */
typedef QList<Property> PropertyList;
public:
/**
* Create a new, empty base node. The node is unnamed, unlocked
* visible and unlinked.
*/
KisBaseNode(KisImageWSP image);
/**
* Create a copy of this node.
*/
KisBaseNode(const KisBaseNode & rhs);
/**
* Delete this node
*/
~KisBaseNode() override;
/**
* Return the paintdevice you can use to change pixels on. For a
* paint layer these will be paint pixels, for an adjustment layer or a mask
* the selection paint device.
*
* @return the paint device to paint on. Can be 0 if the actual
* node type does not support painting.
*/
virtual KisPaintDeviceSP paintDevice() const = 0;
/**
* @return the rendered representation of a node
* before the effect masks have had their go at it. Can be 0.
*/
virtual KisPaintDeviceSP original() const = 0;
/**
* @return the fully rendered representation of this layer: its
* rendered original and its effect masks. Can be 0.
*/
virtual KisPaintDeviceSP projection() const = 0;
/**
* @return a special device from where the color picker tool should pick
* color when in layer-only mode. For most of the nodes just shortcuts
* to projection() device. TODO: can it be null?
*/
virtual KisPaintDeviceSP colorPickSourceDevice() const;
virtual const KoColorSpace *colorSpace() const = 0;
/**
* Return the opacity of this layer, scaled to a range between 0
* and 255.
* XXX: Allow true float opacity
*/
quint8 opacity() const; //0-255
/**
* Set the opacity for this layer. The range is between 0 and 255.
* The layer will be marked dirty.
*
* XXX: Allow true float opacity
*/
void setOpacity(quint8 val); //0-255
/**
* return the 8-bit opacity of this layer scaled to the range
* 0-100
*
* XXX: Allow true float opacity
*/
quint8 percentOpacity() const; //0-100
/**
* Set the opacity of this layer with a number between 0 and 100;
* the number will be scaled to between 0 and 255.
* XXX: Allow true float opacity
*/
void setPercentOpacity(quint8 val); //0-100
/**
* Return the composite op associated with this layer.
*/
virtual const KoCompositeOp *compositeOp() const = 0;
const QString& compositeOpId() const;
/**
* Set a new composite op for this layer. The layer will be marked
* dirty.
*/
void setCompositeOpId(const QString& compositeOpId);
/**
* @return unique id, which is now used by clone layers.
*/
QUuid uuid() const;
/**
* Set the uuid of node. This should only be used when loading
* existing node and in constructor.
*/
void setUuid(const QUuid& id);
/**
* return the name of this node. This is the same as the
* QObject::objectName.
*/
QString name() const {
return objectName();
}
/**
* set the QObject::objectName. This is also the user-visible name
* of the layer. The reason for this is that we want to see the
* layer name also when debugging.
*/
void setName(const QString& name) {
setObjectName(name);
baseNodeChangedCallback();
}
/**
* @return the icon used to represent the node type, for instance
* in the layerbox and in the menu.
*/
virtual QIcon icon() const {
return QIcon();
}
/**
* Return a the properties of this base node (locked, visible etc,
* with the right icons for their representation and their state.
*
* Subclasses can extend this list with new properties, like
* opacity for layers or visualized for masks.
*
* The order of properties is, unfortunately, for now, important,
* so take care which properties superclasses of your class
* define.
*
* KisBaseNode defines visible = 0, locked = 1
* KisLayer defines opacity = 2, compositeOp = 3
* KisMask defines active = 2 (KisMask does not inherit kislayer)
*/
virtual PropertyList sectionModelProperties() const;
/**
* Change the section model properties.
*/
virtual void setSectionModelProperties(const PropertyList &properties);
/**
* Return all the properties of this layer as a KoProperties-based
* serializable key-value list.
*/
const KoProperties & nodeProperties() const;
/**
* Set a node property.
* @param name name of the property to be set.
* @param value value to set the property to.
*/
void setNodeProperty(const QString & name, const QVariant & value);
/**
* Merge the specified properties with the properties of this
* layer. Wherever these properties overlap, the value of the
* node properties is changed. No properties on the node are
* deleted. If there are new properties in this list, they will be
* added on the node.
*/
void mergeNodeProperties(const KoProperties & properties);
/**
* Compare the given properties list with the properties of this
* node.
*
* @return false only if the same property exists in both lists
* but with a different value. Properties that are not in both
* lists are disregarded.
*/
bool check(const KoProperties & properties) const;
/**
* Accept the KisNodeVisitor (for the Visitor design pattern),
* should call the correct function on the KisNodeVisitor for this
* node type, so you need to override it for all leaf classes in
* the node inheritance hierarchy.
*
* return false if the visitor could not successfully act on this
* node instance.
*/
virtual bool accept(KisNodeVisitor &) {
return false;
}
/**
* Accept the KisNodeVisitor (for the Visitor design pattern),
* should call the correct function on the KisProcessingVisitor
* for this node type, so you need to override it for all leaf
* classes in the node inheritance hierarchy.
*
* The processing visitor differs from node visitor in the way
* that it accepts undo adapter, that allows the processing to
* be multithreaded
*/
virtual void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) {
Q_UNUSED(visitor);
Q_UNUSED(undoAdapter);
}
/**
* @return a thumbnail in requested size. The thumbnail is a rgba
* QImage and may have transparent parts. Returns a fully
* transparent QImage of the requested size if the current node
* type cannot generate a thumbnail. If the requested size is too
* big, return a null QImage.
*/
virtual QImage createThumbnail(qint32 w, qint32 h);
/**
* @return a thumbnail in requested size for the defined timestamp.
* The thumbnail is a rgba Image and may have transparent parts.
* Returns a fully transparent QImage of the requested size if the
* current node type cannot generate a thumbnail. If the requested
* size is too big, return a null QImage.
*/
virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time);
/**
* Ask this node to re-read the pertinent settings from the krita
* configuration.
*/
virtual void updateSettings() {
}
/**
* @return true if this node is visible (i.e, active (except for
* selection masks where visible and active properties are
* different)) in the graph
*
* @param bool recursive if true, check whether all parents of
* this node are visible as well.
*/
virtual bool visible(bool recursive = false) const;
/**
* Set the visible status of this node. Visible nodes are active
* in the graph (except for selections masks which can be active
* while hidden), that is to say, they are taken into account
* when merging. Invisible nodes play no role in the final image
*, but will be modified when modifying all layers, for instance
* when cropping.
*
* Toggling the visibility of a node will not automatically lead
* to recomposition.
*
* @param visible the new visibility state
* @param isLoading if true, the property is set during loading.
*/
virtual void setVisible(bool visible, bool loading = false);
/**
* Return the locked status of this node. Locked nodes cannot be
* edited.
*/
bool userLocked() const;
/**
* Set the locked status of this node. Locked nodes cannot be
* edited.
*/
virtual void setUserLocked(bool l);
/**
* @return true if the node can be edited:
*
* if checkVisibility is true, then the node is only editable if it is visible and not locked.
* if checkVisibility is false, then the node is editable if it's not locked.
*/
bool isEditable(bool checkVisibility = true) const;
/**
* @return true if the node is editable and has a paintDevice()
* which which can be used for accessing pixels. It is an
* equivalent to (isEditable() && paintDevice())
*/
bool hasEditablePaintDevice() const;
/**
* @return the x-offset of this layer in the image plane.
*/
virtual qint32 x() const {
return 0;
}
/**
* Set the x offset of this layer in the image place.
* Re-implement this where it makes sense, by default it does
* nothing. It should not move child nodes.
*/
virtual void setX(qint32) {
}
/**
* @return the y-offset of this layer in the image plane.
*/
virtual qint32 y() const {
return 0;
}
/**
* Set the y offset of this layer in the image place.
* Re-implement this where it makes sense, by default it does
* nothing. It should not move child nodes.
*/
virtual void setY(qint32) {
}
/**
* Returns an approximation of where the bounds on actual data are
* in this node.
*/
virtual QRect extent() const {
return QRect();
}
/**
* Returns the exact bounds of where the actual data resides in
* this node.
*/
virtual QRect exactBounds() const {
return QRect();
}
/**
* Sets the state of the node to the value of @param collapsed
*/
void setCollapsed(bool collapsed);
/**
* returns the collapsed state of this node
*/
bool collapsed() const;
/**
* Sets a color label index associated to the layer. The actual
* color of the label and the number of available colors is
* defined by Krita GUI configuration.
*/
void setColorLabelIndex(int index);
/**
* \see setColorLabelIndex
*/
int colorLabelIndex() const;
/**
* Returns true if the offset of the node can be changed in a LodN
* stroke. Currently, all the nodes except shape layers support that.
*/
bool supportsLodMoves() const;
/**
* Return the keyframe channels associated with this node
* @return list of keyframe channels
*/
QMap<QString, KisKeyframeChannel*> keyframeChannels() const;
/**
* Get the keyframe channel with given id.
* If the channel does not yet exist and the node supports the requested
* channel, it will be created if create is true.
* @param id internal name for channel
* @param create attempt to create the channel if it does not exist yet
* @return keyframe channel with the id, or null if not found
*/
KisKeyframeChannel *getKeyframeChannel(const QString &id, bool create);
KisKeyframeChannel *getKeyframeChannel(const QString &id) const;
bool useInTimeline() const;
void setUseInTimeline(bool value);
bool isAnimated() const;
void enableAnimation();
virtual void setImage(KisImageWSP image);
KisImageWSP image() const;
+
+ /**
+ * Fake node is not present in the layer stack and is not used
+ * for normal projection rendering algorithms.
+ */
+ virtual bool isFakeNode() const;
+
protected:
void setSupportsLodMoves(bool value);
/**
* FIXME: This method is a workaround for getting parent node
* on a level of KisBaseNode. In fact, KisBaseNode should inherit
* KisNode (in terms of current Krita) to be able to traverse
* the node stack
*/
virtual KisBaseNodeSP parentCallback() const {
return KisBaseNodeSP();
}
virtual void notifyParentVisibilityChanged(bool value) {
Q_UNUSED(value);
}
/**
* This callback is called when some meta state of the base node
* that can be interesting to the UI has changed. E.g. visibility,
* lockness, opacity, compositeOp and etc. This signal is
* forwarded by the KisNode and KisNodeGraphListener to the model
* in KisLayerBox, so it can update its controls when information
* changes.
*/
virtual void baseNodeChangedCallback() {
}
virtual void baseNodeInvalidateAllFramesCallback() {
}
/**
* Add a keyframe channel for this node. The channel will be added
* to the common hash table which will be available to the UI.
*
* WARNING: the \p channel object *NOT* become owned by the node!
* The caller must ensure manually that the lifetime of
* the object coincide with the lifetime of the node.
*/
virtual void addKeyframeChannel(KisKeyframeChannel* channel);
/**
* Attempt to create the requested channel. Used internally by getKeyframeChannel.
* Subclasses should implement this method to catch any new channel types they support.
* @param id channel to create
* @return newly created channel or null
*/
virtual KisKeyframeChannel * requestKeyframeChannel(const QString &id);
Q_SIGNALS:
void keyframeChannelAdded(KisKeyframeChannel *channel);
private:
struct Private;
Private * const m_d;
};
Q_DECLARE_METATYPE( KisBaseNode::PropertyList )
#endif
diff --git a/libs/image/kis_base_rects_walker.h b/libs/image/kis_base_rects_walker.h
index 77e7c66740..dc6aaa1684 100644
--- a/libs/image/kis_base_rects_walker.h
+++ b/libs/image/kis_base_rects_walker.h
@@ -1,510 +1,517 @@
/*
* Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_BASE_RECTS_WALKER_H
#define __KIS_BASE_RECTS_WALKER_H
#include <QStack>
#include "kis_layer.h"
#include "kis_abstract_projection_plane.h"
#include "kis_projection_leaf.h"
class KisBaseRectsWalker;
typedef KisSharedPtr<KisBaseRectsWalker> KisBaseRectsWalkerSP;
class KRITAIMAGE_EXPORT KisBaseRectsWalker : public KisShared
{
public:
enum UpdateType {
UPDATE,
UPDATE_NO_FILTHY,
FULL_REFRESH,
UNSUPPORTED
};
typedef qint32 NodePosition;
enum NodePositionValues {
/**
* There are two different sets of values.
* The first describes the position of the node to the graph,
* the second shows the position to the filthy node
*/
N_NORMAL = 0x00,
N_TOPMOST = 0x01,
N_BOTTOMMOST = 0x02,
N_EXTRA = 0x04,
N_ABOVE_FILTHY = 0x08,
N_FILTHY_ORIGINAL = 0x10, // not used actually
N_FILTHY_PROJECTION = 0x20,
N_FILTHY = 0x40,
N_BELOW_FILTHY = 0x80
};
#define GRAPH_POSITION_MASK 0x07
static inline KisNode::PositionToFilthy convertPositionToFilthy(NodePosition position) {
static const int positionToFilthyMask =
N_ABOVE_FILTHY |
N_FILTHY_PROJECTION |
N_FILTHY |
N_BELOW_FILTHY;
qint32 positionToFilthy = position & N_EXTRA ? N_FILTHY : position & positionToFilthyMask;
// We do not use N_FILTHY_ORIGINAL yet, so...
Q_ASSERT(positionToFilthy);
return static_cast<KisNode::PositionToFilthy>(positionToFilthy);
}
struct CloneNotification {
CloneNotification() {}
CloneNotification(KisNodeSP node, const QRect &dirtyRect)
: m_layer(qobject_cast<KisLayer*>(node.data())),
m_dirtyRect(dirtyRect) {}
void notify() {
Q_ASSERT(m_layer); // clones are possible for layers only
m_layer->updateClones(m_dirtyRect);
}
private:
friend class KisWalkersTest;
KisLayerSP m_layer;
QRect m_dirtyRect;
};
typedef QVector<CloneNotification> CloneNotificationsVector;
struct JobItem {
KisProjectionLeafSP m_leaf;
NodePosition m_position;
/**
* The rect that should be prepared on this node.
* E.g. area where the filter applies on filter layer
* or an area of a paint layer that will be copied to
* the projection.
*/
QRect m_applyRect;
};
typedef QStack<JobItem> LeafStack;
public:
KisBaseRectsWalker()
: m_levelOfDetail(0)
{
}
virtual ~KisBaseRectsWalker() {
}
void collectRects(KisNodeSP node, const QRect& requestedRect) {
clear();
KisProjectionLeafSP startLeaf = node->projectionLeaf();
m_nodeChecksum = calculateChecksum(startLeaf, requestedRect);
m_graphChecksum = node->graphSequenceNumber();
m_resultChangeRect = requestedRect;
m_resultUncroppedChangeRect = requestedRect;
m_requestedRect = requestedRect;
m_startNode = node;
m_levelOfDetail = getNodeLevelOfDetail(startLeaf);
startTrip(startLeaf);
}
inline void recalculate(const QRect& requestedRect) {
KIS_SAFE_ASSERT_RECOVER_RETURN(m_startNode);
KisProjectionLeafSP startLeaf = m_startNode->projectionLeaf();
int calculatedLevelOfDetail = getNodeLevelOfDetail(startLeaf);
if (m_levelOfDetail != calculatedLevelOfDetail) {
qWarning() << "WARNING: KisBaseRectsWalker::recalculate()"
<< "The levelOfDetail has changes with time,"
<< "which couldn't have happened!"
<< ppVar(m_levelOfDetail)
<< ppVar(calculatedLevelOfDetail);
m_levelOfDetail = calculatedLevelOfDetail;
}
if(startLeaf->isStillInGraph()) {
collectRects(m_startNode, requestedRect);
}
else {
clear();
m_nodeChecksum = calculateChecksum(startLeaf, requestedRect);
m_graphChecksum = m_startNode->graphSequenceNumber();
m_resultChangeRect = QRect();
m_resultUncroppedChangeRect = QRect();
}
}
bool checksumValid() {
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_startNode, false);
return
m_nodeChecksum == calculateChecksum(m_startNode->projectionLeaf(), m_requestedRect) &&
m_graphChecksum == m_startNode->graphSequenceNumber();
}
inline void setCropRect(QRect cropRect) {
m_cropRect = cropRect;
}
inline QRect cropRect() const{
return m_cropRect;
}
// return a reference for efficiency reasons
inline LeafStack& leafStack() {
return m_mergeTask;
}
// return a reference for efficiency reasons
inline CloneNotificationsVector& cloneNotifications() {
return m_cloneNotifications;
}
inline QRect accessRect() const {
return m_resultAccessRect;
}
inline QRect changeRect() const {
return m_resultChangeRect;
}
inline QRect uncroppedChangeRect() const {
return m_resultUncroppedChangeRect;
}
inline bool needRectVaries() const {
return m_needRectVaries;
}
inline bool changeRectVaries() const {
return m_changeRectVaries;
}
inline KisNodeSP startNode() const {
return m_startNode;
}
inline QRect requestedRect() const {
return m_requestedRect;
}
inline int levelOfDetail() const {
return m_levelOfDetail;
}
virtual UpdateType type() const = 0;
protected:
/**
* Initiates collecting of rects.
* Should be implemented in derived classes
*/
virtual void startTrip(KisProjectionLeafSP startWith) = 0;
protected:
static inline qint32 getGraphPosition(qint32 position) {
return position & GRAPH_POSITION_MASK;
}
static inline bool hasClones(KisNodeSP node) {
KisLayer *layer = qobject_cast<KisLayer*>(node.data());
return layer && layer->hasClones();
}
static inline NodePosition calculateNodePosition(KisProjectionLeafSP leaf) {
KisProjectionLeafSP nextLeaf = leaf->nextSibling();
while(nextLeaf && !nextLeaf->isLayer()) nextLeaf = nextLeaf->nextSibling();
if (!nextLeaf) return N_TOPMOST;
KisProjectionLeafSP prevLeaf = leaf->prevSibling();
while(prevLeaf && !prevLeaf->isLayer()) prevLeaf = prevLeaf->prevSibling();
if (!prevLeaf) return N_BOTTOMMOST;
return N_NORMAL;
}
inline bool isStartLeaf(KisProjectionLeafSP leaf) const {
return leaf->node() == m_startNode;
}
inline void clear() {
m_resultAccessRect = m_resultNeedRect = /*m_resultChangeRect =*/
m_childNeedRect = m_lastNeedRect = QRect();
m_needRectVaries = m_changeRectVaries = false;
m_mergeTask.clear();
m_cloneNotifications.clear();
// Not needed really. Think over removing.
//m_startNode = 0;
//m_requestedRect = QRect();
}
inline void pushJob(KisProjectionLeafSP leaf, NodePosition position, QRect applyRect) {
JobItem item = {leaf, position, applyRect};
m_mergeTask.push(item);
}
inline QRect cropThisRect(const QRect& rect) {
return m_cropRect.isValid() ? rect & m_cropRect : rect;
}
/**
* Used by KisFullRefreshWalker as it has a special changeRect strategy
*/
inline void setExplicitChangeRect(const QRect &changeRect, bool changeRectVaries) {
m_resultChangeRect = changeRect;
m_resultUncroppedChangeRect = changeRect;
m_changeRectVaries = changeRectVaries;
}
/**
* Called for every node we meet on a forward way of the trip.
*/
virtual void registerChangeRect(KisProjectionLeafSP leaf, NodePosition position) {
// We do not work with masks here. It is KisLayer's job.
if(!leaf->isLayer()) return;
if(!(position & N_FILTHY) && !leaf->visible()) return;
QRect currentChangeRect = leaf->projectionPlane()->changeRect(m_resultChangeRect,
convertPositionToFilthy(position));
currentChangeRect = cropThisRect(currentChangeRect);
if(!m_changeRectVaries)
m_changeRectVaries = currentChangeRect != m_resultChangeRect;
m_resultChangeRect = currentChangeRect;
m_resultUncroppedChangeRect = leaf->projectionPlane()->changeRect(m_resultUncroppedChangeRect,
convertPositionToFilthy(position));
registerCloneNotification(leaf->node(), position);
}
void registerCloneNotification(KisNodeSP node, NodePosition position) {
/**
* Note, we do not check for (N_ABOVE_FILTHY &&
* dependOnLowerNodes(node)) because it may lead to an
* infinite loop with filter layer. Activate it when it is
* guaranteed that it is not possible to create a filter layer
* avobe its own clone
*/
if(hasClones(node) && position & (N_FILTHY | N_FILTHY_PROJECTION | N_EXTRA)) {
m_cloneNotifications.append(
CloneNotification(node, m_resultUncroppedChangeRect));
}
}
/**
* Called for every node we meet on a backward way of the trip.
*/
virtual void registerNeedRect(KisProjectionLeafSP leaf, NodePosition position) {
// We do not work with masks here. It is KisLayer's job.
if(!leaf->isLayer()) return;
if(m_mergeTask.isEmpty())
m_resultAccessRect = m_resultNeedRect = m_childNeedRect =
m_lastNeedRect = m_resultChangeRect;
if (leaf->parent() && position & N_TOPMOST) {
bool parentNeedRectFound = false;
QRect parentNeedRect;
Q_FOREACH(const JobItem &job, m_mergeTask) {
if (job.m_leaf == leaf->parent()) {
parentNeedRect =
job.m_leaf->projectionPlane()->needRectForOriginal(job.m_applyRect);
parentNeedRectFound = true;
}
}
// TODO: check if we can put this requirement
// KIS_SAFE_ASSERT_RECOVER_NOOP(parentNeedRectFound);
if (parentNeedRectFound) {
m_lastNeedRect = parentNeedRect;
} else {
// legacy way of fetching parent need rect, just
// takes need rect of the last visited filthy node
m_lastNeedRect = m_childNeedRect;
}
}
if (!leaf->visible()) {
if (!m_lastNeedRect.isEmpty()) {
// push a dumb job to fit state machine requirements
pushJob(leaf, position, m_lastNeedRect);
}
} else if(position & (N_FILTHY | N_ABOVE_FILTHY | N_EXTRA)) {
if(!m_lastNeedRect.isEmpty())
pushJob(leaf, position, m_lastNeedRect);
//else /* Why push empty rect? */;
m_resultAccessRect |= leaf->projectionPlane()->accessRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = leaf->projectionPlane()->needRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = cropThisRect(m_lastNeedRect);
m_childNeedRect = m_lastNeedRect;
}
else if(position & (N_BELOW_FILTHY | N_FILTHY_PROJECTION)) {
if(!m_lastNeedRect.isEmpty()) {
pushJob(leaf, position, m_lastNeedRect);
m_resultAccessRect |= leaf->projectionPlane()->accessRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = leaf->projectionPlane()->needRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = cropThisRect(m_lastNeedRect);
}
}
else {
// N_FILTHY_ORIGINAL is not used so it goes there
qFatal("KisBaseRectsWalker: node position(%d) is out of range", position);
}
if(!m_needRectVaries)
m_needRectVaries = m_resultNeedRect != m_lastNeedRect;
m_resultNeedRect |= m_lastNeedRect;
}
virtual void adjustMasksChangeRect(KisProjectionLeafSP firstMask) {
KisProjectionLeafSP currentLeaf = firstMask;
while (currentLeaf) {
/**
* ATTENTION: we miss the first mask
*/
do {
currentLeaf = currentLeaf->nextSibling();
} while (currentLeaf &&
(!currentLeaf->isMask() || !currentLeaf->visible()));
if(currentLeaf) {
QRect changeRect = currentLeaf->projectionPlane()->changeRect(m_resultChangeRect);
m_changeRectVaries |= changeRect != m_resultChangeRect;
m_resultChangeRect = changeRect;
m_resultUncroppedChangeRect = changeRect;
}
}
KisProjectionLeafSP parentLayer = firstMask->parent();
KIS_SAFE_ASSERT_RECOVER_RETURN(parentLayer);
registerCloneNotification(parentLayer->node(), N_FILTHY_PROJECTION);
}
static qint32 calculateChecksum(KisProjectionLeafSP leaf, const QRect &requestedRect) {
qint32 checksum = 0;
qint32 x, y, w, h;
QRect tempRect;
tempRect = leaf->projectionPlane()->changeRect(requestedRect);
tempRect.getRect(&x, &y, &w, &h);
checksum += -x - y + w + h;
tempRect = leaf->projectionPlane()->needRect(requestedRect);
tempRect.getRect(&x, &y, &w, &h);
checksum += -x - y + w + h;
// errKrita << leaf << requestedRect << "-->" << checksum;
return checksum;
}
private:
inline int getNodeLevelOfDetail(KisProjectionLeafSP leaf) {
- while (!leaf->projection()) {
+ while (leaf && !leaf->projection()) {
leaf = leaf->parent();
}
- KIS_ASSERT_RECOVER(leaf->projection()) {
+ if (!leaf || !leaf->projection()) {
+ /**
+ * Such errors may happen during undo or too quick node removal,
+ * they shouldn't cause any real problems in Krita work.
+ */
+ qWarning() << "WARNING: KisBaseRectsWalker::getNodeLevelOfDetail() "
+ "failed to fetch currentLevelOfDetail() from the node. "
+ "Perhaps the node was removed from the image in the meantime.";
return 0;
}
return leaf->projection()->defaultBounds()->currentLevelOfDetail();
}
private:
/**
* The result variables.
* By the end of a recursion they will store a complete
* data for a successful merge operation.
*/
QRect m_resultAccessRect;
QRect m_resultNeedRect;
QRect m_resultChangeRect;
QRect m_resultUncroppedChangeRect;
bool m_needRectVaries;
bool m_changeRectVaries;
LeafStack m_mergeTask;
CloneNotificationsVector m_cloneNotifications;
/**
* Used by update optimization framework
*/
KisNodeSP m_startNode;
QRect m_requestedRect;
/**
* Used for getting know whether the start node
* properties have changed since the walker was
* calculated
*/
qint32 m_nodeChecksum;
/**
* Used for getting know whether the structure of
* the graph has changed since the walker was
* calculated
*/
qint32 m_graphChecksum;
/**
* Temporary variables
*/
QRect m_cropRect;
QRect m_childNeedRect;
QRect m_lastNeedRect;
int m_levelOfDetail;
};
#endif /* __KIS_BASE_RECTS_WALKER_H */
diff --git a/libs/image/kis_filter_strategy.h b/libs/image/kis_filter_strategy.h
index 3ebf21963a..09699cfc07 100644
--- a/libs/image/kis_filter_strategy.h
+++ b/libs/image/kis_filter_strategy.h
@@ -1,208 +1,210 @@
/*
* Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2013 Juan Palacios <jpalaciosdev@gmail.com>
*
* 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_FILTER_STRATEGY_H_
#define KIS_FILTER_STRATEGY_H_
#include <klocalizedstring.h>
#include "KoGenericRegistry.h"
#include "KoID.h"
#include "kritaimage_export.h"
class KRITAIMAGE_EXPORT KisFilterStrategy
{
public:
KisFilterStrategy(KoID id) : m_id(id) {}
virtual ~KisFilterStrategy() { }
QString id() {
return m_id.id();
}
QString name() {
return m_id.name();
}
virtual qreal valueAt(qreal t, qreal weightsPositionScale) const {
Q_UNUSED(t);
Q_UNUSED(weightsPositionScale);
return 0;
}
virtual qint32 intValueAt(qint32 t, qreal weightsPositionScale) const {
return qint32(255*valueAt(t / 256.0, weightsPositionScale));
}
virtual qreal support(qreal weightsPositionScale) {
Q_UNUSED(weightsPositionScale);
return supportVal;
}
virtual qint32 intSupport(qreal weightsPositionScale) {
Q_UNUSED(weightsPositionScale);
return intSupportVal;
}
virtual QString description() {
return QString();
}
protected:
qreal supportVal;
qint32 intSupportVal;
KoID m_id;
};
class KRITAIMAGE_EXPORT KisHermiteFilterStrategy : public KisFilterStrategy
{
public:
KisHermiteFilterStrategy() : KisFilterStrategy(KoID("Hermite", i18n("Hermite"))) {
supportVal = 1.0; intSupportVal = 256;
}
~KisHermiteFilterStrategy() override {}
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBicubicFilterStrategy : public KisFilterStrategy
{
public:
KisBicubicFilterStrategy() : KisFilterStrategy(KoID("Bicubic", i18n("Bicubic"))) {
supportVal = 2.0; intSupportVal = 512;
}
~KisBicubicFilterStrategy() override {}
QString description() override {
return i18n("Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.");
}
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBoxFilterStrategy : public KisFilterStrategy
{
public:
KisBoxFilterStrategy() : KisFilterStrategy(KoID("NearestNeighbor", i18n("Nearest Neighbor"))) {
- supportVal = 0.5; intSupportVal = 128;
+ // 0.5 and 128, but with a bit of margin to ensure the correct pixel will be used
+ // even in case of calculation errors
+ supportVal = 0.51; intSupportVal = 129;
}
~KisBoxFilterStrategy() override {}
QString description() override {
return i18n("Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.");
}
virtual qreal support(qreal weightsPositionScale) override;
virtual qint32 intSupport(qreal weightsPositionScale) override;
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBilinearFilterStrategy : public KisFilterStrategy
{
public:
KisBilinearFilterStrategy() : KisFilterStrategy(KoID("Bilinear", i18n("Bilinear"))) {
supportVal = 1.0; intSupportVal = 256;
}
~KisBilinearFilterStrategy() override {}
QString description() override {
return i18n("Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.");
}
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBellFilterStrategy : public KisFilterStrategy
{
public:
KisBellFilterStrategy() : KisFilterStrategy(KoID("Bell", i18n("Bell"))) {
supportVal = 1.5; intSupportVal = 128 + 256;
}
~KisBellFilterStrategy() override {}
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBSplineFilterStrategy : public KisFilterStrategy
{
public:
KisBSplineFilterStrategy() : KisFilterStrategy(KoID("BSpline", i18n("BSpline"))) {
supportVal = 2.0; intSupportVal = 512;
}
~KisBSplineFilterStrategy() override {}
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisLanczos3FilterStrategy : public KisFilterStrategy
{
public:
KisLanczos3FilterStrategy() : KisFilterStrategy(KoID("Lanczos3", i18n("Lanczos3"))) {
supportVal = 3.0; intSupportVal = 768;
}
~KisLanczos3FilterStrategy() override {}
QString description() override {
return i18n("Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.");
}
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
private:
qreal sinc(qreal x) const;
};
class KRITAIMAGE_EXPORT KisMitchellFilterStrategy : public KisFilterStrategy
{
public:
KisMitchellFilterStrategy() : KisFilterStrategy(KoID("Mitchell", i18n("Mitchell"))) {
supportVal = 2.0; intSupportVal = 256;
}
~KisMitchellFilterStrategy() override {}
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisFilterStrategyRegistry : public KoGenericRegistry<KisFilterStrategy *>
{
public:
KisFilterStrategyRegistry();
~KisFilterStrategyRegistry() override;
static KisFilterStrategyRegistry* instance();
/**
* This function return a list of all the keys in KoID format by using the name() method
* on the objects stored in the registry.
*/
QList<KoID> listKeys() const;
/**
* This function return a string formatted in HTML that contains the descriptions of all objects
* (with a non empty description) stored in the registry.
*/
QString formattedDescriptions() const;
private:
KisFilterStrategyRegistry(const KisFilterStrategyRegistry&);
KisFilterStrategyRegistry operator=(const KisFilterStrategyRegistry&);
};
#endif // KIS_FILTER_STRATEGY_H_
diff --git a/libs/image/kis_group_layer.cc b/libs/image/kis_group_layer.cc
index 680c3ee597..bb303f460d 100644
--- a/libs/image/kis_group_layer.cc
+++ b/libs/image/kis_group_layer.cc
@@ -1,410 +1,422 @@
/*
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_group_layer.h"
#include <KoIcon.h>
#include <kis_icon.h>
#include <KoCompositeOpRegistry.h>
#include <KoColorSpace.h>
#include <KoColor.h>
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_debug.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_default_bounds.h"
#include "kis_clone_layer.h"
#include "kis_selection_mask.h"
#include "kis_psd_layer_style.h"
#include "kis_layer_properties_icons.h"
struct Q_DECL_HIDDEN KisGroupLayer::Private
{
public:
Private()
: paintDevice(0)
, x(0)
, y(0)
, passThroughMode(false)
{
}
KisPaintDeviceSP paintDevice;
qint32 x;
qint32 y;
bool passThroughMode;
};
KisGroupLayer::KisGroupLayer(KisImageWSP image, const QString &name, quint8 opacity) :
KisLayer(image, name, opacity),
m_d(new Private())
{
resetCache();
}
KisGroupLayer::KisGroupLayer(const KisGroupLayer &rhs) :
KisLayer(rhs),
m_d(new Private())
{
m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data());
m_d->x = rhs.m_d->x;
m_d->y = rhs.m_d->y;
m_d->paintDevice->setDefaultPixel(const_cast<KisGroupLayer*>(&rhs)->m_d->paintDevice->defaultPixel());
m_d->paintDevice->setProjectionDevice(true);
m_d->passThroughMode = rhs.passThroughMode();
}
KisGroupLayer::~KisGroupLayer()
{
delete m_d;
}
bool KisGroupLayer::checkCloneLayer(KisCloneLayerSP clone) const
{
KisNodeSP source = clone->copyFrom();
if (source) {
if(!allowAsChild(source)) return false;
if (source->inherits("KisGroupLayer")) {
KisNodeSP newParent = const_cast<KisGroupLayer*>(this);
while (newParent) {
if (newParent == source) {
return false;
}
newParent = newParent->parent();
}
}
}
return true;
}
bool KisGroupLayer::checkNodeRecursively(KisNodeSP node) const
{
KisCloneLayerSP cloneLayer = dynamic_cast<KisCloneLayer*>(node.data());
if(cloneLayer) {
return checkCloneLayer(cloneLayer);
}
else if (node->inherits("KisGroupLayer")) {
KisNodeSP child = node->firstChild();
while (child) {
if (!checkNodeRecursively(child)) {
return false;
}
child = child->nextSibling();
}
}
return true;
}
bool KisGroupLayer::allowAsChild(KisNodeSP node) const
{
- if (!parent()) { // We are the root layer, so we need to check
+ if (!checkNodeRecursively(node)) return false;
+
+ if (!parent()) {
+ // We are the root layer, so we need to check
// whether the node that's going to be added is
// a selection mask; that is only allowed if
// there isn't a global selection. See
// BUG:294905
- if (node->inherits("KisSelectionMask") && selectionMask()) {
- return false;
+
+ if (node->inherits("KisSelectionMask")) {
+ return !selectionMask();
+ }
+
+ KisImageSP image = this->image();
+
+ if (!image || !image->allowMasksOnRootNode()) {
+ if (node->inherits("KisMask")) {
+ return false;
+ }
}
}
return checkNodeRecursively(node);
}
const KoColorSpace * KisGroupLayer::colorSpace() const
{
return m_d->paintDevice->colorSpace();
}
QIcon KisGroupLayer::icon() const
{
return KisIconUtils::loadIcon("groupLayer");
}
void KisGroupLayer::setImage(KisImageWSP image)
{
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(image));
KisLayer::setImage(image);
}
KisLayerSP KisGroupLayer::createMergedLayerTemplate(KisLayerSP prevLayer)
{
KisGroupLayer *prevGroup = dynamic_cast<KisGroupLayer*>(prevLayer.data());
if (prevGroup && canMergeAndKeepBlendOptions(prevLayer)) {
KisSharedPtr<KisGroupLayer> merged(new KisGroupLayer(*prevGroup));
KisNodeSP child, cloned;
for (child = firstChild(); child; child = child->nextSibling()) {
cloned = child->clone();
image()->addNode(cloned, merged);
}
if (!merged->passThroughMode()) {
image()->refreshGraphAsync(merged);
}
return merged;
} else
return KisLayer::createMergedLayerTemplate(prevLayer);
}
void KisGroupLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer)
{
if (!dynamic_cast<KisGroupLayer*>(dstLayer.data())) {
KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer);
}
}
void KisGroupLayer::resetCache(const KoColorSpace *colorSpace)
{
if (!colorSpace)
colorSpace = image()->colorSpace();
Q_ASSERT(colorSpace);
if (!m_d->paintDevice) {
KisPaintDeviceSP dev = new KisPaintDevice(this, colorSpace, new KisDefaultBounds(image()));
dev->setX(this->x());
dev->setY(this->y());
m_d->paintDevice = dev;
m_d->paintDevice->setProjectionDevice(true);
}
else if(*m_d->paintDevice->colorSpace() != *colorSpace) {
KisPaintDeviceSP dev = new KisPaintDevice(this, colorSpace, new KisDefaultBounds(image()));
dev->setX(this->x());
dev->setY(this->y());
dev->setDefaultPixel(m_d->paintDevice->defaultPixel());
m_d->paintDevice = dev;
m_d->paintDevice->setProjectionDevice(true);
} else {
m_d->paintDevice->clear();
}
}
KisLayer* KisGroupLayer::onlyMeaningfulChild() const
{
KisNode *child = firstChild().data();
KisLayer *onlyLayer = 0;
while (child) {
KisLayer *layer = qobject_cast<KisLayer*>(child);
- if (layer) {
+ if (layer && !layer->isFakeNode()) {
if (onlyLayer) return 0;
onlyLayer = layer;
}
child = child->nextSibling().data();
}
return onlyLayer;
}
KisPaintDeviceSP KisGroupLayer::tryObligeChild() const
{
const KisLayer *child = onlyMeaningfulChild();
if (child &&
child->channelFlags().isEmpty() &&
child->projection() &&
child->visible() &&
(child->compositeOpId() == COMPOSITE_OVER ||
child->compositeOpId() == COMPOSITE_ALPHA_DARKEN ||
child->compositeOpId() == COMPOSITE_COPY) &&
child->opacity() == OPACITY_OPAQUE_U8 &&
*child->projection()->colorSpace() == *colorSpace() &&
!child->layerStyle()) {
quint8 defaultOpacity =
m_d->paintDevice->defaultPixel().opacityU8();
if(defaultOpacity == OPACITY_TRANSPARENT_U8) {
return child->projection();
}
}
return 0;
}
KisPaintDeviceSP KisGroupLayer::original() const
{
/**
* We are too lazy! Let's our children work for us.
* Try to use children's paintDevice if it's the only
* one in stack and meets some conditions
*/
KisPaintDeviceSP realOriginal = tryObligeChild();
if (!realOriginal) {
if (!childCount() && !m_d->paintDevice->extent().isEmpty()) {
m_d->paintDevice->clear();
}
realOriginal = m_d->paintDevice;
}
return realOriginal;
}
KisPaintDeviceSP KisGroupLayer::paintDevice() const
{
return 0;
}
bool KisGroupLayer::projectionIsValid() const
{
return !tryObligeChild();
}
void KisGroupLayer::setDefaultProjectionColor(KoColor color)
{
m_d->paintDevice->setDefaultPixel(color);
}
KoColor KisGroupLayer::defaultProjectionColor() const
{
KoColor color(m_d->paintDevice->defaultPixel(), m_d->paintDevice->colorSpace());
return color;
}
bool KisGroupLayer::passThroughMode() const
{
return m_d->passThroughMode;
}
void KisGroupLayer::setPassThroughMode(bool value)
{
if (m_d->passThroughMode == value) return;
m_d->passThroughMode = value;
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
KisBaseNode::PropertyList KisGroupLayer::sectionModelProperties() const
{
KisBaseNode::PropertyList l = KisLayer::sectionModelProperties();
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::passThrough, passThroughMode());
return l;
}
void KisGroupLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
{
Q_FOREACH (const KisBaseNode::Property &property, properties) {
if (property.name == i18n("Pass Through")) {
setPassThroughMode(property.state.toBool());
}
}
KisLayer::setSectionModelProperties(properties);
}
bool KisGroupLayer::accept(KisNodeVisitor &v)
{
return v.visit(this);
}
void KisGroupLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
qint32 KisGroupLayer::x() const
{
return m_d->paintDevice ? m_d->paintDevice->x() : m_d->x;
}
qint32 KisGroupLayer::y() const
{
return m_d->paintDevice ? m_d->paintDevice->y() : m_d->y;
}
void KisGroupLayer::setX(qint32 x)
{
m_d->x = x;
if(m_d->paintDevice) {
m_d->paintDevice->setX(x);
}
}
void KisGroupLayer::setY(qint32 y)
{
m_d->y = y;
if(m_d->paintDevice) {
m_d->paintDevice->setY(y);
}
}
struct ExtentPolicy
{
inline QRect operator() (const KisNode *node) {
return node->extent();
}
};
struct ExactBoundsPolicy
{
inline QRect operator() (const KisNode *node) {
return node->exactBounds();
}
};
template <class MetricPolicy>
QRect collectRects(const KisNode *node, MetricPolicy policy)
{
QRect accumulator;
const KisNode *child = node->firstChild();
while (child) {
if (!qobject_cast<const KisMask*>(child)) {
accumulator |= policy(child);
}
child = child->nextSibling();
}
return accumulator;
}
QRect KisGroupLayer::extent() const
{
return m_d->passThroughMode ?
collectRects(this, ExtentPolicy()) :
KisLayer::extent();
}
QRect KisGroupLayer::exactBounds() const
{
return m_d->passThroughMode ?
collectRects(this, ExactBoundsPolicy()) :
KisLayer::exactBounds();
}
diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc
index edc6552856..cace3629d0 100644
--- a/libs/image/kis_image.cc
+++ b/libs/image/kis_image.cc
@@ -1,1901 +1,1961 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_image.h"
#include <KoConfig.h> // WORDS_BIGENDIAN
#include <stdlib.h>
#include <math.h>
#include <QImage>
#include <QPainter>
#include <QSize>
#include <QDateTime>
#include <QRect>
#include <QRegion>
#include <QtConcurrent>
#include <klocalizedstring.h>
#include "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoColorProfile.h"
#include <KoCompositeOpRegistry.h>
#include "KisProofingConfiguration.h"
#include "kis_adjustment_layer.h"
#include "kis_annotation.h"
#include "kis_change_profile_visitor.h"
#include "kis_colorspace_convert_visitor.h"
#include "kis_count_visitor.h"
#include "kis_filter_strategy.h"
#include "kis_group_layer.h"
#include "commands/kis_image_commands.h"
#include "kis_layer.h"
#include "kis_meta_data_merge_strategy_registry.h"
#include "kis_name_server.h"
#include "kis_paint_layer.h"
#include "kis_projection_leaf.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_transaction.h"
#include "kis_meta_data_merge_strategy.h"
#include "kis_memory_statistics_server.h"
#include "kis_image_config.h"
#include "kis_update_scheduler.h"
#include "kis_image_signal_router.h"
#include "kis_image_animation_interface.h"
#include "kis_stroke_strategy.h"
#include "kis_simple_stroke_strategy.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_stores.h"
#include "kis_legacy_undo_adapter.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_transform_worker.h"
#include "kis_processing_applicator.h"
#include "processing/kis_crop_processing_visitor.h"
#include "processing/kis_crop_selections_processing_visitor.h"
#include "processing/kis_transform_processing_visitor.h"
#include "commands_new/kis_image_resize_command.h"
#include "commands_new/kis_image_set_resolution_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "kis_composite_progress_proxy.h"
#include "kis_layer_composition.h"
#include "kis_wrapped_rect.h"
#include "kis_crop_saved_extra_data.h"
#include "kis_layer_utils.h"
#include "kis_lod_transform.h"
#include "kis_suspend_projection_updates_stroke_strategy.h"
#include "kis_sync_lod_cache_stroke_strategy.h"
#include "kis_projection_updates_filter.h"
#include "kis_layer_projection_plane.h"
#include "kis_update_time_monitor.h"
#include "tiles3/kis_lockless_stack.h"
#include <QtCore>
#include <functional>
#include "kis_time_range.h"
// #define SANITY_CHECKS
#ifdef SANITY_CHECKS
#define SANITY_CHECK_LOCKED(name) \
if (!locked()) warnKrita() << "Locking policy failed:" << name \
<< "has been called without the image" \
"being locked";
#else
#define SANITY_CHECK_LOCKED(name)
#endif
struct KisImageSPStaticRegistrar {
KisImageSPStaticRegistrar() {
qRegisterMetaType<KisImageSP>("KisImageSP");
}
};
static KisImageSPStaticRegistrar __registrar;
class KisImage::KisImagePrivate
{
public:
KisImagePrivate(KisImage *_q, qint32 w, qint32 h,
const KoColorSpace *c,
KisUndoStore *undo,
KisImageAnimationInterface *_animationInterface)
: q(_q)
, lockedForReadOnly(false)
, width(w)
, height(h)
, colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8())
, nserver(1)
, undoStore(undo ? undo : new KisDumbUndoStore())
, legacyUndoAdapter(undoStore.data(), _q)
, postExecutionUndoAdapter(undoStore.data(), _q)
, signalRouter(_q)
, animationInterface(_animationInterface)
, scheduler(_q, _q)
, axesCenter(QPointF(0.5, 0.5))
{
{
KisImageConfig cfg(true);
if (cfg.enableProgressReporting()) {
scheduler.setProgressProxy(&compositeProgressProxy);
}
// Each of these lambdas defines a new factory function.
scheduler.setLod0ToNStrokeStrategyFactory(
[=](bool forgettable) {
return KisLodSyncPair(
new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable),
KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q)));
});
scheduler.setSuspendUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
});
scheduler.setResumeUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
});
}
connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
}
~KisImagePrivate() {
/**
* Stop animation interface. It may use the rootLayer.
*/
delete animationInterface;
/**
* First delete the nodes, while strokes
* and undo are still alive
*/
rootLayer.clear();
}
KisImage *q;
quint32 lockCount = 0;
bool lockedForReadOnly;
qint32 width;
qint32 height;
double xres = 1.0;
double yres = 1.0;
const KoColorSpace * colorSpace;
KisProofingConfigurationSP proofingConfig;
KisSelectionSP deselectedGlobalSelection;
KisGroupLayerSP rootLayer; // The layers are contained in here
KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask
KisSelectionMaskSP overlaySelectionMask;
QList<KisLayerCompositionSP> compositions;
KisNodeSP isolatedRootNode;
bool wrapAroundModePermitted = false;
KisNameServer nserver;
QScopedPointer<KisUndoStore> undoStore;
KisLegacyUndoAdapter legacyUndoAdapter;
KisPostExecutionUndoAdapter postExecutionUndoAdapter;
vKisAnnotationSP annotations;
QAtomicInt disableUIUpdateSignals;
KisLocklessStack<QRect> savedDisabledUIUpdates;
KisProjectionUpdatesFilterSP projectionUpdatesFilter;
KisImageSignalRouter signalRouter;
KisImageAnimationInterface *animationInterface;
KisUpdateScheduler scheduler;
QAtomicInt disableDirtyRequests;
KisCompositeProgressProxy compositeProgressProxy;
bool blockLevelOfDetail = false;
QPointF axesCenter;
+ bool allowMasksOnRootNode = false;
bool tryCancelCurrentStrokeAsync();
void notifyProjectionUpdatedInPatches(const QRect &rc);
};
KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name)
: QObject(0)
, KisShared()
, m_d(new KisImagePrivate(this, width, height,
colorSpace, undoStore,
new KisImageAnimationInterface(this)))
{
// make sure KisImage belongs to the GUI thread
moveToThread(qApp->thread());
connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
setObjectName(name);
setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
}
KisImage::~KisImage()
{
dbgImage << "deleting kisimage" << objectName();
/**
* Request the tools to end currently running strokes
*/
waitForDone();
delete m_d;
disconnect(); // in case Qt gets confused
}
KisImage *KisImage::clone(bool exactCopy)
{
return new KisImage(*this, 0, exactCopy);
}
-KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
- : KisNodeFacade(),
- KisNodeGraphListener(),
- KisShared(),
- m_d(new KisImagePrivate(this,
- rhs.width(), rhs.height(),
- rhs.colorSpace(),
- undoStore ? undoStore : new KisDumbUndoStore(),
- new KisImageAnimationInterface(*rhs.animationInterface(), this)))
+void KisImage::copyFromImage(const KisImage &rhs)
{
- // make sure KisImage belongs to the GUI thread
- moveToThread(qApp->thread());
- connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
+ copyFromImageImpl(rhs, REPLACE);
+}
+void KisImage::copyFromImageImpl(const KisImage &rhs, int policy)
+{
+ // make sure we choose exactly one from REPLACE and CONSTRUCT
+ KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT));
+
+ // only when replacing do we need to emit signals
+#define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit
+
+ if (policy & REPLACE) { // if we are constructing the image, these are already set
+ if (m_d->width != rhs.width() || m_d->height != rhs.height()) {
+ m_d->width = rhs.width();
+ m_d->height = rhs.height();
+ emit sigSizeChanged(QPointF(), QPointF());
+ }
+ if (m_d->colorSpace != rhs.colorSpace()) {
+ m_d->colorSpace = rhs.colorSpace();
+ emit sigColorSpaceChanged(m_d->colorSpace);
+ }
+ }
+
+ // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool)
setObjectName(rhs.objectName());
- m_d->xres = rhs.m_d->xres;
- m_d->yres = rhs.m_d->yres;
+ if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) {
+ m_d->xres = rhs.m_d->xres;
+ m_d->yres = rhs.m_d->yres;
+ EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres);
+ }
+ m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode;
if (rhs.m_d->proofingConfig) {
- m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
+ KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
+ if (policy & REPLACE) {
+ setProofingConfiguration(proofingConfig);
+ } else {
+ m_d->proofingConfig = proofingConfig;
+ }
}
KisNodeSP newRoot = rhs.root()->clone();
newRoot->setGraphListener(this);
newRoot->setImage(this);
m_d->rootLayer = dynamic_cast<KisGroupLayer*>(newRoot.data());
setRoot(newRoot);
+ bool exactCopy = policy & EXACT_COPY;
+
if (exactCopy || rhs.m_d->isolatedRootNode) {
QQueue<KisNodeSP> linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.root(),
- [&linearizedNodes](KisNodeSP node) {
- linearizedNodes.enqueue(node);
- });
+ [&linearizedNodes](KisNodeSP node) {
+ linearizedNodes.enqueue(node);
+ });
KisLayerUtils::recursiveApplyNodes(newRoot,
- [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) {
- KisNodeSP refNode = linearizedNodes.dequeue();
-
- if (exactCopy) {
- node->setUuid(refNode->uuid());
- }
+ [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) {
+ KisNodeSP refNode = linearizedNodes.dequeue();
- if (rhs.m_d->isolatedRootNode &&
- rhs.m_d->isolatedRootNode == refNode) {
+ if (exactCopy) {
+ node->setUuid(refNode->uuid());
+ }
- m_d->isolatedRootNode = node;
- }
- });
+ if (rhs.m_d->isolatedRootNode &&
+ rhs.m_d->isolatedRootNode == refNode) {
+ m_d->isolatedRootNode = node;
+ }
+ });
}
+ KisLayerUtils::recursiveApplyNodes(newRoot,
+ [](KisNodeSP node) {
+ dbgImage << "Node: " << (void *)node.data();
+ });
+
+ m_d->compositions.clear();
+
Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
}
- rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver);
+ EMIT_IF_NEEDED sigLayersChangedAsync();
+
+ m_d->nserver = rhs.m_d->nserver;
vKisAnnotationSP newAnnotations;
Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
newAnnotations << annotation->clone();
}
m_d->annotations = newAnnotations;
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests);
m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
/**
* The overlay device is not inherited when cloning the image!
*/
if (rhs.m_d->overlaySelectionMask) {
const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent();
m_d->rootLayer->setDirty(dirtyRect);
}
+#undef EMIT_IF_NEEDED
+}
+
+KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
+ : KisNodeFacade(),
+ KisNodeGraphListener(),
+ KisShared(),
+ m_d(new KisImagePrivate(this,
+ rhs.width(), rhs.height(),
+ rhs.colorSpace(),
+ undoStore ? undoStore : new KisDumbUndoStore(),
+ new KisImageAnimationInterface(*rhs.animationInterface(), this)))
+{
+ // make sure KisImage belongs to the GUI thread
+ moveToThread(qApp->thread());
+ connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
+
+ copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0));
}
void KisImage::aboutToAddANode(KisNode *parent, int index)
{
KisNodeGraphListener::aboutToAddANode(parent, index);
SANITY_CHECK_LOCKED("aboutToAddANode");
}
void KisImage::nodeHasBeenAdded(KisNode *parent, int index)
{
KisNodeGraphListener::nodeHasBeenAdded(parent, index);
SANITY_CHECK_LOCKED("nodeHasBeenAdded");
m_d->signalRouter.emitNodeHasBeenAdded(parent, index);
}
void KisImage::aboutToRemoveANode(KisNode *parent, int index)
{
KisNodeSP deletedNode = parent->at(index);
if (!dynamic_cast<KisSelectionMask*>(deletedNode.data()) &&
deletedNode == m_d->isolatedRootNode) {
emit sigInternalStopIsolatedModeRequested();
}
KisNodeGraphListener::aboutToRemoveANode(parent, index);
SANITY_CHECK_LOCKED("aboutToRemoveANode");
m_d->signalRouter.emitAboutToRemoveANode(parent, index);
}
void KisImage::nodeChanged(KisNode* node)
{
KisNodeGraphListener::nodeChanged(node);
requestStrokeEnd();
m_d->signalRouter.emitNodeChanged(node);
}
void KisImage::invalidateAllFrames()
{
invalidateFrames(KisTimeRange::infinite(0), QRect());
}
void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask)
{
if (m_d->targetOverlaySelectionMask == mask) return;
m_d->targetOverlaySelectionMask = mask;
struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy {
UpdateOverlaySelectionStroke(KisImageSP image)
: KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")),
m_image(image)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask;
KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask;
if (oldMask == newMask) return;
KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image);
m_image->m_d->overlaySelectionMask = newMask;
if (oldMask || newMask) {
m_image->m_d->rootLayer->notifyChildMaskChanged();
}
if (oldMask) {
m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent());
}
if (newMask) {
newMask->setDirty();
}
m_image->undoAdapter()->emitSelectionChanged();
}
private:
KisImageSP m_image;
};
KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this));
endStroke(id);
}
KisSelectionMaskSP KisImage::overlaySelectionMask() const
{
return m_d->overlaySelectionMask;
}
bool KisImage::hasOverlaySelectionMask() const
{
return m_d->overlaySelectionMask;
}
KisSelectionSP KisImage::globalSelection() const
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
return selectionMask->selection();
} else {
return 0;
}
}
void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (!globalSelection) {
if (selectionMask) {
removeNode(selectionMask);
}
}
else {
if (!selectionMask) {
selectionMask = new KisSelectionMask(this);
selectionMask->initSelection(m_d->rootLayer);
addNode(selectionMask);
// If we do not set the selection now, the setActive call coming next
// can be very, very expensive, depending on the size of the image.
selectionMask->setSelection(globalSelection);
selectionMask->setActive(true);
}
else {
selectionMask->setSelection(globalSelection);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0);
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask());
}
m_d->deselectedGlobalSelection = 0;
m_d->legacyUndoAdapter.emitSelectionChanged();
}
void KisImage::deselectGlobalSelection()
{
KisSelectionSP savedSelection = globalSelection();
setGlobalSelection(0);
m_d->deselectedGlobalSelection = savedSelection;
}
bool KisImage::canReselectGlobalSelection()
{
return m_d->deselectedGlobalSelection;
}
void KisImage::reselectGlobalSelection()
{
if(m_d->deselectedGlobalSelection) {
setGlobalSelection(m_d->deselectedGlobalSelection);
}
}
QString KisImage::nextLayerName(const QString &_baseName) const
{
QString baseName = _baseName;
if (m_d->nserver.currentSeed() == 0) {
m_d->nserver.number();
return i18n("background");
}
if (baseName.isEmpty()) {
baseName = i18n("Layer");
}
return QString("%1 %2").arg(baseName).arg(m_d->nserver.number());
}
void KisImage::rollBackLayerName()
{
m_d->nserver.rollback();
}
KisCompositeProgressProxy* KisImage::compositeProgressProxy()
{
return &m_d->compositeProgressProxy;
}
bool KisImage::locked() const
{
return m_d->lockCount != 0;
}
void KisImage::barrierLock(bool readOnly)
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.barrierLock();
m_d->lockedForReadOnly = readOnly;
} else {
m_d->lockedForReadOnly &= readOnly;
}
m_d->lockCount++;
}
bool KisImage::tryBarrierLock(bool readOnly)
{
bool result = true;
if (!locked()) {
result = m_d->scheduler.tryBarrierLock();
m_d->lockedForReadOnly = readOnly;
}
if (result) {
m_d->lockCount++;
m_d->lockedForReadOnly &= readOnly;
}
return result;
}
bool KisImage::isIdle(bool allowLocked)
{
return (allowLocked || !locked()) && m_d->scheduler.isIdle();
}
void KisImage::lock()
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.lock();
}
m_d->lockCount++;
m_d->lockedForReadOnly = false;
}
void KisImage::unlock()
{
Q_ASSERT(locked());
if (locked()) {
m_d->lockCount--;
if (m_d->lockCount == 0) {
m_d->scheduler.unlock(!m_d->lockedForReadOnly);
}
}
}
void KisImage::blockUpdates()
{
m_d->scheduler.blockUpdates();
}
void KisImage::unblockUpdates()
{
m_d->scheduler.unblockUpdates();
}
void KisImage::setSize(const QSize& size)
{
m_d->width = size.width();
m_d->height = size.height();
}
void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
{
if (newRect == bounds() && !cropLayers) return;
KUndo2MagicString actionName = cropLayers ?
kundo2_i18n("Crop Image") :
kundo2_i18n("Resize Image");
KisImageSignalVector emitSignals;
emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(cropLayers ?
KisCropSavedExtraData::CROP_IMAGE :
KisCropSavedExtraData::RESIZE_IMAGE,
newRect);
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE |
KisProcessingApplicator::NO_UI_UPDATES,
emitSignals, actionName, extraData);
if (cropLayers || !newRect.topLeft().isNull()) {
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, cropLayers, true);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
applicator.end();
}
void KisImage::resizeImage(const QRect& newRect)
{
resizeImageImpl(newRect, false);
}
void KisImage::cropImage(const QRect& newRect)
{
resizeImageImpl(newRect, true);
}
void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
{
bool isLayer = qobject_cast<KisLayer*>(node.data());
KUndo2MagicString actionName = isLayer ?
kundo2_i18n("Crop Layer") :
kundo2_i18n("Crop Mask");
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER,
newRect, node);
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName, extraData);
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, true, false);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
{
bool resolutionChanged = xres != xRes() && yres != yRes();
bool sizeChanged = size != this->size();
if (!resolutionChanged && !sizeChanged) return;
KisImageSignalVector emitSignals;
if (resolutionChanged) emitSignals << ResolutionChangedSignal;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
emitSignals << ModifiedSignal;
KUndo2MagicString actionName = sizeChanged ?
kundo2_i18n("Scale Image") :
kundo2_i18n("Change Image Resolution");
KisProcessingApplicator::ProcessingFlags signalFlags =
(resolutionChanged || sizeChanged) ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
qreal sx = qreal(size.width()) / this->size().width();
qreal sy = qreal(size.height()) / this->size().height();
QTransform shapesCorrection;
if (resolutionChanged) {
shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
}
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(sx, sy,
0, 0,
QPointF(),
0,
0, 0,
filterStrategy,
shapesCorrection);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (resolutionChanged) {
KUndo2Command *parent =
new KisResetShapesCommand(m_d->rootLayer);
new KisImageSetResolutionCommand(this, xres, yres, parent);
applicator.applyCommand(parent);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, size));
}
applicator.end();
}
void KisImage::scaleNode(KisNodeSP node, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
{
KUndo2MagicString actionName(kundo2_i18n("Scale Layer"));
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
QPointF offset;
{
KisTransformWorker worker(0,
scaleX, scaleY,
0, 0, 0, 0,
0.0,
0, 0, 0, 0);
QTransform transform = worker.transform();
offset = center - transform.map(center);
}
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(scaleX, scaleY,
0, 0,
QPointF(),
0,
offset.x(), offset.y(),
filterStrategy);
visitor->setSelection(selection);
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.end();
}
void KisImage::rotateImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
double radians,
bool resizeImage,
KisSelectionSP selection)
{
// we can either transform (and resize) the whole image or
// transform a selection, we cannot do both at the same time
KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) {
selection = 0;
}
const QRect baseBounds =
resizeImage ? bounds() :
selection ? selection->selectedExactRect() :
rootNode->exactBounds();
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
0, 0, 0, 0,
radians,
0, 0, 0, 0);
QTransform transform = worker.transform();
if (resizeImage) {
QRect newRect = transform.mapRect(baseBounds);
newSize = newRect.size();
offset = -newRect.topLeft();
}
else {
QPointF origin = QRectF(baseBounds).center();
newSize = size();
offset = -(transform.map(origin) - origin);
}
}
bool sizeChanged = resizeImage &&
(newSize.width() != baseBounds.width() ||
newSize.height() != baseBounds.height());
// These signals will be emitted after processing is done
KisImageSignalVector emitSignals;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
emitSignals << ModifiedSignal;
// These flags determine whether updates are transferred to the UI during processing
KisProcessingApplicator::ProcessingFlags signalFlags =
sizeChanged ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, rootNode,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic");
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
QPointF(),
radians,
offset.x(), offset.y(),
filter);
if (selection) {
visitor->setSelection(selection);
}
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::rotateImage(double radians)
{
rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0);
}
void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection)
{
if (node->inherits("KisMask")) {
rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection);
} else {
rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection);
}
}
void KisImage::shearImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
bool resizeImage,
double angleX, double angleY,
KisSelectionSP selection)
{
const QRect baseBounds =
resizeImage ? bounds() :
selection ? selection->selectedExactRect() :
rootNode->exactBounds();
const QPointF origin = QRectF(baseBounds).center();
//angleX, angleY are in degrees
const qreal pi = 3.1415926535897932385;
const qreal deg2rad = pi / 180.0;
qreal tanX = tan(angleX * deg2rad);
qreal tanY = tan(angleY * deg2rad);
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
tanX, tanY, origin.x(), origin.y(),
0,
0, 0, 0, 0);
QRect newRect = worker.transform().mapRect(baseBounds);
newSize = newRect.size();
if (resizeImage) offset = -newRect.topLeft();
}
if (newSize == baseBounds.size()) return;
KisImageSignalVector emitSignals;
if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
emitSignals << ModifiedSignal;
KisProcessingApplicator::ProcessingFlags signalFlags =
KisProcessingApplicator::RECURSIVE;
if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES;
KisProcessingApplicator applicator(this, rootNode,
signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear");
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(1.0, 1.0,
tanX, tanY, origin,
0,
offset.x(), offset.y(),
filter);
if (selection) {
visitor->setSelection(selection);
}
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
if (resizeImage) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection)
{
if (node->inherits("KisMask")) {
shearImpl(kundo2_i18n("Shear Mask"), node, false,
angleX, angleY, selection);
} else {
shearImpl(kundo2_i18n("Shear Layer"), node, false,
angleX, angleY, selection);
}
}
void KisImage::shear(double angleX, double angleY)
{
shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
angleX, angleY, 0);
}
void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
if (!dstColorSpace) return;
const KoColorSpace *srcColorSpace = m_d->colorSpace;
undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
m_d->rootLayer->accept(visitor);
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
bool KisImage::assignImageProfile(const KoColorProfile *profile)
{
if (!profile) return false;
const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
const KoColorSpace *srcCs = colorSpace();
if (!dstCs) return false;
m_d->colorSpace = dstCs;
KisChangeProfileVisitor visitor(srcCs, dstCs);
bool retval = m_d->rootLayer->accept(visitor);
m_d->signalRouter.emitNotification(ProfileChangedSignal);
return retval;
}
void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace)
{
if (*m_d->colorSpace == *dstColorSpace) return;
undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace)
{
m_d->colorSpace = colorSpace;
m_d->rootLayer->resetCache();
m_d->signalRouter.emitNotification(ColorSpaceChangedSignal);
}
const KoColorSpace * KisImage::colorSpace() const
{
return m_d->colorSpace;
}
const KoColorProfile * KisImage::profile() const
{
return colorSpace()->profile();
}
double KisImage::xRes() const
{
return m_d->xres;
}
double KisImage::yRes() const
{
return m_d->yres;
}
void KisImage::setResolution(double xres, double yres)
{
m_d->xres = xres;
m_d->yres = yres;
m_d->signalRouter.emitNotification(ResolutionChangedSignal);
}
QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
{
return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
}
QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const
{
QPointF pixelCoord = documentToPixel(documentCoord);
return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y()));
}
QRectF KisImage::documentToPixel(const QRectF &documentRect) const
{
return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
}
QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
{
return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
}
QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
{
return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
}
QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
{
return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
}
qint32 KisImage::width() const
{
return m_d->width;
}
qint32 KisImage::height() const
{
return m_d->height;
}
KisGroupLayerSP KisImage::rootLayer() const
{
Q_ASSERT(m_d->rootLayer);
return m_d->rootLayer;
}
KisPaintDeviceSP KisImage::projection() const
{
if (m_d->isolatedRootNode) {
return m_d->isolatedRootNode->projection();
}
Q_ASSERT(m_d->rootLayer);
KisPaintDeviceSP projection = m_d->rootLayer->projection();
Q_ASSERT(projection);
return projection;
}
qint32 KisImage::nlayers() const
{
QStringList list;
list << "KisLayer";
KisCountVisitor visitor(list, KoProperties());
m_d->rootLayer->accept(visitor);
return visitor.count();
}
qint32 KisImage::nHiddenLayers() const
{
QStringList list;
list << "KisLayer";
KoProperties properties;
properties.setProperty("visible", false);
KisCountVisitor visitor(list, properties);
m_d->rootLayer->accept(visitor);
return visitor.count();
}
void KisImage::flatten(KisNodeSP activeNode)
{
KisLayerUtils::flattenImage(this, activeNode);
}
void KisImage::mergeMultipleLayers(QList<KisNodeSP> mergedNodes, KisNodeSP putAfter)
{
if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) {
KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter);
}
}
void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
KisLayerUtils::mergeDown(this, layer, strategy);
}
void KisImage::flattenLayer(KisLayerSP layer)
{
KisLayerUtils::flattenLayer(this, layer);
}
void KisImage::setModified()
{
m_d->signalRouter.emitNotification(ModifiedSignal);
}
QImage KisImage::convertToQImage(QRect imageRect,
const KoColorProfile * profile)
{
qint32 x;
qint32 y;
qint32 w;
qint32 h;
imageRect.getRect(&x, &y, &w, &h);
return convertToQImage(x, y, w, h, profile);
}
QImage KisImage::convertToQImage(qint32 x,
qint32 y,
qint32 w,
qint32 h,
const KoColorProfile * profile)
{
KisPaintDeviceSP dev = projection();
if (!dev) return QImage();
QImage image = dev->convertToQImage(const_cast<KoColorProfile*>(profile), x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return image;
}
QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
{
if (scaledImageSize.isEmpty()) {
return QImage();
}
KisPaintDeviceSP dev = new KisPaintDevice(colorSpace());
KisPainter gc;
gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
gc.end();
double scaleX = qreal(scaledImageSize.width()) / width();
double scaleY = qreal(scaledImageSize.height()) / height();
QPointer<KoUpdater> updater = new KoDummyUpdater();
KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
worker.run();
delete updater;
return dev->convertToQImage(profile);
}
void KisImage::notifyLayersChanged()
{
m_d->signalRouter.emitNotification(LayersChangedSignal);
}
QRect KisImage::bounds() const
{
return QRect(0, 0, width(), height());
}
QRect KisImage::effectiveLodBounds() const
{
QRect boundRect = bounds();
const int lod = currentLevelOfDetail();
if (lod > 0) {
KisLodTransform t(lod);
boundRect = t.map(boundRect);
}
return boundRect;
}
KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const
{
const int lod = currentLevelOfDetail();
return lod > 0 ?
m_d->scheduler.lodNPostExecutionUndoAdapter() :
&m_d->postExecutionUndoAdapter;
}
void KisImage::setUndoStore(KisUndoStore *undoStore)
{
m_d->legacyUndoAdapter.setUndoStore(undoStore);
m_d->postExecutionUndoAdapter.setUndoStore(undoStore);
m_d->undoStore.reset(undoStore);
}
KisUndoStore* KisImage::undoStore()
{
return m_d->undoStore.data();
}
KisUndoAdapter* KisImage::undoAdapter() const
{
return &m_d->legacyUndoAdapter;
}
void KisImage::setDefaultProjectionColor(const KoColor &color)
{
KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer);
m_d->rootLayer->setDefaultProjectionColor(color);
}
KoColor KisImage::defaultProjectionColor() const
{
KIS_ASSERT_RECOVER(m_d->rootLayer) {
return KoColor(Qt::transparent, m_d->colorSpace);
}
return m_d->rootLayer->defaultProjectionColor();
}
void KisImage::setRootLayer(KisGroupLayerSP rootLayer)
{
emit sigInternalStopIsolatedModeRequested();
KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace);
if (m_d->rootLayer) {
m_d->rootLayer->setGraphListener(0);
m_d->rootLayer->disconnect();
KisPaintDeviceSP original = m_d->rootLayer->original();
defaultProjectionColor = original->defaultPixel();
}
m_d->rootLayer = rootLayer;
m_d->rootLayer->disconnect();
m_d->rootLayer->setGraphListener(this);
m_d->rootLayer->setImage(this);
setRoot(m_d->rootLayer.data());
this->setDefaultProjectionColor(defaultProjectionColor);
}
void KisImage::addAnnotation(KisAnnotationSP annotation)
{
// Find the icc annotation, if there is one
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == annotation->type()) {
*it = annotation;
return;
}
++it;
}
m_d->annotations.push_back(annotation);
}
KisAnnotationSP KisImage::annotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
return *it;
}
++it;
}
return KisAnnotationSP(0);
}
void KisImage::removeAnnotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
m_d->annotations.erase(it);
return;
}
++it;
}
}
vKisAnnotationSP_it KisImage::beginAnnotations()
{
return m_d->annotations.begin();
}
vKisAnnotationSP_it KisImage::endAnnotations()
{
return m_d->annotations.end();
}
void KisImage::notifyAboutToBeDeleted()
{
emit sigAboutToBeDeleted();
}
KisImageSignalRouter* KisImage::signalRouter()
{
return &m_d->signalRouter;
}
void KisImage::waitForDone()
{
requestStrokeEnd();
m_d->scheduler.waitForDone();
}
KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy)
{
/**
* Ask open strokes to end gracefully. All the strokes clients
* (including the one calling this method right now) will get
* a notification that they should probably end their strokes.
* However this is purely their choice whether to end a stroke
* or not.
*/
if (strokeStrategy->requestsOtherStrokesToEnd()) {
requestStrokeEnd();
}
/**
* Some of the strokes can cancel their work with undoing all the
* changes they did to the paint devices. The problem is that undo
* stack will know nothing about it. Therefore, just notify it
* explicitly
*/
if (strokeStrategy->clearsRedoOnStart()) {
m_d->undoStore->purgeRedoState();
}
return m_d->scheduler.startStroke(strokeStrategy);
}
void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc)
{
KisImageConfig imageConfig(true);
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < rc.height(); y += patchHeight) {
for (int x = 0; x < rc.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
patchRect &= rc;
QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
}
}
}
bool KisImage::startIsolatedMode(KisNodeSP node)
{
struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy {
StartIsolatedModeStroke(KisNodeSP node, KisImageSP image)
: KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")),
m_node(node),
m_image(image)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
// pass-though node don't have any projection prepared, so we should
// explicitly regenerate it before activating isolated mode.
m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
m_image->m_d->isolatedRootNode = m_node;
emit m_image->sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds());
m_image->invalidateAllFrames();
}
private:
KisNodeSP m_node;
KisImageSP m_image;
};
KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this));
endStroke(id);
return true;
}
void KisImage::stopIsolatedMode()
{
if (!m_d->isolatedRootNode) return;
struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy {
StopIsolatedModeStroke(KisImageSP image)
: KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")),
m_image(image)
{
this->enableJob(JOB_INIT);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
if (!m_image->m_d->isolatedRootNode) return;
//KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode;
m_image->m_d->isolatedRootNode = 0;
emit m_image->sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds());
m_image->invalidateAllFrames();
// TODO: Substitute notifyProjectionUpdated() with this code
// when update optimization is implemented
//
// QRect updateRect = bounds() | oldRootNode->extent();
// oldRootNode->setDirty(updateRect);
}
private:
KisImageSP m_image;
};
KisStrokeId id = startStroke(new StopIsolatedModeStroke(this));
endStroke(id);
}
KisNodeSP KisImage::isolatedModeRoot() const
{
return m_d->isolatedRootNode;
}
void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data)
{
KisUpdateTimeMonitor::instance()->reportJobStarted(data);
m_d->scheduler.addJob(id, data);
}
void KisImage::endStroke(KisStrokeId id)
{
m_d->scheduler.endStroke(id);
}
bool KisImage::cancelStroke(KisStrokeId id)
{
return m_d->scheduler.cancelStroke(id);
}
bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync()
{
return scheduler.tryCancelCurrentStrokeAsync();
}
void KisImage::requestUndoDuringStroke()
{
emit sigUndoDuringStrokeRequested();
}
void KisImage::requestStrokeCancellation()
{
if (!m_d->tryCancelCurrentStrokeAsync()) {
emit sigStrokeCancellationRequested();
}
}
UndoResult KisImage::tryUndoUnfinishedLod0Stroke()
{
return m_d->scheduler.tryUndoLastStrokeAsync();
}
void KisImage::requestStrokeEnd()
{
emit sigStrokeEndRequested();
emit sigStrokeEndRequestedActiveNodeFiltered();
}
void KisImage::requestStrokeEndActiveNode()
{
emit sigStrokeEndRequested();
}
void KisImage::refreshGraph(KisNodeSP root)
{
refreshGraph(root, bounds(), bounds());
}
void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefresh(root, rc, cropRect);
}
void KisImage::initialRefreshGraph()
{
/**
* NOTE: Tricky part. We set crop rect to null, so the clones
* will not rely on precalculated projections of their sources
*/
refreshGraphAsync(0, bounds(), QRect());
waitForDone();
}
void KisImage::refreshGraphAsync(KisNodeSP root)
{
refreshGraphAsync(root, bounds(), bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc)
{
refreshGraphAsync(root, rc, bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefreshAsync(root, rc, cropRect);
}
void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect)
{
KIS_ASSERT_RECOVER_RETURN(pseudoFilthy);
m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false);
m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect);
}
void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->scheduler.addSpontaneousJob(spontaneousJob);
}
void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)
{
// update filters are *not* recursive!
KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter);
m_d->projectionUpdatesFilter = filter;
}
KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const
{
return m_d->projectionUpdatesFilter;
}
void KisImage::disableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter()));
}
void KisImage::enableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP());
}
void KisImage::disableUIUpdates()
{
m_d->disableUIUpdateSignals.ref();
}
QVector<QRect> KisImage::enableUIUpdates()
{
m_d->disableUIUpdateSignals.deref();
QRect rect;
QVector<QRect> postponedUpdates;
while (m_d->savedDisabledUIUpdates.pop(rect)) {
postponedUpdates.append(rect);
}
return postponedUpdates;
}
void KisImage::notifyProjectionUpdated(const QRect &rc)
{
KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc);
if (!m_d->disableUIUpdateSignals) {
int lod = currentLevelOfDetail();
QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
if (dirtyRect.isEmpty()) return;
emit sigImageUpdated(dirtyRect);
} else {
m_d->savedDisabledUIUpdates.push(rc);
}
}
void KisImage::setWorkingThreadsLimit(int value)
{
m_d->scheduler.setThreadsLimit(value);
}
int KisImage::workingThreadsLimit() const
{
return m_d->scheduler.threadsLimit();
}
void KisImage::notifySelectionChanged()
{
/**
* The selection is calculated asynchromously, so it is not
* handled by disableUIUpdates() and other special signals of
* KisImageSignalRouter
*/
m_d->legacyUndoAdapter.emitSelectionChanged();
/**
* Editing of selection masks doesn't necessary produce a
* setDirty() call, so in the end of the stroke we need to request
* direct update of the UI's cache.
*/
if (m_d->isolatedRootNode &&
dynamic_cast<KisSelectionMask*>(m_d->isolatedRootNode.data())) {
notifyProjectionUpdated(bounds());
}
}
void KisImage::requestProjectionUpdateImpl(KisNode *node,
const QVector<QRect> &rects,
const QRect &cropRect)
{
if (rects.isEmpty()) return;
m_d->scheduler.updateProjection(node, rects, cropRect);
}
void KisImage::requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache)
{
if (m_d->projectionUpdatesFilter
&& m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) {
return;
}
if (resetAnimationCache) {
m_d->animationInterface->notifyNodeChanged(node, rects, false);
}
/**
* Here we use 'permitted' instead of 'active' intentively,
* because the updates may come after the actual stroke has been
* finished. And having some more updates for the stroke not
* supporting the wrap-around mode will not make much harm.
*/
if (m_d->wrapAroundModePermitted) {
QVector<QRect> allSplitRects;
const QRect boundRect = effectiveLodBounds();
Q_FOREACH (const QRect &rc, rects) {
KisWrappedRect splitRect(rc, boundRect);
allSplitRects.append(splitRect);
}
requestProjectionUpdateImpl(node, allSplitRects, boundRect);
} else {
requestProjectionUpdateImpl(node, rects, bounds());
}
KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache);
}
void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
m_d->animationInterface->invalidateFrames(range, rect);
}
void KisImage::requestTimeSwitch(int time)
{
m_d->animationInterface->requestTimeSwitchNonGUI(time);
}
KisNode *KisImage::graphOverlayNode() const
{
return m_d->overlaySelectionMask.data();
}
QList<KisLayerCompositionSP> KisImage::compositions()
{
return m_d->compositions;
}
void KisImage::addComposition(KisLayerCompositionSP composition)
{
m_d->compositions.append(composition);
}
void KisImage::removeComposition(KisLayerCompositionSP composition)
{
m_d->compositions.removeAll(composition);
}
bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds)
{
KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(root.data());
if (mask &&
(!bounds.contains(mask->paintDevice()->exactBounds()) ||
mask->selection()->hasShapeSelection())) {
return true;
}
KisNodeSP node = root->firstChild();
while (node) {
if (checkMasksNeedConversion(node, bounds)) {
return true;
}
node = node->nextSibling();
}
return false;
}
void KisImage::setWrapAroundModePermitted(bool value)
{
if (m_d->wrapAroundModePermitted != value) {
requestStrokeEnd();
}
m_d->wrapAroundModePermitted = value;
if (m_d->wrapAroundModePermitted &&
checkMasksNeedConversion(root(), bounds())) {
KisProcessingApplicator applicator(this, root(),
KisProcessingApplicator::RECURSIVE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Crop Selections"));
KisProcessingVisitorSP visitor =
new KisCropSelectionsProcessingVisitor(bounds());
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
}
bool KisImage::wrapAroundModePermitted() const
{
return m_d->wrapAroundModePermitted;
}
bool KisImage::wrapAroundModeActive() const
{
return m_d->wrapAroundModePermitted &&
m_d->scheduler.wrapAroundModeSupported();
}
void KisImage::setDesiredLevelOfDetail(int lod)
{
if (m_d->blockLevelOfDetail) {
qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()"
<< "was called while LoD functionality was being blocked!";
return;
}
m_d->scheduler.setDesiredLevelOfDetail(lod);
}
int KisImage::currentLevelOfDetail() const
{
if (m_d->blockLevelOfDetail) {
return 0;
}
return m_d->scheduler.currentLevelOfDetail();
}
void KisImage::setLevelOfDetailBlocked(bool value)
{
KisImageBarrierLockerRaw l(this);
if (value && !m_d->blockLevelOfDetail) {
m_d->scheduler.setDesiredLevelOfDetail(0);
}
m_d->blockLevelOfDetail = value;
}
void KisImage::explicitRegenerateLevelOfDetail()
{
if (!m_d->blockLevelOfDetail) {
m_d->scheduler.explicitRegenerateLevelOfDetail();
}
}
bool KisImage::levelOfDetailBlocked() const
{
return m_d->blockLevelOfDetail;
}
void KisImage::notifyNodeCollpasedChanged()
{
emit sigNodeCollapsedChanged();
}
KisImageAnimationInterface* KisImage::animationInterface() const
{
return m_d->animationInterface;
}
void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig)
{
m_d->proofingConfig = proofingConfig;
emit sigProofingConfigChanged();
}
KisProofingConfigurationSP KisImage::proofingConfiguration() const
{
if (m_d->proofingConfig) {
return m_d->proofingConfig;
}
return KisProofingConfigurationSP();
}
QPointF KisImage::mirrorAxesCenter() const
{
return m_d->axesCenter;
}
void KisImage::setMirrorAxesCenter(const QPointF &value) const
{
m_d->axesCenter = value;
}
+
+void KisImage::setAllowMasksOnRootNode(bool value)
+{
+ m_d->allowMasksOnRootNode = value;
+}
+
+bool KisImage::allowMasksOnRootNode() const
+{
+ return m_d->allowMasksOnRootNode;
+}
diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h
index 6f9fbee478..d28a034dfe 100644
--- a/libs/image/kis_image.h
+++ b/libs/image/kis_image.h
@@ -1,1127 +1,1154 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMAGE_H_
#define KIS_IMAGE_H_
#include <QObject>
#include <QString>
#include <QPainter>
#include <QRect>
#include <QRegion>
#include <QBitArray>
#include <KoColorConversionTransformation.h>
#include "kis_types.h"
#include "kis_shared.h"
#include "kis_node_graph_listener.h"
#include "kis_node_facade.h"
#include "kis_image_interfaces.h"
#include "kis_strokes_queue_undo_result.h"
#include <kritaimage_export.h>
class KoColorSpace;
class KoColor;
class KisCompositeProgressProxy;
class KisUndoStore;
class KisUndoAdapter;
class KisImageSignalRouter;
class KisPostExecutionUndoAdapter;
class KisFilterStrategy;
class KoColorProfile;
class KisLayerComposition;
class KisSpontaneousJob;
class KisImageAnimationInterface;
class KUndo2MagicString;
class KisProofingConfiguration;
class KisPaintDevice;
namespace KisMetaData
{
class MergeStrategy;
}
/**
* This is the image class, it contains a tree of KisLayer stack and
* meta information about the image. And it also provides some
* functions to manipulate the whole image.
*/
class KRITAIMAGE_EXPORT KisImage : public QObject,
public KisStrokesFacade,
public KisStrokeUndoFacade,
public KisUpdatesFacade,
public KisProjectionUpdateListener,
public KisNodeFacade,
public KisNodeGraphListener,
public KisShared
{
Q_OBJECT
public:
/// @p colorSpace can be null. In that case, it will be initialised to a default color space.
KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name);
~KisImage() override;
public: // KisNodeGraphListener implementation
void aboutToAddANode(KisNode *parent, int index) override;
void nodeHasBeenAdded(KisNode *parent, int index) override;
void aboutToRemoveANode(KisNode *parent, int index) override;
void nodeChanged(KisNode * node) override;
void invalidateAllFrames() override;
void notifySelectionChanged() override;
void requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache) override;
void invalidateFrames(const KisTimeRange &range, const QRect &rect) override;
void requestTimeSwitch(int time) override;
KisNode* graphOverlayNode() const override;
public: // KisProjectionUpdateListener implementation
void notifyProjectionUpdated(const QRect &rc) override;
public:
/**
* Set the number of threads used by the image's working threads
*/
void setWorkingThreadsLimit(int value);
/**
* Return the number of threads available to the image's working threads
*/
int workingThreadsLimit() const;
/**
* Makes a copy of the image with all the layers. If possible, shallow
* copies of the layers are made.
*
* \p exactCopy shows if the copied image should look *exactly* the same as
* the other one (according to it's .kra xml representation). It means that
* the layers will have the same UUID keys and, therefore, you are not
* expected to use the copied image anywhere except for saving. Don't use
* this option if you plan to work with the copied image later.
*/
KisImage *clone(bool exactCopy = false);
+ void copyFromImage(const KisImage &rhs);
+
+private:
+
+ // must specify exactly one from CONSTRUCT or REPLACE.
+ enum CopyPolicy {
+ CONSTRUCT = 1, ///< we are copy-constructing a new KisImage
+ REPLACE = 2, ///< we are replacing the current KisImage with another
+ EXACT_COPY = 4, /// we need an exact copy of the original image
+ };
+
+ void copyFromImageImpl(const KisImage &rhs, int policy);
+
+public:
+
/**
* Render the projection onto a QImage.
*/
QImage convertToQImage(qint32 x1,
qint32 y1,
qint32 width,
qint32 height,
const KoColorProfile * profile);
/**
* Render the projection onto a QImage.
* (this is an overloaded function)
*/
QImage convertToQImage(QRect imageRect,
const KoColorProfile * profile);
/**
* Render a thumbnail of the projection onto a QImage.
*/
QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile);
/**
* [low-level] Lock the image without waiting for all the internal job queues are processed
*
* WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead!
*
* Waits for all the **currently running** internal jobs to complete and locks the image
* for writing. Please note that this function does **not** wait for all the internal
* queues to process, so there might be some non-finished actions pending. It means that
* you just postpone these actions until you unlock() the image back. Until then, then image
* might easily be frozen in some inconsistent state.
*
* The only sane usage for this function is to lock the image for **emergency**
* processing, when some internal action or scheduler got hung up, and you just want
* to fetch some data from the image without races.
*
* In all other cases, please use barrierLock() instead!
*/
void lock();
/**
* Unlocks the image and starts/resumes all the pending internal jobs. If the image
* has been locked for a non-readOnly access, then all the internal caches of the image
* (e.g. lod-planes) are reset and regeneration jobs are scheduled.
*/
void unlock();
/**
* @return return true if the image is in a locked state, i.e. all the internal
* jobs are blocked from execution by calling wither lock() or barrierLock().
*
* When the image is locked, the user can do some modifications to the image
* contents safely without a perspective having race conditions with internal
* image jobs.
*/
bool locked() const;
/**
* Sets the mask (it must be a part of the node hierarchy already) to be paited on
* the top of all layers. This method does all the locking and syncing for you. It
* is executed asynchronously.
*/
void setOverlaySelectionMask(KisSelectionMaskSP mask);
/**
* \see setOverlaySelectionMask
*/
KisSelectionMaskSP overlaySelectionMask() const;
/**
* \see setOverlaySelectionMask
*/
bool hasOverlaySelectionMask() const;
/**
* @return the global selection object or 0 if there is none. The
* global selection is always read-write.
*/
KisSelectionSP globalSelection() const;
/**
* Retrieve the next automatic layername (XXX: fix to add option to return Mask X)
*/
QString nextLayerName(const QString &baseName = "") const;
/**
* Set the automatic layer name counter one back.
*/
void rollBackLayerName();
/**
* @brief start asynchronous operation on resizing the image
*
* The method will resize the image to fit the new size without
* dropping any pixel data. The GUI will get correct
* notification with old and new sizes, so it adjust canvas origin
* accordingly and avoid jumping of the canvas on screen
*
* @param newRect the rectangle of the image which will be visible
* after operation is completed
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void resizeImage(const QRect& newRect);
/**
* @brief start asynchronous operation on cropping the image
*
* The method will **drop** all the image data outside \p newRect
* and resize the image to fit the new size. The GUI will get correct
* notification with old and new sizes, so it adjust canvas origin
* accordingly and avoid jumping of the canvas on screen
*
* @param newRect the rectangle of the image which will be cut-out
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void cropImage(const QRect& newRect);
/**
* @brief start asynchronous operation on cropping a subtree of nodes starting at \p node
*
* The method will **drop** all the layer data outside \p newRect. Neither
* image nor a layer will be moved anywhere
*
* @param node node to crop
* @param newRect the rectangle of the layer which will be cut-out
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void cropNode(KisNodeSP node, const QRect& newRect);
/**
* @brief start asynchronous operation on scaling the image
* @param size new image size in pixels
* @param xres new image x-resolution pixels-per-pt
* @param yres new image y-resolution pixels-per-pt
* @param filterStrategy filtering strategy
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy);
/**
* @brief start asynchronous operation on scaling a subtree of nodes starting at \p node
* @param node node to scale
* @param center the center of the scaling
* @param scaleX x-scale coefficient to be applied to the node
* @param scaleY y-scale coefficient to be applied to the node
* @param filterStrategy filtering strategy
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void scaleNode(KisNodeSP node, const QPointF &center, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection);
/**
* @brief start asynchronous operation on rotating the image
*
* The image is resized to fit the rotated rectangle
*
* @param radians rotation angle in radians
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void rotateImage(double radians);
/**
* @brief start asynchronous operation on rotating a subtree of nodes starting at \p node
*
* The image is not resized!
*
* @param node the root of the subtree to rotate
* @param radians rotation angle in radians
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection);
/**
* @brief start asynchronous operation on shearing the image
*
* The image is resized to fit the sheared polygon
*
* @p angleX, @p angleY are given in degrees.
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void shear(double angleX, double angleY);
/**
* @brief start asynchronous operation on shearing a subtree of nodes starting at \p node
*
* The image is not resized!
*
* @param node the root of the subtree to rotate
* @param angleX x-shear given in degrees.
* @param angleY y-shear given in degrees.
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection);
/**
* Convert the image and all its layers to the dstColorSpace
*/
void convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
/**
* Set the color space of the projection (and the root layer)
* to dstColorSpace. No conversion is done for other layers,
* their colorspace can differ.
* @note No conversion is done, only regeneration, so no rendering
* intent needed
*/
void convertProjectionColorSpace(const KoColorSpace *dstColorSpace);
// Get the profile associated with this image
const KoColorProfile * profile() const;
/**
* Set the profile of the image to the new profile and do the same for
* all layers that have the same colorspace and profile of the image.
* It doesn't do any pixel conversion.
*
* This is essential if you have loaded an image that didn't
* have an embedded profile to which you want to attach the right profile.
*
* This does not create an undo action; only call it when creating or
* loading an image.
*
* @returns false if the profile could not be assigned
*/
bool assignImageProfile(const KoColorProfile *profile);
/**
* Returns the current undo adapter. You can add new commands to the
* undo stack using the adapter. This adapter is used for a backward
* compatibility for old commands created before strokes. It blocks
* all the porcessing at the scheduler, waits until it's finished
* and executes commands exclusively.
*/
KisUndoAdapter* undoAdapter() const;
/**
* This adapter is used by the strokes system. The commands are added
* to it *after* redo() is done (in the scheduler context). They are
* wrapped into a special command and added to the undo stack. redo()
* in not called.
*/
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override;
/**
* Replace current undo store with the new one. The old store
* will be deleted.
* This method is used by KisDocument for dropping all the commands
* during file loading.
*/
void setUndoStore(KisUndoStore *undoStore);
/**
* Return current undo store of the image
*/
KisUndoStore* undoStore();
/**
* Tell the image it's modified; this emits the sigImageModified
* signal. This happens when the image needs to be saved
*/
void setModified();
/**
* The default colorspace of this image: new layers will have this
* colorspace and the projection will have this colorspace.
*/
const KoColorSpace * colorSpace() const;
/**
* X resolution in pixels per pt
*/
double xRes() const;
/**
* Y resolution in pixels per pt
*/
double yRes() const;
/**
* Set the resolution in pixels per pt.
*/
void setResolution(double xres, double yres);
/**
* Convert a document coordinate to a pixel coordinate.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPointF documentToPixel(const QPointF &documentCoord) const;
/**
* Convert a document coordinate to an integer pixel coordinate rounded down.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPoint documentToImagePixelFloored(const QPointF &documentCoord) const;
/**
* Convert a document rectangle to a pixel rectangle.
*
* @param documentRect PostScript Pt rectangle to convert.
*/
QRectF documentToPixel(const QRectF &documentRect) const;
/**
* Convert a pixel coordinate to a document coordinate.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPointF &pixelCoord) const;
/**
* Convert an integer pixel coordinate to a document coordinate.
* The document coordinate is at the centre of the pixel.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPoint &pixelCoord) const;
/**
* Convert a document rectangle to an integer pixel rectangle.
*
* @param pixelCoord pixel coordinate to convert.
*/
QRectF pixelToDocument(const QRectF &pixelCoord) const;
/**
* Return the width of the image
*/
qint32 width() const;
/**
* Return the height of the image
*/
qint32 height() const;
/**
* Return the size of the image
*/
QSize size() const {
return QSize(width(), height());
}
/**
* @return the root node of the image node graph
*/
KisGroupLayerSP rootLayer() const;
/**
* Return the projection; that is, the complete, composited
* representation of this image.
*/
KisPaintDeviceSP projection() const;
/**
* Return the number of layers (not other nodes) that are in this
* image.
*/
qint32 nlayers() const;
/**
* Return the number of layers (not other node types) that are in
* this image and that are hidden.
*/
qint32 nHiddenLayers() const;
/**
* Merge all visible layers and discard hidden ones.
*/
void flatten(KisNodeSP activeNode);
/**
* Merge the specified layer with the layer
* below this layer, remove the specified layer.
*/
void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy);
/**
* flatten the layer: that is, the projection becomes the layer
* and all subnodes are removed. If this is not a paint layer, it will morph
* into a paint layer.
*/
void flattenLayer(KisLayerSP layer);
/**
* Merges layers in \p mergedLayers and creates a new layer above
* \p putAfter
*/
void mergeMultipleLayers(QList<KisNodeSP> mergedLayers, KisNodeSP putAfter);
/// @return the exact bounds of the image in pixel coordinates.
QRect bounds() const;
/**
* Returns the actual bounds of the image, taking LevelOfDetail
* into account. This value is used as a bounds() value of
* KisDefaultBounds object.
*/
QRect effectiveLodBounds() const;
/// use if the layers have changed _completely_ (eg. when flattening)
void notifyLayersChanged();
/**
* Sets the default color of the root layer projection. All the layers
* will be merged on top of this very color
*/
void setDefaultProjectionColor(const KoColor &color);
/**
* \see setDefaultProjectionColor()
*/
KoColor defaultProjectionColor() const;
void setRootLayer(KisGroupLayerSP rootLayer);
/**
* Add an annotation for this image. This can be anything: Gamma, EXIF, etc.
* Note that the "icc" annotation is reserved for the color strategies.
* If the annotation already exists, overwrite it with this one.
*/
void addAnnotation(KisAnnotationSP annotation);
/** get the annotation with the given type, can return 0 */
KisAnnotationSP annotation(const QString& type);
/** delete the annotation, if the image contains it */
void removeAnnotation(const QString& type);
/**
* Start of an iteration over the annotations of this image (including the ICC Profile)
*/
vKisAnnotationSP_it beginAnnotations();
/** end of an iteration over the annotations of this image */
vKisAnnotationSP_it endAnnotations();
/**
* Called before the image is deleted and sends the sigAboutToBeDeleted signal
*/
void notifyAboutToBeDeleted();
KisImageSignalRouter* signalRouter();
/**
* Returns whether we can reselect current global selection
*
* \see reselectGlobalSelection()
*/
bool canReselectGlobalSelection();
/**
* Returns the layer compositions for the image
*/
QList<KisLayerCompositionSP> compositions();
/**
* Adds a new layer composition, will be saved with the image
*/
void addComposition(KisLayerCompositionSP composition);
/**
* Remove the layer compostion
*/
void removeComposition(KisLayerCompositionSP composition);
/**
* Permit or deny the wrap-around mode for all the paint devices
* of the image. Note that permitting the wraparound mode will not
* necessarily activate it right now. To be activated the wrap
* around mode should be 1) permitted; 2) supported by the
* currently running stroke.
*/
void setWrapAroundModePermitted(bool value);
/**
* \return whether the wrap-around mode is permitted for this
* image. If the wrap around mode is permitted and the
* currently running stroke supports it, the mode will be
* activated for all paint devices of the image.
*
* \see setWrapAroundMode
*/
bool wrapAroundModePermitted() const;
/**
* \return whether the wraparound mode is activated for all the
* devices of the image. The mode is activated when both
* factors are true: the user permitted it and the stroke
* supports it
*/
bool wrapAroundModeActive() const;
/**
* \return current level of detail which is used when processing the image.
* Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is
* null, which means we work on the original image.
*/
int currentLevelOfDetail() const;
/**
* Notify KisImage which level of detail should be used in the
* lod-mode. Setting the mode does not guarantee the LOD to be
* used. It will be activated only when the stokes supports it.
*/
void setDesiredLevelOfDetail(int lod);
/**
* Relative position of the mirror axis center
* 0,0 - topleft corner of the image
* 1,1 - bottomright corner of the image
*/
QPointF mirrorAxesCenter() const;
/**
* Sets the relative position of the axes center
* \see mirrorAxesCenter() for details
*/
void setMirrorAxesCenter(const QPointF &value) const;
+ /**
+ * Configure the image to allow masks on the root not (as reported by
+ * root()->allowAsChild()). By default it is not allowed (because it
+ * looks weird from GUI point of view)
+ */
+ void setAllowMasksOnRootNode(bool value);
+
+ /**
+ * \see setAllowMasksOnRootNode()
+ */
+ bool allowMasksOnRootNode() const;
+
public Q_SLOTS:
/**
* Explicitly start regeneration of LoD planes of all the devices
* in the image. This call should be performed when the user is idle,
* just to make the quality of image updates better.
*/
void explicitRegenerateLevelOfDetail();
public:
/**
* Blocks usage of level of detail functionality. After this method
* has been called, no new strokes will use LoD.
*/
void setLevelOfDetailBlocked(bool value);
/**
* \see setLevelOfDetailBlocked()
*/
bool levelOfDetailBlocked() const;
/**
* Notifies that the node collapsed state has changed
*/
void notifyNodeCollpasedChanged();
KisImageAnimationInterface *animationInterface() const;
/**
* @brief setProofingConfiguration, this sets the image's proofing configuration, and signals
* the proofingConfiguration has changed.
* @param proofingConfig - the kis proofing config that will be used instead.
*/
void setProofingConfiguration(KisProofingConfigurationSP proofingConfig);
/**
* @brief proofingConfiguration
* @return the proofing configuration of the image.
*/
KisProofingConfigurationSP proofingConfiguration() const;
public Q_SLOTS:
bool startIsolatedMode(KisNodeSP node);
void stopIsolatedMode();
public:
KisNodeSP isolatedModeRoot() const;
Q_SIGNALS:
/**
* Emitted whenever an action has caused the image to be
* recomposited. Parameter is the rect that has been recomposited.
*/
void sigImageUpdated(const QRect &);
/**
Emitted whenever the image has been modified, so that it
doesn't match with the version saved on disk.
*/
void sigImageModified();
/**
* The signal is emitted when the size of the image is changed.
* \p oldStillPoint and \p newStillPoint give the receiver the
* hint about how the new and old rect of the image correspond to
* each other. They specify the point of the image around which
* the conversion was done. This point will stay still on the
* user's screen. That is the \p newStillPoint of the new image
* will be painted at the same screen position, where \p
* oldStillPoint of the old image was painted.
*
* \param oldStillPoint is a still point represented in *old*
* image coordinates
*
* \param newStillPoint is a still point represented in *new*
* image coordinates
*/
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void sigResolutionChanged(double xRes, double yRes);
void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes);
/**
* Inform the model that a node was changed
*/
void sigNodeChanged(KisNodeSP node);
/**
* Inform that the image is going to be deleted
*/
void sigAboutToBeDeleted();
/**
* The signal is emitted right after a node has been connected
* to the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread. If you need
* information about the parent/siblings of the node connect
* with Qt::DirectConnection, get needed information and then
* emit another Qt::AutoConnection signal to pass this information
* to your thread. See details of the implementation
* in KisDummiesfacadeBase.
*/
void sigNodeAddedAsync(KisNodeSP node);
/**
* This signal is emitted right before a node is going to removed
* from the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread.
*
* \see comment in sigNodeAddedAsync()
*/
void sigRemoveNodeAsync(KisNodeSP node);
/**
* Emitted when the root node of the image has changed.
* It happens, e.g. when we flatten the image. When
* this happens the receiver should reload information
* about the image
*/
void sigLayersChangedAsync();
/**
* Emitted when the UI has requested the undo of the last stroke's
* operation. The point is, we cannot deal with the internals of
* the stroke without its creator knowing about it (which most
* probably cause a crash), so we just forward this request from
* the UI to the creator of the stroke.
*
* If your tool supports undoing part of its work, just listen to
* this signal and undo when it comes
*/
void sigUndoDuringStrokeRequested();
/**
* Emitted when the UI has requested the cancellation of
* the stroke. The point is, we cannot cancel the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports cancelling of its work in the middle
* of operation, just listen to this signal and cancel
* the stroke when it comes
*/
void sigStrokeCancellationRequested();
/**
* Emitted when the image decides that the stroke should better
* be ended. The point is, we cannot just end the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports long strokes that may involve multiple
* mouse actions in one stroke, just listen to this signal and
* end the stroke when it comes.
*/
void sigStrokeEndRequested();
/**
* Same as sigStrokeEndRequested() but is not emitted when the active node
* is changed.
*/
void sigStrokeEndRequestedActiveNodeFiltered();
/**
* Emitted when the isolated mode status has changed.
*
* Can be used by the receivers to catch a fact of forcefully
* stopping the isolated mode by the image when some complex
* action was requested
*/
void sigIsolatedModeChanged();
/**
* Emitted when one or more nodes changed the collapsed state
*
*/
void sigNodeCollapsedChanged();
/**
* Emitted when the proofing configuration of the image is being changed.
*
*/
void sigProofingConfigChanged();
/**
* Internal signal for asynchronously requesting isolated mode to stop. Don't use it
* outside KisImage, use sigIsolatedModeChanged() instead.
*/
void sigInternalStopIsolatedModeRequested();
public Q_SLOTS:
KisCompositeProgressProxy* compositeProgressProxy();
bool isIdle(bool allowLocked = false);
/**
* @brief Wait until all the queued background jobs are completed and lock the image.
*
* KisImage object has a local scheduler that executes long-running image
* rendering/modifying jobs (we call them "strokes") in a background. Basically,
* one should either access the image from the scope of such jobs (strokes) or
* just lock the image before using.
*
* Calling barrierLock() will wait until all the queued operations are finished
* and lock the image, so you can start accessing it in a safe way.
*
* @p readOnly tells the image if the caller is going to modify the image during
* holding the lock. Locking with non-readOnly access will reset all
* the internal caches of the image (lod-planes) when the lock status
* will be lifted.
*/
void barrierLock(bool readOnly = false);
/**
* @brief Tries to lock the image without waiting for the jobs to finish
*
* Same as barrierLock(), but doesn't block execution of the calling thread
* until all the background jobs are finished. Instead, in case of presence of
* unfinished jobs in the queue, it just returns false
*
* @return whether the lock has been acquired
* @see barrierLock
*/
bool tryBarrierLock(bool readOnly = false);
/**
* Wait for all the internal image jobs to complete and return without locking
* the image. This function is handly for tests or other synchronous actions,
* when one needs to wait for the result of his actions.
*/
void waitForDone();
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override;
void addJob(KisStrokeId id, KisStrokeJobData *data) override;
void endStroke(KisStrokeId id) override;
bool cancelStroke(KisStrokeId id) override;
/**
* @brief blockUpdates block updating the image projection
*/
void blockUpdates() override;
/**
* @brief unblockUpdates unblock updating the image project. This
* only restarts the scheduler and does not schedule a full refresh.
*/
void unblockUpdates() override;
/**
* Disables notification of the UI about the changes in the image.
* This feature is used by KisProcessingApplicator. It is needed
* when we change the size of the image. In this case, the whole
* image will be reloaded into UI by sigSizeChanged(), so there is
* no need to inform the UI about individual dirty rects.
*
* The last call to enableUIUpdates() will return the list of udpates
* that were requested while they were blocked.
*/
void disableUIUpdates() override;
/**
* \see disableUIUpdates
*/
QVector<QRect> enableUIUpdates() override;
/**
* Disables the processing of all the setDirty() requests that
* come to the image. The incoming requests are effectively
* *dropped*.
*
* This feature is used by KisProcessingApplicator. For many cases
* it provides its own updates interface, which recalculates the
* whole subtree of nodes. But while we change any particular
* node, it can ask for an update itself. This method is a way of
* blocking such intermediate (and excessive) requests.
*
* NOTE: this is a convenience function for setProjectionUpdatesFilter()
* that installs a predefined filter that eats everything. Please
* note that these calls are *not* recursive
*/
void disableDirtyRequests() override;
/**
* \see disableDirtyRequests()
*/
void enableDirtyRequests() override;
/**
* Installs a filter object that will filter all the incoming projection update
* requests. If the filter return true, the incoming update is dropped.
*
* NOTE: you cannot set filters recursively!
*/
void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override;
/**
* \see setProjectionUpdatesFilter()
*/
KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override;
void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override;
/**
* Triggers synchronous recomposition of the projection
*/
void refreshGraph(KisNodeSP root = KisNodeSP());
void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void initialRefreshGraph();
/**
* Initiate a stack regeneration skipping the recalculation of the
* filthy node's projection.
*
* Works exactly as pseudoFilthy->setDirty() with the only
* exception that pseudoFilthy::updateProjection() will not be
* called. That is used by KisRecalculateTransformMaskJob to avoid
* cyclic dependencies.
*/
void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect);
/**
* Adds a spontaneous job to the updates queue.
*
* A spontaneous job may do some trivial tasks in the background,
* like updating the outline of selection or purging unused tiles
* from the existing paint devices.
*/
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks the current stroke should undo its last
* action, for example, when the user presses Ctrl+Z while some
* stroke is active.
*
* If the creator of the stroke supports undoing of intermediate
* actions, it will be notified about this request and can undo
* its last action.
*/
void requestUndoDuringStroke();
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks current stroke should be cancelled. If
* there is a running stroke that has already been detached from
* its creator (ended or cancelled), it will be forcefully
* cancelled and reverted. If there is an open stroke present, and
* if its creator supports cancelling, it will be notified about
* the request and the stroke will be cancelled
*/
void requestStrokeCancellation();
/**
* This method requests the last stroke executed on the image to become undone.
* If the stroke is not ended, or if all the Lod0 strokes are completed, the method
* returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT
* is returned and the caller should just wait for its completion and call global undo
* instead. UNDO_OK means one unfinished stroke has been undone.
*/
UndoResult tryUndoUnfinishedLod0Stroke();
/**
* This method is called when image or some other part of Krita
* (*not* the creator of the stroke) decides that the stroke
* should be ended. If the creator of the stroke supports it, it
* will be notified and the stroke will be cancelled
*/
void requestStrokeEnd();
/**
* Same as requestStrokeEnd() but is called by view manager when
* the current node is changed. Use to distinguish
* sigStrokeEndRequested() and
* sigStrokeEndRequestedActiveNodeFiltered() which are used by
* KisNodeJugglerCompressed
*/
void requestStrokeEndActiveNode();
private:
KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy);
KisImage& operator=(const KisImage& rhs);
void emitSizeChanged();
void resizeImageImpl(const QRect& newRect, bool cropLayers);
void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians,
bool resizeImage, KisSelectionSP selection);
void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode,
bool resizeImage, double angleX, double angleY, KisSelectionSP selection);
void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2);
void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea);
void requestProjectionUpdateImpl(KisNode *node,
const QVector<QRect> &rects,
const QRect &cropRect);
friend class KisImageResizeCommand;
void setSize(const QSize& size);
friend class KisImageSetProjectionColorSpaceCommand;
void setProjectionColorSpace(const KoColorSpace * colorSpace);
friend class KisDeselectGlobalSelectionCommand;
friend class KisReselectGlobalSelectionCommand;
friend class KisSetGlobalSelectionCommand;
friend class KisImageTest;
friend class Document; // For libkis
/**
* Replaces the current global selection with globalSelection. If
* \p globalSelection is empty, removes the selection object, so that
* \ref globalSelection() will return 0 after that.
*/
void setGlobalSelection(KisSelectionSP globalSelection);
/**
* Deselects current global selection.
* \ref globalSelection() will return 0 after that.
*/
void deselectGlobalSelection();
/**
* Reselects current deselected selection
*
* \see deselectGlobalSelection()
*/
void reselectGlobalSelection();
private:
class KisImagePrivate;
KisImagePrivate * m_d;
};
#endif // KIS_IMAGE_H_
diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc
index 2763ef05ff..6713137a8e 100644
--- a/libs/image/kis_layer.cc
+++ b/libs/image/kis_layer.cc
@@ -1,988 +1,990 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer.h"
#include <klocalizedstring.h>
#include <QImage>
#include <QBitArray>
#include <QStack>
#include <QMutex>
#include <QMutexLocker>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
#include <KoIcon.h>
#include <kis_icon.h>
#include <KoProperties.h>
#include <KoCompositeOpRegistry.h>
#include <KoColorSpace.h>
#include "kis_debug.h"
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_mask.h"
#include "kis_effect_mask.h"
#include "kis_selection_mask.h"
#include "kis_meta_data_store.h"
#include "kis_selection.h"
#include "kis_paint_layer.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_clone_layer.h"
#include "kis_psd_layer_style.h"
#include "kis_layer_projection_plane.h"
#include "layerstyles/kis_layer_style_projection_plane.h"
#include "krita_utils.h"
#include "kis_layer_properties_icons.h"
#include "kis_layer_utils.h"
#include "kis_projection_leaf.h"
#include "KisSafeNodeProjectionStore.h"
class KisCloneLayersList {
public:
void addClone(KisCloneLayerWSP cloneLayer) {
m_clonesList.append(cloneLayer);
}
void removeClone(KisCloneLayerWSP cloneLayer) {
m_clonesList.removeOne(cloneLayer);
}
void setDirty(const QRect &rect) {
Q_FOREACH (KisCloneLayerSP clone, m_clonesList) {
if (clone) {
clone->setDirtyOriginal(rect);
}
}
}
const QList<KisCloneLayerWSP> registeredClones() const {
return m_clonesList;
}
bool hasClones() const {
return !m_clonesList.isEmpty();
}
private:
QList<KisCloneLayerWSP> m_clonesList;
};
class KisLayerMasksCache {
public:
KisLayerMasksCache(KisLayer *parent)
: m_parent(parent)
{
}
KisSelectionMaskSP selectionMask() {
QReadLocker readLock(&m_lock);
if (!m_isSelectionMaskValid) {
readLock.unlock();
QWriteLocker writeLock(&m_lock);
if (!m_isSelectionMaskValid) {
KoProperties properties;
properties.setProperty("active", true);
properties.setProperty("visible", true);
QList<KisNodeSP> masks = m_parent->childNodes(QStringList("KisSelectionMask"), properties);
// return the first visible mask
Q_FOREACH (KisNodeSP mask, masks) {
if (mask) {
m_selectionMask = dynamic_cast<KisSelectionMask*>(mask.data());
break;
}
}
m_isSelectionMaskValid = true;
}
// return under write lock
return m_selectionMask;
}
// return under read lock
return m_selectionMask;
}
QList<KisEffectMaskSP> effectMasks() {
QReadLocker readLock(&m_lock);
if (!m_isEffectMasksValid) {
readLock.unlock();
QWriteLocker writeLock(&m_lock);
if (!m_isEffectMasksValid) {
m_effectMasks = m_parent->searchEffectMasks(0);
m_isEffectMasksValid = true;
}
// return under write lock
return m_effectMasks;
}
// return under read lock
return m_effectMasks;
}
void setDirty()
{
QWriteLocker l(&m_lock);
m_isSelectionMaskValid = false;
m_isEffectMasksValid = false;
m_selectionMask = 0;
m_effectMasks.clear();
}
private:
KisLayer *m_parent;
QReadWriteLock m_lock;
bool m_isSelectionMaskValid = false;
bool m_isEffectMasksValid = false;
KisSelectionMaskSP m_selectionMask;
QList<KisEffectMaskSP> m_effectMasks;
};
struct Q_DECL_HIDDEN KisLayer::Private
{
Private(KisLayer *q)
: masksCache(q)
{
}
QBitArray channelFlags;
KisMetaData::Store* metaDataStore;
KisCloneLayersList clonesList;
KisPSDLayerStyleSP layerStyle;
KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane;
KisAbstractProjectionPlaneSP projectionPlane;
KisSafeNodeProjectionStoreSP safeProjection;
KisLayerMasksCache masksCache;
};
KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity)
: KisNode(image)
, m_d(new Private(this))
{
setName(name);
setOpacity(opacity);
m_d->metaDataStore = new KisMetaData::Store();
m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this));
m_d->safeProjection = new KisSafeNodeProjectionStore();
+ m_d->safeProjection->setImage(image);
}
KisLayer::KisLayer(const KisLayer& rhs)
: KisNode(rhs)
, m_d(new Private(this))
{
if (this != &rhs) {
m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore);
m_d->channelFlags = rhs.m_d->channelFlags;
setName(rhs.name());
m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this));
m_d->safeProjection = new KisSafeNodeProjectionStore(*rhs.m_d->safeProjection);
+ m_d->safeProjection->setImage(image());
if (rhs.m_d->layerStyle) {
m_d->layerStyle = rhs.m_d->layerStyle->clone();
if (rhs.m_d->layerStyleProjectionPlane) {
m_d->layerStyleProjectionPlane = toQShared(
new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane,
this,
m_d->layerStyle));
}
}
}
}
KisLayer::~KisLayer()
{
delete m_d->metaDataStore;
delete m_d;
}
const KoColorSpace * KisLayer::colorSpace() const
{
KisImageSP image = this->image();
if (!image) {
return nullptr;
}
return image->colorSpace();
}
const KoCompositeOp * KisLayer::compositeOp() const
{
/**
* FIXME: This function duplicates the same function from
* KisMask. We can't move it to KisBaseNode as it doesn't
* know anything about parent() method of KisNode
* Please think it over...
*/
KisNodeSP parentNode = parent();
if (!parentNode) return 0;
if (!parentNode->colorSpace()) return 0;
const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId());
return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER);
}
KisPSDLayerStyleSP KisLayer::layerStyle() const
{
return m_d->layerStyle;
}
void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle)
{
if (layerStyle) {
m_d->layerStyle = layerStyle;
KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ?
KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) :
KisLayerStyleProjectionPlaneSP(0);
m_d->layerStyleProjectionPlane = plane;
} else {
m_d->layerStyleProjectionPlane.clear();
m_d->layerStyle.clear();
}
}
KisBaseNode::PropertyList KisLayer::sectionModelProperties() const
{
KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties();
l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity()));
const KoCompositeOp * compositeOp = this->compositeOp();
if (compositeOp) {
l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description());
}
if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) {
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled());
}
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled());
return l;
}
void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
{
KisBaseNode::setSectionModelProperties(properties);
Q_FOREACH (const KisBaseNode::Property &property, properties) {
if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) {
disableAlphaChannel(property.state.toBool());
}
if (property.id == KisLayerPropertiesIcons::layerStyle.id()) {
if (m_d->layerStyle &&
m_d->layerStyle->isEnabled() != property.state.toBool()) {
m_d->layerStyle->setEnabled(property.state.toBool());
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
}
}
}
void KisLayer::disableAlphaChannel(bool disable)
{
QBitArray newChannelFlags = m_d->channelFlags;
if(newChannelFlags.isEmpty())
newChannelFlags = colorSpace()->channelFlags(true, true);
if(disable)
newChannelFlags &= colorSpace()->channelFlags(true, false);
else
newChannelFlags |= colorSpace()->channelFlags(false, true);
setChannelFlags(newChannelFlags);
}
bool KisLayer::alphaChannelDisabled() const
{
QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags;
return flags.count(true) == 0 && !m_d->channelFlags.isEmpty();
}
void KisLayer::setChannelFlags(const QBitArray & channelFlags)
{
Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount()));
if (KritaUtils::compareChannelFlags(channelFlags,
this->channelFlags())) {
return;
}
if (!channelFlags.isEmpty() &&
channelFlags == QBitArray(channelFlags.size(), true)) {
m_d->channelFlags.clear();
} else {
m_d->channelFlags = channelFlags;
}
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
QBitArray & KisLayer::channelFlags() const
{
return m_d->channelFlags;
}
bool KisLayer::temporary() const
{
return nodeProperties().boolProperty("temporary", false);
}
void KisLayer::setTemporary(bool t)
{
setNodeProperty("temporary", t);
}
void KisLayer::setImage(KisImageWSP image)
{
// we own the projection device, so we should take care about it
KisPaintDeviceSP projection = this->projection();
if (projection && projection != original()) {
projection->setDefaultBounds(new KisDefaultBounds(image));
}
m_d->safeProjection->setImage(image);
KisNode::setImage(image);
}
bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer)
{
return
this->compositeOpId() == otherLayer->compositeOpId() &&
this->opacity() == otherLayer->opacity() &&
this->channelFlags() == otherLayer->channelFlags() &&
!this->layerStyle() && !otherLayer->layerStyle() &&
(this->colorSpace() == otherLayer->colorSpace() ||
*this->colorSpace() == *otherLayer->colorSpace());
}
KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer)
{
const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer);
KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8);
if (keepBlendingOptions) {
newLayer->setCompositeOpId(compositeOpId());
newLayer->setOpacity(opacity());
newLayer->setChannelFlags(channelFlags());
}
return newLayer;
}
void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer)
{
const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer);
QRect layerProjectionExtent = this->projection()->extent();
QRect prevLayerProjectionExtent = prevLayer->projection()->extent();
bool alphaDisabled = this->alphaChannelDisabled();
bool prevAlphaDisabled = prevLayer->alphaChannelDisabled();
KisPaintDeviceSP mergedDevice = dstLayer->paintDevice();
if (!keepBlendingOptions) {
KisPainter gc(mergedDevice);
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return;
}
//Copy the pixels of previous layer with their actual alpha value
prevLayer->disableAlphaChannel(false);
prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds());
//Restore the previous prevLayer disableAlpha status for correct undo/redo
prevLayer->disableAlphaChannel(prevAlphaDisabled);
//Paint the pixels of the current layer, using their actual alpha value
if (alphaDisabled == prevAlphaDisabled) {
this->disableAlphaChannel(false);
}
this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds());
//Restore the layer disableAlpha status for correct undo/redo
this->disableAlphaChannel(alphaDisabled);
}
else {
//Copy prevLayer
KisPaintDeviceSP srcDev = prevLayer->projection();
mergedDevice->makeCloneFrom(srcDev, srcDev->extent());
//Paint layer on the copy
KisPainter gc(mergedDevice);
gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent);
}
}
void KisLayer::registerClone(KisCloneLayerWSP clone)
{
m_d->clonesList.addClone(clone);
}
void KisLayer::unregisterClone(KisCloneLayerWSP clone)
{
m_d->clonesList.removeClone(clone);
}
const QList<KisCloneLayerWSP> KisLayer::registeredClones() const
{
return m_d->clonesList.registeredClones();
}
bool KisLayer::hasClones() const
{
return m_d->clonesList.hasClones();
}
void KisLayer::updateClones(const QRect &rect)
{
m_d->clonesList.setDirty(rect);
}
void KisLayer::notifyChildMaskChanged()
{
m_d->masksCache.setDirty();
}
KisSelectionMaskSP KisLayer::selectionMask() const
{
return m_d->masksCache.selectionMask();
}
KisSelectionSP KisLayer::selection() const
{
KisSelectionMaskSP mask = selectionMask();
if (mask) {
return mask->selection();
}
KisImageSP image = this->image();
if (image) {
return image->globalSelection();
}
return KisSelectionSP();
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
QList<KisEffectMaskSP> KisLayer::effectMasks() const
{
return m_d->masksCache.effectMasks();
}
QList<KisEffectMaskSP> KisLayer::effectMasks(KisNodeSP lastNode) const
{
if (lastNode.isNull()) {
return effectMasks();
} else {
// happens rarely.
return searchEffectMasks(lastNode);
}
}
QList<KisEffectMaskSP> KisLayer::searchEffectMasks(KisNodeSP lastNode) const
{
QList<KisEffectMaskSP> masks;
KIS_SAFE_ASSERT_RECOVER_NOOP(projectionLeaf());
KisProjectionLeafSP child = projectionLeaf()->firstChild();
while (child) {
if (child->node() == lastNode) break;
KIS_SAFE_ASSERT_RECOVER_NOOP(child);
KIS_SAFE_ASSERT_RECOVER_NOOP(child->node());
if (child->visible()) {
KisEffectMaskSP mask = dynamic_cast<KisEffectMask*>(const_cast<KisNode*>(child->node().data()));
if (mask) {
masks.append(mask);
}
}
child = child->nextSibling();
}
return masks;
}
bool KisLayer::hasEffectMasks() const
{
return !m_d->masksCache.effectMasks().isEmpty();
}
QRect KisLayer::masksChangeRect(const QList<KisEffectMaskSP> &masks,
const QRect &requestedRect,
bool &rectVariesFlag) const
{
rectVariesFlag = false;
QRect prevChangeRect = requestedRect;
/**
* We set default value of the change rect for the case
* when there is no mask at all
*/
QRect changeRect = requestedRect;
Q_FOREACH (const KisEffectMaskSP& mask, masks) {
changeRect = mask->changeRect(prevChangeRect);
if (changeRect != prevChangeRect)
rectVariesFlag = true;
prevChangeRect = changeRect;
}
return changeRect;
}
QRect KisLayer::masksNeedRect(const QList<KisEffectMaskSP> &masks,
const QRect &changeRect,
QStack<QRect> &applyRects,
bool &rectVariesFlag) const
{
rectVariesFlag = false;
QRect prevNeedRect = changeRect;
QRect needRect;
for (qint32 i = masks.size() - 1; i >= 0; i--) {
applyRects.push(prevNeedRect);
needRect = masks[i]->needRect(prevNeedRect);
if (prevNeedRect != needRect)
rectVariesFlag = true;
prevNeedRect = needRect;
}
return needRect;
}
KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion,
KisNodeSP filthy,
KisNodeSP parent)
{
if (parent == filthy || parent != filthy->parent()) {
return KisNode::N_ABOVE_FILTHY;
}
if (nodeInQuestion == filthy) {
return KisNode::N_FILTHY;
}
KisNodeSP node = nodeInQuestion->prevSibling();
while (node) {
if (node == filthy) {
return KisNode::N_ABOVE_FILTHY;
}
node = node->prevSibling();
}
return KisNode::N_BELOW_FILTHY;
}
QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
KisPaintDeviceSP destination,
const QRect &requestedRect,
KisNodeSP filthyNode,
KisNodeSP lastNode) const
{
Q_ASSERT(source);
Q_ASSERT(destination);
QList<KisEffectMaskSP> masks = effectMasks(lastNode);
QRect changeRect;
QRect needRect;
if (masks.isEmpty()) {
changeRect = requestedRect;
if (source != destination) {
copyOriginalToProjection(source, destination, requestedRect);
}
} else {
QStack<QRect> applyRects;
bool changeRectVaries;
bool needRectVaries;
/**
* FIXME: Assume that varying of the changeRect has already
* been taken into account while preparing walkers
*/
changeRectVaries = false;
changeRect = requestedRect;
//changeRect = masksChangeRect(masks, requestedRect,
// changeRectVaries);
needRect = masksNeedRect(masks, changeRect,
applyRects, needRectVaries);
if (!changeRectVaries && !needRectVaries) {
/**
* A bit of optimization:
* All filters will read/write exactly from/to the requested
* rect so we needn't create temporary paint device,
* just apply it onto destination
*/
Q_ASSERT(needRect == requestedRect);
if (source != destination) {
copyOriginalToProjection(source, destination, needRect);
}
Q_FOREACH (const KisEffectMaskSP& mask, masks) {
const QRect maskApplyRect = applyRects.pop();
const QRect maskNeedRect =
applyRects.isEmpty() ? needRect : applyRects.top();
PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast<KisLayer*>(this));
mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition);
}
Q_ASSERT(applyRects.isEmpty());
} else {
/**
* We can't eliminate additional copy-op
* as filters' behaviour may be quite insane here,
* so let them work on their own paintDevice =)
*/
KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace());
tempDevice->prepareClone(source);
copyOriginalToProjection(source, tempDevice, needRect);
QRect maskApplyRect = applyRects.pop();
QRect maskNeedRect = needRect;
Q_FOREACH (const KisEffectMaskSP& mask, masks) {
PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast<KisLayer*>(this));
mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition);
if (!applyRects.isEmpty()) {
maskNeedRect = maskApplyRect;
maskApplyRect = applyRects.pop();
}
}
Q_ASSERT(applyRects.isEmpty());
KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect);
}
}
return changeRect;
}
QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode)
{
QRect updatedRect = rect;
KisPaintDeviceSP originalDevice = original();
if (!rect.isValid() ||
- !visible() ||
- !originalDevice) return QRect();
+ (!visible() && !hasClones()) ||
+ !originalDevice) return QRect();
if (!needProjection() && !hasEffectMasks()) {
m_d->safeProjection->releaseDevice();
} else {
if (!updatedRect.isEmpty()) {
KisPaintDeviceSP projection = m_d->safeProjection->getDeviceLazy(originalDevice);
updatedRect = applyMasks(originalDevice, projection,
updatedRect, filthyNode, 0);
}
}
return updatedRect;
}
QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect)
{
bool changeRectVaries = false;
QRect changeRect = outgoingChangeRect(rect);
changeRect = masksChangeRect(effectMasks(lastNode), changeRect,
changeRectVaries);
return changeRect;
}
/**
* \p rect is a dirty rect in layer's original() coordinates!
*/
void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect)
{
QRect changeRect = partialChangeRect(lastNode, rect);
KisPaintDeviceSP originalDevice = original();
KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks());
if (!changeRect.isEmpty()) {
applyMasks(originalDevice, projection,
changeRect, this, lastNode);
}
}
bool KisLayer::needProjection() const
{
return false;
}
void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const
{
KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect);
}
KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const
{
return m_d->layerStyleProjectionPlane ?
KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : m_d->projectionPlane;
}
KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const
{
return m_d->projectionPlane;
}
KisPaintDeviceSP KisLayer::projection() const
{
KisPaintDeviceSP originalDevice = original();
return needProjection() || hasEffectMasks() ?
m_d->safeProjection->getDeviceLazy(originalDevice) : originalDevice;
}
QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const
{
QRect changeRect = rect;
changeRect = incomingChangeRect(changeRect);
if(pos == KisNode::N_FILTHY) {
QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect;
bool changeRectVaries;
changeRect = outgoingChangeRect(changeRect);
changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries);
/**
* If the projection contains some dirty areas we should also
* add them to the change rect, because they might have
* changed. E.g. when a visibility of the mask has chnaged
* while the parent layer was invinisble.
*/
if (!projectionToBeUpdated.isEmpty() &&
!changeRect.contains(projectionToBeUpdated)) {
changeRect |= projectionToBeUpdated;
}
}
// TODO: string comparizon: optimize!
if (pos != KisNode::N_FILTHY &&
pos != KisNode::N_FILTHY_PROJECTION &&
compositeOpId() != COMPOSITE_COPY) {
changeRect |= rect;
}
return changeRect;
}
void KisLayer::childNodeChanged(KisNodeSP changedChildNode)
{
if (dynamic_cast<KisMask*>(changedChildNode.data())) {
notifyChildMaskChanged();
}
}
QRect KisLayer::incomingChangeRect(const QRect &rect) const
{
return rect;
}
QRect KisLayer::outgoingChangeRect(const QRect &rect) const
{
return rect;
}
QRect KisLayer::needRectForOriginal(const QRect &rect) const
{
QRect needRect = rect;
const QList<KisEffectMaskSP> masks = effectMasks();
if (!masks.isEmpty()) {
QStack<QRect> applyRects;
bool needRectVaries;
needRect = masksNeedRect(masks, rect,
applyRects, needRectVaries);
}
return needRect;
}
QImage KisLayer::createThumbnail(qint32 w, qint32 h)
{
if (w == 0 || h == 0) {
return QImage();
}
KisPaintDeviceSP originalDevice = original();
return originalDevice ?
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) : QImage();
}
QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time)
{
if (w == 0 || h == 0) {
return QImage();
}
KisPaintDeviceSP originalDevice = original();
if (originalDevice ) {
KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel();
if (channel) {
KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace());
KisKeyframeSP keyframe = channel->activeKeyframeAt(time);
channel->fetchFrame(keyframe, targetDevice);
return targetDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
}
return createThumbnail(w, h);
}
qint32 KisLayer::x() const
{
KisPaintDeviceSP originalDevice = original();
return originalDevice ? originalDevice->x() : 0;
}
qint32 KisLayer::y() const
{
KisPaintDeviceSP originalDevice = original();
return originalDevice ? originalDevice->y() : 0;
}
void KisLayer::setX(qint32 x)
{
KisPaintDeviceSP originalDevice = original();
if (originalDevice)
originalDevice->setX(x);
}
void KisLayer::setY(qint32 y)
{
KisPaintDeviceSP originalDevice = original();
if (originalDevice)
originalDevice->setY(y);
}
QRect KisLayer::layerExtentImpl(bool needExactBounds) const
{
QRect additionalMaskExtent = QRect();
QList<KisEffectMaskSP> effectMasks = this->effectMasks();
Q_FOREACH(KisEffectMaskSP mask, effectMasks) {
additionalMaskExtent |= mask->nonDependentExtent();
}
KisPaintDeviceSP originalDevice = original();
QRect layerExtent;
if (originalDevice) {
layerExtent = needExactBounds ?
originalDevice->exactBounds() :
originalDevice->extent();
}
QRect additionalCompositeOpExtent;
if (compositeOpId() == COMPOSITE_DESTINATION_IN ||
compositeOpId() == COMPOSITE_DESTINATION_ATOP) {
additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds();
}
return layerExtent | additionalMaskExtent | additionalCompositeOpExtent;
}
QRect KisLayer::extent() const
{
return layerExtentImpl(false);
}
QRect KisLayer::exactBounds() const
{
return layerExtentImpl(true);
}
KisLayerSP KisLayer::parentLayer() const
{
return qobject_cast<KisLayer*>(parent().data());
}
KisMetaData::Store* KisLayer::metaData()
{
return m_d->metaDataStore;
}
diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp
index d401b74277..ba7e7cab6b 100644
--- a/libs/image/kis_layer_utils.cpp
+++ b/libs/image/kis_layer_utils.cpp
@@ -1,1542 +1,1557 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer_utils.h"
#include <algorithm>
#include <QUuid>
#include <KoColorSpaceConstants.h>
#include <KoProperties.h>
#include "kis_painter.h"
#include "kis_image.h"
#include "kis_node.h"
#include "kis_layer.h"
#include "kis_paint_layer.h"
#include "kis_clone_layer.h"
#include "kis_group_layer.h"
#include "kis_selection.h"
#include "kis_selection_mask.h"
#include "kis_meta_data_merge_strategy.h"
#include <kundo2command.h>
#include "commands/kis_image_layer_add_command.h"
#include "commands/kis_image_layer_remove_command.h"
#include "commands/kis_image_layer_move_command.h"
#include "commands/kis_image_change_layers_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "commands/kis_image_change_visibility_command.h"
#include "kis_abstract_projection_plane.h"
#include "kis_processing_applicator.h"
#include "kis_image_animation_interface.h"
#include "kis_keyframe_channel.h"
#include "kis_command_utils.h"
#include "commands_new/kis_change_projection_color_command.h"
#include "kis_layer_properties_icons.h"
#include "lazybrush/kis_colorize_mask.h"
#include "commands/kis_node_property_list_command.h"
#include "commands/kis_node_compositeop_command.h"
#include <KisDelayedUpdateNodeInterface.h>
#include "krita_utils.h"
#include "kis_image_signal_router.h"
namespace KisLayerUtils {
void fetchSelectionMasks(KisNodeList mergedNodes, QVector<KisSelectionMaskSP> &selectionMasks)
{
foreach (KisNodeSP node, mergedNodes) {
- KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
- KisSelectionMaskSP mask;
+ Q_FOREACH(KisNodeSP child, node->childNodes(QStringList("KisSelectionMask"), KoProperties())) {
- if (layer && (mask = layer->selectionMask())) {
- selectionMasks.append(mask);
+ KisSelectionMaskSP mask = qobject_cast<KisSelectionMask*>(child.data());
+ if (mask) {
+ selectionMasks.append(mask);
+ }
}
}
}
struct MergeDownInfoBase {
MergeDownInfoBase(KisImageSP _image)
: image(_image),
storage(new SwitchFrameCommand::SharedStorage())
{
}
virtual ~MergeDownInfoBase() {}
KisImageWSP image;
QVector<KisSelectionMaskSP> selectionMasks;
KisNodeSP dstNode;
SwitchFrameCommand::SharedStorageSP storage;
QSet<int> frames;
bool useInTimeline = false;
bool enableOnionSkins = false;
virtual KisNodeList allSrcNodes() = 0;
KisLayerSP dstLayer() {
return qobject_cast<KisLayer*>(dstNode.data());
}
};
struct MergeDownInfo : public MergeDownInfoBase {
MergeDownInfo(KisImageSP _image,
KisLayerSP _prevLayer,
KisLayerSP _currLayer)
: MergeDownInfoBase(_image),
prevLayer(_prevLayer),
currLayer(_currLayer)
{
frames =
fetchLayerFramesRecursive(prevLayer) |
fetchLayerFramesRecursive(currLayer);
useInTimeline = prevLayer->useInTimeline() || currLayer->useInTimeline();
const KisPaintLayer *paintLayer = qobject_cast<KisPaintLayer*>(currLayer.data());
if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled();
paintLayer = qobject_cast<KisPaintLayer*>(prevLayer.data());
if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled();
}
KisLayerSP prevLayer;
KisLayerSP currLayer;
KisNodeList allSrcNodes() override {
KisNodeList mergedNodes;
mergedNodes << currLayer;
mergedNodes << prevLayer;
return mergedNodes;
}
};
struct MergeMultipleInfo : public MergeDownInfoBase {
MergeMultipleInfo(KisImageSP _image,
KisNodeList _mergedNodes)
: MergeDownInfoBase(_image),
mergedNodes(_mergedNodes)
{
foreach (KisNodeSP node, mergedNodes) {
frames |= fetchLayerFramesRecursive(node);
useInTimeline |= node->useInTimeline();
const KisPaintLayer *paintLayer = qobject_cast<KisPaintLayer*>(node.data());
if (paintLayer) {
enableOnionSkins |= paintLayer->onionSkinEnabled();
}
}
}
KisNodeList mergedNodes;
bool nodesCompositingVaries = false;
KisNodeList allSrcNodes() override {
return mergedNodes;
}
};
typedef QSharedPointer<MergeDownInfoBase> MergeDownInfoBaseSP;
typedef QSharedPointer<MergeDownInfo> MergeDownInfoSP;
typedef QSharedPointer<MergeMultipleInfo> MergeMultipleInfoSP;
struct FillSelectionMasks : public KUndo2Command {
FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks);
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand {
DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {}
void populateChildCommands() override {
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
recursiveApplyNodes(node,
[this] (KisNodeSP node) {
if (dynamic_cast<KisColorizeMask*>(node.data()) &&
KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
});
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableOnionSkins : public KisCommandUtils::AggregateCommand {
DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {}
void populateChildCommands() override {
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
recursiveApplyNodes(node,
[this] (KisNodeSP node) {
if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::onionSkins,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
});
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableExtraCompositing : public KisCommandUtils::AggregateCommand {
DisableExtraCompositing(MergeMultipleInfoSP info) : m_info(info) {}
void populateChildCommands() override {
/**
* We disable extra compositing only in case all the layers have
* the same compositing properties, therefore, we can just sum them using
* Normal blend mode
*/
if (m_info->nodesCompositingVaries) return;
// we should disable dirty requests on **redo only**, otherwise
// the state of the layers will not be recovered on undo
m_info->image->disableDirtyRequests();
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
if (node->compositeOpId() != COMPOSITE_OVER) {
addCommand(new KisNodeCompositeOpCommand(node, node->compositeOpId(), COMPOSITE_OVER));
}
if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::inheritAlpha, false).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::inheritAlpha,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
}
m_info->image->enableDirtyRequests();
}
private:
MergeMultipleInfoSP m_info;
};
struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand {
DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false)
: m_info(info),
m_skipIfDstIsGroup(skipIfDstIsGroup)
{
}
void populateChildCommands() override {
if (m_skipIfDstIsGroup &&
m_info->dstLayer() &&
m_info->dstLayer()->inherits("KisGroupLayer")) {
return;
}
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::passThrough,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
}
}
private:
MergeDownInfoBaseSP m_info;
bool m_skipIfDstIsGroup;
};
struct RefreshHiddenAreas : public KUndo2Command {
RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
KisImageAnimationInterface *interface = m_info->image->animationInterface();
const QRect preparedRect = !interface->externalFrameActive() ?
m_info->image->bounds() : QRect();
foreach (KisNodeSP node, m_info->allSrcNodes()) {
refreshHiddenAreaAsync(node, preparedRect);
}
}
private:
QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) {
KisNodeSP node = rootNode->firstChild();
while(node) {
currentRect |= realNodeExactBounds(node, currentRect);
node = node->nextSibling();
}
// TODO: it would be better to count up changeRect inside
// node's extent() method
currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds());
return currentRect;
}
void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) {
QRect realNodeRect = realNodeExactBounds(rootNode);
if (!preparedArea.contains(realNodeRect)) {
QRegion dirtyRegion = realNodeRect;
dirtyRegion -= preparedArea;
foreach(const QRect &rc, dirtyRegion.rects()) {
m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect);
}
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct RefreshDelayedUpdateLayers : public KUndo2Command {
RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
foreach (KisNodeSP node, m_info->allSrcNodes()) {
forceAllDelayedNodesUpdate(node);
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand {
KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing)
: m_singleInfo(info),
m_finalizing(finalizing) {}
KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing)
: m_multipleInfo(info),
m_finalizing(finalizing),
m_putAfter(putAfter) {}
void populateChildCommands() override {
KisNodeSP prevNode;
KisNodeSP nextNode;
KisNodeList prevSelection;
KisNodeList nextSelection;
KisImageSP image;
if (m_singleInfo) {
prevNode = m_singleInfo->currLayer;
nextNode = m_singleInfo->dstNode;
image = m_singleInfo->image;
} else if (m_multipleInfo) {
prevNode = m_putAfter;
nextNode = m_multipleInfo->dstNode;
prevSelection = m_multipleInfo->allSrcNodes();
image = m_multipleInfo->image;
}
if (!m_finalizing) {
addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(),
prevNode, KisNodeSP(),
image, false));
} else {
addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection,
KisNodeSP(), nextNode,
image, true));
}
}
private:
MergeDownInfoSP m_singleInfo;
MergeMultipleInfoSP m_multipleInfo;
bool m_finalizing;
KisNodeSP m_putAfter;
};
struct CreateMergedLayer : public KisCommandUtils::AggregateCommand {
CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {}
void populateChildCommands() override {
// actual merging done by KisLayer::createMergedLayer (or specialized descendant)
m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer);
if (m_info->frames.size() > 0) {
m_info->dstNode->enableAnimation();
m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
}
m_info->dstNode->setUseInTimeline(m_info->useInTimeline);
KisPaintLayer *dstPaintLayer = qobject_cast<KisPaintLayer*>(m_info->dstNode.data());
if (dstPaintLayer) {
dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins);
}
}
private:
MergeDownInfoSP m_info;
};
struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand {
CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() )
: m_info(info),
m_name(name) {}
void populateChildCommands() override {
QString mergedLayerName;
if (m_name.isEmpty()){
const QString mergedLayerSuffix = i18n("Merged");
mergedLayerName = m_info->mergedNodes.first()->name();
if (!mergedLayerName.endsWith(mergedLayerSuffix)) {
mergedLayerName = QString("%1 %2")
.arg(mergedLayerName).arg(mergedLayerSuffix);
}
} else {
mergedLayerName = m_name;
}
KisPaintLayer *dstPaintLayer = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8);
m_info->dstNode = dstPaintLayer;
if (m_info->frames.size() > 0) {
m_info->dstNode->enableAnimation();
m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
}
auto channelFlagsLazy = [](KisNodeSP node) {
KisLayer *layer = dynamic_cast<KisLayer*>(node.data());
return layer ? layer->channelFlags() : QBitArray();
};
QString compositeOpId;
QBitArray channelFlags;
bool compositionVaries = false;
bool isFirstCycle = true;
foreach (KisNodeSP node, m_info->allSrcNodes()) {
if (isFirstCycle) {
compositeOpId = node->compositeOpId();
channelFlags = channelFlagsLazy(node);
isFirstCycle = false;
} else if (compositeOpId != node->compositeOpId() ||
channelFlags != channelFlagsLazy(node)) {
compositionVaries = true;
break;
}
KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
if (layer && layer->layerStyle()) {
compositionVaries = true;
break;
}
}
if (!compositionVaries) {
if (!compositeOpId.isEmpty()) {
m_info->dstNode->setCompositeOpId(compositeOpId);
}
if (m_info->dstLayer() && !channelFlags.isEmpty()) {
m_info->dstLayer()->setChannelFlags(channelFlags);
}
}
m_info->nodesCompositingVaries = compositionVaries;
m_info->dstNode->setUseInTimeline(m_info->useInTimeline);
dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins);
}
private:
MergeMultipleInfoSP m_info;
QString m_name;
};
struct MergeLayers : public KisCommandUtils::AggregateCommand {
MergeLayers(MergeDownInfoSP info) : m_info(info) {}
void populateChildCommands() override {
// actual merging done by KisLayer::createMergedLayer (or specialized descendant)
m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer);
}
private:
MergeDownInfoSP m_info;
};
struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand {
MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {}
void populateChildCommands() override {
KisPainter gc(m_info->dstNode->paintDevice());
foreach (KisNodeSP node, m_info->allSrcNodes()) {
QRect rc = node->exactBounds() | m_info->image->bounds();
node->projectionPlane()->apply(&gc, rc);
}
}
private:
MergeMultipleInfoSP m_info;
};
struct MergeMetaData : public KUndo2Command {
MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy)
: m_info(info),
m_strategy(strategy) {}
void redo() override {
QRect layerProjectionExtent = m_info->currLayer->projection()->extent();
QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent();
int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height();
int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height();
QList<double> scores;
double norm = qMax(prevLayerArea, layerArea);
scores.append(prevLayerArea / norm);
scores.append(layerArea / norm);
QList<const KisMetaData::Store*> srcs;
srcs.append(m_info->prevLayer->metaData());
srcs.append(m_info->currLayer->metaData());
m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores);
}
private:
MergeDownInfoSP m_info;
const KisMetaData::MergeStrategy *m_strategy;
};
KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore,
const KisNodeList &selectedAfter,
KisNodeSP activeBefore,
KisNodeSP activeAfter,
KisImageSP image,
bool finalize, KUndo2Command *parent)
: FlipFlopCommand(finalize, parent),
m_selectedBefore(selectedBefore),
m_selectedAfter(selectedAfter),
m_activeBefore(activeBefore),
m_activeAfter(activeAfter),
m_image(image)
{
}
void KeepNodesSelectedCommand::partB() {
KisImageSignalType type;
if (getState() == State::FINALIZING) {
type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter);
} else {
type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore);
}
m_image->signalRouter()->emitNotification(type);
}
SelectGlobalSelectionMask::SelectGlobalSelectionMask(KisImageSP image)
: m_image(image)
{
}
void SelectGlobalSelectionMask::redo() {
KisImageSignalType type =
ComplexNodeReselectionSignal(m_image->rootLayer()->selectionMask(), KisNodeList());
m_image->signalRouter()->emitNotification(type);
}
RemoveNodeHelper::~RemoveNodeHelper()
{
}
/**
* The removal of two nodes in one go may be a bit tricky, because one
* of them may be the clone of another. If we remove the source of a
* clone layer, it will reincarnate into a paint layer. In this case
* the pointer to the second layer will be lost.
*
* That's why we need to care about the order of the nodes removal:
* the clone --- first, the source --- last.
*/
void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) {
const bool lastLayer = scanForLastLayer(image, nodes);
auto isNodeWeird = [] (KisNodeSP node) {
const bool normalCompositeMode = node->compositeOpId() == COMPOSITE_OVER;
KisLayer *layer = dynamic_cast<KisLayer*>(node.data());
const bool hasInheritAlpha = layer && layer->alphaChannelDisabled();
return !normalCompositeMode && !hasInheritAlpha;
};
while (!nodes.isEmpty()) {
KisNodeList::iterator it = nodes.begin();
while (it != nodes.end()) {
if (!checkIsSourceForClone(*it, nodes)) {
KisNodeSP node = *it;
addCommandImpl(new KisImageLayerRemoveCommand(image, node, !isNodeWeird(node), true));
it = nodes.erase(it);
} else {
++it;
}
}
}
if (lastLayer) {
KisLayerSP newLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
addCommandImpl(new KisImageLayerAddCommand(image, newLayer,
image->root(),
KisNodeSP(),
false, false));
}
}
bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) {
foreach (KisNodeSP node, nodes) {
if (node == src) continue;
KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node.data());
if (clone && KisNodeSP(clone->copyFrom()) == src) {
return true;
}
}
return false;
}
bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) {
bool removeLayers = false;
Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) {
if (qobject_cast<KisLayer*>(nodeToRemove.data())) {
removeLayers = true;
break;
}
}
if (!removeLayers) return false;
bool lastLayer = true;
KisNodeSP node = image->root()->firstChild();
while (node) {
if (!nodesToRemove.contains(node) &&
- qobject_cast<KisLayer*>(node.data())) {
+ qobject_cast<KisLayer*>(node.data()) &&
+ !node->isFakeNode()) {
lastLayer = false;
break;
}
node = node->nextSibling();
}
return lastLayer;
}
SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes,
KisImageSP image)
: m_nodes(nodes),
m_image(image)
{
}
void SimpleRemoveLayers::populateChildCommands() {
if (m_nodes.isEmpty()) return;
safeRemoveMultipleNodes(m_nodes, m_image);
}
void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) {
addCommand(cmd);
}
struct InsertNode : public KisCommandUtils::AggregateCommand {
InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info), m_putAfter(putAfter) {}
void populateChildCommands() override {
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
m_putAfter->parent(),
m_putAfter,
true, false));
}
private:
virtual void addCommandImpl(KUndo2Command *cmd) {
addCommand(cmd);
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info), m_putAfter(putAfter) {}
static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) {
if (!putAfter) {
putAfter = nodesToDelete.last();
}
// Add the new merged node on top of the active node
// -- checking all parents if they are included in nodesToDelete
// Not every descendant is included in nodesToDelete even if in fact
// they are going to be deleted, so we need to check it.
// If we consider the path from root to the putAfter node,
// if there are any nodes marked for deletion, any node afterwards
// is going to be deleted, too.
// example: root . . . . . ! ! . . ! ! ! ! . . . . putAfter
// it should be: root . . . . . ! ! ! ! ! ! ! ! ! ! ! ! !putAfter
// and here: root . . . . X ! ! . . ! ! ! ! . . . . putAfter
// you can see which node is "the perfect ancestor"
// (marked X; called "parent" in the function arguments).
// and here: root . . . . . O ! . . ! ! ! ! . . . . putAfter
// you can see which node is "the topmost deleted ancestor" (marked 'O')
KisNodeSP node = putAfter->parent();
bool foundDeletedAncestor = false;
KisNodeSP topmostAncestorToDelete = nullptr;
while (node) {
if (nodesToDelete.contains(node)
&& !nodesToDelete.contains(node->parent())) {
foundDeletedAncestor = true;
topmostAncestorToDelete = node;
// Here node is to be deleted and its parent is not,
// so its parent is the one of the first not deleted (="perfect") ancestors.
// We need the one that is closest to the top (root)
}
node = node->parent();
}
if (foundDeletedAncestor) {
parent = topmostAncestorToDelete->parent();
putAfter = topmostAncestorToDelete;
}
else {
parent = putAfter->parent(); // putAfter (and none of its ancestors) is to be deleted, so its parent is the first not deleted ancestor
}
}
void populateChildCommands() override {
KisNodeList nodesToDelete = m_info->allSrcNodes();
KisNodeSP parent;
findPerfectParent(nodesToDelete, m_putAfter, parent);
if (!parent) {
KisNodeSP oldRoot = m_info->image->root();
KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8));
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
newRoot,
KisNodeSP(),
true, false));
addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot));
}
else {
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
parent,
m_putAfter,
true, false));
/**
* We can merge selection masks, in this case dstLayer is not defined!
*/
if (m_info->dstLayer()) {
reparentSelectionMasks(m_info->image,
m_info->dstLayer(),
m_info->selectionMasks);
}
KisNodeList safeNodesToDelete = m_info->allSrcNodes();
for (KisNodeList::iterator it = safeNodesToDelete.begin(); it != safeNodesToDelete.end(); ++it) {
KisNodeSP node = *it;
if (node->userLocked() && node->visible()) {
addCommand(new KisImageChangeVisibilityCommand(false, node));
}
}
KritaUtils::filterContainer<KisNodeList>(safeNodesToDelete, [](KisNodeSP node) {
return !node->userLocked();
});
safeRemoveMultipleNodes(safeNodesToDelete, m_info->image);
}
}
private:
void addCommandImpl(KUndo2Command *cmd) override {
addCommand(cmd);
}
void reparentSelectionMasks(KisImageSP image,
KisLayerSP newLayer,
const QVector<KisSelectionMaskSP> &selectionMasks) {
KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer);
foreach (KisSelectionMaskSP mask, selectionMasks) {
addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild()));
addCommand(new KisActivateSelectionMaskCommand(mask, false));
}
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
SwitchFrameCommand::SharedStorage::~SharedStorage() {
}
SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage)
: FlipFlopCommand(finalize),
m_image(image),
m_newTime(time),
m_storage(storage) {}
SwitchFrameCommand::~SwitchFrameCommand() {}
void SwitchFrameCommand::partA() {
KisImageAnimationInterface *interface = m_image->animationInterface();
const int currentTime = interface->currentTime();
if (currentTime == m_newTime) {
m_storage->value = m_newTime;
return;
}
interface->image()->disableUIUpdates();
interface->saveAndResetCurrentTime(m_newTime, &m_storage->value);
}
void SwitchFrameCommand::partB() {
KisImageAnimationInterface *interface = m_image->animationInterface();
const int currentTime = interface->currentTime();
if (currentTime == m_storage->value) {
return;
}
interface->restoreCurrentTime(&m_storage->value);
interface->image()->enableUIUpdates();
}
struct AddNewFrame : public KisCommandUtils::AggregateCommand {
AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {}
void populateChildCommands() override {
KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper();
KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id());
KisKeyframeSP keyframe = channel->addKeyframe(m_frame, cmd);
applyKeyframeColorLabel(keyframe);
addCommand(cmd);
}
void applyKeyframeColorLabel(KisKeyframeSP dstKeyframe) {
Q_FOREACH(KisNodeSP srcNode, m_info->allSrcNodes()) {
Q_FOREACH(KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) {
KisKeyframeSP keyframe = channel->keyframeAt(m_frame);
if (!keyframe.isNull() && keyframe->colorLabel() != 0) {
dstKeyframe->setColorLabel(keyframe->colorLabel());
return;
}
}
}
dstKeyframe->setColorLabel(0);
}
private:
MergeDownInfoBaseSP m_info;
int m_frame;
};
QSet<int> fetchLayerFrames(KisNodeSP node) {
KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id());
if (!channel) return QSet<int>();
return channel->allKeyframeIds();
}
QSet<int> fetchLayerFramesRecursive(KisNodeSP rootNode) {
QSet<int> frames = fetchLayerFrames(rootNode);
KisNodeSP node = rootNode->firstChild();
while(node) {
frames |= fetchLayerFramesRecursive(node);
node = node->nextSibling();
}
return frames;
}
void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) {
QSet<int> frames = fetchLayerFrames(node);
if (frames.isEmpty()) {
(*jobs)[0].insert(node);
} else {
foreach (int frame, frames) {
(*jobs)[frame].insert(node);
}
}
}
void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) {
updateFrameJobs(jobs, rootNode);
KisNodeSP node = rootNode->firstChild();
while(node) {
updateFrameJobsRecursive(jobs, node);
node = node->nextSibling();
}
}
void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
if (!layer->prevSibling()) return;
// XXX: this breaks if we allow free mixing of masks and layers
KisLayerSP prevLayer = qobject_cast<KisLayer*>(layer->prevSibling().data());
if (!prevLayer) return;
if (!layer->visible() && !prevLayer->visible()) {
return;
}
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
kundo2_i18n("Merge Down"));
if (layer->visible() && prevLayer->visible()) {
MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer));
// disable key strokes on all colorize masks, all onion skins on
// paint layers and wait until update is finished with a barrier
applicator.applyCommand(new DisableColorizeKeyStrokes(info));
applicator.applyCommand(new DisableOnionSkins(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KeepMergedNodesSelected(info, false));
applicator.applyCommand(new FillSelectionMasks(info));
applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER);
// NOTE: shape layer may have emitted spontaneous jobs during layer creation,
// wait for them to complete!
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
// in two-layer mode we disable pass through only when the destination layer
// is not a group layer
applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
if (info->frames.size() > 0) {
foreach (int frame, info->frames) {
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage));
applicator.applyCommand(new AddNewFrame(info, frame));
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage));
}
} else {
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER);
}
applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER);
applicator.applyCommand(new CleanUpNodes(info, layer),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepMergedNodesSelected(info, true));
} else if (layer->visible()) {
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
layer, KisNodeSP(),
image, false));
applicator.applyCommand(
new SimpleRemoveLayers(KisNodeList() << prevLayer,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
KisNodeSP(), layer,
image, true));
} else if (prevLayer->visible()) {
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
layer, KisNodeSP(),
image, false));
applicator.applyCommand(
new SimpleRemoveLayers(KisNodeList() << layer,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
KisNodeSP(), prevLayer,
image, true));
}
applicator.end();
}
bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents)
{
KisNodeList nodeParents;
KisNodeSP parent = node->parent();
while (parent) {
nodeParents << parent;
parent = parent->parent();
}
foreach(KisNodeSP perspectiveParent, parents) {
if (nodeParents.contains(perspectiveParent)) {
return true;
}
}
return false;
}
bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes)
{
bool result = false;
KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node.data());
if (clone) {
KisNodeSP cloneSource = KisNodeSP(clone->copyFrom());
Q_FOREACH(KisNodeSP subtree, nodes) {
result =
recursiveFindNode(subtree,
[cloneSource](KisNodeSP node) -> bool
{
return node == cloneSource;
});
if (!result) {
result = checkIsCloneOf(cloneSource, nodes);
}
if (result) {
break;
}
}
}
return result;
}
void filterMergableNodes(KisNodeList &nodes, bool allowMasks)
{
KisNodeList::iterator it = nodes.begin();
while (it != nodes.end()) {
if ((!allowMasks && !qobject_cast<KisLayer*>(it->data())) ||
checkIsChildOf(*it, nodes)) {
//qDebug() << "Skipping node" << ppVar((*it)->name());
it = nodes.erase(it);
} else {
++it;
}
}
}
void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes)
{
KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root);
if (it != inputNodes.end()) {
outputNodes << *it;
inputNodes.erase(it);
}
if (inputNodes.isEmpty()) {
return;
}
KisNodeSP child = root->firstChild();
while (child) {
sortMergableNodes(child, inputNodes, outputNodes);
child = child->nextSibling();
}
/**
* By the end of recursion \p inputNodes must be empty
*/
KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty());
}
KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes)
{
KisNodeList result;
sortMergableNodes(root, nodes, result);
return result;
}
KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks)
{
KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; }
KisNodeSP root;
Q_FOREACH(KisNodeSP node, nodes) {
KisNodeSP localRoot = node;
while (localRoot->parent()) {
localRoot = localRoot->parent();
}
if (!root) {
root = localRoot;
}
KIS_ASSERT_RECOVER(root == localRoot) { return nodes; }
}
KisNodeList result;
sortMergableNodes(root, nodes, result);
filterMergableNodes(result, allowMasks);
return result;
}
void addCopyOfNameTag(KisNodeSP node)
{
const QString prefix = i18n("Copy of");
QString newName = node->name();
if (!newName.startsWith(prefix)) {
newName = QString("%1 %2").arg(prefix).arg(newName);
node->setName(newName);
}
}
KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot)
{
KisNodeList nodes;
if ((!excludeRoot || root->parent()) && root->check(props)) {
nodes << root;
}
KisNodeSP node = root->firstChild();
while (node) {
nodes += findNodesWithProps(node, props, excludeRoot);
node = node->nextSibling();
}
return nodes;
}
KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter)
{
KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; }
KIS_ASSERT_RECOVER(putAfter) { return nodes; }
KisNodeList visibleNodes;
int putAfterIndex = -1;
Q_FOREACH(KisNodeSP node, nodes) {
if (node->visible() || node->userLocked()) {
visibleNodes << node;
} else {
*invisibleNodes << node;
if (node == *putAfter) {
putAfterIndex = visibleNodes.size() - 1;
}
}
}
if (!visibleNodes.isEmpty() && putAfterIndex >= 0) {
putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1);
*putAfter = visibleNodes[putAfterIndex];
}
return visibleNodes;
}
+ void filterUnlockedNodes(KisNodeList &nodes)
+ {
+ KisNodeList::iterator it = nodes.begin();
+
+ while (it != nodes.end()) {
+ if ((*it)->userLocked()) {
+ it = nodes.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image,
image->root(),
KisProcessingApplicator::RECURSIVE,
emitSignals,
kundo2_i18n("Change projection color"),
0,
142857 + 1);
applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
applicator.end();
}
void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter,
bool flattenSingleLayer, const KUndo2MagicString &actionName,
bool cleanupNodes = true, const QString layerName = QString())
{
if (!putAfter) {
putAfter = mergedNodes.first();
}
filterMergableNodes(mergedNodes);
{
KisNodeList tempNodes;
std::swap(mergedNodes, tempNodes);
sortMergableNodes(image->root(), tempNodes, mergedNodes);
}
if (mergedNodes.size() <= 1 &&
(!flattenSingleLayer && mergedNodes.size() == 1)) return;
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes);
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
actionName);
KisNodeList originalNodes = mergedNodes;
KisNodeList invisibleNodes;
mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter);
if (!invisibleNodes.isEmpty()) {
/* If the putAfter node is invisible,
* we should instead pick one of the nodes
* to be merged to avoid a null putAfter.
*/
if (!putAfter->visible()){
putAfter = mergedNodes.first();
}
applicator.applyCommand(
new SimpleRemoveLayers(invisibleNodes,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) {
MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes));
// disable key strokes on all colorize masks, all onion skins on
// paint layers and wait until update is finished with a barrier
applicator.applyCommand(new DisableColorizeKeyStrokes(info));
applicator.applyCommand(new DisableOnionSkins(info));
applicator.applyCommand(new DisablePassThroughForHeadsOnly(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false));
applicator.applyCommand(new FillSelectionMasks(info));
applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER);
applicator.applyCommand(new DisableExtraCompositing(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
if (info->frames.size() > 0) {
foreach (int frame, info->frames) {
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage));
applicator.applyCommand(new AddNewFrame(info, frame));
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage));
}
} else {
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER);
}
//applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER);
if (cleanupNodes){
applicator.applyCommand(new CleanUpNodes(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
} else {
applicator.applyCommand(new InsertNode(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true));
}
applicator.end();
}
void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter)
{
mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes"));
}
void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter)
{
KisNodeList mergedNodes;
mergedNodes << image->root();
mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible"));
}
struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand {
MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info),
m_putAfter(putAfter){}
void populateChildCommands() override {
KisNodeSP parent;
CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent);
KisLayerSP parentLayer;
do {
parentLayer = qobject_cast<KisLayer*>(parent.data());
parent = parent->parent();
} while(!parentLayer && parent);
KisSelectionSP selection = new KisSelection();
foreach (KisNodeSP node, m_info->allSrcNodes()) {
KisMaskSP mask = dynamic_cast<KisMask*>(node.data());
if (!mask) continue;
selection->pixelSelection()->applySelection(
mask->selection()->pixelSelection(), SELECTION_ADD);
}
KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image);
mergedMask->initSelection(parentLayer);
mergedMask->setSelection(selection);
m_info->dstNode = mergedMask;
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand {
ActivateSelectionMask(MergeDownInfoBaseSP info)
: m_info(info) {}
void populateChildCommands() override {
KisSelectionMaskSP mergedMask = dynamic_cast<KisSelectionMask*>(m_info->dstNode.data());
addCommand(new KisActivateSelectionMaskCommand(mergedMask, true));
}
private:
MergeDownInfoBaseSP m_info;
};
bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter)
{
QList<KisSelectionMaskSP> selectionMasks;
for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) {
KisSelectionMaskSP mask = dynamic_cast<KisSelectionMask*>(it->data());
if (!mask) {
it = mergedNodes.erase(it);
} else {
selectionMasks.append(mask);
++it;
}
}
if (mergedNodes.isEmpty()) return false;
KisLayerSP parentLayer = qobject_cast<KisLayer*>(selectionMasks.first()->parent().data());
KIS_ASSERT_RECOVER(parentLayer) { return 0; }
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
kundo2_i18n("Merge Selection Masks"));
MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes));
applicator.applyCommand(new MergeSelectionMasks(info, putAfter));
applicator.applyCommand(new CleanUpNodes(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new ActivateSelectionMask(info));
applicator.end();
return true;
}
void flattenLayer(KisImageSP image, KisLayerSP layer)
{
if (!layer->childCount() && !layer->layerStyle())
return;
KisNodeList mergedNodes;
mergedNodes << layer;
mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer"));
}
void flattenImage(KisImageSP image, KisNodeSP activeNode)
{
if (!activeNode) {
activeNode = image->root()->lastChild();
}
KisNodeList mergedNodes;
mergedNodes << image->root();
mergeMultipleLayersImpl(image, mergedNodes, activeNode, true, kundo2_i18n("Flatten Image"));
}
KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent)
: FlipFlopCommand(finalize, parent),
m_nodes(nodes)
{
}
void KisSimpleUpdateCommand::partB()
{
updateNodes(m_nodes);
}
void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes)
{
Q_FOREACH(KisNodeSP node, nodes) {
node->setDirty(node->extent());
}
}
KisNodeSP recursiveFindNode(KisNodeSP node, std::function<bool(KisNodeSP)> func)
{
if (func(node)) {
return node;
}
node = node->firstChild();
while (node) {
KisNodeSP resultNode = recursiveFindNode(node, func);
if (resultNode) {
return resultNode;
}
node = node->nextSibling();
}
return 0;
}
KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid)
{
return recursiveFindNode(root,
[uuid] (KisNodeSP node) {
return node->uuid() == uuid;
});
}
void forceAllDelayedNodesUpdate(KisNodeSP root)
{
KisLayerUtils::recursiveApplyNodes(root,
[] (KisNodeSP node) {
KisDelayedUpdateNodeInterface *delayedUpdate =
dynamic_cast<KisDelayedUpdateNodeInterface*>(node.data());
if (delayedUpdate) {
delayedUpdate->forceUpdateTimedNode();
}
});
}
bool hasDelayedNodeWithUpdates(KisNodeSP root)
{
return recursiveFindNode(root,
[] (KisNodeSP node) {
KisDelayedUpdateNodeInterface *delayedUpdate =
dynamic_cast<KisDelayedUpdateNodeInterface*>(node.data());
return delayedUpdate ? delayedUpdate->hasPendingTimedUpdates() : false;
});
}
KisImageSP findImageByHierarchy(KisNodeSP node)
{
while (node) {
const KisLayer *layer = dynamic_cast<const KisLayer*>(node.data());
if (layer) {
return layer->image();
}
node = node->parent();
}
return 0;
}
}
diff --git a/libs/image/kis_layer_utils.h b/libs/image/kis_layer_utils.h
index 0577e13564..06aad322e7 100644
--- a/libs/image/kis_layer_utils.h
+++ b/libs/image/kis_layer_utils.h
@@ -1,225 +1,226 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_LAYER_UTILS_H
#define __KIS_LAYER_UTILS_H
#include <functional>
#include "kundo2command.h"
#include "kis_types.h"
#include "kritaimage_export.h"
#include "kis_command_utils.h"
class KoProperties;
class KoColor;
class QUuid;
namespace KisMetaData
{
class MergeStrategy;
}
namespace KisLayerUtils
{
KRITAIMAGE_EXPORT void sortMergableNodes(KisNodeSP root, QList<KisNodeSP> &inputNodes, QList<KisNodeSP> &outputNodes);
KRITAIMAGE_EXPORT KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes);
KRITAIMAGE_EXPORT void filterMergableNodes(KisNodeList &nodes, bool allowMasks = false);
KRITAIMAGE_EXPORT bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents);
+ KRITAIMAGE_EXPORT void filterUnlockedNodes(KisNodeList &nodes);
/**
* Returns true if:
* o \p node is a clone of some layer in \p nodes
* o \p node is a clone any child layer of any layer in \p nodes
* o \p node is a clone of a clone of a ..., that in the end points
* to any layer in \p nodes of their children.
*/
KRITAIMAGE_EXPORT bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes);
KRITAIMAGE_EXPORT void forceAllDelayedNodesUpdate(KisNodeSP root);
KRITAIMAGE_EXPORT bool hasDelayedNodeWithUpdates(KisNodeSP root);
KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false);
KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy);
KRITAIMAGE_EXPORT QSet<int> fetchLayerFrames(KisNodeSP node);
KRITAIMAGE_EXPORT QSet<int> fetchLayerFramesRecursive(KisNodeSP rootNode);
KRITAIMAGE_EXPORT void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter);
KRITAIMAGE_EXPORT void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter);
KRITAIMAGE_EXPORT bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter);
KRITAIMAGE_EXPORT void flattenLayer(KisImageSP image, KisLayerSP layer);
KRITAIMAGE_EXPORT void flattenImage(KisImageSP image, KisNodeSP activeNode);
KRITAIMAGE_EXPORT void addCopyOfNameTag(KisNodeSP node);
KRITAIMAGE_EXPORT KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot);
KRITAIMAGE_EXPORT void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color);
typedef QMap<int, QSet<KisNodeSP> > FrameJobs;
void updateFrameJobs(FrameJobs *jobs, KisNodeSP node);
void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode);
struct SwitchFrameCommand : public KisCommandUtils::FlipFlopCommand {
struct SharedStorage {
/**
* For some reason the absence of a destructor in the SharedStorage
* makes Krita crash on exit. Seems like some compiler weirdness... (DK)
*/
~SharedStorage();
int value;
};
typedef QSharedPointer<SharedStorage> SharedStorageSP;
public:
SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage);
~SwitchFrameCommand() override;
private:
void partA() override;
void partB() override;
private:
KisImageWSP m_image;
int m_newTime;
SharedStorageSP m_storage;
};
/**
* A command to keep correct set of selected/active nodes thoroughout
* the action.
*/
class KRITAIMAGE_EXPORT KeepNodesSelectedCommand : public KisCommandUtils::FlipFlopCommand
{
public:
KeepNodesSelectedCommand(const KisNodeList &selectedBefore,
const KisNodeList &selectedAfter,
KisNodeSP activeBefore,
KisNodeSP activeAfter,
KisImageSP image,
bool finalize, KUndo2Command *parent = 0);
void partB() override;
private:
KisNodeList m_selectedBefore;
KisNodeList m_selectedAfter;
KisNodeSP m_activeBefore;
KisNodeSP m_activeAfter;
KisImageWSP m_image;
};
struct KRITAIMAGE_EXPORT SelectGlobalSelectionMask : public KUndo2Command
{
SelectGlobalSelectionMask(KisImageSP image);
void redo() override;
KisImageSP m_image;
};
class KRITAIMAGE_EXPORT RemoveNodeHelper {
public:
virtual ~RemoveNodeHelper();
protected:
virtual void addCommandImpl(KUndo2Command *cmd) = 0;
void safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image);
private:
bool checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes);
static bool scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove);
};
struct SimpleRemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
SimpleRemoveLayers(const KisNodeList &nodes,
KisImageSP image);
void populateChildCommands() override;
protected:
void addCommandImpl(KUndo2Command *cmd) override;
private:
KisNodeList m_nodes;
KisImageSP m_image;
KisNodeList m_selectedNodes;
KisNodeSP m_activeNode;
};
class KRITAIMAGE_EXPORT KisSimpleUpdateCommand : public KisCommandUtils::FlipFlopCommand
{
public:
KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent = 0);
void partB() override;
static void updateNodes(const KisNodeList &nodes);
private:
KisNodeList m_nodes;
};
template <typename T>
bool checkNodesDiffer(KisNodeList nodes, std::function<T(KisNodeSP)> checkerFunc)
{
bool valueDiffers = false;
bool initialized = false;
T currentValue = T();
Q_FOREACH (KisNodeSP node, nodes) {
if (!initialized) {
currentValue = checkerFunc(node);
initialized = true;
} else if (currentValue != checkerFunc(node)) {
valueDiffers = true;
break;
}
}
return valueDiffers;
}
/**
* Applies \p func to \p node and all its children recursively
*/
template <typename NodePointer, typename Functor>
void recursiveApplyNodes(NodePointer node, Functor func)
{
func(node);
node = node->firstChild();
while (node) {
recursiveApplyNodes(node, func);
node = node->nextSibling();
}
}
/**
* Walks through \p node and all its children recursively until
* \p func returns true. When \p func returns true, the node is
* considered to be found, the search is stopped and the found
* node is returned to the caller.
*/
KisNodeSP KRITAIMAGE_EXPORT recursiveFindNode(KisNodeSP node, std::function<bool(KisNodeSP)> func);
/**
* Recursively searches for a node with specified Uuid
*/
KisNodeSP KRITAIMAGE_EXPORT findNodeByUuid(KisNodeSP root, const QUuid &uuid);
KisImageSP KRITAIMAGE_EXPORT findImageByHierarchy(KisNodeSP node);
}
#endif /* __KIS_LAYER_UTILS_H */
diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc
index cf7c4d560a..2a71588134 100644
--- a/libs/image/kis_mask.cc
+++ b/libs/image/kis_mask.cc
@@ -1,510 +1,511 @@
/*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
* (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_mask.h"
#include <kis_debug.h>
// to prevent incomplete class types on "delete selection->flatten();"
#include <kundo2command.h>
#include <QScopedPointer>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include "kis_paint_device.h"
#include "kis_selection.h"
#include "kis_pixel_selection.h"
#include "kis_painter.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_cached_paint_device.h"
#include "kis_mask_projection_plane.h"
#include "kis_raster_keyframe_channel.h"
#include "KisSafeNodeProjectionStore.h"
struct Q_DECL_HIDDEN KisMask::Private {
Private(KisMask *_q)
: q(_q),
projectionPlane(new KisMaskProjectionPlane(q))
{
}
mutable KisSelectionSP selection;
KisCachedPaintDevice paintDeviceCache;
KisMask *q;
/**
* Due to the design of the Kra format the X,Y offset of the paint
* device belongs to the node, but not to the device itself. So
* the offset is set when the node is created, but not when the
* selection is initialized. This causes the X,Y values to be
* lost, since the selection doen not exist at the moment. That is
* why we save it separately.
*/
QScopedPointer<QPoint> deferredSelectionOffset;
KisAbstractProjectionPlaneSP projectionPlane;
KisSafeSelectionNodeProjectionStoreSP safeProjection;
void initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice);
};
KisMask::KisMask(const QString & name)
: KisNode(nullptr)
, m_d(new Private(this))
{
setName(name);
m_d->safeProjection = new KisSafeSelectionNodeProjectionStore();
+ m_d->safeProjection->setImage(image());
}
KisMask::KisMask(const KisMask& rhs)
: KisNode(rhs)
, KisIndirectPaintingSupport()
, m_d(new Private(this))
{
setName(rhs.name());
m_d->safeProjection = new KisSafeSelectionNodeProjectionStore(*rhs.m_d->safeProjection);
if (rhs.m_d->selection) {
m_d->selection = new KisSelection(*rhs.m_d->selection.data());
m_d->selection->setParentNode(this);
KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection();
if (pixelSelection->framesInterface()) {
addKeyframeChannel(pixelSelection->keyframeChannel());
enableAnimation();
}
}
}
KisMask::~KisMask()
{
if (m_d->selection) {
m_d->selection->setParentNode(0);
}
delete m_d;
}
void KisMask::setImage(KisImageWSP image)
{
KisPaintDeviceSP parentPaintDevice = parent() ? parent()->original() : 0;
KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice, image);
if (m_d->selection) {
m_d->selection->setDefaultBounds(defaultBounds);
}
m_d->safeProjection->setImage(image);
KisNode::setImage(image);
}
bool KisMask::allowAsChild(KisNodeSP node) const
{
Q_UNUSED(node);
return false;
}
const KoColorSpace * KisMask::colorSpace() const
{
KisNodeSP parentNode = parent();
return parentNode ? parentNode->colorSpace() : 0;
}
const KoCompositeOp * KisMask::compositeOp() const
{
/**
* FIXME: This function duplicates the same function from
* KisLayer. We can't move it to KisBaseNode as it doesn't
* know anything about parent() method of KisNode
* Please think it over...
*/
const KoColorSpace *colorSpace = this->colorSpace();
if (!colorSpace) return 0;
const KoCompositeOp* op = colorSpace->compositeOp(compositeOpId());
return op ? op : colorSpace->compositeOp(COMPOSITE_OVER);
}
void KisMask::initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer)
{
m_d->initSelectionImpl(copyFrom, parentLayer, 0);
}
void KisMask::initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer)
{
m_d->initSelectionImpl(0, parentLayer, copyFromDevice);
}
void KisMask::initSelection(KisLayerSP parentLayer)
{
m_d->initSelectionImpl(0, parentLayer, 0);
}
void KisMask::Private::initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice)
{
Q_ASSERT(parentLayer);
KisPaintDeviceSP parentPaintDevice = parentLayer->original();
if (copyFrom) {
/**
* We can't use setSelection as we may not have parent() yet
*/
selection = new KisSelection(*copyFrom);
selection->setDefaultBounds(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image()));
if (copyFrom->hasShapeSelection()) {
delete selection->flatten();
}
} else if (copyFromDevice) {
KritaUtils::DeviceCopyMode copyMode =
q->inherits("KisFilterMask") || q->inherits("KisTransparencyMask") ?
KritaUtils::CopyAllFrames : KritaUtils::CopySnapshot;
selection = new KisSelection(copyFromDevice, copyMode, new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image()));
KisPixelSelectionSP pixelSelection = selection->pixelSelection();
if (pixelSelection->framesInterface()) {
q->addKeyframeChannel(pixelSelection->keyframeChannel());
q->enableAnimation();
}
} else {
selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image()));
selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, selection->pixelSelection()->colorSpace()));
if (deferredSelectionOffset) {
selection->setX(deferredSelectionOffset->x());
selection->setY(deferredSelectionOffset->y());
deferredSelectionOffset.reset();
}
}
selection->setParentNode(q);
selection->updateProjection();
}
KisSelectionSP KisMask::selection() const
{
return m_d->selection;
}
KisPaintDeviceSP KisMask::paintDevice() const
{
KisSelectionSP selection = this->selection();
return selection ? selection->pixelSelection() : 0;
}
KisPaintDeviceSP KisMask::original() const
{
return paintDevice();
}
KisPaintDeviceSP KisMask::projection() const
{
KisPaintDeviceSP originalDevice = original();
KisPaintDeviceSP result = originalDevice;
KisSelectionSP selection = this->selection();
if (selection && hasTemporaryTarget()) {
result = m_d->safeProjection->getDeviceLazy(selection)->pixelSelection();
}
return result;
}
KisAbstractProjectionPlaneSP KisMask::projectionPlane() const
{
return m_d->projectionPlane;
}
void KisMask::setSelection(KisSelectionSP selection)
{
m_d->selection = selection;
if (parent()) {
const KisLayer *parentLayer = qobject_cast<const KisLayer*>(parent());
m_d->selection->setDefaultBounds(new KisDefaultBounds(parentLayer->image()));
}
m_d->selection->setParentNode(this);
}
void KisMask::select(const QRect & rc, quint8 selectedness)
{
KisSelectionSP sel = selection();
KisPixelSelectionSP psel = sel->pixelSelection();
psel->select(rc, selectedness);
sel->updateProjection(rc);
}
QRect KisMask::decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc,
PositionToFilthy maskPos) const
{
Q_UNUSED(src);
Q_UNUSED(dst);
Q_UNUSED(maskPos);
Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors");
return rc;
}
bool KisMask::paintsOutsideSelection() const
{
return false;
}
void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy maskPos) const
{
if (selection()) {
flattenSelectionProjection(m_d->selection, applyRect);
KisSelectionSP effectiveSelection = m_d->selection;
{
// Access temporary target under the lock held
KisIndirectPaintingSupport::ReadLocker l(this);
if (!paintsOutsideSelection()) {
// extent of m_d->selection should also be accessed under a lock,
// because it might be being merged in by the temporary target atm
QRect effectiveExtent = m_d->selection->selectedRect();
if (hasTemporaryTarget()) {
effectiveExtent |= temporaryTarget()->extent();
}
if(!effectiveExtent.intersects(applyRect)) {
return;
}
}
if (hasTemporaryTarget()) {
effectiveSelection = m_d->safeProjection->getDeviceLazy(m_d->selection);
KisPainter::copyAreaOptimized(applyRect.topLeft(),
m_d->selection->pixelSelection(),
effectiveSelection->pixelSelection(), applyRect);
KisPainter gc(effectiveSelection->pixelSelection());
setupTemporaryPainter(&gc);
gc.bitBlt(applyRect.topLeft(), temporaryTarget(), applyRect);
} else {
m_d->safeProjection->releaseDevice();
}
mergeInMaskInternal(projection, effectiveSelection, applyRect, needRect, maskPos);
}
} else {
mergeInMaskInternal(projection, 0, applyRect, needRect, maskPos);
}
}
void KisMask::mergeInMaskInternal(KisPaintDeviceSP projection,
KisSelectionSP effectiveSelection,
const QRect &applyRect,
const QRect &preparedNeedRect,
KisNode::PositionToFilthy maskPos) const
{
KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection);
if (effectiveSelection) {
QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos);
// masks don't have any compositioning
KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, effectiveSelection);
} else {
cacheDevice->makeCloneFromRough(projection, preparedNeedRect);
projection->clear(preparedNeedRect);
decorateRect(cacheDevice, projection, applyRect, maskPos);
}
m_d->paintDeviceCache.putDevice(cacheDevice);
}
void KisMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const
{
selection->updateProjection(dirtyRect);
}
QRect KisMask::needRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
QRect resultRect = rect;
if (m_d->selection) {
QRect selectionExtent = m_d->selection->selectedRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
selectionExtent |= temporaryTarget->extent();
}
resultRect &= selectionExtent;
}
return resultRect;
}
QRect KisMask::changeRect(const QRect &rect, PositionToFilthy pos) const
{
return KisMask::needRect(rect, pos);
}
QRect KisMask::extent() const
{
QRect resultRect;
if (m_d->selection) {
resultRect = m_d->selection->selectedRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
resultRect |= temporaryTarget->extent();
}
} else if (KisNodeSP parent = this->parent()) {
resultRect = parent->extent();
}
return resultRect;
}
QRect KisMask::exactBounds() const
{
QRect resultRect;
if (m_d->selection) {
resultRect = m_d->selection->selectedExactRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
resultRect |= temporaryTarget->exactBounds();
}
} else if (KisNodeSP parent = this->parent()) {
resultRect = parent->exactBounds();
}
return resultRect;
}
qint32 KisMask::x() const
{
return m_d->selection ? m_d->selection->x() :
m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->x() :
parent() ? parent()->x() : 0;
}
qint32 KisMask::y() const
{
return m_d->selection ? m_d->selection->y() :
m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->y() :
parent() ? parent()->y() : 0;
}
void KisMask::setX(qint32 x)
{
if (m_d->selection) {
m_d->selection->setX(x);
} else if (!m_d->deferredSelectionOffset) {
m_d->deferredSelectionOffset.reset(new QPoint(x, 0));
} else {
m_d->deferredSelectionOffset->rx() = x;
}
}
void KisMask::setY(qint32 y)
{
if (m_d->selection) {
m_d->selection->setY(y);
} else if (!m_d->deferredSelectionOffset) {
m_d->deferredSelectionOffset.reset(new QPoint(0, y));
} else {
m_d->deferredSelectionOffset->ry() = y;
}
}
QRect KisMask::nonDependentExtent() const
{
return QRect();
}
QImage KisMask::createThumbnail(qint32 w, qint32 h)
{
KisPaintDeviceSP originalDevice =
selection() ? selection()->projection() : 0;
return originalDevice ?
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) : QImage();
}
void KisMask::testingInitSelection(const QRect &rect, KisLayerSP parentLayer)
{
if (parentLayer) {
m_d->selection = new KisSelection(new KisSelectionDefaultBounds(parentLayer->paintDevice(), parentLayer->image()));
} else {
m_d->selection = new KisSelection();
}
m_d->selection->pixelSelection()->select(rect, OPACITY_OPAQUE_U8);
m_d->selection->updateProjection(rect);
m_d->selection->setParentNode(this);
}
KisKeyframeChannel *KisMask::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::Content.id()) {
KisPaintDeviceSP device = paintDevice();
if (device) {
KisRasterKeyframeChannel *contentChannel = device->createKeyframeChannel(KisKeyframeChannel::Content);
contentChannel->setFilenameSuffix(".pixelselection");
return contentChannel;
}
}
return KisNode::requestKeyframeChannel(id);
}
void KisMask::baseNodeChangedCallback()
{
KisNodeSP up = parent();
KisLayer *layer = dynamic_cast<KisLayer*>(up.data());
if (layer) {
layer->notifyChildMaskChanged();
}
KisNode::baseNodeChangedCallback();
}
diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc
index 3f33243078..fe28fef884 100644
--- a/libs/image/kis_painter.cc
+++ b/libs/image/kis_painter.cc
@@ -1,3019 +1,3021 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 José Luis Vergara Toloza <pentalis@gmail.com>
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_painter.h"
#include <stdlib.h>
#include <string.h>
#include <cfloat>
#include <cmath>
#include <climits>
#ifndef Q_OS_WIN
#include <strings.h>
#endif
#include <QImage>
#include <QRect>
#include <QString>
#include <QStringList>
#include <kundo2command.h>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include "kis_image.h"
#include "filter/kis_filter.h"
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_fixed_paint_device.h"
#include "kis_transaction.h"
#include "kis_vec.h"
#include "kis_iterator_ng.h"
#include "kis_random_accessor_ng.h"
#include "filter/kis_filter_configuration.h"
#include "kis_pixel_selection.h"
#include <brushengine/kis_paint_information.h>
#include "kis_paintop_registry.h"
#include "kis_perspective_math.h"
#include "tiles3/kis_random_accessor.h"
#include <kis_distance_information.h>
#include <KoColorSpaceMaths.h>
#include "kis_lod_transform.h"
#include "kis_algebra_2d.h"
#include "krita_utils.h"
// Maximum distance from a Bezier control point to the line through the start
// and end points for the curve to be considered flat.
#define BEZIER_FLATNESS_THRESHOLD 0.5
#include "kis_painter_p.h"
KisPainter::KisPainter()
: d(new Private(this))
{
init();
}
KisPainter::KisPainter(KisPaintDeviceSP device)
: d(new Private(this, device->colorSpace()))
{
init();
Q_ASSERT(device);
begin(device);
}
KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection)
: d(new Private(this, device->colorSpace()))
{
init();
Q_ASSERT(device);
begin(device);
d->selection = selection;
}
void KisPainter::init()
{
d->selection = 0 ;
d->transaction = 0;
d->paintOp = 0;
d->pattern = 0;
d->sourceLayer = 0;
d->fillStyle = FillStyleNone;
d->strokeStyle = StrokeStyleBrush;
d->antiAliasPolygonFill = true;
d->progressUpdater = 0;
d->gradient = 0;
d->maskPainter = 0;
d->fillPainter = 0;
d->maskImageWidth = 255;
d->maskImageHeight = 255;
d->mirrorHorizontally = false;
d->mirrorVertically = false;
d->isOpacityUnit = true;
d->paramInfo = KoCompositeOp::ParameterInfo();
d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent();
d->conversionFlags = KoColorConversionTransformation::internalConversionFlags();
}
KisPainter::~KisPainter()
{
// TODO: Maybe, don't be that strict?
// deleteTransaction();
end();
delete d->paintOp;
delete d->maskPainter;
delete d->fillPainter;
delete d;
}
template <bool useOldData>
void copyAreaOptimizedImpl(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &srcRect)
{
const QRect dstRect(dstPt, srcRect.size());
const QRect srcExtent = src->extent();
const QRect dstExtent = dst->extent();
const QRect srcSampleRect = srcExtent & srcRect;
const QRect dstSampleRect = dstExtent & dstRect;
const bool srcEmpty = srcSampleRect.isEmpty();
const bool dstEmpty = dstSampleRect.isEmpty();
if (!srcEmpty || !dstEmpty) {
if (srcEmpty) {
dst->clear(dstRect);
} else {
QRect srcCopyRect = srcRect;
QRect dstCopyRect = dstRect;
if (!srcExtent.contains(srcRect)) {
if (src->defaultPixel() == dst->defaultPixel()) {
const QRect dstSampleInSrcCoords = dstSampleRect.translated(srcRect.topLeft() - dstPt);
if (dstSampleInSrcCoords.isEmpty() || srcSampleRect.contains(dstSampleInSrcCoords)) {
srcCopyRect = srcSampleRect;
} else {
srcCopyRect = srcSampleRect | dstSampleInSrcCoords;
}
dstCopyRect = QRect(dstPt + srcCopyRect.topLeft() - srcRect.topLeft(), srcCopyRect.size());
}
}
KisPainter gc(dst);
gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY));
if (useOldData) {
gc.bitBltOldData(dstCopyRect.topLeft(), src, srcCopyRect);
} else {
gc.bitBlt(dstCopyRect.topLeft(), src, srcCopyRect);
}
}
}
}
void KisPainter::copyAreaOptimized(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &srcRect)
{
copyAreaOptimizedImpl<false>(dstPt, src, dst, srcRect);
}
void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &srcRect)
{
copyAreaOptimizedImpl<true>(dstPt, src, dst, srcRect);
}
void KisPainter::copyAreaOptimized(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &originalSrcRect,
KisSelectionSP selection)
{
if (!selection) {
copyAreaOptimized(dstPt, src, dst, originalSrcRect);
return;
}
const QRect selectionRect = selection->selectedRect();
const QRect srcRect = originalSrcRect & selectionRect;
const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft();
const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size());
const bool srcEmpty = (src->extent() & srcRect).isEmpty();
const bool dstEmpty = (dst->extent() & dstRect).isEmpty();
if (!srcEmpty || !dstEmpty) {
//if (srcEmpty) {
// doesn't support dstRect
// dst->clearSelection(selection);
// } else */
{
KisPainter gc(dst);
gc.setSelection(selection);
gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY));
gc.bitBlt(dstRect.topLeft(), src, srcRect);
}
}
}
KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src)
{
const KoColorSpace *srcCS = src->colorSpace();
const QRect processRect = src->extent();
KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()));
if (processRect.isEmpty()) return dst;
KisSequentialConstIterator srcIt(src, processRect);
KisSequentialIterator dstIt(dst, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
const quint8 *srcPtr = srcIt.rawDataConst();
quint8 *alpha8Ptr = dstIt.rawData();
const quint8 white = srcCS->intensity8(srcPtr);
const quint8 alpha = srcCS->opacityU8(srcPtr);
*alpha8Ptr = KoColorSpaceMaths<quint8>::multiply(alpha, KoColorSpaceMathsTraits<quint8>::unitValue - white);
}
return dst;
}
KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src)
{
const KoColorSpace *srcCS = src->colorSpace();
const QRect processRect = src->extent();
KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()));
if (processRect.isEmpty()) return dst;
KisSequentialConstIterator srcIt(src, processRect);
KisSequentialIterator dstIt(dst, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
const quint8 *srcPtr = srcIt.rawDataConst();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->intensity8(srcPtr);
}
return dst;
}
bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev)
{
const QRect deviceBounds = dev->exactBounds();
const QRect imageBounds = dev->defaultBounds()->bounds();
if (deviceBounds.isEmpty() ||
(deviceBounds & imageBounds) != imageBounds) {
return true;
}
const KoColorSpace *cs = dev->colorSpace();
KisSequentialConstIterator it(dev, deviceBounds);
while(it.nextPixel()) {
if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) {
return true;
}
}
return false;
}
void KisPainter::begin(KisPaintDeviceSP device)
{
begin(device, d->selection);
}
void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection)
{
if (!device) return;
d->selection = selection;
Q_ASSERT(device->colorSpace());
end();
d->device = device;
d->colorSpace = device->colorSpace();
d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER);
d->pixelSize = device->pixelSize();
}
void KisPainter::end()
{
Q_ASSERT_X(!d->transaction, "KisPainter::end()",
"end() was called for the painter having a transaction. "
"Please use end/deleteTransaction() instead");
}
void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID)
{
Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()",
"You asked for a new transaction while still having "
"another one. Please finish the first one with "
"end/deleteTransaction() first");
d->transaction = new KisTransaction(transactionName, d->device);
Q_CHECK_PTR(d->transaction);
d->transaction->undoCommand()->setTimedID(timedID);
}
void KisPainter::revertTransaction()
{
Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()",
"No transaction is in progress");
d->transaction->revert();
delete d->transaction;
d->transaction = 0;
}
void KisPainter::endTransaction(KisUndoAdapter *undoAdapter)
{
Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()",
"No transaction is in progress");
d->transaction->commit(undoAdapter);
delete d->transaction;
d->transaction = 0;
}
void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter)
{
Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()",
"No transaction is in progress");
d->transaction->commit(undoAdapter);
delete d->transaction;
d->transaction = 0;
}
KUndo2Command* KisPainter::endAndTakeTransaction()
{
Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()",
"No transaction is in progress");
KUndo2Command *transactionData = d->transaction->endAndTake();
delete d->transaction;
d->transaction = 0;
return transactionData;
}
void KisPainter::deleteTransaction()
{
if (!d->transaction) return;
delete d->transaction;
d->transaction = 0;
}
void KisPainter::putTransaction(KisTransaction* transaction)
{
Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()",
"You asked for a new transaction while still having "
"another one. Please finish the first one with "
"end/deleteTransaction() first");
d->transaction = transaction;
}
KisTransaction* KisPainter::takeTransaction()
{
Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()",
"No transaction is in progress");
KisTransaction *temp = d->transaction;
d->transaction = 0;
return temp;
}
QVector<QRect> KisPainter::takeDirtyRegion()
{
QVector<QRect> vrect = d->dirtyRects;
d->dirtyRects.clear();
return vrect;
}
void KisPainter::addDirtyRect(const QRect & rc)
{
QRect r = rc.normalized();
if (r.isValid()) {
d->dirtyRects.append(rc);
}
}
void KisPainter::addDirtyRects(const QVector<QRect> &rects)
{
d->dirtyRects.reserve(d->dirtyRects.size() + rects.size());
Q_FOREACH (const QRect &rc, rects) {
const QRect r = rc.normalized();
if (r.isValid()) {
d->dirtyRects.append(rc);
}
}
}
inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev,
QRect *srcRect,
qint32 *srcX,
qint32 *srcY,
qint32 *srcWidth,
qint32 *srcHeight,
qint32 *dstX,
qint32 *dstY)
{
/**
* In case of COMPOSITE_COPY and Wrap Around Mode even the pixels
* outside the device extent matter, because they will be either
* directly copied (former case) or cloned from another area of
* the image.
*/
if (compositeOp->id() != COMPOSITE_COPY &&
compositeOp->id() != COMPOSITE_DESTINATION_IN &&
compositeOp->id() != COMPOSITE_DESTINATION_ATOP &&
!srcDev->defaultBounds()->wrapAroundMode()) {
/**
* If srcDev->extent() (the area of the tiles containing
* srcDev) is smaller than srcRect, then shrink srcRect to
* that size. This is done as a speed optimization, useful for
* stack recomposition in KisImage. srcRect won't grow if
* srcDev->extent() is larger.
*/
*srcRect &= srcDev->extent();
if (srcRect->isEmpty()) return true;
// Readjust the function paramenters to the new dimensions.
*dstX += srcRect->x() - *srcX; // This will only add, not subtract
*dstY += srcRect->y() - *srcY; // Idem
srcRect->getRect(srcX, srcY, srcWidth, srcHeight);
}
return false;
}
void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 selX, qint32 selY,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
// TODO: get selX and selY working as intended
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
// Check that selection has an alpha colorspace, crash if false
Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8());
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
QRect selRect = QRect(selX, selY, srcWidth, srcHeight);
/* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done,
so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */
Q_ASSERT(selection->bounds().contains(selRect));
Q_UNUSED(selRect); // only used by the above Q_ASSERT
/**
* An optimization, which crops the source rect by the bounds of
* the source device when it is possible
*/
if (d->tryReduceSourceRect(srcDev, &srcRect,
&srcX, &srcY,
&srcWidth, &srcHeight,
&dstX, &dstY)) return;
/* Create an intermediate byte array to hold information before it is written
to the current paint device (d->device) */
quint8* dstBytes = 0;
try {
dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
} catch (const std::bad_alloc&) {
warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes";
return;
}
d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
// Copy the relevant bytes of raw data from srcDev
quint8* srcBytes = 0;
try {
srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()];
} catch (const std::bad_alloc&) {
warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes";
return;
}
srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight);
QRect selBounds = selection->bounds();
const quint8 *selRowStart = selection->data() +
(selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize();
/*
* This checks whether there is nothing selected.
*/
if (!d->selection) {
/* As there's nothing selected, blit to dstBytes (intermediary bit array),
ignoring d->selection (the user selection)*/
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcBytes;
d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize();
d->paramInfo.maskRowStart = selRowStart;
d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
}
else {
/* Read the user selection (d->selection) bytes into an array, ready
to merge in the next block*/
quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize();
quint8* mergedSelectionBytes = 0;
try {
mergedSelectionBytes = new quint8[ totalBytes ];
} catch (const std::bad_alloc&) {
warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes";
return;
}
d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight);
// Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT)
d->paramInfo.dstRowStart = mergedSelectionBytes;
d->paramInfo.dstRowStride = srcWidth * selection->pixelSize();
d->paramInfo.srcRowStart = selRowStart;
d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo);
// Blit to dstBytes (intermediary bit array)
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcBytes;
d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize();
d->paramInfo.maskRowStart = mergedSelectionBytes;
d->paramInfo.maskRowStride = srcWidth * selection->pixelSize();
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
delete[] mergedSelectionBytes;
}
d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
delete[] dstBytes;
delete[] srcBytes;
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 srcWidth, qint32 srcHeight)
{
bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight);
}
template <bool useOldSrcData>
void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
if (d->compositeOp->id() == COMPOSITE_COPY) {
if(!d->selection && d->isOpacityUnit &&
srcX == dstX && srcY == dstY &&
d->device->fastBitBltPossible(srcDev)) {
if(useOldSrcData) {
d->device->fastBitBltOldData(srcDev, srcRect);
} else {
d->device->fastBitBlt(srcDev, srcRect);
}
addDirtyRect(srcRect);
return;
}
}
else {
/**
* An optimization, which crops the source rect by the bounds of
* the source device when it is possible
*/
if (d->tryReduceSourceRect(srcDev, &srcRect,
&srcX, &srcY,
&srcWidth, &srcHeight,
&dstX, &dstY)) return;
}
qint32 dstY_ = dstY;
qint32 srcY_ = srcY;
qint32 rowsRemaining = srcHeight;
// Read below
KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY);
KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY);
/* Here be a huge block of verbose code that does roughly the same than
the other bit blit operations. This one is longer than the rest in an effort to
optimize speed and memory use */
if (d->selection) {
KisPaintDeviceSP selectionProjection(d->selection->projection());
KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY);
while (rowsRemaining > 0) {
qint32 dstX_ = dstX;
qint32 srcX_ = srcX;
qint32 columnsRemaining = srcWidth;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_);
qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_);
qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_);
qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows);
rows = qMin(rows, numContiguousSelRows);
rows = qMin(rows, rowsRemaining);
while (columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_);
qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_);
qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_);
qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns);
columns = qMin(columns, numContiguousSelColumns);
columns = qMin(columns, columnsRemaining);
qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_);
srcIt->moveTo(srcX_, srcY_);
qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_);
dstIt->moveTo(dstX_, dstY_);
qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_);
maskIt->moveTo(dstX_, dstY_);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
// if we don't use the oldRawData, we need to access the rawData of the source device.
d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast<KisRandomAccessor2*>(srcIt.data())->rawData();
d->paramInfo.srcRowStride = srcRowStride;
d->paramInfo.maskRowStart = static_cast<KisRandomAccessor2*>(maskIt.data())->rawData();
d->paramInfo.maskRowStride = maskRowStride;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
srcX_ += columns;
dstX_ += columns;
columnsRemaining -= columns;
}
srcY_ += rows;
dstY_ += rows;
rowsRemaining -= rows;
}
}
else {
while (rowsRemaining > 0) {
qint32 dstX_ = dstX;
qint32 srcX_ = srcX;
qint32 columnsRemaining = srcWidth;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_);
qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_);
qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows);
rows = qMin(rows, rowsRemaining);
while (columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_);
qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_);
qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns);
columns = qMin(columns, columnsRemaining);
qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_);
srcIt->moveTo(srcX_, srcY_);
qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_);
dstIt->moveTo(dstX_, dstY_);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
// if we don't use the oldRawData, we need to access the rawData of the source device.
d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast<KisRandomAccessor2*>(srcIt.data())->rawData();
d->paramInfo.srcRowStride = srcRowStride;
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
srcX_ += columns;
dstX_ += columns;
columnsRemaining -= columns;
}
srcY_ += rows;
dstY_ += rows;
rowsRemaining -= rows;
}
}
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bitBlt(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
bitBltImpl<false>(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight);
}
void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect)
{
bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height());
}
void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
bitBltImpl<true>(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight);
}
void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect)
{
bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height());
}
void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color)
{
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
* initializing they perform some dummy passes with those parameters, and it must not crash */
if(width == 0 || height == 0 || d->device.isNull())
return;
KoColor srcColor(color, d->device->compositionSourceColorSpace());
qint32 dstY = y;
qint32 rowsRemaining = height;
KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y);
if(d->selection) {
KisPaintDeviceSP selectionProjection(d->selection->projection());
KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y);
while(rowsRemaining > 0) {
qint32 dstX = x;
qint32 columnsRemaining = width;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY);
qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY);
qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows);
rows = qMin(rows, rowsRemaining);
while (columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX);
qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX);
qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns);
columns = qMin(columns, columnsRemaining);
qint32 dstRowStride = dstIt->rowStride(dstX, dstY);
dstIt->moveTo(dstX, dstY);
qint32 maskRowStride = maskIt->rowStride(dstX, dstY);
maskIt->moveTo(dstX, dstY);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
d->paramInfo.srcRowStart = srcColor.data();
d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel
d->paramInfo.maskRowStart = maskIt->oldRawData();
d->paramInfo.maskRowStride = maskRowStride;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
dstX += columns;
columnsRemaining -= columns;
}
dstY += rows;
rowsRemaining -= rows;
}
}
else {
while(rowsRemaining > 0) {
qint32 dstX = x;
qint32 columnsRemaining = width;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY);
qint32 rows = qMin(numContiguousDstRows, rowsRemaining);
while(columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX);
qint32 columns = qMin(numContiguousDstColumns, columnsRemaining);
qint32 dstRowStride = dstIt->rowStride(dstX, dstY);
dstIt->moveTo(dstX, dstY);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
d->paramInfo.srcRowStart = srcColor.data();
d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
dstX += columns;
columnsRemaining -= columns;
}
dstY += rows;
rowsRemaining -= rows;
}
}
addDirtyRect(QRect(x, y, width, height));
}
void KisPainter::bltFixed(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
QRect srcBounds = srcDev->bounds();
/* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done,
so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */
KIS_SAFE_ASSERT_RECOVER_RETURN(srcBounds.contains(srcRect));
Q_UNUSED(srcRect); // only used in above assertion
/* Create an intermediate byte array to hold information before it is written
to the current paint device (aka: d->device) */
quint8* dstBytes = 0;
try {
dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
} catch (const std::bad_alloc&) {
warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes";
return;
}
d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
const quint8 *srcRowStart = srcDev->data() +
(srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize();
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcRowStart;
d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize();
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
if (d->selection) {
/* d->selection is a KisPaintDevice, so first a readBytes is performed to
get the area of interest... */
KisPaintDeviceSP selectionProjection(d->selection->projection());
quint8* selBytes = 0;
try {
selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()];
}
catch (const std::bad_alloc&) {
delete[] dstBytes;
return;
}
selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight);
d->paramInfo.maskRowStart = selBytes;
d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize();
}
// ...and then blit.
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
delete[] d->paramInfo.maskRowStart;
delete[] dstBytes;
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect)
{
bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height());
}
void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 selX, qint32 selY,
qint32 srcX, qint32 srcY,
quint32 srcWidth, quint32 srcHeight)
{
// TODO: get selX and selY working as intended
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
// Check that selection has an alpha colorspace, crash if false
Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8());
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
QRect selRect = QRect(selX, selY, srcWidth, srcHeight);
QRect srcBounds = srcDev->bounds();
QRect selBounds = selection->bounds();
/* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done,
so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */
Q_ASSERT(srcBounds.contains(srcRect));
Q_UNUSED(srcRect); // only used in above assertion
Q_ASSERT(selBounds.contains(selRect));
Q_UNUSED(selRect); // only used in above assertion
/* Create an intermediate byte array to hold information before it is written
to the current paint device (aka: d->device) */
quint8* dstBytes = 0;
try {
dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
} catch (const std::bad_alloc&) {
warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes";
return;
}
d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
const quint8 *srcRowStart = srcDev->data() +
(srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize();
const quint8 *selRowStart = selection->data() +
(selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize();
if (!d->selection) {
/* As there's nothing selected, blit to dstBytes (intermediary bit array),
ignoring d->selection (the user selection)*/
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcRowStart;
d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize();
d->paramInfo.maskRowStart = selRowStart;
d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
}
else {
/* Read the user selection (d->selection) bytes into an array, ready
to merge in the next block*/
quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize();
quint8 * mergedSelectionBytes = 0;
try {
mergedSelectionBytes = new quint8[ totalBytes ];
} catch (const std::bad_alloc&) {
warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes";
delete[] dstBytes;
return;
}
d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight);
// Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT)
d->paramInfo.dstRowStart = mergedSelectionBytes;
d->paramInfo.dstRowStride = srcWidth * selection->pixelSize();
d->paramInfo.srcRowStart = selRowStart;
d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo);
// Blit to dstBytes (intermediary bit array)
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcRowStart;
d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize();
d->paramInfo.maskRowStart = mergedSelectionBytes;
d->paramInfo.maskRowStride = srcWidth * selection->pixelSize();
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
delete[] mergedSelectionBytes;
}
d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
delete[] dstBytes;
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
quint32 srcWidth, quint32 srcHeight)
{
bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight);
}
void KisPainter::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
if (d->device && d->paintOp && d->paintOp->canPaint()) {
d->paintOp->paintLine(pi1, pi2, currentDistance);
}
}
void KisPainter::paintPolyline(const vQPointF &points,
int index, int numPoints)
{
if (d->fillStyle != FillStyleNone) {
fillPolygon(points, d->fillStyle);
}
if (d->strokeStyle == StrokeStyleNone) return;
if (index >= points.count())
return;
if (numPoints < 0)
numPoints = points.count();
if (index + numPoints > points.count())
numPoints = points.count() - index;
if (numPoints > 1) {
KisDistanceInformation saveDist(points[0],
KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0));
for (int i = index; i < index + numPoints - 1; i++) {
paintLine(points [i], points [i + 1], &saveDist);
}
}
}
static void getBezierCurvePoints(const KisVector2D &pos1,
const KisVector2D &control1,
const KisVector2D &control2,
const KisVector2D &pos2,
vQPointF& points)
{
LineEquation line = LineEquation::Through(pos1, pos2);
qreal d1 = line.absDistance(control1);
qreal d2 = line.absDistance(control2);
if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) {
points.push_back(toQPointF(pos1));
} else {
// Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508
KisVector2D l2 = (pos1 + control1) / 2;
KisVector2D h = (control1 + control2) / 2;
KisVector2D l3 = (l2 + h) / 2;
KisVector2D r3 = (control2 + pos2) / 2;
KisVector2D r2 = (h + r3) / 2;
KisVector2D l4 = (l3 + r2) / 2;
getBezierCurvePoints(pos1, l2, l3, l4, points);
getBezierCurvePoints(l4, r2, r3, pos2, points);
}
}
void KisPainter::getBezierCurvePoints(const QPointF &pos1,
const QPointF &control1,
const QPointF &control2,
const QPointF &pos2,
vQPointF& points) const
{
::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points);
}
void KisPainter::paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
if (d->paintOp && d->paintOp->canPaint()) {
d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance);
}
}
void KisPainter::paintRect(const QRectF &rect)
{
QRectF normalizedRect = rect.normalized();
vQPointF points;
points.push_back(normalizedRect.topLeft());
points.push_back(normalizedRect.bottomLeft());
points.push_back(normalizedRect.bottomRight());
points.push_back(normalizedRect.topRight());
paintPolygon(points);
}
void KisPainter::paintRect(const qreal x,
const qreal y,
const qreal w,
const qreal h)
{
paintRect(QRectF(x, y, w, h));
}
void KisPainter::paintEllipse(const QRectF &rect)
{
QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too
if (r.isEmpty()) return;
// See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation.
// kappa = (4/3*(sqrt(2)-1))
const qreal kappa = 0.5522847498;
const qreal lx = (r.width() / 2) * kappa;
const qreal ly = (r.height() / 2) * kappa;
QPointF center = r.center();
QPointF p0(r.left(), center.y());
QPointF p1(r.left(), center.y() - ly);
QPointF p2(center.x() - lx, r.top());
QPointF p3(center.x(), r.top());
vQPointF points;
getBezierCurvePoints(p0, p1, p2, p3, points);
QPointF p4(center.x() + lx, r.top());
QPointF p5(r.right(), center.y() - ly);
QPointF p6(r.right(), center.y());
getBezierCurvePoints(p3, p4, p5, p6, points);
QPointF p7(r.right(), center.y() + ly);
QPointF p8(center.x() + lx, r.bottom());
QPointF p9(center.x(), r.bottom());
getBezierCurvePoints(p6, p7, p8, p9, points);
QPointF p10(center.x() - lx, r.bottom());
QPointF p11(r.left(), center.y() + ly);
getBezierCurvePoints(p9, p10, p11, p0, points);
paintPolygon(points);
}
void KisPainter::paintEllipse(const qreal x,
const qreal y,
const qreal w,
const qreal h)
{
paintEllipse(QRectF(x, y, w, h));
}
void KisPainter::paintAt(const KisPaintInformation& pi,
KisDistanceInformation *savedDist)
{
if (d->paintOp && d->paintOp->canPaint()) {
d->paintOp->paintAt(pi, savedDist);
}
}
void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle)
{
if (points.count() < 3) {
return;
}
if (fillStyle == FillStyleNone) {
return;
}
QPainterPath polygonPath;
polygonPath.moveTo(points.at(0));
for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) {
polygonPath.lineTo(points.at(pointIndex));
}
polygonPath.closeSubpath();
d->fillStyle = fillStyle;
fillPainterPath(polygonPath);
}
void KisPainter::paintPolygon(const vQPointF& points)
{
if (d->fillStyle != FillStyleNone) {
fillPolygon(points, d->fillStyle);
}
if (d->strokeStyle != StrokeStyleNone) {
if (points.count() > 1) {
KisDistanceInformation distance(points[0],
KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0));
for (int i = 0; i < points.count() - 1; i++) {
paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance);
}
paintLine(points[points.count() - 1], points[0], &distance);
}
}
}
void KisPainter::paintPainterPath(const QPainterPath& path)
{
if (d->fillStyle != FillStyleNone) {
fillPainterPath(path);
}
if (d->strokeStyle == StrokeStyleNone) return;
QPointF lastPoint, nextPoint;
int elementCount = path.elementCount();
KisDistanceInformation saveDist;
for (int i = 0; i < elementCount; i++) {
QPainterPath::Element element = path.elementAt(i);
switch (element.type) {
case QPainterPath::MoveToElement:
lastPoint = QPointF(element.x, element.y);
break;
case QPainterPath::LineToElement:
nextPoint = QPointF(element.x, element.y);
paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist);
lastPoint = nextPoint;
break;
case QPainterPath::CurveToElement:
nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y);
paintBezierCurve(KisPaintInformation(lastPoint),
QPointF(path.elementAt(i).x, path.elementAt(i).y),
QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y),
KisPaintInformation(nextPoint), &saveDist);
lastPoint = nextPoint;
break;
default:
continue;
}
}
}
void KisPainter::fillPainterPath(const QPainterPath& path)
{
fillPainterPath(path, QRect());
}
void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect)
{
if (d->mirrorHorizontally || d->mirrorVertically) {
KisLodTransform lod(d->device);
QPointF effectiveAxesCenter = lod.map(d->axesCenter);
QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y());
QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y());
QTransform t;
QPainterPath newPath;
QRect newRect;
if (d->mirrorHorizontally) {
t = C1 * QTransform::fromScale(-1,1) * C2;
newPath = t.map(path);
newRect = t.mapRect(requestedRect);
d->fillPainterPathImpl(newPath, newRect);
}
if (d->mirrorVertically) {
t = C1 * QTransform::fromScale(1,-1) * C2;
newPath = t.map(path);
newRect = t.mapRect(requestedRect);
d->fillPainterPathImpl(newPath, newRect);
}
if (d->mirrorHorizontally && d->mirrorVertically) {
t = C1 * QTransform::fromScale(-1,-1) * C2;
newPath = t.map(path);
newRect = t.mapRect(requestedRect);
d->fillPainterPathImpl(newPath, newRect);
}
}
d->fillPainterPathImpl(path, requestedRect);
}
void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect)
{
if (fillStyle == FillStyleNone) {
return;
}
// Fill the polygon bounding rectangle with the required contents then we'll
// create a mask for the actual polygon coverage.
if (!fillPainter) {
polygon = device->createCompositionSourceDevice();
fillPainter = new KisFillPainter(polygon);
} else {
polygon->clear();
}
Q_CHECK_PTR(polygon);
QRectF boundingRect = path.boundingRect();
QRect fillRect = boundingRect.toAlignedRect();
// Expand the rectangle to allow for anti-aliasing.
fillRect.adjust(-1, -1, 1, 1);
if (requestedRect.isValid()) {
fillRect &= requestedRect;
}
switch (fillStyle) {
default:
Q_FALLTHROUGH();
case FillStyleGradient:
// Currently unsupported
Q_FALLTHROUGH();
case FillStyleStrokes:
// Currently unsupported
warnImage << "Unknown or unsupported fill style in fillPolygon\n";
Q_FALLTHROUGH();
case FillStyleForegroundColor:
fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8);
break;
case FillStyleBackgroundColor:
fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8);
break;
case FillStylePattern:
if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash...
fillPainter->fillRect(fillRect, pattern);
}
break;
case FillStyleGenerator:
if (generator) { // if the user hasn't got any generators, we shouldn't crash...
fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator());
}
break;
}
if (polygonMaskImage.isNull() || (maskPainter == 0)) {
polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied);
maskPainter = new QPainter(&polygonMaskImage);
maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill());
}
// Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
const QColor black(Qt::black);
const QBrush brush(Qt::white);
for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) {
for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) {
polygonMaskImage.fill(black.rgb());
maskPainter->translate(-x, -y);
maskPainter->fillPath(path, brush);
maskPainter->translate(x, y);
qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth);
qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight);
KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth);
quint8 tmp;
for (int row = y; row < y + rectHeight; row++) {
QRgb* line = reinterpret_cast<QRgb*>(polygonMaskImage.scanLine(row - y));
do {
tmp = qRed(line[lineIt->x() - x]);
polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1);
} while (lineIt->nextPixel());
lineIt->nextRow();
}
}
}
QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect;
q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height());
}
void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen)
{
drawPainterPath(path, pen, QRect());
}
-void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect)
+void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& _pen, const QRect &requestedRect)
{
// we are drawing mask, it has to be white
// color of the path is given by paintColor()
- Q_ASSERT(pen.color() == Qt::white);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(_pen.color() == Qt::white);
+ QPen pen(_pen);
+ pen.setColor(Qt::white);
if (!d->fillPainter) {
d->polygon = d->device->createCompositionSourceDevice();
d->fillPainter = new KisFillPainter(d->polygon);
} else {
d->polygon->clear();
}
Q_CHECK_PTR(d->polygon);
QRectF boundingRect = path.boundingRect();
QRect fillRect = boundingRect.toAlignedRect();
// take width of the pen into account
int penWidth = qRound(pen.widthF());
fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth);
// Expand the rectangle to allow for anti-aliasing.
fillRect.adjust(-1, -1, 1, 1);
if (!requestedRect.isNull()) {
fillRect &= requestedRect;
}
d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8);
if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) {
d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied);
d->maskPainter = new QPainter(&d->polygonMaskImage);
d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill());
}
// Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
const QColor black(Qt::black);
QPen oldPen = d->maskPainter->pen();
d->maskPainter->setPen(pen);
for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) {
for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) {
d->polygonMaskImage.fill(black.rgb());
d->maskPainter->translate(-x, -y);
d->maskPainter->drawPath(path);
d->maskPainter->translate(x, y);
qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth);
qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight);
KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth);
quint8 tmp;
for (int row = y; row < y + rectHeight; row++) {
QRgb* line = reinterpret_cast<QRgb*>(d->polygonMaskImage.scanLine(row - y));
do {
tmp = qRed(line[lineIt->x() - x]);
d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1);
} while (lineIt->nextPixel());
lineIt->nextRow();
}
}
}
d->maskPainter->setPen(oldPen);
QRect r = d->polygon->extent();
bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height());
}
inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color)
{
d->paramInfo.dstRowStart = dst;
d->paramInfo.dstRowStride = 0;
d->paramInfo.srcRowStart = color.data();
d->paramInfo.srcRowStride = 0;
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = 1;
d->paramInfo.cols = 1;
d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp,
d->renderingIntent,
d->conversionFlags);
}
/**/
void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){
int x1 = qFloor(start.x());
int y1 = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(end.y());
if ((x2 == x1 ) && (y2 == y1)) return;
int dstX = x2-x1;
int dstY = y2-y1;
qreal uniC = dstX*y1 - dstY*x1;
qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2));
qreal subPixel;
if (qAbs(dstX) > qAbs(dstY)){
subPixel = start.x() - x1;
}else{
subPixel = start.y() - y1;
}
qreal halfWidth = width * 0.5 + subPixel;
int W_ = qRound(halfWidth) + 1;
// save the state
int X1_ = x1;
int Y1_ = y1;
int X2_ = x2;
int Y2_ = y2;
if (x2<x1) std::swap(x1,x2);
if (y2<y1) std::swap(y1,y2);
qreal denominator = sqrt(pow((double)dstY,2) + pow((double)dstX,2));
if (denominator == 0.0) {
denominator = 1.0;
}
denominator = 1.0/denominator;
qreal projection,scanX,scanY,AA_;
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1);
}
for (int y = y1-W_; y < y2+W_ ; y++){
for (int x = x1-W_; x < x2+W_; x++){
projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator;
scanX = X1_ + projection * dstX;
scanY = Y1_ + projection * dstY;
if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) {
AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ),
sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) ));
}else{
AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator;
}
if (AA_>halfWidth) {
continue;
}
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x,y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
KoColor mycolor = d->paintColor;
if (antialias && AA_ > halfWidth-1.0) {
mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1);
}
compositeOnePixel(accessor->rawData(), mycolor);
}
}
}
}
/**/
void KisPainter::drawLine(const QPointF & start, const QPointF & end)
{
drawThickLine(start, end, 1, 1);
}
void KisPainter::drawDDALine(const QPointF & start, const QPointF & end)
{
int x = qFloor(start.x());
int y = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(end.y());
// Width and height of the line
int xd = x2 - x;
int yd = y2 - y;
float m = (float)yd / (float)xd;
float fx = x;
float fy = y;
int inc;
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y);
}
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x,y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), d->paintColor);
}
if (fabs(m) > 1.0f) {
inc = (yd > 0) ? 1 : -1;
m = 1.0f / m;
m *= inc;
while (y != y2) {
y = y + inc;
fx = fx + m;
x = qRound(fx);
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), d->paintColor);
}
}
} else {
inc = (xd > 0) ? 1 : -1;
m *= inc;
while (x != x2) {
x = x + inc;
fy = fy + m;
y = qRound(fy);
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), d->paintColor);
}
}
}
}
void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end)
{
KoColor mycolor(d->paintColor);
int x1 = qFloor(start.x());
int y1 = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(end.y());
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1);
}
// Width and height of the line
int xd = (x2 - x1);
int yd = (y2 - y1);
int x;
int y;
float fx = (x = x1);
float fy = (y = y1);
float m = (float)yd / (float)xd;
int inc;
if (fabs(m) > 1) {
inc = (yd > 0) ? 1 : -1;
m = 1.0f / m;
m *= inc;
while (y != y2) {
fx = fx + m;
y = y + inc;
x = qRound(fx);
float br1 = qFloor(fx + 1) - fx;
float br2 = fx - qFloor(fx);
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br1));
compositeOnePixel(accessor->rawData(), mycolor);
}
accessor->moveTo(x + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(x + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br2));
compositeOnePixel(accessor->rawData(), mycolor);
}
}
} else {
inc = (xd > 0) ? 1 : -1;
m *= inc;
while (x != x2) {
fy = fy + m;
x = x + inc;
y = qRound(fy);
float br1 = qFloor(fy + 1) - fy;
float br2 = fy - qFloor(fy);
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br1));
compositeOnePixel(accessor->rawData(), mycolor);
}
accessor->moveTo(x, y + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, y + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br2));
compositeOnePixel(accessor->rawData(), mycolor);
}
}
}
}
void KisPainter::drawWuLine(const QPointF & start, const QPointF & end)
{
KoColor lineColor(d->paintColor);
int x1 = qFloor(start.x());
int y1 = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(end.y());
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1);
}
float grad, xd, yd;
float xgap, ygap, xend, yend, yf, xf;
float brightness1, brightness2;
int ix1, ix2, iy1, iy2;
quint8 c1, c2;
// gradient of line
xd = (x2 - x1);
yd = (y2 - y1);
if (yd == 0) {
/* Horizontal line */
int incr = (x1 < x2) ? 1 : -1;
ix1 = x1;
ix2 = x2;
iy1 = y1;
while (ix1 != ix2) {
ix1 = ix1 + incr;
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), lineColor);
}
}
return;
}
if (xd == 0) {
/* Vertical line */
int incr = (y1 < y2) ? 1 : -1;
iy1 = y1;
iy2 = y2;
ix1 = x1;
while (iy1 != iy2) {
iy1 = iy1 + incr;
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), lineColor);
}
}
return;
}
if (fabs(xd) > fabs(yd)) {
// horizontal line
// line have to be paint from left to right
if (x1 > x2) {
std::swap(x1, x2);
std::swap(y1, y2);
xd = (x2 - x1);
yd = (y2 - y1);
}
grad = yd / xd;
// nearest X,Y integer coordinates
xend = x1;
yend = y1 + grad * (xend - x1);
xgap = invertFrac(x1 + 0.5f);
ix1 = x1;
iy1 = qFloor(yend);
// calc the intensity of the other end point pixel pair.
brightness1 = invertFrac(yend) * xgap;
brightness2 = frac(yend) * xgap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(ix1, iy1 + 1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// calc first Y-intersection for main loop
yf = yend + grad;
xend = x2;
yend = y2 + grad * (xend - x2);
xgap = invertFrac(x2 - 0.5f);
ix2 = x2;
iy2 = qFloor(yend);
brightness1 = invertFrac(yend) * xgap;
brightness2 = frac(yend) * xgap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix2, iy2);
if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(ix2, iy2 + 1);
if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// main loop
for (int x = ix1 + 1; x <= ix2 - 1; x++) {
brightness1 = invertFrac(yf);
brightness2 = frac(yf);
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(x, qFloor(yf));
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf));
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(x, qFloor(yf) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf) + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
yf = yf + grad;
}
} else {
//vertical
// line have to be painted from left to right
if (y1 > y2) {
std::swap(x1, x2);
std::swap(y1, y2);
xd = (x2 - x1);
yd = (y2 - y1);
}
grad = xd / yd;
// nearest X,Y integer coordinates
yend = y1;
xend = x1 + grad * (yend - y1);
ygap = y1;
ix1 = qFloor(xend);
iy1 = y1;
// calc the intensity of the other end point pixel pair.
brightness1 = invertFrac(xend) * ygap;
brightness2 = frac(xend) * ygap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(x1 + 1, y1);
if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// calc first Y-intersection for main loop
xf = xend + grad;
yend = y2;
xend = x2 + grad * (yend - y2);
ygap = invertFrac(y2 - 0.5f);
ix2 = qFloor(xend);
iy2 = y2;
brightness1 = invertFrac(xend) * ygap;
brightness2 = frac(xend) * ygap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix2, iy2);
if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(ix2 + 1, iy2);
if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// main loop
for (int y = iy1 + 1; y <= iy2 - 1; y++) {
brightness1 = invertFrac(xf);
brightness2 = frac(xf);
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(qFloor(xf), y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf), y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(qFloor(xf) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf) + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
xf = xf + grad;
}
}//end-of-else
}
void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth)
{
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y());
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y());
}
const KoColorSpace *cs = d->device->colorSpace();
KoColor c1(d->paintColor);
KoColor c2(d->paintColor);
KoColor c3(d->paintColor);
KoColor col1(c1);
KoColor col2(c1);
float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb,
xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY;
int x, y, ix1, ix2, iy1, iy2;
int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b;
int tp0, tn0, tp1, tn1;
int horizontal = 0;
float opacity = 1.0;
tp0 = startWidth / 2;
tn0 = startWidth / 2;
if (startWidth % 2 == 0) // even width startWidth
tn0--;
tp1 = endWidth / 2;
tn1 = endWidth / 2;
if (endWidth % 2 == 0) // even width endWidth
tn1--;
int x0 = qRound(start.x());
int y0 = qRound(start.y());
int x1 = qRound(end.x());
int y1 = qRound(end.y());
dstX = x1 - x0; // run of general line
dstY = y1 - y0; // rise of general line
if (dstY < 0) dstY = -dstY;
if (dstX < 0) dstX = -dstX;
if (dstX > dstY) { // horizontalish
horizontal = 1;
x0a = x0; y0a = y0 - tn0;
x0b = x0; y0b = y0 + tp0;
x1a = x1; y1a = y1 - tn1;
x1b = x1; y1b = y1 + tp1;
} else {
x0a = x0 - tn0; y0a = y0;
x0b = x0 + tp0; y0b = y0;
x1a = x1 - tn1; y1a = y1;
x1b = x1 + tp1; y1b = y1;
}
if (horizontal) { // draw endpoints
for (int i = y0a; i <= y0b; i++) {
accessor->moveTo(x0, i);
if (selectionAccessor) selectionAccessor->moveTo(x0, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
for (int i = y1a; i <= y1b; i++) {
accessor->moveTo(x1, i);
if (selectionAccessor) selectionAccessor->moveTo(x1, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
} else {
for (int i = x0a; i <= x0b; i++) {
accessor->moveTo(i, y0);
if (selectionAccessor) selectionAccessor->moveTo(i, y0);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
for (int i = x1a; i <= x1b; i++) {
accessor->moveTo(i, y1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
}
//antialias endpoints
if (x1 != x0 && y1 != y0) {
if (horizontal) {
accessor->moveTo(x0a, y0a - 1);
if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c1.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
accessor->moveTo(x1b, y1b + 1);
if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c2.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
} else {
accessor->moveTo(x0a - 1, y0a);
if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c1.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
accessor->moveTo(x1b + 1, y1b);
if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c2.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
}
}
dxa = x1a - x0a; // run of a
dya = y1a - y0a; // rise of a
dxb = x1b - x0b; // run of b
dyb = y1b - y0b; // rise of b
if (horizontal) { // horizontal-ish lines
if (x1 < x0) {
int xt, yt, wt;
KoColor tmp;
xt = x1a; x1a = x0a; x0a = xt;
yt = y1a; y1a = y0a; y0a = yt;
xt = x1b; x1b = x0b; x0b = xt;
yt = y1b; y1b = y0b; y0b = yt;
xt = x1; x1 = x0; x0 = xt;
yt = y1; y1 = y0; y0 = yt;
tmp = c1; c1 = c2; c2 = tmp;
wt = startWidth; startWidth = endWidth; endWidth = wt;
}
grada = dya / dxa;
gradb = dyb / dxb;
ix1 = x0; iy1 = y0;
ix2 = x1; iy2 = y1;
yfa = y0a + grada;
yfb = y0b + gradb;
for (x = ix1 + 1; x <= ix2 - 1; x++) {
fraca = yfa - qFloor(yfa);
b1a = 1 - fraca;
b2a = fraca;
fracb = yfb - qFloor(yfb);
b1b = 1 - fracb;
b2b = fracb;
// color first pixel of bottom line
opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF();
c3.setOpacity(opacity);
accessor->moveTo(x, qFloor(yfa));
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa));
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1a * c3.opacityF() + (1 - b1a) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
// color first pixel of top line
if (!(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(x, qFloor(yfb));
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb));
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1b * c3.opacityF() + (1 - b1b) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
}
// color second pixel of bottom line
if (grada != 0 && grada != 1) { // if not flat or exact diagonal
accessor->moveTo(x, qFloor(yfa) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfa) + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2a * c3.opacityF() + (1 - b2a) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// color second pixel of top line
if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(x, qFloor(yfb) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yfb) + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2b * c3.opacityF() + (1 - b2b) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// fill remaining pixels
if (!(startWidth == 1 && endWidth == 1)) {
if (yfa < yfb)
for (int i = qFloor(yfa) + 1; i <= qFloor(yfb); i++) {
accessor->moveTo(x, i);
if (selectionAccessor) selectionAccessor->moveTo(x, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
else
for (int i = qFloor(yfa) + 1; i >= qFloor(yfb); i--) {
accessor->moveTo(x, i);
if (selectionAccessor) selectionAccessor->moveTo(x, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
}
yfa += grada;
yfb += gradb;
}
} else { // vertical-ish lines
if (y1 < y0) {
int xt, yt, wt;
xt = x1a; x1a = x0a; x0a = xt;
yt = y1a; y1a = y0a; y0a = yt;
xt = x1b; x1b = x0b; x0b = xt;
yt = y1b; y1b = y0b; y0b = yt;
xt = x1; x1 = x0; x0 = xt;
yt = y1; y1 = y0; y0 = yt;
KoColor tmp;
tmp = c1; c1 = c2; c2 = tmp;
wt = startWidth; startWidth = endWidth; endWidth = wt;
}
grada = dxa / dya;
gradb = dxb / dyb;
ix1 = x0; iy1 = y0;
ix2 = x1; iy2 = y1;
xfa = x0a + grada;
xfb = x0b + gradb;
for (y = iy1 + 1; y <= iy2 - 1; y++) {
fraca = xfa - qFloor(xfa);
b1a = 1 - fraca;
b2a = fraca;
fracb = xfb - qFloor(xfb);
b1b = 1 - fracb;
b2b = fracb;
// color first pixel of left line
opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF();
c3.setOpacity(opacity);
accessor->moveTo(qFloor(xfa), y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa), y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1a * c3.opacityF() + (1 - b1a) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
// color first pixel of right line
if (!(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(qFloor(xfb), y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb), y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1b * c3.opacityF() + (1 - b1b) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
}
// color second pixel of left line
if (grada != 0 && grada != 1) { // if not flat or exact diagonal
accessor->moveTo(qFloor(xfa) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfa) + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2a * c3.opacityF() + (1 - b2a) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// color second pixel of right line
if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(qFloor(xfb) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(xfb) + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2b * c3.opacityF() + (1 - b2b) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// fill remaining pixels between current xfa,xfb
if (!(startWidth == 1 && endWidth == 1)) {
if (xfa < xfb)
for (int i = qFloor(xfa) + 1; i <= qFloor(xfb); i++) {
accessor->moveTo(i, y);
if (selectionAccessor) selectionAccessor->moveTo(i, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
else
for (int i = qFloor(xfb); i <= qFloor(xfa) + 1; i++) {
accessor->moveTo(i, y);
if (selectionAccessor) selectionAccessor->moveTo(i, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
}
xfa += grada;
xfb += gradb;
}
}
}
void KisPainter::setProgress(KoUpdater * progressUpdater)
{
d->progressUpdater = progressUpdater;
}
const KisPaintDeviceSP KisPainter::device() const
{
return d->device;
}
KisPaintDeviceSP KisPainter::device()
{
return d->device;
}
void KisPainter::setChannelFlags(QBitArray channelFlags)
{
// Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount());
// Now, if all bits in the channelflags are true, pass an empty channel flags bitarray
// because otherwise the compositeops cannot optimize.
d->paramInfo.channelFlags = channelFlags;
if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) {
d->paramInfo.channelFlags = QBitArray();
}
}
QBitArray KisPainter::channelFlags()
{
return d->paramInfo.channelFlags;
}
void KisPainter::setPattern(const KoPattern * pattern)
{
d->pattern = pattern;
}
const KoPattern * KisPainter::pattern() const
{
return d->pattern;
}
void KisPainter::setPaintColor(const KoColor& color)
{
d->paintColor = color;
if (d->device) {
d->paintColor.convertTo(d->device->compositionSourceColorSpace());
}
}
const KoColor &KisPainter::paintColor() const
{
return d->paintColor;
}
void KisPainter::setBackgroundColor(const KoColor& color)
{
d->backgroundColor = color;
if (d->device) {
d->backgroundColor.convertTo(d->device->compositionSourceColorSpace());
}
}
const KoColor &KisPainter::backgroundColor() const
{
return d->backgroundColor;
}
void KisPainter::setGenerator(KisFilterConfigurationSP generator)
{
d->generator = generator;
}
const KisFilterConfigurationSP KisPainter::generator() const
{
return d->generator;
}
void KisPainter::setFillStyle(FillStyle fillStyle)
{
d->fillStyle = fillStyle;
}
KisPainter::FillStyle KisPainter::fillStyle() const
{
return d->fillStyle;
}
void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill)
{
d->antiAliasPolygonFill = antiAliasPolygonFill;
}
bool KisPainter::antiAliasPolygonFill()
{
return d->antiAliasPolygonFill;
}
void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle)
{
d->strokeStyle = strokeStyle;
}
KisPainter::StrokeStyle KisPainter::strokeStyle() const
{
return d->strokeStyle;
}
void KisPainter::setFlow(quint8 flow)
{
d->paramInfo.flow = float(flow) / 255.0f;
}
quint8 KisPainter::flow() const
{
return quint8(d->paramInfo.flow * 255.0f);
}
void KisPainter::setOpacityUpdateAverage(quint8 opacity)
{
d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8;
d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f);
}
void KisPainter::setAverageOpacity(qreal averageOpacity)
{
d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity);
}
qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity)
{
const float exponent = 0.1;
return averageOpacity < opacity ?
opacity :
exponent * opacity + (1.0 - exponent) * (averageOpacity);
}
void KisPainter::setOpacity(quint8 opacity)
{
d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8;
d->paramInfo.opacity = float(opacity) / 255.0f;
}
quint8 KisPainter::opacity() const
{
return quint8(d->paramInfo.opacity * 255.0f);
}
void KisPainter::setCompositeOp(const KoCompositeOp * op)
{
d->compositeOp = op;
}
const KoCompositeOp * KisPainter::compositeOp()
{
return d->compositeOp;
}
/**
* TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h
*/
void KisPainter::setCompositeOp(const QString& op)
{
d->compositeOp = d->colorSpace->compositeOp(op);
}
void KisPainter::setSelection(KisSelectionSP selection)
{
d->selection = selection;
}
KisSelectionSP KisPainter::selection()
{
return d->selection;
}
KoUpdater * KisPainter::progressUpdater()
{
return d->progressUpdater;
}
void KisPainter::setGradient(const KoAbstractGradient* gradient)
{
d->gradient = gradient;
}
const KoAbstractGradient* KisPainter::gradient() const
{
return d->gradient;
}
void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image)
{
d->paintOpPreset = preset;
KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image);
Q_ASSERT(paintop);
if (paintop) {
delete d->paintOp;
d->paintOp = paintop;
}
else {
warnKrita << "Could not create paintop for preset " << preset->name();
}
}
KisPaintOpPresetSP KisPainter::preset() const
{
return d->paintOpPreset;
}
KisPaintOp* KisPainter::paintOp() const
{
return d->paintOp;
}
void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically)
{
d->axesCenter = axesCenter;
d->mirrorHorizontally = mirrorHorizontally;
d->mirrorVertically = mirrorVertically;
}
void KisPainter::copyMirrorInformationFrom(const KisPainter *other)
{
d->axesCenter = other->d->axesCenter;
d->mirrorHorizontally = other->d->mirrorHorizontally;
d->mirrorVertically = other->d->mirrorVertically;
}
bool KisPainter::hasMirroring() const
{
return d->mirrorHorizontally || d->mirrorVertically;
}
bool KisPainter::hasHorizontalMirroring() const
{
return d->mirrorHorizontally;
}
bool KisPainter::hasVerticalMirroring() const
{
return d->mirrorVertically;
}
void KisPainter::setMaskImageSize(qint32 width, qint32 height)
{
d->maskImageWidth = qBound(1, width, 256);
d->maskImageHeight = qBound(1, height, 256);
d->fillPainter = 0;
d->polygonMaskImage = QImage();
}
//void KisPainter::setLockAlpha(bool protect)
//{
// if(d->paramInfo.channelFlags.isEmpty()) {
// d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true);
// }
// QBitArray switcher =
// d->colorSpace->channelFlags(protect, !protect);
// if(protect) {
// d->paramInfo.channelFlags &= switcher;
// }
// else {
// d->paramInfo.channelFlags |= switcher;
// }
// Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount());
//}
//bool KisPainter::alphaLocked() const
//{
// QBitArray switcher = d->colorSpace->channelFlags(false, true);
// return !(d->paramInfo.channelFlags & switcher).count(true);
//}
void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent)
{
d->renderingIntent = intent;
}
void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags)
{
d->conversionFlags = conversionFlags;
}
void KisPainter::setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface)
{
d->runnableStrokeJobsInterface = interface;
}
KisRunnableStrokeJobsInterface *KisPainter::runnableStrokeJobsInterface() const
{
if (!d->runnableStrokeJobsInterface) {
if (!d->fakeRunnableStrokeJobsInterface) {
d->fakeRunnableStrokeJobsInterface.reset(new KisFakeRunnableStrokeJobsExecutor());
}
return d->fakeRunnableStrokeJobsInterface.data();
}
return d->runnableStrokeJobsInterface;
}
void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab)
{
if (!d->mirrorHorizontally && !d->mirrorVertically) return;
KisFixedPaintDeviceSP dabToProcess = dab;
if (preserveDab) {
dabToProcess = new KisFixedPaintDevice(*dab);
}
renderMirrorMask(rc, dabToProcess);
}
void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask)
{
if (!d->mirrorHorizontally && !d->mirrorVertically) return;
KisFixedPaintDeviceSP maskToProcess = mask;
if (preserveMask) {
maskToProcess = new KisFixedPaintDevice(*mask);
}
renderMirrorMask(rc, dab, sx, sy, maskToProcess);
}
void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab)
{
int x = rc.topLeft().x();
int y = rc.topLeft().y();
KisLodTransform t(d->device);
QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x();
int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y();
if (d->mirrorHorizontally && d->mirrorVertically){
dab->mirror(true, false);
bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height());
dab->mirror(false,true);
bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height());
dab->mirror(true, false);
bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height());
}
else if (d->mirrorHorizontally){
dab->mirror(true, false);
bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height());
}
else if (d->mirrorVertically){
dab->mirror(false, true);
bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height());
}
}
void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask)
{
int x = rc.topLeft().x();
int y = rc.topLeft().y();
KisLodTransform t(d->device);
QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x();
int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y();
if (d->mirrorHorizontally && d->mirrorVertically){
dab->mirror(true, false);
mask->mirror(true, false);
bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() );
dab->mirror(false,true);
mask->mirror(false, true);
bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() );
dab->mirror(true, false);
mask->mirror(true, false);
bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() );
}else if (d->mirrorHorizontally){
dab->mirror(true, false);
mask->mirror(true, false);
bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() );
}else if (d->mirrorVertically){
dab->mirror(false, true);
mask->mirror(false, true);
bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() );
}
}
void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){
if (d->mirrorHorizontally || d->mirrorVertically){
KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace()));
QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) );
mirrorDab->setRect(dabRc);
mirrorDab->lazyGrowBufferWithoutInitialization();
dab->readBytes(mirrorDab->data(),rc);
renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab);
}
}
void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask)
{
if (d->mirrorHorizontally || d->mirrorVertically){
KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace()));
QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) );
mirrorDab->setRect(dabRc);
mirrorDab->lazyGrowBufferWithoutInitialization();
dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size()));
renderMirrorMask(rc, mirrorDab, mask);
}
}
void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab)
{
QVector<QRect> rects;
int x = rc.topLeft().x();
int y = rc.topLeft().y();
KisLodTransform t(d->device);
QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x();
int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y();
rects << rc;
if (d->mirrorHorizontally && d->mirrorVertically){
rects << QRect(mirrorX, y, rc.width(), rc.height());
rects << QRect(mirrorX, mirrorY, rc.width(), rc.height());
rects << QRect(x, mirrorY, rc.width(), rc.height());
} else if (d->mirrorHorizontally) {
rects << QRect(mirrorX, y, rc.width(), rc.height());
} else if (d->mirrorVertically) {
rects << QRect(x, mirrorY, rc.width(), rc.height());
}
Q_FOREACH (const QRect &rc, rects) {
d->device->clear(rc);
}
QRect resultRect = dab->extent() | rc;
bool intersects = false;
for (int i = 1; i < rects.size(); i++) {
if (rects[i].intersects(resultRect)) {
intersects = true;
break;
}
}
/**
* If there are no cross-intersections, we can use a fast path
* and do no cycling recompositioning
*/
if (!intersects) {
rects.resize(1);
}
Q_FOREACH (const QRect &rc, rects) {
bitBlt(rc.topLeft(), dab, rc);
}
Q_FOREACH (const QRect &rc, rects) {
renderMirrorMask(rc, dab);
}
}
bool KisPainter::hasDirtyRegion() const
{
return !d->dirtyRects.isEmpty();
}
void KisPainter::mirrorRect(Qt::Orientation direction, QRect *rc) const
{
KisLodTransform t(d->device);
QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
KritaUtils::mirrorRect(direction, effectiveAxesCenter, rc);
}
void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const
{
KisLodTransform t(d->device);
QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab);
}
namespace {
inline void mirrorOneObject(Qt::Orientation dir, const QPoint &center, QRect *rc) {
KritaUtils::mirrorRect(dir, center, rc);
}
inline void mirrorOneObject(Qt::Orientation dir, const QPoint &center, QPointF *pt) {
KritaUtils::mirrorPoint(dir, center, pt);
}
inline void mirrorOneObject(Qt::Orientation dir, const QPoint &center, QPair<QPointF, QPointF> *pair) {
KritaUtils::mirrorPoint(dir, center, &pair->first);
KritaUtils::mirrorPoint(dir, center, &pair->second);
}
}
template<class T> QVector<T> KisPainter::Private::calculateMirroredObjects(const T &object)
{
QVector<T> result;
KisLodTransform t(this->device);
const QPoint effectiveAxesCenter = t.map(this->axesCenter).toPoint();
T baseObject = object;
result << baseObject;
if (this->mirrorHorizontally && this->mirrorVertically){
mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject);
result << baseObject;
mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject);
result << baseObject;
mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject);
result << baseObject;
} else if (this->mirrorHorizontally) {
mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject);
result << baseObject;
} else if (this->mirrorVertically) {
mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject);
result << baseObject;
}
return result;
}
const QVector<QRect> KisPainter::calculateAllMirroredRects(const QRect &rc)
{
return d->calculateMirroredObjects(rc);
}
const QVector<QPointF> KisPainter::calculateAllMirroredPoints(const QPointF &pos)
{
return d->calculateMirroredObjects(pos);
}
const QVector<QPair<QPointF, QPointF>> KisPainter::calculateAllMirroredPoints(const QPair<QPointF, QPointF> &pair)
{
return d->calculateMirroredObjects(pair);
}
diff --git a/libs/image/kis_perspectivetransform_worker.cpp b/libs/image/kis_perspectivetransform_worker.cpp
index 514e0ecac5..adfa76ed03 100644
--- a/libs/image/kis_perspectivetransform_worker.cpp
+++ b/libs/image/kis_perspectivetransform_worker.cpp
@@ -1,190 +1,192 @@
/*
* This file is part of Krita
*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2009 Edward Apap <schumifer@hotmail.com>
* Copyright (c) 2010 Marc Pegon <pe.marc@free.fr>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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_perspectivetransform_worker.h"
#include <QMatrix4x4>
#include <QTransform>
#include <QVector3D>
#include <QPolygonF>
#include <KoUpdater.h>
#include <KoColor.h>
#include <KoCompositeOpRegistry.h>
#include "kis_paint_device.h"
#include "kis_perspective_math.h"
#include "kis_random_accessor_ng.h"
#include "kis_random_sub_accessor.h"
#include "kis_selection.h"
#include <kis_iterator_ng.h>
#include "krita_utils.h"
#include "kis_progress_update_helper.h"
#include "kis_painter.h"
#include "kis_image.h"
KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, QPointF center, double aX, double aY, double distance, KoUpdaterPtr progress)
: m_dev(dev), m_progressUpdater(progress)
{
QMatrix4x4 m;
m.rotate(180. * aX / M_PI, QVector3D(1, 0, 0));
m.rotate(180. * aY / M_PI, QVector3D(0, 1, 0));
QTransform project = m.toTransform(distance);
QTransform t = QTransform::fromTranslate(center.x(), center.y());
QTransform forwardTransform = t.inverted() * project * t;
init(forwardTransform);
}
KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const QTransform &transform, KoUpdaterPtr progress)
: m_dev(dev), m_progressUpdater(progress)
{
init(transform);
}
void KisPerspectiveTransformWorker::fillParams(const QRectF &srcRect,
const QRect &dstBaseClipRect,
QRegion *dstRegion,
QPolygonF *dstClipPolygon)
{
QPolygonF bounds = srcRect;
QPolygonF newBounds = m_forwardTransform.map(bounds);
newBounds = newBounds.intersected(QRectF(dstBaseClipRect));
QPainterPath path;
path.addPolygon(newBounds);
*dstRegion = KritaUtils::splitPath(path);
*dstClipPolygon = newBounds;
}
void KisPerspectiveTransformWorker::init(const QTransform &transform)
{
m_isIdentity = transform.isIdentity();
m_forwardTransform = transform;
m_backwardTransform = transform.inverted();
if (m_dev) {
m_srcRect = m_dev->exactBounds();
QPolygonF dstClipPolygonUnused;
fillParams(m_srcRect,
m_dev->defaultBounds()->bounds(),
&m_dstRegion,
&dstClipPolygonUnused);
}
}
KisPerspectiveTransformWorker::~KisPerspectiveTransformWorker()
{
}
void KisPerspectiveTransformWorker::setForwardTransform(const QTransform &transform)
{
init(transform);
}
void KisPerspectiveTransformWorker::run()
{
KIS_ASSERT_RECOVER_RETURN(m_dev);
if (m_isIdentity) return;
KisPaintDeviceSP cloneDevice = new KisPaintDevice(*m_dev.data());
// Clear the destination device, since all the tiles are already
// shared with cloneDevice
m_dev->clear();
KIS_ASSERT_RECOVER_NOOP(!m_isIdentity);
KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, m_dstRegion.rectCount());
KisRandomSubAccessorSP srcAcc = cloneDevice->createRandomSubAccessor();
KisRandomAccessorSP accessor = m_dev->createRandomAccessorNG(0, 0);
Q_FOREACH (const QRect &rect, m_dstRegion.rects()) {
for (int y = rect.y(); y < rect.y() + rect.height(); ++y) {
for (int x = rect.x(); x < rect.x() + rect.width(); ++x) {
QPointF dstPoint(x, y);
QPointF srcPoint = m_backwardTransform.map(dstPoint);
if (m_srcRect.contains(srcPoint)) {
accessor->moveTo(dstPoint.x(), dstPoint.y());
srcAcc->moveTo(srcPoint.x(), srcPoint.y());
srcAcc->sampledOldRawData(accessor->rawData());
}
}
}
progressHelper.step();
}
}
void KisPerspectiveTransformWorker::runPartialDst(KisPaintDeviceSP srcDev,
KisPaintDeviceSP dstDev,
const QRect &dstRect)
{
if (m_isIdentity) {
KisPainter::copyAreaOptimizedOldData(dstRect.topLeft(), srcDev, dstDev, dstRect);
return;
}
QRectF srcClipRect = srcDev->exactBounds();
if (srcClipRect.isEmpty()) return;
KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, dstRect.height());
KisRandomSubAccessorSP srcAcc = srcDev->createRandomSubAccessor();
KisRandomAccessorSP accessor = dstDev->createRandomAccessorNG(dstRect.x(), dstRect.y());
for (int y = dstRect.y(); y < dstRect.y() + dstRect.height(); ++y) {
for (int x = dstRect.x(); x < dstRect.x() + dstRect.width(); ++x) {
QPointF dstPoint(x, y);
QPointF srcPoint = m_backwardTransform.map(dstPoint);
if (srcClipRect.contains(srcPoint)) {
accessor->moveTo(dstPoint.x(), dstPoint.y());
srcAcc->moveTo(srcPoint.x(), srcPoint.y());
srcAcc->sampledOldRawData(accessor->rawData());
}
}
progressHelper.step();
}
}
QTransform KisPerspectiveTransformWorker::forwardTransform() const
{
return m_forwardTransform;
}
QTransform KisPerspectiveTransformWorker::backwardTransform() const
{
return m_backwardTransform;
}
diff --git a/libs/image/kis_perspectivetransform_worker.h b/libs/image/kis_perspectivetransform_worker.h
index f148ea61a2..c80697fbd1 100644
--- a/libs/image/kis_perspectivetransform_worker.h
+++ b/libs/image/kis_perspectivetransform_worker.h
@@ -1,69 +1,68 @@
/*
- * This file is part of Krita
- *
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Marc Pegon <pe.marc@free.fr>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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_PERSPECTIVETRANSFORM_WORKER_H
#define KIS_PERSPECTIVETRANSFORM_WORKER_H
#include "kis_types.h"
#include "kritaimage_export.h"
#include <QRect>
#include <QRegion>
#include <QTransform>
#include <KoUpdater.h>
class KRITAIMAGE_EXPORT KisPerspectiveTransformWorker
{
public:
KisPerspectiveTransformWorker(KisPaintDeviceSP dev, QPointF center, double aX, double aY, double distance, KoUpdaterPtr progress);
KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const QTransform &transform, KoUpdaterPtr progress);
~KisPerspectiveTransformWorker();
void run();
void runPartialDst(KisPaintDeviceSP srcDev,
KisPaintDeviceSP dstDev,
const QRect &dstRect);
void setForwardTransform(const QTransform &transform);
QTransform forwardTransform() const;
QTransform backwardTransform() const;
private:
void init(const QTransform &transform);
void fillParams(const QRectF &srcRect,
const QRect &dstBaseClipRect,
QRegion *dstRegion,
QPolygonF *dstClipPolygon);
private:
KisPaintDeviceSP m_dev;
KoUpdaterPtr m_progressUpdater;
QRegion m_dstRegion;
QRectF m_srcRect;
QTransform m_backwardTransform;
QTransform m_forwardTransform;
bool m_isIdentity;
};
#endif
diff --git a/libs/image/kis_projection_leaf.cpp b/libs/image/kis_projection_leaf.cpp
index 816305889f..fb20387535 100644
--- a/libs/image/kis_projection_leaf.cpp
+++ b/libs/image/kis_projection_leaf.cpp
@@ -1,385 +1,393 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_projection_leaf.h"
#include <KoColorSpace.h>
#include "kis_layer.h"
#include "kis_mask.h"
#include "kis_group_layer.h"
#include "kis_selection_mask.h"
#include "kis_adjustment_layer.h"
#include "krita_utils.h"
#include "kis_refresh_subtree_walker.h"
#include "kis_async_merger.h"
#include "kis_node_graph_listener.h"
struct Q_DECL_HIDDEN KisProjectionLeaf::Private
{
Private(KisNode *_node) : node(_node) {}
KisNodeWSP node;
bool isTemporaryHidden = false;
static bool checkPassThrough(const KisNode *node) {
const KisGroupLayer *group = qobject_cast<const KisGroupLayer*>(node);
return group && group->passThroughMode();
}
static bool isSelectionMask(const KisNode *node) {
return qobject_cast<const KisSelectionMask*>(node);
}
static KisNodeSP skipSelectionMasksForward(KisNodeSP node) {
while (node && isSelectionMask(node)) {
node = node->nextSibling();
}
return node;
}
static KisNodeSP skipSelectionMasksBackward(KisNodeSP node) {
while (node && isSelectionMask(node)) {
node = node->prevSibling();
}
return node;
}
bool checkParentPassThrough() {
return node->parent() && checkPassThrough(node->parent());
}
bool checkThisPassThrough() {
return checkPassThrough(node);
}
KisProjectionLeafSP overlayProjectionLeaf() const {
return node && node->graphListener() && node->graphListener()->graphOverlayNode() ?
node->graphListener()->graphOverlayNode()->projectionLeaf() : 0;
}
bool isTopmostNode() const {
return !skipSelectionMasksForward(node->nextSibling()) &&
node->parent() &&
!node->parent()->parent();
}
KisNodeSP findRoot() const {
KisNodeSP root = node;
while (root->parent()) {
root = root->parent();
}
return root;
}
void temporarySetPassThrough(bool value) {
KisGroupLayer *group = qobject_cast<KisGroupLayer*>(node.data());
if (!group) return;
group->setPassThroughMode(value);
}
};
KisProjectionLeaf::KisProjectionLeaf(KisNode *node)
: m_d(new Private(node))
{
}
KisProjectionLeaf::~KisProjectionLeaf()
{
}
KisProjectionLeafSP KisProjectionLeaf::parent() const
{
KisNodeSP node;
if (Private::isSelectionMask(m_d->node)) {
if (m_d->overlayProjectionLeaf() == this) {
node = m_d->findRoot();
}
} else {
node = m_d->node->parent();
}
while (node && Private::checkPassThrough(node)) {
node = node->parent();
}
return node ? node->projectionLeaf() : KisProjectionLeafSP();
}
KisProjectionLeafSP KisProjectionLeaf::firstChild() const
{
KisNodeSP node;
if (!m_d->checkThisPassThrough()) {
node = m_d->node->firstChild();
node = Private::skipSelectionMasksForward(node);
}
if (!node && isRoot()) {
KisProjectionLeafSP overlayLeaf = m_d->overlayProjectionLeaf();
if (overlayLeaf) {
return overlayLeaf;
}
}
return node ? node->projectionLeaf() : KisProjectionLeafSP();
}
KisProjectionLeafSP KisProjectionLeaf::lastChild() const
{
KisNodeSP node;
if (isRoot()) {
KisProjectionLeafSP overlayLeaf = m_d->overlayProjectionLeaf();
if (overlayLeaf) {
return overlayLeaf;
}
}
if (!m_d->checkThisPassThrough()) {
node = m_d->node->lastChild();
node = Private::skipSelectionMasksBackward(node);
}
return node ? node->projectionLeaf() : KisProjectionLeafSP();
}
KisProjectionLeafSP KisProjectionLeaf::prevSibling() const
{
if (Private::isSelectionMask(m_d->node)) {
KisProjectionLeafSP leaf;
if (m_d->overlayProjectionLeaf() == this) {
KisNodeSP node = m_d->findRoot()->lastChild();
node = Private::skipSelectionMasksBackward(node);
leaf = node->projectionLeaf();
}
return leaf;
}
KisNodeSP node;
if (m_d->checkThisPassThrough()) {
node = m_d->node->lastChild();
node = Private::skipSelectionMasksBackward(node);
}
if (!node) {
node = m_d->node->prevSibling();
node = Private::skipSelectionMasksBackward(node);
}
const KisProjectionLeaf *leaf = this;
while (!node && leaf->m_d->checkParentPassThrough()) {
leaf = leaf->node()->parent()->projectionLeaf().data();
node = leaf->node()->prevSibling();
node = Private::skipSelectionMasksBackward(node);
}
return node ? node->projectionLeaf() : KisProjectionLeafSP();
}
KisProjectionLeafSP KisProjectionLeaf::nextSibling() const
{
if (Private::isSelectionMask(m_d->node)) {
return KisProjectionLeafSP();
}
KisProjectionLeafSP overlayLeaf = m_d->overlayProjectionLeaf();
if (overlayLeaf && m_d->isTopmostNode()) {
return overlayLeaf;
}
KisNodeSP node = m_d->node->nextSibling();
node = Private::skipSelectionMasksForward(node);
while (node && Private::checkPassThrough(node) && node->firstChild()) {
node = node->firstChild();
node = Private::skipSelectionMasksForward(node);
}
if (!node && m_d->checkParentPassThrough()) {
node = m_d->node->parent();
node = Private::skipSelectionMasksForward(node);
}
return node ? node->projectionLeaf() : KisProjectionLeafSP();
}
KisNodeSP KisProjectionLeaf::node() const
{
return m_d->node;
}
KisAbstractProjectionPlaneSP KisProjectionLeaf::projectionPlane() const
{
return m_d->node->projectionPlane();
}
bool KisProjectionLeaf::accept(KisNodeVisitor &visitor)
{
return m_d->node->accept(visitor);
}
KisPaintDeviceSP KisProjectionLeaf::original()
{
return m_d->node->original();
}
KisPaintDeviceSP KisProjectionLeaf::projection()
{
return m_d->node->projection();
}
bool KisProjectionLeaf::isRoot() const
{
return (bool)!m_d->node->parent();
}
bool KisProjectionLeaf::isLayer() const
{
- return (bool)qobject_cast<const KisLayer*>(m_d->node.data());
+ return (bool)qobject_cast<const KisLayer*>(m_d->node.data()) &&
+ !m_d->node->isFakeNode();
}
bool KisProjectionLeaf::isMask() const
{
- return (bool)qobject_cast<const KisMask*>(m_d->node.data());
+ return (bool)qobject_cast<const KisMask*>(m_d->node.data()) &&
+ !m_d->node->isFakeNode();
}
bool KisProjectionLeaf::canHaveChildLayers() const
{
return (bool)qobject_cast<const KisGroupLayer*>(m_d->node.data());
}
bool KisProjectionLeaf::dependsOnLowerNodes() const
{
return (bool)qobject_cast<const KisAdjustmentLayer*>(m_d->node.data());
}
bool KisProjectionLeaf::visible() const
{
if (m_d->isTemporaryHidden) return false;
// TODO: check opacity as well!
bool hiddenByParentPassThrough = false;
KisNodeSP node = m_d->node->parent();
while (node && node->projectionLeaf()->m_d->checkThisPassThrough()) {
hiddenByParentPassThrough |= !node->visible();
node = node->parent();
}
return m_d->node->visible(false) &&
!m_d->checkThisPassThrough() &&
!hiddenByParentPassThrough;
}
quint8 KisProjectionLeaf::opacity() const
{
quint8 resultOpacity = m_d->node->opacity();
if (m_d->checkParentPassThrough()) {
quint8 parentOpacity = m_d->node->parent()->projectionLeaf()->opacity();
resultOpacity = KritaUtils::mergeOpacity(resultOpacity, parentOpacity);
}
return resultOpacity;
}
QBitArray KisProjectionLeaf::channelFlags() const
{
QBitArray channelFlags;
KisLayer *layer = qobject_cast<KisLayer*>(m_d->node.data());
if (!layer) return channelFlags;
channelFlags = layer->channelFlags();
if (m_d->checkParentPassThrough()) {
QBitArray parentChannelFlags;
if (*m_d->node->colorSpace() ==
*m_d->node->parent()->colorSpace()) {
KisLayer *parentLayer = qobject_cast<KisLayer*>(m_d->node->parent().data());
parentChannelFlags = parentLayer->channelFlags();
}
channelFlags = KritaUtils::mergeChannelFlags(channelFlags, parentChannelFlags);
}
return channelFlags;
}
bool KisProjectionLeaf::isStillInGraph() const
{
return (bool)m_d->node->graphListener();
}
+bool KisProjectionLeaf::hasClones() const
+{
+ KisLayer *layer = qobject_cast<KisLayer*>(m_d->node.data());
+ return layer ? layer->hasClones() : false;
+}
+
bool KisProjectionLeaf::isDroppedMask() const
{
return qobject_cast<KisMask*>(m_d->node.data()) &&
m_d->checkParentPassThrough();
}
bool KisProjectionLeaf::isOverlayProjectionLeaf() const
{
return this == m_d->overlayProjectionLeaf();
}
void KisProjectionLeaf::setTemporaryHiddenFromRendering(bool value)
{
m_d->isTemporaryHidden = value;
}
bool KisProjectionLeaf::isTemporaryHiddenFromRendering() const
{
return m_d->isTemporaryHidden;
}
/**
* This method is rather slow and dangerous. It should be executes in
* exclusive environment only.
*/
void KisProjectionLeaf::explicitlyRegeneratePassThroughProjection()
{
if (!m_d->checkThisPassThrough()) return;
m_d->temporarySetPassThrough(false);
const QRect updateRect = projection()->defaultBounds()->bounds();
KisRefreshSubtreeWalker walker(updateRect);
walker.collectRects(m_d->node, updateRect);
KisAsyncMerger merger;
merger.startMerge(walker);
m_d->temporarySetPassThrough(true);
}
diff --git a/libs/image/kis_projection_leaf.h b/libs/image/kis_projection_leaf.h
index faf616d71b..a883862854 100644
--- a/libs/image/kis_projection_leaf.h
+++ b/libs/image/kis_projection_leaf.h
@@ -1,95 +1,96 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_PROJECTION_LEAF_H
#define __KIS_PROJECTION_LEAF_H
#include <QScopedPointer>
#include "kis_types.h"
#include "kritaimage_export.h"
class KisNodeVisitor;
class KRITAIMAGE_EXPORT KisProjectionLeaf
{
public:
KisProjectionLeaf(KisNode *node);
virtual ~KisProjectionLeaf();
KisProjectionLeafSP parent() const;
KisProjectionLeafSP firstChild() const;
KisProjectionLeafSP lastChild() const;
KisProjectionLeafSP prevSibling() const;
KisProjectionLeafSP nextSibling() const;
KisNodeSP node() const;
KisAbstractProjectionPlaneSP projectionPlane() const;
bool accept(KisNodeVisitor &visitor);
KisPaintDeviceSP original();
KisPaintDeviceSP projection();
bool isRoot() const;
bool isLayer() const;
bool isMask() const;
bool canHaveChildLayers() const;
bool dependsOnLowerNodes() const;
bool visible() const;
quint8 opacity() const;
QBitArray channelFlags() const;
bool isStillInGraph() const;
+ bool hasClones() const;
bool isDroppedMask() const;
bool isOverlayProjectionLeaf() const;
/**
* Temporarily exclude the projection leaf from rendering by making
* it invisible (KisProjectionLeaf::visible() == false).
*
* This method is used by the tools that want to hide the
* original layer's content temporarily.
*
* NOTE: the method is not thread-safe! The caller must guarantee
* exclusive access to the projection leaf himself.
*/
void setTemporaryHiddenFromRendering(bool value);
/**
* \see setTemporaryHiddenFromRendering
*/
bool isTemporaryHiddenFromRendering() const;
/**
* Regenerate projection of the current group layer iff it is
* pass-through mode.
*
* WARNING: must be called either under the image lock held
* or in the context of an exclusive stroke job.
*/
void explicitlyRegeneratePassThroughProjection();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_PROJECTION_LEAF_H */
diff --git a/libs/image/kis_random_sub_accessor.cpp b/libs/image/kis_random_sub_accessor.cpp
index 08ae4128cb..9a0dfa87fa 100644
--- a/libs/image/kis_random_sub_accessor.cpp
+++ b/libs/image/kis_random_sub_accessor.cpp
@@ -1,92 +1,109 @@
/*
* This file is part of the KDE project
*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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_random_sub_accessor.h"
#include <QtGlobal>
#include <KoColorSpace.h>
#include <KoMixColorsOp.h>
#include <math.h>
#include "kis_paint_device.h"
KisRandomSubAccessor::KisRandomSubAccessor(KisPaintDeviceSP device)
: m_device(device)
, m_currentPoint(0, 0)
, m_randomAccessor(device->createRandomConstAccessorNG(0, 0))
{
}
KisRandomSubAccessor::~KisRandomSubAccessor()
{
}
void KisRandomSubAccessor::sampledOldRawData(quint8* dst)
{
const quint8* pixels[4];
qint16 weights[4];
- int x = (int)floor(m_currentPoint.x());
- int y = (int)floor(m_currentPoint.y());
+ qreal x = m_currentPoint.x();
+ qreal y = m_currentPoint.y();
+ int xz = qRound(x);
+ int yz = qRound(y);
+
double hsub = m_currentPoint.x() - x;
- if (hsub < 0.0) hsub = 1.0 + hsub;
+ if (hsub < 0.0) {
+ hsub = 1.0 + hsub;
+ }
double vsub = m_currentPoint.y() - y;
- if (vsub < 0.0) vsub = 1.0 + vsub;
+ if (vsub < 0.0) {
+ vsub = 1.0 + vsub;
+ }
+
weights[0] = qRound((1.0 - hsub) * (1.0 - vsub) * 255);
- m_randomAccessor->moveTo(x, y);
+ m_randomAccessor->moveTo(xz, yz);
pixels[0] = m_randomAccessor->oldRawData();
weights[1] = qRound((1.0 - vsub) * hsub * 255);
- m_randomAccessor->moveTo(x + 1, y);
+ m_randomAccessor->moveTo(xz + 1, yz);
pixels[1] = m_randomAccessor->oldRawData();
weights[2] = qRound(vsub * (1.0 - hsub) * 255);
- m_randomAccessor->moveTo(x, y + 1);
+ m_randomAccessor->moveTo(xz, yz + 1);
pixels[2] = m_randomAccessor->oldRawData();
weights[3] = qRound(hsub * vsub * 255);
- m_randomAccessor->moveTo(x + 1, y + 1);
+ m_randomAccessor->moveTo(xz + 1, yz + 1);
pixels[3] = m_randomAccessor->oldRawData();
+
m_device->colorSpace()->mixColorsOp()->mixColors(pixels, weights, 4, dst);
}
void KisRandomSubAccessor::sampledRawData(quint8* dst)
{
const quint8* pixels[4];
qint16 weights[4];
- int x = (int)floor(m_currentPoint.x());
- int y = (int)floor(m_currentPoint.y());
+ qreal x = m_currentPoint.x();
+ qreal y = m_currentPoint.y();
+ int xz = qRound(x);
+ int yz = qRound(y);
+
double hsub = m_currentPoint.x() - x;
- if (hsub < 0.0) hsub = 1.0 + hsub;
+ if (hsub < 0.0) {
+ hsub = 1.0 + hsub;
+ }
double vsub = m_currentPoint.y() - y;
- if (vsub < 0.0) vsub = 1.0 + vsub;
+ if (vsub < 0.0) {
+ vsub = 1.0 + vsub;
+ }
+
weights[0] = qRound((1.0 - hsub) * (1.0 - vsub) * 255);
- m_randomAccessor->moveTo(x, y);
+ m_randomAccessor->moveTo(xz, yz);
pixels[0] = m_randomAccessor->rawDataConst();
weights[1] = qRound((1.0 - vsub) * hsub * 255);
- m_randomAccessor->moveTo(x + 1, y);
+ m_randomAccessor->moveTo(xz + 1, yz);
pixels[1] = m_randomAccessor->rawDataConst();
weights[2] = qRound(vsub * (1.0 - hsub) * 255);
- m_randomAccessor->moveTo(x, y + 1);
+ m_randomAccessor->moveTo(xz, yz + 1);
pixels[2] = m_randomAccessor->rawDataConst();
weights[3] = qRound(hsub * vsub * 255);
- m_randomAccessor->moveTo(x + 1, y + 1);
+ m_randomAccessor->moveTo(xz + 1, yz + 1);
pixels[3] = m_randomAccessor->rawDataConst();
m_device->colorSpace()->mixColorsOp()->mixColors(pixels, weights, 4, dst);
}
diff --git a/libs/image/kis_random_sub_accessor.h b/libs/image/kis_random_sub_accessor.h
index 2b3cca8c63..b3b81e1810 100644
--- a/libs/image/kis_random_sub_accessor.h
+++ b/libs/image/kis_random_sub_accessor.h
@@ -1,62 +1,63 @@
/*
* This file is part of the KDE project
*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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_RANDOM_SUB_ACCESSOR_H
#define KIS_RANDOM_SUB_ACCESSOR_H
#include "kis_random_accessor_ng.h"
#include "kis_types.h"
#include <kritaimage_export.h>
#include "kis_shared.h"
/**
* Gives a random access to the sampled subpixels of an image. Use the
* moveTo function to select the pixel. And then rawData to access the
* value of a pixel.
*/
class KRITAIMAGE_EXPORT KisRandomSubAccessor : public KisShared
{
public:
KisRandomSubAccessor(KisPaintDeviceSP device);
~KisRandomSubAccessor();
/**
* Copy the sampled old value to destination
*/
void sampledOldRawData(quint8* dst);
/**
* Copy the sampled value to destination
*/
void sampledRawData(quint8* dst);
- inline void moveTo(double x, double y) {
+ inline void moveTo(qreal x, qreal y) {
m_currentPoint.setX(x); m_currentPoint.setY(y);
}
inline void moveTo(const QPointF& p) {
m_currentPoint = p;
}
+
private:
KisPaintDeviceSP m_device;
QPointF m_currentPoint;
KisRandomConstAccessorSP m_randomAccessor;
};
#endif
diff --git a/libs/image/kis_selection_based_layer.cpp b/libs/image/kis_selection_based_layer.cpp
index 7a785d17e7..3cac42942e 100644
--- a/libs/image/kis_selection_based_layer.cpp
+++ b/libs/image/kis_selection_based_layer.cpp
@@ -1,353 +1,358 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_selection_based_layer.h"
#include <klocalizedstring.h>
#include "kis_debug.h"
#include <KoCompositeOpRegistry.h>
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_default_bounds.h"
#include "kis_selection.h"
#include "kis_pixel_selection.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "filter/kis_filter.h"
#include "kis_raster_keyframe_channel.h"
struct Q_DECL_HIDDEN KisSelectionBasedLayer::Private
{
public:
Private() : useSelectionInProjection(true) {}
Private(const Private &rhs) : useSelectionInProjection(rhs.useSelectionInProjection) {}
KisSelectionSP selection;
KisPaintDeviceSP paintDevice;
bool useSelectionInProjection;
};
KisSelectionBasedLayer::KisSelectionBasedLayer(KisImageWSP image,
const QString &name,
KisSelectionSP selection,
KisFilterConfigurationSP filterConfig,
bool useGeneratorRegistry)
: KisLayer(image.data(), name, OPACITY_OPAQUE_U8),
KisNodeFilterInterface(filterConfig, useGeneratorRegistry),
m_d(new Private())
{
if (!selection) {
initSelection();
} else {
setInternalSelection(selection);
}
KisImageSP imageSP = image.toStrongRef();
if (!imageSP) {
return;
}
m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(this, imageSP->colorSpace(), KisDefaultBoundsSP(new KisDefaultBounds(image))));
connect(imageSP.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged()));
}
KisSelectionBasedLayer::KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs)
: KisLayer(rhs)
, KisIndirectPaintingSupport()
, KisNodeFilterInterface(rhs)
, m_d(new Private(*rhs.m_d))
{
setInternalSelection(rhs.m_d->selection);
m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data());
}
KisSelectionBasedLayer::~KisSelectionBasedLayer()
{
delete m_d;
}
void KisSelectionBasedLayer::initSelection()
{
m_d->selection = KisSelectionSP(new KisSelection(KisDefaultBoundsSP(new KisDefaultBounds(image()))));
m_d->selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, m_d->selection->pixelSelection()->colorSpace()));
m_d->selection->setParentNode(this);
m_d->selection->updateProjection();
}
void KisSelectionBasedLayer::slotImageSizeChanged()
{
if (m_d->selection) {
/**
* Make sure exactBounds() of the selection got recalculated after
* the image changed
*/
m_d->selection->pixelSelection()->setDirty();
setDirty();
}
}
void KisSelectionBasedLayer::setImage(KisImageWSP image)
{
m_d->paintDevice->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image)));
KisLayer::setImage(image);
connect(image.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged()));
}
bool KisSelectionBasedLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
KisPaintDeviceSP KisSelectionBasedLayer::original() const
{
return m_d->paintDevice;
}
KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const
{
return m_d->selection->pixelSelection();
}
bool KisSelectionBasedLayer::needProjection() const
{
return m_d->selection;
}
void KisSelectionBasedLayer::setUseSelectionInProjection(bool value) const
{
m_d->useSelectionInProjection = value;
}
KisSelectionSP KisSelectionBasedLayer::fetchComposedInternalSelection(const QRect &rect) const
{
if (!m_d->selection) return KisSelectionSP();
m_d->selection->updateProjection(rect);
KisSelectionSP tempSelection = m_d->selection;
KisIndirectPaintingSupport::ReadLocker l(this);
if (hasTemporaryTarget()) {
/**
- * Cloning a selection with COW
- * FIXME: check whether it's faster than usual bitBlt'ing
+ * WARNING: we don't try to clone the selection entirely, because
+ * it might be unsafe for shape selections.
+ *
+ * TODO: make cloning of vector selections safe! See a comment in
+ * KisShapeSelection::clone().
*/
- tempSelection = new KisSelection(*tempSelection);
+ tempSelection = new KisSelection();
+
+ KisPainter::copyAreaOptimized(rect.topLeft(), m_d->selection->pixelSelection(), tempSelection->pixelSelection(), rect);
KisPainter gc2(tempSelection->pixelSelection());
setupTemporaryPainter(&gc2);
gc2.bitBlt(rect.topLeft(), temporaryTarget(), rect);
}
return tempSelection;
}
void KisSelectionBasedLayer::copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const
{
KisSelectionSP tempSelection;
if (m_d->useSelectionInProjection) {
tempSelection = fetchComposedInternalSelection(rect);
/**
* When we paint with a selection, the deselected areas will *not* be
* overwritten by copyAreaOptimized(), so we need to clear them beforehand
*/
projection->clear(rect);
}
KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect, tempSelection);
}
QRect KisSelectionBasedLayer::cropChangeRectBySelection(const QRect &rect) const
{
return m_d->selection ?
rect & m_d->selection->selectedRect() :
rect;
}
QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
void KisSelectionBasedLayer::resetCache()
{
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return;
}
if (!m_d->paintDevice || *m_d->paintDevice->colorSpace() != *imageSP->colorSpace()) {
m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(KisNodeWSP(this), imageSP->colorSpace(), new KisDefaultBounds(image())));
} else {
m_d->paintDevice->clear();
}
}
KisSelectionSP KisSelectionBasedLayer::internalSelection() const
{
return m_d->selection;
}
void KisSelectionBasedLayer::setInternalSelection(KisSelectionSP selection)
{
if (selection) {
m_d->selection = new KisSelection(*selection.data());
m_d->selection->setParentNode(this);
m_d->selection->setDefaultBounds(new KisDefaultBounds(image()));
m_d->selection->updateProjection();
KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection();
if (pixelSelection->framesInterface()) {
addKeyframeChannel(pixelSelection->keyframeChannel());
enableAnimation();
}
KisImageSP imageSP = image().toStrongRef();
KIS_SAFE_ASSERT_RECOVER_RETURN(imageSP);
if (m_d->selection->pixelSelection()->defaultBounds()->bounds() != imageSP->bounds()) {
qWarning() << "WARNING: KisSelectionBasedLayer::setInternalSelection"
<< "New selection has suspicious default bounds";
qWarning() << "WARNING:" << ppVar(m_d->selection->pixelSelection()->defaultBounds()->bounds());
qWarning() << "WARNING:" << ppVar(imageSP->bounds());
}
} else {
m_d->selection = 0;
}
}
qint32 KisSelectionBasedLayer::x() const
{
return m_d->selection ? m_d->selection->x() : 0;
}
qint32 KisSelectionBasedLayer::y() const
{
return m_d->selection ? m_d->selection->y() : 0;
}
void KisSelectionBasedLayer::setX(qint32 x)
{
if (m_d->selection) {
m_d->selection->setX(x);
}
}
void KisSelectionBasedLayer::setY(qint32 y)
{
if (m_d->selection) {
m_d->selection->setY(y);
}
}
KisKeyframeChannel *KisSelectionBasedLayer::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::Content.id()) {
KisRasterKeyframeChannel *contentChannel = m_d->selection->pixelSelection()->createKeyframeChannel(KisKeyframeChannel::Content);
contentChannel->setFilenameSuffix(".pixelselection");
return contentChannel;
}
return KisLayer::requestKeyframeChannel(id);
}
void KisSelectionBasedLayer::setDirty()
{
Q_ASSERT(image());
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return;
}
setDirty(imageSP->bounds());
}
QRect KisSelectionBasedLayer::extent() const
{
QRect resultRect;
if (m_d->selection) {
resultRect = m_d->selection->selectedRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
resultRect |= temporaryTarget->extent();
}
} else {
KisImageSP image = this->image().toStrongRef();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect());
resultRect = image->bounds();
}
return resultRect;
}
QRect KisSelectionBasedLayer::exactBounds() const
{
QRect resultRect;
if (m_d->selection) {
resultRect = m_d->selection->selectedExactRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
resultRect |= temporaryTarget->exactBounds();
}
} else {
KisImageSP image = this->image().toStrongRef();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect());
resultRect = image->bounds();
}
return resultRect;
}
QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h)
{
KisSelectionSP originalSelection = internalSelection();
KisPaintDeviceSP originalDevice = original();
return originalDevice && originalSelection ?
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) :
QImage();
}
diff --git a/libs/image/kis_transform_mask.cpp b/libs/image/kis_transform_mask.cpp
index 67351a8b04..a2746b3504 100644
--- a/libs/image/kis_transform_mask.cpp
+++ b/libs/image/kis_transform_mask.cpp
@@ -1,480 +1,489 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <KoIcon.h>
#include <kis_icon.h>
#include <KoCompositeOpRegistry.h>
#include "kis_layer.h"
#include "kis_transform_mask.h"
#include "filter/kis_filter.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "kis_selection.h"
#include "kis_processing_information.h"
#include "kis_node.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_node_progress_proxy.h"
#include "kis_transaction.h"
#include "kis_painter.h"
#include "kis_busy_progress_indicator.h"
#include "kis_perspectivetransform_worker.h"
#include "kis_transform_mask_params_interface.h"
#include "kis_transform_mask_params_factory_registry.h"
#include "kis_recalculate_transform_mask_job.h"
#include "kis_thread_safe_signal_compressor.h"
#include "kis_algebra_2d.h"
#include "kis_safe_transform.h"
#include "kis_keyframe_channel.h"
#include "kis_image_config.h"
//#include "kis_paint_device_debug_utils.h"
//#define DEBUG_RENDERING
//#define DUMP_RECT QRect(0,0,512,512)
#define UPDATE_DELAY 3000 /*ms */
struct Q_DECL_HIDDEN KisTransformMask::Private
{
Private()
: worker(0, QTransform(), 0),
staticCacheValid(false),
recalculatingStaticImage(false),
updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE),
offBoundsReadArea(0.5)
{
}
Private(const Private &rhs)
: worker(rhs.worker),
params(rhs.params),
staticCacheValid(rhs.staticCacheValid),
recalculatingStaticImage(rhs.recalculatingStaticImage),
updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE),
offBoundsReadArea(rhs.offBoundsReadArea)
{
}
void reloadParameters()
{
QTransform affineTransform;
if (params->isAffine()) {
affineTransform = params->finalAffineTransform();
}
worker.setForwardTransform(affineTransform);
params->clearChangedFlag();
staticCacheValid = false;
}
KisPerspectiveTransformWorker worker;
KisTransformMaskParamsInterfaceSP params;
bool staticCacheValid;
bool recalculatingStaticImage;
KisPaintDeviceSP staticCacheDevice;
KisThreadSafeSignalCompressor updateSignalCompressor;
qreal offBoundsReadArea;
};
KisTransformMask::KisTransformMask()
: KisEffectMask(),
m_d(new Private())
{
setTransformParams(
KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams()));
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
connect(this, SIGNAL(sigInternalForceStaticImageUpdate()), SLOT(slotInternalForceStaticImageUpdate()));
m_d->offBoundsReadArea = KisImageConfig(true).transformMaskOffBoundsReadArea();
}
KisTransformMask::~KisTransformMask()
{
}
KisTransformMask::KisTransformMask(const KisTransformMask& rhs)
: KisEffectMask(rhs),
m_d(new Private(*rhs.m_d))
{
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
}
KisPaintDeviceSP KisTransformMask::paintDevice() const
{
return 0;
}
QIcon KisTransformMask::icon() const
{
return KisIconUtils::loadIcon("transformMask");
}
void KisTransformMask::setTransformParams(KisTransformMaskParamsInterfaceSP params)
{
KIS_ASSERT_RECOVER(params) {
params = KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams());
}
m_d->params = params;
m_d->reloadParameters();
m_d->updateSignalCompressor.stop();
}
KisTransformMaskParamsInterfaceSP KisTransformMask::transformParams() const
{
return m_d->params;
}
void KisTransformMask::slotDelayedStaticUpdate()
{
/**
* The mask might have been deleted from the layers stack in the
* meanwhile. Just ignore the updates in the case.
*/
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
if (!parentLayer) return;
KisImageSP image = parentLayer->image();
if (image) {
image->addSpontaneousJob(new KisRecalculateTransformMaskJob(this));
}
}
KisPaintDeviceSP KisTransformMask::buildPreviewDevice()
{
/**
* Note: this function must be called from within the scheduler's
* context. We are accessing parent's updateProjection(), which
* is not entirely safe.
*/
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
KIS_ASSERT_RECOVER(parentLayer) { return new KisPaintDevice(colorSpace()); }
KisPaintDeviceSP device =
new KisPaintDevice(parentLayer->original()->colorSpace());
QRect requestedRect = parentLayer->original()->exactBounds();
parentLayer->buildProjectionUpToNode(device, this, requestedRect);
return device;
}
void KisTransformMask::recaclulateStaticImage()
{
/**
* Note: this function must be called from within the scheduler's
* context. We are accessing parent's updateProjection(), which
* is not entirely safe.
*/
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
KIS_ASSERT_RECOVER_RETURN(parentLayer);
if (!m_d->staticCacheDevice) {
m_d->staticCacheDevice =
new KisPaintDevice(parentLayer->original()->colorSpace());
}
m_d->recalculatingStaticImage = true;
/**
* updateProjection() is assuming that the requestedRect takes
* into account all the change rects of all the masks. Usually,
* this work is done by the walkers.
*/
QRect requestedRect = parentLayer->changeRect(parentLayer->original()->exactBounds());
/**
* Here we use updateProjection() to regenerate the projection of
* the layer and after that a special update call (no-filthy) will
* be issued to pass the changed further through the stack.
*/
parentLayer->updateProjection(requestedRect, this);
m_d->recalculatingStaticImage = false;
m_d->staticCacheValid = true;
}
QRect KisTransformMask::decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc,
PositionToFilthy maskPos) const
{
Q_ASSERT_X(src != dst, "KisTransformMask::decorateRect",
"src must be != dst, because we can't create transactions "
"during merge, as it breaks reentrancy");
KIS_ASSERT_RECOVER(m_d->params) { return rc; }
if (m_d->params->isHidden()) return rc;
KIS_ASSERT_RECOVER_NOOP(maskPos == N_FILTHY ||
maskPos == N_ABOVE_FILTHY ||
maskPos == N_BELOW_FILTHY);
if (m_d->params->hasChanged()) m_d->reloadParameters();
if (!m_d->recalculatingStaticImage &&
(maskPos == N_FILTHY || maskPos == N_ABOVE_FILTHY)) {
m_d->staticCacheValid = false;
m_d->updateSignalCompressor.start();
}
if (m_d->recalculatingStaticImage) {
m_d->staticCacheDevice->clear();
m_d->params->transformDevice(const_cast<KisTransformMask*>(this), src, m_d->staticCacheDevice);
QRect updatedRect = m_d->staticCacheDevice->extent();
KisPainter::copyAreaOptimized(updatedRect.topLeft(), m_d->staticCacheDevice, dst, updatedRect);
#ifdef DEBUG_RENDERING
qDebug() << "Recalculate" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc);
KIS_DUMP_DEVICE_2(src, DUMP_RECT, "recalc_src", "dd");
KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "recalc_dst", "dd");
#endif /* DEBUG_RENDERING */
} else if (!m_d->staticCacheValid && m_d->params->isAffine()) {
m_d->worker.runPartialDst(src, dst, rc);
#ifdef DEBUG_RENDERING
qDebug() << "Partial" << name() << ppVar(src->exactBounds()) << ppVar(src->extent()) << ppVar(dst->exactBounds()) << ppVar(dst->extent()) << ppVar(rc);
KIS_DUMP_DEVICE_2(src, DUMP_RECT, "partial_src", "dd");
KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "partial_dst", "dd");
#endif /* DEBUG_RENDERING */
} else if (m_d->staticCacheDevice && m_d->staticCacheValid) {
KisPainter::copyAreaOptimized(rc.topLeft(), m_d->staticCacheDevice, dst, rc);
#ifdef DEBUG_RENDERING
qDebug() << "Fetch" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc);
KIS_DUMP_DEVICE_2(src, DUMP_RECT, "fetch_src", "dd");
KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "fetch_dst", "dd");
#endif /* DEBUG_RENDERING */
}
KIS_ASSERT_RECOVER_NOOP(this->busyProgressIndicator());
this->busyProgressIndicator()->update();
return rc;
}
bool KisTransformMask::accept(KisNodeVisitor &v)
{
return v.visit(this);
}
void KisTransformMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
QRect KisTransformMask::changeRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
/**
* FIXME: This check of the emptiness should be done
* on the higher/lower level
*/
if (rect.isEmpty()) return rect;
QRect changeRect = rect;
if (m_d->params->isAffine()) {
QRect bounds;
QRect interestRect;
KisNodeSP parentNode = parent();
if (parentNode) {
bounds = parentNode->original()->defaultBounds()->bounds();
interestRect = parentNode->original()->extent();
} else {
bounds = QRect(0,0,777,777);
interestRect = QRect(0,0,888,888);
warnKrita << "WARNING: transform mask has no parent (change rect)."
<< "Cannot run safe transformations."
<< "Will limit bounds to" << ppVar(bounds);
}
const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea);
if (m_d->params->hasChanged()) m_d->reloadParameters();
KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
changeRect = transform.mapRectForward(rect);
} else {
QRect interestRect;
interestRect = parent() ? parent()->original()->extent() : QRect();
changeRect = m_d->params->nonAffineChangeRect(rect);
}
return changeRect;
}
QRect KisTransformMask::needRect(const QRect& rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
/**
* FIXME: This check of the emptiness should be done
* on the higher/lower level
*/
if (rect.isEmpty()) return rect;
if (!m_d->params->isAffine()) return rect;
QRect bounds;
QRect interestRect;
KisNodeSP parentNode = parent();
if (parentNode) {
bounds = parentNode->original()->defaultBounds()->bounds();
interestRect = parentNode->original()->extent();
} else {
bounds = QRect(0,0,777,777);
interestRect = QRect(0,0,888,888);
warnKrita << "WARNING: transform mask has no parent (need rect)."
<< "Cannot run safe transformations."
<< "Will limit bounds to" << ppVar(bounds);
}
QRect needRect = rect;
if (m_d->params->isAffine()) {
const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea);
if (m_d->params->hasChanged()) m_d->reloadParameters();
KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
needRect = transform.mapRectBackward(rect);
} else {
needRect = m_d->params->nonAffineNeedRect(rect, interestRect);
}
return needRect;
}
QRect KisTransformMask::extent() const
{
QRect rc = KisMask::extent();
QRect partialChangeRect;
QRect existentProjection;
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
if (parentLayer) {
partialChangeRect = parentLayer->partialChangeRect(const_cast<KisTransformMask*>(this), rc);
existentProjection = parentLayer->projection()->extent();
}
return changeRect(partialChangeRect) | existentProjection;
}
QRect KisTransformMask::exactBounds() const
+{
+ QRect existentProjection;
+ KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
+ if (parentLayer) {
+ existentProjection = parentLayer->projection()->exactBounds();
+ }
+
+ return changeRect(sourceDataBounds()) | existentProjection;
+}
+
+QRect KisTransformMask::sourceDataBounds() const
{
QRect rc = KisMask::exactBounds();
- QRect partialChangeRect;
- QRect existentProjection;
+ QRect partialChangeRect = rc;
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
if (parentLayer) {
partialChangeRect = parentLayer->partialChangeRect(const_cast<KisTransformMask*>(this), rc);
- existentProjection = parentLayer->projection()->exactBounds();
}
- return changeRect(partialChangeRect) | existentProjection;
+ return partialChangeRect;
}
void KisTransformMask::setX(qint32 x)
{
m_d->params->translate(QPointF(x - this->x(), 0));
setTransformParams(m_d->params);
KisEffectMask::setX(x);
}
void KisTransformMask::setY(qint32 y)
{
m_d->params->translate(QPointF(0, y - this->y()));
setTransformParams(m_d->params);
KisEffectMask::setY(y);
}
void KisTransformMask::forceUpdateTimedNode()
{
if (hasPendingTimedUpdates()) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->staticCacheValid);
m_d->updateSignalCompressor.stop();
slotDelayedStaticUpdate();
}
}
bool KisTransformMask::hasPendingTimedUpdates() const
{
return m_d->updateSignalCompressor.isActive();
}
void KisTransformMask::threadSafeForceStaticImageUpdate()
{
emit sigInternalForceStaticImageUpdate();
}
void KisTransformMask::slotInternalForceStaticImageUpdate()
{
m_d->updateSignalCompressor.stop();
slotDelayedStaticUpdate();
}
KisKeyframeChannel *KisTransformMask::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::TransformArguments.id() ||
id == KisKeyframeChannel::TransformPositionX.id() ||
id == KisKeyframeChannel::TransformPositionY.id() ||
id == KisKeyframeChannel::TransformScaleX.id() ||
id == KisKeyframeChannel::TransformScaleY.id() ||
id == KisKeyframeChannel::TransformShearX.id() ||
id == KisKeyframeChannel::TransformShearY.id() ||
id == KisKeyframeChannel::TransformRotationX.id() ||
id == KisKeyframeChannel::TransformRotationY.id() ||
id == KisKeyframeChannel::TransformRotationZ.id()) {
KisAnimatedTransformParamsInterface *animatedParams = dynamic_cast<KisAnimatedTransformParamsInterface*>(m_d->params.data());
if (!animatedParams) {
auto converted = KisTransformMaskParamsFactoryRegistry::instance()->animateParams(m_d->params);
if (converted.isNull()) return KisEffectMask::requestKeyframeChannel(id);
m_d->params = converted;
animatedParams = dynamic_cast<KisAnimatedTransformParamsInterface*>(converted.data());
}
KisKeyframeChannel *channel = animatedParams->getKeyframeChannel(id, parent()->original()->defaultBounds());
if (channel) return channel;
}
return KisEffectMask::requestKeyframeChannel(id);
}
diff --git a/libs/image/kis_transform_mask.h b/libs/image/kis_transform_mask.h
index e7308a226c..313aef8c9e 100644
--- a/libs/image/kis_transform_mask.h
+++ b/libs/image/kis_transform_mask.h
@@ -1,98 +1,99 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_TRANSFORM_MASK_
#define _KIS_TRANSFORM_MASK_
#include <QScopedPointer>
#include "kis_types.h"
#include "kis_effect_mask.h"
#include "KisDelayedUpdateNodeInterface.h"
/**
Transform a layer according to a matrix transform
*/
class KRITAIMAGE_EXPORT KisTransformMask : public KisEffectMask, public KisDelayedUpdateNodeInterface
{
Q_OBJECT
public:
/**
* Create an empty filter mask.
*/
KisTransformMask();
~KisTransformMask() override;
QIcon icon() const override;
KisNodeSP clone() const override {
return KisNodeSP(new KisTransformMask(*this));
}
KisPaintDeviceSP paintDevice() const override;
bool accept(KisNodeVisitor &v) override;
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
KisTransformMask(const KisTransformMask& rhs);
QRect decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc,
PositionToFilthy maskPos) const override;
QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override;
QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override;
QRect extent() const override;
QRect exactBounds() const override;
+ QRect sourceDataBounds() const;
void setTransformParams(KisTransformMaskParamsInterfaceSP params);
KisTransformMaskParamsInterfaceSP transformParams() const;
void recaclulateStaticImage();
KisPaintDeviceSP buildPreviewDevice();
void setX(qint32 x) override;
void setY(qint32 y) override;
void forceUpdateTimedNode() override;
bool hasPendingTimedUpdates() const override;
void threadSafeForceStaticImageUpdate();
protected:
KisKeyframeChannel *requestKeyframeChannel(const QString &id) override;
Q_SIGNALS:
void sigInternalForceStaticImageUpdate();
private Q_SLOTS:
void slotDelayedStaticUpdate();
void slotInternalForceStaticImageUpdate();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif //_KIS_TRANSFORM_MASK_
diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt
index a7bf615c7a..bfcc78a7f9 100644
--- a/libs/image/tests/CMakeLists.txt
+++ b/libs/image/tests/CMakeLists.txt
@@ -1,157 +1,157 @@
# cmake in some versions for some not yet known reasons fails to run automoc
# on random targets (changing target names already has an effect)
# As temporary workaround skipping build of tests on these versions for now
# See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html
# extend range of affected cmake versions as needed
if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND
NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3)
message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)")
set (HAVE_FAILING_CMAKE TRUE)
else()
set (HAVE_FAILING_CMAKE FALSE)
endif()
include_directories(
${CMAKE_BINARY_DIR}/libs/image/
${CMAKE_SOURCE_DIR}/libs/image/
${CMAKE_SOURCE_DIR}/libs/image/brushengine
${CMAKE_SOURCE_DIR}/libs/image/tiles3
${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap
${CMAKE_SOURCE_DIR}/sdk/tests
)
include_Directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
)
if(HAVE_VC)
include_directories(${Vc_INCLUDE_DIR})
endif()
include(ECMAddTests)
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp)
ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui)
add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources})
target_link_libraries(KisRandomGeneratorDemo kritaimage)
ecm_mark_as_test(KisRandomGeneratorDemo)
ecm_add_tests(
kis_base_node_test.cpp
kis_fast_math_test.cpp
kis_node_test.cpp
kis_node_facade_test.cpp
kis_fixed_paint_device_test.cpp
kis_layer_test.cpp
kis_effect_mask_test.cpp
kis_iterator_test.cpp
kis_painter_test.cpp
kis_selection_test.cpp
kis_count_visitor_test.cpp
kis_projection_test.cpp
kis_properties_configuration_test.cpp
kis_transaction_test.cpp
kis_pixel_selection_test.cpp
kis_group_layer_test.cpp
kis_paint_layer_test.cpp
kis_adjustment_layer_test.cpp
kis_annotation_test.cpp
kis_change_profile_visitor_test.cpp
kis_clone_layer_test.cpp
kis_colorspace_convert_visitor_test.cpp
kis_convolution_painter_test.cpp
kis_crop_processing_visitor_test.cpp
kis_processing_applicator_test.cpp
kis_datamanager_test.cpp
kis_fill_painter_test.cpp
kis_filter_configuration_test.cpp
kis_filter_test.cpp
kis_filter_processing_information_test.cpp
kis_filter_registry_test.cpp
kis_filter_strategy_test.cpp
kis_gradient_painter_test.cpp
kis_image_commands_test.cpp
kis_image_test.cpp
kis_image_signal_router_test.cpp
kis_iterators_ng_test.cpp
kis_iterator_benchmark.cpp
kis_updater_context_test.cpp
kis_simple_update_queue_test.cpp
kis_stroke_test.cpp
kis_simple_stroke_strategy_test.cpp
kis_stroke_strategy_undo_command_based_test.cpp
kis_strokes_queue_test.cpp
kis_mask_test.cpp
kis_math_toolbox_test.cpp
kis_name_server_test.cpp
kis_node_commands_test.cpp
kis_node_graph_listener_test.cpp
kis_node_visitor_test.cpp
kis_paint_information_test.cpp
kis_distance_information_test.cpp
kis_paintop_test.cpp
kis_pattern_test.cpp
kis_selection_mask_test.cpp
kis_shared_ptr_test.cpp
kis_bsplines_test.cpp
kis_warp_transform_worker_test.cpp
kis_liquify_transform_worker_test.cpp
kis_transparency_mask_test.cpp
kis_types_test.cpp
kis_vec_test.cpp
kis_filter_config_widget_test.cpp
kis_mask_generator_test.cpp
kis_cubic_curve_test.cpp
kis_fixed_point_maths_test.cpp
kis_node_query_path_test.cpp
kis_filter_weights_buffer_test.cpp
kis_filter_weights_applicator_test.cpp
kis_fill_interval_test.cpp
kis_fill_interval_map_test.cpp
kis_scanline_fill_test.cpp
kis_psd_layer_style_test.cpp
kis_layer_style_projection_plane_test.cpp
kis_lod_capable_layer_offset_test.cpp
kis_algebra_2d_test.cpp
kis_marker_painter_test.cpp
kis_lazy_brush_test.cpp
kis_colorize_mask_test.cpp
kis_mask_similarity_test.cpp
- KisMaskGeneratorBenchmark.cpp
+ KisMaskGeneratorTest.cpp
kis_layer_style_filter_environment_test.cpp
kis_asl_parser_test.cpp
KisPerStrokeRandomSourceTest.cpp
KisWatershedWorkerTest.cpp
kis_dom_utils_test.cpp
kis_transform_worker_test.cpp
kis_perspective_transform_worker_test.cpp
kis_cs_conversion_test.cpp
kis_processings_test.cpp
kis_projection_leaf_test.cpp
kis_histogram_test.cpp
kis_onion_skin_compositor_test.cpp
kis_paint_device_test.cpp
kis_queues_progress_updater_test.cpp
kis_image_animation_interface_test.cpp
kis_walkers_test.cpp
kis_async_merger_test.cpp
kis_cage_transform_worker_test.cpp
kis_random_generator_test.cpp
kis_keyframing_test.cpp
kis_filter_mask_test.cpp
LINK_LIBRARIES kritaimage Qt5::Test
NAME_PREFIX "libs-image-"
)
krita_add_broken_unit_tests(
kis_transform_mask_test.cpp
kis_layer_styles_test.cpp
kis_update_scheduler_test.cpp
LINK_LIBRARIES kritaimage Qt5::Test
NAME_PREFIX "libs-image-"
)
diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.cpp b/libs/image/tests/KisMaskGeneratorTest.cpp
similarity index 71%
rename from libs/image/tests/KisMaskGeneratorBenchmark.cpp
rename to libs/image/tests/KisMaskGeneratorTest.cpp
index a5dcdb1016..6b4fa1c070 100644
--- a/libs/image/tests/KisMaskGeneratorBenchmark.cpp
+++ b/libs/image/tests/KisMaskGeneratorTest.cpp
@@ -1,202 +1,202 @@
/*
* Copyright (c) 2018 Iván Santa María <ghevan@gmail.com>
*
* 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 <QTest>
#include <QPointF>
#include <KoColor.h>
#include <QElapsedTimer>
-#include "KisMaskGeneratorBenchmark.h"
+#include "KisMaskGeneratorTest.h"
#include "kis_mask_similarity_test.h"
#include "kis_brush_mask_applicator_base.h"
#include "kis_mask_generator.h"
#include "kis_cubic_curve.h"
#include "krita_utils.h"
#include "testutil.h"
-class KisMaskGeneratorBenchmarkTester
+class KisMaskGeneratorTestTester
{
public:
- KisMaskGeneratorBenchmarkTester(KisBrushMaskApplicatorBase *_applicatorBase, QRect _bounds)
+ KisMaskGeneratorTestTester(KisBrushMaskApplicatorBase *_applicatorBase, QRect _bounds)
: m_bounds(_bounds),
applicatorBase(_applicatorBase)
{
KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace);
m_paintDev->setRect(m_bounds);
m_paintDev->initialize(255);
MaskProcessingData data(m_paintDev, m_colorSpace,
0.0, 1.0,
m_bounds.width() / 2.0, m_bounds.height() / 2.0,0);
// Start Benchmark
applicatorBase->initializeData(&data);
QElapsedTimer maskGenerationTime;
maskGenerationTime.start();
QBENCHMARK {
applicatorBase->process(m_bounds);
}
}
protected:
const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8();
QRect m_bounds;
KisBrushMaskApplicatorBase *applicatorBase;
KisFixedPaintDeviceSP m_paintDev;
};
-void KisMaskGeneratorBenchmark::testDefaultScalarMask()
+void KisMaskGeneratorTest::testDefaultScalarMask()
{
QRect bounds(0,0,1000,1000);
{
KisCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true);
circScalar.resetMaskApplicator(true); // Force usage of scalar backend
- KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds);
+ KisMaskGeneratorTestTester(circScalar.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testDefaultVectorMask()
+void KisMaskGeneratorTest::testDefaultVectorMask()
{
QRect bounds(0,0,1000,1000);
{
KisCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true);
- KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds);
+ KisMaskGeneratorTestTester(circVectr.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testCircularGaussScalarMask()
+void KisMaskGeneratorTest::testCircularGaussScalarMask()
{
QRect bounds(0,0,1000,1000);
{
KisGaussCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true);
circScalar.setDiameter(1000);
circScalar.resetMaskApplicator(true); // Force usage of scalar backend
- KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds);
+ KisMaskGeneratorTestTester(circScalar.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testCircularGaussVectorMask()
+void KisMaskGeneratorTest::testCircularGaussVectorMask()
{
QRect bounds(0,0,1000,1000);
{
KisGaussCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true);
circVectr.setDiameter(1000);
- KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds);
+ KisMaskGeneratorTestTester(circVectr.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testCircularSoftScalarMask()
+void KisMaskGeneratorTest::testCircularSoftScalarMask()
{
QRect bounds(0,0,1000,1000);
KisCubicCurve pointsCurve;
pointsCurve.fromString(QString("0,1;1,0"));
{
KisCurveCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true);
circScalar.setSoftness(0.5);
circScalar.resetMaskApplicator(true); // Force usage of scalar backend
- KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds);
+ KisMaskGeneratorTestTester(circScalar.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testCircularSoftVectorMask()
+void KisMaskGeneratorTest::testCircularSoftVectorMask()
{
QRect bounds(0,0,1000,1000);
KisCubicCurve pointsCurve;
pointsCurve.fromString(QString("0,1;1,0"));
{
KisCurveCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true);
circVectr.setSoftness(0.5);
- KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds);
+ KisMaskGeneratorTestTester(circVectr.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testRectangularScalarMask(){
+void KisMaskGeneratorTest::testRectangularScalarMask(){
QRect bounds(0,0,1000,1000);
{
KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true);
rectScalar.resetMaskApplicator(true); // Force usage of scalar backend
- KisMaskGeneratorBenchmarkTester(rectScalar.applicator(), bounds);
+ KisMaskGeneratorTestTester(rectScalar.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testRectangularVectorMask(){
+void KisMaskGeneratorTest::testRectangularVectorMask(){
QRect bounds(0,0,1000,1000);
{
KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true);
- KisMaskGeneratorBenchmarkTester(rectScalar.applicator(), bounds);
+ KisMaskGeneratorTestTester(rectScalar.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testRectangularGaussScalarMask()
+void KisMaskGeneratorTest::testRectangularGaussScalarMask()
{
QRect bounds(0,0,1000,1000);
{
KisGaussRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true);
// circScalar.setDiameter(1000);
circScalar.resetMaskApplicator(true); // Force usage of scalar backend
- KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds);
+ KisMaskGeneratorTestTester(circScalar.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testRectangularGaussVectorMask()
+void KisMaskGeneratorTest::testRectangularGaussVectorMask()
{
QRect bounds(0,0,1000,1000);
{
KisGaussRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true);
// circVectr.setDiameter(1000);
- KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds);
+ KisMaskGeneratorTestTester(circVectr.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testRectangularSoftScalarMask()
+void KisMaskGeneratorTest::testRectangularSoftScalarMask()
{
QRect bounds(0,0,1000,1000);
KisCubicCurve pointsCurve;
pointsCurve.fromString(QString("0,1;1,0"));
{
KisCurveRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true);
circScalar.resetMaskApplicator(true); // Force usage of scalar backend
- KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds);
+ KisMaskGeneratorTestTester(circScalar.applicator(), bounds);
}
}
-void KisMaskGeneratorBenchmark::testRectangularSoftVectorMask()
+void KisMaskGeneratorTest::testRectangularSoftVectorMask()
{
QRect bounds(0,0,1000,1000);
KisCubicCurve pointsCurve;
pointsCurve.fromString(QString("0,1;1,0"));
{
KisCurveRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true);
- KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds);
+ KisMaskGeneratorTestTester(circVectr.applicator(), bounds);
}
}
-QTEST_MAIN(KisMaskGeneratorBenchmark)
+QTEST_MAIN(KisMaskGeneratorTest)
diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.h b/libs/image/tests/KisMaskGeneratorTest.h
similarity index 96%
rename from libs/image/tests/KisMaskGeneratorBenchmark.h
rename to libs/image/tests/KisMaskGeneratorTest.h
index 5c4b59a36d..e4135e8086 100644
--- a/libs/image/tests/KisMaskGeneratorBenchmark.h
+++ b/libs/image/tests/KisMaskGeneratorTest.h
@@ -1,48 +1,48 @@
/*
* Copyright (c) 2018 Iván Santa María <ghevan@gmail.com>
*
* 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 KISMASKGENERATORBENCHMARK_H
#define KISMASKGENERATORBENCHMARK_H
#include <QtTest>
-class KisMaskGeneratorBenchmark : public QObject
+class KisMaskGeneratorTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testDefaultScalarMask();
void testDefaultVectorMask();
void testCircularGaussScalarMask();
void testCircularGaussVectorMask();
void testCircularSoftScalarMask();
void testCircularSoftVectorMask();
void testRectangularScalarMask();
void testRectangularVectorMask();
void testRectangularGaussScalarMask();
void testRectangularGaussVectorMask();
void testRectangularSoftScalarMask();
void testRectangularSoftVectorMask();
};
#endif // KISMASKGENERATORBENCHMARK_H
diff --git a/libs/image/tests/kis_filter_weights_applicator_test.cpp b/libs/image/tests/kis_filter_weights_applicator_test.cpp
index 75f6025460..90f2ae768b 100644
--- a/libs/image/tests/kis_filter_weights_applicator_test.cpp
+++ b/libs/image/tests/kis_filter_weights_applicator_test.cpp
@@ -1,648 +1,769 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_filter_weights_applicator_test.h"
#include <QTest>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include "kis_paint_device.h"
+#include <sstream>
+
//#define DEBUG_ENABLED
#include "kis_filter_weights_applicator.h"
void debugSpan(const KisFilterWeightsApplicator::BlendSpan &span)
{
dbgKrita << ppVar(span.weights->centerIndex);
for (int i = 0; i < span.weights->span; i++) {
dbgKrita << "Weights" << i << span.weights->weight[i];
}
dbgKrita << ppVar(span.firstBlendPixel);
dbgKrita << ppVar(span.offset);
dbgKrita << ppVar(span.offsetInc);
}
void testSpan(qreal scale, qreal dx, int dst_l,
int expectedFirstPixel,
qreal expectedOffset,
qreal expectedOffsetInc)
{
KisFilterStrategy *filter = new KisBilinearFilterStrategy();
KisFilterWeightsBuffer buf(filter, qAbs(scale));
KisFilterWeightsApplicator applicator(0, 0, scale, 0.0, dx, false);
KisFilterWeightsApplicator::BlendSpan span;
span = applicator.calculateBlendSpan(dst_l, 0, &buf);
//debugSpan(span);
if (span.firstBlendPixel != expectedFirstPixel ||
span.offset != KisFixedPoint(expectedOffset) ||
span.offsetInc != KisFixedPoint(expectedOffsetInc)) {
dbgKrita << "Failed to generate a span:";
dbgKrita << ppVar(scale) << ppVar(dx) << ppVar(dst_l);
dbgKrita << ppVar(span.firstBlendPixel) << ppVar(expectedFirstPixel);
dbgKrita << ppVar(span.offset) << ppVar(KisFixedPoint(expectedOffset));
dbgKrita << ppVar(span.offsetInc) << ppVar(KisFixedPoint(expectedOffsetInc));
QFAIL("fail");
}
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_2_0_Aligned()
{
testSpan(2.0, 0.0, 0, -1, 0.25, 1.0);
testSpan(2.0, 0.0, 1, 0, 0.75, 1.0);
testSpan(2.0, 0.0, -1, -1, 0.75, 1.0);
testSpan(2.0, 0.0, -2, -2, 0.25, 1.0);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_2_0_Shift_0_5()
{
testSpan(2.0, 0.5, 0, -1, 0.5, 1.0);
testSpan(2.0, 0.5, 1, -1, 0.0, 1.0);
testSpan(2.0, 0.5, -1, -2, 0.0, 1.0);
testSpan(2.0, 0.5, -2, -2, 0.5, 1.0);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_2_0_Shift_0_75()
{
testSpan(2.0, 0.75, 0, -1, 0.625, 1.0);
testSpan(2.0, 0.75, 1, -1, 0.125, 1.0);
testSpan(2.0, 0.75, -1, -2, 0.125, 1.0);
testSpan(2.0, 0.75, -2, -2, 0.625, 1.0);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Aligned()
{
testSpan(0.5, 0.0, 0, -1, 0.25, 0.5);
testSpan(0.5, 0.0, 1, 1, 0.25, 0.5);
testSpan(0.5, 0.0, -1, -3, 0.25, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_5()
{
testSpan(0.5, 0.5, 0, -2, 0.25, 0.5);
testSpan(0.5, 0.5, 1, 0, 0.25, 0.5);
testSpan(0.5, 0.5, -1, -4, 0.25, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_25()
{
testSpan(0.5, 0.25, 0, -2, 0.0, 0.5);
testSpan(0.5, 0.25, 1, 0, 0.0, 0.5);
testSpan(0.5, 0.25, -1, -4, 0.0, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_375()
{
testSpan(0.5, 0.375, 0, -2, 0.125, 0.5);
testSpan(0.5, 0.375, 1, 0, 0.125, 0.5);
testSpan(0.5, 0.375, -1, -4, 0.125, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_m0_5()
{
testSpan(0.5, -0.5, 0, 0, 0.25, 0.5);
testSpan(0.5, -0.5, 1, 2, 0.25, 0.5);
testSpan(0.5, -0.5, -1, -2, 0.25, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_m0_25()
{
testSpan(0.5, -0.25, 0, -1, 0.0, 0.5);
testSpan(0.5, -0.25, 1, 1, 0.0, 0.5);
testSpan(0.5, -0.25, -1, -3, 0.0, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_m0_375()
{
testSpan(0.5, -0.375, 0, 0, 0.375, 0.5);
testSpan(0.5, -0.375, 1, 2, 0.375, 0.5);
testSpan(0.5, -0.375, -1, -2, 0.375, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_1_0_Aligned_Mirrored()
{
testSpan(-1.0, 0.0, 0, -2, 0.0, 1.0);
testSpan(-1.0, 0.0, 1, -3, 0.0, 1.0);
testSpan(-1.0, 0.0, -1, -1, 0.0, 1.0);
testSpan(-1.0, 0.0, -2, 0, 0.0, 1.0);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Aligned_Mirrored()
{
testSpan(-0.5, 0.0, 0, -3, 0.25, 0.5);
testSpan(-0.5, 0.0, 1, -5, 0.25, 0.5);
testSpan(-0.5, 0.0, -1, -1, 0.25, 0.5);
testSpan(-0.5, 0.0, -2, 1, 0.25, 0.5);
}
void KisFilterWeightsApplicatorTest::testSpan_Scale_0_5_Shift_0_125_Mirrored()
{
testSpan(-0.5, 0.125, 0, -3, 0.125, 0.5);
testSpan(-0.5, 0.125, 1, -5, 0.125, 0.5);
testSpan(-0.5, 0.125, -1, -1, 0.125, 0.5);
testSpan(-0.5, 0.125, -2, 1, 0.125, 0.5);
}
-void printPixels(KisPaintDeviceSP dev, int x0, int len, bool horizontal)
+void printPixels(KisPaintDeviceSP dev, int x0, int len, bool horizontal, bool dense = false)
{
+ std::stringstream ss;
for (int i = x0; i < x0 + len; i++) {
QColor c;
int x = horizontal ? i : 0;
int y = horizontal ? 0 : i;
dev->pixel(x, y, &c);
- dbgKrita << "px" << x << y << "|" << c.red() << c.green() << c.blue() << c.alpha();
+ if (dense){
+ ss << c.red() << " , " << c.alpha() << " | ";
+ } else {
+ dbgKrita << "px" << x << y << "|" << c.red() << c.green() << c.blue() << c.alpha();
+ }
+ }
+
+ if (dense) {
+ qDebug() << ss.str().c_str();
}
}
void checkRA(KisPaintDeviceSP dev, int x0, int len, quint8 r[], quint8 a[], bool horizontal)
{
+ bool failed = false;
for (int i = 0; i < len; i++) {
QColor c;
int x = horizontal ? x0 + i : 0;
int y = horizontal ? 0 : x0 + i;
dev->pixel(x, y, &c);
if (c.red() != r[i] ||
c.alpha() != a[i]) {
dbgKrita << "Failed to compare RA channels:" << ppVar(x0 + i);
dbgKrita << "Red:" << c.red() << "Expected:" << r[i];
dbgKrita << "Alpha:" << c.alpha() << "Expected:" << a[i];
- QFAIL("failed");
+ failed = true;
}
}
+
+ if (failed) {
+ QFAIL("failed");
+ }
}
void testLineImpl(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge, bool horizontal, KisFilterStrategy *filter = 0, KisPaintDeviceSP dev = 0)
{
int startPos = 0;
int endPos = 4;
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
if (!filter) {
filter = new KisBilinearFilterStrategy();
}
if (!dev) {
dev = new KisPaintDevice(cs);
for (int i = 0; i < 4; i++) {
int x = horizontal ? i : 0;
int y = horizontal ? 0 : i;
dev->setPixel(x,y,QColor(10 + i * 10, 20 + i * 10, 40 + i * 10));
}
{
quint8 r[] = { 0, 10, 20, 30, 40, 0, 0};
quint8 a[] = { 0,255,255,255,255, 0, 0};
checkRA(dev, -1, 6, r, a, horizontal);
}
startPos = 0;
endPos = 4;
} else {
QRect rc = dev->exactBounds();
if (horizontal) {
startPos = rc.left();
endPos = rc.left() + rc.width();
} else {
startPos = rc.top();
endPos = rc.top() + rc.height();
}
}
KisFilterWeightsBuffer buf(filter, qAbs(scale));
KisFilterWeightsApplicator applicator(dev, dev, scale, 0.0, dx, clampToEdge);
KisFilterWeightsApplicator::LinePos srcPos(startPos, endPos);
KisFilterWeightsApplicator::LinePos dstPos;
if (horizontal) {
dstPos = applicator.processLine<KisHLineIteratorSP>(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat()));
} else {
dstPos = applicator.processLine<KisVLineIteratorSP>(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat()));
}
QRect rc = dev->exactBounds();
if (horizontal) {
QVERIFY(rc.left() >= dstPos.start());
QVERIFY(rc.left() + rc.width() <= dstPos.end());
} else {
QVERIFY(rc.top() >= dstPos.start());
QVERIFY(rc.top() + rc.height() <= dstPos.end());
}
- //printPixels(dev, x0, len, horizontal);
+ //printPixels(dev, x0, len, horizontal, true);
checkRA(dev, x0, len, expR, expA, horizontal);
}
void testLine(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge = false, KisFilterStrategy* filter = 0, KisPaintDeviceSP dev = 0)
{
testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, true, filter, dev);
testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, false, filter, dev);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Aligned()
{
qreal scale = 1.0;
qreal dx = 0.0;
quint8 r[] = { 0, 10, 20, 30, 40, 0, 0};
quint8 a[] = { 0,255,255,255,255, 0, 0};
testLine(scale, dx, r, a, -1, 7);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_0_5()
{
qreal scale = 1.0;
qreal dx = 0.5;
quint8 r[] = { 0, 10, 15, 25, 35, 40, 0};
quint8 a[] = { 0,128,255,255,255,127, 0};
testLine(scale, dx, r, a, -1, 7);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_m0_5()
{
qreal scale = 1.0;
qreal dx = -0.5;
quint8 r[] = { 10, 15, 25, 35, 40, 0, 0};
quint8 a[] = {128,255,255,255,127, 0, 0};
testLine(scale, dx, r, a, -1, 7);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_0_25()
{
qreal scale = 1.0;
qreal dx = 0.25;
quint8 r[] = { 0, 10, 17, 27, 37, 40, 0};
quint8 a[] = { 0,191,255,255,255, 64, 0};
testLine(scale, dx, r, a, -1, 7);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_m0_25()
{
qreal scale = 1.0;
qreal dx = -0.25;
quint8 r[] = { 10, 12, 22, 32, 40, 0, 0};
quint8 a[] = { 64,255,255,255,191, 0, 0};
testLine(scale, dx, r, a, -1, 7);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Aligned()
{
qreal scale = 0.5;
qreal dx = 0.0;
quint8 r[] = { 10, 17, 32, 40, 0, 0, 0};
quint8 a[] = { 32,223,223, 32, 0, 0, 0};
testLine(scale, dx, r, a, -1, 7);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Shift_0_25()
{
qreal scale = 0.5;
qreal dx = 0.25;
quint8 r[] = { 0, 13, 30, 40, 0, 0, 0};
quint8 a[] = { 0,191,255, 64, 0, 0, 0};
testLine(scale, dx, r, a, -1, 7);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Aligned()
{
qreal scale = 2.0;
qreal dx = 0.0;
quint8 r[] = { 0, 10, 10, 12, 17, 22, 27, 32, 37, 40, 40, 0};
quint8 a[] = { 0, 64,191,255,255,255,255,255,255,191, 64, 0};
testLine(scale, dx, r, a, -2, 12);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Shift_0_25()
{
qreal scale = 2.0;
qreal dx = 0.25;
quint8 r[] = { 0, 10, 10, 11, 16, 21, 26, 31, 36, 40, 40, 0};
quint8 a[] = { 0, 32,159,255,255,255,255,255,255,223, 96, 0};
testLine(scale, dx, r, a, -2, 12);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Shift_0_5()
{
qreal scale = 2.0;
qreal dx = 0.5;
quint8 r[] = { 0, 0, 10, 10, 15, 20, 25, 30, 35, 40, 40, 0};
quint8 a[] = { 0, 0,128,255,255,255,255,255,255,255,127, 0};
testLine(scale, dx, r, a, -2, 12);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Aligned_Clamped()
{
qreal scale = 1.0;
qreal dx = 0.0;
quint8 r[] = { 0, 10, 20, 30, 40, 0, 0};
quint8 a[] = { 0,255,255,255,255, 0, 0};
testLine(scale, dx, r, a, -1, 7, true);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Aligned_Clamped()
{
qreal scale = 0.5;
qreal dx = 0.0;
quint8 r[] = { 0, 16, 33, 0, 0, 0, 0};
quint8 a[] = { 0,255,255, 0, 0, 0, 0};
testLine(scale, dx, r, a, -1, 7, true);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_2_0_Aligned_Clamped()
{
qreal scale = 2.0;
qreal dx = 0.0;
quint8 r[] = { 0, 0, 10, 12, 17, 22, 27, 32, 37, 40, 0, 0};
quint8 a[] = { 0, 0,255,255,255,255,255,255,255,255, 0, 0};
testLine(scale, dx, r, a, -2, 12, true);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Aligned_Mirrored()
{
qreal scale = -1.0;
qreal dx = 0.0;
quint8 r[] = { 0, 0, 40, 30, 20, 10, 0, 0, 0, 0};
quint8 a[] = { 0, 0,255,255,255,255, 0, 0, 0, 0};
testLine(scale, dx, r, a, -6, 10);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Shift_0_25_Mirrored()
{
qreal scale = -1.0;
qreal dx = 0.25;
quint8 r[] = { 0, 0, 40, 32, 22, 12, 10, 0, 0, 0};
quint8 a[] = { 0, 0,191,255,255,255, 64, 0, 0, 0};
testLine(scale, dx, r, a, -6, 10);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Aligned_Mirrored_Clamped()
{
qreal scale = -0.5;
qreal dx = 0.0;
quint8 r[] = { 0, 0, 0, 0, 33, 16, 0, 0, 0, 0};
quint8 a[] = { 0, 0, 0, 0,255,255, 0, 0, 0, 0};
testLine(scale, dx, r, a, -6, 10, true);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Shift_0_125_Mirrored()
{
qreal scale = -0.5;
qreal dx = 0.125;
quint8 r[] = { 0, 0, 0, 40, 34, 18, 10, 0, 0, 0};
quint8 a[] = { 0, 0, 0, 16,207,239, 48, 0, 0, 0};
testLine(scale, dx, r, a, -6, 10);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_2x()
{
qreal scale = 2.0;
qreal dx = 0;
quint8 r[] = {0, 10, 10, 20, 20, 30, 30, 40, 40, 0, 0};
quint8 a[] = {0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 11, true, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_1x()
{
qreal scale = 1.0;
qreal dx = 0;
quint8 r[] = { 0, 10, 20, 30, 40, 0, 0};
quint8 a[] = { 0,255,255,255,255, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_05x()
{
qreal scale = 0.5;
qreal dx = 0;
quint8 r[] = { 0, 10, 30, 0, 0, 0, 0};
quint8 a[] = { 0,255,255, 0, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_077x()
{
qreal scale = 0.77;
qreal dx = 0;
quint8 r[] = { 0, 10, 20, 40, 0, 0, 0};
quint8 a[] = { 0,255,255, 255, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_074x()
{
qreal scale = 0.74;
qreal dx = 0;
quint8 r[] = { 0, 10, 30, 40, 0, 0, 0};
quint8 a[] = { 0,255,255, 255, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_075x()
{
qreal scale = 0.75;
qreal dx = 0;
quint8 r[] = { 0, 10, 20, 40, 0, 0, 0};
quint8 a[] = { 0,255,255, 255, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
+void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_051x()
+{
+
+ qreal scale = 0.51;
+ qreal dx = 0;
+
+ quint8 r[] = { 0, 10, 30, 0, 0, 0, 0};
+ quint8 a[] = { 0,255,255, 0, 0, 0, 0};
+
+ KisFilterStrategy* filter = new KisBoxFilterStrategy();
+ testLine(scale, dx, r, a, -1, 7, false, filter);
+}
+
+
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_15x()
{
qreal scale = 1.5;
qreal dx = 0;
quint8 r[] = { 0, 10, 10, 20, 30, 30, 40};
quint8 a[] = { 0,255,255, 255, 255, 255, 255};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
+void preparePixelData(quint8* r, quint8* a, int i)
+{
+ for (int j = 0; j < 7; j ++) {
+ r[j] = 0;
+ a[j] = 0;
+ }
+
+ if (i < 13) {
+ // nothing to do
+ } else if (i < 17) {
+ r[1] = 40;
+ a[1] = 255;
+ } else if (i < 25) {
+ r[1] = 30;
+ a[1] = 255;
+ } else if (i < 38) {
+ r[1] = 20;
+ a[1] = 255;
+ } else if (i < 50) {
+ r[1] = 20;
+ r[2] = 40;
+ a[1] = 255;
+ a[2] = 255;
+ } else if (i < 63) {
+ r[1] = 10;
+ r[2] = 30;
+ a[1] = 255;
+ a[2] = 255;
+ } else if (i < 75) {
+ r[1] = 10;
+ r[2] = 30;
+ r[3] = 40;
+
+ a[1] = 255;
+ a[2] = 255;
+ a[3] = 255;
+
+ } else if (i < 84) {
+ r[1] = 10;
+ r[2] = 20;
+ r[3] = 40;
+
+ a[1] = 255;
+ a[2] = 255;
+ a[3] = 255;
+
+ } else if (i < 88) {
+ r[1] = 10;
+ r[2] = 20;
+ r[3] = 30;
+
+ a[1] = 255;
+ a[2] = 255;
+ a[3] = 255;
+ } else {
+
+ r[1] = 10;
+ r[2] = 20;
+ r[3] = 30;
+ r[4] = 40;
+
+ a[1] = 255;
+ a[2] = 255;
+ a[3] = 255;
+ a[4] = 255;
+ }
+
+}
+
+
+void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_all()
+{
+
+ KisFilterStrategy* filter = new KisBoxFilterStrategy();
+
+ for (int i = 1; i < 100; i++) {
+
+ qreal scale = i/100.0;
+ qreal dx = 0;
+
+ quint8 r[7];
+ quint8 a[7];
+
+ preparePixelData(r, a, i);
+ testLine(scale, dx, r, a, -1, 7, false, filter);
+
+ }
+}
+
+
+
KisPaintDeviceSP prepareUniformPaintDevice(int pixelsNumber, bool horizontal)
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
for (int i = 0; i < pixelsNumber; i++) {
int x = horizontal ? i : 0;
int y = horizontal ? 0 : i;
QColor c = QColor(10, 0, 0, 255);
dev->setPixel(x, y, c);
}
return dev;
}
void prepareUniformPixels(quint8 r[], quint8 a[], int pixelsNumber, bool /*horizontal*/)
{
for (int i = 0; i < pixelsNumber; i++) {
QColor c = QColor(10, 0, 0, 255);
r[i] = c.red();
a[i] = c.alpha();
}
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_0098x_horizontal()
{
int before = 5075;
int after = 500;
qreal scale = before/after;
qreal dx = 0;
bool horizontal = true;
KisPaintDeviceSP dev = prepareUniformPaintDevice(before, horizontal);
quint8 *r = new quint8[after];
quint8 *a = new quint8[after];
prepareUniformPixels(r, a, after, horizontal);
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLineImpl(scale, dx, r, a, 0, after, false, horizontal, filter, dev);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_0098x_vertical()
{
int before = 4725;
int after = 466;
qreal scale = before/after;
qreal dx = 0;
bool horizontal = false;
KisPaintDeviceSP dev = prepareUniformPaintDevice(before, horizontal);
quint8 *r = new quint8[after];
quint8 *a = new quint8[after];
prepareUniformPixels(r, a, after, horizontal);
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLineImpl(scale, dx, r, a, 0, after, false, horizontal, filter, dev);
}
void KisFilterWeightsApplicatorTest::benchmarkProcesssLine()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
KisFilterStrategy *filter = new KisBilinearFilterStrategy();
const qreal scale = 0.873;
const qreal dx = 0.0387;
KisFilterWeightsBuffer buf(filter, qAbs(scale));
KisFilterWeightsApplicator applicator(dev, dev, scale, 0.0, dx, false);
for (int i = 0; i < 32767; i++) {
dev->setPixel(i,0,QColor(10 + i%240,20,40));
}
KisFilterWeightsApplicator::LinePos linePos(0,32767);
QBENCHMARK {
applicator.processLine<KisHLineIteratorSP>(linePos,0,&buf, filter->support(buf.weightsPositionScale().toFloat()));
}
}
QTEST_MAIN(KisFilterWeightsApplicatorTest)
diff --git a/libs/image/tests/kis_filter_weights_applicator_test.h b/libs/image/tests/kis_filter_weights_applicator_test.h
index ef72303542..2b4fa66583 100644
--- a/libs/image/tests/kis_filter_weights_applicator_test.h
+++ b/libs/image/tests/kis_filter_weights_applicator_test.h
@@ -1,80 +1,82 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_FILTER_WEIGHTS_APPLICATOR_TEST_H
#define __KIS_FILTER_WEIGHTS_APPLICATOR_TEST_H
#include <QtTest>
class KisFilterWeightsApplicatorTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testSpan_Scale_2_0_Aligned();
void testSpan_Scale_2_0_Shift_0_5();
void testSpan_Scale_2_0_Shift_0_75();
void testSpan_Scale_0_5_Aligned();
void testSpan_Scale_0_5_Shift_0_5();
void testSpan_Scale_0_5_Shift_0_25();
void testSpan_Scale_0_5_Shift_0_375();
void testSpan_Scale_0_5_Shift_m0_5();
void testSpan_Scale_0_5_Shift_m0_25();
void testSpan_Scale_0_5_Shift_m0_375();
void testSpan_Scale_1_0_Aligned_Mirrored();
void testSpan_Scale_0_5_Aligned_Mirrored();
void testSpan_Scale_0_5_Shift_0_125_Mirrored();
void testProcessLine_Scale_1_0_Aligned();
void testProcessLine_Scale_1_0_Shift_0_5();
void testProcessLine_Scale_1_0_Shift_m0_5();
void testProcessLine_Scale_1_0_Shift_0_25();
void testProcessLine_Scale_1_0_Shift_m0_25();
void testProcessLine_Scale_0_5_Aligned();
void testProcessLine_Scale_0_5_Shift_0_25();
void testProcessLine_Scale_2_0_Aligned();
void testProcessLine_Scale_2_0_Shift_0_25();
void testProcessLine_Scale_2_0_Shift_0_5();
void testProcessLine_Scale_1_0_Aligned_Clamped();
void testProcessLine_Scale_0_5_Aligned_Clamped();
void testProcessLine_Scale_2_0_Aligned_Clamped();
void testProcessLine_Scale_1_0_Aligned_Mirrored();
void testProcessLine_Scale_1_0_Shift_0_25_Mirrored();
void testProcessLine_Scale_0_5_Aligned_Mirrored_Clamped();
void testProcessLine_Scale_0_5_Shift_0_125_Mirrored();
void testProcessLine_NearestNeighbourFilter_2x();
void testProcessLine_NearestNeighbourFilter_1x();
void testProcessLine_NearestNeighbourFilter_05x();
void testProcessLine_NearestNeighbourFilter_077x();
void testProcessLine_NearestNeighbourFilter_074x();
void testProcessLine_NearestNeighbourFilter_075x();
+ void testProcessLine_NearestNeighbourFilter_051x();
void testProcessLine_NearestNeighbourFilter_15x();
+ void testProcessLine_NearestNeighbourFilter_all();
void testProcessLine_NearestNeighbourFilter_0098x_horizontal();
void testProcessLine_NearestNeighbourFilter_0098x_vertical();
void benchmarkProcesssLine();
};
#endif /* __KIS_FILTER_WEIGHTS_APPLICATOR_TEST_H */
diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp
index 5563ab15d7..9bc1c567b2 100644
--- a/libs/libkis/Document.cpp
+++ b/libs/libkis/Document.cpp
@@ -1,993 +1,994 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "Document.h"
#include <QPointer>
#include <QUrl>
#include <QDomDocument>
#include <KoColorSpaceConstants.h>
#include <KoXmlReader.h>
#include <KisDocument.h>
#include <kis_colorspace_convert_visitor.h>
#include <kis_image.h>
#include <KisPart.h>
#include <kis_paint_device.h>
#include <KisMainWindow.h>
#include <kis_node_manager.h>
#include <kis_node_selection_adapter.h>
#include <KisViewManager.h>
#include <kis_file_layer.h>
#include <kis_adjustment_layer.h>
#include <kis_mask.h>
#include <kis_clone_layer.h>
#include <kis_group_layer.h>
#include <kis_filter_mask.h>
#include <kis_transform_mask.h>
#include <kis_transparency_mask.h>
#include <kis_selection_mask.h>
#include <kis_effect_mask.h>
#include <kis_paint_layer.h>
#include <kis_generator_layer.h>
#include <kis_generator_registry.h>
#include <kis_shape_layer.h>
#include <kis_filter_configuration.h>
#include <kis_filter_registry.h>
#include <kis_selection.h>
#include <KisMimeDatabase.h>
#include <kis_filter_strategy.h>
#include <kis_guides_config.h>
#include <kis_coordinates_converter.h>
#include <kis_time_range.h>
+#include <KisImportExportErrorCode.h>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorConversionTransformation.h>
#include <KoDocumentInfo.h>
#include <InfoObject.h>
#include <Node.h>
#include <Selection.h>
#include <LibKisUtils.h>
#include "kis_animation_importer.h"
#include <kis_canvas2.h>
#include <KoUpdater.h>
#include <QMessageBox>
#include <kis_image_animation_interface.h>
struct Document::Private {
Private() {}
QPointer<KisDocument> document;
};
Document::Document(KisDocument *document, QObject *parent)
: QObject(parent)
, d(new Private)
{
d->document = document;
}
Document::~Document()
{
delete d;
}
bool Document::operator==(const Document &other) const
{
return (d->document == other.d->document);
}
bool Document::operator!=(const Document &other) const
{
return !(operator==(other));
}
bool Document::batchmode() const
{
if (!d->document) return false;
return d->document->fileBatchMode();
}
void Document::setBatchmode(bool value)
{
if (!d->document) return;
d->document->setFileBatchMode(value);
}
Node *Document::activeNode() const
{
QList<KisNodeSP> activeNodes;
Q_FOREACH(QPointer<KisView> view, KisPart::instance()->views()) {
if (view && view->document() == d->document) {
activeNodes << view->currentNode();
}
}
if (activeNodes.size() > 0) {
QList<Node*> nodes = LibKisUtils::createNodeList(activeNodes, d->document->image());
return nodes.first();
}
return 0;
}
void Document::setActiveNode(Node* value)
{
if (!value->node()) return;
KisMainWindow *mainWin = KisPart::instance()->currentMainwindow();
if (!mainWin) return;
KisViewManager *viewManager = mainWin->viewManager();
if (!viewManager) return;
if (viewManager->document() != d->document) return;
KisNodeManager *nodeManager = viewManager->nodeManager();
if (!nodeManager) return;
KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter();
if (!selectionAdapter) return;
selectionAdapter->setActiveNode(value->node());
}
QList<Node *> Document::topLevelNodes() const
{
if (!d->document) return QList<Node *>();
Node n(d->document->image(), d->document->image()->rootLayer());
return n.childNodes();
}
Node *Document::nodeByName(const QString &name) const
{
if (!d->document) return 0;
KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name);
return new Node(d->document->image(), node);
}
QString Document::colorDepth() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->colorDepthId().id();
}
QString Document::colorModel() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->colorModelId().id();
}
QString Document::colorProfile() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->profile()->name();
}
bool Document::setColorProfile(const QString &value)
{
if (!d->document) return false;
if (!d->document->image()) return false;
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value);
if (!profile) return false;
bool retval = d->document->image()->assignImageProfile(profile);
d->document->image()->setModified();
d->document->image()->initialRefreshGraph();
return retval;
}
bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile)
{
if (!d->document) return false;
if (!d->document->image()) return false;
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile);
if (!colorSpace) return false;
d->document->image()->convertImageColorSpace(colorSpace,
KoColorConversionTransformation::IntentPerceptual,
KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization);
d->document->image()->setModified();
d->document->image()->initialRefreshGraph();
return true;
}
QColor Document::backgroundColor()
{
if (!d->document) return QColor();
if (!d->document->image()) return QColor();
const KoColor color = d->document->image()->defaultProjectionColor();
return color.toQColor();
}
bool Document::setBackgroundColor(const QColor &color)
{
if (!d->document) return false;
if (!d->document->image()) return false;
KoColor background = KoColor(color, d->document->image()->colorSpace());
d->document->image()->setDefaultProjectionColor(background);
d->document->image()->setModified();
d->document->image()->initialRefreshGraph();
return true;
}
QString Document::documentInfo() const
{
QDomDocument doc = KisDocument::createDomDocument("document-info"
/*DTD name*/, "document-info" /*tag name*/, "1.1");
doc = d->document->documentInfo()->save(doc);
return doc.toString();
}
void Document::setDocumentInfo(const QString &document)
{
KoXmlDocument doc;
QString errorMsg;
int errorLine, errorColumn;
doc.setContent(document, &errorMsg, &errorLine, &errorColumn);
d->document->documentInfo()->load(doc);
}
QString Document::fileName() const
{
if (!d->document) return QString();
return d->document->url().toLocalFile();
}
void Document::setFileName(QString value)
{
if (!d->document) return;
QString mimeType = KisMimeDatabase::mimeTypeForFile(value, false);
d->document->setMimeType(mimeType.toLatin1());
d->document->setUrl(QUrl::fromLocalFile(value));
}
int Document::height() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->height();
}
void Document::setHeight(int value)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
d->document->image()->bounds().y(),
d->document->image()->width(),
value);
}
QString Document::name() const
{
if (!d->document) return "";
return d->document->documentInfo()->aboutInfo("title");
}
void Document::setName(QString value)
{
if (!d->document) return;
d->document->documentInfo()->setAboutInfo("title", value);
}
int Document::resolution() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return qRound(d->document->image()->xRes() * 72);
}
void Document::setResolution(int value)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
d->document->image()->setResolution(value / 72.0, value / 72.0);
}
Node *Document::rootNode() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return new Node(image, image->root());
}
Selection *Document::selection() const
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
if (!d->document->image()->globalSelection()) return 0;
return new Selection(d->document->image()->globalSelection());
}
void Document::setSelection(Selection* value)
{
if (!d->document) return;
if (!d->document->image()) return;
if (value) {
d->document->image()->setGlobalSelection(value->selection());
}
else {
d->document->image()->setGlobalSelection(0);
}
}
int Document::width() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->width();
}
void Document::setWidth(int value)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
d->document->image()->bounds().y(),
value,
d->document->image()->height());
}
int Document::xOffset() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->bounds().x();
}
void Document::setXOffset(int x)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(x,
d->document->image()->bounds().y(),
d->document->image()->width(),
d->document->image()->height());
}
int Document::yOffset() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->bounds().y();
}
void Document::setYOffset(int y)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
y,
d->document->image()->width(),
d->document->image()->height());
}
double Document::xRes() const
{
if (!d->document) return 0.0;
if (!d->document->image()) return 0.0;
return d->document->image()->xRes()*72.0;
}
void Document::setXRes(double xRes) const
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->setResolution(xRes/72.0, d->document->image()->yRes());
}
double Document::yRes() const
{
if (!d->document) return 0.0;
if (!d->document->image()) return 0.0;
return d->document->image()->yRes()*72.0;
}
void Document::setYRes(double yRes) const
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->setResolution(d->document->image()->xRes(), yRes/72.0);
}
QByteArray Document::pixelData(int x, int y, int w, int h) const
{
QByteArray ba;
if (!d->document) return ba;
KisImageSP image = d->document->image();
if (!image) return ba;
KisPaintDeviceSP dev = image->projection();
ba.resize(w * h * dev->pixelSize());
dev->readBytes(reinterpret_cast<quint8*>(ba.data()), x, y, w, h);
return ba;
}
bool Document::close()
{
bool retval = d->document->closeUrl(false);
Q_FOREACH(KisView *view, KisPart::instance()->views()) {
if (view->document() == d->document) {
view->close();
view->closeView();
view->deleteLater();
}
}
KisPart::instance()->removeDocument(d->document);
d->document = 0;
return retval;
}
void Document::crop(int x, int y, int w, int h)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc(x, y, w, h);
image->cropImage(rc);
}
bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration)
{
if (!d->document) return false;
const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false);
const QByteArray outputFormat = outputFormatString.toLatin1();
return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration());
}
void Document::flatten()
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->flatten(0);
}
void Document::resizeImage(int x, int y, int w, int h)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc;
rc.setX(x);
rc.setY(y);
rc.setWidth(w);
rc.setHeight(h);
image->resizeImage(rc);
}
void Document::scaleImage(int w, int h, int xres, int yres, QString strategy)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc = image->bounds();
rc.setWidth(w);
rc.setHeight(h);
KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy);
if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic");
image->scaleImage(rc.size(), xres/72, yres/72, actualStrategy);
}
void Document::rotateImage(double radians)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
image->rotateImage(radians);
}
void Document::shearImage(double angleX, double angleY)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
image->shear(angleX, angleY);
}
bool Document::save()
{
if (!d->document) return false;
if (d->document->url().isEmpty()) return false;
bool retval = d->document->save(true, 0);
d->document->waitForSavingToComplete();
return retval;
}
bool Document::saveAs(const QString &filename)
{
if (!d->document) return false;
const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false);
const QByteArray outputFormat = outputFormatString.toLatin1();
QUrl oldUrl = d->document->url();
d->document->setUrl(QUrl::fromLocalFile(filename));
bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true);
d->document->waitForSavingToComplete();
d->document->setUrl(oldUrl);
return retval;
}
Node* Document::createNode(const QString &name, const QString &nodeType)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
Node *node = 0;
if (nodeType.toLower()== "paintlayer") {
node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "grouplayer") {
node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "filelayer") {
node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "filterlayer") {
node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0));
}
else if (nodeType.toLower() == "filllayer") {
node = new Node(image, new KisGeneratorLayer(image, name, 0, 0));
}
else if (nodeType.toLower() == "clonelayer") {
node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "vectorlayer") {
node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "transparencymask") {
node = new Node(image, new KisTransparencyMask());
}
else if (nodeType.toLower() == "filtermask") {
node = new Node(image, new KisFilterMask());
}
else if (nodeType.toLower() == "transformmask") {
node = new Node(image, new KisTransformMask());
}
else if (nodeType.toLower() == "selectionmask") {
node = new Node(image, new KisSelectionMask(image));
}
return node;
}
GroupLayer *Document::createGroupLayer(const QString &name)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new GroupLayer(image, name);
}
FileLayer *Document::createFileLayer(const QString &name, const QString fileName, const QString scalingMethod)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new FileLayer(image, name, this->fileName(), fileName, scalingMethod);
}
FilterLayer *Document::createFilterLayer(const QString &name, Filter &filter, Selection &selection)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new FilterLayer(image, name, filter, selection);
}
FillLayer *Document::createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName);
if (generator) {
KisFilterConfigurationSP config = generator->defaultConfiguration();
Q_FOREACH(const QString property, configuration.properties().keys()) {
config->setProperty(property, configuration.property(property));
}
return new FillLayer(image, name, config, selection);
}
return 0;
}
CloneLayer *Document::createCloneLayer(const QString &name, const Node *source)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
KisLayerSP layer = qobject_cast<KisLayer*>(source->node().data());
return new CloneLayer(image, name, layer);
}
VectorLayer *Document::createVectorLayer(const QString &name)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new VectorLayer(d->document->shapeController(), image, name);
}
FilterMask *Document::createFilterMask(const QString &name, Filter &filter, const Node *selection_source)
{
if (!d->document)
return 0;
if (!d->document->image())
return 0;
if(!selection_source)
return 0;
KisLayerSP layer = qobject_cast<KisLayer*>(selection_source->node().data());
if(layer.isNull())
return 0;
KisImageSP image = d->document->image();
FilterMask* mask = new FilterMask(image, name, filter);
qobject_cast<KisMask*>(mask->node().data())->initSelection(layer);
return mask;
}
FilterMask *Document::createFilterMask(const QString &name, Filter &filter, Selection &selection)
{
if (!d->document)
return 0;
if (!d->document->image())
return 0;
KisImageSP image = d->document->image();
FilterMask* mask = new FilterMask(image, name, filter);
qobject_cast<KisMask*>(mask->node().data())->setSelection(selection.selection());
return mask;
}
SelectionMask *Document::createSelectionMask(const QString &name)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new SelectionMask(image, name);
}
QImage Document::projection(int x, int y, int w, int h) const
{
if (!d->document || !d->document->image()) return QImage();
return d->document->image()->convertToQImage(x, y, w, h, 0);
}
QImage Document::thumbnail(int w, int h) const
{
if (!d->document || !d->document->image()) return QImage();
return d->document->generatePreview(QSize(w, h)).toImage();
}
void Document::lock()
{
if (!d->document || !d->document->image()) return;
d->document->image()->barrierLock();
}
void Document::unlock()
{
if (!d->document || !d->document->image()) return;
d->document->image()->unlock();
}
void Document::waitForDone()
{
if (!d->document || !d->document->image()) return;
d->document->image()->waitForDone();
}
bool Document::tryBarrierLock()
{
if (!d->document || !d->document->image()) return false;
return d->document->image()->tryBarrierLock();
}
bool Document::isIdle()
{
if (!d->document || !d->document->image()) return false;
return d->document->image()->isIdle();
}
void Document::refreshProjection()
{
if (!d->document || !d->document->image()) return;
d->document->image()->refreshGraph();
}
QList<qreal> Document::horizontalGuides() const
{
QList<qreal> lines;
if (!d->document || !d->document->image()) return lines;
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform().inverted();
QList<qreal> untransformedLines = d->document->guidesConfig().horizontalGuideLines();
for (int i = 0; i< untransformedLines.size(); i++) {
qreal line = untransformedLines[i];
lines.append(transform.map(QPointF(line, line)).x());
}
return lines;
}
QList<qreal> Document::verticalGuides() const
{
QList<qreal> lines;
if (!d->document || !d->document->image()) return lines;
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform().inverted();
QList<qreal> untransformedLines = d->document->guidesConfig().verticalGuideLines();
for (int i = 0; i< untransformedLines.size(); i++) {
qreal line = untransformedLines[i];
lines.append(transform.map(QPointF(line, line)).y());
}
return lines;
}
bool Document::guidesVisible() const
{
- return d->document->guidesConfig().lockGuides();
+ return d->document->guidesConfig().showGuides();
}
bool Document::guidesLocked() const
{
- return d->document->guidesConfig().showGuides();
+ return d->document->guidesConfig().lockGuides();
}
Document *Document::clone() const
{
if (!d->document) return 0;
QPointer<KisDocument> clone = d->document->clone();
Document * d = new Document(clone);
clone->setParent(d); // It's owned by the document, not KisPart
return d;
}
void Document::setHorizontalGuides(const QList<qreal> &lines)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform();
QList<qreal> transformedLines;
for (int i = 0; i< lines.size(); i++) {
qreal line = lines[i];
transformedLines.append(transform.map(QPointF(line, line)).x());
}
config.setHorizontalGuideLines(transformedLines);
d->document->setGuidesConfig(config);
}
void Document::setVerticalGuides(const QList<qreal> &lines)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform();
QList<qreal> transformedLines;
for (int i = 0; i< lines.size(); i++) {
qreal line = lines[i];
transformedLines.append(transform.map(QPointF(line, line)).y());
}
config.setVerticalGuideLines(transformedLines);
d->document->setGuidesConfig(config);
}
void Document::setGuidesVisible(bool visible)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
config.setShowGuides(visible);
d->document->setGuidesConfig(config);
}
void Document::setGuidesLocked(bool locked)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
config.setLockGuides(locked);
d->document->setGuidesConfig(config);
}
bool Document::modified() const
{
if (!d->document) return false;
return d->document->isModified();
}
QRect Document::bounds() const
{
if (!d->document) return QRect();
return d->document->image()->bounds();
}
QPointer<KisDocument> Document::document() const
{
return d->document;
}
/* Animation related function */
bool Document::importAnimation(const QList<QString> &files, int firstFrame, int step)
{
KisView *activeView = KisPart::instance()->currentMainwindow()->activeView();
KoUpdaterPtr updater = 0;
if (activeView && d->document->fileBatchMode()) {
updater = activeView->viewManager()->createUnthreadedUpdater(i18n("Import frames"));
}
KisAnimationImporter importer(d->document->image(), updater);
- KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
+ KisImportExportErrorCode status = importer.import(files, firstFrame, step);
- return (status == KisImportExportFilter::OK);
+ return status.isOk();
}
int Document::framesPerSecond()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->framerate();
}
void Document::setFramesPerSecond(int fps)
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->animationInterface()->setFramerate(fps);
}
void Document::setFullClipRangeStartTime(int startTime)
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->animationInterface()->setFullClipRangeStartTime(startTime);
}
int Document::fullClipRangeStartTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->fullClipRange().start();
}
void Document::setFullClipRangeEndTime(int endTime)
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->animationInterface()->setFullClipRangeEndTime(endTime);
}
int Document::fullClipRangeEndTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->fullClipRange().end();
}
int Document::animationLength()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->totalLength();
}
void Document::setPlayBackRange(int start, int stop)
{
if (!d->document) return;
if (!d->document->image()) return;
const KisTimeRange newTimeRange = KisTimeRange(start, (stop-start));
d->document->image()->animationInterface()->setPlaybackRange(newTimeRange);
}
int Document::playBackStartTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->playbackRange().start();
}
int Document::playBackEndTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->playbackRange().end();
}
int Document::currentTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->currentTime();
}
void Document::setCurrentTime(int time)
{
if (!d->document) return;
if (!d->document->image()) return;
return d->document->image()->animationInterface()->requestTimeSwitchWithUndo(time);
}
diff --git a/libs/libkis/Palette.cpp b/libs/libkis/Palette.cpp
index 19fc2523e4..568331976c 100644
--- a/libs/libkis/Palette.cpp
+++ b/libs/libkis/Palette.cpp
@@ -1,155 +1,155 @@
/*
* Copyright (c) 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "Palette.h"
#include <KoColorSet.h>
#include <KisSwatch.h>
#include <KisSwatchGroup.h>
#include <ManagedColor.h>
#include <KisPaletteModel.h>
struct Palette::Private {
KoColorSet *palette {0};
};
Palette::Palette(Resource *resource): d(new Private()) {
d->palette = dynamic_cast<KoColorSet*>(resource->resource());
}
Palette::~Palette()
{
delete d;
}
int Palette::numberOfEntries() const
{
if (!d->palette) return 0;
return d->palette->colorCount();
}
int Palette::columnCount()
{
if (!d->palette) return 0;
return d->palette->columnCount();
}
void Palette::setColumnCount(int columns)
{
if (d->palette)
d->palette->setColumnCount(columns);
}
QString Palette::comment()
{
if (!d->palette) return "";
return d->palette->comment();
}
void Palette::setComment(QString comment)
{
if (!d->palette) return;
return d->palette->setComment(comment);
}
QStringList Palette::groupNames() const
{
if (!d->palette) return QStringList();
return d->palette->getGroupNames();
}
bool Palette::addGroup(QString name)
{
if (!d->palette) return false;
return d->palette->addGroup(name);
}
bool Palette::removeGroup(QString name, bool keepColors)
{
if (!d->palette) return false;
return d->palette->removeGroup(name, keepColors);
}
int Palette::colorsCountTotal()
{
if (!d->palette) return 0;
return d->palette->colorCount();
}
Swatch *Palette::colorSetEntryByIndex(int index)
{
if (!d->palette) return new Swatch();
int col = index % columnCount();
int row = (index - col) / columnCount();
return new Swatch(d->palette->getColorGlobal(col, row));
}
Swatch *Palette::colorSetEntryFromGroup(int index, const QString &groupName)
{
if (!d->palette) return new Swatch();
int row = index % columnCount();
return new Swatch(d->palette->getColorGroup((index - row) / columnCount(), row, groupName));
}
void Palette::addEntry(Swatch entry, QString groupName)
{
d->palette->add(entry.kisSwatch(), groupName);
}
void Palette::removeEntry(int index, const QString &/*groupName*/)
{
int col = index % columnCount();
int tmp = index;
int row = (index - col) / columnCount();
- KisSwatchGroup *groupFoundIn = Q_NULLPTR;
+ KisSwatchGroup *groupFoundIn = 0;
Q_FOREACH(const QString &name, groupNames()) {
KisSwatchGroup *g = d->palette->getGroup(name);
tmp -= g->rowCount() * columnCount();
if (tmp < 0) {
groupFoundIn = g;
break;
}
row -= g->rowCount();
}
if (!groupFoundIn) { return; }
groupFoundIn->removeEntry(col, row);
}
bool Palette::changeGroupName(QString oldGroupName, QString newGroupName)
{
return d->palette->changeGroupName(oldGroupName, newGroupName);
}
bool Palette::moveGroup(const QString &groupName, const QString &groupNameInsertBefore)
{
return d->palette->moveGroup(groupName, groupNameInsertBefore);
}
bool Palette::save()
{
if (d->palette->filename().size()>0) {
return d->palette->save();
}
//if there's no filename the palette proly doesn't even exist...
return false;
}
KoColorSet *Palette::colorSet()
{
return d->palette;
}
diff --git a/libs/odf/tests/TestStorage.cpp b/libs/odf/tests/TestStorage.cpp
index f2e3e88091..5ea955e1f4 100644
--- a/libs/odf/tests/TestStorage.cpp
+++ b/libs/odf/tests/TestStorage.cpp
@@ -1,257 +1,257 @@
/* This file is part of the KDE project
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
Copyright (C) 2008 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License 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 <QFile>
#include <QDir>
#include <KoStore.h>
#include <KoEncryptionChecker.h>
#include <OdfDebug.h>
#include <stdlib.h>
#include <string.h>
#include <QTest>
class TestStorage : public QObject
{
Q_OBJECT
private Q_SLOTS:
void storage_data();
void storage();
void storage2_data();
void storage2();
private:
char getch(QIODevice * dev);
};
char TestStorage::getch(QIODevice * dev)
{
char c = 0;
dev->getChar(&c);
return c;
}
void TestStorage::storage_data()
{
QTest::addColumn<int>("type");
QTest::addColumn<QString>("testFile");
QTest::newRow("directory") << (int) KoStore::Directory << "testdir";
QTest::newRow("zip") << (int) KoStore::Zip <<"test.zip";
}
void TestStorage::storage()
{
const char test1[] = "This test checks whether we're able to write to some arbitrary directory.\n";
const char testDir[] = "0";
const char testDirResult[] = "0/";
const char test2[] = "This time we try to append the given relative path to the current dir.\n";
const char test3[] = "<xml>Hello World</xml>";
const char testDir2[] = "test2/with/a";
const char testDir2Result[] = "0/test2/with/a/";
const char test4[] = "<xml>Heureka, it works</xml>";
QFETCH(int, type);
QFETCH(QString, testFile);
KoStore::Backend backend = static_cast<KoStore::Backend>(type);
if (QFile::exists(testFile))
QFile::remove(testFile);
QDir dirTest(testFile);
if (dirTest.exists()) {
#ifdef Q_OS_UNIX
QByteArray ba = QByteArray("rm -rf ") + QFile::encodeName(testFile);
- system(ba.constData()); // QDir::rmdir isn't recursive!
+ Q_UNUSED(system(ba.constData())); // QDir::rmdir isn't recursive!
#else
QFAIL("build dir not empty");
#endif
}
KoStore* store = KoStore::createStore(testFile, KoStore::Write, "", backend);
QVERIFY(store);
QVERIFY(store->bad() == false);
if (store->isEncrypted())
store->setPassword("password");
QVERIFY(store->open("test1/with/a/relative/dir.txt"));
for (int i = 0; i < 100; ++i)
store->write(test1, strlen(test1));
store->close();
store->enterDirectory(testDir);
QCOMPARE(store->currentPath(), QString(testDirResult));
QVERIFY(store->open("test2/with/a/relative/dir.txt"));
for (int i = 0; i < 100; ++i)
store->write(test2, strlen(test2));
store->close();
QVERIFY(store->open("root"));
store->write(test3, strlen(test3));
store->close();
store->enterDirectory(testDir2);
QCOMPARE(store->currentPath(), QString(testDir2Result));
QVERIFY(store->open("root"));
store->write(test4, strlen(test4));
store->close();
if (store->isOpen())
store->close();
delete store;
store = KoStore::createStore(testFile, KoStore::Read, "", backend);
QVERIFY(store->bad() == false);
if (store->isEncrypted())
store->setPassword("password");
QVERIFY (store->open("test1/with/a/relative/dir.txt"));
QIODevice* dev = store->device();
int i = 0, lim = strlen(test1), count = 0;
while (static_cast<char>(getch(dev)) == test1[i++]) {
if (i == lim) {
i = 0;
++count;
}
}
store->close();
QCOMPARE(count, 100);
store->enterDirectory(testDir);
QCOMPARE (store->currentPath(), QString(testDirResult));
QVERIFY (store->open("test2/with/a/relative/dir.txt"));
dev = store->device();
i = 0;
lim = strlen(test2);
count = 0;
while (static_cast<char>(getch(dev)) == test2[i++]) {
if (i == lim) {
i = 0;
++count;
}
}
store->close();
QCOMPARE(count, 100);
store->enterDirectory(testDir2);
store->pushDirectory();
while (store->leaveDirectory()) {
;
}
store->enterDirectory(testDir);
QCOMPARE (store->currentPath(), QString(testDirResult));
QVERIFY (store->open("root"));
QCOMPARE (store->size(), (qint64) 22);
dev = store->device();
QByteArray dataReadBack = dev->read(strlen(test3));
store->close();
QCOMPARE (dataReadBack, QByteArray(test3));
store->popDirectory();
QCOMPARE(store->currentPath(), QString(testDir2Result));
QVERIFY (store->hasFile("relative/dir.txt"));
QVERIFY (store->open("root"));
char buf[29];
store->read(buf, 28);
buf[28] = '\0';
store->close();
QVERIFY(strncmp(buf, test4, 28) == 0);
if (store->isOpen())
store->close();
delete store;
QFile::remove(testFile);
}
#define DATALEN 64
void TestStorage::storage2_data()
{
QTest::addColumn<int>("type");
QTest::addColumn<QString>("testFile");
QTest::newRow("directory") << (int) KoStore::Directory << "testdir";
QTest::newRow("zip") << (int) KoStore::Zip <<"test.zip";
}
void TestStorage::storage2()
{
QFETCH(int, type);
QFETCH(QString, testFile);
KoStore::Backend backend = static_cast<KoStore::Backend>(type);
if (QFile::exists(testFile))
QFile::remove(testFile);
QDir dirTest(testFile);
if (dirTest.exists()) {
#ifdef Q_OS_UNIX
QByteArray ba = QByteArray("rm -rf ") + QFile::encodeName(testFile);
- system(ba.constData()); // QDir::rmdir isn't recursive!
+ Q_UNUSED(system(ba.constData())); // QDir::rmdir isn't recursive!
#else
QFAIL("build dir not empty");
#endif
}
KoStore* store = KoStore::createStore(testFile, KoStore::Write, "", backend);
QVERIFY(store->bad() == false);
// Write
QVERIFY (store->open("layer"));
char str[DATALEN];
sprintf(str, "1,2,3,4\n");
store->write(str, strlen(str));
memset(str, '\0', DATALEN);
store->write(str, DATALEN);
store->close();
delete store;
store = KoStore::createStore(testFile, KoStore::Read, "", backend);
QVERIFY(store->bad() == false);
// Read back
QVERIFY (store->open("layer"));
char str2[DATALEN];
QIODevice *stream = store->device(); // << Possible suspect!
stream->readLine(str2, DATALEN); // << as is this
qint64 len = store->read(str2, DATALEN);
QCOMPARE(len, (qint64) DATALEN);
store->close();
delete store;
QFile::remove(testFile);
}
QTEST_GUILESS_MAIN(TestStorage)
#include <TestStorage.moc>
diff --git a/libs/pigment/KoLabColorSpaceTraits.h b/libs/pigment/KoLabColorSpaceTraits.h
index 87cf81c435..925aba8f1c 100644
--- a/libs/pigment/KoLabColorSpaceTraits.h
+++ b/libs/pigment/KoLabColorSpaceTraits.h
@@ -1,400 +1,400 @@
/*
* Copyright (c) 2006-2007 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_LAB_COLORSPACE_TRAITS_H_
#define _KO_LAB_COLORSPACE_TRAITS_H_
-/**
+/**
* LAB traits, it provides some convenient functions to
* access LAB channels through an explicit API.
*
* Use this class in conjonction with KoColorSpace::toLabA16 and
* KoColorSpace::fromLabA16 data.
*
* Example:
* quint8* p = KoLabU16Traits::allocate(1);
* oneKoColorSpace->toLabA16(somepointertodata, p, 1);
* KoLabU16Traits::setL( p, KoLabU16Traits::L(p) / 10 );
* oneKoColorSpace->fromLabA16(p, somepointertodata, 1);
*/
template<typename _channels_type_>
struct KoLabTraits : public KoColorSpaceTrait<_channels_type_, 4, 3> {
typedef _channels_type_ channels_type;
typedef KoColorSpaceTrait<_channels_type_, 4, 3> parent;
static const qint32 L_pos = 0;
static const qint32 a_pos = 1;
static const qint32 b_pos = 2;
/**
* An Lab pixel
*/
struct Pixel {
channels_type L;
channels_type a;
channels_type b;
channels_type alpha;
};
/// @return the L component
inline static channels_type L(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[L_pos];
}
/// Set the L component
inline static void setL(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[L_pos] = nv;
}
/// @return the a component
inline static channels_type a(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[a_pos];
}
/// Set the a component
inline static void setA(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[a_pos] = nv;
}
/// @return the b component
inline static channels_type b(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[b_pos];
}
/// Set the a component
inline static void setB(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[b_pos] = nv;
}
};
//For quint* values must range from 0 to 1 - see KoColorSpaceMaths<double, quint*>
struct KoLabU8Traits : public KoLabTraits<quint8> {
static const quint32 MAX_CHANNEL_L = 100;
static const quint32 MAX_CHANNEL_AB = 255;
static const quint32 CHANNEL_AB_ZERO_OFFSET = 128;
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = nativeArray(pixel)[i];
switch (i) {
case L_pos:
channels[i] = ((qreal)c) / MAX_CHANNEL_L;
break;
case a_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case b_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case 3:
channels[i] = ((qreal)c) / UINT16_MAX;
break;
default:
channels[i] = ((qreal)c) / KoColorSpaceMathsTraits<channels_type>::unitValue;
break;
}
}
}
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
if (channelIndex > parent::channels_nb) return QString("Error");
channels_type c = nativeArray(pixel)[channelIndex];
switch (channelIndex) {
case L_pos:
return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L);
case a_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case b_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case 3:
return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX);
default:
return QString("Error");
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < channels_nb; i++) {
float b = 0;
switch (i) {
case L_pos:
b = qBound((float)0,
(float)MAX_CHANNEL_L * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)0,
- (float)MAX_CHANNEL_AB * values[i],
+ (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET,
(float)MAX_CHANNEL_AB);
break;
default:
b = qBound((float)KoColorSpaceMathsTraits<channels_type>::min,
(float)KoColorSpaceMathsTraits<channels_type>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<channels_type>::max);
break;
}
c = (channels_type)b;
nativeArray(pixel)[i] = c;
}
}
};
struct KoLabU16Traits : public KoLabTraits<quint16> {
// https://github.com/mm2/Little-CMS/blob/master/src/cmspcs.c
static const quint32 MAX_CHANNEL_L = 0xFF00;
static const quint32 MAX_CHANNEL_AB = 0xFFFF;
static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000;
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = nativeArray(pixel)[i];
switch (i) {
case L_pos:
channels[i] = ((qreal)c) / MAX_CHANNEL_L;
break;
case a_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case b_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case 3:
channels[i] = ((qreal)c) / UINT16_MAX;
break;
default:
channels[i] = ((qreal)c) / KoColorSpaceMathsTraits<channels_type>::unitValue;
break;
}
}
}
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
if (channelIndex > parent::channels_nb) return QString("Error");
channels_type c = nativeArray(pixel)[channelIndex];
switch (channelIndex) {
case L_pos:
return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L);
case a_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case b_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case 3:
return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX);
default:
return QString("Error");
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < channels_nb; i++) {
float b = 0;
switch (i) {
case L_pos:
b = qBound((float)0,
(float)MAX_CHANNEL_L * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)0,
- (float)MAX_CHANNEL_AB * values[i],
+ (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET,
(float)MAX_CHANNEL_AB);
break;
default:
b = qBound((float)KoColorSpaceMathsTraits<channels_type>::min,
(float)KoColorSpaceMathsTraits<channels_type>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<channels_type>::max);
break;
}
c = (channels_type)b;
nativeArray(pixel)[i] = c;
}
}
};
// Float values are not normalised
// XXX: is it really necessary to bind them to these ranges?
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
struct KoLabF16Traits : public KoLabTraits<half> {
static constexpr float MIN_CHANNEL_L = 0;
static constexpr float MAX_CHANNEL_L = 100;
static constexpr float MIN_CHANNEL_AB = -128;
static constexpr float MAX_CHANNEL_AB = +127;
// Lab has some... particulars
// For instance, float et al. are NOT normalised
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case L_pos:
b = qBound((float)MIN_CHANNEL_L,
(float)KoColorSpaceMathsTraits<half>::unitValue * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)MIN_CHANNEL_AB,
(float)KoColorSpaceMathsTraits<half>::unitValue * values[i],
(float)MAX_CHANNEL_AB);
break;
case 3:
b = qBound((float)KoColorSpaceMathsTraits<half>::min,
(float)KoColorSpaceMathsTraits<half>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<half>::max);
default:
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
#endif
struct KoLabF32Traits : public KoLabTraits<float> {
static constexpr float MIN_CHANNEL_L = 0;
static constexpr float MAX_CHANNEL_L = 100;
static constexpr float MIN_CHANNEL_AB = -128;
static constexpr float MAX_CHANNEL_AB = +127;
// Lab has some... particulars
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case L_pos:
b = qBound((float)MIN_CHANNEL_L,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)MIN_CHANNEL_AB,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)MAX_CHANNEL_AB);
break;
case 3:
b = qBound((float)KoColorSpaceMathsTraits<float>::min,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<float>::max);
default:
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
struct KoLabF64Traits : public KoLabTraits<double> {
static constexpr double MIN_CHANNEL_L = 0;
static constexpr double MAX_CHANNEL_L = 100;
static constexpr double MIN_CHANNEL_AB = -128;
static constexpr double MAX_CHANNEL_AB = +127;
// Lab has some... particulars
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case L_pos:
b = qBound((float)MIN_CHANNEL_L,
(float)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)MIN_CHANNEL_AB,
(float)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(float)MAX_CHANNEL_AB);
break;
case 3:
b = qBound((float)KoColorSpaceMathsTraits<double>::min,
(float)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<double>::max);
default:
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
#endif
diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp
index ade72507a9..70f4945b25 100644
--- a/libs/pigment/resources/KoColorSet.cpp
+++ b/libs/pigment/resources/KoColorSet.cpp
@@ -1,1631 +1,1652 @@
/* This file is part of the KDE project
Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <resources/KoColorSet.h>
#include <sys/types.h>
#include <QFile>
#include <QFileInfo>
#include <QBuffer>
#include <QVector>
#include <QTextStream>
#include <QTextCodec>
#include <QHash>
#include <QList>
#include <QByteArray>
#include <QDomDocument>
#include <QDomElement>
#include <QDomNodeList>
#include <QString>
#include <QStringList>
#include <QImage>
#include <QPainter>
#include <QXmlStreamReader>
#include <QXmlStreamAttributes>
#include <QtEndian> // qFromLittleEndian
#include <DebugPigment.h>
#include <klocalizedstring.h>
#include <KoStore.h>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoColorModelStandardIds.h>
#include "KisSwatch.h"
#include "KoColorSet.h"
#include "KoColorSet_p.h"
namespace {
/**
* readAllLinesSafe() reads all the lines in the byte array
* using the automated UTF8 and CR/LF transformations. That
* might be necessary for opening GPL palettes created on Linux
* in Windows environment.
*/
QStringList readAllLinesSafe(QByteArray *data)
{
QStringList lines;
QBuffer buffer(data);
buffer.open(QBuffer::ReadOnly);
QTextStream stream(&buffer);
QString line;
while (stream.readLineInto(&line)) {
lines << line;
}
return lines;
}
}
const QString KoColorSet::GLOBAL_GROUP_NAME = QString();
const QString KoColorSet::KPL_VERSION_ATTR = "version";
const QString KoColorSet::KPL_GROUP_ROW_COUNT_ATTR = "rows";
const QString KoColorSet::KPL_PALETTE_COLUMN_COUNT_ATTR = "columns";
const QString KoColorSet::KPL_PALETTE_NAME_ATTR = "name";
const QString KoColorSet::KPL_PALETTE_COMMENT_ATTR = "comment";
const QString KoColorSet::KPL_PALETTE_FILENAME_ATTR = "filename";
const QString KoColorSet::KPL_PALETTE_READONLY_ATTR = "readonly";
const QString KoColorSet::KPL_COLOR_MODEL_ID_ATTR = "colorModelId";
const QString KoColorSet::KPL_COLOR_DEPTH_ID_ATTR = "colorDepthId";
const QString KoColorSet::KPL_GROUP_NAME_ATTR = "name";
const QString KoColorSet::KPL_SWATCH_ROW_ATTR = "row";
const QString KoColorSet::KPL_SWATCH_COL_ATTR = "column";
const QString KoColorSet::KPL_SWATCH_NAME_ATTR = "name";
const QString KoColorSet::KPL_SWATCH_ID_ATTR = "id";
const QString KoColorSet::KPL_SWATCH_SPOT_ATTR = "spot";
const QString KoColorSet::KPL_SWATCH_BITDEPTH_ATTR = "bitdepth";
const QString KoColorSet::KPL_PALETTE_PROFILE_TAG = "Profile";
const QString KoColorSet::KPL_SWATCH_POS_TAG = "Position";
const QString KoColorSet::KPL_SWATCH_TAG = "ColorSetEntry";
const QString KoColorSet::KPL_GROUP_TAG = "Group";
const QString KoColorSet::KPL_PALETTE_TAG = "ColorSet";
+const int MAXIMUM_ALLOWED_COLUMNS = 4096;
+
KoColorSet::KoColorSet(const QString& filename)
: KoResource(filename)
, d(new Private(this))
{
if (!filename.isEmpty()) {
QFileInfo f(filename);
setIsEditable(f.isWritable());
}
}
/// Create an copied palette
KoColorSet::KoColorSet(const KoColorSet& rhs)
- : QObject(Q_NULLPTR)
+ : QObject(0)
, KoResource(rhs)
, d(new Private(this))
{
d->paletteType = rhs.d->paletteType;
d->data = rhs.d->data;
d->comment = rhs.d->comment;
d->groupNames = rhs.d->groupNames;
d->groups = rhs.d->groups;
d->isGlobal = rhs.d->isGlobal;
d->isEditable = rhs.d->isEditable;
}
KoColorSet::~KoColorSet()
{ }
bool KoColorSet::load()
{
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnPigment << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
if (!QFileInfo(filename()).isWritable()) {
setIsEditable(false);
}
return res;
}
bool KoColorSet::loadFromDevice(QIODevice *dev)
{
if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
d->data = dev->readAll();
Q_ASSERT(d->data.size() != 0);
return d->init();
}
bool KoColorSet::save()
{
if (d->isGlobal) {
// save to resource dir
QFile file(filename());
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
saveToDevice(&file);
file.close();
return true;
} else {
return true; // palette is not global, but still indicate that it's saved
}
}
bool KoColorSet::saveToDevice(QIODevice *dev) const
{
bool res;
switch(d->paletteType) {
case GPL:
res = d->saveGpl(dev);
break;
default:
res = d->saveKpl(dev);
}
if (res) {
KoResource::saveToDevice(dev);
}
return res;
}
QByteArray KoColorSet::toByteArray() const
{
QBuffer s;
s.open(QIODevice::WriteOnly);
if (!saveToDevice(&s)) {
warnPigment << "saving palette failed:" << name();
return QByteArray();
}
s.close();
s.open(QIODevice::ReadOnly);
QByteArray res = s.readAll();
s.close();
return res;
}
bool KoColorSet::fromByteArray(QByteArray &data)
{
QBuffer buf(&data);
buf.open(QIODevice::ReadOnly);
return loadFromDevice(&buf);
}
KoColorSet::PaletteType KoColorSet::paletteType() const
{
return d->paletteType;
}
void KoColorSet::setPaletteType(PaletteType paletteType)
{
d->paletteType = paletteType;
QString suffix;
switch(d->paletteType) {
case GPL:
suffix = ".gpl";
break;
case ACT:
suffix = ".act";
break;
case RIFF_PAL:
case PSP_PAL:
suffix = ".pal";
break;
case ACO:
suffix = ".aco";
break;
case XML:
suffix = ".xml";
break;
case KPL:
suffix = ".kpl";
break;
case SBZ:
suffix = ".sbz";
break;
default:
suffix = defaultFileExtension();
}
QStringList fileName = filename().split(".");
fileName.last() = suffix.replace(".", "");
setFilename(fileName.join("."));
}
quint32 KoColorSet::colorCount() const
{
- int colorCount = d->groups[GLOBAL_GROUP_NAME].colorCount();
+ int colorCount = 0;
for (KisSwatchGroup &g : d->groups.values()) {
colorCount += g.colorCount();
}
return colorCount;
}
void KoColorSet::add(const KisSwatch &c, const QString &groupName)
{
KisSwatchGroup &modifiedGroup = d->groups.contains(groupName)
? d->groups[groupName] : d->global();
modifiedGroup.addEntry(c);
}
void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName)
{
KisSwatchGroup &modifiedGroup = d->groups.contains(groupName)
? d->groups[groupName] : d->global();
modifiedGroup.setEntry(e, x, y);
}
void KoColorSet::clear()
{
d->groups.clear();
d->groupNames.clear();
d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup();
d->groupNames.append(GLOBAL_GROUP_NAME);
}
KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const
{
int yInGroup = y;
QString nameGroupFoundIn;
for (const QString &groupName : d->groupNames) {
if (yInGroup < d->groups[groupName].rowCount()) {
nameGroupFoundIn = groupName;
break;
} else {
yInGroup -= d->groups[groupName].rowCount();
}
}
const KisSwatchGroup &groupFoundIn = nameGroupFoundIn == GLOBAL_GROUP_NAME
? d->global() : d->groups[nameGroupFoundIn];
Q_ASSERT(groupFoundIn.checkEntry(x, yInGroup));
return groupFoundIn.getEntry(x, yInGroup);
}
KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName)
{
KisSwatch e;
const KisSwatchGroup &sourceGroup = groupName == QString()
? d->global() : d->groups[groupName];
if (sourceGroup.checkEntry(x, y)) {
e = sourceGroup.getEntry(x, y);
}
return e;
}
QStringList KoColorSet::getGroupNames()
{
if (d->groupNames.size() != d->groups.size()) {
warnPigment << "mismatch between groups and the groupnames list.";
return QStringList(d->groups.keys());
}
return d->groupNames;
}
bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName)
{
if (!d->groups.contains(oldGroupName)) {
return false;
}
if (oldGroupName == newGroupName) {
return true;
}
d->groups[newGroupName] = d->groups[oldGroupName];
d->groups.remove(oldGroupName);
d->groups[newGroupName].setName(newGroupName);
//rename the string in the stringlist;
int index = d->groupNames.indexOf(oldGroupName);
d->groupNames.replace(index, newGroupName);
return true;
}
void KoColorSet::setColumnCount(int columns)
{
d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns);
for (KisSwatchGroup &g : d->groups.values()) {
g.setColumnCount(columns);
}
}
int KoColorSet::columnCount() const
{
return d->groups[GLOBAL_GROUP_NAME].columnCount();
}
QString KoColorSet::comment()
{
return d->comment;
}
void KoColorSet::setComment(QString comment)
{
d->comment = comment;
}
bool KoColorSet::addGroup(const QString &groupName)
{
if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) {
return false;
}
d->groupNames.append(groupName);
d->groups[groupName] = KisSwatchGroup();
d->groups[groupName].setName(groupName);
return true;
}
bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore)
{
if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) {
return false;
}
if (groupNameInsertBefore != GLOBAL_GROUP_NAME && groupName != GLOBAL_GROUP_NAME) {
d->groupNames.removeAt(d->groupNames.indexOf(groupName));
int index = d->groupNames.indexOf(groupNameInsertBefore);
d->groupNames.insert(index, groupName);
}
return true;
}
bool KoColorSet::removeGroup(const QString &groupName, bool keepColors)
{
if (!d->groups.contains(groupName)) {
return false;
}
if (groupName == GLOBAL_GROUP_NAME) {
return false;
}
if (keepColors) {
// put all colors directly below global
int startingRow = d->groups[GLOBAL_GROUP_NAME].rowCount();
for (const KisSwatchGroup::SwatchInfo &info : d->groups[groupName].infoList()) {
d->groups[GLOBAL_GROUP_NAME].setEntry(info.swatch,
info.column,
info.row + startingRow);
}
}
d->groupNames.removeAt(d->groupNames.indexOf(groupName));
d->groups.remove(groupName);
return true;
}
QString KoColorSet::defaultFileExtension() const
{
return QString(".kpl");
}
int KoColorSet::rowCount() const
{
int res = 0;
for (const QString &name : d->groupNames) {
res += d->groups[name].rowCount();
}
return res;
}
KisSwatchGroup *KoColorSet::getGroup(const QString &name)
{
if (!d->groups.contains(name)) {
- return Q_NULLPTR;
+ return 0;
}
return &(d->groups[name]);
}
KisSwatchGroup *KoColorSet::getGlobalGroup()
{
return getGroup(GLOBAL_GROUP_NAME);
}
bool KoColorSet::isGlobal() const
{
return d->isGlobal;
}
void KoColorSet::setIsGlobal(bool isGlobal)
{
d->isGlobal = isGlobal;
}
bool KoColorSet::isEditable() const
{
return d->isEditable;
}
void KoColorSet::setIsEditable(bool isEditable)
{
d->isEditable = isEditable;
}
KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace)
{
KisSwatchGroup::SwatchInfo res;
quint8 highestPercentage = 0;
quint8 testPercentage = 0;
for (const QString &groupName : getGroupNames()) {
KisSwatchGroup *group = getGroup(groupName);
for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) {
KoColor color = currInfo.swatch.color();
if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) {
color.convertTo(compare.colorSpace());
} else if (compare.colorSpace() != color.colorSpace()) {
compare.convertTo(color.colorSpace());
}
testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data()));
if (testPercentage > highestPercentage)
{
highestPercentage = testPercentage;
res = currInfo;
}
}
}
return res;
}
/********************************KoColorSet::Private**************************/
KoColorSet::Private::Private(KoColorSet *a_colorSet)
: colorSet(a_colorSet)
, isGlobal(true)
, isEditable(false)
{
groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup();
groupNames.append(KoColorSet::GLOBAL_GROUP_NAME);
}
KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba)
{
QFileInfo fi(fileName);
// .pal
if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) {
return KoColorSet::RIFF_PAL;
}
// .gpl
else if (ba.startsWith("GIMP Palette")) {
return KoColorSet::GPL;
}
// .pal
else if (ba.startsWith("JASC-PAL")) {
return KoColorSet::PSP_PAL;
}
else if (fi.suffix().toLower() == "aco") {
return KoColorSet::ACO;
}
else if (fi.suffix().toLower() == "act") {
return KoColorSet::ACT;
}
else if (fi.suffix().toLower() == "xml") {
return KoColorSet::XML;
}
else if (fi.suffix().toLower() == "kpl") {
return KoColorSet::KPL;
}
else if (fi.suffix().toLower() == "sbz") {
return KoColorSet::SBZ;
}
return KoColorSet::UNKNOWN;
}
void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml)
{
KisSwatch colorEntry;
// It's a color, retrieve it
QXmlStreamAttributes colorProperties = xml->attributes();
QStringRef colorName = colorProperties.value("NAME");
colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString());
// RGB or CMYK?
if (colorProperties.hasAttribute("RGB")) {
dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB");
KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8());
QStringRef colorValue = colorProperties.value("RGB");
if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number
xml->raiseError("Invalid rgb8 color (malformed): " + colorValue);
return;
} else {
bool rgbOk;
quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16);
if (!rgbOk) {
xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue);
return;
}
quint8 r = rgb >> 16 & 0xff;
quint8 g = rgb >> 8 & 0xff;
quint8 b = rgb & 0xff;
dbgPigment << "Color parsed: "<< r << g << b;
currentColor.data()[0] = r;
currentColor.data()[1] = g;
currentColor.data()[2] = b;
currentColor.setOpacity(OPACITY_OPAQUE_U8);
colorEntry.setColor(currentColor);
set->add(colorEntry);
while(xml->readNextStartElement()) {
//ignore - these are all unknown or the /> element tag
xml->skipCurrentElement();
}
return;
}
}
else if (colorProperties.hasAttribute("CMYK")) {
dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK");
KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()));
QStringRef colorValue = colorProperties.value("CMYK");
if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number
xml->raiseError("Invalid cmyk color (malformed): " % colorValue);
return;
}
else {
bool cmykOk;
quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits
if (!cmykOk) {
xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue);
return;
}
quint8 c = cmyk >> 24 & 0xff;
quint8 m = cmyk >> 16 & 0xff;
quint8 y = cmyk >> 8 & 0xff;
quint8 k = cmyk & 0xff;
dbgPigment << "Color parsed: "<< c << m << y << k;
currentColor.data()[0] = c;
currentColor.data()[1] = m;
currentColor.data()[2] = y;
currentColor.data()[3] = k;
currentColor.setOpacity(OPACITY_OPAQUE_U8);
colorEntry.setColor(currentColor);
set->add(colorEntry);
while(xml->readNextStartElement()) {
//ignore - these are all unknown or the /> element tag
xml->skipCurrentElement();
}
return;
}
}
else {
xml->raiseError("Unknown color space for color " + colorEntry.name());
}
}
bool KoColorSet::Private::loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml)
{
//1. Get name
QXmlStreamAttributes paletteProperties = xml->attributes();
QStringRef paletteName = paletteProperties.value("Name");
dbgPigment << "Processed name of palette:" << paletteName;
set->setName(paletteName.toString());
//2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them
while(xml->readNextStartElement()) {
QStringRef currentElement = xml->name();
if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) {
scribusParseColor(set, xml);
}
else {
xml->skipCurrentElement();
}
}
if(xml->hasError()) {
return false;
}
return true;
}
quint16 KoColorSet::Private::readShort(QIODevice *io) {
quint16 val;
quint64 read = io->read((char*)&val, 2);
if (read != 2) return false;
return qFromBigEndian(val);
}
bool KoColorSet::Private::init()
{
// just in case this is a reload (eg by KoEditColorSetDialog),
groupNames.clear();
groups.clear();
groupNames.append(KoColorSet::GLOBAL_GROUP_NAME);
groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup();
if (colorSet->filename().isNull()) {
warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set";
return false;
}
if (data.isNull()) {
QFile file(colorSet->filename());
if (file.size() == 0) {
warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available";
return false;
}
file.open(QIODevice::ReadOnly);
data = file.readAll();
file.close();
}
bool res = false;
paletteType = detectFormat(colorSet->filename(), data);
switch(paletteType) {
case GPL:
res = loadGpl();
break;
case ACT:
res = loadAct();
break;
case RIFF_PAL:
res = loadRiff();
break;
case PSP_PAL:
res = loadPsp();
break;
case ACO:
res = loadAco();
break;
case XML:
res = loadXml();
break;
case KPL:
res = loadKpl();
break;
case SBZ:
res = loadSbz();
break;
default:
res = false;
}
colorSet->setValid(res);
QImage img(global().columnCount() * 4, global().rowCount() * 4, QImage::Format_ARGB32);
QPainter gc(&img);
gc.fillRect(img.rect(), Qt::darkGray);
for (const KisSwatchGroup::SwatchInfo &info : global().infoList()) {
QColor c = info.swatch.color().toQColor();
gc.fillRect(info.column * 4, info.row * 4, 4, 4, c);
}
colorSet->setImage(img);
colorSet->setValid(res);
data.clear();
return res;
}
bool KoColorSet::Private::saveGpl(QIODevice *dev) const
{
Q_ASSERT(dev->isOpen());
Q_ASSERT(dev->isWritable());
QTextStream stream(dev);
stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n";
/*
* Qt doesn't provide an interface to get a const reference to a QHash, that is
* the underlying data structure of groups. Therefore, directly use
* groups[KoColorSet::GLOBAL_GROUP_NAME] so that saveGpl can stay const
*/
for (int y = 0; y < groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount(); y++) {
for (int x = 0; x < colorSet->columnCount(); x++) {
if (!groups[KoColorSet::GLOBAL_GROUP_NAME].checkEntry(x, y)) {
continue;
}
const KisSwatch& entry = groups[KoColorSet::GLOBAL_GROUP_NAME].getEntry(x, y);
QColor c = entry.color().toQColor();
stream << c.red() << " " << c.green() << " " << c.blue() << "\t";
if (entry.name().isEmpty())
stream << "Untitled\n";
else
stream << entry.name() << "\n";
}
}
return true;
}
bool KoColorSet::Private::loadGpl()
{
if (data.isEmpty() || data.isNull() || data.length() < 50) {
warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
return false;
}
quint32 index = 0;
QStringList lines = readAllLinesSafe(&data);
if (lines.size() < 3) {
warnPigment << "Not enough lines in palette file: " << colorSet->filename();
return false;
}
QString columnsText;
qint32 r, g, b;
KisSwatch e;
// Read name
if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) {
warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
return false;
}
colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1()));
index = 2;
// Read columns
int columns = 0;
if (lines[index].toLower().contains("columns")) {
columnsText = lines[index].split(":")[1].trimmed();
columns = columnsText.toInt();
- global().setColumnCount(columns);
+ if (columns > MAXIMUM_ALLOWED_COLUMNS) {
+ warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead";
+ global().setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
+ }
+ else {
+ global().setColumnCount(columns);
+ }
index = 3;
}
for (qint32 i = index; i < lines.size(); i++) {
if (lines[i].startsWith('#')) {
comment += lines[i].mid(1).trimmed() + ' ';
} else if (!lines[i].isEmpty()) {
QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts);
if (a.count() < 3) {
- break;
+ continue;
}
r = qBound(0, a[0].toInt(), 255);
g = qBound(0, a[1].toInt(), 255);
b = qBound(0, a[2].toInt(), 255);
e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
for (int i = 0; i != 3; i++) {
a.pop_front();
}
QString name = a.join(" ");
e.setName(name.isEmpty() ? i18n("Untitled") : name);
global().addEntry(e);
}
}
int rowCount = global().colorCount()/ global().columnCount();
if (global().colorCount() % global().columnCount()>0) {
rowCount ++;
}
global().setRowCount(rowCount);
return true;
}
bool KoColorSet::Private::loadAct()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.baseName());
KisSwatch e;
for (int i = 0; i < data.size(); i += 3) {
quint8 r = data[i];
quint8 g = data[i+1];
quint8 b = data[i+2];
e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
global().addEntry(e);
}
return true;
}
bool KoColorSet::Private::loadRiff()
{
// http://worms2d.info/Palette_file
QFileInfo info(colorSet->filename());
colorSet->setName(info.baseName());
KisSwatch e;
RiffHeader header;
memcpy(&header, data.constData(), sizeof(RiffHeader));
header.colorcount = qFromBigEndian(header.colorcount);
for (int i = sizeof(RiffHeader);
(i < (int)(sizeof(RiffHeader) + header.colorcount) && i < data.size());
i += 4) {
quint8 r = data[i];
quint8 g = data[i+1];
quint8 b = data[i+2];
e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
return true;
}
bool KoColorSet::Private::loadPsp()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.baseName());
KisSwatch e;
qint32 r, g, b;
QStringList l = readAllLinesSafe(&data);
if (l.size() < 4) return false;
if (l[0] != "JASC-PAL") return false;
if (l[1] != "0100") return false;
int entries = l[2].toInt();
for (int i = 0; i < entries; ++i) {
QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts);
if (a.count() != 3) {
continue;
}
r = qBound(0, a[0].toInt(), 255);
g = qBound(0, a[1].toInt(), 255);
b = qBound(0, a[2].toInt(), 255);
e.setColor(KoColor(QColor(r, g, b),
KoColorSpaceRegistry::instance()->rgb8()));
QString name = a.join(" ");
e.setName(name.isEmpty() ? i18n("Untitled") : name);
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
return true;
}
bool KoColorSet::Private::loadKpl()
{
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
QScopedPointer<KoStore> store(KoStore::createStore(&buf, KoStore::Read, "krita/x-colorset", KoStore::Zip));
if (!store || store->bad()) { return false; }
if (store->hasFile("profiles.xml")) {
if (!store->open("profiles.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
QDomDocument doc;
doc.setContent(ba);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement(KPL_PALETTE_PROFILE_TAG);
while (!c.isNull()) {
QString name = c.attribute(KPL_PALETTE_NAME_ATTR);
QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR);
QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR);
QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR);
if (!KoColorSpaceRegistry::instance()->profileByName(name)) {
store->open(filename);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
}
}
c = c.nextSiblingElement();
}
}
{
if (!store->open("colorset.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
+ int desiredColumnCount;
+
QDomDocument doc;
doc.setContent(ba);
QDomElement e = doc.documentElement();
colorSet->setName(e.attribute(KPL_PALETTE_NAME_ATTR));
- colorSet->setColumnCount(e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt());
colorSet->setIsEditable(e.attribute(KPL_PALETTE_READONLY_ATTR) != "true");
comment = e.attribute(KPL_PALETTE_COMMENT_ATTR);
+ desiredColumnCount = e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt();
+ if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) {
+ warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount << ") in KPL palette file " << colorSet->filename() << " - setting maximum allowed column count instead.";
+ colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
+ }
+ else {
+ colorSet->setColumnCount(desiredColumnCount);
+ }
+
loadKplGroup(doc, e, colorSet->getGlobalGroup());
QDomElement g = e.firstChildElement(KPL_GROUP_TAG);
while (!g.isNull()) {
QString groupName = g.attribute(KPL_GROUP_NAME_ATTR);
colorSet->addGroup(groupName);
loadKplGroup(doc, g, colorSet->getGroup(groupName));
g = g.nextSiblingElement(KPL_GROUP_TAG);
}
}
buf.close();
return true;
}
bool KoColorSet::Private::loadAco()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.baseName());
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
quint16 version = readShort(&buf);
quint16 numColors = readShort(&buf);
KisSwatch e;
if (version == 1 && buf.size() > 4+numColors*10) {
buf.seek(4+numColors*10);
version = readShort(&buf);
numColors = readShort(&buf);
}
const quint16 quint16_MAX = 65535;
for (int i = 0; i < numColors && !buf.atEnd(); ++i) {
quint16 colorSpace = readShort(&buf);
quint16 ch1 = readShort(&buf);
quint16 ch2 = readShort(&buf);
quint16 ch3 = readShort(&buf);
quint16 ch4 = readShort(&buf);
bool skip = false;
if (colorSpace == 0) { // RGB
const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile();
KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb));
reinterpret_cast<quint16*>(c.data())[0] = ch3;
reinterpret_cast<quint16*>(c.data())[1] = ch2;
reinterpret_cast<quint16*>(c.data())[2] = ch1;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 1) { // HSB
QColor qc;
qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0);
KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16());
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 2) { // CMYK
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString()));
reinterpret_cast<quint16*>(c.data())[0] = quint16_MAX - ch1;
reinterpret_cast<quint16*>(c.data())[1] = quint16_MAX - ch2;
reinterpret_cast<quint16*>(c.data())[2] = quint16_MAX - ch3;
reinterpret_cast<quint16*>(c.data())[3] = quint16_MAX - ch4;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 7) { // LAB
KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16());
reinterpret_cast<quint16*>(c.data())[0] = ch3;
reinterpret_cast<quint16*>(c.data())[1] = ch2;
reinterpret_cast<quint16*>(c.data())[2] = ch1;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 8) { // GRAY
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString()));
reinterpret_cast<quint16*>(c.data())[0] = ch1 * (quint16_MAX / 10000);
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else {
warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")";
skip = true;
}
if (version == 2) {
quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped.
Q_UNUSED(v2);
quint16 size = readShort(&buf) -1; //then comes the length
if (size>0) {
QByteArray ba = buf.read(size*2);
if (ba.size() == size*2) {
QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE");
e.setName(Utf16Codec->toUnicode(ba));
} else {
warnPigment << "Version 2 name block is the wrong size" << colorSet->filename();
}
}
v2 = readShort(&buf); //end marker also needs to be skipped.
Q_UNUSED(v2);
}
if (!skip) {
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
}
return true;
}
bool KoColorSet::Private::loadSbz() {
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
// &buf is a subclass of QIODevice
QScopedPointer<KoStore> store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip));
if (!store || store->bad()) return false;
if (store->hasFile("swatchbook.xml")) { // Try opening...
if (!store->open("swatchbook.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format";
QDomDocument doc;
int errorLine, errorColumn;
QString errorMessage;
bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn);
if (!status) {
warnPigment << "Illegal XML palette:" << colorSet->filename();
warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage;
return false;
}
QDomElement e = doc.documentElement(); // SwatchBook
// Start reading properties...
QDomElement metadata = e.firstChildElement("metadata");
if (e.isNull()) {
warnPigment << "Palette metadata not found";
return false;
}
QDomElement title = metadata.firstChildElement("dc:title");
QString colorName = title.text();
colorName = colorName.isEmpty() ? i18n("Untitled") : colorName;
colorSet->setName(colorName);
dbgPigment << "Processed name of palette:" << colorSet->name();
// End reading properties
// Now read colors...
QDomElement materials = e.firstChildElement("materials");
if (materials.isNull()) {
warnPigment << "Materials (color definitions) not found";
return false;
}
// This one has lots of "color" elements
QDomElement colorElement = materials.firstChildElement("color");
if (colorElement.isNull()) {
warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")";
return false;
}
// Also read the swatch book...
QDomElement book = e.firstChildElement("book");
if (book.isNull()) {
warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")";
return false;
}
// Which has lots of "swatch"es (todo: support groups)
QDomElement swatch = book.firstChildElement();
if (swatch.isNull()) {
warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")";
return false;
}
// We'll store colors here, and as we process swatches
// we'll add them to the palette
QHash<QString, KisSwatch> materialsBook;
QHash<QString, const KoColorSpace*> fileColorSpaces;
// Color processing
for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color"))
{
KisSwatch currentEntry;
// Set if color is spot
currentEntry.setSpotColor(colorElement.attribute("usage") == "spot");
// <metadata> inside contains id and name
// one or more <values> define the color
QDomElement currentColorMetadata = colorElement.firstChildElement("metadata");
QDomNodeList currentColorValues = colorElement.elementsByTagName("values");
// Get color name
QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title");
QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier");
// Is there an id? (we need that at the very least for identifying a color)
if (colorId.text().isEmpty()) {
warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")";
return false;
}
if (materialsBook.contains(colorId.text())) {
warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")";
return false;
}
// Get a valid color name
currentEntry.setId(colorId.text());
currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text());
// Get a valid color definition
if (currentColorValues.isEmpty()) {
warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")";
return false;
}
bool firstDefinition = false;
const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile();
// Priority: Lab, otherwise the first definition found
for(int j = 0; j < currentColorValues.size(); j++) {
QDomNode colorValue = currentColorValues.at(j);
QDomElement colorValueE = colorValue.toElement();
QString model = colorValueE.attribute("model", QString());
// sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1
// YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5
// Lab: L 0 -> 100 : ab -128 -> 127
// XYZ: 0 -> ~100
if (model == "Lab") {
QStringList lab = colorValueE.text().split(" ");
if (lab.length() != 3) {
warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float l = lab.at(0).toFloat(&status);
float a = lab.at(1).toFloat(&status);
float b = lab.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString()));
reinterpret_cast<float*>(c.data())[0] = l;
reinterpret_cast<float*>(c.data())[1] = a;
reinterpret_cast<float*>(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
firstDefinition = true;
currentEntry.setColor(c);
break; // Immediately add this one
}
else if (model == "sRGB" && !firstDefinition) {
QStringList rgb = colorValueE.text().split(" ");
if (rgb.length() != 3) {
warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float r = rgb.at(0).toFloat(&status);
float g = rgb.at(1).toFloat(&status);
float b = rgb.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb));
reinterpret_cast<float*>(c.data())[0] = r;
reinterpret_cast<float*>(c.data())[1] = g;
reinterpret_cast<float*>(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else if (model == "XYZ" && !firstDefinition) {
QStringList xyz = colorValueE.text().split(" ");
if (xyz.length() != 3) {
warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float x = xyz.at(0).toFloat(&status);
float y = xyz.at(1).toFloat(&status);
float z = xyz.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString()));
reinterpret_cast<float*>(c.data())[0] = x;
reinterpret_cast<float*>(c.data())[1] = y;
reinterpret_cast<float*>(c.data())[2] = z;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
// The following color spaces admit an ICC profile (in SwatchBooker)
else if (model == "CMYK" && !firstDefinition) {
QStringList cmyk = colorValueE.text().split(" ");
if (cmyk.length() != 4) {
warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float c = cmyk.at(0).toFloat(&status);
float m = cmyk.at(1).toFloat(&status);
float y = cmyk.at(2).toFloat(&status);
float k = cmyk.at(3).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString());
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor color(colorSpace);
reinterpret_cast<float*>(color.data())[0] = c;
reinterpret_cast<float*>(color.data())[1] = m;
reinterpret_cast<float*>(color.data())[2] = y;
reinterpret_cast<float*>(color.data())[3] = k;
color.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(color);
firstDefinition = true;
}
else if (model == "GRAY" && !firstDefinition) {
QString gray = colorValueE.text();
float g = gray.toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString());
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor c(colorSpace);
reinterpret_cast<float*>(c.data())[0] = g;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else if (model == "RGB" && !firstDefinition) {
QStringList rgb = colorValueE.text().split(" ");
if (rgb.length() != 3) {
warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float r = rgb.at(0).toFloat(&status);
float g = rgb.at(1).toFloat(&status);
float b = rgb.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb);
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor c(colorSpace);
reinterpret_cast<float*>(c.data())[0] = r;
reinterpret_cast<float*>(c.data())[1] = g;
reinterpret_cast<float*>(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else {
warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")";
}
}
if (firstDefinition) {
materialsBook.insert(currentEntry.id(), currentEntry);
}
else {
warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")";
return false;
}
}
// End colors
// Now decide which ones will go into the palette
for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) {
QString type = swatch.tagName();
if (type.isEmpty() || type.isNull()) {
warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
else if (type == "swatch") {
QString id = swatch.attribute("material");
if (id.isEmpty() || id.isNull()) {
warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
if (materialsBook.contains(id)) {
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(materialsBook.value(id));
}
else {
warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
}
else if (type == "group") {
QDomElement groupMetadata = swatch.firstChildElement("metadata");
if (groupMetadata.isNull()) {
warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")";
return false;
}
QDomElement groupTitle = metadata.firstChildElement("dc:title");
if (groupTitle.isNull()) {
warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")";
return false;
}
QString currentGroupName = groupTitle.text();
QDomElement groupSwatch = swatch.firstChildElement("swatch");
while(!groupSwatch.isNull()) {
QString id = groupSwatch.attribute("material");
if (id.isEmpty() || id.isNull()) {
warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")";
return false;
}
if (materialsBook.contains(id)) {
groups[currentGroupName].addEntry(materialsBook.value(id));
}
else {
warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")";
return false;
}
groupSwatch = groupSwatch.nextSiblingElement("swatch");
}
}
}
// End palette
}
buf.close();
return true;
}
bool KoColorSet::Private::loadXml() {
bool res = false;
QXmlStreamReader *xml = new QXmlStreamReader(data);
if (xml->readNextStartElement()) {
QStringRef paletteId = xml->name();
if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus
dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format";
res = loadScribusXmlPalette(colorSet, xml);
}
else {
// Unknown XML format
xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId);
}
}
// If there is any error (it should be returned through the stream)
if (xml->hasError() || !res) {
warnPigment << "Illegal XML palette:" << colorSet->filename();
warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString();
return false;
}
else {
dbgPigment << "XML palette parsed successfully:" << colorSet->filename();
return true;
}
}
bool KoColorSet::Private::saveKpl(QIODevice *dev) const
{
QScopedPointer<KoStore> store(KoStore::createStore(dev, KoStore::Write, "krita/x-colorset", KoStore::Zip));
if (!store || store->bad()) return false;
QSet<const KoColorSpace *> colorSpaces;
{
QDomDocument doc;
QDomElement root = doc.createElement(KPL_PALETTE_TAG);
root.setAttribute(KPL_VERSION_ATTR, "1.0");
root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name());
root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment);
root.setAttribute(KPL_PALETTE_READONLY_ATTR,
(colorSet->isEditable() || !colorSet->isGlobal()) ? "false" : "true");
root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount());
root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount());
saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces);
for (const QString &groupName : groupNames) {
if (groupName == KoColorSet::GLOBAL_GROUP_NAME) { continue; }
QDomElement gl = doc.createElement(KPL_GROUP_TAG);
gl.setAttribute(KPL_GROUP_NAME_ATTR, groupName);
root.appendChild(gl);
saveKplGroup(doc, gl, colorSet->getGroup(groupName), colorSpaces);
}
doc.appendChild(root);
if (!store->open("colorset.xml")) { return false; }
QByteArray ba = doc.toByteArray();
if (store->write(ba) != ba.size()) { return false; }
if (!store->close()) { return false; }
}
QDomDocument doc;
QDomElement profileElement = doc.createElement("Profiles");
for (const KoColorSpace *colorSpace : colorSpaces) {
QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName();
if (!store->open(fn)) { return false; }
QByteArray profileRawData = colorSpace->profile()->rawData();
if (!store->write(profileRawData)) { return false; }
if (!store->close()) { return false; }
QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG);
el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn);
el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name());
el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id());
el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->colorDepthId().id());
profileElement.appendChild(el);
}
doc.appendChild(profileElement);
if (!store->open("profiles.xml")) { return false; }
QByteArray ba = doc.toByteArray();
if (store->write(ba) != ba.size()) { return false; }
if (!store->close()) { return false; }
return store->finalize();
}
void KoColorSet::Private::saveKplGroup(QDomDocument &doc,
QDomElement &groupEle,
const KisSwatchGroup *group,
QSet<const KoColorSpace *> &colorSetSet) const
{
groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount()));
for (const SwatchInfoType &info : group->infoList()) {
const KoColorProfile *profile = info.swatch.color().colorSpace()->profile();
// Only save non-builtin profiles.=
if (!profile->fileName().isEmpty()) {
colorSetSet.insert(info.swatch.color().colorSpace());
}
QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG);
swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name());
swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id());
swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false");
swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id());
info.swatch.color().toXML(doc, swatchEle);
QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG);
positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row);
positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column);
swatchEle.appendChild(positionEle);
groupEle.appendChild(swatchEle);
}
}
void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroup *group)
{
Q_UNUSED(doc);
if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) {
group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt());
}
group->setColumnCount(colorSet->columnCount());
for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG);
!swatchEle.isNull();
swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) {
QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id());
KisSwatch entry;
entry.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId));
entry.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR));
entry.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR));
entry.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false);
QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG);
if (!positionEle.isNull()) {
int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt();
int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt();
if (columnNumber < 0 ||
columnNumber >= colorSet->columnCount() ||
rowNumber < 0
) {
warnPigment << "Swatch" << entry.name()
<< "of palette" << colorSet->name()
<< "has invalid position.";
continue;
}
group->setEntry(entry, columnNumber, rowNumber);
} else {
group->addEntry(entry);
}
}
- if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull() && group->colorCount()/group->columnCount()+1 < 20) {
- group->setRowCount(group->colorCount()/group->columnCount()+1);
+ if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()
+ && group->colorCount() > 0
+ && group->columnCount() > 0
+ && (group->colorCount() / (group->columnCount()) + 1) < 20) {
+ group->setRowCount((group->colorCount() / group->columnCount()) + 1);
}
}
diff --git a/libs/pigment/resources/KoStopGradient.cpp b/libs/pigment/resources/KoStopGradient.cpp
index 7f31c131c8..a49a173aec 100644
--- a/libs/pigment/resources/KoStopGradient.cpp
+++ b/libs/pigment/resources/KoStopGradient.cpp
@@ -1,606 +1,612 @@
/*
Copyright (C) 2005 Tim Beaulen <tbscope@gmail.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <resources/KoStopGradient.h>
#include <cfloat>
#include <QColor>
#include <QFile>
#include <QDomDocument>
#include <QDomElement>
#include <QBuffer>
#include <klocalizedstring.h>
#include <DebugPigment.h>
#include "KoColorSpaceRegistry.h"
#include "KoMixColorsOp.h"
#include "kis_dom_utils.h"
#include <math.h>
#include <KoColorModelStandardIds.h>
KoStopGradient::KoStopGradient(const QString& filename)
: KoAbstractGradient(filename)
{
}
KoStopGradient::~KoStopGradient()
{
}
bool KoStopGradient::operator==(const KoStopGradient &rhs) const
{
return
*colorSpace() == *rhs.colorSpace() &&
spread() == rhs.spread() &&
type() == rhs.type() &&
m_start == rhs.m_start &&
m_stop == rhs.m_stop &&
m_focalPoint == rhs.m_focalPoint &&
m_stops == rhs.m_stops;
}
KoAbstractGradient* KoStopGradient::clone() const
{
return new KoStopGradient(*this);
}
bool KoStopGradient::load()
{
QFile f(filename());
if (!f.open(QIODevice::ReadOnly)) {
warnPigment << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&f);
f.close();
return res;
}
bool KoStopGradient::loadFromDevice(QIODevice *dev)
{
QString strExt;
const int result = filename().lastIndexOf('.');
if (result >= 0) {
strExt = filename().mid(result).toLower();
}
QByteArray ba = dev->readAll();
QBuffer buf(&ba);
loadSvgGradient(&buf);
if (m_stops.count() >= 2) {
setValid(true);
}
updatePreview();
return true;
}
bool KoStopGradient::save()
{
QFile fileOut(filename());
if (! fileOut.open(QIODevice::WriteOnly))
return false;
bool retval = saveToDevice(&fileOut);
fileOut.close();
return retval;
}
QGradient* KoStopGradient::toQGradient() const
{
QGradient* gradient;
switch (type()) {
case QGradient::LinearGradient: {
gradient = new QLinearGradient(m_start, m_stop);
break;
}
case QGradient::RadialGradient: {
QPointF diff = m_stop - m_start;
qreal radius = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
gradient = new QRadialGradient(m_start, radius, m_focalPoint);
break;
}
case QGradient::ConicalGradient: {
qreal angle = atan2(m_start.y(), m_start.x()) * 180.0 / M_PI;
if (angle < 0.0)
angle += 360.0;
gradient = new QConicalGradient(m_start, angle);
break;
}
default:
return 0;
}
QColor color;
for (QList<KoGradientStop>::const_iterator i = m_stops.begin(); i != m_stops.end(); ++i) {
i->second.toQColor(&color);
gradient->setColorAt(i->first , color);
}
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
gradient->setSpread(this->spread());
return gradient;
}
-void KoStopGradient::colorAt(KoColor& dst, qreal t) const
+bool KoStopGradient::stopsAt(KoGradientStop &leftStop, KoGradientStop &rightStop, qreal t) const
{
- KoColor buffer;
-
if (! m_stops.count())
- return;
+ return false;
if (t <= m_stops.first().first || m_stops.count() == 1) {
// we have only one stop or t is before the first stop
- // -> use the color of the first stop
- dst.fromKoColor(m_stops.first().second);
+ leftStop = m_stops.first();
+ rightStop = KoGradientStop(-std::numeric_limits<double>::infinity(), leftStop.second);
+ return true;
} else if (t >= m_stops.last().first) {
// t is after the last stop
- // -> use the color of the last stop
- dst.fromKoColor(m_stops.last().second);
+ rightStop = m_stops.last();
+ leftStop = KoGradientStop(std::numeric_limits<double>::infinity(), rightStop.second);
+ return true;
} else {
// we have at least two color stops
// -> find the two stops which frame our t
QList<KoGradientStop>::const_iterator stop = m_stops.begin();
QList<KoGradientStop>::const_iterator lastStop = m_stops.end();
// we already checked the first stop, so we start at the second
for (++stop; stop != lastStop; ++stop) {
// we break at the stop which is just after our t
if (stop->first > t)
break;
}
- //if ( *buffer.colorSpace() != *colorSpace()) {
- // buffer = KoColor(colorSpace());
- //}
- //hack to get a color space with the bitdepth of the gradients(8bit), but with the colour profile of the image//
- const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile());
+ leftStop = *(stop - 1);
+ rightStop = *(stop);
+ return true;
+ }
+}
- const KoGradientStop& leftStop = *(stop - 1);
- const KoGradientStop& rightStop = *(stop);
+void KoStopGradient::colorAt(KoColor& dst, qreal t) const
+{
+ KoColor buffer;
- KoColor startDummy, endDummy;
- if (mixSpace){
- startDummy = KoColor(leftStop.second, mixSpace);
- endDummy = KoColor(rightStop.second, mixSpace);
- } else {
- startDummy = leftStop.second;
- endDummy = rightStop.second;
- }
- const quint8 *colors[2];
- colors[0] = startDummy.data();
- colors[1] = endDummy.data();
-
- qreal localT;
- qreal stopDistance = rightStop.first - leftStop.first;
- if (stopDistance < DBL_EPSILON) {
- localT = 0.5;
- } else {
- localT = (t - leftStop.first) / stopDistance;
- }
- qint16 colorWeights[2];
- colorWeights[0] = static_cast<quint8>((1.0 - localT) * 255 + 0.5);
- colorWeights[1] = 255 - colorWeights[0];
+ KoGradientStop leftStop, rightStop;
+ if (!stopsAt(leftStop, rightStop, t)) return;
+ const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile());
- //check if our mixspace exists, it doesn't at startup.
- if (mixSpace){
- if (*buffer.colorSpace() != *mixSpace) {
- buffer = KoColor(mixSpace);
- }
- mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
- }
- else {
- buffer = KoColor(colorSpace());
- colorSpace()->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
- }
+ KoColor startDummy, endDummy;
+ if (mixSpace){
+ startDummy = KoColor(leftStop.second, mixSpace);
+ endDummy = KoColor(rightStop.second, mixSpace);
+ } else {
+ startDummy = leftStop.second;
+ endDummy = rightStop.second;
+ }
+ const quint8 *colors[2];
+ colors[0] = startDummy.data();
+ colors[1] = endDummy.data();
+
+ qreal localT;
+ qreal stopDistance = rightStop.first - leftStop.first;
+ if (stopDistance < DBL_EPSILON) {
+ localT = 0.5;
+ } else {
+ localT = (t - leftStop.first) / stopDistance;
+ }
+ qint16 colorWeights[2];
+ colorWeights[0] = static_cast<quint8>((1.0 - localT) * 255 + 0.5);
+ colorWeights[1] = 255 - colorWeights[0];
- dst.fromKoColor(buffer);
+
+ //check if our mixspace exists, it doesn't at startup.
+ if (mixSpace){
+ if (*buffer.colorSpace() != *mixSpace) {
+ buffer = KoColor(mixSpace);
+ }
+ mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
+ }
+ else {
+ buffer = KoColor(colorSpace());
+ colorSpace()->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
}
+
+
+ dst.fromKoColor(buffer);
}
KoStopGradient * KoStopGradient::fromQGradient(const QGradient * gradient)
{
if (! gradient)
return 0;
KoStopGradient * newGradient = new KoStopGradient(QString());
newGradient->setType(gradient->type());
newGradient->setSpread(gradient->spread());
switch (gradient->type()) {
case QGradient::LinearGradient: {
const QLinearGradient * g = static_cast<const QLinearGradient*>(gradient);
newGradient->m_start = g->start();
newGradient->m_stop = g->finalStop();
newGradient->m_focalPoint = g->start();
break;
}
case QGradient::RadialGradient: {
const QRadialGradient * g = static_cast<const QRadialGradient*>(gradient);
newGradient->m_start = g->center();
newGradient->m_stop = g->center() + QPointF(g->radius(), 0);
newGradient->m_focalPoint = g->focalPoint();
break;
}
case QGradient::ConicalGradient: {
const QConicalGradient * g = static_cast<const QConicalGradient*>(gradient);
qreal radian = g->angle() * M_PI / 180.0;
newGradient->m_start = g->center();
newGradient->m_stop = QPointF(100.0 * cos(radian), 100.0 * sin(radian));
newGradient->m_focalPoint = g->center();
break;
}
default:
delete newGradient;
return 0;
}
Q_FOREACH (const QGradientStop & stop, gradient->stops()) {
KoColor color(newGradient->colorSpace());
color.fromQColor(stop.second);
newGradient->m_stops.append(KoGradientStop(stop.first, color));
}
newGradient->setValid(true);
return newGradient;
}
void KoStopGradient::setStops(QList< KoGradientStop > stops)
{
m_stops.clear();
KoColor color;
Q_FOREACH (const KoGradientStop & stop, stops) {
color = stop.second;
color.convertTo(colorSpace());
m_stops.append(KoGradientStop(stop.first, color));
}
updatePreview();
}
QList<KoGradientStop> KoStopGradient::stops() const
{
return m_stops;
}
void KoStopGradient::loadSvgGradient(QIODevice *file)
{
QDomDocument doc;
if (!(doc.setContent(file)))
file->close();
else {
for (QDomNode n = doc.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) {
QDomElement e = n.toElement();
if (e.isNull()) continue;
if (e.tagName() == "linearGradient" || e.tagName() == "radialGradient") {
parseSvgGradient(e);
return;
}
// Inkscape gradients are in another defs
if (e.tagName() == "defs") {
for (QDomNode defnode = e.firstChild(); !defnode.isNull(); defnode = defnode.nextSibling()) {
QDomElement defelement = defnode.toElement();
if (defelement.isNull()) continue;
if (defelement.tagName() == "linearGradient" || defelement.tagName() == "radialGradient") {
parseSvgGradient(defelement);
return;
}
}
}
}
}
}
void KoStopGradient::parseSvgGradient(const QDomElement& element)
{
m_stops.clear();
setSpread(QGradient::PadSpread);
/*QString href = e.attribute( "xlink:href" ).mid( 1 );
if( !href.isEmpty() )
{
}*/
setName(element.attribute("id", i18n("SVG Gradient")));
const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8();
bool bbox = element.attribute("gradientUnits") != "userSpaceOnUse";
if (element.tagName() == "linearGradient") {
if (bbox) {
QString s;
s = element.attribute("x1", "0%");
qreal xOrigin;
if (s.endsWith('%'))
xOrigin = s.remove('%').toDouble();
else
xOrigin = s.toDouble() * 100.0;
s = element.attribute("y1", "0%");
qreal yOrigin;
if (s.endsWith('%'))
yOrigin = s.remove('%').toDouble();
else
yOrigin = s.toDouble() * 100.0;
s = element.attribute("x2", "100%");
qreal xVector;
if (s.endsWith('%'))
xVector = s.remove('%').toDouble();
else
xVector = s.toDouble() * 100.0;
s = element.attribute("y2", "0%");
qreal yVector;
if (s.endsWith('%'))
yVector = s.remove('%').toDouble();
else
yVector = s.toDouble() * 100.0;
m_start = QPointF(xOrigin, yOrigin);
m_stop = QPointF(xVector, yVector);
} else {
m_start = QPointF(element.attribute("x1").toDouble(), element.attribute("y1").toDouble());
m_stop = QPointF(element.attribute("x2").toDouble(), element.attribute("y2").toDouble());
}
setType(QGradient::LinearGradient);
} else {
if (bbox) {
QString s;
s = element.attribute("cx", "50%");
qreal xOrigin;
if (s.endsWith('%'))
xOrigin = s.remove('%').toDouble();
else
xOrigin = s.toDouble() * 100.0;
s = element.attribute("cy", "50%");
qreal yOrigin;
if (s.endsWith('%'))
yOrigin = s.remove('%').toDouble();
else
yOrigin = s.toDouble() * 100.0;
s = element.attribute("cx", "50%");
qreal xVector;
if (s.endsWith('%'))
xVector = s.remove('%').toDouble();
else
xVector = s.toDouble() * 100.0;
s = element.attribute("r", "50%");
if (s.endsWith('%'))
xVector += s.remove('%').toDouble();
else
xVector += s.toDouble() * 100.0;
s = element.attribute("cy", "50%");
qreal yVector;
if (s.endsWith('%'))
yVector = s.remove('%').toDouble();
else
yVector = s.toDouble() * 100.0;
s = element.attribute("fx", "50%");
qreal xFocal;
if (s.endsWith('%'))
xFocal = s.remove('%').toDouble();
else
xFocal = s.toDouble() * 100.0;
s = element.attribute("fy", "50%");
qreal yFocal;
if (s.endsWith('%'))
yFocal = s.remove('%').toDouble();
else
yFocal = s.toDouble() * 100.0;
m_start = QPointF(xOrigin, yOrigin);
m_stop = QPointF(xVector, yVector);
m_focalPoint = QPointF(xFocal, yFocal);
} else {
m_start = QPointF(element.attribute("cx").toDouble(), element.attribute("cy").toDouble());
m_stop = QPointF(element.attribute("cx").toDouble() + element.attribute("r").toDouble(),
element.attribute("cy").toDouble());
m_focalPoint = QPointF(element.attribute("fx").toDouble(), element.attribute("fy").toDouble());
}
setType(QGradient::RadialGradient);
}
// handle spread method
QString spreadMethod = element.attribute("spreadMethod");
if (!spreadMethod.isEmpty()) {
if (spreadMethod == "reflect")
setSpread(QGradient::ReflectSpread);
else if (spreadMethod == "repeat")
setSpread(QGradient::RepeatSpread);
}
for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
QDomElement colorstop = n.toElement();
if (colorstop.tagName() == "stop") {
qreal opacity = 0.0;
QColor c;
float off;
QString temp = colorstop.attribute("offset");
if (temp.contains('%')) {
temp = temp.left(temp.length() - 1);
off = temp.toFloat() / 100.0;
} else
off = temp.toFloat();
if (!colorstop.attribute("stop-color").isEmpty())
parseSvgColor(c, colorstop.attribute("stop-color"));
else {
// try style attr
QString style = colorstop.attribute("style").simplified();
QStringList substyles = style.split(';', QString::SkipEmptyParts);
Q_FOREACH (const QString & s, substyles) {
QStringList substyle = s.split(':');
QString command = substyle[0].trimmed();
QString params = substyle[1].trimmed();
if (command == "stop-color")
parseSvgColor(c, params);
if (command == "stop-opacity")
opacity = params.toDouble();
}
}
if (!colorstop.attribute("stop-opacity").isEmpty())
opacity = colorstop.attribute("stop-opacity").toDouble();
KoColor color(rgbColorSpace);
color.fromQColor(c);
color.setOpacity(static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5));
//According to the SVG spec each gradient offset has to be equal to or greater than the previous one
//if not it needs to be adjusted to be equal
if (m_stops.count() > 0 && m_stops.last().first >= off) {
off = m_stops.last().first;
}
m_stops.append(KoGradientStop(off, color));
}
}
}
void KoStopGradient::parseSvgColor(QColor &color, const QString &s)
{
if (s.startsWith("rgb(")) {
QString parse = s.trimmed();
QStringList colors = parse.split(',');
QString r = colors[0].right((colors[0].length() - 4));
QString g = colors[1];
QString b = colors[2].left((colors[2].length() - 1));
if (r.contains('%')) {
r = r.left(r.length() - 1);
r = QString::number(int((qreal(255 * r.toDouble()) / 100.0)));
}
if (g.contains('%')) {
g = g.left(g.length() - 1);
g = QString::number(int((qreal(255 * g.toDouble()) / 100.0)));
}
if (b.contains('%')) {
b = b.left(b.length() - 1);
b = QString::number(int((qreal(255 * b.toDouble()) / 100.0)));
}
color = QColor(r.toInt(), g.toInt(), b.toInt());
} else {
QString rgbColor = s.trimmed();
QColor c;
if (rgbColor.startsWith('#'))
c.setNamedColor(rgbColor);
else {
c = QColor(rgbColor);
}
color = c;
}
}
QString KoStopGradient::defaultFileExtension() const
{
return QString(".svg");
}
void KoStopGradient::toXML(QDomDocument &doc, QDomElement &gradientElt) const
{
gradientElt.setAttribute("type", "stop");
for (int s = 0; s < m_stops.size(); s++) {
KoGradientStop stop = m_stops.at(s);
QDomElement stopElt = doc.createElement("stop");
stopElt.setAttribute("offset", KisDomUtils::toString(stop.first));
stopElt.setAttribute("bitdepth", stop.second.colorSpace()->colorDepthId().id());
stopElt.setAttribute("alpha", KisDomUtils::toString(stop.second.opacityF()));
stop.second.toXML(doc, stopElt);
gradientElt.appendChild(stopElt);
}
}
KoStopGradient KoStopGradient::fromXML(const QDomElement &elt)
{
KoStopGradient gradient;
QList<KoGradientStop> stops;
QDomElement stopElt = elt.firstChildElement("stop");
while (!stopElt.isNull()) {
qreal offset = KisDomUtils::toDouble(stopElt.attribute("offset", "0.0"));
QString bitDepth = stopElt.attribute("bitdepth", Integer8BitsColorDepthID.id());
KoColor color = KoColor::fromXML(stopElt.firstChildElement(), bitDepth);
color.setOpacity(KisDomUtils::toDouble(stopElt.attribute("alpha", "1.0")));
stops.append(KoGradientStop(offset, color));
stopElt = stopElt.nextSiblingElement("stop");
}
gradient.setStops(stops);
return gradient;
}
bool KoStopGradient::saveToDevice(QIODevice *dev) const
{
QTextStream stream(dev);
const QString spreadMethod[3] = {
QString("spreadMethod=\"pad\" "),
QString("spreadMethod=\"reflect\" "),
QString("spreadMethod=\"repeat\" ")
};
const QString indent = " ";
stream << "<svg>" << endl;
stream << indent;
stream << "<linearGradient id=\"" << name() << "\" ";
stream << "gradientUnits=\"objectBoundingBox\" ";
stream << spreadMethod[spread()];
stream << ">" << endl;
QColor color;
// color stops
Q_FOREACH (const KoGradientStop & stop, m_stops) {
stop.second.toQColor(&color);
stream << indent << indent;
stream << "<stop stop-color=\"";
stream << color.name();
stream << "\" offset=\"" << QString().setNum(stop.first);
stream << "\" stop-opacity=\"" << static_cast<float>(color.alpha()) / 255.0f << "\"" << " />" << endl;
}
stream << indent;
stream << "</linearGradient>" << endl;
stream << "</svg>" << endl;
KoResource::saveToDevice(dev);
return true;
}
diff --git a/libs/pigment/resources/KoStopGradient.h b/libs/pigment/resources/KoStopGradient.h
index b7161166e8..e9a4f31ab4 100644
--- a/libs/pigment/resources/KoStopGradient.h
+++ b/libs/pigment/resources/KoStopGradient.h
@@ -1,94 +1,97 @@
/*
Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef KOSTOPGRADIENT_H
#define KOSTOPGRADIENT_H
#include <QPair>
#include <QGradient>
#include "KoColor.h"
#include <resources/KoAbstractGradient.h>
#include <resources/KoResource.h>
#include <kritapigment_export.h>
#include <boost/operators.hpp>
typedef QPair<qreal, KoColor> KoGradientStop;
/**
* Resource for colorstop based gradients like SVG gradients
*/
class KRITAPIGMENT_EXPORT KoStopGradient : public KoAbstractGradient, public boost::equality_comparable<KoStopGradient>
{
public:
explicit KoStopGradient(const QString &filename = QString());
~KoStopGradient() override;
bool operator==(const KoStopGradient &rhs) const;
KoAbstractGradient* clone() const override;
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
/// reimplemented
QGradient* toQGradient() const override;
+ /// Find stops surrounding position, returns false if position outside gradient
+ bool stopsAt(KoGradientStop& leftStop, KoGradientStop& rightStop, qreal t) const;
+
/// reimplemented
void colorAt(KoColor&, qreal t) const override;
/// Creates KoStopGradient from a QGradient
static KoStopGradient * fromQGradient(const QGradient * gradient);
/// Sets the gradient stops
void setStops(QList<KoGradientStop> stops);
QList<KoGradientStop> stops() const;
/// reimplemented
QString defaultFileExtension() const override;
/**
* @brief toXML
* Convert the gradient to an XML string.
*/
void toXML(QDomDocument& doc, QDomElement& gradientElt) const;
/**
* @brief fromXML
* convert a gradient from xml.
* @return a gradient.
*/
static KoStopGradient fromXML(const QDomElement& elt);
protected:
QList<KoGradientStop> m_stops;
QPointF m_start;
QPointF m_stop;
QPointF m_focalPoint;
private:
void loadSvgGradient(QIODevice *file);
void parseSvgGradient(const QDomElement& element);
void parseSvgColor(QColor &color, const QString &s);
};
#endif // KOSTOPGRADIENT_H
diff --git a/libs/store/KoLegacyZipStore.cpp b/libs/store/KoLegacyZipStore.cpp
index 72ceb155d8..121992ede0 100644
--- a/libs/store/KoLegacyZipStore.cpp
+++ b/libs/store/KoLegacyZipStore.cpp
@@ -1,272 +1,272 @@
/* This file is part of the KDE project
Copyright (C) 2000-2002 David Faure <faure@kde.org>
Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
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 "KoLegacyZipStore.h"
#include "KoStore_p.h"
#include <QBuffer>
#include <QByteArray>
#include <QTemporaryFile>
#include <kzip.h>
#include <StoreDebug.h>
#include <QUrl>
-class SaveZip : public KZip {
+class SafeZip : public KZip {
public:
- SaveZip(const QString &filename) : KZip(filename) {}
- SaveZip(QIODevice *dev) : KZip(dev) {}
- ~SaveZip() override {}
+ SafeZip(const QString &filename) : KZip(filename) {}
+ SafeZip(QIODevice *dev) : KZip(dev) {}
+ ~SafeZip() override {}
void resetDevice() {
closeArchive();
setDevice(0);
}
};
KoLegacyZipStore::KoLegacyZipStore(const QString & _filename, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
// qDebug() << "KoLegacyZipStore Constructor filename =" << _filename
// << " mode = " << int(mode)
// << " mimetype = " << appIdentification;
Q_D(KoStore);
d->localFileName = _filename;
- m_pZip = new SaveZip(_filename);
+ m_pZip = new SafeZip(_filename);
init(appIdentification); // open the zip file and init some vars
}
KoLegacyZipStore::KoLegacyZipStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
// qDebug() << "KoLegacyZipStore Constructor device =" << dev
// << " mode = " << int(mode)
// << " mimetype = " << appIdentification;
- m_pZip = new SaveZip(dev);
+ m_pZip = new SafeZip(dev);
init(appIdentification);
}
KoLegacyZipStore::KoLegacyZipStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode mode,
const QByteArray & appIdentification, bool writeMimetype)
: KoStore(mode, writeMimetype)
{
debugStore << "KoLegacyZipStore Constructor url" << _url.url(QUrl::PreferLocalFile)
<< " filename = " << _filename
<< " mode = " << int(mode)
<< " mimetype = " << appIdentification;
Q_D(KoStore);
d->url = _url;
d->window = window;
if (mode == KoStore::Read) {
d->localFileName = _filename;
} else {
QTemporaryFile f("kozip");
f.open();
d->localFileName = f.fileName();
f.close();
}
- m_pZip = new SaveZip(d->localFileName);
+ m_pZip = new SafeZip(d->localFileName);
init(appIdentification); // open the zip file and init some vars
}
KoLegacyZipStore::~KoLegacyZipStore()
{
Q_D(KoStore);
// bool sf = false;
// if (m_pZip && m_pZip->device()) {
// sf = true;
// }
// qDebug() << "KoLegacyZipStore::~KoLegacyZipStore" << d->localFileName << m_pZip << m_pZip->device() << "savefile" << sf;
if (m_pZip->device() && m_pZip->device()->inherits("QSaveFile")) {
m_pZip->resetDevice(); // otherwise, kzip's destructor will call close(), which aborts on a qsavefile
}
else {
if (!d->finalized) {
finalize(); // ### no error checking when the app forgot to call finalize itself
}
}
delete m_pZip;
// When writing, we write to a temp file that then gets copied over the original filename
if (d->mode == Write && (!d->localFileName.isEmpty() && !d->url.isEmpty())) {
QFile f(d->localFileName);
if (f.copy(d->url.toLocalFile())) {
f.remove();
}
}
}
void KoLegacyZipStore::init(const QByteArray& appIdentification)
{
Q_D(KoStore);
m_currentDir = 0;
d->good = m_pZip->open(d->mode == Write ? QIODevice::WriteOnly : QIODevice::ReadOnly);
if (!d->good)
return;
if (d->mode == Write) {
//debugStore <<"KoLegacyZipStore::init writing mimetype" << appIdentification;
m_pZip->setCompression(KZip::NoCompression);
m_pZip->setExtraField(KZip::NoExtraField);
// Write identification
if (d->writeMimetype) {
(void)m_pZip->writeFile(QLatin1String("mimetype"), appIdentification);
}
m_pZip->setCompression(KZip::DeflateCompression);
// We don't need the extra field in Krita - so we leave it as "no extra field".
} else {
d->good = m_pZip->directory() != 0;
}
}
void KoLegacyZipStore::setCompressionEnabled(bool e)
{
if (e) {
m_pZip->setCompression(KZip::DeflateCompression);
} else {
m_pZip->setCompression(KZip::NoCompression);
}
}
bool KoLegacyZipStore::doFinalize()
{
if (m_pZip && m_pZip->device() && !m_pZip->device()->inherits("QSaveFile")) {
return m_pZip->close();
}
else {
return true;
}
}
bool KoLegacyZipStore::openWrite(const QString& name)
{
Q_D(KoStore);
d->stream = 0; // Don't use!
return m_pZip->prepareWriting(name, "", "" /*m_pZip->rootDir()->user(), m_pZip->rootDir()->group()*/, 0);
}
bool KoLegacyZipStore::openRead(const QString& name)
{
Q_D(KoStore);
const KArchiveEntry * entry = m_pZip->directory()->entry(name);
if (entry == 0) {
return false;
}
if (entry->isDirectory()) {
warnStore << name << " is a directory !";
return false;
}
// Must cast to KZipFileEntry, not only KArchiveFile, because device() isn't virtual!
const KZipFileEntry * f = static_cast<const KZipFileEntry *>(entry);
delete d->stream;
d->stream = f->createDevice();
d->size = f->size();
return true;
}
qint64 KoLegacyZipStore::write(const char* _data, qint64 _len)
{
Q_D(KoStore);
if (_len == 0) return 0;
//debugStore <<"KoLegacyZipStore::write" << _len;
if (!d->isOpen) {
errorStore << "KoStore: You must open before writing" << endl;
return 0;
}
if (d->mode != Write) {
errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
return 0;
}
d->size += _len;
if (m_pZip->writeData(_data, _len)) // writeData returns a bool!
return _len;
return 0;
}
QStringList KoLegacyZipStore::directoryList() const
{
QStringList retval;
const KArchiveDirectory *directory = m_pZip->directory();
Q_FOREACH (const QString &name, directory->entries()) {
const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
if (fileArchiveEntry->isDirectory()) {
retval << name;
}
}
return retval;
}
bool KoLegacyZipStore::closeWrite()
{
Q_D(KoStore);
debugStore << "Wrote file" << d->fileName << " into ZIP archive. size" << d->size;
return m_pZip->finishWriting(d->size);
}
bool KoLegacyZipStore::enterRelativeDirectory(const QString& dirName)
{
Q_D(KoStore);
if (d->mode == Read) {
if (!m_currentDir) {
m_currentDir = m_pZip->directory(); // initialize
Q_ASSERT(d->currentPath.isEmpty());
}
const KArchiveEntry *entry = m_currentDir->entry(dirName);
if (entry && entry->isDirectory()) {
m_currentDir = dynamic_cast<const KArchiveDirectory*>(entry);
return m_currentDir != 0;
}
return false;
} else // Write, no checking here
return true;
}
bool KoLegacyZipStore::enterAbsoluteDirectory(const QString& path)
{
if (path.isEmpty()) {
m_currentDir = 0;
return true;
}
m_currentDir = dynamic_cast<const KArchiveDirectory*>(m_pZip->directory()->entry(path));
Q_ASSERT(m_currentDir);
return m_currentDir != 0;
}
bool KoLegacyZipStore::fileExists(const QString& absPath) const
{
const KArchiveEntry *entry = m_pZip->directory()->entry(absPath);
return entry && entry->isFile();
}
diff --git a/libs/store/KoLegacyZipStore.h b/libs/store/KoLegacyZipStore.h
index e9d414c82b..6e31ffdd9f 100644
--- a/libs/store/KoLegacyZipStore.h
+++ b/libs/store/KoLegacyZipStore.h
@@ -1,73 +1,73 @@
/* This file is part of the KDE project
Copyright (C) 2002 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License 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 koZipStore_h
#define koZipStore_h
#include "KoStore.h"
-class SaveZip;
+class SafeZip;
class KArchiveDirectory;
class QUrl;
class KoLegacyZipStore : public KoStore
{
public:
KoLegacyZipStore(const QString & _filename, Mode _mode, const QByteArray & appIdentification,
bool writeMimetype = true);
KoLegacyZipStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype = true);
/**
* QUrl-constructor
* @todo saving not completely implemented (fixed temporary file)
*/
KoLegacyZipStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode _mode,
const QByteArray & appIdentification, bool writeMimetype = true);
~KoLegacyZipStore() override;
void setCompressionEnabled(bool e) override;
qint64 write(const char* _data, qint64 _len) override;
QStringList directoryList() const override;
protected:
void init(const QByteArray& appIdentification);
bool doFinalize() override;
bool openWrite(const QString& name) override;
bool openRead(const QString& name) override;
bool closeWrite() override;
bool closeRead() override {
return true;
}
bool enterRelativeDirectory(const QString& dirName) override;
bool enterAbsoluteDirectory(const QString& path) override;
bool fileExists(const QString& absPath) const override;
private:
// The archive
- SaveZip * m_pZip;
+ SafeZip * m_pZip;
// In "Read" mode this pointer is pointing to the current directory in the archive to speed up the verification process
const KArchiveDirectory* m_currentDir;
Q_DECLARE_PRIVATE(KoStore)
};
#endif
diff --git a/libs/store/KoQuaZipStore.cpp b/libs/store/KoQuaZipStore.cpp
index 7ac1a17215..6e09f75921 100644
--- a/libs/store/KoQuaZipStore.cpp
+++ b/libs/store/KoQuaZipStore.cpp
@@ -1,292 +1,291 @@
/*
* Copyright (C) 2019 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoQuaZipStore.h"
#include "KoStore_p.h"
#include <StoreDebug.h>
#include <zlib.h>
#include <quazip.h>
#include <quazipfile.h>
#include <quazipdir.h>
#include <quazipfileinfo.h>
#include <quazipnewinfo.h>
#include <QTemporaryFile>
#include <QTextCodec>
#include <QByteArray>
#include <QBuffer>
#include <KConfig>
#include <KSharedConfig>
#include <KConfigGroup>
struct KoQuaZipStore::Private {
Private() {}
~Private() {}
QuaZip *archive {0};
QuaZipFile *currentFile {0};
int compressionLevel {Z_DEFAULT_COMPRESSION};
bool usingSaveFile {false};
QByteArray cache;
QBuffer buffer;
};
KoQuaZipStore::KoQuaZipStore(const QString &_filename, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype)
: KoStore(_mode, writeMimetype)
, dd(new Private())
{
Q_D(KoStore);
d->localFileName = _filename;
dd->archive = new QuaZip(_filename);
init(appIdentification);
}
KoQuaZipStore::KoQuaZipStore(QIODevice *dev, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype)
: KoStore(_mode, writeMimetype)
, dd(new Private())
{
dd->archive = new QuaZip(dev);
init(appIdentification);
}
KoQuaZipStore::~KoQuaZipStore()
{
Q_D(KoStore);
if (dd->currentFile && dd->currentFile->isOpen()) {
dd->currentFile->close();
}
if (!d->finalized) {
finalize();
}
delete dd->archive;
delete dd->currentFile;
}
void KoQuaZipStore::setCompressionEnabled(bool enabled)
{
if (enabled) {
dd->compressionLevel = Z_BEST_COMPRESSION;
}
else {
dd->compressionLevel = Z_NO_COMPRESSION;
}
}
qint64 KoQuaZipStore::write(const char *_data, qint64 _len)
{
Q_D(KoStore);
if (_len == 0) return 0;
if (!d->isOpen) {
errorStore << "KoStore: You must open before writing" << endl;
return 0;
}
if (d->mode != Write) {
errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
return 0;
}
d->size += _len;
if (dd->buffer.write(_data, _len)) { // writeData returns a bool!
return _len;
}
return 0;
}
QStringList KoQuaZipStore::directoryList() const
{
return dd->archive->getFileNameList();
}
void KoQuaZipStore::init(const QByteArray &appIdentification)
{
Q_D(KoStore);
bool enableZip64 = false;
if (appIdentification == "application/x-krita") {
enableZip64 = KSharedConfig::openConfig()->group("").readEntry<bool>("UseZip64", false);
}
dd->archive->setZip64Enabled(enableZip64);
dd->archive->setFileNameCodec("UTF-8");
dd->usingSaveFile = dd->archive->getIoDevice() && dd->archive->getIoDevice()->inherits("QSaveFile");
dd->archive->setAutoClose(!dd->usingSaveFile);
d->good = dd->archive->open(d->mode == Write ? QuaZip::mdCreate : QuaZip::mdUnzip);
if (!d->good) {
return;
}
if (d->mode == Write) {
if (d->writeMimetype) {
QuaZipFile f(dd->archive);
QuaZipNewInfo newInfo("mimetype");
newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther);
if (!f.open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, Z_NO_COMPRESSION)) {
d->good = false;
return;
}
f.write(appIdentification);
f.close();
}
}
else {
debugStore << dd->archive->getEntriesCount() << dd->archive->getFileNameList();
d->good = dd->archive->getEntriesCount();
}
}
bool KoQuaZipStore::doFinalize()
{
Q_D(KoStore);
d->stream = 0;
if (!dd->usingSaveFile) {
dd->archive->close();
}
return dd->archive->getZipError() == ZIP_OK;
}
bool KoQuaZipStore::openWrite(const QString &name)
{
Q_D(KoStore);
QString fixedPath = name;
fixedPath.replace("//", "/");
delete d->stream;
d->stream = 0; // Not used when writing
delete dd->currentFile;
dd->currentFile = new QuaZipFile(dd->archive);
QuaZipNewInfo newInfo(fixedPath);
newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther);
bool r = dd->currentFile->open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, dd->compressionLevel);
if (!r) {
qWarning() << "Could not open" << name << dd->currentFile->getZipError();
}
dd->cache = QByteArray();
dd->buffer.setBuffer(&dd->cache);
dd->buffer.open(QBuffer::WriteOnly);
return r;
}
bool KoQuaZipStore::openRead(const QString &name)
{
Q_D(KoStore);
QString fixedPath = name;
fixedPath.replace("//", "/");
- if (!d->substituteThis.isEmpty()) {
- fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith);
- }
-
delete d->stream;
d->stream = 0;
delete dd->currentFile;
dd->currentFile = 0;
if (!currentPath().isEmpty() && !fixedPath.startsWith(currentPath())) {
fixedPath = currentPath() + '/' + fixedPath;
}
+ if (!d->substituteThis.isEmpty()) {
+ fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith);
+ }
if (!dd->archive->setCurrentFile(fixedPath)) {
qWarning() << "\t\tCould not set current file" << dd->archive->getZipError() << fixedPath;
return false;
}
dd->currentFile = new QuaZipFile(dd->archive);
if (!dd->currentFile->open(QIODevice::ReadOnly)) {
qWarning() << "\t\t\tBut could not open!!!" << dd->archive->getZipError();
return false;
}
d->stream = dd->currentFile;
d->size = dd->currentFile->size();
return true;
}
bool KoQuaZipStore::closeWrite()
{
Q_D(KoStore);
bool r = true;
if (!dd->currentFile->write(dd->cache)) {
qWarning() << "Could not write buffer to the file";
r = false;
}
dd->buffer.close();
dd->currentFile->close();
d->stream = 0;
return (r && dd->currentFile->getZipError() == ZIP_OK);
}
bool KoQuaZipStore::closeRead()
{
Q_D(KoStore);
d->stream = 0;
return true;
}
bool KoQuaZipStore::enterRelativeDirectory(const QString & /*path*/)
{
return true;
}
bool KoQuaZipStore::enterAbsoluteDirectory(const QString &path)
{
QString fixedPath = path;
fixedPath.replace("//", "/");
if (fixedPath.isEmpty()) {
fixedPath = "/";
}
QuaZipDir currentDir (dd->archive, fixedPath);
return currentDir.exists();
}
bool KoQuaZipStore::fileExists(const QString &absPath) const
{
Q_D(const KoStore);
QString fixedPath = absPath;
fixedPath.replace("//", "/");
if (!d->substituteThis.isEmpty()) {
fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith);
}
return dd->archive->getFileNameList().contains(fixedPath);
}
diff --git a/libs/store/KoStore.cpp b/libs/store/KoStore.cpp
index 7854a358ac..6fc071041a 100644
--- a/libs/store/KoStore.cpp
+++ b/libs/store/KoStore.cpp
@@ -1,477 +1,477 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2002 David Faure <faure@kde.org>, Werner Trobin <trobin@kde.org>
Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
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 "KoStore.h"
#include "KoStore_p.h"
#include "KoLegacyZipStore.h"
#include "KoQuaZipStore.h"
#include "KoDirectoryStore.h"
#include <QBuffer>
#include <QFileInfo>
#include <QFile>
#include <QUrl>
#include <StoreDebug.h>
#include <KConfig>
#include <KSharedConfig>
#include <KConfigGroup>
#define DefaultFormat KoStore::Zip
static KoStore::Backend determineBackend(QIODevice *dev)
{
unsigned char buf[5];
if (dev->read((char *)buf, 4) < 4)
return DefaultFormat; // will create a "bad" store (bad()==true)
if (buf[0] == 'P' && buf[1] == 'K' && buf[2] == 3 && buf[3] == 4)
return KoStore::Zip;
return DefaultFormat; // fallback
}
KoStore* KoStore::createStore(const QString& fileName, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype)
{
if (backend == Auto) {
if (mode == KoStore::Write)
backend = DefaultFormat;
else {
QFileInfo inf(fileName);
if (inf.isDir())
backend = Directory;
else {
QFile file(fileName);
if (file.open(QIODevice::ReadOnly))
backend = determineBackend(&file);
else
backend = DefaultFormat; // will create a "bad" store (bad()==true)
}
}
}
switch (backend) {
case Zip:
- if (appIdentification == "application/x-krita" && KSharedConfig::openConfig()->group("").readEntry<bool>("UseZip64", false)) {
+ if (mode == KoStore::Read || (appIdentification == "application/x-krita" && KSharedConfig::openConfig()->group("").readEntry<bool>("UseZip64", false))) {
return new KoQuaZipStore(fileName, mode, appIdentification, writeMimetype);
}
else {
KoStore *store = new KoLegacyZipStore(fileName, mode, appIdentification, writeMimetype);
if (store->bad()) {
return new KoQuaZipStore(fileName, mode, appIdentification, writeMimetype);
}
return store;
}
case Directory:
return new KoDirectoryStore(fileName /* should be a dir name.... */, mode, writeMimetype);
default:
warnStore << "Unsupported backend requested for KoStore : " << backend;
return 0;
}
}
KoStore* KoStore::createStore(QIODevice *device, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype)
{
if (backend == Auto) {
if (mode == KoStore::Write)
backend = DefaultFormat;
else {
if (device->open(QIODevice::ReadOnly)) {
backend = determineBackend(device);
device->close();
}
}
}
switch (backend) {
case Directory:
errorStore << "Can't create a Directory store for a memory buffer!" << endl;
return 0;
case Zip:
- if (appIdentification == "application/x-krita" && KSharedConfig::openConfig()->group("").readEntry<bool>("UseZip64", false)) {
+ if (mode == KoStore::Read || (appIdentification == "application/x-krita" && KSharedConfig::openConfig()->group("").readEntry<bool>("UseZip64", false))) {
return new KoQuaZipStore(device, mode, appIdentification, writeMimetype);
}
else {
KoStore *store = new KoLegacyZipStore(device, mode, appIdentification, writeMimetype);
if (store->bad()) {
return new KoQuaZipStore(device, mode, appIdentification, writeMimetype);
}
return store;
}
default:
warnStore << "Unsupported backend requested for KoStore : " << backend;
return 0;
}
}
namespace
{
const char ROOTPART[] = "root";
const char MAINNAME[] = "maindoc.xml";
}
KoStore::KoStore(Mode mode, bool writeMimetype)
: d_ptr(new KoStorePrivate(this, mode, writeMimetype))
{}
KoStore::~KoStore()
{
Q_D(KoStore);
delete d->stream;
delete d_ptr;
}
bool KoStore::open(const QString & _name)
{
Q_D(KoStore);
// This also converts from relative to absolute, i.e. merges the currentPath()
d->fileName = d->toExternalNaming(_name);
debugStore << "KOStore" << _name << d->fileName;
if (d->isOpen) {
warnStore << "Store is already opened, missing close";
return false;
}
if (d->fileName.length() > 512) {
errorStore << "KoStore: Filename " << d->fileName << " is too long" << endl;
return false;
}
if (d->mode == Write) {
debugStore << "opening for writing" << d->fileName;
if (d->filesList.contains(d->fileName)) {
warnStore << "KoStore: Duplicate filename" << d->fileName;
return false;
}
d->filesList.append(d->fileName);
d->size = 0;
if (!openWrite(d->fileName))
return false;
} else if (d->mode == Read) {
debugStore << "Opening for reading" << d->fileName;
if (!openRead(d->fileName))
return false;
} else
return false;
d->isOpen = true;
return true;
}
bool KoStore::isOpen() const
{
Q_D(const KoStore);
return d->isOpen;
}
bool KoStore::close()
{
Q_D(KoStore);
if (!d->isOpen) {
warnStore << "You must open before closing";
return false;
}
bool ret = d->mode == Write ? closeWrite() : closeRead();
delete d->stream;
d->stream = 0;
d->isOpen = false;
return ret;
}
QIODevice* KoStore::device() const
{
Q_D(const KoStore);
if (!d->isOpen)
warnStore << "You must open before asking for a device";
if (d->mode != Read)
warnStore << "Can not get device from store that is opened for writing";
return d->stream;
}
QByteArray KoStore::read(qint64 max)
{
Q_D(KoStore);
QByteArray data;
if (!d->isOpen) {
warnStore << "You must open before reading";
return data;
}
if (d->mode != Read) {
errorStore << "KoStore: Can not read from store that is opened for writing" << endl;
return data;
}
return d->stream->read(max);
}
qint64 KoStore::write(const QByteArray& data)
{
return write(data.constData(), data.size()); // see below
}
qint64 KoStore::read(char *_buffer, qint64 _len)
{
Q_D(KoStore);
if (!d->isOpen) {
errorStore << "KoStore: You must open before reading" << endl;
return -1;
}
if (d->mode != Read) {
errorStore << "KoStore: Can not read from store that is opened for writing" << endl;
return -1;
}
return d->stream->read(_buffer, _len);
}
qint64 KoStore::write(const char* _data, qint64 _len)
{
Q_D(KoStore);
if (_len == 0) return 0;
if (!d->isOpen) {
errorStore << "KoStore: You must open before writing" << endl;
return 0;
}
if (d->mode != Write) {
errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
return 0;
}
int nwritten = d->stream->write(_data, _len);
Q_ASSERT(nwritten == (int)_len);
d->size += nwritten;
return nwritten;
}
qint64 KoStore::size() const
{
Q_D(const KoStore);
if (!d->isOpen) {
warnStore << "You must open before asking for a size";
return static_cast<qint64>(-1);
}
if (d->mode != Read) {
warnStore << "Can not get size from store that is opened for writing";
return static_cast<qint64>(-1);
}
return d->size;
}
bool KoStore::enterDirectory(const QString &directory)
{
Q_D(KoStore);
//debugStore <<"enterDirectory" << directory;
int pos;
bool success = true;
QString tmp(directory);
while ((pos = tmp.indexOf('/')) != -1 &&
(success = d->enterDirectoryInternal(tmp.left(pos))))
tmp.remove(0, pos + 1);
if (success && !tmp.isEmpty())
return d->enterDirectoryInternal(tmp);
return success;
}
bool KoStore::leaveDirectory()
{
Q_D(KoStore);
if (d->currentPath.isEmpty())
return false;
d->currentPath.pop_back();
return enterAbsoluteDirectory(currentPath());
}
QString KoStore::currentPath() const
{
Q_D(const KoStore);
QString path;
QStringList::ConstIterator it = d->currentPath.begin();
QStringList::ConstIterator end = d->currentPath.end();
for (; it != end; ++it) {
path += *it;
path += '/';
}
return path;
}
void KoStore::pushDirectory()
{
Q_D(KoStore);
d->directoryStack.push(currentPath());
}
void KoStore::popDirectory()
{
Q_D(KoStore);
d->currentPath.clear();
enterAbsoluteDirectory(QString());
enterDirectory(d->directoryStack.pop());
}
bool KoStore::extractFile(const QString &srcName, QByteArray &data)
{
Q_D(KoStore);
QBuffer buffer(&data);
return d->extractFile(srcName, buffer);
}
bool KoStorePrivate::extractFile(const QString &srcName, QIODevice &buffer)
{
if (!q->open(srcName))
return false;
if (!buffer.open(QIODevice::WriteOnly)) {
q->close();
return false;
}
QByteArray data;
data.resize(8 * 1024);
uint total = 0;
for (int block = 0; (block = q->read(data.data(), data.size())) > 0; total += block) {
buffer.write(data.data(), block);
}
if (q->size() != static_cast<qint64>(-1))
Q_ASSERT(total == q->size());
buffer.close();
q->close();
return true;
}
bool KoStore::seek(qint64 pos)
{
Q_D(KoStore);
return d->stream->seek(pos);
}
qint64 KoStore::pos() const
{
Q_D(const KoStore);
return d->stream->pos();
}
bool KoStore::atEnd() const
{
Q_D(const KoStore);
return d->stream->atEnd();
}
// See the specification for details of what this function does.
QString KoStorePrivate::toExternalNaming(const QString & _internalNaming) const
{
if (_internalNaming == ROOTPART)
return q->currentPath() + MAINNAME;
QString intern;
if (_internalNaming.startsWith("tar:/")) // absolute reference
intern = _internalNaming.mid(5); // remove protocol
else
intern = q->currentPath() + _internalNaming;
return intern;
}
bool KoStorePrivate::enterDirectoryInternal(const QString &directory)
{
if (q->enterRelativeDirectory(directory)) {
currentPath.append(directory);
return true;
}
return false;
}
bool KoStore::hasFile(const QString& fileName) const
{
Q_D(const KoStore);
return fileExists(d->toExternalNaming(fileName));
}
bool KoStore::hasDirectory(const QString &directoryName)
{
return enterAbsoluteDirectory(directoryName);
}
bool KoStore::finalize()
{
Q_D(KoStore);
Q_ASSERT(!d->finalized); // call this only once!
d->finalized = true;
return doFinalize();
}
void KoStore::setCompressionEnabled(bool /*e*/)
{
}
void KoStore::setSubstitution(const QString &name, const QString &substitution)
{
Q_D(KoStore);
d->substituteThis = name;
d->substituteWith = substitution;
}
bool KoStore::isEncrypted()
{
return false;
}
bool KoStore::setPassword(const QString& /*password*/)
{
return false;
}
QString KoStore::password()
{
return QString();
}
bool KoStore::bad() const
{
Q_D(const KoStore);
return !d->good;
}
KoStore::Mode KoStore::mode() const
{
Q_D(const KoStore);
return d->mode;
}
QStringList KoStore::directoryList() const
{
return QStringList();
}
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index 332f41950e..8e6f7b3ac5 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,618 +1,626 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
)
if (ANDROID)
add_definitions(-DQT_OPENGL_ES_3)
add_definitions(-DHAS_ONLY_OPENGL_ES)
endif()
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
find_library(APPKIT_LIBRARY AppKit)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_paintop_transformation_connector.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
canvas/KisSnapPointStrategy.cpp
canvas/KisSnapPixelStrategy.cpp
canvas/KisMirrorAxisConfig.cpp
dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/KisSessionManagerDialog.cpp
dialogs/KisNewWindowLayoutDialog.cpp
+ dialogs/KisDlgChangeCloneSource.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
KisPaintopPropertiesBase.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
KisOcioConfiguration.cpp
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
kis_change_file_layer_command.h
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
KisImageBarrierLockerWithFeedback.cpp
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
KisNodeDisplayModeAdapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
KisDecorationsManager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
KisResourceServerProvider.cpp
KisResourceBundleServerProvider.cpp
KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
KisSelectionActionsAdapter.cpp
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
KisActionPlugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
KisWelcomePageWidget.cpp
+ KisChangeCloneLayersCommand.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
opengl/kis_texture_tile_info_pool.cpp
opengl/KisOpenGLUpdateInfoBuilder.cpp
opengl/KisOpenGLModeProber.cpp
opengl/KisScreenInformationAdapter.cpp
kis_fps_decoration.cpp
tool/KisToolChangesTracker.cpp
tool/KisToolChangesTrackerData.cpp
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/KisStrokeSpeedMonitor.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/KisStrokeEfficiencyMeasurer.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
tool/strokes/KisFreehandStrokeInfo.cpp
tool/strokes/KisMaskedFreehandStrokePainter.cpp
tool/strokes/KisMaskingBrushRenderer.cpp
tool/strokes/KisMaskingBrushCompositeOpFactory.cpp
tool/strokes/move_stroke_strategy.cpp
tool/KisSelectionToolFactoryBase.cpp
tool/KisToolPaintFactoryBase.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_paintop_presets_save.cpp
widgets/kis_paintop_preset_icon_library.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/KisSelectionPropertySlider.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_preset_live_preview_view.cpp
widgets/KisScreenColorPicker.cpp
widgets/KoDualColorButton.cpp
widgets/KoStrokeConfigWidget.cpp
widgets/KoFillConfigWidget.cpp
widgets/KisLayerStyleAngleSelector.cpp
widgets/KisMemoryReportButton.cpp
+ widgets/KisDitherWidget.cpp
KisPaletteEditor.cpp
dialogs/KisDlgPaletteEditor.cpp
widgets/KisNewsWidget.cpp
widgets/KisGamutMaskToolbar.cpp
utils/kis_document_aware_spin_box_unit_manager.cpp
utils/KisSpinBoxSplineUnitConverter.cpp
+ utils/KisClipboardUtil.cpp
+ utils/KisDitherUtil.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_native_gesture_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
input/KisQtWidgetsTweaker.cpp
input/KisInputActionGroup.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
- actions/KisPasteActionFactory.cpp
+ actions/KisPasteActionFactories.cpp
actions/KisTransformToolActivationCommand.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
KisCloneDocumentStroke.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisFilterEntry.cpp
KisImportExportManager.cpp
KisImportExportUtils.cpp
kis_async_action_feedback.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoActionsUpdateManager.cpp
KisView.cpp
KisImportExportErrorCode.cpp
+ KisImportExportAdditionalChecks.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisSaveGroupVisitor.cpp
KisWindowLayoutResource.cpp
KisWindowLayoutManager.cpp
KisSessionResource.cpp
KisReferenceImagesDecoration.cpp
KisReferenceImage.cpp
flake/KisReferenceImagesLayer.cpp
flake/KisReferenceImagesLayer.h
-
+ KisMouseClickEater.cpp
)
if(WIN32)
# Private headers are needed for:
# * KisDlgCustomTabletResolution
# * KisScreenInformationAdapter
include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
qtlockedfile/qtlockedfile_win.cpp
)
if (NOT USE_QT_TABLET_WINDOWS)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
input/wintab/kis_tablet_support_win8.cpp
)
else()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
dialogs/KisDlgCustomTabletResolution.cpp
)
endif()
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
KisAsyncAnimationRendererBase.cpp
KisAsyncAnimationCacheRenderer.cpp
KisAsyncAnimationFramesSavingRenderer.cpp
dialogs/KisAsyncAnimationRenderDialogBase.cpp
dialogs/KisAsyncAnimationCacheRenderDialog.cpp
dialogs/KisAsyncAnimationFramesSaveDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
KisFrameDataSerializer.cpp
KisFrameCacheStore.cpp
KisFrameCacheSwapper.cpp
KisAbstractFrameCacheSwapper.cpp
KisInMemoryFrameCacheSwapper.cpp
input/wintab/drawpile_tablettester/tablettester.cpp
input/wintab/drawpile_tablettester/tablettest.cpp
)
if (UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
qtlockedfile/qtlockedfile_unix.cpp
)
endif()
if(APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
osx.mm
)
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
+ widgets/KisDitherWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgsavebrushpreset.ui
forms/wdgpreseticonlibrary.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
forms/wdgsessionmanager.ui
forms/wdgnewwindowlayout.ui
forms/KisWelcomePage.ui
forms/WdgDlgPaletteEditor.ui
forms/KisNewsPage.ui
forms/wdgGamutMaskToolbar.ui
+ forms/wdgchangeclonesource.ui
brushhud/kis_dlg_brush_hud_config.ui
dialogs/kis_delayed_save_dialog.ui
input/config/kis_input_configuration_page.ui
input/config/kis_edit_profiles_dialog.ui
input/config/kis_input_configuration_page_item.ui
input/config/kis_mouse_input_editor.ui
input/config/kis_wheel_input_editor.ui
input/config/kis_key_input_editor.ui
layerstyles/wdgBevelAndEmboss.ui
layerstyles/wdgblendingoptions.ui
layerstyles/WdgColorOverlay.ui
layerstyles/wdgContour.ui
layerstyles/wdgdropshadow.ui
layerstyles/WdgGradientOverlay.ui
layerstyles/wdgInnerGlow.ui
layerstyles/wdglayerstyles.ui
layerstyles/WdgPatternOverlay.ui
layerstyles/WdgSatin.ui
layerstyles/WdgStroke.ui
layerstyles/wdgstylesselector.ui
layerstyles/wdgTexture.ui
layerstyles/wdgKisLayerStyleAngleSelector.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
input/wintab/drawpile_tablettester/tablettest.ui
)
if(WIN32)
if(USE_QT_TABLET_WINDOWS)
ki18n_wrap_ui(kritaui_LIB_SRCS
dialogs/KisDlgCustomTabletResolution.ui
)
else()
ki18n_wrap_ui(kritaui_LIB_SRCS
input/wintab/kis_screen_size_choice_dialog.ui
)
endif()
endif()
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2
)
if (ANDROID)
target_link_libraries(kritaui GLESv3)
target_link_libraries(kritaui Qt5::Gui)
endif()
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (NOT WIN32 AND NOT APPLE AND NOT ANDROID)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
target_link_libraries(kritaui ${APPKIT_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/canvas>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/flake>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ora>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/tool>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/utils>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/widgets>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/input/wintab>
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp
index ba5fdd2495..cf299dd5e8 100644
--- a/libs/ui/KisApplication.cpp
+++ b/libs/ui/KisApplication.cpp
@@ -1,859 +1,873 @@
/*
* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
* Copyright (C) 2012 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisApplication.h"
#include <stdlib.h>
#ifdef Q_OS_WIN
#include <windows.h>
#include <tchar.h>
#endif
#ifdef Q_OS_MACOS
#include "osx.h"
#endif
#include <QStandardPaths>
#include <QDesktopWidget>
#include <QDir>
#include <QFile>
#include <QLocale>
#include <QMessageBox>
#include <QProcessEnvironment>
#include <QStringList>
#include <QStyle>
#include <QStyleFactory>
#include <QSysInfo>
#include <QTimer>
#include <QWidget>
#include <klocalizedstring.h>
#include <kdesktopfile.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <KoColorSpaceRegistry.h>
#include <KoPluginLoader.h>
#include <KoShapeRegistry.h>
#include <KoDpi.h>
#include "KoConfig.h"
#include <resources/KoHashGeneratorProvider.h>
#include <KoResourcePaths.h>
#include <KisMimeDatabase.h>
#include "thememanager.h"
#include "KisPrintJob.h"
#include "KisDocument.h"
#include "KisMainWindow.h"
#include "KisAutoSaveRecoveryDialog.h"
#include "KisPart.h"
#include <kis_icon.h>
#include "kis_md5_generator.h"
#include "kis_splash_screen.h"
#include "kis_config.h"
#include "flake/kis_shape_selection.h"
#include <filter/kis_filter.h>
#include <filter/kis_filter_registry.h>
#include <filter/kis_filter_configuration.h>
#include <generator/kis_generator_registry.h>
#include <generator/kis_generator.h>
#include <brushengine/kis_paintop_registry.h>
#include <kis_meta_data_io_backend.h>
#include "kisexiv2/kis_exiv2.h"
#include "KisApplicationArguments.h"
#include <kis_debug.h>
#include "kis_action_registry.h"
#include <kis_brush_server.h>
#include <KisResourceServerProvider.h>
#include <KoResourceServerProvider.h>
#include "kis_image_barrier_locker.h"
#include "opengl/kis_opengl.h"
#include "kis_spin_box_unit_manager.h"
#include "kis_document_aware_spin_box_unit_manager.h"
#include "KisViewManager.h"
#include "kis_workspace_resource.h"
#include <KritaVersionWrapper.h>
#include <dialogs/KisSessionManagerDialog.h>
#include "widgets/KisScreenColorPicker.h"
#include "KisDlgInternalColorSelector.h"
#include <dialogs/KisAsyncAnimationFramesSaveDialog.h>
#include <kis_image_animation_interface.h>
namespace {
const QTime appStartTime(QTime::currentTime());
}
class KisApplication::Private
{
public:
Private() {}
QPointer<KisSplashScreen> splashScreen;
KisAutoSaveRecoveryDialog *autosaveDialog {0};
QPointer<KisMainWindow> mainWindow; // The first mainwindow we create on startup
bool batchRun {false};
};
class KisApplication::ResetStarting
{
public:
ResetStarting(KisSplashScreen *splash, int fileCount)
: m_splash(splash)
, m_fileCount(fileCount)
{
}
~ResetStarting() {
if (m_splash) {
m_splash->hide();
}
}
QPointer<KisSplashScreen> m_splash;
int m_fileCount;
};
KisApplication::KisApplication(const QString &key, int &argc, char **argv)
: QtSingleApplication(key, argc, argv)
, d(new Private)
{
#ifdef Q_OS_MACOS
setMouseCoalescingEnabled(false);
#endif
QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
setApplicationDisplayName("Krita");
setApplicationName("krita");
// Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird.
// setOrganizationName("krita");
setOrganizationDomain("krita.org");
QString version = KritaVersionWrapper::versionString(true);
setApplicationVersion(version);
setWindowIcon(KisIconUtils::loadIcon("calligrakrita"));
if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) {
QStringList styles = QStringList() /*<< "breeze"*/ << "fusion" << "plastique";
if (!styles.contains(style()->objectName().toLower())) {
Q_FOREACH (const QString & style, styles) {
if (!setStyle(style)) {
qDebug() << "No" << style << "available.";
}
else {
qDebug() << "Set style" << style;
break;
}
}
}
}
else {
qDebug() << "Style override disabled, using" << style()->objectName();
}
KisOpenGL::initialize();
}
#if defined(Q_OS_WIN) && defined(ENV32BIT)
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
BOOL isWow64()
{
BOOL bIsWow64 = FALSE;
//IsWow64Process is not available on all supported versions of Windows.
//Use GetModuleHandle to get a handle to the DLL that contains the function
//and GetProcAddress to get a pointer to the function if available.
fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
if(0 != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
//handle error
}
}
return bIsWow64;
}
#endif
void KisApplication::initializeGlobals(const KisApplicationArguments &args)
{
int dpiX = args.dpiX();
int dpiY = args.dpiY();
if (dpiX > 0 && dpiY > 0) {
KoDpi::setDPI(dpiX, dpiY);
}
}
void KisApplication::addResourceTypes()
{
// qDebug() << "addResourceTypes();";
// All Krita's resource types
KoResourcePaths::addResourceType("markers", "data", "/styles/");
KoResourcePaths::addResourceType("kis_pics", "data", "/pics/");
KoResourcePaths::addResourceType("kis_images", "data", "/images/");
KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/");
KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/");
KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/");
KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/");
KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/");
KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/");
KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/");
KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/");
KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl");
KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true);
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/");
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true);
KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true);
KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/");
KoResourcePaths::addResourceType("kis_actions", "data", "/actions");
KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc");
KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/");
KoResourcePaths::addResourceType("ko_effects", "data", "/effects/");
KoResourcePaths::addResourceType("tags", "data", "/tags/");
KoResourcePaths::addResourceType("templates", "data", "/templates");
KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita");
KoResourcePaths::addResourceType("symbols", "data", "/symbols");
KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons");
KoResourcePaths::addResourceType("ko_gamutmasks", "data", "/gamutmasks/", true);
// // Extra directories to look for create resources. (Does anyone actually use that anymore?)
// KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp");
// KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp"));
// KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp");
// KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp"));
// KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp");
// KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp"));
// KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches");
// KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches"));
// Make directories for all resources we can save, and tags
QDir d;
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/");
}
void KisApplication::loadResources()
{
// qDebug() << "loadResources();";
setSplashScreenLoadingText(i18n("Loading Resources..."));
processEvents();
KoResourceServerProvider::instance();
setSplashScreenLoadingText(i18n("Loading Brush Presets..."));
processEvents();
KisResourceServerProvider::instance();
setSplashScreenLoadingText(i18n("Loading Brushes..."));
processEvents();
KisBrushServer::instance()->brushServer();
setSplashScreenLoadingText(i18n("Loading Bundles..."));
processEvents();
KisResourceBundleServerProvider::instance();
}
void KisApplication::loadResourceTags()
{
// qDebug() << "loadResourceTags()";
KoResourceServerProvider::instance()->patternServer()->loadTags();
KoResourceServerProvider::instance()->gradientServer()->loadTags();
KoResourceServerProvider::instance()->paletteServer()->loadTags();
KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags();
KisBrushServer::instance()->brushServer()->loadTags();
KisResourceServerProvider::instance()->workspaceServer()->loadTags();
KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags();
KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags();
KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags();
KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags();
}
void KisApplication::loadPlugins()
{
// qDebug() << "loadPlugins();";
KoShapeRegistry* r = KoShapeRegistry::instance();
r->add(new KisShapeSelectionFactory());
KisActionRegistry::instance();
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
KisPaintOpRegistry::instance();
KoColorSpaceRegistry::instance();
}
void KisApplication::loadGuiPlugins()
{
// qDebug() << "loadGuiPlugins();";
// Load the krita-specific tools
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool..."));
processEvents();
// qDebug() << "loading tools";
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// Load dockers
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock..."));
processEvents();
// qDebug() << "loading dockers";
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// XXX_EXIV: make the exiv io backends real plugins
setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO..."));
processEvents();
// qDebug() << "loading exiv2";
KisExiv2::initialize();
}
bool KisApplication::start(const KisApplicationArguments &args)
{
KisConfig cfg(false);
#if defined(Q_OS_WIN)
#ifdef ENV32BIT
if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) {
QMessageBox::information(0,
i18nc("@title:window", "Krita: Warning"),
i18n("You are running a 32 bits build on a 64 bits Windows.\n"
"This is not recommended.\n"
"Please download and install the x64 build instead."));
cfg.writeEntry("WarnedAbout32Bits", true);
}
#endif
#endif
QString opengl = cfg.canvasState();
if (opengl == "OPENGL_NOT_TRIED" ) {
cfg.setCanvasState("TRY_OPENGL");
}
else if (opengl != "OPENGL_SUCCESS") {
cfg.setCanvasState("OPENGL_FAILED");
}
setSplashScreenLoadingText(i18n("Initializing Globals"));
processEvents();
initializeGlobals(args);
const bool doNewImage = args.doNewImage();
const bool doTemplate = args.doTemplate();
const bool exportAs = args.exportAs();
const bool exportSequence = args.exportSequence();
const QString exportFileName = args.exportFileName();
d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty());
const bool needsMainWindow = (!exportAs && !exportSequence);
// only show the mainWindow when no command-line mode option is passed
bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun;
const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH");
if (showSplashScreen && d->splashScreen) {
d->splashScreen->show();
d->splashScreen->repaint();
processEvents();
}
KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator());
KConfigGroup group(KSharedConfig::openConfig(), "theme");
Digikam::ThemeManager themeManager;
themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark"));
ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done
Q_UNUSED(resetStarting);
// Make sure we can save resources and tags
setSplashScreenLoadingText(i18n("Adding resource types"));
processEvents();
addResourceTypes();
// Load the plugins
loadPlugins();
// Load all resources
loadResources();
// Load all the tags
loadResourceTags();
// Load the gui plugins
loadGuiPlugins();
KisPart *kisPart = KisPart::instance();
if (needsMainWindow) {
// show a mainWindow asap, if we want that
setSplashScreenLoadingText(i18n("Loading Main Window..."));
processEvents();
bool sessionNeeded = true;
auto sessionMode = cfg.sessionOnStartup();
if (!args.session().isEmpty()) {
sessionNeeded = !kisPart->restoreSession(args.session());
} else if (sessionMode == KisConfig::SOS_ShowSessionManager) {
showmainWindow = false;
sessionNeeded = false;
kisPart->showSessionManager();
} else if (sessionMode == KisConfig::SOS_PreviousSession) {
KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session");
const QString &sessionName = sessionCfg.readEntry("previousSession");
sessionNeeded = !kisPart->restoreSession(sessionName);
}
if (sessionNeeded) {
kisPart->startBlankSession();
}
if (!args.windowLayout().isEmpty()) {
KoResourceServer<KisWindowLayoutResource> * rserver = KisResourceServerProvider::instance()->windowLayoutServer();
KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout());
if (windowLayout) {
windowLayout->applyLayout();
}
}
if (showmainWindow) {
d->mainWindow = kisPart->currentMainwindow();
if (!args.workspace().isEmpty()) {
KoResourceServer<KisWorkspaceResource> * rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace());
if (workspace) {
d->mainWindow->restoreWorkspace(workspace);
}
}
if (args.canvasOnly()) {
d->mainWindow->viewManager()->switchCanvasOnly(true);
}
if (args.fullScreen()) {
d->mainWindow->showFullScreen();
}
} else {
d->mainWindow = kisPart->createMainWindow();
}
}
short int numberOfOpenDocuments = 0; // number of documents open
// Check for autosave files that can be restored, if we're not running a batchrun (test)
if (!d->batchRun) {
checkAutosaveFiles();
}
setSplashScreenLoadingText(QString()); // done loading, so clear out label
processEvents();
//configure the unit manager
KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder());
connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave.
//the new syntax slot syntax allow to connect to a non q_object static method.
// Create a new image, if needed
if (doNewImage) {
KisDocument *doc = args.image();
if (doc) {
kisPart->addDocument(doc);
d->mainWindow->addViewAndNotifyLoadingCompleted(doc);
}
}
// Get the command line arguments which we have to parse
int argsCount = args.filenames().count();
if (argsCount > 0) {
// Loop through arguments
for (int argNumber = 0; argNumber < argsCount; argNumber++) {
QString fileName = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
// called in mix with batch options? ignore and silently skip
if (d->batchRun) {
continue;
}
if (createNewDocFromTemplate(fileName, d->mainWindow)) {
++numberOfOpenDocuments;
}
// now try to load
}
else {
if (exportAs) {
QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false);
if (outputMimetype == "application/octetstream") {
dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl;
- return 1;
+ return false;
}
KisDocument *doc = kisPart->createDocument();
doc->setFileBatchMode(d->batchRun);
- doc->openUrl(QUrl::fromLocalFile(fileName));
+ bool result = doc->openUrl(QUrl::fromLocalFile(fileName));
+
+ if (!result) {
+ errKrita << "Could not load " << fileName << ":" << doc->errorMessage();
+ QTimer::singleShot(0, this, SLOT(quit()));
+ return false;
+ }
+
+ if (exportFileName.isEmpty()) {
+ errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option";
+ QTimer::singleShot(0, this, SLOT(quit()));
+ return false;
+ }
qApp->processEvents(); // For vector layers to be updated
doc->setFileBatchMode(true);
if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) {
- dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage();
+ errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage();
}
QTimer::singleShot(0, this, SLOT(quit()));
+ return true;
}
else if (exportSequence) {
KisDocument *doc = kisPart->createDocument();
doc->setFileBatchMode(d->batchRun);
doc->openUrl(QUrl::fromLocalFile(fileName));
qApp->processEvents(); // For vector layers to be updated
if (!doc->image()->animationInterface()->hasAnimation()) {
errKrita << "This file has no animation." << endl;
QTimer::singleShot(0, this, SLOT(quit()));
- return 1;
+ return false;
}
doc->setFileBatchMode(true);
int sequenceStart = 0;
KisAsyncAnimationFramesSaveDialog exporter(doc->image(),
doc->image()->animationInterface()->fullClipRange(),
exportFileName,
sequenceStart,
0);
exporter.setBatchMode(d->batchRun);
KisAsyncAnimationFramesSaveDialog::Result result =
exporter.regenerateRange(0);
if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) {
errKrita << i18n("Failed to render animation frames!") << endl;
}
QTimer::singleShot(0, this, SLOT(quit()));
+ return true;
}
else if (d->mainWindow) {
if (fileName.endsWith(".bundle")) {
d->mainWindow->installBundle(fileName);
}
else {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) {
// Normal case, success
numberOfOpenDocuments++;
}
}
}
}
}
}
// fixes BUG:369308 - Krita crashing on splash screen when loading.
// trying to open a file before Krita has loaded can cause it to hang and crash
if (d->splashScreen) {
d->splashScreen->displayLinks(true);
d->splashScreen->displayRecentFiles(true);
}
// not calling this before since the program will quit there.
return true;
}
KisApplication::~KisApplication()
{
}
void KisApplication::setSplashScreen(QWidget *splashScreen)
{
d->splashScreen = qobject_cast<KisSplashScreen*>(splashScreen);
}
void KisApplication::setSplashScreenLoadingText(QString textToLoad)
{
if (d->splashScreen) {
//d->splashScreen->loadingLabel->setText(textToLoad);
d->splashScreen->setLoadingText(textToLoad);
d->splashScreen->repaint();
}
}
void KisApplication::hideSplashScreen()
{
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
}
bool KisApplication::notify(QObject *receiver, QEvent *event)
{
try {
return QApplication::notify(receiver, event);
} catch (std::exception &e) {
qWarning("Error %s sending event %i to object %s",
e.what(), event->type(), qPrintable(receiver->objectName()));
} catch (...) {
qWarning("Error <unknown> sending event %i to object %s",
event->type(), qPrintable(receiver->objectName()));
}
return false;
}
void KisApplication::remoteArguments(QByteArray message, QObject *socket)
{
Q_UNUSED(socket);
// check if we have any mainwindow
KisMainWindow *mw = qobject_cast<KisMainWindow*>(qApp->activeWindow());
if (!mw) {
mw = KisPart::instance()->mainWindows().first();
}
if (!mw) {
return;
}
KisApplicationArguments args = KisApplicationArguments::deserialize(message);
const bool doTemplate = args.doTemplate();
const int argsCount = args.filenames().count();
if (argsCount > 0) {
// Loop through arguments
for (int argNumber = 0; argNumber < argsCount; ++argNumber) {
QString filename = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
createNewDocFromTemplate(filename, mw);
}
else if (QFile(filename).exists()) {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
mw->openDocument(QUrl::fromLocalFile(filename), flags);
}
}
}
}
void KisApplication::fileOpenRequested(const QString &url)
{
KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first();
if (mainWindow) {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
mainWindow->openDocument(QUrl::fromLocalFile(url), flags);
}
}
void KisApplication::checkAutosaveFiles()
{
if (d->batchRun) return;
#ifdef Q_OS_WIN
QDir dir = QDir::temp();
#else
QDir dir = QDir::home();
#endif
// Check for autosave files from a previous run. There can be several, and
// we want to offer a restore for every one. Including a nice thumbnail!
// Hidden autosave files
QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra");
// all autosave files for our application
QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden);
// Visibile autosave files
filters = QStringList() << QString("krita-*-*-autosave.kra");
autosaveFiles += dir.entryList(filters, QDir::Files);
// Allow the user to make their selection
if (autosaveFiles.size() > 0) {
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow());
QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec();
if (result == QDialog::Accepted) {
QStringList filesToRecover = d->autosaveDialog->recoverableFiles();
Q_FOREACH (const QString &autosaveFile, autosaveFiles) {
if (!filesToRecover.contains(autosaveFile)) {
QFile::remove(dir.absolutePath() + "/" + autosaveFile);
}
}
autosaveFiles = filesToRecover;
} else {
autosaveFiles.clear();
}
if (autosaveFiles.size() > 0) {
QList<QUrl> autosaveUrls;
Q_FOREACH (const QString &autoSaveFile, autosaveFiles) {
const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile);
autosaveUrls << url;
}
if (d->mainWindow) {
Q_FOREACH (const QUrl &url, autosaveUrls) {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile);
}
}
}
// cleanup
delete d->autosaveDialog;
d->autosaveDialog = nullptr;
}
}
bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow)
{
QString templatePath;
const QUrl templateUrl = QUrl::fromLocalFile(fileName);
if (QFile::exists(fileName)) {
templatePath = templateUrl.toLocalFile();
dbgUI << "using full path...";
}
else {
QString desktopName(fileName);
const QString templatesResourcePath = QStringLiteral("templates/");
QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName);
if (paths.isEmpty()) {
paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName);
}
if (paths.isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("No template found for: %1", desktopName));
} else if (paths.count() > 1) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Too many templates found for: %1", desktopName));
} else {
templatePath = paths.at(0);
}
}
if (!templatePath.isEmpty()) {
QUrl templateBase;
templateBase.setPath(templatePath);
KDesktopFile templateInfo(templatePath);
QString templateName = templateInfo.readUrl();
QUrl templateURL;
templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName);
if (templateURL.scheme().isEmpty()) {
templateURL.setScheme("file");
}
KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) {
dbgUI << "Template loaded...";
return true;
}
else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Template %1 failed to load.", templateURL.toDisplayString()));
}
}
return false;
}
void KisApplication::clearConfig()
{
KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread());
KSharedConfigPtr config = KSharedConfig::openConfig();
// find user settings file
bool createDir = false;
QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir);
QFile configFile(kritarcPath);
if (configFile.exists()) {
// clear file
if (configFile.open(QFile::WriteOnly)) {
configFile.close();
}
else {
QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("Failed to clear %1\n\n"
"Please make sure no other program is using the file and try again.",
kritarcPath),
QMessageBox::Ok, QMessageBox::Ok);
}
}
// reload from disk; with the user file settings cleared,
// this should load any default configuration files shipping with the program
config->reparseConfiguration();
config->sync();
}
void KisApplication::askClearConfig()
{
Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers();
bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier);
if (askClearConfig) {
bool ok = QMessageBox::question(0,
i18nc("@title:window", "Krita"),
i18n("Do you want to clear the settings file?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes;
if (ok) {
clearConfig();
}
}
}
diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
index 539a7cb4db..b4b6c8f510 100644
--- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
+++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
@@ -1,127 +1,127 @@
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KisAsyncAnimationFramesSavingRenderer.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "KisImportExportFilter.h"
#include "KisPart.h"
#include "KisDocument.h"
#include "kis_time_range.h"
#include "kis_paint_layer.h"
struct KisAsyncAnimationFramesSavingRenderer::Private
{
Private(KisImageSP image, const KisTimeRange &_range, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration)
: savingDoc(KisPart::instance()->createDocument()),
range(_range),
sequenceNumberingOffset(_sequenceNumberingOffset),
exportConfiguration(_exportConfiguration)
{
savingDoc->setInfiniteAutoSaveInterval();
savingDoc->setFileBatchMode(true);
KisImageSP savingImage = new KisImage(savingDoc->createUndoStore(),
image->bounds().width(),
image->bounds().height(),
image->colorSpace(),
QString());
savingImage->setResolution(image->xRes(), image->yRes());
savingDoc->setCurrentImage(savingImage);
KisPaintLayer* paintLayer = new KisPaintLayer(savingImage, "paint device", 255);
savingImage->addNode(paintLayer, savingImage->root(), KisLayerSP(0));
savingDevice = paintLayer->paintDevice();
}
QScopedPointer<KisDocument> savingDoc;
KisPaintDeviceSP savingDevice;
KisTimeRange range;
int sequenceNumberingOffset = 0;
QString filenamePrefix;
QString filenameSuffix;
QByteArray outputMimeType;
KisPropertiesConfigurationSP exportConfiguration;
};
KisAsyncAnimationFramesSavingRenderer::KisAsyncAnimationFramesSavingRenderer(KisImageSP image,
const QString &fileNamePrefix,
const QString &fileNameSuffix,
const QByteArray &outputMimeType,
const KisTimeRange &range,
const int sequenceNumberingOffset,
KisPropertiesConfigurationSP exportConfiguration)
: m_d(new Private(image, range, sequenceNumberingOffset, exportConfiguration))
{
m_d->filenamePrefix = fileNamePrefix;
m_d->filenameSuffix = fileNameSuffix;
m_d->outputMimeType = outputMimeType;
connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(notifyFrameCompleted(int)));
connect(this, SIGNAL(sigCancelRegenerationInternal(int)), SLOT(notifyFrameCancelled(int)));
}
KisAsyncAnimationFramesSavingRenderer::~KisAsyncAnimationFramesSavingRenderer()
{
}
void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion)
{
KisImageSP image = requestedImage();
if (!image) return;
KIS_SAFE_ASSERT_RECOVER (requestedRegion == image->bounds()) {
emit sigCancelRegenerationInternal(frame);
return;
}
m_d->savingDevice->makeCloneFromRough(image->projection(), image->bounds());
- KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
+ KisImportExportErrorCode status = ImportExportCodes::OK;
QString frameNumber = QString("%1").arg(frame + m_d->sequenceNumberingOffset, 4, 10, QChar('0'));
QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix;
if (!m_d->savingDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, m_d->exportConfiguration)) {
- status = KisImportExportFilter::InternalError;
+ status = ImportExportCodes::InternalError;
}
- if (status == KisImportExportFilter::OK) {
+ if (status.isOk()) {
emit sigCompleteRegenerationInternal(frame);
} else {
emit sigCancelRegenerationInternal(frame);
}
}
void KisAsyncAnimationFramesSavingRenderer::frameCancelledCallback(int frame)
{
notifyFrameCancelled(frame);
}
diff --git a/libs/ui/KisChangeCloneLayersCommand.cpp b/libs/ui/KisChangeCloneLayersCommand.cpp
new file mode 100644
index 0000000000..2d8c29a75d
--- /dev/null
+++ b/libs/ui/KisChangeCloneLayersCommand.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "KisChangeCloneLayersCommand.h"
+
+#include <kis_clone_layer.h>
+
+struct KisChangeCloneLayersCommand::Private
+{
+ QList<KisCloneLayerSP> cloneLayers;
+ QList<KisLayerSP> originalSource;
+ KisLayerSP newSource;
+};
+
+KisChangeCloneLayersCommand::KisChangeCloneLayersCommand(QList<KisCloneLayerSP> cloneLayers, KisLayerSP newSource, KUndo2Command *parent)
+ : KUndo2Command(kundo2_i18n("Change Clone Layers"), parent)
+ , d(new Private())
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!cloneLayers.isEmpty());
+ d->cloneLayers = cloneLayers;
+ Q_FOREACH (KisCloneLayerSP layer, d->cloneLayers) {
+ d->originalSource << layer->copyFrom();
+ }
+ d->newSource = newSource;
+}
+
+void KisChangeCloneLayersCommand::redo()
+{
+ Q_FOREACH (KisCloneLayerSP layer, d->cloneLayers) {
+ layer->setCopyFrom(d->newSource);
+ layer->setDirty();
+ }
+}
+
+void KisChangeCloneLayersCommand::undo()
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(d->cloneLayers.size() == d->originalSource.size());
+ for (int i = 0; i < d->cloneLayers.size(); ++i) {
+ KisCloneLayerSP layer = d->cloneLayers.at(i);
+ layer->setCopyFrom(d->originalSource.at(i));
+ layer->setDirty();
+ }
+}
+
+bool KisChangeCloneLayersCommand::mergeWith(const KUndo2Command *command)
+{
+ const KisChangeCloneLayersCommand *other = dynamic_cast<const KisChangeCloneLayersCommand *>(command);
+
+ if (other && d->cloneLayers == other->d->cloneLayers) {
+ d->newSource = other->d->newSource;
+ return true;
+ }
+
+ return false;
+}
diff --git a/plugins/impex/psd/psd_saver.h b/libs/ui/KisChangeCloneLayersCommand.h
similarity index 59%
copy from plugins/impex/psd/psd_saver.h
copy to libs/ui/KisChangeCloneLayersCommand.h
index bf142d5128..3e0308b95b 100644
--- a/plugins/impex/psd/psd_saver.h
+++ b/libs/ui/KisChangeCloneLayersCommand.h
@@ -1,57 +1,40 @@
/*
- * Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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 _PSD_CONVERTER_H_
-#define _PSD_CONVERTER_H_
-#include <stdio.h>
-
-#include <QObject>
-
-#include <QFileInfo>
+#ifndef KIS_CHANGE_CLONE_LAYERS_COMMAND_H_
+#define KIS_CHANGE_CLONE_LAYERS_COMMAND_H_
+#include <kundo2command.h>
#include "kis_types.h"
-#include <KisImageBuilderResult.h>
-class KisDocument;
-
-class PSDSaver : public QObject {
-
- Q_OBJECT
-
-public:
- PSDSaver(KisDocument *doc);
- ~PSDSaver() override;
+class KisChangeCloneLayersCommand : public KUndo2Command
+{
public:
+ KisChangeCloneLayersCommand(QList<KisCloneLayerSP> cloneLayers, KisLayerSP newSource, KUndo2Command *parent = 0);
- KisImageBuilder_Result buildFile(QIODevice *io);
-
- KisImageSP image();
-
-public Q_SLOTS:
-
- virtual void cancel();
+ void undo() override;
+ void redo() override;
+ bool mergeWith(const KUndo2Command *) override;
private:
-
- KisImageSP m_image;
- KisDocument *m_doc;
- bool m_stop;
+ struct Private;
+ QScopedPointer<Private> d;
};
-#endif
+#endif // KIS_CHANGE_CLONE_LAYERS_COMMAND_H_
diff --git a/libs/ui/KisDecorationsManager.cpp b/libs/ui/KisDecorationsManager.cpp
index fd6aed8c25..08015a0230 100644
--- a/libs/ui/KisDecorationsManager.cpp
+++ b/libs/ui/KisDecorationsManager.cpp
@@ -1,128 +1,131 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2014 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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 "KisDecorationsManager.h"
#include "KisViewManager.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include "kis_canvas2.h"
#include <klocalizedstring.h>
#include <kguiitem.h>
#include <ktoggleaction.h>
#include <kactioncollection.h>
+#include <KisDocument.h>
KisDecorationsManager::KisDecorationsManager(KisViewManager* view)
: QObject(view)
, m_imageView(0)
{
}
KisDecorationsManager::~KisDecorationsManager()
{
}
void KisDecorationsManager::setup(KisActionManager * actionManager)
{
m_toggleAssistant = actionManager->createAction("view_toggle_painting_assistants");
m_togglePreview = actionManager->createAction("view_toggle_assistant_previews");
m_toggleReferenceImages = actionManager->createAction("view_toggle_reference_images");
updateAction();
}
void KisDecorationsManager::setView(QPointer<KisView> imageView)
{
// set view is called twice when a document is open, so we need to disconnect the original signals
// if m_imageView has already been created. This prevents double signal events firing
if (m_imageView) {
m_toggleAssistant->disconnect();
m_togglePreview->disconnect();
if (assistantsDecoration()) {
assistantsDecoration()->disconnect(this);
}
if (referenceImagesDecoration()) {
referenceImagesDecoration()->disconnect(this);
}
}
m_imageView = imageView;
if (m_imageView && !referenceImagesDecoration()) {
KisReferenceImagesDecoration *deco = new KisReferenceImagesDecoration(m_imageView, imageView->document());
m_imageView->canvasBase()->addDecoration(deco);
}
if (m_imageView && !assistantsDecoration()) {
KisPaintingAssistantsDecoration *deco = new KisPaintingAssistantsDecoration(m_imageView);
m_imageView->canvasBase()->addDecoration(deco);
}
if (m_imageView && assistantsDecoration()) {
connect(m_toggleAssistant, SIGNAL(triggered()), assistantsDecoration(), SLOT(toggleAssistantVisible()));
connect(m_togglePreview, SIGNAL(triggered()), assistantsDecoration(), SLOT(toggleOutlineVisible()));
connect(assistantsDecoration(), SIGNAL(assistantChanged()), SLOT(updateAction()));
+ connect(m_imageView->document(), &KisDocument::sigAssistantsChanged,
+ m_imageView->canvasBase(), QOverload<>::of(&KisCanvas2::updateCanvas));
}
if (m_imageView && referenceImagesDecoration()) {
connect(m_toggleReferenceImages, SIGNAL(triggered(bool)), referenceImagesDecoration(), SLOT(setVisible(bool)), Qt::UniqueConnection);
}
updateAction();
}
void KisDecorationsManager::updateAction()
{
if (assistantsDecoration()) {
bool enabled = !assistantsDecoration()->assistants().isEmpty();
m_toggleAssistant->setChecked(assistantsDecoration()->visible());
m_toggleAssistant->setEnabled(enabled);
m_togglePreview->setChecked(assistantsDecoration()->outlineVisibility());
m_togglePreview->setEnabled(enabled);
} else {
m_toggleAssistant->setEnabled(false);
}
if (referenceImagesDecoration()) {
m_toggleReferenceImages->setEnabled(referenceImagesDecoration()->documentHasReferenceImages());
m_toggleReferenceImages->setChecked(referenceImagesDecoration()->visible());
}
}
KisPaintingAssistantsDecorationSP KisDecorationsManager::assistantsDecoration() const
{
if (m_imageView) {
return m_imageView->canvasBase()->paintingAssistantsDecoration();
}
return 0;
}
KisReferenceImagesDecorationSP KisDecorationsManager::referenceImagesDecoration() const
{
if (m_imageView) {
return m_imageView->canvasBase()->referenceImagesDecoration();
}
return 0;
}
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 1f80a3bf40..8a24cee187 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,2041 +1,2180 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include <QMessageBox> // XXX: remove
#include <KisMimeDatabase.h>
#include <KoCanvasBase.h>
#include <KoColor.h>
#include <KoColorProfile.h>
#include <KoColorSpaceEngine.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoDocumentInfoDlg.h>
#include <KoDocumentInfo.h>
#include <KoDpi.h>
#include <KoUnit.h>
#include <KoID.h>
#include <KoOdfReadStore.h>
#include <KoProgressProxy.h>
#include <KoProgressUpdater.h>
#include <KoSelection.h>
#include <KoShape.h>
#include <KoShapeController.h>
#include <KoStore.h>
#include <KoUpdater.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoStoreDevice.h>
#include <KoDialog.h>
+#include <KisImportExportErrorCode.h>
+#include <KoDocumentResourceManager.h>
#include <KisUsageLogger.h>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kis_generator_layer.h>
#include <kis_generator_registry.h>
#include <kdesktopfile.h>
#include <kconfiggroup.h>
#include <kbackup.h>
#include <QTextBrowser>
#include <QApplication>
#include <QBuffer>
#include <QStandardPaths>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QFileInfo>
#include <QImage>
#include <QList>
#include <QPainter>
#include <QRect>
#include <QScopedPointer>
#include <QSize>
#include <QStringList>
#include <QtGlobal>
#include <QTimer>
#include <QWidget>
#include <QFuture>
#include <QFutureWatcher>
// Krita Image
#include <kis_image_animation_interface.h>
#include <kis_config.h>
#include <flake/kis_shape_layer.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_name_server.h>
#include <kis_paint_layer.h>
#include <kis_painter.h>
#include <kis_selection.h>
#include <kis_fill_painter.h>
#include <kis_document_undo_store.h>
#include <kis_idle_watcher.h>
#include <kis_signal_auto_connection.h>
#include <kis_canvas_widget_base.h>
#include "kis_layer_utils.h"
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include "KisReferenceImagesLayer.h"
#include <mutex>
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
#include "KisCloneDocumentStroke.h"
#include <KisMirrorAxisConfig.h>
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include <unistd.h>
using namespace std;
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}
/**********************************************************
*
* KisDocument
*
**********************************************************/
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: KUndo2Stack(doc),
m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private(KisDocument *q)
: docInfo(new KoDocumentInfo(q)) // deleted by QObject
, importExportManager(new KisImportExportManager(q)) // deleted manually
, autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q)) // deleted by QObject
, m_bAutoDetectedMime(false)
, modified(false)
, readwrite(true)
, firstMod(QDateTime::currentDateTime())
, lastMod(firstMod)
, nserver(new KisNameServer(1))
, imageIdleWatcher(2000 /*ms*/)
, globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
, savingLock(&savingMutex)
, batchMode(false)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
Private(const Private &rhs, KisDocument *q)
: docInfo(new KoDocumentInfo(*rhs.docInfo, q))
- , unit(rhs.unit)
, importExportManager(new KisImportExportManager(q))
- , mimeType(rhs.mimeType)
- , outputMimeType(rhs.outputMimeType)
, autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q))
- , guidesConfig(rhs.guidesConfig)
- , mirrorAxisConfig(rhs.mirrorAxisConfig)
- , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime)
- , m_url(rhs.m_url)
- , m_file(rhs.m_file)
- , modified(rhs.modified)
- , readwrite(rhs.readwrite)
- , firstMod(rhs.firstMod)
- , lastMod(rhs.lastMod)
, nserver(new KisNameServer(*rhs.nserver))
, preActivatedNode(0) // the node is from another hierarchy!
, imageIdleWatcher(2000 /*ms*/)
- , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document!
- , globalAssistantsColor(rhs.globalAssistantsColor)
- , paletteList(rhs.paletteList)
- , gridConfig(rhs.gridConfig)
, savingLock(&savingMutex)
- , batchMode(rhs.batchMode)
{
- // TODO: clone assistants
+ copyFromImpl(rhs, q, CONSTRUCT);
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
QTimer *autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay = 300; // in seconds, 0 to disable.
bool modifiedAfterAutosave = false;
bool isAutosaving = false;
bool disregardAutosaveFailure = false;
int autoSaveFailureCount = 0;
KUndo2Stack *undoStack = 0;
KisGuidesConfig guidesConfig;
KisMirrorAxisConfig mirrorAxisConfig;
bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QMutex savingMutex;
bool modified = false;
bool readwrite = false;
QDateTime firstMod;
QDateTime lastMod;
KisNameServer *nserver;
KisImageSP image;
KisImageSP savingImage;
KisNodeWSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer<KisSignalAutoConnection> imageIdleConnection;
QList<KisPaintingAssistantSP> assistants;
QColor globalAssistantsColor;
KisSharedPtr<KisReferenceImagesLayer> referenceImagesLayer;
QList<KoColorSet*> paletteList;
+ bool ownsPaletteList = false;
KisGridConfig gridConfig;
StdLockableWrapper<QMutex> savingLock;
bool modifiedWhileSaving = false;
QScopedPointer<KisDocument> backgroundSaveDocument;
QPointer<KoUpdater> savingUpdater;
- QFuture<KisImportExportFilter::ConversionStatus> childSavingFuture;
+ QFuture<KisImportExportErrorCode> childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
bool isRecovered = false;
bool batchMode { false };
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
+ void copyFrom(const Private &rhs, KisDocument *q);
+ void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy);
+
+ /// clones the palette list oldList
+ /// the ownership of the returned KoColorSet * belongs to the caller
+ QList<KoColorSet *> clonePaletteList(const QList<KoColorSet *> &oldList);
+
class StrippedSafeSavingLocker;
};
+void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q)
+{
+ copyFromImpl(rhs, q, KisDocument::REPLACE);
+}
+
+void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
+{
+ if (policy == REPLACE) {
+ delete docInfo;
+ }
+ docInfo = (new KoDocumentInfo(*rhs.docInfo, q));
+ unit = rhs.unit;
+ mimeType = rhs.mimeType;
+ outputMimeType = rhs.outputMimeType;
+
+ if (policy == REPLACE) {
+ q->setGuidesConfig(rhs.guidesConfig);
+ q->setMirrorAxisConfig(rhs.mirrorAxisConfig);
+ q->setModified(rhs.modified);
+ q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants));
+ q->setGridConfig(rhs.gridConfig);
+ } else {
+ // in CONSTRUCT mode, we cannot use the functions of KisDocument
+ // because KisDocument does not yet have a pointer to us.
+ guidesConfig = rhs.guidesConfig;
+ mirrorAxisConfig = rhs.mirrorAxisConfig;
+ modified = rhs.modified;
+ assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants);
+ gridConfig = rhs.gridConfig;
+ }
+ m_bAutoDetectedMime = rhs.m_bAutoDetectedMime;
+ m_url = rhs.m_url;
+ m_file = rhs.m_file;
+ readwrite = rhs.readwrite;
+ firstMod = rhs.firstMod;
+ lastMod = rhs.lastMod;
+ // XXX: the display properties will be shared between different snapshots
+ globalAssistantsColor = rhs.globalAssistantsColor;
+
+ if (policy == REPLACE) {
+ QList<KoColorSet *> newPaletteList = clonePaletteList(rhs.paletteList);
+ q->setPaletteList(newPaletteList, /* emitSignal = */ true);
+ // we still do not own palettes if we did not
+ } else {
+ paletteList = rhs.paletteList;
+ }
+
+ batchMode = rhs.batchMode;
+}
+
+QList<KoColorSet *> KisDocument::Private::clonePaletteList(const QList<KoColorSet *> &oldList)
+{
+ QList<KoColorSet *> newList;
+ Q_FOREACH (KoColorSet *palette, oldList) {
+ newList << new KoColorSet(*palette);
+ }
+ return newList;
+}
+
class KisDocument::Private::StrippedSafeSavingLocker {
public:
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
: m_locked(false)
, m_image(image)
, m_savingLock(savingMutex)
, m_imageLock(image, true)
{
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
if (!m_locked) {
m_image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
}
}
~StrippedSafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
m_savingLock.unlock();
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
bool m_locked;
KisImageSP m_image;
StdLockableWrapper<QMutex> m_savingLock;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private(this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(newObjectName());
// preload the krita resources
KisResourceServerProvider::instance();
- d->shapeController = new KisShapeController(this, d->nserver),
- d->koShapeController = new KoShapeController(0, d->shapeController),
+ d->shapeController = new KisShapeController(this, d->nserver);
+ d->koShapeController = new KoShapeController(0, d->shapeController);
+ d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
- slotConfigChanged();
+ slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
- connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
- connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
- connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
- setObjectName(rhs.objectName());
-
- d->shapeController = new KisShapeController(this, d->nserver),
- d->koShapeController = new KoShapeController(0, d->shapeController),
-
- slotConfigChanged();
-
- // clone the image with keeping the GUIDs of the layers intact
- // NOTE: we expect the image to be locked!
- setCurrentImage(rhs.image()->clone(true), false);
-
- if (rhs.d->preActivatedNode) {
- // since we clone uuid's, we can use them for lacating new
- // nodes. Otherwise we would need to use findSymmetricClone()
- d->preActivatedNode =
- KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid());
- }
+ copyFromDocumentImpl(rhs, CONSTRUCT);
}
KisDocument::~KisDocument()
{
// wait until all the pending operations are in progress
waitForSavingToComplete();
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer->disconnect(this);
d->autoSaveTimer->stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
+ if (d->ownsPaletteList) {
+ qDeleteAll(d->paletteList);
+ }
+
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
KisDocument *KisDocument::clone()
{
return new KisDocument(*this);
}
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
{
QFileInfo filePathInfo(job.filePath);
if (filePathInfo.exists() && !filePathInfo.isWritable()) {
- slotCompleteSavingDocument(job,
- KisImportExportFilter::CreationError,
+ slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
+ //return ImportExportCodes::NoAccessToWrite;
return false;
}
KisConfig cfg(true);
if (cfg.backupFile() && filePathInfo.exists()) {
QString backupDir;
switch(cfg.readEntry<int>("backupfilelocation", 0)) {
case 1:
backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
break;
case 2:
backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
break;
default:
// Do nothing: the empty string is user file location
break;
}
int numOfBackupsKept = cfg.readEntry<int>("numberofbackupfiles", 1);
QString suffix = cfg.readEntry<QString>("backupfilesuffix", "~");
if (numOfBackupsKept == 1) {
KBackup::simpleBackupFile(job.filePath, backupDir, suffix);
}
else if (numOfBackupsKept > 2) {
KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept);
}
}
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
+ //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
+ if (job.mimeType.isEmpty()) {
+
+ KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect;
+ slotCompleteSavingDocument(job, error, error.errorMessage());
+ return false;
+
+ }
const QString actionName =
- job.flags & KritaUtils::SaveIsExporting ?
- i18n("Exporting Document...") :
- i18n("Saving Document...");
+ job.flags & KritaUtils::SaveIsExporting ?
+ i18n("Exporting Document...") :
+ i18n("Saving Document...");
bool started =
- initiateSavingInBackground(actionName,
- this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
- job, exportConfiguration);
-
+ initiateSavingInBackground(actionName,
+ this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)),
+ job, exportConfiguration);
if (!started) {
emit canceled(QString());
}
return started;
}
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
SaveFlags flags = SaveIsExporting;
if (showWarnings) {
flags |= SaveShowWarnings;
}
KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8")
.arg(url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration"));
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8")
.arg(_url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")
.arg(url().toLocalFile()));
return exportDocumentImpl(ExportFileJob(_url.toLocalFile(),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer<KisImportExportFilter> filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return byteArray;
}
d->savingImage = d->image;
- if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
+ if (!filter->convert(this, &buffer).isOk()) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
-void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
+void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage)
{
- if (status == KisImportExportFilter::UserCancelled)
+ if (status.isCancelled())
return;
const QString fileName = QFileInfo(job.filePath).fileName();
- if (status != KisImportExportFilter::OK) {
+ if (!status.isOk()) {
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during saving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
if (!fileBatchMode()) {
const QString filePath = job.filePath;
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage)));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
const QString existingAutoSaveBaseName = localFilePath();
const bool wasRecovered = isRecovered();
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
/**
* If undo stack is already clean/empty, it doesn't emit any
* signals, so we might forget update document modified state
* (which was set, e.g. while recovering an autosave file)
*/
if (d->undoStack->isClean()) {
setModified(false);
} else {
d->undoStack->setClean();
}
}
setRecovered(false);
removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
}
emit completed();
emit sigSavingFinished();
emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
}
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->batchMode;
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->batchMode = batchMode;
}
KisDocument* KisDocument::lockAndCloneForSaving()
{
// force update of all the asynchronous nodes before cloning
QApplication::processEvents();
KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
if (!window->viewManager()->blockUntilOperationsFinished(d->image)) {
return 0;
}
}
}
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return 0;
}
return new KisDocument(*this);
}
+KisDocument *KisDocument::lockAndCreateSnapshot()
+{
+ KisDocument *doc = lockAndCloneForSaving();
+ if (doc) {
+ // clone palette list
+ doc->d->paletteList = doc->d->clonePaletteList(doc->d->paletteList);
+ doc->d->ownsPaletteList = true;
+ }
+ return doc;
+}
+
+void KisDocument::copyFromDocument(const KisDocument &rhs)
+{
+ copyFromDocumentImpl(rhs, REPLACE);
+}
+
+void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy)
+{
+ if (policy == REPLACE) {
+ d->copyFrom(*(rhs.d), this);
+
+ d->undoStack->clear();
+ } else {
+ // in CONSTRUCT mode, d should be already initialized
+ connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
+ connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
+ connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
+
+ d->shapeController = new KisShapeController(this, d->nserver);
+ d->koShapeController = new KoShapeController(0, d->shapeController);
+ d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
+ }
+
+ setObjectName(rhs.objectName());
+
+ slotConfigChanged();
+
+ if (rhs.d->image) {
+ if (policy == REPLACE) {
+ d->image->barrierLock(/* readOnly = */ false);
+ rhs.d->image->barrierLock(/* readOnly = */ true);
+ d->image->copyFromImage(*(rhs.d->image));
+ d->image->unlock();
+ rhs.d->image->unlock();
+ setCurrentImage(d->image, /* forceInitialUpdate = */ true);
+ } else {
+ // clone the image with keeping the GUIDs of the layers intact
+ // NOTE: we expect the image to be locked!
+ setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
+ }
+ }
+
+ if (rhs.d->preActivatedNode) {
+ QQueue<KisNodeSP> linearizedNodes;
+ KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
+ [&linearizedNodes](KisNodeSP node) {
+ linearizedNodes.enqueue(node);
+ });
+ KisLayerUtils::recursiveApplyNodes(d->image->root(),
+ [&linearizedNodes, &rhs, this](KisNodeSP node) {
+ KisNodeSP refNode = linearizedNodes.dequeue();
+ if (rhs.d->preActivatedNode.data() == refNode.data()) {
+ d->preActivatedNode = node;
+ }
+ });
+ }
+
+ KisNodeSP foundNode = KisLayerUtils::recursiveFindNode(image()->rootLayer(), [](KisNodeSP node) -> bool { return dynamic_cast<KisReferenceImagesLayer *>(node.data()); });
+ KisReferenceImagesLayer *refLayer = dynamic_cast<KisReferenceImagesLayer *>(foundNode.data());
+ setReferenceImagesLayer(refLayer, /* updateImage = */ false);
+
+ if (policy == REPLACE) {
+ setModified(true);
+ }
+}
+
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
{
/**
* The caller guarantees that noone else uses the document (usually,
* it is a temporary docuent created specifically for exporting), so
* we don't need to copy or lock the document. Instead we should just
* ensure the barrier lock is synced and then released.
*/
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
- KisImportExportFilter::ConversionStatus status =
+ KisImportExportErrorCode status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
- return status == KisImportExportFilter::OK;
+ return status.isOk();
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
{
return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
job, exportConfiguration, std::unique_ptr<KisDocument>());
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr<KisDocument> &&optionalClonedDocument)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
QScopedPointer<KisDocument> clonedDocument;
if (!optionalClonedDocument) {
clonedDocument.reset(lockAndCloneForSaving());
} else {
clonedDocument.reset(optionalClonedDocument.release());
}
// we block saving until the current saving is finished!
if (!clonedDocument || !d->savingMutex.tryLock()) {
return false;
}
auto waitForImage = [] (KisImageSP image) {
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
window->viewManager()->blockUntilOperationsFinishedForced(image);
}
}
};
{
KisNodeSP newRoot = clonedDocument->image()->root();
KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) {
KisLayerUtils::forceAllDelayedNodesUpdate(newRoot);
waitForImage(clonedDocument->image());
}
}
KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
waitForImage(clonedDocument->image());
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
d->modifiedWhileSaving = false;
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = true;
}
connect(d->backgroundSaveDocument.data(),
- SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)),
+ SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)),
this,
- SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString)));
+ SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString)));
- connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
+ connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)),
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
exportConfiguration);
if (!started) {
// the state should have been deinitialized in slotChildCompletedSavingInBackground()
KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
d->backgroundSaveJob = KritaUtils::ExportFileJob();
}
}
return started;
}
-void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
+void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage)
{
KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
d->savingMutex.unlock();
return;
}
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3")
.arg(job.filePath)
.arg(QString::fromLatin1(job.mimeType))
- .arg(status != KisImportExportFilter::OK ? exportErrorToUserMessage(status, errorMessage) : "OK"));
+ .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK"));
emit sigCompleteBackgroundSaving(job, status, errorMessage);
}
void KisDocument::slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument)
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
const bool hadClonedDocument = bool(optionalClonedDocument);
bool started = false;
if (d->image->isIdle() || hadClonedDocument) {
started = initiateSavingInBackground(i18n("Autosaving..."),
- this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
+ this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)),
KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
0,
std::move(optionalClonedDocument));
} else {
emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
}
if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
Qt::BlockingQueuedConnection);
KisStrokeId strokeId = d->image->startStroke(stroke);
d->image->endStroke(strokeId);
setInfiniteAutoSaveInterval();
} else if (!started) {
setEmergencyAutoSaveInterval();
} else {
d->modifiedAfterAutosave = false;
}
}
void KisDocument::slotAutoSave()
{
slotAutoSaveImpl(std::unique_ptr<KisDocument>());
}
void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
{
slotAutoSaveImpl(std::unique_ptr<KisDocument>(clonedDocument));
}
-void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
+void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
- if (status != KisImportExportFilter::OK) {
+ if (!status.isOk()) {
setEmergencyAutoSaveInterval();
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg(true);
d->autoSaveDelay = cfg.autoSaveInterval();
if (!d->modifiedWhileSaving) {
d->autoSaveTimer->stop(); // until the next change
d->autoSaveFailureCount = 0;
} else {
setNormalAutoSaveInterval();
}
emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
}
}
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
d->importExportManager->setUpdater(d->savingUpdater);
}
}
- KisImportExportFilter::ConversionStatus initializationStatus;
+ KisImportExportErrorCode initializationStatus(ImportExportCodes::OK);
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
initializationStatus,
showWarnings,
exportConfiguration);
- if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) {
+ if (!initializationStatus.isOk()) {
if (d->savingUpdater) {
d->savingUpdater->cancel();
}
d->savingImage.clear();
- emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage());
+ emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage());
return false;
}
- typedef QFutureWatcher<KisImportExportFilter::ConversionStatus> StatusWatcher;
+ typedef QFutureWatcher<KisImportExportErrorCode> StatusWatcher;
StatusWatcher *watcher = new StatusWatcher();
watcher->setFuture(d->childSavingFuture);
connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return true;
}
void KisDocument::finishExportInBackground()
{
KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
- emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, "");
+ emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, "");
return;
}
- KisImportExportFilter::ConversionStatus status =
+ KisImportExportErrorCode status =
d->childSavingFuture.result();
- const QString errorMessage = this->errorMessage();
+ const QString errorMessage = status.errorMessage();
d->savingImage.clear();
- d->childSavingFuture = QFuture<KisImportExportFilter::ConversionStatus>();
+ d->childSavingFuture = QFuture<KisImportExportErrorCode>();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setNormalAutoSaveInterval();
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
if (isReadWrite() && delay > 0) {
d->autoSaveTimer->start(delay * 1000);
} else {
d->autoSaveTimer->stop();
}
}
void KisDocument::setNormalAutoSaveInterval()
{
setAutoSaveDelay(d->autoSaveDelay);
d->autoSaveFailureCount = 0;
}
void KisDocument::setEmergencyAutoSaveInterval()
{
const int emergencyAutoSaveInterval = 10; /* sec */
setAutoSaveDelay(emergencyAutoSaveInterval);
d->autoSaveFailureCount++;
}
void KisDocument::setInfiniteAutoSaveInterval()
{
setAutoSaveDelay(-1);
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
QString prefix = KisConfig(true).readEntry<bool>("autosavefileshidden") ? QString(".") : QString();
QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
QRegularExpression autosavePattern2("^.+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#endif
} else {
retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast<KisApplication*>(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
setRecovered(true);
}
else {
if (ret) {
if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
setRecovered(false);
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "<html><body><p><b>";
if (warnings.size() == 1) {
warning += "</b> Reason:</p>";
}
else {
warning += "</b> Reasons:</p>";
}
warning += "<p/><ul>";
Q_FOREACH(const QString &w, warnings) {
warning += "\n<li>" + w + "</li>";
}
warning += "</ul>";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
KoUpdaterPtr updater;
if (window && window->viewManager()) {
updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
- KisImportExportFilter::ConversionStatus status;
-
- status = d->importExportManager->importDocument(localFilePath(), typeName);
+ KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName);
- if (status != KisImportExportFilter::OK) {
+ if (!status.isOk()) {
if (window && window->viewManager()) {
updater->cancel();
}
- QString msg = KisImportExportFilter::conversionStatusString(status);
+ QString msg = status.errorMessage();
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
void KisDocument::autoSaveOnPause()
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
bool started = initiateSavingSynchronously(
KritaUtils::ExportFileJob(
autoSaveFileName, nativeFormatMimeType(),
KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode));
// FIXME?: when the document is pushed to background while it is being
// drawn on, it doesn't get saved. One possible solution could be to block
// and emit signal when it finishes.
if (started)
{
d->modifiedAfterAutosave = false;
}
else
{
qDebug() << "Could not auto-save when paused";
}
}
bool KisDocument::initiateSavingSynchronously(const KritaUtils::ExportFileJob& job)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false)
QUrl url("file:/" + job.filePath);
bool started = exportDocumentSync(url, job.mimeType);
if (!started)
{
d->savingMutex.unlock();
// KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
// KisStrokeId strokeId = d->image->startStroke(stroke);
// d->image->endStroke(strokeId);
// d->image->unlock();
// try again
started = exportDocumentSync(url, job.mimeType);
}
return started;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified();
if (mod && !d->autoSaveTimer->isActive()) {
// First change since last autosave -> start the autosave timer
setNormalAutoSaveInterval();
}
d->modifiedAfterAutosave = mod;
d->modifiedWhileSaving = mod;
if (mod == isModified())
return;
d->modified = mod;
if (mod) {
documentInfo()->updateParameters();
}
// This influences the title
setTitleModified();
emit modified(mod);
}
void KisDocument::setRecovered(bool value)
{
d->isRecovered = value;
}
bool KisDocument::isRecovered() const
{
return d->isRecovered;
}
void KisDocument::updateEditingTime(bool forceStoreElapsed)
{
QDateTime now = QDateTime::currentDateTime();
int firstModDelta = d->firstMod.secsTo(now);
int lastModDelta = d->lastMod.secsTo(now);
if (lastModDelta > 30) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
const QString _url(url().fileName());
// if URL is empty...it is probably an unsaved file
if (_url.isEmpty()) {
c = " [" + i18n("Not Saved") + "] ";
} else {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
QList<QRegularExpression> expressions;
expressions << QRegularExpression("^\\..+-autosave.kra$")
<< QRegularExpression("^.+-autosave.kra$");
Q_FOREACH(const QRegularExpression &rex, expressions) {
if (wasRecovered &&
!autosaveBaseName.isEmpty() &&
rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
QFile::exists(autosaveBaseName)) {
QFile::remove(autosaveBaseName);
}
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg(true);
- if (!d->undoStack->isClean() && d->undoStack->undoLimit() != cfg.undoStackLimit()) {
- d->undoStack->clear();
+
+ if (d->undoStack->undoLimit() != cfg.undoStackLimit()) {
+ if (!d->undoStack->isClean()) {
+ d->undoStack->clear();
+ }
d->undoStack->setUndoLimit(cfg.undoStackLimit());
}
d->autoSaveDelay = cfg.autoSaveInterval();
setNormalAutoSaveInterval();
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
- d->gridConfig = config;
+ if (d->gridConfig != config) {
+ d->gridConfig = config;
+ emit sigGridConfigChanged(config);
+ }
}
QList<KoColorSet *> &KisDocument::paletteList()
{
return d->paletteList;
}
-void KisDocument::setPaletteList(const QList<KoColorSet *> &paletteList)
+void KisDocument::setPaletteList(const QList<KoColorSet *> &paletteList, bool emitSignal)
{
- d->paletteList = paletteList;
+ if (d->paletteList != paletteList) {
+ QList<KoColorSet *> oldPaletteList = d->paletteList;
+ d->paletteList = paletteList;
+ if (emitSignal) {
+ emit sigPaletteListChanged(oldPaletteList, paletteList);
+ }
+ }
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const
{
return d->mirrorAxisConfig;
}
void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config)
{
if (d->mirrorAxisConfig == config) {
return;
}
d->mirrorAxisConfig = config;
setModified(true);
emit sigMirrorAxisConfigChanged();
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() ) {
return false;
}
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() ) {
return false;
}
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setUrl(d->m_url);
ret = openFile();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisImageSP image;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
documentInfo()->setAboutInfo("abstract", description);
KisLayerSP layer;
if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
KoColor strippedAlpha = bgColor;
strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
if (bgStyle == KisConfig::RASTER_LAYER) {
layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);;
layer->paintDevice()->setDefaultPixel(strippedAlpha);
} else if (bgStyle == KisConfig::FILL_LAYER) {
KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration();
filter_config->setProperty("color", strippedAlpha.toQColor());
layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection());
}
layer->setOpacity(bgColor.opacityU8());
if (numberOfLayers > 1) {
//Lock bg layer if others are present.
layer->setUserLocked(true);
}
}
else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
image->setDefaultProjectionColor(bgColor);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
}
Q_CHECK_PTR(layer);
image->addNode(layer.data(), image->rootLayer().data());
layer->setDirty(QRect(0, 0, width, height));
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
KisConfig cfg(false);
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8"
, name
, width, height
, imageResolution * 72.0
, image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name()
, image->colorSpace()->profile()->name()
, numberOfLayers));
QApplication::restoreOverrideCursor();
return true;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
if (isSaving()) {
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
}
KoShapeControllerBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList<KisPaintingAssistantSP> KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList<KisPaintingAssistantSP> &value)
{
- d->assistants = value;
+ if (d->assistants != value) {
+ d->assistants = value;
+ emit sigAssistantsChanged();
+ }
}
KisSharedPtr<KisReferenceImagesLayer> KisDocument::referenceImagesLayer() const
{
return d->referenceImagesLayer.data();
}
void KisDocument::setReferenceImagesLayer(KisSharedPtr<KisReferenceImagesLayer> layer, bool updateImage)
{
+ if (d->referenceImagesLayer == layer) {
+ return;
+ }
+
if (d->referenceImagesLayer) {
d->referenceImagesLayer->disconnect(this);
}
if (updateImage) {
if (layer) {
d->image->addNode(layer);
} else {
d->image->removeNode(d->referenceImagesLayer);
}
}
d->referenceImagesLayer = layer;
if (d->referenceImagesLayer) {
connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
this, SIGNAL(sigReferenceImagesChanged()));
}
+ emit sigReferenceImagesLayerChanged(layer);
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->setUndoStore(new KisDumbUndoStore());
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->image->setUndoStore(new KisDocumentUndoStore(this));
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
- setModified(true);
+ // we only set as modified if undo stack is not at clean state
+ setModified(!d->undoStack->isClean());
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
-QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
+QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
{
- return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage;
+ return errorMessage.isEmpty() ? status.errorMessage() : errorMessage;
}
void KisDocument::setAssistantsGlobalColor(QColor color)
{
d->globalAssistantsColor = color;
}
QColor KisDocument::assistantsGlobalColor()
{
return d->globalAssistantsColor;
}
QRectF KisDocument::documentBounds() const
{
QRectF bounds = d->image->bounds();
if (d->referenceImagesLayer) {
bounds |= d->referenceImagesLayer->boundingImageRect();
}
return bounds;
}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index cd6c373a2a..1d41b9eed7 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,673 +1,698 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt <boud@valdyas.org>
*
* 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 KISDOCUMENT_H
#define KISDOCUMENT_H
#include <QDateTime>
#include <QTransform>
#include <QList>
#include <klocalizedstring.h>
#include <KoPageLayout.h>
#include <KoDocumentBase.h>
#include <kundo2stack.h>
#include <KoColorSet.h>
#include <kis_image.h>
#include <KisImportExportFilter.h>
#include <kis_properties_configuration.h>
#include <kis_types.h>
#include <kis_painting_assistant.h>
#include <KisReferenceImage.h>
#include <kis_debug.h>
#include <KisImportExportUtils.h>
#include <kis_config.h>
#include "kritaui_export.h"
#include <memory>
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeControllerBase;
class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
class KisMirrorAxisConfig;
class QDomDocument;
class KisReferenceImagesLayer;
#define KIS_MIME_TYPE "application/x-krita"
/**
* The %Calligra document class
*
* This class provides some functionality each %Calligra document should have.
*
* @short The %Calligra document class
*/
class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase
{
Q_OBJECT
protected:
explicit KisDocument();
/**
* @brief KisDocument makes a deep copy of the document \p rhs.
* The caller *must* ensure that the image is properly
* locked and is in consistent state before asking for
* cloning.
* @param rhs the source document to copy from
*/
explicit KisDocument(const KisDocument &rhs);
public:
enum OpenFlag {
None = 0,
DontAddToRecent = 0x1,
RecoveryFile = 0x2
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
/**
* Destructor.
*
* The destructor does not delete any attached KisView objects and it does not
* delete the attached widget as returned by widget().
*/
~KisDocument() override;
/**
* @brief reload Reloads the document from the original url
* @return the result of loading the document
*/
bool reload();
/**
* @brief creates a clone of the document and returns it. Please make sure that you
* hold all the necessary locks on the image before asking for a clone!
*/
KisDocument* clone();
/**
* @brief openUrl Open an URL
* @param url The URL to open
* @param flags Control specific behavior
* @return success status
*/
bool openUrl(const QUrl &url, OpenFlags flags = None);
/**
* Opens the document given by @p url, without storing the URL
* in the KisDocument.
* Call this instead of openUrl() to implement KisMainWindow's
* File --> Import feature.
*
* @note This will call openUrl(). To differentiate this from an ordinary
* Open operation (in any reimplementation of openUrl() or openFile())
* call isImporting().
*/
bool importDocument(const QUrl &url);
/**
* Saves the document as @p url without changing the state of the
* KisDocument (URL, modified flag etc.). Call this instead of
* KisParts::ReadWritePart::saveAs() to implement KisMainWindow's
* File --> Export feature.
*/
bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0);
/**
* Exports he document is a synchronous way. The caller must ensure that the
* image is not accessed by any other actors, because the exporting happens
* without holding the image lock.
*/
bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0);
private:
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration);
public:
/**
* @brief Sets whether the document can be edited or is read only.
*
* This recursively applied to all child documents and
* KisView::updateReadWrite is called for every attached
* view.
*/
void setReadWrite(bool readwrite = true);
/**
* To be preferred when a document exists. It is fast when calling
* it multiple times since it caches the result that readNativeFormatMimeType()
* delivers.
* This comes from the X-KDE-NativeMimeType key in the .desktop file.
*/
static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; }
/// Checks whether a given mimetype can be handled natively.
bool isNativeFormat(const QByteArray& mimetype) const;
/// Returns a list of the mimetypes considered "native", i.e. which can
/// be saved by KisDocument without a filter, in *addition* to the main one
static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; }
/**
* Returns the actual mimetype of the document
*/
QByteArray mimeType() const override;
/**
* @brief Sets the mime type for the document.
*
* When choosing "save as" this is also the mime type
* selected by default.
*/
void setMimeType(const QByteArray & mimeType) override;
/**
* @return true if file operations should inhibit the option dialog
*/
bool fileBatchMode() const;
/**
* @param batchMode if true, do not show the option dialog for file operations.
*/
void setFileBatchMode(const bool batchMode);
/**
* Sets the error message to be shown to the user (use i18n()!)
* when loading or saving fails.
* If you asked the user about something and they chose "Cancel",
*/
void setErrorMessage(const QString& errMsg);
/**
* Return the last error message. Usually KisDocument takes care of
* showing it; this method is mostly provided for non-interactive use.
*/
QString errorMessage() const;
/**
* Sets the warning message to be shown to the user (use i18n()!)
* when loading or saving fails.
*/
void setWarningMessage(const QString& warningMsg);
/**
* Return the last warning message set by loading or saving. Warnings
* mean that the document could not be completely loaded, but the errors
* were not absolutely fatal.
*/
QString warningMessage() const;
/**
* @brief Generates a preview picture of the document
* @note The preview is used in the File Dialog and also to create the Thumbnail
*/
QPixmap generatePreview(const QSize& size);
/**
* Tells the document that its title has been modified, either because
* the modified status changes (this is done by setModified() ) or
* because the URL or the document-info's title changed.
*/
void setTitleModified();
/**
* @brief Sets the document to empty.
*
* Used after loading a template
* (which is not empty, but not the user's input).
*
* @see isEmpty()
*/
void setEmpty(bool empty = true);
/**
* Return a correctly created QDomDocument for this KisDocument,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* @param tagName the name of the tag for the root element
* @param version the DTD version (usually the application's version).
*/
QDomDocument createDomDocument(const QString& tagName, const QString& version) const;
/**
* Return a correctly created QDomDocument for an old (1.3-style) %Calligra document,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* This static method can be used e.g. by filters.
* @param appName the app's instance name, e.g. words, kspread, kpresenter etc.
* @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter.
* @param version the DTD version (usually the application's version).
*/
static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version);
/**
* Loads a document in the native format from a given URL.
* Reimplement if your native format isn't XML.
*
* @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter
*/
bool loadNativeFormat(const QString & file);
/**
* Set standard autosave interval that is set by a config file
*/
void setNormalAutoSaveInterval();
/**
* Set emergency interval that autosave uses when the image is busy,
* by default it is 10 sec
*/
void setEmergencyAutoSaveInterval();
/**
* Disable autosave
*/
void setInfiniteAutoSaveInterval();
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() const;
/**
* Performs a cleanup of unneeded backup files
*/
void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered);
/**
* Returns true if this document or any of its internal child documents are modified.
*/
bool isModified() const override;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* @internal (public for KisMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
KisGridConfig gridConfig() const;
void setGridConfig(const KisGridConfig &config);
/// returns the guides data for this document.
const KisGuidesConfig& guidesConfig() const;
void setGuidesConfig(const KisGuidesConfig &data);
const KisMirrorAxisConfig& mirrorAxisConfig() const;
void setMirrorAxisConfig(const KisMirrorAxisConfig& config);
QList<KoColorSet *> &paletteList();
- void setPaletteList(const QList<KoColorSet *> &paletteList);
+ void setPaletteList(const QList<KoColorSet *> &paletteList, bool emitSignal = false);
void clearUndoHistory();
/**
* Sets the modified flag on the document. This means that it has
* to be saved or not before deleting it.
*/
void setModified(bool _mod);
void setRecovered(bool value);
bool isRecovered() const;
void updateEditingTime(bool forceStoreElapsed);
/**
* Returns the global undo stack
*/
KUndo2Stack *undoStack();
/**
* @brief importExportManager gives access to the internal import/export manager
* @return the document's import/export manager
*/
KisImportExportManager *importExportManager() const;
/**
* @brief serializeToNativeByteArray daves the document into a .kra file wtitten
* to a memory-based byte-array
* @return a byte array containing the .kra file
*/
QByteArray serializeToNativeByteArray();
/**
* @brief isInSaving shown if the document has any (background) saving process or not
* @return true if there is some saving in action
*/
bool isInSaving() const;
public Q_SLOTS:
/**
* Adds a command to the undo stack and executes it by calling the redo() function.
* @param command command to add to the undo stack
*/
void addCommand(KUndo2Command *command);
/**
* Begins recording of a macro command. At the end endMacro needs to be called.
* @param text command description
*/
void beginMacro(const KUndo2MagicString &text);
/**
* Ends the recording of a macro command.
*/
void endMacro();
Q_SIGNALS:
/**
* This signal is emitted when the unit is changed by setUnit().
* It is common to connect views to it, in order to change the displayed units
* (e.g. in the rulers)
*/
void unitChanged(const KoUnit &unit);
/**
* Emitted e.g. at the beginning of a save operation
* This is emitted by KisDocument and used by KisView to display a statusbar message
*/
void statusBarMessage(const QString& text, int timeout = 0);
/**
* Emitted e.g. at the end of a save operation
* This is emitted by KisDocument and used by KisView to clear the statusbar message
*/
void clearStatusBarMessage();
/**
* Emitted when the document is modified
*/
void modified(bool);
void titleModified(const QString &caption, bool isModified);
void sigLoadingFinished();
void sigSavingFinished();
void sigGuidesConfigChanged(const KisGuidesConfig &config);
- void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
+ void sigBackgroundSavingFinished(KisImportExportErrorCode status, const QString &errorMessage);
- void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
+ void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage);
void sigReferenceImagesChanged();
void sigMirrorAxisConfigChanged();
+ void sigGridConfigChanged(const KisGridConfig &config);
+
+ void sigReferenceImagesLayerChanged(KisSharedPtr<KisReferenceImagesLayer> layer);
+
+ /**
+ * Emitted when the palette list has changed.
+ * The pointers in oldPaletteList are to be deleted by the resource server.
+ **/
+ void sigPaletteListChanged(const QList<KoColorSet *> &oldPaletteList, const QList<KoColorSet *> &newPaletteList);
+
+ void sigAssistantsChanged();
+
private Q_SLOTS:
void finishExportInBackground();
- void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
- void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
+ void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage);
+ void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage);
- void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
+ void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage);
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument);
private:
friend class KisPart;
friend class SafeSavingLocker;
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr<KisDocument> &&optionalClonedDocument);
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration);
bool startExportInBackground(const QString &actionName, const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration);
bool initiateSavingSynchronously(const KritaUtils::ExportFileJob& job);
/**
* Activate/deactivate/configure the autosave feature.
* @param delay in seconds, 0 to disable
*/
void setAutoSaveDelay(int delay);
/**
* Generate a name for the document.
*/
QString newObjectName();
QString generateAutoSaveFileName(const QString & path) const;
/**
* Loads a document
*
* Applies a filter if necessary, and calls loadNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*
* NOTE: this method also creates a new KisView instance!
*
* This method is called from the KReadOnlyPart::openUrl method.
*/
bool openFile();
public:
bool isAutosaving() const override;
public:
QString localFilePath() const override;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const override;
void setUrl(const QUrl &url) override;
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0);
/**
* Create a new image that has this document as a parent and
* replace the current image with this image.
*/
bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
int numberOfLayers, const QString &imageDescription, const double imageResolution);
bool isSaving() const;
void waitForSavingToComplete();
KisImageWSP image() const;
/**
* @brief savingImage provides a detached, shallow copy of the original image that must be used when saving.
* Any strokes in progress will not be applied to this image, so the result might be missing some data. On
* the other hand, it won't block.
*
* @return a shallow copy of the original image, or 0 is saving is not in progress
*/
KisImageSP savingImage() const;
/**
* Set the current image to the specified image and turn undo on.
*/
void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true);
/**
* Set the image of the document preliminary, before the document
* has completed loading. Some of the document items (shapes) may want
* to access image properties (bounds and resolution), so we should provide
* it to them even before the entire image is loaded.
*
* Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove
* after it is deprecated.
*/
void hackPreliminarySetImage(KisImageSP image);
KisUndoStore* createUndoStore();
/**
* The shape controller matches internal krita image layers with
* the flake shape hierarchy.
*/
KoShapeControllerBase * shapeController() const;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
/**
* Set the list of nodes that was marked as currently active. Used *only*
* for saving loading. Never use it for tools or processing.
*/
void setPreActivatedNode(KisNodeSP activatedNode);
/**
* @return the node that was set as active during loading. Used *only*
* for saving loading. Never use it for tools or processing.
*/
KisNodeSP preActivatedNode() const;
/// @return the list of assistants associated with this document
QList<KisPaintingAssistantSP> assistants() const;
/// @replace the current list of assistants with @param value
void setAssistants(const QList<KisPaintingAssistantSP> &value);
void setAssistantsGlobalColor(QColor color);
QColor assistantsGlobalColor();
/**
* Get existing reference images layer or null if none exists.
*/
KisSharedPtr<KisReferenceImagesLayer> referenceImagesLayer() const;
void setReferenceImagesLayer(KisSharedPtr<KisReferenceImagesLayer> layer, bool updateImage);
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
/**
* Return the bounding box of the image and associated elements (e.g. reference images)
*/
QRectF documentBounds() const;
/**
* @brief Start saving when android activity is pushed to the background
*/
void autoSaveOnPause();
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
void slotUndoStackCleanChanged(bool value);
void slotConfigChanged();
-
-private:
/**
* @brief try to clone the image. This method handles all the locking for you. If locking
* has failed, no cloning happens
* @return cloned document on success, null otherwise
*/
KisDocument *lockAndCloneForSaving();
- QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
+public:
+
+ KisDocument *lockAndCreateSnapshot();
+
+ void copyFromDocument(const KisDocument &rhs);
+
+private:
+
+ enum CopyPolicy {
+ CONSTRUCT = 0, ///< we are copy-constructing a new KisDocument
+ REPLACE ///< we are replacing the current KisDocument with another
+ };
+
+ void copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy);
+
+ QString exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage);
QString prettyPathOrUrl() const;
bool openUrlInternal(const QUrl &url);
void slotAutoSaveImpl(std::unique_ptr<KisDocument> &&optionalClonedDocument);
class Private;
Private *const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags)
Q_DECLARE_METATYPE(KisDocument*)
#endif
diff --git a/libs/ui/KisFrameDataSerializer.cpp b/libs/ui/KisFrameDataSerializer.cpp
index 9ddd6c55c5..9bff923337 100644
--- a/libs/ui/KisFrameDataSerializer.cpp
+++ b/libs/ui/KisFrameDataSerializer.cpp
@@ -1,354 +1,355 @@
/*
* Copyright (c) 2018 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KisFrameDataSerializer.h"
#include <cstring>
#include <QTemporaryDir>
#include <QElapsedTimer>
#include "tiles3/swap/kis_lzf_compression.h"
struct KRITAUI_NO_EXPORT KisFrameDataSerializer::Private
{
Private(const QString &frameCachePath)
: framesDir(
- (!frameCachePath.isEmpty() ? frameCachePath : QDir::tempPath()) +
- QDir::separator() + "KritaFrameCacheXXXXXX")
+ (!frameCachePath.isEmpty() && QTemporaryDir(frameCachePath + "/KritaFrameCacheXXXXXX").isValid()
+ ? frameCachePath
+ : QDir::tempPath())
+ + "/KritaFrameCacheXXXXXX")
{
- KIS_SAFE_ASSERT_RECOVER_NOOP(framesDir.isValid());
framesDirObject = QDir(framesDir.path());
framesDirObject.makeAbsolute();
}
QString subfolderNameForFrame(int frameId)
{
const int subfolderIndex = frameId & 0xff00;
return QString::number(subfolderIndex);
}
QString fileNameForFrame(int frameId) {
return QString("frame_%1").arg(frameId);
}
QString filePathForFrame(int frameId)
{
return framesDirObject.filePath(
subfolderNameForFrame(frameId) + QDir::separator() +
fileNameForFrame(frameId));
}
int generateFrameId() {
// TODO: handle wrapping and range compression
return nextFrameId++;
}
quint8* getCompressionBuffer(int size) {
if (compressionBuffer.size() < size) {
compressionBuffer.resize(size);
}
return reinterpret_cast<quint8*>(compressionBuffer.data());
}
QTemporaryDir framesDir;
QDir framesDirObject;
int nextFrameId = 0;
QByteArray compressionBuffer;
};
KisFrameDataSerializer::KisFrameDataSerializer()
: KisFrameDataSerializer(QString())
{
}
KisFrameDataSerializer::KisFrameDataSerializer(const QString &frameCachePath)
: m_d(new Private(frameCachePath))
{
}
KisFrameDataSerializer::~KisFrameDataSerializer()
{
}
int KisFrameDataSerializer::saveFrame(const KisFrameDataSerializer::Frame &frame)
{
KisLzfCompression compression;
const int frameId = m_d->generateFrameId();
const QString frameSubfolder = m_d->subfolderNameForFrame(frameId);
if (!m_d->framesDirObject.exists(frameSubfolder)) {
m_d->framesDirObject.mkpath(frameSubfolder);
}
const QString frameRelativePath = frameSubfolder + QDir::separator() + m_d->fileNameForFrame(frameId);
if (m_d->framesDirObject.exists(frameRelativePath)) {
qWarning() << "WARNING: overwriting existing frame file!" << frameRelativePath;
forgetFrame(frameId);
}
const QString frameFilePath = m_d->framesDirObject.filePath(frameRelativePath);
QFile file(frameFilePath);
file.open(QFile::WriteOnly);
QDataStream stream(&file);
stream << frameId;
stream << frame.pixelSize;
stream << int(frame.frameTiles.size());
for (int i = 0; i < int(frame.frameTiles.size()); i++) {
const FrameTile &tile = frame.frameTiles[i];
stream << tile.col;
stream << tile.row;
stream << tile.rect;
const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
const int maxBufferSize = compression.outputBufferSize(frameByteSize);
quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
const int compressedSize =
compression.compress(tile.data.data(), frameByteSize, buffer, maxBufferSize);
//ENTER_FUNCTION() << ppVar(compressedSize) << ppVar(frameByteSize);
const bool isCompressed = compressedSize < frameByteSize;
stream << isCompressed;
if (isCompressed) {
stream << compressedSize;
stream.writeRawData((char*)buffer, compressedSize);
} else {
stream << frameByteSize;
stream.writeRawData((char*)tile.data.data(), frameByteSize);
}
}
file.close();
return frameId;
}
KisFrameDataSerializer::Frame KisFrameDataSerializer::loadFrame(int frameId, KisTextureTileInfoPoolSP pool)
{
KisLzfCompression compression;
QElapsedTimer loadingTime;
loadingTime.start();
int loadedFrameId = -1;
KisFrameDataSerializer::Frame frame;
qint64 compressionTime = 0;
const QString framePath = m_d->filePathForFrame(frameId);
QFile file(framePath);
KIS_SAFE_ASSERT_RECOVER_NOOP(file.exists());
if (!file.open(QFile::ReadOnly)) return frame;
QDataStream stream(&file);
int numTiles = 0;
stream >> loadedFrameId;
stream >> frame.pixelSize;
stream >> numTiles;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(loadedFrameId == frameId, KisFrameDataSerializer::Frame());
for (int i = 0; i < numTiles; i++) {
FrameTile tile(pool);
stream >> tile.col;
stream >> tile.row;
stream >> tile.rect;
const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize <= pool->chunkSize(frame.pixelSize),
KisFrameDataSerializer::Frame());
bool isCompressed = false;
int inputSize = -1;
stream >> isCompressed;
stream >> inputSize;
if (isCompressed) {
const int maxBufferSize = compression.outputBufferSize(inputSize);
quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
stream.readRawData((char*)buffer, inputSize);
tile.data.allocate(frame.pixelSize);
QElapsedTimer compTime;
compTime.start();
const int decompressedSize =
compression.decompress(buffer, inputSize, tile.data.data(), frameByteSize);
compressionTime += compTime.nsecsElapsed();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == decompressedSize,
KisFrameDataSerializer::Frame());
} else {
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == inputSize,
KisFrameDataSerializer::Frame());
tile.data.allocate(frame.pixelSize);
stream.readRawData((char*)tile.data.data(), inputSize);
}
frame.frameTiles.push_back(std::move(tile));
}
file.close();
return frame;
}
void KisFrameDataSerializer::moveFrame(int srcFrameId, int dstFrameId)
{
const QString srcFramePath = m_d->filePathForFrame(srcFrameId);
const QString dstFramePath = m_d->filePathForFrame(dstFrameId);
KIS_SAFE_ASSERT_RECOVER_RETURN(QFileInfo(srcFramePath).exists());
KIS_SAFE_ASSERT_RECOVER(!QFileInfo(dstFramePath).exists()) {
QFile::remove(dstFramePath);
}
QFile::rename(srcFramePath, dstFramePath);
}
bool KisFrameDataSerializer::hasFrame(int frameId) const
{
const QString framePath = m_d->filePathForFrame(frameId);
return QFileInfo(framePath).exists();
}
void KisFrameDataSerializer::forgetFrame(int frameId)
{
const QString framePath = m_d->filePathForFrame(frameId);
QFile::remove(framePath);
}
boost::optional<qreal> KisFrameDataSerializer::estimateFrameUniqueness(const KisFrameDataSerializer::Frame &lhs, const KisFrameDataSerializer::Frame &rhs, qreal portion)
{
if (lhs.pixelSize != rhs.pixelSize) return boost::none;
if (lhs.frameTiles.size() != rhs.frameTiles.size()) return boost::none;
const int pixelSize = lhs.pixelSize;
int numSampledPixels = 0;
int numUniquePixels = 0;
const int sampleStep = portion > 0.0 ? qMax(1, qRound(1.0 / portion)) : 0;
for (int i = 0; i < int(lhs.frameTiles.size()); i++) {
const FrameTile &lhsTile = lhs.frameTiles[i];
const FrameTile &rhsTile = rhs.frameTiles[i];
if (lhsTile.col != rhsTile.col ||
lhsTile.row != rhsTile.row ||
lhsTile.rect != rhsTile.rect) {
return boost::none;
}
if (sampleStep > 0) {
const int numPixels = lhsTile.rect.width() * lhsTile.rect.height();
for (int j = 0; j < numPixels; j += sampleStep) {
quint8 *lhsDataPtr = lhsTile.data.data() + j * pixelSize;
quint8 *rhsDataPtr = rhsTile.data.data() + j * pixelSize;
if (std::memcmp(lhsDataPtr, rhsDataPtr, pixelSize) != 0) {
numUniquePixels++;
}
numSampledPixels++;
}
}
}
return numSampledPixels > 0 ? qreal(numUniquePixels) / numSampledPixels : 1.0;
}
template <template <typename U> class OpPolicy, typename T>
bool processData(T *dst, const T *src, int numUnits)
{
OpPolicy<T> op;
bool unitsAreSame = true;
for (int j = 0; j < numUnits; j++) {
*dst = op(*dst, *src);
if (*dst != 0) {
unitsAreSame = false;
}
src++;
dst++;
}
return unitsAreSame;
}
template<template <typename U> class OpPolicy>
bool KisFrameDataSerializer::processFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src)
{
bool framesAreSame = true;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(estimateFrameUniqueness(src, dst, 0.0), false);
for (int i = 0; i < int(src.frameTiles.size()); i++) {
const FrameTile &srcTile = src.frameTiles[i];
FrameTile &dstTile = dst.frameTiles[i];
const int numBytes = srcTile.rect.width() * srcTile.rect.height() * src.pixelSize;
const int numQWords = numBytes / 8;
const quint64 *srcDataPtr = reinterpret_cast<const quint64*>(srcTile.data.data());
quint64 *dstDataPtr = reinterpret_cast<quint64*>(dstTile.data.data());
framesAreSame &= processData<OpPolicy>(dstDataPtr, srcDataPtr, numQWords);
const int tailBytes = numBytes % 8;
const quint8 *srcTailDataPtr = srcTile.data.data() + numBytes - tailBytes;
quint8 *dstTailDataPtr = dstTile.data.data() + numBytes - tailBytes;
framesAreSame &= processData<OpPolicy>(dstTailDataPtr, srcTailDataPtr, tailBytes);
}
return framesAreSame;
}
bool KisFrameDataSerializer::subtractFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src)
{
return processFrames<std::minus>(dst, src);
}
void KisFrameDataSerializer::addFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src)
{
// TODO: don't spend time on calculation of "framesAreSame" in this case
(void) processFrames<std::plus>(dst, src);
}
diff --git a/libs/ui/KisImageBuilderResult.h b/libs/ui/KisImageBuilderResult.h
index bbdab8e5d7..cbe8f74a25 100644
--- a/libs/ui/KisImageBuilderResult.h
+++ b/libs/ui/KisImageBuilderResult.h
@@ -1,44 +1,46 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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_IMAGEBUILDER_RESULT_H
#define KIS_IMAGEBUILDER_RESULT_H
/**
* Image import/export plugins can use these results to report about success or failure.
*/
enum KisImageBuilder_Result {
KisImageBuilder_RESULT_FAILURE = -400,
KisImageBuilder_RESULT_NOT_EXIST = -300,
KisImageBuilder_RESULT_NOT_LOCAL = -200,
KisImageBuilder_RESULT_BAD_FETCH = -100,
KisImageBuilder_RESULT_INVALID_ARG = -50,
KisImageBuilder_RESULT_OK = 0,
KisImageBuilder_RESULT_PROGRESS = 1,
KisImageBuilder_RESULT_CANCEL = 50,
KisImageBuilder_RESULT_EMPTY = 100,
KisImageBuilder_RESULT_BUSY = 150,
KisImageBuilder_RESULT_NO_URI = 200,
KisImageBuilder_RESULT_UNSUPPORTED = 300,
KisImageBuilder_RESULT_INTR = 400,
KisImageBuilder_RESULT_PATH = 500,
KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE = 600
};
+
+
#endif
diff --git a/plugins/impex/pdf/kis_pdf_import.h b/libs/ui/KisImportExportAdditionalChecks.cpp
similarity index 59%
copy from plugins/impex/pdf/kis_pdf_import.h
copy to libs/ui/KisImportExportAdditionalChecks.cpp
index df387515ff..fa698a310c 100644
--- a/plugins/impex/pdf/kis_pdf_import.h
+++ b/libs/ui/KisImportExportAdditionalChecks.cpp
@@ -1,37 +1,40 @@
/*
- * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
+ * Copyright (c) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_PDF_IMPORT_H
-#define KIS_PDF_IMPORT_H
+#include "KisImportExportAdditionalChecks.h"
+#include <QFileInfo>
-#include <QVariant>
-#include <KisImportExportFilter.h>
+bool KisImportExportAdditionalChecks::isFileWritable(QString filepath)
+{
+ QFileInfo finfo(filepath);
+ return finfo.isWritable();
+}
+
+bool KisImportExportAdditionalChecks::isFileReadable(QString filepath)
+{
+ QFileInfo finfo(filepath);
+ return finfo.isReadable();
+}
-class KisPDFImport : public KisImportExportFilter
+bool KisImportExportAdditionalChecks::doesFileExist(QString filepath)
{
- Q_OBJECT
-public:
- KisPDFImport(QObject *parent, const QVariantList &);
- virtual ~KisPDFImport();
-public:
- virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
-};
+ return QFile::exists(filepath);
+}
-#endif
diff --git a/plugins/impex/pdf/kis_pdf_import.h b/libs/ui/KisImportExportAdditionalChecks.h
similarity index 61%
copy from plugins/impex/pdf/kis_pdf_import.h
copy to libs/ui/KisImportExportAdditionalChecks.h
index df387515ff..007c1c1a50 100644
--- a/plugins/impex/pdf/kis_pdf_import.h
+++ b/libs/ui/KisImportExportAdditionalChecks.h
@@ -1,37 +1,38 @@
/*
- * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
+ * Copyright (c) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_IMPORT_EXPORT_ADDITIONAL_CHECKS_H
+#define KIS_IMPORT_EXPORT_ADDITIONAL_CHECKS_H
-#ifndef KIS_PDF_IMPORT_H
-#define KIS_PDF_IMPORT_H
+#include <QString>
+#include <KisImportExportErrorCode.h>
-#include <QVariant>
-
-#include <KisImportExportFilter.h>
-
-class KisPDFImport : public KisImportExportFilter
+class KRITAUI_EXPORT KisImportExportAdditionalChecks
{
- Q_OBJECT
-public:
- KisPDFImport(QObject *parent, const QVariantList &);
- virtual ~KisPDFImport();
+
public:
- virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
+
+ static bool isFileWritable(QString filepath);
+ static bool isFileReadable(QString filepath);
+ static bool doesFileExist(QString filepath);
};
-#endif
+
+
+
+#endif // #ifndef KIS_IMPORT_EXPORT_ADDITIONAL_CHECKS_H
diff --git a/libs/ui/KisImportExportErrorCode.cpp b/libs/ui/KisImportExportErrorCode.cpp
index 5874351653..b66d5edc78 100644
--- a/libs/ui/KisImportExportErrorCode.cpp
+++ b/libs/ui/KisImportExportErrorCode.cpp
@@ -1,189 +1,223 @@
/*
* Copyright (c) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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 "KisImportExportErrorCode.h"
#include <KLocalizedString>
#include <kis_assert.h>
KisImportExportComplexError::KisImportExportComplexError(QFileDevice::FileError error) : m_error(error) { }
QString KisImportExportComplexError::qtErrorMessage() const
{
// Error descriptions in most cases taken from https://doc.qt.io/qt-5/qfiledevice.html
QString unspecifiedError = i18n("An unspecified error occurred.");
switch (m_error) {
case QFileDevice::FileError::NoError :
// Returning this file error may mean that something is wrong in our code.
// Successful operation should return ImportExportCodes::OK instead.
return i18n("The action has been completed successfully.");
case QFileDevice::FileError::ReadError :
return i18n("An error occurred when reading from the file.");
case QFileDevice::FileError::WriteError :
return i18n("An error occurred when writing to the file.");
case QFileDevice::FileError::FatalError :
return i18n("A fatal error occurred.");
case QFileDevice::FileError::ResourceError :
return i18n("Out of resources (e.g. out of memory).");
case QFileDevice::FileError::OpenError :
return i18n("The file could not be opened.");
case QFileDevice::FileError::AbortError :
return i18n("The operation was aborted.");
case QFileDevice::FileError::TimeOutError :
return i18n("A timeout occurred.");
case QFileDevice::FileError::UnspecifiedError :
return unspecifiedError;
case QFileDevice::FileError::RemoveError :
return i18n("The file could not be removed.");
case QFileDevice::FileError::RenameError :
return i18n("The file could not be renamed.");
case QFileDevice::FileError::PositionError :
return i18n("The position in the file could not be changed.");
case QFileDevice::FileError::ResizeError :
return i18n("The file could not be resized.");
case QFileDevice::FileError::PermissionsError :
return i18n("Permission denied. Krita is not allowed to read or write to the file.");
case QFileDevice::FileError::CopyError :
return i18n("The file could not be copied.");
}
return unspecifiedError;
}
+KisImportExportErrorCannotRead::KisImportExportErrorCannotRead() : KisImportExportComplexError(QFileDevice::FileError()) { }
KisImportExportErrorCannotRead::KisImportExportErrorCannotRead(QFileDevice::FileError error) : KisImportExportComplexError(error) {
KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError);
}
QString KisImportExportErrorCannotRead::errorMessage() const
{
return i18n("Cannot open file for reading. Reason: %1", qtErrorMessage());
}
+bool KisImportExportErrorCannotRead::operator==(KisImportExportErrorCannotRead other)
+{
+ return other.m_error == m_error;
+}
+
+
+
+KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite() : KisImportExportComplexError(QFileDevice::FileError()) { }
KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite(QFileDevice::FileError error) : KisImportExportComplexError(error) {
KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError);
}
QString KisImportExportErrorCannotWrite::errorMessage() const
{
return i18n("Cannot open file for writing. Reason: %1", qtErrorMessage());
}
-KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(QFileDevice::FileError()), cannotWrite(QFileDevice::FileError()) { }
+bool KisImportExportErrorCannotWrite::operator==(KisImportExportErrorCannotWrite other)
+{
+ return other.m_error == m_error;
+}
+
+
-KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(QFileDevice::FileError()), cannotWrite(QFileDevice::FileError()) { }
-KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite(QFileDevice::FileError()) { }
-KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(QFileDevice::FileError()), cannotWrite(error) { }
+KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(), cannotWrite() { }
+KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(), cannotWrite() { }
+KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite() { }
+KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(), cannotWrite(error) { }
bool KisImportExportErrorCode::isOk() const
{
// if cannotRead or cannotWrite is "NoError", it means that something is wrong in our code
return errorFieldUsed == CodeId && codeId == ImportExportCodes::OK;
}
bool KisImportExportErrorCode::isCancelled() const
{
return errorFieldUsed == CodeId && codeId == ImportExportCodes::Cancelled;
}
bool KisImportExportErrorCode::isInternalError() const
{
return errorFieldUsed == CodeId && codeId == ImportExportCodes::InternalError;
}
QString KisImportExportErrorCode::errorMessage() const
{
QString internal = i18n("Unexpected error. Please contact developers.");
if (errorFieldUsed == CannotRead) {
return cannotRead.errorMessage();
} else if (errorFieldUsed == CannotWrite) {
return cannotWrite.errorMessage();
} else if (errorFieldUsed == CodeId) {
switch (codeId) {
// Reading
case ImportExportCodes::FileNotExist:
return i18n("The file doesn't exists.");
case ImportExportCodes::NoAccessToRead:
return i18n("Permission denied: Krita is not allowed to read the file.");
case ImportExportCodes::FileFormatIncorrect:
return i18n("The file format cannot be parsed.");
case ImportExportCodes::FormatFeaturesUnsupported:
return i18n("The file format contains unsupported features.");
case ImportExportCodes::FormatColorSpaceUnsupported:
return i18n("The file format contains unsupported color space.");
+ case ImportExportCodes::ErrorWhileReading:
+ return i18n("Error occurred while reading from the file.");
// Writing
case ImportExportCodes::CannotCreateFile:
return i18n("The file cannot be created.");
case ImportExportCodes::NoAccessToWrite:
return i18n("Permission denied: Krita is not allowed to write to the file.");
case ImportExportCodes::InsufficientMemory:
- return i18n("There is not enough memory left to save the file.");
+ return i18n("There is not enough disk space left to save the file.");
+ case ImportExportCodes::ErrorWhileWriting:
+ return i18n("Error occurred while writing to the file.");
// Both
case ImportExportCodes::Cancelled:
return i18n("The action was cancelled by the user.");
// Other
case ImportExportCodes::Failure:
return i18n("Unknown error.");
case ImportExportCodes::InternalError:
return internal;
// OK
case ImportExportCodes::OK:
return i18n("The action has been completed successfully.");
default:
return internal;
}
}
return internal; // errorFieldUsed = None
}
+bool KisImportExportErrorCode::operator==(KisImportExportErrorCode errorCode)
+{
+ if (errorFieldUsed != errorCode.errorFieldUsed) {
+ return false;
+ }
+ if (errorFieldUsed == CodeId) {
+ return codeId == errorCode.codeId;
+ }
+ if (errorFieldUsed == CannotRead) {
+ return cannotRead == errorCode.cannotRead;
+ }
+ return cannotWrite == errorCode.cannotWrite;
+}
+
+
QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode)
{
switch(errorCode.errorFieldUsed) {
case KisImportExportErrorCode::None:
d << "None of the error fields is in use.";
break;
case KisImportExportErrorCode::CannotRead:
d << "Cannot read: " << errorCode.cannotRead.m_error;
break;
case KisImportExportErrorCode::CannotWrite:
- d << "Cannot read: " << errorCode.cannotRead.m_error;
+ d << "Cannot write: " << errorCode.cannotRead.m_error;
break;
case KisImportExportErrorCode::CodeId:
d << "Error code = " << errorCode.codeId;
}
d << " " << errorCode.errorMessage();
return d;
}
diff --git a/libs/ui/KisImportExportErrorCode.h b/libs/ui/KisImportExportErrorCode.h
index bef2d6395a..dda02c6b15 100644
--- a/libs/ui/KisImportExportErrorCode.h
+++ b/libs/ui/KisImportExportErrorCode.h
@@ -1,141 +1,163 @@
/*
* Copyright (c) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_IMPORT_EXPORT_ERROR_CODES_H
#define KIS_IMPORT_EXPORT_ERROR_CODES_H
#include <QFile>
#include <QFileDevice>
#include <QString>
#include <kritaui_export.h>
#include <QDebug>
namespace ImportExportCodes
{
enum KRITAUI_EXPORT ErrorCodeID
{
InternalError, // error that shouldn't happen; only inside ASSERTS
// Reading
FileNotExist, // there is no file with that name in that location,
NoAccessToRead, // Krita has no reading access to the file,
ErrorWhileReading, // there was an error that occured during reading,
FileFormatIncorrect, // file format cannot be parsed,
FormatFeaturesUnsupported, // file format can be parsed, but some features are unsupported,
FormatColorSpaceUnsupported, // file format can be parsed, but color space of the image is unsupported
// Writing
CannotCreateFile, // file cannot be created
NoAccessToWrite, // Krita has no writing access to the file
ErrorWhileWriting, // there was an error that occured during writing (can be insufficient memory, too, just we don't know)
InsufficientMemory, // there is not enough memory left
// Both
Cancelled, // cancelled by a user
// Other
Failure, // unspecified error
// OK
OK, // everything went ok
};
};
-struct KisImportExportErrorCode;
+class KisImportExportErrorCode;
struct KRITAUI_EXPORT KisImportExportComplexError
{
virtual QString errorMessage() const = 0;
KisImportExportComplexError(QFileDevice::FileError error);
friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode);
+
+
protected:
QString qtErrorMessage() const;
QFileDevice::FileError m_error;
virtual ~KisImportExportComplexError() {}
};
struct KRITAUI_EXPORT KisImportExportErrorCannotWrite : KisImportExportComplexError
{
KisImportExportErrorCannotWrite(QFileDevice::FileError error);
QString errorMessage() const override;
~KisImportExportErrorCannotWrite() { }
+
+ bool operator==(KisImportExportErrorCannotWrite other);
+
+private:
+ KisImportExportErrorCannotWrite();
+
+ //friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite code);
+ friend class KisImportExportErrorCode;
+
};
struct KRITAUI_EXPORT KisImportExportErrorCannotRead : KisImportExportComplexError
{
KisImportExportErrorCannotRead(QFileDevice::FileError error);
QString errorMessage() const override;
~KisImportExportErrorCannotRead() { }
+
+ bool operator==(KisImportExportErrorCannotRead other);
+
+private:
+ KisImportExportErrorCannotRead();
+
+ //friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead code);
+ friend class KisImportExportErrorCode;
+
};
-struct KRITAUI_EXPORT KisImportExportErrorCode
+class KRITAUI_EXPORT KisImportExportErrorCode
{
public:
// required by kis_async_action_feedback
KisImportExportErrorCode();
KisImportExportErrorCode(ImportExportCodes::ErrorCodeID code);
KisImportExportErrorCode(KisImportExportErrorCannotRead code);
KisImportExportErrorCode(KisImportExportErrorCannotWrite code);
QString errorMessage() const;
bool isOk() const;
bool isCancelled() const;
bool isInternalError() const;
friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode);
+ bool operator==(KisImportExportErrorCode errorCode);
+
private:
enum ErrorFieldUsed
{
None,
CodeId,
CannotRead,
CannotWrite
};
ErrorFieldUsed errorFieldUsed;
ImportExportCodes::ErrorCodeID codeId;
KisImportExportErrorCannotRead cannotRead;
KisImportExportErrorCannotWrite cannotWrite;
};
KRITAUI_EXPORT QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode);
#endif // KIS_IMPORT_EXPORT_ERROR_CODES_H
diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp
index 7f5ee2c4c7..65eaf8dedc 100644
--- a/libs/ui/KisImportExportFilter.cpp
+++ b/libs/ui/KisImportExportFilter.cpp
@@ -1,289 +1,344 @@
/* This file is part of the KDE libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
2002 Werner Trobin <trobin@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License 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 "KisImportExportFilter.h"
#include <QFile>
+#include <QFileInfo>
#include <kis_debug.h>
#include <QStack>
#include "KisImportExportManager.h"
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KisExportCheckBase.h>
#include <KisExportCheckRegistry.h>
#include "KoUpdater.h"
#include <klocalizedstring.h>
#include "kis_config.h"
+#include <KoStore.h>
+#include <KisDocument.h>
const QString KisImportExportFilter::ImageContainsTransparencyTag = "ImageContainsTransparency";
const QString KisImportExportFilter::ColorModelIDTag = "ColorModelID";
const QString KisImportExportFilter::ColorDepthIDTag = "ColorDepthID";
const QString KisImportExportFilter::sRGBTag = "sRGB";
class Q_DECL_HIDDEN KisImportExportFilter::Private
{
public:
QPointer<KoUpdater> updater;
QByteArray mime;
QString filename;
QString realFilename;
bool batchmode;
QMap<QString, KisExportCheckBase*> capabilities;
Private()
: updater(0), mime("")
, batchmode(false)
{}
~Private()
{
qDeleteAll(capabilities);
}
};
KisImportExportFilter::KisImportExportFilter(QObject *parent)
: QObject(parent)
, d(new Private)
{
}
KisImportExportFilter::~KisImportExportFilter()
{
if (d->updater) {
d->updater->setProgress(100);
}
delete d;
}
QString KisImportExportFilter::filename() const
{
return d->filename;
}
QString KisImportExportFilter::realFilename() const
{
return d->realFilename;
}
bool KisImportExportFilter::batchMode() const
{
return d->batchmode;
}
void KisImportExportFilter::setBatchMode(bool batchmode)
{
d->batchmode = batchmode;
}
void KisImportExportFilter::setFilename(const QString &filename)
{
d->filename = filename;
}
void KisImportExportFilter::setRealFilename(const QString &filename)
{
d->realFilename = filename;
}
void KisImportExportFilter::setMimeType(const QString &mime)
{
d->mime = mime.toLatin1();
}
QByteArray KisImportExportFilter::mimeType() const
{
return d->mime;
}
QString KisImportExportFilter::conversionStatusString(ConversionStatus status)
{
QString msg;
switch (status) {
case OK: break;
case FilterCreationError:
msg = i18n("Krita does not support this file format"); break;
case CreationError:
msg = i18n("Could not create the output document"); break;
case FileNotFound:
msg = i18n("File not found"); break;
case StorageCreationError:
msg = i18n("Cannot create storage"); break;
case BadMimeType:
msg = i18n("Bad MIME type"); break;
case WrongFormat:
msg = i18n("Format not recognized"); break;
case NotImplemented:
msg = i18n("Not implemented"); break;
case ParsingError:
msg = i18n("Parsing error"); break;
case InvalidFormat:
msg = i18n("Invalid file format"); break;
case InternalError:
case UsageError:
msg = i18n("Internal error"); break;
case ProgressCancelled:
msg = i18n("Cancelled by user"); break;
case BadConversionGraph:
msg = i18n("Unknown file type"); break;
case UnsupportedVersion:
msg = i18n("Unsupported file version"); break;
case UserCancelled:
// intentionally we do not prompt the error message here
break;
default: msg = i18n("Unknown error"); break;
}
return msg;
}
KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const
{
Q_UNUSED(from);
Q_UNUSED(to);
return 0;
}
KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const
{
KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to);
const QString filterConfig = KisConfig(true).exportConfigurationXML(to);
if (cfg && !filterConfig.isEmpty()) {
cfg->fromXML(filterConfig, false);
}
return cfg;
}
KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const
{
Q_UNUSED(from);
Q_UNUSED(to);
return 0;
}
QMap<QString, KisExportCheckBase *> KisImportExportFilter::exportChecks()
{
qDeleteAll(d->capabilities);
initializeCapabilities();
return d->capabilities;
}
+QString KisImportExportFilter::verify(const QString &fileName) const
+{
+ QFileInfo fi(fileName);
+
+ if (!fi.exists()) {
+ return i18n("%1 does not exit after writing. Try saving again under a different name, in another location.", fileName);
+ }
+
+ if (!fi.isReadable()) {
+ return i18n("%1 is not readable", fileName);
+ }
+
+ if (fi.size() < 10) {
+ return i18n("%1 is smaller than 10 bytes, it must be corrupt. Try saving again under a different name, in another location.", fileName);
+ }
+
+ QFile f(fileName);
+ f.open(QFile::ReadOnly);
+ QByteArray ba = f.read(std::min(f.size(), (qint64)1000));
+ bool found = false;
+ for(int i = 0; i < ba.size(); ++i) {
+ if (ba.at(i) > 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return i18n("%1 has only zero bytes in the first 1000 bytes, it's probably corrupt. Try saving again under a different name, in another location.", fileName);
+ }
+
+ return QString();
+}
+
void KisImportExportFilter::setUpdater(QPointer<KoUpdater> updater)
{
d->updater = updater;
}
void KisImportExportFilter::setProgress(int value)
{
if (d->updater) {
d->updater->setValue(value);
}
}
void KisImportExportFilter::initializeCapabilities()
{
// XXX: Initialize everything to fully supported?
}
void KisImportExportFilter::addCapability(KisExportCheckBase *capability)
{
d->capabilities[capability->id()] = capability;
}
void KisImportExportFilter::addSupportedColorModels(QList<QPair<KoID, KoID> > supportedColorModels, const QString &name, KisExportCheckBase::Level level)
{
Q_ASSERT(level != KisExportCheckBase::SUPPORTED);
QString layerMessage;
QString imageMessage;
QList<KoID> allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces);
Q_FOREACH(const KoID &colorModelID, allColorModels) {
QList<KoID> allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces);
Q_FOREACH(const KoID &colorDepthID, allColorDepths) {
KisExportCheckFactory *colorModelCheckFactory =
KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id());
KisExportCheckFactory *colorModelPerLayerCheckFactory =
KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id());
if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) {
qWarning() << "No factory for" << colorModelID << colorDepthID;
continue;
}
if (supportedColorModels.contains(QPair<KoID, KoID>(colorModelID, colorDepthID))) {
addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED));
addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED));
}
else {
if (level == KisExportCheckBase::PARTIALLY) {
imageMessage = i18nc("image conversion warning",
"%1 cannot save images with color model <b>%2</b> and depth <b>%3</b>. The image will be converted."
,name, colorModelID.name(), colorDepthID.name());
layerMessage =
i18nc("image conversion warning",
"%1 cannot save layers with color model <b>%2</b> and depth <b>%3</b>. The layers will be converted or skipped."
,name, colorModelID.name(), colorDepthID.name());
}
else {
imageMessage = i18nc("image conversion warning",
"%1 cannot save images with color model <b>%2</b> and depth <b>%3</b>. The image will not be saved."
,name, colorModelID.name(), colorDepthID.name());
layerMessage =
i18nc("image conversion warning",
"%1 cannot save layers with color model <b>%2</b> and depth <b>%3</b>. The layers will be skipped."
, name, colorModelID.name(), colorDepthID.name());
}
addCapability(colorModelCheckFactory->create(level, imageMessage));
addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage));
}
}
}
}
+
+QString KisImportExportFilter::verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const
+{
+ QScopedPointer<KoStore> store(KoStore::createStore(fileName, KoStore::Read, KIS_MIME_TYPE, KoStore::Zip));
+
+ if (!store || store->bad()) {
+ return i18n("Could not open the saved file %1. Please try to save again in a different location.", fileName);
+ }
+
+ Q_FOREACH(const QString &file, filesToCheck) {
+ if (!store->hasFile(file)) {
+ return i18n("File %1 is missing in %2 and is broken. Please try to save again in a different location.", file, fileName);
+ }
+ }
+
+ return QString();
+
+}
diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h
index 8fa8b4d961..190b803617 100644
--- a/libs/ui/KisImportExportFilter.h
+++ b/libs/ui/KisImportExportFilter.h
@@ -1,183 +1,190 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
2002 Werner Trobin <trobin@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License 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_IMPORT_EXPORT_FILTER_H
#define KIS_IMPORT_EXPORT_FILTER_H
+#include "KisImageBuilderResult.h"
+
#include <QObject>
#include <QIODevice>
#include <QMap>
#include <QPointer>
#include <QString>
#include <QPair>
#include <QList>
#include <KoID.h>
#include <QSharedPointer>
#include <kis_properties_configuration.h>
#include <kis_types.h>
#include <KisExportCheckBase.h>
class KoUpdater;
class KisDocument;
class KisConfigWidget;
#include "kritaui_export.h"
-
+#include "KisImportExportErrorCode.h"
/**
* @brief The base class for import and export filters.
*
* Derive your filter class from this base class and implement
* the @ref convert() method. Don't forget to specify the Q_OBJECT
* macro in your class even if you don't use signals or slots.
* This is needed as filters are created on the fly.
*
* @note Take care: The m_chain pointer is invalid while the constructor
* runs due to the implementation -- @em don't use it in the constructor.
* After the constructor, when running the @ref convert() method it's
* guaranteed to be valid, so no need to check against 0.
*
* @note If the code is compiled in debug mode, setting CALLIGRA_DEBUG_FILTERS
* environment variable to any value disables deletion of temporary files while
* importing/exporting. This is useful for testing purposes.
*
* @author Werner Trobin <trobin@kde.org>
* @todo the class has no constructor and therefore cannot initialize its private class
*/
class KRITAUI_EXPORT KisImportExportFilter : public QObject
{
Q_OBJECT
public:
static const QString ImageContainsTransparencyTag;
static const QString ColorModelIDTag;
static const QString ColorDepthIDTag;
static const QString sRGBTag;
public:
/**
* This enum is used to signal the return state of your filter.
* Return OK in @ref convert() in case everything worked as expected.
* Feel free to add some more error conditions @em before the last item
* if it's needed.
*/
enum ConversionStatus { OK,
UsageError,
CreationError,
FileNotFound,
StorageCreationError,
BadMimeType,
BadConversionGraph,
WrongFormat,
NotImplemented,
ParsingError,
InternalError,
UserCancelled,
InvalidFormat,
FilterCreationError,
ProgressCancelled,
UnsupportedVersion,
JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255
};
~KisImportExportFilter() override;
void setBatchMode(bool batchmode);
void setFilename(const QString &filename);
void setRealFilename(const QString &filename);
void setMimeType(const QString &mime);
void setUpdater(QPointer<KoUpdater> updater);
/**
* The filter chain calls this method to perform the actual conversion.
* The passed mimetypes should be a pair of those you specified in your
* .desktop file.
* You @em have to implement this method to make the filter work.
*
* @return The error status, see the @ref #ConversionStatus enum.
* KisImportExportFilter::OK means that everything is alright.
*/
- virtual ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0;
+ virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0;
/**
* Get the text version of the status value
*/
static QString conversionStatusString(ConversionStatus status);
/**
* @brief defaultConfiguration defines the default settings for the given import export filter
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
* @return a serializable KisPropertiesConfiguration object
*/
virtual KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
/**
* @brief lastSavedConfiguration return the last saved configuration for this filter
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
* @return a serializable KisPropertiesConfiguration object
*/
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
/**
* @brief createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter
* @param parent the owner of the widget; the caller is responsible for deleting
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
*
* @return the widget
*/
virtual KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
/**
* @brief generate and return the list of capabilities of this export filter. The list
* @return returns the list of capabilities of this export filter
*/
virtual QMap<QString, KisExportCheckBase*> exportChecks();
/// Override and return false for the filters that use a library that cannot handle file handles, only file names.
virtual bool supportsIO() const { return true; }
+ /// Verify whether the given file is correct and readable
+ virtual QString verify(const QString &fileName) const;
+
protected:
/**
* This is the constructor your filter has to call, obviously.
*/
KisImportExportFilter(QObject *parent = 0);
QString filename() const;
QString realFilename() const;
bool batchMode() const;
QByteArray mimeType() const;
void setProgress(int value);
virtual void initializeCapabilities();
void addCapability(KisExportCheckBase *capability);
void addSupportedColorModels(QList<QPair<KoID, KoID> > supportedColorModels, const QString &name, KisExportCheckBase::Level level = KisExportCheckBase::PARTIALLY);
+ QString verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const;
+
private:
KisImportExportFilter(const KisImportExportFilter& rhs);
KisImportExportFilter& operator=(const KisImportExportFilter& rhs);
class Private;
Private *const d;
};
#endif
diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index e5ac96de10..eb552c1d4f 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,699 +1,702 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KisImportExportManager.h"
#include <QFile>
#include <QLabel>
#include <QVBoxLayout>
#include <QList>
#include <QApplication>
#include <QByteArray>
#include <QPluginLoader>
#include <QFileInfo>
#include <QMessageBox>
#include <QJsonObject>
#include <QTextBrowser>
#include <QCheckBox>
#include <QSaveFile>
#include <QGroupBox>
#include <QFuture>
#include <QtConcurrent>
#include <klocalizedstring.h>
#include <ksqueezedtextlabel.h>
#include <kpluginfactory.h>
#include <KisUsageLogger.h>
#include <KoFileDialog.h>
#include <kis_icon_utils.h>
#include <KoDialog.h>
#include <KoProgressUpdater.h>
#include <KoJsonTrader.h>
#include <KisMimeDatabase.h>
#include <kis_config_widget.h>
#include <kis_debug.h>
#include <KisPreExportChecker.h>
#include <KisPart.h>
#include "kis_config.h"
#include "KisImportExportFilter.h"
#include "KisDocument.h"
#include <kis_image.h>
#include <kis_paint_layer.h>
#include "kis_painter.h"
#include "kis_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include <kis_iterator_ng.h>
#include "kis_async_action_feedback.h"
#include "KisReferenceImagesLayer.h"
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
KoUpdaterPtr updater;
QString cachedExportFilterMimeType;
QSharedPointer<KisImportExportFilter> cachedExportFilter;
};
struct KisImportExportManager::ConversionResult {
ConversionResult()
{
}
- ConversionResult(const QFuture<KisImportExportFilter::ConversionStatus> &futureStatus)
+ ConversionResult(const QFuture<KisImportExportErrorCode> &futureStatus)
: m_isAsync(true),
m_futureStatus(futureStatus)
{
}
- ConversionResult(KisImportExportFilter::ConversionStatus status)
+ ConversionResult(KisImportExportErrorCode status)
: m_isAsync(false),
m_status(status)
{
}
bool isAsync() const {
return m_isAsync;
}
- QFuture<KisImportExportFilter::ConversionStatus> futureStatus() const {
+ QFuture<KisImportExportErrorCode> futureStatus() const {
// if the result is not async, then it means some failure happened,
// just return a cancelled future
- KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || m_status != KisImportExportFilter::OK);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk());
return m_futureStatus;
}
- KisImportExportFilter::ConversionStatus status() const {
+ KisImportExportErrorCode status() const {
return m_status;
}
- void setStatus(KisImportExportFilter::ConversionStatus value) {
+ void setStatus(KisImportExportErrorCode value) {
m_status = value;
}
private:
bool m_isAsync = false;
- QFuture<KisImportExportFilter::ConversionStatus> m_futureStatus;
- KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError;
+ QFuture<KisImportExportErrorCode> m_futureStatus;
+ KisImportExportErrorCode m_status = ImportExportCodes::InternalError;
};
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
-KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
+KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
ConversionResult result = convert(Import, location, location, mimeType, false, 0, false);
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
return result.status();
}
-KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
+KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false);
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
return result.status();
}
-QFuture<KisImportExportFilter::ConversionStatus> KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
+QFuture<KisImportExportErrorCode> KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType,
+ KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() ||
- result.status() != KisImportExportFilter::OK, QFuture<KisImportExportFilter::ConversionStatus>());
+ !result.status().isOk(), QFuture<KisImportExportErrorCode>());
status = result.status();
return result.futureStatus();
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::supportedMimeTypes(Direction direction)
{
// Find the right mimetype by the extension
QSet<QString> mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
QList<QPluginLoader *>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
QList<QPluginLoader *>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
QList<QPluginLoader *>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
KLibFactory *factory = qobject_cast<KLibFactory *>(loader->instance());
if (!factory) {
warnUI << loader->errorString();
continue;
}
QObject* obj = factory->create<KisImportExportFilter>(0);
if (!obj || !obj->inherits("KisImportExportFilter")) {
delete obj;
continue;
}
KisImportExportFilter *f = qobject_cast<KisImportExportFilter*>(obj);
if (!f) {
delete obj;
continue;
}
int w = json.value("X-KDE-Weight").toInt();
if (w > weight) {
delete filter;
filter = f;
f->setObjectName(loader->fileName());
weight = w;
}
}
}
qDeleteAll(list);
if (filter) {
filter->setMimeType(mimetype);
}
return filter;
}
bool KisImportExportManager::batchMode(void) const
{
return m_document->fileBatchMode();
}
void KisImportExportManager::setUpdater(KoUpdaterPtr updater)
{
d->updater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@titile:window", "Open Audio"));
return dialog.filename();
}
KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync)
{
// export configuration is supported for export only
KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration));
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true);
}
QSharedPointer<KisImportExportFilter> filter;
/**
* Fetching a filter from the registry is a really expensive operation,
* because it blocks all the threads. Cache the filter if possible.
*/
if (direction == KisImportExportManager::Export &&
d->cachedExportFilter &&
d->cachedExportFilterMimeType == typeName) {
filter = d->cachedExportFilter;
} else {
filter = toQShared(filterForMimeType(typeName, direction));
if (direction == Export) {
d->cachedExportFilter = filter;
d->cachedExportFilterMimeType = typeName;
}
}
if (!filter) {
- return KisImportExportFilter::FilterCreationError;
+ return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect);
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (!d->updater.isNull()) {
// WARNING: The updater is not guaranteed to be persistent! If you ever want
// to add progress reporting to "Save also as .kra", make sure you create
// a separate KoProgressUpdater for that!
// WARNING2: the failsafe completion of the updater happens in the destructor
// the filter.
filter->setUpdater(d->updater);
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = typeName.toLatin1();
}
else {
from = typeName.toLatin1();
to = m_document->nativeFormatMimeType();
}
KIS_ASSERT_RECOVER_RETURN_VALUE(
direction == Import || direction == Export,
- KisImportExportFilter::BadConversionGraph);
+ KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph"
- ConversionResult result = KisImportExportFilter::OK;
+ ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK);
if (direction == Import) {
KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5")
.arg(QString::fromLatin1(from))
.arg(QString::fromLatin1(to))
.arg(location)
.arg(realLocation)
.arg(batchMode()));
// async importing is not yet supported!
KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync);
if (0 && !batchMode()) {
KisAsyncActionFeedback f(i18n("Opening document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
} else {
result = doImport(location, filter);
}
}
else /* if (direction == Export) */ {
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
}
if (exportConfiguration) {
fillStaticExportConfigurationProperties(exportConfiguration);
}
bool alsoAsKra = false;
bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration,
from, to,
batchMode(), showWarnings,
&alsoAsKra);
if (!batchMode() && !askUser) {
- return KisImportExportFilter::UserCancelled;
+ return KisImportExportErrorCode(ImportExportCodes::Cancelled);
}
KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6")
.arg(QString::fromLatin1(from))
.arg(QString::fromLatin1(to))
.arg(location)
.arg(realLocation)
.arg(batchMode())
.arg(exportConfiguration ? exportConfiguration->toXML() : "none"));
if (isAsync) {
result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
// we should explicitly report that the exporting has been initiated
- result.setStatus(KisImportExportFilter::OK);
+ result.setStatus(ImportExportCodes::OK);
} else if (!batchMode()) {
KisAsyncActionFeedback f(i18n("Saving document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
} else {
result = doExport(location, filter, exportConfiguration, alsoAsKra);
}
if (exportConfiguration && !batchMode() && showWarnings) {
KisConfig(false).setExportConfiguration(typeName, exportConfiguration);
}
}
return result;
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image)
{
KisPaintDeviceSP dev = image->projection();
const KoColorSpace* cs = dev->colorSpace();
const bool isThereAlpha =
KisPainter::checkDeviceHasTransparency(image->projection());
exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha);
exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id());
exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id());
const bool sRGB =
(cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) &&
!cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB);
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration)
{
return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image());
}
bool KisImportExportManager::askUserAboutExportConfiguration(
QSharedPointer<KisImportExportFilter> filter,
KisPropertiesConfigurationSP exportConfiguration,
const QByteArray &from,
const QByteArray &to,
const bool batchMode,
const bool showWarnings,
bool *alsoAsKra)
{
// prevents the animation renderer from running this code
const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to);
QStringList warnings;
QStringList errors;
{
KisPreExportChecker checker;
checker.check(m_document->image(), filter->exportChecks());
warnings = checker.warnings();
errors = checker.errors();
}
KisConfigWidget *wdg = 0;
if (QThread::currentThread() == qApp->thread()) {
wdg = filter->createConfigurationWidget(0, from, to);
+
+ KisMainWindow *kisMain = KisPart::instance()->currentMainwindow();
+ if (wdg && kisMain) {
+ KisViewManager *manager = kisMain->viewManager();
+ wdg->setView(manager);
+ }
}
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains <b>assistants</b>. The assistants will not be saved."));
}
if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains <b>reference images</b>. The reference images will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains <b>guides</b>. The guides will not be saved."));
}
if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains a <b>custom grid configuration</b>. The configuration will not be saved."));
}
if (!batchMode && !errors.isEmpty()) {
QString error = "<html><body><p><b>"
+ i18n("Error: cannot save this image as a %1.", mimeUserDescription)
+ "</b> Reasons:</p>"
+ "<p/><ul>";
Q_FOREACH(const QString &w, errors) {
error += "\n<li>" + w + "</li>";
}
error += "</ul>";
QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
return false;
}
if (!batchMode && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(mimeUserDescription);
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (showWarnings && !warnings.isEmpty()) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hLayout->addWidget(labelWarning);
KisPopupButton *bn = new KisPopupButton(0);
bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription));
hLayout->addWidget(bn);
layout->addLayout(hLayout);
QTextBrowser *browser = new QTextBrowser();
browser->setMinimumWidth(bn->width());
bn->setPopupWidget(browser);
QString warning = "<html><body><p><b>"
+ i18n("You will lose information when saving this image as a %1.", mimeUserDescription);
if (warnings.size() == 1) {
warning += "</b> Reason:</p>";
}
else {
warning += "</b> Reasons:</p>";
}
warning += "<p/><ul>";
Q_FOREACH(const QString &w, warnings) {
warning += "\n<li>" + w + "</li>";
}
warning += "</ul>";
browser->setHtml(warning);
}
if (wdg) {
QGroupBox *box = new QGroupBox(i18n("Options"));
QVBoxLayout *boxLayout = new QVBoxLayout(box);
wdg->setConfiguration(exportConfiguration);
boxLayout->addWidget(wdg);
layout->addWidget(box);
}
QCheckBox *chkAlsoAsKra = 0;
if (showWarnings && !warnings.isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig(true).readEntry<bool>("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return false;
}
}
*alsoAsKra = false;
if (chkAlsoAsKra) {
KisConfig(false).writeEntry<bool>("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
*alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
*exportConfiguration = *wdg->configuration();
}
}
return true;
}
-KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer<KisImportExportFilter> filter)
+KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer<KisImportExportFilter> filter)
{
QFile file(location);
if (!file.exists()) {
- return KisImportExportFilter::FileNotFound;
+ return ImportExportCodes::FileNotExist;
}
if (filter->supportsIO() && !file.open(QFile::ReadOnly)) {
- return KisImportExportFilter::FileNotFound;
+ return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error()));
}
- KisImportExportFilter::ConversionStatus status =
- filter->convert(m_document, &file, KisPropertiesConfigurationSP());
+ KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP());
if (file.isOpen()) {
file.close();
}
return status;
}
-KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra)
+KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra)
{
- KisImportExportFilter::ConversionStatus status =
+ KisImportExportErrorCode status =
doExportImpl(location, filter, exportConfiguration);
- if (alsoAsKra && status == KisImportExportFilter::OK) {
+ if (alsoAsKra && status.isOk()) {
QString kraLocation = location + ".kra";
QByteArray mime = m_document->nativeFormatMimeType();
QSharedPointer<KisImportExportFilter> filter(
filterForMimeType(QString::fromLatin1(mime), Export));
KIS_SAFE_ASSERT_RECOVER_NOOP(filter);
if (filter) {
filter->setFilename(kraLocation);
KisPropertiesConfigurationSP kraExportConfiguration =
filter->lastSavedConfiguration(mime, mime);
status = doExportImpl(kraLocation, filter, kraExportConfiguration);
} else {
- status = KisImportExportFilter::FilterCreationError;
+ status = ImportExportCodes::FileFormatIncorrect;
}
}
return status;
}
// Temporary workaround until QTBUG-57299 is fixed.
#ifndef Q_OS_WIN
#define USE_QSAVEFILE
#endif
-KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration)
+KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration)
{
#ifdef USE_QSAVEFILE
QSaveFile file(location);
file.setDirectWriteFallback(true);
if (filter->supportsIO() && !file.open(QFile::WriteOnly)) {
#else
QFileInfo fi(location);
QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra");
if (filter->supportsIO() && !file.open()) {
#endif
- QString error = file.errorString();
- if (error.isEmpty()) {
- error = i18n("Could not open %1 for writing.", location);
- }
- m_document->setErrorMessage(error);
+ KisImportExportErrorCannotWrite result(file.error());
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
- return KisImportExportFilter::CreationError;
+ return result;
}
- KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, exportConfiguration);
+ KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration);
if (filter->supportsIO()) {
- if (status != KisImportExportFilter::OK) {
+ if (!status.isOk()) {
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
} else {
#ifdef USE_QSAVEFILE
if (!file.commit()) {
qWarning() << "Could not commit QSaveFile";
- QString error = file.errorString();
- if (error.isEmpty()) {
- error = i18n("Could not write to %1.", location);
- }
- if (m_document->errorMessage().isEmpty()) {
- m_document->setErrorMessage(error);
- }
- status = KisImportExportFilter::CreationError;
+ status = KisImportExportErrorCannotWrite(file.error());
}
#else
file.flush();
file.close();
QFile target(location);
if (target.exists()) {
// There should already be a .kra~ backup
target.remove();
}
if (!file.copy(location)) {
file.setAutoRemove(false);
- m_document->setErrorMessage(i18n("Could not copy %1 to its final location %2", file.fileName(), location));
- return KisImportExportFilter::CreationError;
+ return KisImportExportErrorCannotWrite(file.error());
}
#endif
}
}
+
+ // Do some minimal verification
+ QString verificationResult = filter->verify(location);
+ if (!verificationResult.isEmpty()) {
+ status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting);
+ m_document->setErrorMessage(verificationResult);
+ }
+
+
return status;
}
#include <KisMimeDatabase.h>
diff --git a/libs/ui/KisImportExportManager.h b/libs/ui/KisImportExportManager.h
index 9e8785608b..4a6a358736 100644
--- a/libs/ui/KisImportExportManager.h
+++ b/libs/ui/KisImportExportManager.h
@@ -1,158 +1,158 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* 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_IMPORT_EXPORT_MANAGER_H
#define KIS_IMPORT_EXPORT_MANAGER_H
#include <QObject>
#include <QMap>
#include <QByteArray>
#include <QUrl>
#include "KisImportExportFilter.h"
#include "kritaui_export.h"
class KisDocument;
class KoProgressUpdater;
template <class T>
class QFuture;
/**
* @brief The class managing all the filters.
*
* This class manages all filters for a %Calligra application. Normally
* you will not have to use it, since KisMainWindow takes care of loading
* and saving documents.
*
* @ref KisFilter
*
* @author Kalle Dalheimer <kalle@kde.org>
* @author Torben Weis <weis@kde.org>
* @author Werner Trobin <trobin@kde.org>
*/
class KRITAUI_EXPORT KisImportExportManager : public QObject
{
Q_OBJECT
public:
/**
* This enum is used to distinguish the import/export cases
*/
enum Direction { Import = 1, Export = 2 };
/**
* Create a filter manager for a document
*/
explicit KisImportExportManager(KisDocument *document);
public:
~KisImportExportManager() override;
/**
* Imports the specified document and returns the resultant filename
* (most likely some file in /tmp).
* @p path can be either a URL or a filename.
* @p documentMimeType gives importDocument a hint about what type
* the document may be. It can be left empty.
*
* @return status signals the success/error of the conversion.
* If the QString which is returned isEmpty() and the status is OK,
* then we imported the file directly into the document.
*/
- KisImportExportFilter::ConversionStatus importDocument(const QString &location, const QString &mimeType);
+ KisImportExportErrorCode importDocument(const QString &location, const QString &mimeType);
/**
* @brief Exports the given file/document to the specified URL/mimetype.
*
* If @p mimeType is empty, then the closest matching Calligra part is searched
* and when the method returns @p mimeType contains this mimetype.
* Oh, well, export is a C++ keyword ;)
*/
- KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0);
+ KisImportExportErrorCode exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0);
- QFuture<KisImportExportFilter::ConversionStatus> exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0);
+ QFuture<KisImportExportErrorCode> exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, KisImportExportErrorCode &status, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0);
///@name Static API
//@{
/**
* Suitable for passing to KoFileDialog::setMimeTypeFilters. The default mime
* gets set by the "users" of this method, as we do not have enough
* information here.
* Optionally, @p extraNativeMimeTypes are added after the native mimetype.
*/
static QStringList supportedMimeTypes(Direction direction);
/**
* @brief filterForMimeType loads the relevant import/export plugin and returns it. The caller
* is responsible for deleting it!
* @param mimetype the mimetype we want to import/export. If there's more than one plugin, the one
* with the highest weight as defined in the json description will be taken
* @param direction import or export
* @return a pointer to the filter plugin or 0 if none could be found
*/
static KisImportExportFilter *filterForMimeType(const QString &mimetype, Direction direction);
/**
* Fill necessary information for the export filter into the properties, e.g. if the image has
* transparency or has sRGB profile.
*/
static void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image);
/**
* Get if the filter manager is batch mode (true)
* or in interactive mode (true)
*/
bool batchMode(void) const;
void setUpdater(KoUpdaterPtr updater);
static QString askForAudioFileName(const QString &defaultDir, QWidget *parent);
private:
struct ConversionResult;
ConversionResult convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync);
void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration);
bool askUserAboutExportConfiguration(QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, bool batchMode, const bool showWarnings, bool *alsoAsKra);
- KisImportExportFilter::ConversionStatus doImport(const QString &location, QSharedPointer<KisImportExportFilter> filter);
+ KisImportExportErrorCode doImport(const QString &location, QSharedPointer<KisImportExportFilter> filter);
- KisImportExportFilter::ConversionStatus doExport(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra);
- KisImportExportFilter::ConversionStatus doExportImpl(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration);
+ KisImportExportErrorCode doExport(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra);
+ KisImportExportErrorCode doExportImpl(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration);
// Private API
KisImportExportManager(const KisImportExportManager& rhs);
KisImportExportManager &operator=(const KisImportExportManager& rhs);
KisDocument *m_document;
/// A static cache for the availability checks of filters
static QStringList m_importMimeTypes;
static QStringList m_exportMimeTypes;
class Private;
Private * const d;
};
#endif // __KO_FILTER_MANAGER_H__
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index 7819bcbaa0..8aaa5b5d74 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2651 +1,2652 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2006 David Faure <faure@kde.org>
Copyright (C) 2007, 2009 Thomas zander <zander@kde.org>
Copyright (C) 2010 Benjamin Port <port.benjamin@gmail.com>
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 <KoConfig.h>
// qt includes
#include <QApplication>
#include <QByteArray>
#include <QCloseEvent>
#include <QStandardPaths>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialog>
#include <QDockWidget>
#include <QIcon>
#include <QInputDialog>
#include <QLabel>
#include <QLayout>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QMutex>
#include <QMutexLocker>
#include <QPointer>
#include <QPrintDialog>
#include <QPrinter>
#include <QPrintPreviewDialog>
#include <QToolButton>
#include <QSignalMapper>
#include <QTabBar>
#include <QMoveEvent>
#include <QUrl>
#include <QMessageBox>
#include <QStatusBar>
#include <QMenu>
#include <QMenuBar>
#include <KisMimeDatabase.h>
#include <QMimeData>
#include <QStackedWidget>
#include <QProxyStyle>
#include <QScreen>
#include <QAction>
#include <QWindow>
#include <kactioncollection.h>
#include <kactionmenu.h>
#include <kis_debug.h>
#include <kedittoolbar.h>
#include <khelpmenu.h>
#include <klocalizedstring.h>
#include <kaboutdata.h>
#include <kis_workspace_resource.h>
#include <input/kis_input_manager.h>
#include "kis_selection_manager.h"
#include "kis_icon_utils.h"
#include <krecentfilesaction.h>
#include <ktoggleaction.h>
#include <ktoolbar.h>
#include <kmainwindow.h>
#include <kxmlguiwindow.h>
#include <kxmlguifactory.h>
#include <kxmlguiclient.h>
#include <kguiitem.h>
#include <kwindowconfig.h>
#include <kformat.h>
#include <KoResourcePaths.h>
#include <KoToolFactoryBase.h>
#include <KoToolRegistry.h>
#include "KoDockFactoryBase.h"
#include "KoDocumentInfoDlg.h"
#include "KoDocumentInfo.h"
#include "KoFileDialog.h"
#include <kis_icon.h>
#include <KoPageLayoutDialog.h>
#include <KoPageLayoutWidget.h>
#include <KoToolManager.h>
#include <KoZoomController.h>
#include "KoToolDocker.h"
#include "KoToolBoxDocker_p.h"
#include <KoToolBoxFactory.h>
#include <KoDockRegistry.h>
#include <KoPluginLoader.h>
#include <KoColorSpaceEngine.h>
#include <KoUpdater.h>
#include <KoResourceModel.h>
#include <brushengine/kis_paintop_settings.h>
#include "dialogs/kis_about_application.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include "dialogs/kis_dlg_preferences.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "KisApplication.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_custom_image_widget.h"
#include <KisDocument.h>
#include "kis_group_layer.h"
#include "kis_image_from_clipboard_widget.h"
#include "kis_image.h"
#include <KisImportExportFilter.h>
#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 <KisImageConfigNotifier.h>
#include "KisWindowLayoutManager.h"
#include <KisUndoActionsUpdateManager.h>
#include "KisWelcomePageWidget.h"
#include <KritaVersionWrapper.h>
#include <kritaversion.h>
#include <mutex>
class ToolDockerFactory : public KoDockFactoryBase
{
public:
ToolDockerFactory() : KoDockFactoryBase() { }
QString id() const override {
return "sharedtooldocker";
}
QDockWidget* createDockWidget() override {
KoToolDocker* dockWidget = new KoToolDocker();
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
class Q_DECL_HIDDEN KisMainWindow::Private
{
public:
Private(KisMainWindow *parent, QUuid id)
: q(parent)
, id(id)
, dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent))
, windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent))
, documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent))
, workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent))
, welcomePage(new KisWelcomePageWidget(parent))
, widgetStack(new QStackedWidget(parent))
, mdiArea(new QMdiArea(parent))
, windowMapper(new QSignalMapper(parent))
, documentMapper(new QSignalMapper(parent))
{
if (id.isNull()) this->id = QUuid::createUuid();
widgetStack->addWidget(welcomePage);
widgetStack->addWidget(mdiArea);
mdiArea->setTabsMovable(true);
mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
}
~Private() {
qDeleteAll(toolbarList);
}
KisMainWindow *q {0};
QUuid id;
KisViewManager *viewManager {0};
QPointer<KisView> activeView;
QList<QAction *> toolbarList;
bool firstTime {true};
bool windowSizeDirty {false};
bool readOnly {false};
KisAction *showDocumentInfo {0};
KisAction *saveAction {0};
KisAction *saveActionAs {0};
// KisAction *printAction;
// KisAction *printActionPreview;
// KisAction *exportPdf {0};
KisAction *importAnimation {0};
KisAction *closeAll {0};
// KisAction *reloadFile;
KisAction *importFile {0};
KisAction *exportFile {0};
KisAction *undo {0};
KisAction *redo {0};
KisAction *newWindow {0};
KisAction *close {0};
KisAction *mdiCascade {0};
KisAction *mdiTile {0};
KisAction *mdiNextWindow {0};
KisAction *mdiPreviousWindow {0};
KisAction *toggleDockers {0};
KisAction *toggleDockerTitleBars {0};
KisAction *fullScreenMode {0};
KisAction *showSessionManager {0};
KisAction *expandingSpacers[2];
KActionMenu *dockWidgetMenu;
KActionMenu *windowMenu;
KActionMenu *documentMenu;
KActionMenu *workspaceMenu;
KHelpMenu *helpMenu {0};
KRecentFilesAction *recentFiles {0};
KoResourceModel *workspacemodel {0};
QScopedPointer<KisUndoActionsUpdateManager> undoActionsUpdateManager;
QString lastExportLocation;
QMap<QString, QDockWidget *> dockWidgetsMap;
QByteArray dockerStateBeforeHiding;
KoToolDocker *toolOptionsDocker {0};
QCloseEvent *deferredClosingEvent {0};
Digikam::ThemeManager *themeManager {0};
KisWelcomePageWidget *welcomePage {0};
QStackedWidget *widgetStack {0};
QMdiArea *mdiArea;
QMdiSubWindow *activeSubWindow {0};
QSignalMapper *windowMapper;
QSignalMapper *documentMapper;
QByteArray lastExportedFormat;
QScopedPointer<KisSignalCompressorWithParam<int> > tabSwitchCompressor;
QMutex savingEntryMutex;
KConfigGroup windowStateConfig;
QUuid workspaceBorrowedBy;
KisSignalAutoConnectionsStore screenConnectionsStore;
KisActionManager * actionManager() {
return viewManager->actionManager();
}
QTabBar* findTabBarHACK() {
QObjectList objects = mdiArea->children();
Q_FOREACH (QObject *object, objects) {
QTabBar *bar = qobject_cast<QTabBar*>(object);
if (bar) {
return bar;
}
}
return 0;
}
};
KisMainWindow::KisMainWindow(QUuid uuid)
: KXmlGuiWindow()
, d(new Private(this, uuid))
{
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<KisWorkspaceResource>(rserver));
d->workspacemodel = new KoResourceModel(adapter, this);
connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); });
d->viewManager = new KisViewManager(this, actionCollection());
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow");
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<KoToolDocker*>(createDockWidget(&toolDockerFactory));
d->toolOptionsDocker->toggleViewAction()->setEnabled(true);
}
QMap<QString, QAction*> 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<QPointer<QWidget> >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList<QPointer<QWidget> >)));
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<KisMainwindowObserver*>(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*)));
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<QKeySequence> existingShortcuts;
Q_FOREACH (QAction* action, actionCollection()->actions()) {
if(action->shortcut() == QKeySequence(0)) {
continue;
}
dbgKrita << "shortcut " << action->text() << " " << action->shortcut();
Q_ASSERT(!existingShortcuts.contains(action->shortcut()));
existingShortcuts.insert(action->shortcut());
}
#endif
configChanged();
// If we have customized the toolbars, load that first
setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui"));
setXMLFile(":/kxmlgui5/krita4.xmlgui");
guiFactory()->addClient(this);
// Create and plug toolbar list for Settings menu
QList<QAction *> toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast<KToolBar *>(it);
toolBar->setMovable(KisConfig(true).readEntry<bool>("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<bool>("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<void (int)> callback(
std::bind(&KisMainWindow::switchTab, this, _1));
d->tabSwitchCompressor.reset(
new KisSignalCompressorWithParam<int>(500, callback, KisSignalCompressor::FIRST_INACTIVE));
}
if (cfg.readEntry("CanvasOnlyActive", false)) {
QString currentWorkspace = cfg.readEntry<QString>("CurrentWorkspace", "Default");
KoResourceServer<KisWorkspaceResource> * rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace);
if (workspace) {
restoreWorkspace(workspace);
}
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<QAction*>(ac);
// if (action) {
// qDebug() << "<Action"
// << "\n\tname=" << action->objectName()
// << "\n\ticon=" << action->icon().name()
// << "\n\ttext=" << action->text().replace("&", "&amp;")
// << "\n\twhatsThis=" << action->whatsThis()
// << "\n\ttoolTip=" << action->toolTip().replace("<html>", "").replace("</html>", "")
// << "\n\ticonText=" << action->iconText().replace("&", "&amp;")
// << "\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)
{
if (d->activeView == view) return;
if (d->activeView) {
d->activeView->disconnect(this);
}
// register the newly created view in the input manager
viewManager()->inputManager()->addTrackedCanvas(view->canvasBase());
showView(view);
updateCaption();
emit restoringDone();
if (d->activeView) {
connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified()));
connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption()));
}
}
void KisMainWindow::notifyChildViewDestroyed(KisView *view)
{
/**
* If we are the last view of the window, Qt will not activate another tab
* before destroying tab/window. In ths case we should clear oll 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)
{
if (imageView && activeView() != imageView) {
// XXX: find a better way to initialize this!
imageView->setViewManager(d->viewManager);
imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
imageView->slotLoadingFinished();
QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView);
imageView->setSubWindow(subwin);
subwin->setAttribute(Qt::WA_DeleteOnClose, true);
connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
KisConfig cfg(true);
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("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<KisView> koview, KisPart::instance()->views()) {
KisViewManager *view = qobject_cast<KisViewManager*>(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<KisNode*>(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; i<d->mdiArea->subWindowList().size(); i++) {
QMdiSubWindow *window = d->mdiArea->subWindowList().at(i);
if (window) {
window->setPalette(qApp->palette());
KisView *view = qobject_cast<KisView*>(window->widget());
if (view) {
view->slotThemeChanged(qApp->palette());
}
}
}
}
emit themeChanged();
}
void KisMainWindow::updateReloadFileAction(KisDocument *doc)
{
Q_UNUSED(doc);
// d->reloadFile->setEnabled(doc && !doc->url().isEmpty());
}
void KisMainWindow::setReadWrite(bool readwrite)
{
d->saveAction->setEnabled(readwrite);
d->importFile->setEnabled(readwrite);
d->readOnly = !readwrite;
updateCaption();
}
void KisMainWindow::addRecentURL(const QUrl &url)
{
// Add entry to recent documents list
// (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
if (!url.isEmpty()) {
bool ok = true;
if (url.isLocalFile()) {
QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp");
for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) {
if (path.contains(*it)) {
ok = false; // it's in the tmp resource
}
}
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<QUrl> KisMainWindow::recentFilesUrls()
{
return d->recentFiles->urls();
}
void KisMainWindow::clearRecentFiles()
{
d->recentFiles->clear();
}
void KisMainWindow::reloadRecentFileList()
{
d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles"));
}
void KisMainWindow::updateCaption()
{
if (!d->mdiArea->activeSubWindow()) {
updateCaption(QString(), false);
}
else if (d->activeView && d->activeView->document() && d->activeView->image()){
KisDocument *doc = d->activeView->document();
QString caption(doc->caption());
if (d->readOnly) {
caption += " [" + i18n("Write Protected") + "] ";
}
if (doc->isRecovered()) {
caption += " [" + i18n("Recovered") + "] ";
}
// 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<KisView*>(subwindow->widget());
KIS_SAFE_ASSERT_RECOVER_NOOP(view);
if (view) {
if (view->document() == document) {
setActiveSubWindow(subwindow);
return;
}
}
}
addViewAndNotifyLoadingCompleted(document);
}
KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document)
{
showWelcomeScreen(false); // see workaround in function header
KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this);
addView(view);
emit guiLoadingFinished();
return view;
}
QStringList KisMainWindow::showOpenFileDialog(bool isImporting)
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import));
dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images"));
return dialog.filenames();
}
// Separate from openDocument to handle async loading (remote URLs)
void KisMainWindow::slotLoadCompleted()
{
KisDocument *newdoc = qobject_cast<KisDocument*>(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<KisDocument*>(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<KisDocument*>(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<QMutex> wrapper(&d->savingEntryMutex);
std::unique_lock<StdLockableWrapper<QMutex>> 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<QMutex> wrapper(&d->savingEntryMutex);
std::unique_lock<StdLockableWrapper<QMutex>> 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()).baseName();
if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first();
suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename);
suggestedURL.setPath(suggestedURL.path() + suggestedFilename);
}
// force the user to choose outputMimeType
saveas = true;
}
bool ret = false;
if (document->url().isEmpty() || isExporting || saveas) {
// if you're just File/Save As'ing to change filter options you
// don't want to be reminded about overwriting files etc.
bool justChangingFilterOptions = false;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs");
dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As"));
//qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType());
if (isExporting && !d->lastExportLocation.isEmpty()) {
// Use the location where we last exported to, if it's set, as the opening location for the file dialog
QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath();
// If the document doesn't have a filename yet, use the title
QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName();
// Use the last mimetype we exported to by default
QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat;
QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,");
// Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty
dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true);
dialog.setMimeTypeFilters(mimeFilter, proposedMimeType);
}
else {
// Get the last used location for saving
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString proposedPath = group.readEntry("SaveAs", "");
// if that is empty, get the last used location for loading
if (proposedPath.isEmpty()) {
proposedPath = group.readEntry("OpenDocument", "");
}
// If that is empty, too, use the Pictures location.
if (proposedPath.isEmpty()) {
proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
}
// But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise
// open the location where the document currently is.
dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true);
// If exporting, default to all supported file types if user is exporting
QByteArray default_mime_type = "";
if (!isExporting) {
// otherwise use the document's mimetype, or if that is empty, kra, which is the savest.
default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType();
}
dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type));
}
QUrl newURL = QUrl::fromUserInput(dialog.filename());
if (newURL.isLocalFile()) {
QString fn = newURL.toLocalFile();
if (QFileInfo(fn).completeSuffix().isEmpty()) {
fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first());
newURL = QUrl::fromLocalFile(fn);
}
}
if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) {
QString fn = newURL.toLocalFile();
QFileInfo info(fn);
document->documentInfo()->setAboutInfo("title", info.baseName());
}
QByteArray outputFormat = nativeFormat;
QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false);
outputFormat = outputFormatString.toLatin1();
if (!isExporting) {
justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType());
}
else {
QString path = QFileInfo(d->lastExportLocation).absolutePath();
QString filename = QFileInfo(document->url().toLocalFile()).baseName();
justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path)
&& (QFileInfo(newURL.toLocalFile()).baseName() == filename)
&& (outputFormat == d->lastExportedFormat);
}
bool bOk = true;
if (newURL.isEmpty()) {
bOk = false;
}
if (bOk) {
bool wantToSave = true;
// don't change this line unless you know what you're doing :)
if (!justChangingFilterOptions) {
if (!document->isNativeFormat(outputFormat))
wantToSave = true;
}
if (wantToSave) {
if (!isExporting) { // Save As
ret = document->saveAs(newURL, outputFormat, true);
if (ret) {
dbgUI << "Successful Save As!";
KisPart::instance()->addRecentURLToAllMainWindows(newURL);
setReadWrite(true);
} else {
dbgUI << "Failed Save As!";
+
}
}
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 (!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<QMdiSubWindow*> childrenList = d->mdiArea->subWindowList();
if (childrenList.isEmpty()) {
d->deferredClosingEvent = e;
saveWindowState(true);
} else {
e->setAccepted(false);
}
}
void KisMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
if (d->windowSizeDirty ) {
dbgUI << "KisMainWindow::saveWindowSettings";
KConfigGroup group = d->windowStateConfig;
KWindowConfig::saveWindowSize(windowHandle(), group);
config->sync();
d->windowSizeDirty = false;
}
if (!d->activeView || d->activeView->document()) {
// Save toolbar position into the config file of the app, under the doc's component name
KConfigGroup group = d->windowStateConfig;
saveMainWindowSettings(group);
// Save collapsible state of dock widgets
for (QMap<QString, QDockWidget*>::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";
-
- startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
+ 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, item.icon);
+ 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)
{
QStringList urls = showOpenFileDialog(isImporting);
if (urls.isEmpty())
return;
Q_FOREACH (const QString& url, urls) {
if (!url.isEmpty()) {
OpenFlags flags = isImporting ? Import : None;
bool res = openDocument(QUrl::fromLocalFile(url), flags);
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
}
}
void KisMainWindow::slotFileOpenRecent(const QUrl &url)
{
(void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None);
}
void KisMainWindow::slotFileSave()
{
if (saveDocument(d->activeView->document(), false, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotFileSaveAs()
{
if (saveDocument(d->activeView->document(), true, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotExportFile()
{
if (saveDocument(d->activeView->document(), true, true)) {
emit documentSaved();
}
}
void KisMainWindow::slotShowSessionManager() {
KisPart::instance()->showSessionManager();
}
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(KisWorkspaceResource *workspace)
{
bool success = restoreWorkspaceState(workspace->dockerState());
if (activeKisView()) {
activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace);
}
return success;
}
QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other)
{
QByteArray currentWorkspace = saveState();
if (!d->workspaceBorrowedBy.isNull()) {
if (other->id() == d->workspaceBorrowedBy) {
// We're swapping our original workspace back
d->workspaceBorrowedBy = QUuid();
return currentWorkspace;
} else {
// Get our original workspace back before swapping with a third window
KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy);
if (borrower) {
QByteArray originalLayout = borrower->borrowWorkspace(this);
borrower->restoreWorkspaceState(currentWorkspace);
d->workspaceBorrowedBy = other->id();
return originalLayout;
}
}
}
d->workspaceBorrowedBy = other->id();
return currentWorkspace;
}
void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b)
{
QByteArray workspaceA = a->borrowWorkspace(b);
QByteArray workspaceB = b->borrowWorkspace(a);
a->restoreWorkspaceState(workspaceB);
b->restoreWorkspaceState(workspaceA);
}
KisViewManager *KisMainWindow::viewManager() const
{
return d->viewManager;
}
void KisMainWindow::slotDocumentInfo()
{
if (!d->activeView->document())
return;
KoDocumentInfo *docInfo = d->activeView->document()->documentInfo();
if (!docInfo)
return;
KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo);
if (dlg->exec()) {
if (dlg->isDocumentSaved()) {
d->activeView->document()->setModified(false);
} else {
d->activeView->document()->setModified(true);
}
d->activeView->document()->setTitleModified();
}
delete dlg;
}
bool KisMainWindow::slotFileCloseAll()
{
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
if (subwin) {
if(!subwin->close())
return false;
}
}
updateCaption();
return true;
}
void KisMainWindow::slotFileQuit()
{
KisPart::instance()->closeSession();
}
void KisMainWindow::slotFilePrint()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
applyDefaultSettings(printJob->printer());
QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this );
if (printDialog && printDialog->exec() == QDialog::Accepted) {
printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point);
printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()),
activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())),
QPrinter::Inch);
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
}
else {
delete printJob;
}
delete printDialog;
}
void KisMainWindow::slotFilePrintPreview()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
/* Sets the startPrinting() slot to be blocking.
The Qt print-preview dialog requires the printing to be completely blocking
and only return when the full document has been printed.
By default the KisPrintingDialog is non-blocking and
multithreading, setting blocking to true will allow it to be used in the preview dialog */
printJob->setProperty("blocking", true);
QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this);
printJob->setParent(preview); // will take care of deleting the job
connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting()));
preview->exec();
delete preview;
}
KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName)
{
if (!activeView())
return 0;
if (!activeView()->document())
return 0;
KoPageLayout pageLayout;
pageLayout.width = 0;
pageLayout.height = 0;
pageLayout.topMargin = 0;
pageLayout.bottomMargin = 0;
pageLayout.leftMargin = 0;
pageLayout.rightMargin = 0;
if (pdfFileName.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString defaultDir = group.readEntry("SavePdfDialog");
if (defaultDir.isEmpty())
defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl startUrl = QUrl::fromLocalFile(defaultDir);
KisDocument* pDoc = d->activeView->document();
/** if document has a file name, take file name and replace extension with .pdf */
if (pDoc && pDoc->url().isValid()) {
startUrl = pDoc->url();
QString fileName = startUrl.toLocalFile();
fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" );
startUrl = startUrl.adjusted(QUrl::RemoveFilename);
startUrl.setPath(startUrl.path() + fileName );
}
QPointer<KoPageLayoutDialog> layoutDlg(new KoPageLayoutDialog(this, pageLayout));
layoutDlg->setWindowModality(Qt::WindowModal);
if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) {
delete layoutDlg;
return 0;
}
pageLayout = layoutDlg->pageLayout();
delete layoutDlg;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument");
dialog.setCaption(i18n("Export as PDF"));
dialog.setDefaultDir(startUrl.toLocalFile());
dialog.setMimeTypeFilters(QStringList() << "application/pdf");
QUrl url = QUrl::fromUserInput(dialog.filename());
pdfFileName = url.toLocalFile();
if (pdfFileName.isEmpty())
return 0;
}
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return 0;
if (isHidden()) {
printJob->setProperty("noprogressdialog", true);
}
applyDefaultSettings(printJob->printer());
// TODO for remote files we have to first save locally and then upload.
printJob->printer().setOutputFileName(pdfFileName);
printJob->printer().setDocName(pdfFileName);
printJob->printer().setColorMode(QPrinter::Color);
if (pageLayout.format == KoPageFormat::CustomSize) {
printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter);
} else {
printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format));
}
printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter);
switch (pageLayout.orientation) {
case KoPageFormat::Portrait:
printJob->printer().setOrientation(QPrinter::Portrait);
break;
case KoPageFormat::Landscape:
printJob->printer().setOrientation(QPrinter::Landscape);
break;
}
//before printing check if the printer can handle printing
if (!printJob->canPrint()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file"));
}
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
return printJob;
}
void KisMainWindow::importAnimation()
{
if (!activeView()) return;
KisDocument *document = activeView()->document();
if (!document) return;
KisDlgImportImageSequence dlg(this, document);
if (dlg.exec() == QDialog::Accepted) {
QStringList files = dlg.files();
int firstFrame = dlg.firstFrame();
int step = dlg.step();
KoUpdaterPtr updater =
!document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0;
KisAnimationImporter importer(document->image(), updater);
- KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
-
- if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) {
- QString msg = KisImportExportFilter::conversionStatusString(status);
+ 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
}
}
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<bool>("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<Qt::DockWidgetArea>(group.readEntry("DockArea", static_cast<int>(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<QDockWidget*> 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<KoCanvasObserverBase*> KisMainWindow::canvasObservers() const
{
QList<KoCanvasObserverBase*> observers;
Q_FOREACH (QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(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<QDockWidget*>(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<KisDocument> 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();
auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources();
auto m_this = this;
for (auto &w : workspaces) {
auto action = workspaceMenu->addAction(w->name());
connect(action, &QAction::triggered, this, [=]() {
m_this->restoreWorkspace(w);
});
}
workspaceMenu->addSeparator();
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")),
&QAction::triggered,
this,
[&]() {
QString extensions = d->workspacemodel->extensions();
QStringList mimeTypes;
for(const QString &suffix : extensions.split(":")) {
mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix);
}
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
QString filename = dialog.filename();
d->workspacemodel->importResourceFile(filename);
});
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")),
&QAction::triggered,
[=]() {
QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."),
i18nc("@label:textbox", "Name:"));
if (name.isEmpty()) return;
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = new KisWorkspaceResource("");
workspace->setDockerState(m_this->saveState());
d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace);
workspace->setValid(true);
QString saveLocation = rserver->saveLocation();
bool newName = false;
if(name.isEmpty()) {
newName = true;
name = i18n("Workspace");
}
QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension());
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension());
i++;
}
workspace->setFilename(fileInfo.filePath());
if(newName) {
name = i18n("Workspace %1", i);
}
workspace->setName(name);
rserver->addResource(workspace);
});
// TODO: What to do about delete?
// workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace..."));
menu->addSeparator();
menu->addAction(d->close);
menu->addAction(d->closeAll);
if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
menu->addSeparator();
menu->addAction(d->mdiTile);
menu->addAction(d->mdiCascade);
}
menu->addSeparator();
menu->addAction(d->mdiNextWindow);
menu->addAction(d->mdiPreviousWindow);
menu->addSeparator();
QList<QMdiSubWindow *> windows = d->mdiArea->subWindowList();
for (int i = 0; i < windows.size(); ++i) {
QPointer<KisView>child = qobject_cast<KisView*>(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<QDockWidget*>(widget);
if ( dw->objectName() == "ToolBox") {
dw->setEnabled(showMdiArea);
}
}
}
updateCaption();
}
void KisMainWindow::setActiveSubWindow(QWidget *window)
{
if (!window) return;
QMdiSubWindow *subwin = qobject_cast<QMdiSubWindow *>(window);
//dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow;
if (subwin && subwin != d->activeSubWindow) {
KisView *view = qobject_cast<KisView *>(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<int>("mdi_viewmode", (int)QMdiArea::TabbedView);
d->mdiArea->setViewMode(viewMode);
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
/**
* Dirty workaround for a bug in Qt (checked on Qt 5.6.1):
*
* If you make a window "Show on top" and then switch to the tabbed mode
* the window will contiue to be painted in its initial "mid-screen"
* position. It will persist here until you explicitly switch to its tab.
*/
if (viewMode == QMdiArea::TabbedView) {
Qt::WindowFlags oldFlags = subwin->windowFlags();
Qt::WindowFlags flags = oldFlags;
flags &= ~Qt::WindowStaysOnTopHint;
flags &= ~Qt::WindowStaysOnBottomHint;
if (flags != oldFlags) {
subwin->setWindowFlags(flags);
subwin->showMaximized();
}
}
}
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark"));
d->actionManager()->updateGUI();
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)
{
KisDocument *doc = qobject_cast<KisDocument*>(document);
KisView *view = addViewAndNotifyLoadingCompleted(doc);
d->actionManager()->updateGUI();
return view;
}
void KisMainWindow::newWindow()
{
KisMainWindow *mainWindow = KisPart::instance()->createMainWindow();
mainWindow->initializeGeometry();
mainWindow->show();
}
void KisMainWindow::closeCurrentWindow()
{
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->resources().isEmpty()) {
m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
}
void KisMainWindow::showErrorAndDie()
{
QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage);
if (m_dieOnError) {
exit(10);
}
}
void KisMainWindow::showAboutApplication()
{
KisAboutApplication dlg(this);
dlg.exec();
}
QPointer<KisView> KisMainWindow::activeKisView()
{
if (!d->mdiArea) return 0;
QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow();
//dbgKrita << "activeKisView" << activeSubWindow;
if (!activeSubWindow) return 0;
return qobject_cast<KisView*>(activeSubWindow->widget());
}
void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList<QPointer<QWidget> > &optionWidgetList)
{
KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController());
bool isOurOwnView = false;
Q_FOREACH (QPointer<KisView> 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.baseName();
}
if (title.isEmpty()) {
// #139905
title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(),
QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
}
printer.setDocName(title);
}
void KisMainWindow::createActions()
{
KisActionManager *actionManager = d->actionManager();
actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew()));
actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen()));
actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit()));
actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars()));
d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool)));
d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles()));
KSharedConfigPtr configPtr = KSharedConfig::openConfig();
d->recentFiles->loadEntries(configPtr->group("RecentFiles"));
d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave()));
d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs()));
d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint()));
// d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview()));
// d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo()));
d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo()));
d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo));
d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0);
// d->exportPdf = actionManager->createAction("file_export_pdf");
// connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf()));
d->importAnimation = actionManager->createAction("file_import_animation");
connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation()));
d->closeAll = actionManager->createAction("file_close_all");
connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll()));
// d->reloadFile = actionManager->createAction("file_reload_file");
// d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED);
// connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile()));
d->importFile = actionManager->createAction("file_import_file");
connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
d->exportFile = actionManager->createAction("file_export_file");
connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
/* The following entry opens the document information dialog. Since the action is named so it
intends to show data this entry should not have a trailing ellipses (...). */
d->showDocumentInfo = actionManager->createAction("file_documentinfo");
connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this));
d->themeManager->registerThemeActions(actionCollection());
connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()));
connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors()));
d->toggleDockers = actionManager->createAction("view_toggledockers");
KisConfig(true).showDockers(true);
d->toggleDockers->setChecked(true);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
actionCollection()->addAction("window", d->windowMenu);
d->mdiCascade = actionManager->createAction("windows_cascade");
connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows()));
d->mdiTile = actionManager->createAction("windows_tile");
connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows()));
d->mdiNextWindow = actionManager->createAction("windows_next");
connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow()));
d->mdiPreviousWindow = actionManager->createAction("windows_previous");
connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow()));
d->newWindow = actionManager->createAction("view_newwindow");
connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
d->close = actionManager->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 = QApplication::desktop()->availableGeometry(scnum);
// if the desktop is virtual then use virtual screen size
if (QApplication::desktop()->isVirtualDesktop()) {
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum));
}
quint32 x = desk.x();
quint32 y = desk.y();
quint32 w = 0;
quint32 h = 0;
// Default size -- maximize on small screens, something useful on big screens
const int deskWidth = desk.width();
if (deskWidth > 1024) {
// a nice width, and slightly less than total available
// height to 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()));
}
#include <moc_KisMainWindow.cpp>
diff --git a/libs/ui/KisMouseClickEater.cpp b/libs/ui/KisMouseClickEater.cpp
new file mode 100644
index 0000000000..eea726bd8c
--- /dev/null
+++ b/libs/ui/KisMouseClickEater.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2019 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * 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 "KisMouseClickEater.h"
+
+#include <QMouseEvent>
+#include "kis_debug.h"
+
+KisMouseClickEater::KisMouseClickEater(Qt::MouseButtons buttons,
+ int clicksToEat,
+ QObject *parent)
+ : QObject(parent),
+ m_buttons(buttons),
+ m_clicksToEat(clicksToEat)
+{
+ reset();
+}
+
+KisMouseClickEater::~KisMouseClickEater()
+{
+}
+
+void KisMouseClickEater::reset()
+{
+ m_clicksHappened = 0;
+ m_timeSinceReset.start();
+ m_blockTimedRelease = false;
+}
+
+bool KisMouseClickEater::eventFilter(QObject *watched, QEvent *event)
+{
+#ifdef Q_OS_WIN
+ const int tabletMouseEventsFlowDelay = 500;
+#else
+ const int tabletMouseEventsFlowDelay = 100;
+#endif
+
+ if (event->type() == QEvent::TabletMove) {
+ m_blockTimedRelease = true;
+ }
+
+ if (!m_blockTimedRelease &&
+ m_timeSinceReset.elapsed() > tabletMouseEventsFlowDelay) {
+
+ return QObject::eventFilter(watched, event);
+ }
+
+ if (event->type() == QEvent::MouseButtonPress ||
+ event->type() == QEvent::MouseButtonRelease) {
+
+ QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
+ if (mevent->button() & m_buttons) {
+ if (m_clicksHappened >= m_clicksToEat) {
+ return false;
+ }
+
+ if (event->type() == QEvent::MouseButtonRelease) {
+ m_clicksHappened++;
+ }
+
+ return true;
+ }
+ }
+
+ if (event->type() == QEvent::MouseMove) {
+ QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
+ if (mevent->buttons() & m_buttons) {
+ return m_clicksHappened < m_clicksToEat;
+ }
+ }
+
+ return QObject::eventFilter(watched, event);
+}
diff --git a/plugins/impex/psd/psd_loader.h b/libs/ui/KisMouseClickEater.h
similarity index 54%
copy from plugins/impex/psd/psd_loader.h
copy to libs/ui/KisMouseClickEater.h
index 0618f803f5..ec5054e63b 100644
--- a/plugins/impex/psd/psd_loader.h
+++ b/libs/ui/KisMouseClickEater.h
@@ -1,59 +1,47 @@
/*
- * Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2019 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 _PSD_LOADER_H_
-#define _PSD_LOADER_H_
-#include <stdio.h>
+#ifndef KISMOUSECLICKEATER_H
+#define KISMOUSECLICKEATER_H
+#include <QtGlobal>
#include <QObject>
+#include <QElapsedTimer>
-#include <QFileInfo>
-
-#include "kis_types.h"
-#include <KisImageBuilderResult.h>
-class KisDocument;
-
-class PSDLoader : public QObject {
-
- Q_OBJECT
+class KisMouseClickEater : public QObject
+{
public:
+ KisMouseClickEater(Qt::MouseButtons buttons,
+ int clicksToEat = 1,
+ QObject *parent = 0);
- PSDLoader(KisDocument *doc);
- ~PSDLoader() override;
-
- KisImageBuilder_Result buildImage(QIODevice *io);
-
- KisImageSP image();
+ ~KisMouseClickEater();
-public Q_SLOTS:
-
- virtual void cancel();
+ void reset();
+ bool eventFilter(QObject *watched, QEvent *event) override;
private:
-
- KisImageBuilder_Result decode(QIODevice *io);
-
-private:
-
- KisImageSP m_image;
- KisDocument *m_doc;
- bool m_stop;
+ Qt::MouseButtons m_buttons = Qt::NoButton;
+ int m_clicksToEat = 1;
+ int m_clicksHappened = 0;
+ bool m_blockTimedRelease = false;
+ QElapsedTimer m_timeSinceReset;
};
-#endif
+#endif // KISMOUSECLICKEATER_H
diff --git a/libs/ui/KisOpenPane.cpp b/libs/ui/KisOpenPane.cpp
index a075b9f4fa..7d4a3d8207 100644
--- a/libs/ui/KisOpenPane.cpp
+++ b/libs/ui/KisOpenPane.cpp
@@ -1,355 +1,361 @@
/* This file is part of the KDE project
Copyright (C) 2005 Peter Simonsson <psn@linux.se>
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 "KisOpenPane.h"
#include <QLayout>
#include <QLabel>
#include <QImage>
#include <QPainter>
#include <QPen>
#include <QPixmap>
#include <QSize>
#include <QString>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QStyledItemDelegate>
#include <QLinearGradient>
#include <QStandardPaths>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kis_debug.h>
#include <QUrl>
#include <KoFileDialog.h>
#include <KoIcon.h>
#include "KisTemplateTree.h"
#include "KisTemplateGroup.h"
#include "KisTemplate.h"
#include "KisDetailsPane.h"
#include "KisTemplatesPane.h"
#include "ui_KisOpenPaneBase.h"
#include <limits.h>
#include <kconfiggroup.h>
#include <kis_icon.h>
class KoSectionListItem : public QTreeWidgetItem
{
public:
- KoSectionListItem(QTreeWidget* treeWidget, const QString& name, int sortWeight, int widgetIndex = -1)
- : QTreeWidgetItem(treeWidget, QStringList() << name), m_sortWeight(sortWeight), m_widgetIndex(widgetIndex) {
+ KoSectionListItem(QTreeWidget* treeWidget, const QString& name, QString untranslatedName, int sortWeight, int widgetIndex = -1)
+ : QTreeWidgetItem(treeWidget, QStringList() << name)
+ , m_sortWeight(sortWeight)
+ , m_widgetIndex(widgetIndex)
+ , m_untranslatedName(untranslatedName)
+ {
Qt::ItemFlags newFlags = Qt::NoItemFlags;
if(m_widgetIndex >= 0)
newFlags |= Qt::ItemIsEnabled | Qt::ItemIsSelectable;
setFlags(newFlags);
}
bool operator<(const QTreeWidgetItem & other) const override {
const KoSectionListItem* item = dynamic_cast<const KoSectionListItem*>(&other);
if (!item)
return 0;
return ((item->sortWeight() - sortWeight()) < 0);
}
int sortWeight() const {
return m_sortWeight;
}
int widgetIndex() const {
return m_widgetIndex;
}
+ QString untranslatedName() const {
+ return m_untranslatedName;
+ }
+
private:
int m_sortWeight;
int m_widgetIndex;
+ QString m_untranslatedName;
};
class KisOpenPanePrivate : public Ui_KisOpenPaneBase
{
public:
KisOpenPanePrivate() :
Ui_KisOpenPaneBase() {
m_templatesSeparator = 0;
}
int m_freeCustomWidgetIndex;
KoSectionListItem* m_templatesSeparator;
};
KisOpenPane::KisOpenPane(QWidget *parent, const QStringList& mimeFilter, const QString& templatesResourcePath)
: QDialog(parent)
, d(new KisOpenPanePrivate)
{
d->setupUi(this);
m_mimeFilter = mimeFilter;
QStyledItemDelegate* delegate = new QStyledItemDelegate(d->m_sectionList);
d->m_sectionList->setItemDelegate(delegate);
connect(d->m_sectionList, SIGNAL(itemSelectionChanged()),
this, SLOT(updateSelectedWidget()));
connect(d->m_sectionList, SIGNAL(itemClicked(QTreeWidgetItem*,int)),
this, SLOT(itemClicked(QTreeWidgetItem*)));
connect(d->m_sectionList, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
this, SLOT(itemClicked(QTreeWidgetItem*)));
initTemplates(templatesResourcePath);
d->m_freeCustomWidgetIndex = 4;
if (!d->m_sectionList->selectedItems().isEmpty())
{
KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
if (selectedItem) {
d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
}
}
QList<int> sizes;
// Set the sizes of the details pane splitters
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog"); sizes = cfgGrp.readEntry("DetailsPaneSplitterSizes", sizes);
if (!sizes.isEmpty())
emit splitterResized(0, sizes);
connect(this, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)),
this, SLOT(saveSplitterSizes(KisDetailsPane*,QList<int>)));
setAcceptDrops(true);
}
KisOpenPane::~KisOpenPane()
{
if (!d->m_sectionList->selectedItems().isEmpty()) {
KoSectionListItem* item = dynamic_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
if (item) {
if (!qobject_cast<KisDetailsPane*>(d->m_widgetStack->widget(item->widgetIndex()))) {
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
- cfgGrp.writeEntry("LastReturnType", item->text(0));
+ cfgGrp.writeEntry("LastReturnType", item->untranslatedName());
}
}
}
delete d;
}
void KisOpenPane::openFileDialog()
{
KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocument");
dialog.setCaption(i18n("Open Existing Document"));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(m_mimeFilter);
Q_FOREACH (const QString &filename, dialog.filenames()) {
emit openExistingFile(QUrl::fromUserInput(filename));
}
}
void KisOpenPane::initTemplates(const QString& templatesResourcePath)
{
QTreeWidgetItem* selectItem = 0;
QTreeWidgetItem* firstItem = 0;
const int templateOffset = 1000;
if (!templatesResourcePath.isEmpty()) {
KisTemplateTree templateTree(templatesResourcePath, true);
Q_FOREACH (KisTemplateGroup *group, templateTree.groups()) {
if (group->isHidden()) {
continue;
}
if (!d->m_templatesSeparator) {
- d->m_templatesSeparator = new KoSectionListItem(d->m_sectionList, "", 999);
+ d->m_templatesSeparator = new KoSectionListItem(d->m_sectionList, "", "", 999);
}
KisTemplatesPane* pane = new KisTemplatesPane(this, group->name(),
group, templateTree.defaultTemplate());
connect(pane, SIGNAL(openUrl(QUrl)), this, SIGNAL(openTemplate(QUrl)));
connect(pane, SIGNAL(alwaysUseChanged(KisTemplatesPane*,QString)),
this, SIGNAL(alwaysUseChanged(KisTemplatesPane*,QString)));
connect(this, SIGNAL(alwaysUseChanged(KisTemplatesPane*,QString)),
pane, SLOT(changeAlwaysUseTemplate(KisTemplatesPane*,QString)));
connect(pane, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)),
this, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)));
connect(this, SIGNAL(splitterResized(KisDetailsPane*,QList<int>)),
pane, SLOT(resizeSplitter(KisDetailsPane*,QList<int>)));
- QTreeWidgetItem* item = addPane(group->name(), group->templates().first()->loadPicture(),
+ QTreeWidgetItem* item = addPane(group->name(), "Template", group->templates().first()->loadPicture(),
pane, group->sortingWeight() + templateOffset);
if (!firstItem) {
firstItem = item;
}
if (group == templateTree.defaultGroup()) {
firstItem = item;
}
if (pane->isSelected()) {
selectItem = item;
}
}
} else {
firstItem = d->m_sectionList->topLevelItem(0);
}
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
if (selectItem && (cfgGrp.readEntry("LastReturnType") == "Template")) {
d->m_sectionList->setCurrentItem(selectItem, 0, QItemSelectionModel::ClearAndSelect);
- } else if (d->m_sectionList->selectedItems().isEmpty() && firstItem) {
+ }
+ else if (d->m_sectionList->selectedItems().isEmpty() && firstItem) {
d->m_sectionList->setCurrentItem(firstItem, 0, QItemSelectionModel::ClearAndSelect);
}
}
void KisOpenPane::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->accept();
}
}
void KisOpenPane::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
// XXX: when the MVC refactoring is done, this can open a bunch of
// urls, but since the part/document combination is still 1:1
// that won't work for now.
emit openExistingFile(event->mimeData()->urls().first());
}
}
-void KisOpenPane::addCustomDocumentWidget(QWidget *widget, const QString& title, const QString& icon)
+void KisOpenPane::addCustomDocumentWidget(QWidget *widget, const QString& title, const QString &untranslatedName, const QString& icon)
{
Q_ASSERT(widget);
- QString realtitle = title;
-
- if (realtitle.isEmpty())
- realtitle = i18n("Custom Document");
-
- QTreeWidgetItem* item = addPane(realtitle, icon, widget, d->m_freeCustomWidgetIndex);
+ QTreeWidgetItem* item = addPane(title, untranslatedName, icon, widget, d->m_freeCustomWidgetIndex);
++d->m_freeCustomWidgetIndex;
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
QString lastActiveItem = cfgGrp.readEntry("LastReturnType");
bool showCustomItemByDefault = cfgGrp.readEntry("ShowCustomDocumentWidgetByDefault", false);
- if (lastActiveItem == realtitle || (lastActiveItem.isEmpty() && showCustomItemByDefault)) {
+ if (lastActiveItem == untranslatedName || (lastActiveItem.isEmpty() && showCustomItemByDefault)) {
d->m_sectionList->setCurrentItem(item, 0, QItemSelectionModel::ClearAndSelect);
KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item);
d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
}
}
-QTreeWidgetItem* KisOpenPane::addPane(const QString &title, const QString &iconName, QWidget *widget, int sortWeight)
+
+QTreeWidgetItem* KisOpenPane::addPane(const QString &title, const QString &untranslatedName, const QString &iconName, QWidget *widget, int sortWeight)
{
if (!widget) {
return 0;
}
int id = d->m_widgetStack->addWidget(widget);
- KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, sortWeight, id);
+ KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, untranslatedName, sortWeight, id);
// resizes icons so they are a bit smaller
QIcon icon = KisIconUtils::loadIcon(iconName);
QPixmap iconPixmap = icon.pixmap(32, 32);
QIcon finalIcon(iconPixmap);
listItem->setIcon(0, finalIcon);
return listItem;
}
-QTreeWidgetItem* KisOpenPane::addPane(const QString& title, const QPixmap& icon, QWidget* widget, int sortWeight)
+QTreeWidgetItem* KisOpenPane::addPane(const QString &title, const QString &untranslatedName, const QPixmap& icon, QWidget* widget, int sortWeight)
{
if (!widget) {
return 0;
}
int id = d->m_widgetStack->addWidget(widget);
int iconSize = 32;
- KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, sortWeight, id);
+ KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, untranslatedName, sortWeight, id);
if (!icon.isNull()) {
QImage image = icon.toImage();
if ((image.width() > iconSize) || (image.height() > iconSize)) {
image = image.scaled(iconSize, iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
image = image.convertToFormat(QImage::Format_ARGB32);
image = image.copy((image.width() - iconSize) / 2, (image.height() - iconSize) / 2, iconSize, iconSize);
listItem->setIcon(0, QIcon(QPixmap::fromImage(image)));
}
return listItem;
}
void KisOpenPane::updateSelectedWidget()
{
if(!d->m_sectionList->selectedItems().isEmpty())
{
KoSectionListItem* section = dynamic_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
if (!section)
return;
d->m_widgetStack->setCurrentIndex(section->widgetIndex());
}
}
void KisOpenPane::saveSplitterSizes(KisDetailsPane* sender, const QList<int>& sizes)
{
Q_UNUSED(sender);
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
cfgGrp.writeEntry("DetailsPaneSplitterSizes", sizes);
}
void KisOpenPane::itemClicked(QTreeWidgetItem* item)
{
KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item);
if (selectedItem && selectedItem->widgetIndex() >= 0) {
d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
}
}
diff --git a/libs/ui/KisOpenPane.h b/libs/ui/KisOpenPane.h
index 34e7bbd3b6..a192ea0d34 100644
--- a/libs/ui/KisOpenPane.h
+++ b/libs/ui/KisOpenPane.h
@@ -1,109 +1,109 @@
/* This file is part of the KDE project
Copyright (C) 2005 Peter Simonsson <psn@linux.se>
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 KISOPENPANE_H
#define KISOPENPANE_H
#include <QDialog>
#include <QWidget>
#include <QPixmap>
#include <QList>
class KisDetailsPane;
class KisDocument;
class KisOpenPanePrivate;
class KisTemplatesPane;
class QPixmap;
class QString;
class QStringList;
class QTreeWidgetItem;
class QUrl;
/// \internal
class KisOpenPane : public QDialog
{
Q_OBJECT
public:
/**
* Constructor
* @param parent the parent widget.
* @param mimeFilter the template-type (group) that should be selected on creation.
* @param templatesResourcePath the path to the templates.
*/
KisOpenPane(QWidget *parent, const QStringList& mimeFilter, const QString& templatesResourcePath = QString());
~KisOpenPane() override;
- QTreeWidgetItem* addPane(const QString &title, const QString &iconName, QWidget *widget, int sortWeight);
- QTreeWidgetItem* addPane(const QString& title, const QPixmap& icon, QWidget* widget, int sortWeight);
+ QTreeWidgetItem* addPane(const QString &title, const QString &untranslatedName, const QString &iconName, QWidget *widget, int sortWeight);
+ QTreeWidgetItem* addPane(const QString &title, const QString &untranslatedName, const QPixmap& icon, QWidget* widget, int sortWeight);
/**
* If the application has a way to create a document not based on a template, but on user
* provided settings, the widget showing these gets set here.
* @see KisDocument::createCustomDocumentWidget()
* @param widget the widget.
* @param title the title shown in the sidebar
* @param icon the icon shown in the sidebar
*/
- void addCustomDocumentWidget(QWidget *widget, const QString& title = QString(), const QString& icon = QString());
+ void addCustomDocumentWidget(QWidget *widget, const QString& title, const QString &untranslatedName, const QString& icon = QString());
Q_SIGNALS:
/// this signal is emitted (as defined by KisDocument) the moment the document is 'ready'
void documentSelected(KisDocument*);
protected Q_SLOTS:
void updateSelectedWidget();
void itemClicked(QTreeWidgetItem* item);
/// Saves the splitter sizes for KisDetailsPaneBase based panes
void saveSplitterSizes(KisDetailsPane* sender, const QList<int>& sizes);
private Q_SLOTS:
/// when clicked "Open Existing Document" button
void openFileDialog();
Q_SIGNALS:
void openExistingFile(const QUrl&);
void openTemplate(const QUrl&);
/// Emitted when the always use template has changed
void alwaysUseChanged(KisTemplatesPane* sender, const QString& alwaysUse);
/// Emitted when one of the detail panes have changed it's splitter
void splitterResized(KisDetailsPane* sender, const QList<int>& sizes);
void cancelButton();
protected:
/**
* Populate the list with all templates the user can choose.
* @param templatesResourcePath the template-type (group) that should be selected on creation.
*/
void initTemplates(const QString& templatesResourcePath);
// QWidget overrides
void dragEnterEvent(QDragEnterEvent * event) override;
void dropEvent(QDropEvent * event) override;
private:
QStringList m_mimeFilter;
KisOpenPanePrivate * const d;
};
#endif //KOOPENPANE_H
diff --git a/libs/ui/KisPaletteEditor.cpp b/libs/ui/KisPaletteEditor.cpp
index 690894f4c4..8cfe9e1056 100644
--- a/libs/ui/KisPaletteEditor.cpp
+++ b/libs/ui/KisPaletteEditor.cpp
@@ -1,663 +1,663 @@
/*
* Copyright (c) 2018 Michael Zhou <simeirxh@gmail.com>
*
* 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 <QHash>
#include <QString>
#include <QScopedPointer>
#include <QPointer>
#include <QFormLayout>
#include <QCheckBox>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QSpinBox>
#include <QComboBox>
#include <QMessageBox>
#include <KoDialog.h>
#include <KoFileDialog.h>
#include <KoColorSet.h>
#include <KisSwatchGroup.h>
#include <kis_signal_compressor.h>
#include <KisViewManager.h>
#include <KisDocument.h>
#include <KoResourceServer.h>
#include <KoResourceServerProvider.h>
#include <KisPaletteModel.h>
#include <kis_color_button.h>
#include "KisPaletteEditor.h"
struct KisPaletteEditor::PaletteInfo {
QString name;
QString filename;
int columnCount;
bool isGlobal;
bool isReadOnly;
QHash<QString, KisSwatchGroup> groups;
};
struct KisPaletteEditor::Private
{
bool isGlobalModified {false};
bool isNameModified {false};
bool isFilenameModified {false};
bool isColumnCountModified {false};
QSet<QString> modifiedGroupNames; // key is original group name
QSet<QString> newGroupNames;
QSet<QString> keepColorGroups;
QSet<QString> pathsToRemove;
QString groupBeingRenamed;
QPointer<KisPaletteModel> model;
QPointer<KisViewManager> view;
PaletteInfo modified;
QPointer<KoDialog> query;
KoResourceServer<KoColorSet> *rServer;
QPalette normalPalette;
QPalette warnPalette;
};
KisPaletteEditor::KisPaletteEditor(QObject *parent)
: QObject(parent)
, m_d(new Private)
{
m_d->rServer = KoResourceServerProvider::instance()->paletteServer();
m_d->warnPalette.setColor(QPalette::Text, Qt::red);
}
KisPaletteEditor::~KisPaletteEditor()
{ }
void KisPaletteEditor::setPaletteModel(KisPaletteModel *model)
{
if (!model) { return; }
m_d->model = model;
slotPaletteChanged();
connect(model, SIGNAL(sigPaletteChanged()), SLOT(slotPaletteChanged()));
connect(model, SIGNAL(sigPaletteModified()), SLOT(slotSetDocumentModified()));
}
void KisPaletteEditor::setView(KisViewManager *view)
{
m_d->view = view;
}
void KisPaletteEditor::addPalette()
{
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
KoDialog dlg;
QFormLayout layout;
dlg.mainWidget()->setLayout(&layout);
QLabel lbl(i18nc("Label for line edit to set a palette name.","Name"));
QLineEdit le(i18nc("Default name for a new palette","New Palette"));
layout.addRow(&lbl, &le);
if (dlg.exec() != QDialog::Accepted) { return; }
KoColorSet *newColorSet = new KoColorSet(newPaletteFileName(false));
newColorSet->setPaletteType(KoColorSet::KPL);
newColorSet->setIsGlobal(false);
newColorSet->setIsEditable(true);
newColorSet->setValid(true);
newColorSet->setName(le.text());
m_d->rServer->addResource(newColorSet);
m_d->rServer->removeFromBlacklist(newColorSet);
uploadPaletteList();
}
void KisPaletteEditor::importPalette()
{
- KoFileDialog dialog(Q_NULLPTR, KoFileDialog::OpenFile, "Open Palette");
+ KoFileDialog dialog(0, KoFileDialog::OpenFile, "Open Palette");
dialog.setDefaultDir(QDir::homePath());
dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset" << "application/x-gimp-color-palette");
QString filename = dialog.filename();
if (filename.isEmpty()) { return; }
if (duplicateExistsFilename(filename, false)) {
QMessageBox message;
message.setWindowTitle(i18n("Can't Import Palette"));
message.setText(i18n("Can't import palette: there's already imported with the same filename"));
message.exec();
return;
}
KoColorSet *colorSet = new KoColorSet(filename);
colorSet->load();
QString name = filenameFromPath(colorSet->filename());
if (duplicateExistsFilename(name, false)) {
colorSet->setFilename(newPaletteFileName(false));
} else {
colorSet->setFilename(name);
}
colorSet->setIsGlobal(false);
m_d->rServer->addResource(colorSet);
m_d->rServer->removeFromBlacklist(colorSet);
uploadPaletteList();
}
void KisPaletteEditor::removePalette(KoColorSet *cs)
{
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
if (!cs || !cs->isEditable()) {
return;
}
if (cs->isGlobal()) {
m_d->rServer->removeResourceAndBlacklist(cs);
QFile::remove(cs->filename());
return;
}
m_d->rServer->removeResourceFromServer(cs);
uploadPaletteList();
}
int KisPaletteEditor::rowNumberOfGroup(const QString &oriName) const
{
if (!m_d->modified.groups.contains(oriName)) { return 0; }
return m_d->modified.groups[oriName].rowCount();
}
bool KisPaletteEditor::duplicateExistsGroupName(const QString &name) const
{
if (name == m_d->groupBeingRenamed) { return false; }
Q_FOREACH (const KisSwatchGroup &g, m_d->modified.groups.values()) {
if (name == g.name()) { return true; }
}
return false;
}
bool KisPaletteEditor::duplicateExistsOriginalGroupName(const QString &name) const
{
return m_d->modified.groups.contains(name);
}
QString KisPaletteEditor::oldNameFromNewName(const QString &newName) const
{
Q_FOREACH (const QString &oldGroupName, m_d->modified.groups.keys()) {
if (m_d->modified.groups[oldGroupName].name() == newName) {
return oldGroupName;
}
}
return QString();
}
void KisPaletteEditor::rename(const QString &newName)
{
if (newName.isEmpty()) { return; }
m_d->isNameModified = true;
m_d->modified.name = newName;
}
void KisPaletteEditor::changeFilename(const QString &newName)
{
if (newName.isEmpty()) { return; }
m_d->isFilenameModified = true;
m_d->pathsToRemove.insert(m_d->modified.filename);
if (m_d->modified.isGlobal) {
m_d->modified.filename = m_d->rServer->saveLocation() + newName;
} else {
m_d->modified.filename = newName;
}
}
void KisPaletteEditor::changeColCount(int newCount)
{
m_d->isColumnCountModified = true;
m_d->modified.columnCount = newCount;
}
QString KisPaletteEditor::addGroup()
{
KoDialog dlg;
m_d->query = &dlg;
QVBoxLayout layout(&dlg);
dlg.mainWidget()->setLayout(&layout);
QLabel lblName(i18n("Name"), &dlg);
layout.addWidget(&lblName);
QLineEdit leName(&dlg);
leName.setText(newGroupName());
connect(&leName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString)));
layout.addWidget(&leName);
QLabel lblRowCount(i18n("Row count"), &dlg);
layout.addWidget(&lblRowCount);
QSpinBox spxRow(&dlg);
spxRow.setValue(20);
layout.addWidget(&spxRow);
if (dlg.exec() != QDialog::Accepted) { return QString(); }
if (duplicateExistsGroupName(leName.text())) { return QString(); }
QString realName = leName.text();
QString name = realName;
if (duplicateExistsOriginalGroupName(name)) {
name = newGroupName();
}
m_d->modified.groups[name] = KisSwatchGroup();
KisSwatchGroup &newGroup = m_d->modified.groups[name];
newGroup.setName(realName);
m_d->newGroupNames.insert(name);
newGroup.setRowCount(spxRow.value());
return realName;
}
bool KisPaletteEditor::removeGroup(const QString &name)
{
KoDialog window;
window.setWindowTitle(i18nc("@title:window", "Removing Group"));
QFormLayout editableItems(&window);
QCheckBox chkKeep(&window);
window.mainWidget()->setLayout(&editableItems);
editableItems.addRow(i18nc("Shows up when deleting a swatch group", "Keep the Colors"), &chkKeep);
if (window.exec() != KoDialog::Accepted) { return false; }
m_d->modified.groups.remove(name);
m_d->newGroupNames.remove(name);
if (chkKeep.isChecked()) {
m_d->keepColorGroups.insert(name);
}
return true;
}
QString KisPaletteEditor::renameGroup(const QString &oldName)
{
if (oldName.isEmpty() || oldName == KoColorSet::GLOBAL_GROUP_NAME) { return QString(); }
KoDialog dlg;
m_d->query = &dlg;
m_d->groupBeingRenamed = m_d->modified.groups[oldName].name();
QFormLayout form(&dlg);
dlg.mainWidget()->setLayout(&form);
QLineEdit leNewName;
connect(&leNewName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString)));
leNewName.setText(m_d->modified.groups[oldName].name());
form.addRow(i18nc("Renaming swatch group", "New name"), &leNewName);
if (dlg.exec() != KoDialog::Accepted) { return QString(); }
if (leNewName.text().isEmpty()) { return QString(); }
if (duplicateExistsGroupName(leNewName.text())) { return QString(); }
m_d->modified.groups[oldName].setName(leNewName.text());
m_d->modifiedGroupNames.insert(oldName);
return leNewName.text();
}
void KisPaletteEditor::slotGroupNameChanged(const QString &newName)
{
QLineEdit *leGroupName = qobject_cast<QLineEdit*>(sender());
if (duplicateExistsGroupName(newName) || newName == QString()) {
leGroupName->setPalette(m_d->warnPalette);
if (m_d->query->button(KoDialog::Ok)) {
m_d->query->button(KoDialog::Ok)->setEnabled(false);
}
return;
}
leGroupName->setPalette(m_d->normalPalette);
if (m_d->query->button(KoDialog::Ok)) {
m_d->query->button(KoDialog::Ok)->setEnabled(true);
}
}
void KisPaletteEditor::changeGroupRowCount(const QString &name, int newRowCount)
{
if (!m_d->modified.groups.contains(name)) { return; }
m_d->modified.groups[name].setRowCount(newRowCount);
m_d->modifiedGroupNames.insert(name);
}
void KisPaletteEditor::setGlobal(bool isGlobal)
{
m_d->isGlobalModified = true;
m_d->modified.isGlobal = isGlobal;
}
void KisPaletteEditor::setEntry(const KoColor &color, const QModelIndex &index)
{
Q_ASSERT(m_d->model);
if (!m_d->model->colorSet()->isEditable()) { return; }
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
KisSwatch c = KisSwatch(color);
c.setId(QString::number(m_d->model->colorSet()->colorCount() + 1));
c.setName(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1)));
m_d->model->setEntry(c, index);
}
void KisPaletteEditor::slotSetDocumentModified()
{
m_d->view->document()->setModified(true);
}
void KisPaletteEditor::removeEntry(const QModelIndex &index)
{
Q_ASSERT(m_d->model);
if (!m_d->model->colorSet()->isEditable()) { return; }
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
if (qvariant_cast<bool>(index.data(KisPaletteModel::IsGroupNameRole))) {
removeGroup(qvariant_cast<QString>(index.data(KisPaletteModel::GroupNameRole)));
updatePalette();
} else {
m_d->model->removeEntry(index, false);
}
if (m_d->model->colorSet()->isGlobal()) {
m_d->model->colorSet()->save();
return;
}
}
void KisPaletteEditor::modifyEntry(const QModelIndex &index)
{
if (!m_d->model->colorSet()->isEditable()) { return; }
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
KoDialog dlg;
dlg.setCaption(i18nc("@title:window", "Add a Color"));
QFormLayout *editableItems = new QFormLayout(&dlg);
dlg.mainWidget()->setLayout(editableItems);
QString groupName = qvariant_cast<QString>(index.data(Qt::DisplayRole));
if (qvariant_cast<bool>(index.data(KisPaletteModel::IsGroupNameRole))) {
renameGroup(groupName);
updatePalette();
}
else {
QLineEdit *lnIDName = new QLineEdit(&dlg);
QLineEdit *lnGroupName = new QLineEdit(&dlg);
KisColorButton *bnColor = new KisColorButton(&dlg);
QCheckBox *chkSpot = new QCheckBox(&dlg);
chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color."));
KisSwatch entry = m_d->model->getEntry(index);
editableItems->addRow(i18n("ID"), lnIDName);
editableItems->addRow(i18nc("Name for a swatch group", "Swatch group name"), lnGroupName);
editableItems->addRow(i18n("Color"), bnColor);
editableItems->addRow(i18n("Spot"), chkSpot);
lnGroupName->setText(entry.name());
lnIDName->setText(entry.id());
bnColor->setColor(entry.color());
chkSpot->setChecked(entry.spotColor());
if (dlg.exec() == KoDialog::Accepted) {
entry.setName(lnGroupName->text());
entry.setId(lnIDName->text());
entry.setColor(bnColor->color());
entry.setSpotColor(chkSpot->isChecked());
m_d->model->setEntry(entry, index);
}
}
}
void KisPaletteEditor::addEntry(const KoColor &color)
{
Q_ASSERT(m_d->model);
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
if (!m_d->model->colorSet()->isEditable()) { return; }
KoDialog window;
window.setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry"));
QFormLayout editableItems(&window);
window.mainWidget()->setLayout(&editableItems);
QComboBox cmbGroups(&window);
cmbGroups.addItems(m_d->model->colorSet()->getGroupNames());
QLineEdit lnIDName(&window);
QLineEdit lnName(&window);
KisColorButton bnColor(&window);
QCheckBox chkSpot(&window);
chkSpot.setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color."));
editableItems.addRow(i18n("Group"), &cmbGroups);
editableItems.addRow(i18n("ID"), &lnIDName);
editableItems.addRow(i18n("Name"), &lnName);
editableItems.addRow(i18n("Color"), &bnColor);
editableItems.addRow(i18nc("Spot color", "Spot"), &chkSpot);
cmbGroups.setCurrentIndex(0);
lnName.setText(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1)));
lnIDName.setText(QString::number(m_d->model->colorSet()->colorCount() + 1));
bnColor.setColor(color);
chkSpot.setChecked(false);
if (window.exec() != KoDialog::Accepted) { return; }
QString groupName = cmbGroups.currentText();
KisSwatch newEntry;
newEntry.setColor(bnColor.color());
newEntry.setName(lnName.text());
newEntry.setId(lnIDName.text());
newEntry.setSpotColor(chkSpot.isChecked());
m_d->model->addEntry(newEntry, groupName);
if (m_d->model->colorSet()->isGlobal()) {
m_d->model->colorSet()->save();
return;
}
m_d->modifiedGroupNames.insert(groupName);
m_d->modified.groups[groupName].addEntry(newEntry);
}
void KisPaletteEditor::updatePalette()
{
Q_ASSERT(m_d->model);
Q_ASSERT(m_d->model->colorSet());
if (!m_d->model->colorSet()->isEditable()) { return; }
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
KoColorSet *palette = m_d->model->colorSet();
PaletteInfo &modified = m_d->modified;
if (m_d->isColumnCountModified) {
palette->setColumnCount(modified.columnCount);
}
if (m_d->isNameModified) {
palette->setName(modified.name);
}
if (m_d->isFilenameModified) {
QString originalPath = palette->filename();
palette->setFilename(modified.filename);
if (palette->isGlobal()) {
if (!palette->save()) {
palette->setFilename(newPaletteFileName(true));
palette->save();
}
QFile::remove(originalPath);
}
}
if (m_d->isGlobalModified) {
palette->setIsGlobal(modified.isGlobal);
if (modified.isGlobal) {
setGlobal();
} else {
setNonGlobal();
}
}
Q_FOREACH (const QString &groupName, palette->getGroupNames()) {
if (!modified.groups.contains(groupName)) {
m_d->model->removeGroup(groupName, m_d->keepColorGroups.contains(groupName));
}
}
m_d->keepColorGroups.clear();
Q_FOREACH (const QString &groupName, palette->getGroupNames()) {
if (m_d->modifiedGroupNames.contains(groupName)) {
m_d->model->setRowNumber(groupName, modified.groups[groupName].rowCount());
if (groupName != modified.groups[groupName].name()) {
m_d->model->renameGroup(groupName, modified.groups[groupName].name());
modified.groups[modified.groups[groupName].name()] = modified.groups[groupName];
modified.groups.remove(groupName);
}
}
}
m_d->modifiedGroupNames.clear();
Q_FOREACH (const QString &newGroupName, m_d->newGroupNames) {
m_d->model->addGroup(modified.groups[newGroupName]);
}
m_d->newGroupNames.clear();
if (m_d->model->colorSet()->isGlobal()) {
m_d->model->colorSet()->save();
}
}
void KisPaletteEditor::slotPaletteChanged()
{
Q_ASSERT(m_d->model);
if (!m_d->model->colorSet()) { return; }
KoColorSet *palette = m_d->model->colorSet();
m_d->modified.groups.clear();
m_d->keepColorGroups.clear();
m_d->newGroupNames.clear();
m_d->modifiedGroupNames.clear();
m_d->modified.name = palette->name();
m_d->modified.filename = palette->filename();
m_d->modified.columnCount = palette->columnCount();
m_d->modified.isGlobal = palette->isGlobal();
m_d->modified.isReadOnly = !palette->isEditable();
Q_FOREACH (const QString &groupName, palette->getGroupNames()) {
KisSwatchGroup *cs = palette->getGroup(groupName);
m_d->modified.groups[groupName] = KisSwatchGroup(*cs);
}
}
void KisPaletteEditor::setGlobal()
{
Q_ASSERT(m_d->model);
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
if (!m_d->model->colorSet()) { return; }
KoColorSet *colorSet = m_d->model->colorSet();
QString saveLocation = m_d->rServer->saveLocation();
QString name = filenameFromPath(colorSet->filename());
QFileInfo fileInfo(saveLocation + name);
colorSet->setFilename(fileInfo.filePath());
colorSet->setIsGlobal(true);
m_d->rServer->removeFromBlacklist(colorSet);
if (!colorSet->save()) {
QMessageBox message;
message.setWindowTitle(i18n("Saving palette failed"));
message.setText(i18n("Failed to save global palette file. Please set it to non-global, or you will lose the file when you close Krita"));
message.exec();
}
uploadPaletteList();
}
bool KisPaletteEditor::duplicateExistsFilename(const QString &filename, bool global) const
{
QString prefix;
if (global) {
prefix = m_d->rServer->saveLocation();
}
Q_FOREACH (const KoResource *r, KoResourceServerProvider::instance()->paletteServer()->resources()) {
if (r->filename() == prefix + filename && r != m_d->model->colorSet()) {
return true;
}
}
return false;
}
QString KisPaletteEditor::relativePathFromSaveLocation() const
{
return filenameFromPath(m_d->modified.filename);
}
void KisPaletteEditor::setNonGlobal()
{
Q_ASSERT(m_d->model);
if (!m_d->view) { return; }
if (!m_d->view->document()) { return; }
if (!m_d->model->colorSet()) { return; }
KoColorSet *colorSet = m_d->model->colorSet();
QString name = filenameFromPath(colorSet->filename());
QFile::remove(colorSet->filename());
if (duplicateExistsFilename(name, false)) {
colorSet->setFilename(newPaletteFileName(false));
} else {
colorSet->setFilename(name);
}
colorSet->setIsGlobal(false);
uploadPaletteList();
}
QString KisPaletteEditor::newPaletteFileName(bool isGlobal)
{
QSet<QString> nameSet;
Q_FOREACH (const KoResource *r, m_d->rServer->resources()) {
nameSet.insert(r->filename());
}
KoColorSet tmpColorSet;
QString result = "new_palette_";
if (isGlobal) {
result = m_d->rServer->saveLocation() + result;
}
int i = 0;
while (nameSet.contains(result + QString::number(i) + tmpColorSet.defaultFileExtension())) {
i++;
}
result = result + QString::number(i) + tmpColorSet.defaultFileExtension();
return result;
}
QString KisPaletteEditor::newGroupName() const
{
int i = 1;
QString groupname = i18nc("Default new group name", "New Group %1", QString::number(i));
while (m_d->modified.groups.contains(groupname)) {
i++;
groupname = i18nc("Default new group name", "New Group %1", QString::number(i));
}
return groupname;
}
void KisPaletteEditor::uploadPaletteList() const
{
QList<KoColorSet *> list;
Q_FOREACH (KoResource * paletteResource, m_d->rServer->resources()) {
KoColorSet *palette = static_cast<KoColorSet*>(paletteResource);
Q_ASSERT(palette);
if (!palette->isGlobal()) {
list.append(palette);
}
}
m_d->view->document()->setPaletteList(list);
}
QString KisPaletteEditor::filenameFromPath(const QString &path) const
{
return QDir::fromNativeSeparators(path).section('/', -1, -1);
}
diff --git a/libs/ui/KisPaletteEditor.h b/libs/ui/KisPaletteEditor.h
index 6573ff7fdd..a8a8c27aa4 100644
--- a/libs/ui/KisPaletteEditor.h
+++ b/libs/ui/KisPaletteEditor.h
@@ -1,146 +1,146 @@
/*
* Copyright (c) 2018 Michael Zhou <simeirxh@gmail.com>
*
* 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 KISPALETTEMANAGER_H
#define KISPALETTEMANAGER_H
#include <QObject>
#include <QScopedPointer>
#include <KisSwatch.h>
#include <kritaui_export.h>
class KoColorSet;
class KisPaletteModel;
class KisViewManager;
class KisSwatchGroup;
class KisViewManager;
/**
* @brief The PaletteEditor class
* this class manipulates a KisPaletteModel using GUI elements and communicate
* with KisDocument
*
* Changes made in this class won't be done to the palette if the palette is
* read only (not editable, isEditable() == false)
*/
class KRITAUI_EXPORT KisPaletteEditor : public QObject
{
Q_OBJECT
public:
struct PaletteInfo;
public:
- explicit KisPaletteEditor(QObject *parent = Q_NULLPTR);
+ explicit KisPaletteEditor(QObject *parent = 0);
~KisPaletteEditor();
void setPaletteModel(KisPaletteModel *model);
void setView(KisViewManager *view);
void addPalette();
void importPalette();
void removePalette(KoColorSet *);
/**
* @brief rowNumberOfGroup
* @param oriName the original name of a group at the creation of the instance
* @return newest row number of the group
*/
int rowNumberOfGroup(const QString &oriName) const;
/**
* @brief oldNameFromNewName
* @param newName the current name of a group
* @return the name of the group at the creation of the instance
*/
QString oldNameFromNewName(const QString &newName) const;
/**
* @brief duplicateExistsFilename
* @param filename the name of the file
* @param global if this filename is going to be used for a global palette
* @return true if the a palette in the resource system that has filename
* name already exists else false
*/
bool duplicateExistsFilename(const QString &filename, bool global) const;
QString relativePathFromSaveLocation() const;
void rename(const QString &newName);
void changeFilename(const QString &newName);
void changeColCount(int);
/**
* @brief addGroup
* @return new group's name if change accepted, empty string if cancelled
*/
QString addGroup();
/**
* @brief removeGroup
* @param name original group name
* @return true if change accepted, false if cancelled
*/
bool removeGroup(const QString &name);
/**
* @brief renameGroup
* @param oldName
* @return new name if change accpeted, empty string if cancelled
*/
QString renameGroup(const QString &oldName);
void changeGroupRowCount(const QString &name, int newRowCount);
void setGlobal(bool);
void setReadOnly(bool);
void setEntry(const KoColor &color, const QModelIndex &index);
void removeEntry(const QModelIndex &index);
void modifyEntry(const QModelIndex &index);
void addEntry(const KoColor &color);
bool isModified() const;
/**
* @brief getModifiedGroup
* @param originalName name of the group at the creation of the instance
* @return the modified group
*/
const KisSwatchGroup &getModifiedGroup(const QString &originalName) const;
/**
* @brief updatePalette
* MUST be called to make the changes into the resource server
*/
void updatePalette();
private Q_SLOTS:
void slotGroupNameChanged(const QString &newName);
void slotPaletteChanged();
void slotSetDocumentModified();
private:
QString newPaletteFileName(bool isGlobal);
QString newGroupName() const;
void setNonGlobal();
void setGlobal();
bool duplicateExistsGroupName(const QString &name) const;
bool duplicateExistsOriginalGroupName(const QString &name) const;
void uploadPaletteList() const;
QString filenameFromPath(const QString &path) const;
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif // KISPALETTEMANAGER_H
diff --git a/libs/ui/KisReferenceImage.cpp b/libs/ui/KisReferenceImage.cpp
index 5111bcff56..20f91cc3b3 100644
--- a/libs/ui/KisReferenceImage.cpp
+++ b/libs/ui/KisReferenceImage.cpp
@@ -1,334 +1,359 @@
/*
* Copyright (C) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KisReferenceImage.h"
#include <QImage>
#include <QMessageBox>
#include <QPainter>
+#include <QApplication>
+#include <QClipboard>
#include <kundo2command.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoTosContainer_p.h>
#include <krita_utils.h>
#include <kis_coordinates_converter.h>
#include <kis_dom_utils.h>
#include <SvgUtil.h>
#include <libs/flake/svg/parsers/SvgTransformParser.h>
#include <libs/brush/kis_qimage_pyramid.h>
+#include <utils/KisClipboardUtil.h>
struct KisReferenceImage::Private {
// Filename within .kra (for embedding)
QString internalFilename;
// File on disk (for linking)
QString externalFilename;
QImage image;
QImage cachedImage;
KisQImagePyramid mipmap;
qreal saturation{1.0};
int id{-1};
bool embed{true};
bool loadFromFile() {
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!externalFilename.isEmpty(), false);
return image.load(externalFilename);
}
+ bool loadFromClipboard() {
+ image = KisClipboardUtil::getImageFromClipboard();
+ return !image.isNull();
+ }
+
void updateCache() {
if (saturation < 1.0) {
cachedImage = KritaUtils::convertQImageToGrayA(image);
if (saturation > 0.0) {
QPainter gc2(&cachedImage);
gc2.setOpacity(saturation);
gc2.drawImage(QPoint(), image);
}
} else {
cachedImage = image;
}
mipmap = KisQImagePyramid(cachedImage);
}
};
KisReferenceImage::SetSaturationCommand::SetSaturationCommand(const QList<KoShape *> &shapes, qreal newSaturation, KUndo2Command *parent)
: KUndo2Command(kundo2_i18n("Set saturation"), parent)
, newSaturation(newSaturation)
{
images.reserve(shapes.count());
Q_FOREACH(auto *shape, shapes) {
auto *reference = dynamic_cast<KisReferenceImage*>(shape);
KIS_SAFE_ASSERT_RECOVER_BREAK(reference);
images.append(reference);
}
Q_FOREACH(auto *image, images) {
oldSaturations.append(image->saturation());
}
}
void KisReferenceImage::SetSaturationCommand::undo()
{
auto saturationIterator = oldSaturations.begin();
Q_FOREACH(auto *image, images) {
image->setSaturation(*saturationIterator);
image->update();
saturationIterator++;
}
}
void KisReferenceImage::SetSaturationCommand::redo()
{
Q_FOREACH(auto *image, images) {
image->setSaturation(newSaturation);
image->update();
}
}
KisReferenceImage::KisReferenceImage()
: d(new Private())
{
setKeepAspectRatio(true);
}
KisReferenceImage::KisReferenceImage(const KisReferenceImage &rhs)
: KoTosContainer(new KoTosContainerPrivate(*rhs.d_func(), this))
, d(new Private(*rhs.d))
{}
KisReferenceImage::~KisReferenceImage()
{}
KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent)
{
KisReferenceImage *reference = new KisReferenceImage();
reference->d->externalFilename = filename;
bool ok = reference->d->loadFromFile();
if (ok) {
QRect r = QRect(QPoint(), reference->d->image.size());
QSizeF shapeSize = converter.imageToDocument(r).size();
reference->setSize(shapeSize);
} else {
delete reference;
if (parent) {
QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename));
}
return nullptr;
}
return reference;
}
+KisReferenceImage *KisReferenceImage::fromClipboard(const KisCoordinatesConverter &converter)
+{
+ KisReferenceImage *reference = new KisReferenceImage();
+ bool ok = reference->d->loadFromClipboard();
+
+ if (ok) {
+ QRect r = QRect(QPoint(), reference->d->image.size());
+ QSizeF size = converter.imageToDocument(r).size();
+ reference->setSize(size);
+ } else {
+ delete reference;
+ reference = nullptr;
+ }
+
+ return reference;
+}
+
void KisReferenceImage::paint(QPainter &gc, const KoViewConverter &converter, KoShapePaintingContext &/*paintcontext*/)
{
if (!parent()) return;
gc.save();
applyConversion(gc, converter);
QSizeF shapeSize = size();
QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height());
if (d->cachedImage.isNull()) {
d->updateCache();
}
qreal scale;
QImage prescaled = d->mipmap.getClosest(gc.transform() * transform, &scale);
transform.scale(1.0 / scale, 1.0 / scale);
gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip);
gc.setTransform(transform, true);
gc.drawImage(QPoint(), prescaled);
gc.restore();
}
void KisReferenceImage::setSaturation(qreal saturation)
{
d->saturation = saturation;
d->cachedImage = QImage();
}
qreal KisReferenceImage::saturation() const
{
return d->saturation;
}
void KisReferenceImage::setEmbed(bool embed)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty());
d->embed = embed;
}
bool KisReferenceImage::embed()
{
return d->embed;
}
bool KisReferenceImage::hasLocalFile()
{
return !d->externalFilename.isEmpty();
}
QString KisReferenceImage::filename() const
{
return d->externalFilename;
}
QString KisReferenceImage::internalFile() const
{
return d->internalFilename;
}
void KisReferenceImage::setFilename(const QString &filename)
{
d->externalFilename = filename;
d->embed = false;
}
QColor KisReferenceImage::getPixel(QPointF position)
{
if (transparency() == 1.0) return Qt::transparent;
const QSizeF shapeSize = size();
const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height());
const QTransform transform = absoluteTransformation(nullptr).inverted() * scale;
const QPointF localPosition = position * transform;
if (d->cachedImage.isNull()) {
d->updateCache();
}
return d->cachedImage.pixelColor(localPosition.toPoint());
}
void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id)
{
d->id = id;
QDomElement element = document.createElement("referenceimage");
if (d->embed) {
d->internalFilename = QString("reference_images/%1.png").arg(id);
}
const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename);
element.setAttribute("src", src);
const QSizeF &shapeSize = size();
element.setAttribute("width", KisDomUtils::toString(shapeSize.width()));
element.setAttribute("height", KisDomUtils::toString(shapeSize.height()));
element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false");
element.setAttribute("transform", SvgUtil::transformToString(transform()));
element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency()));
element.setAttribute("saturation", KisDomUtils::toString(d->saturation));
parentElement.appendChild(element);
}
KisReferenceImage * KisReferenceImage::fromXml(const QDomElement &elem)
{
auto *reference = new KisReferenceImage();
const QString &src = elem.attribute("src");
if (src.startsWith("file://")) {
reference->d->externalFilename = src.mid(7);
reference->d->embed = false;
} else {
reference->d->internalFilename = src;
reference->d->embed = true;
}
qreal width = KisDomUtils::toDouble(elem.attribute("width", "100"));
qreal height = KisDomUtils::toDouble(elem.attribute("height", "100"));
reference->setSize(QSizeF(width, height));
reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true");
auto transform = SvgTransformParser(elem.attribute("transform")).transform();
reference->setTransformation(transform);
qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1"));
reference->setTransparency(1.0 - opacity);
qreal saturation = KisDomUtils::toDouble(elem.attribute("opacity", "1"));
reference->setSaturation(saturation);
return reference;
}
bool KisReferenceImage::saveImage(KoStore *store) const
{
if (!d->embed) return true;
if (!store->open(d->internalFilename)) {
return false;
}
bool saved = false;
KoStoreDevice storeDev(store);
if (storeDev.open(QIODevice::WriteOnly)) {
saved = d->image.save(&storeDev, "PNG");
}
return store->close() && saved;
}
bool KisReferenceImage::loadImage(KoStore *store)
{
if (!d->embed) {
return d->loadFromFile();
}
if (!store->open(d->internalFilename)) {
return false;
}
KoStoreDevice storeDev(store);
if (!storeDev.open(QIODevice::ReadOnly)) {
return false;
}
if (!d->image.load(&storeDev, "PNG")) {
return false;
}
return store->close();
}
KoShape *KisReferenceImage::cloneShape() const
{
return new KisReferenceImage(*this);
}
diff --git a/libs/ui/KisReferenceImage.h b/libs/ui/KisReferenceImage.h
index 4e7193acaf..e315089574 100644
--- a/libs/ui/KisReferenceImage.h
+++ b/libs/ui/KisReferenceImage.h
@@ -1,96 +1,97 @@
/*
* Copyright (C) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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 KISREFERENCEIMAGE_H
#define KISREFERENCEIMAGE_H
#include <QScopedPointer>
#include <kundo2command.h>
#include <kritaui_export.h>
#include <KoTosContainer.h>
#include <KoColor.h>
class QImage;
class QPointF;
class QPainter;
class QRectF;
class KoStore;
class KisCoordinatesConverter;
class KisCanvas2;
/**
* @brief The KisReferenceImage class represents a single reference image
*/
class KRITAUI_EXPORT KisReferenceImage : public KoTosContainer
{
public:
struct KRITAUI_EXPORT SetSaturationCommand : public KUndo2Command {
QVector<KisReferenceImage*> images;
QVector<qreal> oldSaturations;
qreal newSaturation;
explicit SetSaturationCommand(const QList<KoShape *> &images, qreal newSaturation, KUndo2Command *parent = 0);
void undo() override;
void redo() override;
};
KisReferenceImage();
KisReferenceImage(const KisReferenceImage &rhs);
~KisReferenceImage();
KoShape *cloneShape() const override;
/**
* Load a reference image from specified file.
* If parent is provided and the image cannot be loaded, a warning message will be displayed to user.
* @return reference image or null if one could not be loaded
*/
static KisReferenceImage * fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent /*= nullptr*/);
+ static KisReferenceImage * fromClipboard(const KisCoordinatesConverter &converter);
void setSaturation(qreal saturation);
qreal saturation() const;
void setEmbed(bool embed);
bool embed();
bool hasLocalFile();
void setFilename(const QString &filename);
QString filename() const;
QString internalFile() const;
void paint(QPainter &gc, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
bool loadOdf(const KoXmlElement &/*element*/, KoShapeLoadingContext &/*context*/) override { return false; }
void saveOdf(KoShapeSavingContext &/*context*/) const override {}
QColor getPixel(QPointF position);
void saveXml(QDomDocument &document, QDomElement &parentElement, int id);
bool saveImage(KoStore *store) const;
static KisReferenceImage * fromXml(const QDomElement &elem);
bool loadImage(KoStore *store);
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif // KISREFERENCEIMAGE_H
diff --git a/libs/ui/KisReferenceImagesDecoration.cpp b/libs/ui/KisReferenceImagesDecoration.cpp
index b3d7e2ce1c..e7dee87753 100644
--- a/libs/ui/KisReferenceImagesDecoration.cpp
+++ b/libs/ui/KisReferenceImagesDecoration.cpp
@@ -1,173 +1,180 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KisReferenceImagesDecoration.h"
#include "KoShapeManager.h"
#include "kis_algebra_2d.h"
#include "KisDocument.h"
#include "KisReferenceImagesLayer.h"
struct KisReferenceImagesDecoration::Private {
struct Buffer
{
/// Top left corner of the buffer relative to the viewport
QPointF position;
QImage image;
QRectF bounds() const
{
return QRectF(position, image.size());
}
};
KisReferenceImagesDecoration *q;
KisWeakSharedPtr<KisReferenceImagesLayer> layer;
Buffer buffer;
QTransform previousTransform;
QSizeF previousViewSize;
explicit Private(KisReferenceImagesDecoration *q)
: q(q)
{}
void updateBufferByImageCoordinates(const QRectF &dirtyImageRect)
{
QRectF dirtyWidgetRect = q->view()->viewConverter()->imageToWidget(dirtyImageRect);
updateBuffer(dirtyWidgetRect, dirtyImageRect);
}
void updateBufferByWidgetCoordinates(const QRectF &dirtyWidgetRect)
{
QRectF dirtyImageRect = q->view()->viewConverter()->widgetToImage(dirtyWidgetRect);
updateBuffer(dirtyWidgetRect, dirtyImageRect);
}
private:
void updateBuffer(QRectF widgetRect, QRectF imageRect)
{
KisCoordinatesConverter *viewConverter = q->view()->viewConverter();
QTransform transform = viewConverter->imageToWidgetTransform();
if (buffer.image.isNull() || !buffer.bounds().contains(widgetRect)) {
const QRectF boundingImageRect = layer->boundingImageRect();
const QRectF boundingWidgetRect = q->view()->viewConverter()->imageToWidget(boundingImageRect);
widgetRect = boundingWidgetRect.intersected(q->view()->rect());
if (widgetRect.isNull()) return;
buffer.position = widgetRect.topLeft();
buffer.image = QImage(widgetRect.size().toSize(), QImage::Format_ARGB32);
buffer.image.fill(Qt::transparent);
imageRect = q->view()->viewConverter()->widgetToImage(widgetRect);
}
QPainter gc(&buffer.image);
gc.translate(-buffer.position);
gc.setTransform(transform, true);
gc.save();
gc.setCompositionMode(QPainter::CompositionMode_Source);
gc.fillRect(imageRect, Qt::transparent);
gc.restore();
gc.setClipRect(imageRect);
layer->paintReferences(gc);
}
};
KisReferenceImagesDecoration::KisReferenceImagesDecoration(QPointer<KisView> parent, KisDocument *document)
: KisCanvasDecoration("referenceImagesDecoration", parent)
, d(new Private(this))
{
connect(document->image().data(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), this, SLOT(slotNodeAdded(KisNodeSP)));
+ connect(document, &KisDocument::sigReferenceImagesLayerChanged, this, &KisReferenceImagesDecoration::slotNodeAdded);
auto referenceImageLayer = document->referenceImagesLayer();
if (referenceImageLayer) {
setReferenceImageLayer(referenceImageLayer);
}
}
KisReferenceImagesDecoration::~KisReferenceImagesDecoration()
{}
void KisReferenceImagesDecoration::addReferenceImage(KisReferenceImage *referenceImage)
{
KisDocument *document = view()->document();
KUndo2Command *cmd = KisReferenceImagesLayer::addReferenceImages(document, {referenceImage});
document->addCommand(cmd);
}
bool KisReferenceImagesDecoration::documentHasReferenceImages() const
{
return view()->document()->referenceImagesLayer() != nullptr;
}
void KisReferenceImagesDecoration::drawDecoration(QPainter &gc, const QRectF &/*updateRect*/, const KisCoordinatesConverter *converter, KisCanvas2 */*canvas*/)
{
// TODO: can we use partial updates here?
KisSharedPtr<KisReferenceImagesLayer> layer = d->layer.toStrongRef();
if (!layer.isNull()) {
QSizeF viewSize = view()->size();
QTransform transform = converter->imageToWidgetTransform();
if (d->previousViewSize != viewSize || !KisAlgebra2D::fuzzyMatrixCompare(transform, d->previousTransform, 1e-4)) {
d->previousViewSize = viewSize;
d->previousTransform = transform;
d->buffer.image = QImage();
d->updateBufferByWidgetCoordinates(QRectF(QPointF(0,0), viewSize));
}
if (!d->buffer.image.isNull()) {
gc.drawImage(d->buffer.position, d->buffer.image);
}
}
}
void KisReferenceImagesDecoration::slotNodeAdded(KisNodeSP node)
{
auto *referenceImagesLayer = dynamic_cast<KisReferenceImagesLayer*>(node.data());
if (referenceImagesLayer) {
setReferenceImageLayer(referenceImagesLayer);
}
}
void KisReferenceImagesDecoration::slotReferenceImagesChanged(const QRectF &dirtyRect)
{
d->updateBufferByImageCoordinates(dirtyRect);
QRectF documentRect = view()->viewConverter()->imageToDocument(dirtyRect);
view()->canvasBase()->updateCanvas(documentRect);
}
void KisReferenceImagesDecoration::setReferenceImageLayer(KisSharedPtr<KisReferenceImagesLayer> layer)
{
- d->layer = layer;
- connect(
- layer.data(), SIGNAL(sigUpdateCanvas(QRectF)),
- this, SLOT(slotReferenceImagesChanged(QRectF))
- );
+ if (d->layer.data() != layer.data()) {
+ if (d->layer) {
+ d->layer->disconnect(this);
+ }
+ d->layer = layer;
+ connect(layer.data(), SIGNAL(sigUpdateCanvas(QRectF)),
+ this, SLOT(slotReferenceImagesChanged(QRectF)));
+ if (layer->extent() != QRectF()) { // in case the reference layer is just being loaded from the .kra file
+ slotReferenceImagesChanged(layer->extent());
+ }
+ }
}
diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp
index 3d69a2244e..c5d99788d4 100644
--- a/libs/ui/KisView.cpp
+++ b/libs/ui/KisView.cpp
@@ -1,1062 +1,1062 @@
/*
* Copyright (C) 2014 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KisView.h"
#include "KisView_p.h"
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include <KoDocumentInfo.h>
#include "KoPageLayout.h"
#include <KoToolManager.h>
#include <kis_icon.h>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kselectaction.h>
#include <kconfiggroup.h>
#include <QMenu>
#include <QMessageBox>
#include <QUrl>
#include <QTemporaryFile>
#include <QApplication>
#include <QDesktopWidget>
#include <QDockWidget>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QImage>
#include <QList>
#include <QPrintDialog>
#include <QToolBar>
#include <QStatusBar>
#include <QMoveEvent>
#include <QMdiSubWindow>
#include <kis_image.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <kis_layer.h>
#include <kis_mask.h>
#include <kis_selection.h>
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_config.h"
#include "KisDocument.h"
#include "kis_image_manager.h"
#include "KisMainWindow.h"
#include "kis_mimedata.h"
#include "kis_mirror_axis.h"
#include "kis_node_commands_adapter.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_shape_controller.h"
#include "kis_tool_freehand.h"
#include "KisViewManager.h"
#include "kis_zoom_manager.h"
#include "kis_statusbar.h"
#include "kis_painting_assistants_decoration.h"
#include "KisReferenceImagesDecoration.h"
#include "kis_progress_widget.h"
#include "kis_signal_compressor.h"
#include "kis_filter_manager.h"
#include "kis_file_layer.h"
#include "krita_utils.h"
#include "input/kis_input_manager.h"
#include "KisRemoteFileFetcher.h"
#include "kis_selection_manager.h"
//static
QString KisView::newObjectName()
{
static int s_viewIFNumber = 0;
QString name; name.setNum(s_viewIFNumber++); name.prepend("view_");
return name;
}
bool KisView::s_firstView = true;
class Q_DECL_HIDDEN KisView::Private
{
public:
Private(KisView *_q,
KisDocument *document,
KoCanvasResourceProvider *resourceManager,
KActionCollection *actionCollection)
: actionCollection(actionCollection)
, viewConverter()
, canvasController(_q, actionCollection)
, canvas(&viewConverter, resourceManager, _q, document->shapeController())
, zoomManager(_q, &this->viewConverter, &this->canvasController)
, paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q))
, referenceImagesDecoration(new KisReferenceImagesDecoration(_q, document))
, floatingMessageCompressor(100, KisSignalCompressor::POSTPONE)
{
}
bool inOperation; //in the middle of an operation (no screen refreshing)?
QPointer<KisDocument> document; // our KisDocument
QWidget *tempActiveWidget = 0;
/**
* Signals the document has been deleted. Can't use document==0 since this
* only happens in ~QObject, and views get deleted by ~KisDocument.
* XXX: either provide a better justification to do things this way, or
* rework the mechanism.
*/
bool documentDeleted = false;
KActionCollection* actionCollection;
KisCoordinatesConverter viewConverter;
KisCanvasController canvasController;
KisCanvas2 canvas;
KisZoomManager zoomManager;
KisViewManager *viewManager = 0;
KisNodeSP currentNode;
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration;
KisReferenceImagesDecorationSP referenceImagesDecoration;
bool isCurrent = false;
bool showFloatingMessage = false;
QPointer<KisFloatingMessage> savedFloatingMessage;
KisSignalCompressor floatingMessageCompressor;
QMdiSubWindow *subWindow{nullptr};
bool softProofing = false;
bool gamutCheck = false;
// Hmm sorry for polluting the private class with such a big inner class.
// At the beginning it was a little struct :)
class StatusBarItem
{
public:
StatusBarItem(QWidget * widget, int stretch, bool permanent)
: m_widget(widget),
m_stretch(stretch),
m_permanent(permanent),
m_connected(false),
m_hidden(false) {}
bool operator==(const StatusBarItem& rhs) {
return m_widget == rhs.m_widget;
}
bool operator!=(const StatusBarItem& rhs) {
return m_widget != rhs.m_widget;
}
QWidget * widget() const {
return m_widget;
}
void ensureItemShown(QStatusBar * sb) {
Q_ASSERT(m_widget);
if (!m_connected) {
if (m_permanent)
sb->addPermanentWidget(m_widget, m_stretch);
else
sb->addWidget(m_widget, m_stretch);
if(!m_hidden)
m_widget->show();
m_connected = true;
}
}
void ensureItemHidden(QStatusBar * sb) {
if (m_connected) {
m_hidden = m_widget->isHidden();
sb->removeWidget(m_widget);
m_widget->hide();
m_connected = false;
}
}
private:
QWidget * m_widget = 0;
int m_stretch;
bool m_permanent;
bool m_connected = false;
bool m_hidden = false;
};
};
KisView::KisView(KisDocument *document, KoCanvasResourceProvider *resourceManager, KActionCollection *actionCollection, QWidget *parent)
: QWidget(parent)
, d(new Private(this, document, resourceManager, actionCollection))
{
Q_ASSERT(document);
connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool)));
setObjectName(newObjectName());
d->document = document;
setFocusPolicy(Qt::StrongFocus);
QStatusBar * sb = statusBar();
if (sb) { // No statusbar in e.g. konqueror
connect(d->document, SIGNAL(statusBarMessage(QString,int)),
this, SLOT(slotSavingStatusMessage(QString,int)));
connect(d->document, SIGNAL(clearStatusBarMessage()),
this, SLOT(slotClearStatusText()));
}
d->canvas.setup();
KisConfig cfg(false);
d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->canvasController.setVastScrolling(cfg.vastScrolling());
d->canvasController.setCanvas(&d->canvas);
d->zoomManager.setup(d->actionCollection);
connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged()));
setAcceptDrops(true);
connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished()));
connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished()));
d->canvas.addDecoration(d->referenceImagesDecoration);
d->referenceImagesDecoration->setVisible(true);
d->canvas.addDecoration(d->paintingAssistantsDecoration);
d->paintingAssistantsDecoration->setVisible(true);
d->showFloatingMessage = cfg.showCanvasMessages();
d->zoomManager.updateScreenResolution(this);
}
KisView::~KisView()
{
if (d->viewManager) {
if (d->viewManager->filterManager()->isStrokeRunning()) {
d->viewManager->filterManager()->cancel();
}
d->viewManager->mainWindow()->notifyChildViewDestroyed(this);
}
KoToolManager::instance()->removeCanvasController(&d->canvasController);
d->canvasController.setCanvas(0);
KisPart::instance()->removeView(this);
delete d;
}
void KisView::notifyCurrentStateChanged(bool isCurrent)
{
d->isCurrent = isCurrent;
if (!d->isCurrent && d->savedFloatingMessage) {
d->savedFloatingMessage->removeMessage();
}
KisInputManager *inputManager = globalInputManager();
if (d->isCurrent) {
inputManager->attachPriorityEventFilter(&d->canvasController);
} else {
inputManager->detachPriorityEventFilter(&d->canvasController);
}
/**
* When current view is changed, currently selected node is also changed,
* therefore we should update selection overlay mask
*/
viewManager()->selectionManager()->selectionChanged();
}
bool KisView::isCurrent() const
{
return d->isCurrent;
}
void KisView::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisView::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
{
if (!d->viewManager) return;
if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) {
if (d->savedFloatingMessage) {
d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment);
} else {
d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment);
d->savedFloatingMessage->setShowOverParent(true);
d->savedFloatingMessage->setIcon(icon);
connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage()));
d->floatingMessageCompressor.start();
}
}
}
bool KisView::canvasIsMirrored() const
{
return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored();
}
void KisView::setViewManager(KisViewManager *view)
{
d->viewManager = view;
KoToolManager::instance()->addController(&d->canvasController);
KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController);
dynamic_cast<KisShapeController*>(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas);
if (resourceProvider()) {
resourceProvider()->slotImageSizeChanged();
}
if (d->viewManager && d->viewManager->nodeManager()) {
d->viewManager->nodeManager()->nodesUpdated();
}
connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(slotImageSizeChanged(QPointF,QPointF)));
connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged()));
// executed in a context of an image thread
connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)),
SLOT(slotImageNodeAdded(KisNodeSP)),
Qt::DirectConnection);
// executed in a context of the gui thread
connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)),
SLOT(slotContinueAddNode(KisNodeSP)),
Qt::AutoConnection);
// executed in a context of an image thread
connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)),
SLOT(slotImageNodeRemoved(KisNodeSP)),
Qt::DirectConnection);
// executed in a context of the gui thread
connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)),
SLOT(slotContinueRemoveNode(KisNodeSP)),
Qt::AutoConnection);
d->viewManager->updateGUI();
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
KisViewManager* KisView::viewManager() const
{
return d->viewManager;
}
void KisView::slotImageNodeAdded(KisNodeSP node)
{
emit sigContinueAddNode(node);
}
void KisView::slotContinueAddNode(KisNodeSP newActiveNode)
{
/**
* When deleting the last layer, root node got selected. We should
* fix it when the first layer is added back.
*
* Here we basically reimplement what Qt's view/model do. But
* since they are not connected, we should do it manually.
*/
if (!d->isCurrent &&
(!d->currentNode || !d->currentNode->parent())) {
d->currentNode = newActiveNode;
}
}
void KisView::slotImageNodeRemoved(KisNodeSP node)
{
emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node));
}
void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode)
{
if (!d->isCurrent) {
d->currentNode = newActiveNode;
}
}
KoZoomController *KisView::zoomController() const
{
return d->zoomManager.zoomController();
}
KisZoomManager *KisView::zoomManager() const
{
return &d->zoomManager;
}
KisCanvasController *KisView::canvasController() const
{
return &d->canvasController;
}
KisCanvasResourceProvider *KisView::resourceProvider() const
{
if (d->viewManager) {
return d->viewManager->canvasResourceProvider();
}
return 0;
}
KisInputManager* KisView::globalInputManager() const
{
return d->viewManager ? d->viewManager->inputManager() : 0;
}
KisCanvas2 *KisView::canvasBase() const
{
return &d->canvas;
}
KisImageWSP KisView::image() const
{
if (d->document) {
return d->document->image();
}
return 0;
}
KisCoordinatesConverter *KisView::viewConverter() const
{
return &d->viewConverter;
}
void KisView::dragEnterEvent(QDragEnterEvent *event)
{
//qDebug() << "KisView::dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
if (event->mimeData()->hasImage()
|| event->mimeData()->hasUrls()
|| event->mimeData()->hasFormat("application/x-krita-node")) {
event->accept();
// activate view if it should accept the drop
this->setFocus();
} else {
event->ignore();
}
}
void KisView::dropEvent(QDropEvent *event)
{
KisImageWSP kisimage = image();
Q_ASSERT(kisimage);
QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint();
QRect imageBounds = kisimage->bounds();
QPoint pasteCenter;
bool forceRecenter;
if (event->keyboardModifiers() & Qt::ShiftModifier &&
imageBounds.contains(cursorPos)) {
pasteCenter = cursorPos;
forceRecenter = true;
} else {
pasteCenter = imageBounds.center();
forceRecenter = false;
}
//qDebug() << "KisView::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
if (event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasImage())
{
KisShapeController *kritaShapeController =
dynamic_cast<KisShapeController*>(d->document->shapeController());
QList<KisNodeSP> nodes =
KisMimeData::loadNodes(event->mimeData(), imageBounds,
pasteCenter, forceRecenter,
kisimage, kritaShapeController);
Q_FOREACH (KisNodeSP node, nodes) {
if (node) {
KisNodeCommandsAdapter adapter(viewManager());
if (!viewManager()->nodeManager()->activeLayer()) {
adapter.addNode(node, kisimage->rootLayer() , 0);
} else {
adapter.addNode(node,
viewManager()->nodeManager()->activeLayer()->parent(),
viewManager()->nodeManager()->activeLayer());
}
}
}
}
else if (event->mimeData()->hasUrls()) {
QList<QUrl> urls = event->mimeData()->urls();
if (urls.length() > 0) {
QMenu popup;
popup.setObjectName("drop_popup");
QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup);
QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup);
QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup);
QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup);
QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup);
QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup);
QAction *insertAsReferenceImage = new QAction(i18n("Insert as Reference Image"), &popup);
QAction *insertAsReferenceImages = new QAction(i18n("Insert as Reference Images"), &popup);
QAction *cancel = new QAction(i18n("Cancel"), &popup);
popup.addAction(insertAsNewLayer);
popup.addAction(insertAsNewFileLayer);
popup.addAction(openInNewDocument);
popup.addAction(insertAsReferenceImage);
popup.addAction(insertManyLayers);
popup.addAction(insertManyFileLayers);
popup.addAction(openManyDocuments);
popup.addAction(insertAsReferenceImages);
insertAsNewLayer->setEnabled(image() && urls.count() == 1);
insertAsNewFileLayer->setEnabled(image() && urls.count() == 1);
openInNewDocument->setEnabled(urls.count() == 1);
insertAsReferenceImage->setEnabled(image() && urls.count() == 1);
insertManyLayers->setEnabled(image() && urls.count() > 1);
insertManyFileLayers->setEnabled(image() && urls.count() > 1);
openManyDocuments->setEnabled(urls.count() > 1);
insertAsReferenceImages->setEnabled(image() && urls.count() > 1);
popup.addSeparator();
popup.addAction(cancel);
QAction *action = popup.exec(QCursor::pos());
if (action != 0 && action != cancel) {
QTemporaryFile *tmp = 0;
for (QUrl url : urls) {
if (!url.isLocalFile()) {
// download the file and substitute the url
KisRemoteFileFetcher fetcher;
tmp = new QTemporaryFile();
tmp->setAutoRemove(true);
if (!fetcher.fetchFile(url, tmp)) {
qWarning() << "Fetching" << url << "failed";
continue;
}
url = url.fromLocalFile(tmp->fileName());
}
if (url.isLocalFile()) {
if (action == insertAsNewLayer || action == insertManyLayers) {
d->viewManager->imageManager()->importImage(url);
activateWindow();
}
else if (action == insertAsNewFileLayer || action == insertManyFileLayers) {
KisNodeCommandsAdapter adapter(viewManager());
KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(),
KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8);
adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode());
}
else if (action == openInNewDocument || action == openManyDocuments) {
if (mainWindow()) {
mainWindow()->openDocument(url, KisMainWindow::None);
}
}
else if (action == insertAsReferenceImage || action == insertAsReferenceImages) {
auto *reference = KisReferenceImage::fromFile(url.toLocalFile(), d->viewConverter, this);
if (reference) {
reference->setPosition(d->viewConverter.imageToDocument(cursorPos));
d->referenceImagesDecoration->addReferenceImage(reference);
KoToolManager::instance()->switchToolRequested("ToolReferenceImages");
}
}
}
delete tmp;
tmp = 0;
}
}
}
}
}
void KisView::dragMoveEvent(QDragMoveEvent *event)
{
//qDebug() << "KisView::dragMoveEvent";
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
KisDocument *KisView::document() const
{
return d->document;
}
void KisView::setDocument(KisDocument *document)
{
d->document->disconnect(this);
d->document = document;
QStatusBar *sb = statusBar();
if (sb) { // No statusbar in e.g. konqueror
connect(d->document, SIGNAL(statusBarMessage(QString,int)),
this, SLOT(slotSavingStatusMessage(QString,int)));
connect(d->document, SIGNAL(clearStatusBarMessage()),
this, SLOT(slotClearStatusText()));
}
}
void KisView::setDocumentDeleted()
{
d->documentDeleted = true;
}
QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent)
{
Q_UNUSED(parent);
QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this);
printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage());
printDialog->setEnabledOptions(printJob->printDialogOptions());
return printDialog;
}
KisMainWindow * KisView::mainWindow() const
{
return dynamic_cast<KisMainWindow *>(window());
}
void KisView::setSubWindow(QMdiSubWindow *subWindow)
{
d->subWindow = subWindow;
}
QStatusBar * KisView::statusBar() const
{
KisMainWindow *mw = mainWindow();
return mw ? mw->statusBar() : 0;
}
void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving)
{
QStatusBar *sb = statusBar();
if (sb) {
sb->showMessage(text, timeout);
}
KisConfig cfg(true);
if (!sb || sb->isHidden() ||
(!isAutoSaving && cfg.forceShowSaveMessages()) ||
(cfg.forceShowAutosaveMessages() && isAutoSaving)) {
viewManager()->showFloatingMessage(text, QIcon());
}
}
void KisView::slotClearStatusText()
{
QStatusBar *sb = statusBar();
if (sb) {
sb->clearMessage();
}
}
QList<QAction*> KisView::createChangeUnitActions(bool addPixelUnit)
{
UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this);
return unitActions->actions();
}
void KisView::closeEvent(QCloseEvent *event)
{
// Check whether we're the last (user visible) view
int viewCount = KisPart::instance()->viewCount(document());
if (viewCount > 1 || !isVisible()) {
// there are others still, so don't bother the user
event->accept();
return;
}
if (queryClose()) {
event->accept();
return;
}
event->ignore();
}
bool KisView::queryClose()
{
if (!document())
return true;
document()->waitForSavingToComplete();
if (document()->isModified()) {
QString name;
if (document()->documentInfo()) {
name = document()->documentInfo()->aboutInfo("title");
}
if (name.isEmpty())
name = document()->url().fileName();
if (name.isEmpty())
name = i18n("Untitled");
int res = QMessageBox::warning(this,
i18nc("@title:window", "Krita"),
i18n("<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes : {
bool isNative = (document()->mimeType() == document()->nativeFormatMimeType());
if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false))
return false;
break;
}
case QMessageBox::No : {
KisImageSP image = document()->image();
image->requestStrokeCancellation();
viewManager()->blockUntilOperationsFinishedForced(image);
document()->removeAutoSaveFiles(document()->localFilePath(), document()->isRecovered());
document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
}
default : // case QMessageBox::Cancel :
return false;
}
}
return true;
}
void KisView::slotScreenChanged()
{
d->zoomManager.updateScreenResolution(this);
}
void KisView::slotThemeChanged(QPalette pal)
{
this->setPalette(pal);
for (int i=0; i<this->children().size();i++) {
QWidget *w = qobject_cast<QWidget*> ( this->children().at(i));
if (w) {
w->setPalette(pal);
}
}
if (canvasBase()) {
canvasBase()->canvasWidget()->setPalette(pal);
}
if (canvasController()) {
canvasController()->setPalette(pal);
}
}
void KisView::resetImageSizeAndScroll(bool changeCentering,
const QPointF &oldImageStillPoint,
const QPointF &newImageStillPoint)
{
const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter();
QPointF oldPreferredCenter = d->canvasController.preferredCenter();
/**
* Calculating the still point in old coordinates depending on the
* parameters given
*/
QPointF oldStillPoint;
if (changeCentering) {
oldStillPoint =
converter->imageToWidget(oldImageStillPoint) +
converter->documentOffset();
} else {
- QSize oldDocumentSize = d->canvasController.documentSize();
+ QSizeF oldDocumentSize = d->canvasController.documentSize();
oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height());
}
/**
* Updating the document size
*/
QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes());
KoZoomController *zc = d->zoomManager.zoomController();
zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom());
zc->setPageSize(size);
zc->setDocumentSize(size, true);
/**
* Calculating the still point in new coordinates depending on the
* parameters given
*/
QPointF newStillPoint;
if (changeCentering) {
newStillPoint =
converter->imageToWidget(newImageStillPoint) +
converter->documentOffset();
} else {
- QSize newDocumentSize = d->canvasController.documentSize();
+ QSizeF newDocumentSize = d->canvasController.documentSize();
newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height());
}
d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint);
}
void KisView::syncLastActiveNodeToDocument()
{
KisDocument *doc = document();
if (doc) {
doc->setPreActivatedNode(d->currentNode);
}
}
void KisView::saveViewState(KisPropertiesConfiguration &config) const
{
config.setProperty("file", d->document->url());
config.setProperty("window", mainWindow()->windowStateConfig().name());
if (d->subWindow) {
config.setProperty("geometry", d->subWindow->saveGeometry().toBase64());
}
config.setProperty("zoomMode", (int)zoomController()->zoomMode());
config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom());
d->canvasController.saveCanvasState(config);
}
void KisView::restoreViewState(const KisPropertiesConfiguration &config)
{
if (d->subWindow) {
QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1());
d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry));
}
qreal zoom = config.getFloat("zoom", 1.0f);
int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE);
d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom);
d->canvasController.restoreCanvasState(config);
}
void KisView::setCurrentNode(KisNodeSP node)
{
d->currentNode = node;
d->canvas.slotTrySwitchShapeManager();
syncLastActiveNodeToDocument();
}
KisNodeSP KisView::currentNode() const
{
return d->currentNode;
}
KisLayerSP KisView::currentLayer() const
{
KisNodeSP node;
KisMaskSP mask = currentMask();
if (mask) {
node = mask->parent();
}
else {
node = d->currentNode;
}
return qobject_cast<KisLayer*>(node.data());
}
KisMaskSP KisView::currentMask() const
{
return dynamic_cast<KisMask*>(d->currentNode.data());
}
KisSelectionSP KisView::selection()
{
KisLayerSP layer = currentLayer();
if (layer)
return layer->selection(); // falls through to the global
// selection, or 0 in the end
if (image()) {
return image()->globalSelection();
}
return 0;
}
void KisView::slotSoftProofing(bool softProofing)
{
d->softProofing = softProofing;
QString message;
if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
{
message = i18n("Soft Proofing doesn't work in floating point.");
viewManager()->showFloatingMessage(message,QIcon());
return;
}
if (softProofing){
message = i18n("Soft Proofing turned on.");
} else {
message = i18n("Soft Proofing turned off.");
}
viewManager()->showFloatingMessage(message,QIcon());
canvasBase()->slotSoftProofing(softProofing);
}
void KisView::slotGamutCheck(bool gamutCheck)
{
d->gamutCheck = gamutCheck;
QString message;
if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
{
message = i18n("Gamut Warnings don't work in floating point.");
viewManager()->showFloatingMessage(message,QIcon());
return;
}
if (gamutCheck){
message = i18n("Gamut Warnings turned on.");
if (!d->softProofing){
message += "\n "+i18n("But Soft Proofing is still off.");
}
} else {
message = i18n("Gamut Warnings turned off.");
}
viewManager()->showFloatingMessage(message,QIcon());
canvasBase()->slotGamutCheck(gamutCheck);
}
bool KisView::softProofing()
{
return d->softProofing;
}
bool KisView::gamutCheck()
{
return d->gamutCheck;
}
void KisView::slotLoadingFinished()
{
if (!document()) return;
/**
* Cold-start of image size/resolution signals
*/
slotImageResolutionChanged();
if (image()->locked()) {
// If this is the first view on the image, the image will have been locked
// so unlock it.
image()->blockSignals(false);
image()->unlock();
}
canvasBase()->initializeImage();
/**
* Dirty hack alert
*/
d->zoomManager.zoomController()->setAspectMode(true);
if (viewConverter()) {
viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE);
}
connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)));
connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*)));
connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF)));
KisNodeSP activeNode = document()->preActivatedNode();
if (!activeNode) {
activeNode = image()->rootLayer()->lastChild();
}
while (activeNode && !activeNode->inherits("KisLayer")) {
activeNode = activeNode->prevSibling();
}
setCurrentNode(activeNode);
connect(d->viewManager->mainWindow(), SIGNAL(screenChanged()), SLOT(slotScreenChanged()));
zoomManager()->updateImageBoundsSnapping();
}
void KisView::slotSavingFinished()
{
if (d->viewManager && d->viewManager->mainWindow()) {
d->viewManager->mainWindow()->updateCaption();
}
}
KisPrintJob * KisView::createPrintJob()
{
return new KisPrintJob(image());
}
void KisView::slotImageResolutionChanged()
{
resetImageSizeAndScroll(false);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGUI();
// update KoUnit value for the document
if (resourceProvider()) {
resourceProvider()->resourceManager()->
setResource(KoCanvasResourceProvider::Unit, d->canvas.unit());
}
}
void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
{
resetImageSizeAndScroll(true, oldStillPoint, newStillPoint);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGUI();
}
void KisView::closeView()
{
d->subWindow->close();
}
diff --git a/libs/ui/KisWelcomePageWidget.cpp b/libs/ui/KisWelcomePageWidget.cpp
index 8aa3bfb4c1..9bba1b17ce 100644
--- a/libs/ui/KisWelcomePageWidget.cpp
+++ b/libs/ui/KisWelcomePageWidget.cpp
@@ -1,337 +1,341 @@
/* This file is part of the KDE project
* Copyright (C) 2018 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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 "KisWelcomePageWidget.h"
#include <QDebug>
#include <QDesktopServices>
#include <QFileInfo>
#include <QMimeData>
#include "kis_action_manager.h"
#include "kactioncollection.h"
#include "kis_action.h"
#include "KConfigGroup"
#include "KSharedConfig"
#include <QListWidget>
#include <QListWidgetItem>
#include "kis_icon_utils.h"
#include "krita_utils.h"
#include "KoStore.h"
#include "kis_config.h"
KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
recentDocumentsListView->setDragEnabled(false);
recentDocumentsListView->viewport()->setAutoFillBackground(false);
recentDocumentsListView->setSpacing(2);
// set up URLs that go to web browser
- manualLink->setText(QString("<a href=\"https://docs.krita.org/\">").append(i18n("User Manual")).append("</a>"));
manualLink->setTextFormat(Qt::RichText);
manualLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
manualLink->setOpenExternalLinks(true);
- gettingStartedLink->setText(QString("<a href=\"https://docs.krita.org/en/user_manual/getting_started.html\">").append(i18n("Getting Started")).append("</a>"));
gettingStartedLink->setTextFormat(Qt::RichText);
gettingStartedLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
gettingStartedLink->setOpenExternalLinks(true);
- supportKritaLink->setText(QString("<a href=\"https://krita.org/en/support-us/donations/\">").append(i18n("Support Krita")).append("</a>"));
supportKritaLink->setTextFormat(Qt::RichText);
supportKritaLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
supportKritaLink->setOpenExternalLinks(true);
- userCommunityLink->setText(QString("<a href=\"https://forum.kde.org/viewforum.php?f=136\">").append(i18n("User Community")).append("</a>"));
userCommunityLink->setTextFormat(Qt::RichText);
userCommunityLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
userCommunityLink->setOpenExternalLinks(true);
- kritaWebsiteLink->setText(QString("<a href=\"https://www.krita.org\">").append(i18n("Krita Website")).append("</a>"));
+
kritaWebsiteLink->setTextFormat(Qt::RichText);
kritaWebsiteLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
kritaWebsiteLink->setOpenExternalLinks(true);
- sourceCodeLink->setText(QString("<a href=\"https://phabricator.kde.org/source/krita/\">").append(i18n("Source Code")).append("</a>"));
+
sourceCodeLink->setTextFormat(Qt::RichText);
sourceCodeLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
sourceCodeLink->setOpenExternalLinks(true);
- poweredByKDELink->setText(QString("<a href=\"https://userbase.kde.org/What_is_KDE\">").append(i18n("Powered by KDE")).append("</a>"));
poweredByKDELink->setTextFormat(Qt::RichText);
poweredByKDELink->setTextInteractionFlags(Qt::TextBrowserInteraction);
poweredByKDELink->setOpenExternalLinks(true);
kdeIcon->setIconSize(QSize(20, 20));
kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20));
connect(chkShowNews, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool)));
// configure the News area
KisConfig cfg(true);
bool m_getNews = cfg.readEntry<bool>("FetchNews", false);
chkShowNews->setChecked(m_getNews);
setAcceptDrops(true);
}
KisWelcomePageWidget::~KisWelcomePageWidget()
{
}
void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin)
{
if (mainWin) {
m_mainWindow = mainWin;
// set the shortcut links from actions (only if a shortcut exists)
if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") {
newFileLinkShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() + QString(")"));
}
if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() != "") {
openFileShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() + QString(")"));
}
connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex)));
// we need the view manager to actually call actions, so don't create the connections
// until after the view manager is set
connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked()));
connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked()));
connect(clearRecentFilesLink, SIGNAL(clicked(bool)), this, SLOT(slotClearRecentFiles()));
slotUpdateThemeColors();
}
}
void KisWelcomePageWidget::showDropAreaIndicator(bool show)
{
if (!show) {
QString dropFrameStyle = "QFrame#dropAreaIndicator { border: 0px }";
dropFrameBorder->setStyleSheet(dropFrameStyle);
} else {
QColor textColor = qApp->palette().color(QPalette::Text);
QColor backgroundColor = qApp->palette().color(QPalette::Background);
QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8);
// QColor.name() turns it into a hex/web format
QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ;
dropFrameBorder->setStyleSheet(dropFrameStyle);
}
}
void KisWelcomePageWidget::slotUpdateThemeColors()
{
QColor textColor = qApp->palette().color(QPalette::Text);
QColor backgroundColor = qApp->palette().color(QPalette::Background);
// make the welcome screen labels a subtle color so it doesn't clash with the main UI elements
QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8);
QString blendedStyle = QString("color: ").append(blendedColor.name());
// what labels to change the color...
startTitleLabel->setStyleSheet(blendedStyle);
recentDocumentsLabel->setStyleSheet(blendedStyle);
helpTitleLabel->setStyleSheet(blendedStyle);
- manualLink->setStyleSheet(blendedStyle);
- gettingStartedLink->setStyleSheet(blendedStyle);
- supportKritaLink->setStyleSheet(blendedStyle);
- userCommunityLink->setStyleSheet(blendedStyle);
- kritaWebsiteLink->setStyleSheet(blendedStyle);
- sourceCodeLink->setStyleSheet(blendedStyle);
newFileLinkShortcut->setStyleSheet(blendedStyle);
openFileShortcut->setStyleSheet(blendedStyle);
clearRecentFilesLink->setStyleSheet(blendedStyle);
- poweredByKDELink->setStyleSheet(blendedStyle);
recentDocumentsListView->setStyleSheet(blendedStyle);
newFileLink->setStyleSheet(blendedStyle);
openFileLink->setStyleSheet(blendedStyle);
// giving the drag area messaging a dotted border
QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";");
dragImageHereLabel->setStyleSheet(dottedBorderStyle);
// make drop area QFrame have a dotted line
dropFrameBorder->setObjectName("dropAreaIndicator");
QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}");
dropFrameBorder->setStyleSheet(dropFrameStyle);
// only show drop area when we have a document over the empty area
showDropAreaIndicator(false);
// add icons for new and open settings to make them stand out a bit more
openFileLink->setIconSize(QSize(30, 30));
newFileLink->setIconSize(QSize(30, 30));
openFileLink->setIcon(KisIconUtils::loadIcon("document-open"));
newFileLink->setIcon(KisIconUtils::loadIcon("document-new"));
+
+
+ kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20));
+
+ // HTML links seem to be a bit more stubborn with theme changes... setting inline styles to help with color change
+ userCommunityLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://forum.kde.org/viewforum.php?f=136\">").append(i18n("User Community")).append("</a>"));
+ gettingStartedLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org/en/user_manual/getting_started.html\">").append(i18n("Getting Started")).append("</a>"));
+ manualLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://docs.krita.org/\">").append(i18n("User Manual")).append("</a>"));
+ supportKritaLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://krita.org/en/support-us/donations/\">").append(i18n("Support Krita")).append("</a>"));
+ kritaWebsiteLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://www.krita.org\">").append(i18n("Krita Website")).append("</a>"));
+ sourceCodeLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://phabricator.kde.org/source/krita/\">").append(i18n("Source Code")).append("</a>"));
+ poweredByKDELink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://userbase.kde.org/What_is_KDE\">").append(i18n("Powered by KDE")).append("</a>"));
+
+ // re-populate recent files since they might have themed icons
+ populateRecentDocuments();
+
}
void KisWelcomePageWidget::populateRecentDocuments()
{
m_recentFilesModel.clear(); // clear existing data before it gets re-populated
// grab recent files data
int numRecentFiles = m_mainWindow->recentFilesUrls().length() > 5 ? 5 : m_mainWindow->recentFilesUrls().length(); // grab at most 5
for (int i = 0; i < numRecentFiles; i++ ) {
QStandardItem *recentItem = new QStandardItem(1,2); // 1 row, 1 column
recentItem->setIcon(KisIconUtils::loadIcon("document-export"));
QString recentFileUrlPath = m_mainWindow->recentFilesUrls().at(i).toLocalFile();
QString fileName = recentFileUrlPath.split("/").last();
if (m_thumbnailMap.contains(recentFileUrlPath)) {
recentItem->setIcon(m_thumbnailMap[recentFileUrlPath]);
}
else {
if (QFileInfo(recentFileUrlPath).exists()) {
if (recentFileUrlPath.toLower().endsWith("ora") || recentFileUrlPath.toLower().endsWith("kra")) {
QScopedPointer<KoStore> store(KoStore::createStore(recentFileUrlPath, KoStore::Read));
if (store) {
QString thumbnailpath;
if (store->hasFile(QString("Thumbnails/thumbnail.png"))){
thumbnailpath = QString("Thumbnails/thumbnail.png");
} else if (store->hasFile(QString("preview.png"))) {
thumbnailpath = QString("preview.png");
}
if (!thumbnailpath.isEmpty()) {
if (store->open(thumbnailpath)) {
QByteArray bytes = store->read(store->size());
store->close();
QImage img;
img.loadFromData(bytes);
img.setDevicePixelRatio(devicePixelRatioF());
recentItem->setIcon(QIcon(QPixmap::fromImage(img)));
}
}
}
}
else {
QImage img;
img.setDevicePixelRatio(devicePixelRatioF());
img.load(recentFileUrlPath);
if (!img.isNull()) {
recentItem->setIcon(QIcon(QPixmap::fromImage(img.scaledToWidth(48))));
}
}
m_thumbnailMap[recentFileUrlPath] = recentItem->icon();
}
}
// set the recent object with the data
recentItem->setText(fileName); // what to display for the item
recentItem->setToolTip(recentFileUrlPath);
m_recentFilesModel.appendRow(recentItem);
}
// hide clear and Recent files title if there are none
bool hasRecentFiles = m_mainWindow->recentFilesUrls().length() > 0;
recentDocumentsLabel->setVisible(hasRecentFiles);
clearRecentFilesLink->setVisible(hasRecentFiles);
recentDocumentsListView->setIconSize(QSize(48, 48));
recentDocumentsListView->setModel(&m_recentFilesModel);
}
void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event)
{
//qDebug() << "dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
showDropAreaIndicator(true);
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisWelcomePageWidget::dropEvent(QDropEvent *event)
{
//qDebug() << "KisWelcomePageWidget::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
showDropAreaIndicator(false);
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
if (url.toLocalFile().endsWith(".bundle")) {
bool r = m_mainWindow->installBundle(url.toLocalFile());
if (!r) {
qWarning() << "Could not install bundle" << url.toLocalFile();
}
}
else {
m_mainWindow->openDocument(url, KisMainWindow::None);
}
}
}
}
void KisWelcomePageWidget::dragMoveEvent(QDragMoveEvent *event)
{
//qDebug() << "dragMoveEvent";
m_mainWindow->dragMoveEvent(event);
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisWelcomePageWidget::dragLeaveEvent(QDragLeaveEvent */*event*/)
{
//qDebug() << "dragLeaveEvent";
showDropAreaIndicator(false);
m_mainWindow->dragLeave();
}
void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index)
{
QString fileUrl = index.data(Qt::ToolTipRole).toString();
m_mainWindow->openDocument(QUrl::fromLocalFile(fileUrl), KisMainWindow::None );
}
void KisWelcomePageWidget::slotNewFileClicked()
{
m_mainWindow->slotFileNew();
}
void KisWelcomePageWidget::slotOpenFileClicked()
{
m_mainWindow->slotFileOpen();
}
void KisWelcomePageWidget::slotClearRecentFiles()
{
m_mainWindow->clearRecentFiles();
populateRecentDocuments();
}
diff --git a/libs/ui/actions/KisPasteActionFactory.h b/libs/ui/actions/KisNoParameterActionFactory.h
similarity index 64%
copy from libs/ui/actions/KisPasteActionFactory.h
copy to libs/ui/actions/KisNoParameterActionFactory.h
index bb58608071..791fcf4cad 100644
--- a/libs/ui/actions/KisPasteActionFactory.h
+++ b/libs/ui/actions/KisNoParameterActionFactory.h
@@ -1,35 +1,35 @@
/*
- * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 KISPASTEACTIONFACTORY_H
-#define KISPASTEACTIONFACTORY_H
+#ifndef __KIS_NOPARAMETERACTIONFACTORY_H
+#define __KIS_NOPARAMETERACTIONFACTORY_H
#include "operations/kis_operation.h"
-#include "operations/kis_operation_configuration.h"
-
-struct KRITAUI_EXPORT KisPasteActionFactory : public KisOperation {
- KisPasteActionFactory() : KisOperation("paste-ui-action") {}
+class KRITAUI_EXPORT KisNoParameterActionFactory : public KisOperation
+{
+public:
+ KisNoParameterActionFactory(const QString &id) : KisOperation(id) {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) override {
- run(config.getBool("paste-at-cursor-position", false), view);
+ Q_UNUSED(config);
+ run(view);
}
-
- void run(bool pasteAtCursorPosition, KisViewManager *view);
+ virtual void run(KisViewManager *view) = 0;
};
-#endif // KISPASTEACTIONFACTORY_H
+#endif //__KIS_NOPARAMETERACTIONFACTORY_H
diff --git a/libs/ui/actions/KisPasteActionFactory.cpp b/libs/ui/actions/KisPasteActionFactories.cpp
similarity index 84%
rename from libs/ui/actions/KisPasteActionFactory.cpp
rename to libs/ui/actions/KisPasteActionFactories.cpp
index 826de4e18a..71b3725ff3 100644
--- a/libs/ui/actions/KisPasteActionFactory.cpp
+++ b/libs/ui/actions/KisPasteActionFactories.cpp
@@ -1,260 +1,308 @@
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KisPasteActionFactory.h"
-
-#include <QApplication>
+#include "KisPasteActionFactories.h"
#include "kis_config.h"
#include "kis_image.h"
#include "KisViewManager.h"
#include "kis_tool_proxy.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
+#include "kis_group_layer.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_shape_layer.h"
#include "kis_import_catcher.h"
#include "kis_clipboard.h"
#include "kis_selection.h"
#include "commands/kis_selection_commands.h"
#include "commands/kis_image_layer_add_command.h"
#include "KisTransformToolActivationCommand.h"
#include "kis_processing_applicator.h"
+#include <KoDocumentInfo.h>
#include <KoSvgPaste.h>
#include <KoShapeController.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoSelectedShapesProxy.h>
#include "kis_algebra_2d.h"
#include <KoShapeMoveCommand.h>
#include <KoShapeReorderCommand.h>
#include "kis_time_range.h"
#include "kis_keyframe_channel.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_painter.h"
+#include <KisPart.h>
+#include <KisDocument.h>
+#include <KisReferenceImagesLayer.h>
namespace {
QPointF getFittingOffset(QList<KoShape*> shapes,
const QPointF &shapesOffset,
const QRectF &documentRect,
const qreal fitRatio)
{
QPointF accumulatedFitOffset;
Q_FOREACH (KoShape *shape, shapes) {
const QRectF bounds = shape->boundingRect();
const QPointF center = bounds.center() + shapesOffset;
const qreal wMargin = (0.5 - fitRatio) * bounds.width();
const qreal hMargin = (0.5 - fitRatio) * bounds.height();
const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin);
const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect);
accumulatedFitOffset += fittedCenter - center;
}
return accumulatedFitOffset;
}
bool tryPasteShapes(bool pasteAtCursorPosition, KisViewManager *view)
{
bool result = false;
KoSvgPaste paste;
if (paste.hasShapes()) {
KoCanvasBase *canvas = view->canvasBase();
QSizeF fragmentSize;
QList<KoShape*> shapes =
paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
canvas->shapeController()->pixelsPerInch(), &fragmentSize);
if (!shapes.isEmpty()) {
KoShapeManager *shapeManager = canvas->shapeManager();
shapeManager->selection()->deselectAll();
// adjust z-index of the shapes so that they would be
// pasted on the top of the stack
QList<KoShape*> topLevelShapes = shapeManager->topLevelShapes();
auto it = std::max_element(topLevelShapes.constBegin(), topLevelShapes.constEnd(), KoShape::compareShapeZIndex);
if (it != topLevelShapes.constEnd()) {
const int zIndexOffset = (*it)->zIndex();
std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QList<KoShapeReorderCommand::IndexedShape> indexedShapes;
std::transform(shapes.constBegin(), shapes.constEnd(),
std::back_inserter(indexedShapes),
[zIndexOffset] (KoShape *shape) {
KoShapeReorderCommand::IndexedShape indexedShape(shape);
indexedShape.zIndex += zIndexOffset;
return indexedShape;
});
indexedShapes = KoShapeReorderCommand::homogenizeZIndexesLazy(indexedShapes);
KoShapeReorderCommand cmd(indexedShapes);
cmd.redo();
}
KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes"));
canvas->shapeController()->addShapesDirect(shapes, 0, parentCommand);
QPointF finalShapesOffset;
if (pasteAtCursorPosition) {
QRectF boundingRect = KoShape::boundingRect(shapes);
const QPointF cursorPos = canvas->canvasController()->currentCursorPosition();
finalShapesOffset = cursorPos - boundingRect.center();
} else {
bool foundOverlapping = false;
QRectF boundingRect = KoShape::boundingRect(shapes);
const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height());
QPointF offset;
Q_FOREACH (KoShape *shape, shapes) {
QRectF br1 = shape->boundingRect();
bool hasOverlappingShape = false;
do {
hasOverlappingShape = false;
// we cannot use shapesAt() here, because the groups are not
// handled in the shape manager's tree
QList<KoShape*> conflicts = shapeManager->shapes();
Q_FOREACH (KoShape *intersectedShape, conflicts) {
if (intersectedShape == shape) continue;
QRectF br2 = intersectedShape->boundingRect();
const qreal tolerance = 2.0; /* pt */
if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) {
br1.translate(offsetStep.x(), offsetStep.y());
offset += offsetStep;
hasOverlappingShape = true;
foundOverlapping = true;
break;
}
}
} while (hasOverlappingShape);
if (foundOverlapping) break;
}
if (foundOverlapping) {
finalShapesOffset = offset;
}
}
const QRectF documentRect = canvas->shapeController()->documentRect();
finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1);
if (!finalShapesOffset.isNull()) {
new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand);
}
canvas->addCommand(parentCommand);
Q_FOREACH (KoShape *shape, shapes) {
canvas->selectedShapesProxy()->selection()->select(shape);
}
result = true;
}
}
return result;
}
}
void KisPasteActionFactory::run(bool pasteAtCursorPosition, KisViewManager *view)
{
KisImageSP image = view->image();
if (!image) return;
if (tryPasteShapes(pasteAtCursorPosition, view)) {
return;
}
KisTimeRange range;
const QRect fittingBounds = pasteAtCursorPosition ? QRect() : image->bounds();
KisPaintDeviceSP clip = KisClipboard::instance()->clip(fittingBounds, true, &range);
if (clip) {
if (pasteAtCursorPosition) {
const QPointF docPos = view->canvasBase()->canvasController()->currentCursorPosition();
const QPointF imagePos = view->canvasBase()->coordinatesConverter()->documentToImage(docPos);
const QPointF offset = (imagePos - QRectF(clip->exactBounds()).center()).toPoint();
clip->setX(clip->x() + offset.x());
clip->setY(clip->y() + offset.y());
}
KisImportCatcher::adaptClipToImageColorSpace(clip, image);
KisPaintLayerSP newLayer = new KisPaintLayer(image.data(),
image->nextLayerName() + i18n("(pasted)"),
OPACITY_OPAQUE_U8);
KisNodeSP aboveNode = view->activeLayer();
KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root();
if (range.isValid()) {
newLayer->enableAnimation();
KisKeyframeChannel *channel = newLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
KisRasterKeyframeChannel *rasterChannel = dynamic_cast<KisRasterKeyframeChannel*>(channel);
rasterChannel->importFrame(range.start(), clip, 0);
if (!range.isInfinite()) {
rasterChannel->addKeyframe(range.end() + 1, 0);
}
} else {
const QRect rc = clip->extent();
KisPainter::copyAreaOptimized(rc.topLeft(), clip, newLayer->paintDevice(), rc);
}
KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode);
KisProcessingApplicator *ap = beginAction(view, cmd->text());
ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
if (KisConfig(true).activateTransformToolAfterPaste()) {
KUndo2Command *deselectCmd = new KisDeselectActiveSelectionCommand(view->selection(), image);
ap->applyCommand(deselectCmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
KUndo2Command *transformToolCmd = new KisTransformToolActivationCommand(view);
ap->applyCommand(transformToolCmd, KisStrokeJobData::BARRIER, KisStrokeJobData::NORMAL);
}
endAction(ap, KisOperationConfiguration(id()).toXML());
} else {
// XXX: "Add saving of XML data for Paste of shapes"
view->canvasBase()->toolProxy()->paste();
}
}
+
+void KisPasteNewActionFactory::run(KisViewManager *viewManager)
+{
+ Q_UNUSED(viewManager);
+
+ KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true);
+ if (!clip) return;
+
+ QRect rect = clip->exactBounds();
+ if (rect.isEmpty()) return;
+
+ KisDocument *doc = KisPart::instance()->createDocument();
+ doc->documentInfo()->setAboutInfo("title", i18n("Untitled"));
+ KisImageSP image = new KisImage(doc->createUndoStore(),
+ rect.width(),
+ rect.height(),
+ clip->colorSpace(),
+ i18n("Pasted"));
+ KisPaintLayerSP layer =
+ new KisPaintLayer(image.data(), image->nextLayerName() + " " + i18n("(pasted)"),
+ OPACITY_OPAQUE_U8, clip->colorSpace());
+
+ KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), rect);
+
+ image->addNode(layer.data(), image->rootLayer());
+ doc->setCurrentImage(image);
+ KisPart::instance()->addDocument(doc);
+
+ KisMainWindow *win = viewManager->mainWindow();
+ win->addViewAndNotifyLoadingCompleted(doc);
+}
+
+void KisPasteReferenceActionFactory::run(KisViewManager *viewManager)
+{
+ KisCanvas2 *canvasBase = viewManager->canvasBase();
+ if (!canvasBase) return;
+
+ KisReferenceImage* reference = KisReferenceImage::fromClipboard(*canvasBase->coordinatesConverter());
+ if (!reference) return;
+
+ KisDocument *doc = viewManager->document();
+ doc->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, {reference}));
+
+ KoToolManager::instance()->switchToolRequested("ToolReferenceImages");
+}
diff --git a/libs/ui/actions/KisPasteActionFactory.h b/libs/ui/actions/KisPasteActionFactories.h
similarity index 73%
rename from libs/ui/actions/KisPasteActionFactory.h
rename to libs/ui/actions/KisPasteActionFactories.h
index bb58608071..b27f8b9e80 100644
--- a/libs/ui/actions/KisPasteActionFactory.h
+++ b/libs/ui/actions/KisPasteActionFactories.h
@@ -1,35 +1,46 @@
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 KISPASTEACTIONFACTORY_H
#define KISPASTEACTIONFACTORY_H
+#include "KisNoParameterActionFactory.h"
#include "operations/kis_operation.h"
#include "operations/kis_operation_configuration.h"
struct KRITAUI_EXPORT KisPasteActionFactory : public KisOperation {
KisPasteActionFactory() : KisOperation("paste-ui-action") {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) override {
run(config.getBool("paste-at-cursor-position", false), view);
}
void run(bool pasteAtCursorPosition, KisViewManager *view);
};
+struct KRITAUI_EXPORT KisPasteNewActionFactory : public KisNoParameterActionFactory {
+ KisPasteNewActionFactory() : KisNoParameterActionFactory("paste-new-ui-action") {}
+ void run(KisViewManager *view) override;
+};
+
+struct KRITAUI_EXPORT KisPasteReferenceActionFactory : public KisNoParameterActionFactory {
+ KisPasteReferenceActionFactory() : KisNoParameterActionFactory("paste-reference-ui-action") {}
+ void run(KisViewManager *view) override;
+};
+
#endif // KISPASTEACTIONFACTORY_H
diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp
index 61e4c8358a..8ce74f93e3 100644
--- a/libs/ui/actions/kis_selection_action_factories.cpp
+++ b/libs/ui/actions/kis_selection_action_factories.cpp
@@ -1,651 +1,620 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_selection_action_factories.h"
#include <QMimeData>
#include <klocalizedstring.h>
#include <kundo2command.h>
#include <KisMainWindow.h>
#include <KisDocument.h>
#include <KisPart.h>
#include <KoPathShape.h>
#include <KoShapeController.h>
#include <KoShapeRegistry.h>
#include <KoCompositeOpRegistry.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeStroke.h>
#include <KoDocumentInfo.h>
#include "KisViewManager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_pixel_selection.h"
#include "kis_paint_layer.h"
#include "kis_image.h"
#include "kis_image_barrier_locker.h"
#include "kis_fill_painter.h"
#include "kis_transaction.h"
#include "kis_iterator_ng.h"
#include "kis_processing_applicator.h"
#include "kis_group_layer.h"
#include "commands/kis_selection_commands.h"
#include "commands/kis_image_layer_add_command.h"
#include "kis_tool_proxy.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_selection_manager.h"
#include "commands_new/kis_transaction_based_command.h"
#include "kis_selection_filters.h"
#include "kis_shape_selection.h"
#include "kis_shape_layer.h"
#include <kis_shape_controller.h>
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_keyframe_channel.h"
#include <processing/fill_processing_visitor.h>
#include <kis_selection_tool_helper.h>
#include "kis_figure_painting_tool_helper.h"
#include "kis_update_outline_job.h"
namespace ActionHelper {
void copyFromDevice(KisViewManager *view,
KisPaintDeviceSP device,
bool makeSharpClip = false,
const KisTimeRange &range = KisTimeRange())
{
KisImageWSP image = view->image();
if (!image) return;
KisSelectionSP selection = view->selection();
QRect rc = (selection) ? selection->selectedExactRect() : image->bounds();
KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace());
Q_CHECK_PTR(clip);
const KoColorSpace *cs = clip->colorSpace();
// TODO if the source is linked... copy from all linked layers?!?
// Copy image data
KisPainter::copyAreaOptimized(QPoint(), device, clip, rc);
if (selection) {
// Apply selection mask.
KisPaintDeviceSP selectionProjection = selection->projection();
KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width());
KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
const KoColorSpace *selCs = selection->projection()->colorSpace();
for (qint32 y = 0; y < rc.height(); y++) {
for (qint32 x = 0; x < rc.width(); x++) {
/**
* Sharp method is an exact reverse of COMPOSITE_OVER
* so if you cover the cut/copied piece over its source
* you get an exactly the same image without any seams
*/
if (makeSharpClip) {
qreal dstAlpha = cs->opacityF(layerIt->rawData());
qreal sel = selCs->opacityF(selectionIt->oldRawData());
qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha);
float mask = newAlpha / dstAlpha;
cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1);
} else {
cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1);
}
layerIt->nextPixel();
selectionIt->nextPixel();
}
layerIt->nextRow();
selectionIt->nextRow();
}
}
KisClipboard::instance()->setClip(clip, rc.topLeft(), range);
}
}
void KisSelectAllActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All"));
if (!image->globalSelection()) {
ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
struct SelectAll : public KisTransactionBasedCommand {
SelectAll(KisImageSP image) : m_image(image) {}
KisImageSP m_image;
KUndo2Command* paint() override {
KisSelectionSP selection = m_image->globalSelection();
KisSelectionTransaction transaction(selection->pixelSelection());
selection->pixelSelection()->clear();
selection->pixelSelection()->select(m_image->bounds());
return transaction.endAndTake();
}
};
ap->applyCommand(new SelectAll(image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisDeselectActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
KUndo2Command *cmd = new KisDeselectActiveSelectionCommand(view->selection(), image);
KisProcessingApplicator *ap = beginAction(view, cmd->text());
ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisReselectActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
KUndo2Command *cmd = new KisReselectActiveSelectionCommand(view->activeNode(), image);
KisProcessingApplicator *ap = beginAction(view, cmd->text());
ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view)
{
KisNodeSP node = view->activeNode();
if (!node || !node->hasEditablePaintDevice()) return;
KisSelectionSP selection = view->selection();
QRect selectedRect = selection ?
selection->selectedRect() : view->image()->bounds();
Q_UNUSED(selectedRect);
KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice();
Q_UNUSED(filled);
bool usePattern = false;
bool useBgColor = false;
if (fillSource.contains("pattern")) {
usePattern = true;
} else if (fillSource.contains("bg")) {
useBgColor = true;
}
KisProcessingApplicator applicator(view->image(), node,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Flood Fill Layer"));
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(view->image(), node, view->canvasResourceProvider()->resourceManager());
if (!fillSource.contains("opacity")) {
resources->setOpacity(1.0);
}
KisProcessingVisitorSP visitor =
new FillProcessingVisitor(QPoint(0, 0), // start position
selection,
resources,
false, // fast mode
usePattern,
true, // fill only selection,
0, // feathering radius
0, // sizemod
80, // threshold,
false, // unmerged
useBgColor);
applicator.applyVisitor(visitor,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.end();
view->canvasResourceProvider()->slotPainting();
}
void KisClearActionFactory::run(KisViewManager *view)
{
// XXX: "Add saving of XML data for Clear action"
view->canvasBase()->toolProxy()->deleteSelection();
}
void KisImageResizeToSelectionActionFactory::run(KisViewManager *view)
{
// XXX: "Add saving of XML data for Image Resize To Selection action"
KisSelectionSP selection = view->selection();
if (!selection) return;
view->image()->cropImage(selection->selectedExactRect());
}
void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view)
{
KisImageSP image = view->image();
if (!image) return;
bool haveShapesSelected = view->selectionManager()->haveShapesSelected();
if (haveShapesSelected) {
// XXX: "Add saving of XML data for Cut/Copy of shapes"
KisImageBarrierLocker locker(image);
if (willCut) {
view->canvasBase()->toolProxy()->cut();
} else {
view->canvasBase()->toolProxy()->copy();
}
} else {
KisNodeSP node = view->activeNode();
if (!node) return;
KisSelectionSP selection = view->selection();
if (selection.isNull()) return;
{
KisImageBarrierLocker locker(image);
KisPaintDeviceSP dev = node->paintDevice();
if (!dev) {
dev = node->projection();
}
if (!dev) {
view->showFloatingMessage(
i18nc("floating message when cannot copy from a node",
"Cannot copy pixels from this type of layer "),
QIcon(), 3000, KisFloatingMessage::Medium);
return;
}
if (dev->exactBounds().isEmpty()) {
view->showFloatingMessage(
i18nc("floating message when copying empty selection",
"Selection is empty: no pixels were copied "),
QIcon(), 3000, KisFloatingMessage::Medium);
return;
}
KisTimeRange range;
KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id());
if (channel) {
const int currentTime = image->animationInterface()->currentTime();
range = channel->affectedFrames(currentTime);
}
ActionHelper::copyFromDevice(view, dev, makeSharpClip, range);
}
KUndo2Command *command = 0;
if (willCut && node->hasEditablePaintDevice()) {
struct ClearSelection : public KisTransactionBasedCommand {
ClearSelection(KisNodeSP node, KisSelectionSP sel)
: m_node(node), m_sel(sel) {}
KisNodeSP m_node;
KisSelectionSP m_sel;
KUndo2Command* paint() override {
KisSelectionSP cutSelection = m_sel;
// Shrinking the cutting area was previously used
// for getting seamless cut-paste. Now we use makeSharpClip
// instead.
// QRect originalRect = cutSelection->selectedExactRect();
// static const int preciseSelectionThreshold = 16;
//
// if (originalRect.width() > preciseSelectionThreshold ||
// originalRect.height() > preciseSelectionThreshold) {
// cutSelection = new KisSelection(*m_sel);
// delete cutSelection->flatten();
//
// KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false);
//
// QRect processingRect = filter->changeRect(originalRect);
// filter->process(cutSelection->pixelSelection(), processingRect);
// }
KisTransaction transaction(m_node->paintDevice());
m_node->paintDevice()->clearSelection(cutSelection);
m_node->setDirty(cutSelection->selectedRect());
return transaction.endAndTake();
}
};
command = new ClearSelection(node, selection);
}
KUndo2MagicString actionName = willCut ?
kundo2_i18n("Cut") :
kundo2_i18n("Copy");
KisProcessingApplicator *ap = beginAction(view, actionName);
if (command) {
ap->applyCommand(command,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
KisOperationConfiguration config(id());
config.setProperty("will-cut", willCut);
endAction(ap, config.toXML());
}
}
void KisCopyMergedActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
if (!view->blockUntilOperationsFinished(image)) return;
image->barrierLock();
KisPaintDeviceSP dev = image->root()->projection();
ActionHelper::copyFromDevice(view, dev);
image->unlock();
KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged"));
endAction(ap, KisOperationConfiguration(id()).toXML());
}
-void KisPasteNewActionFactory::run(KisViewManager *viewManager)
-{
- Q_UNUSED(viewManager);
-
- KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true);
- if (!clip) return;
-
- QRect rect = clip->exactBounds();
- if (rect.isEmpty()) return;
-
- KisDocument *doc = KisPart::instance()->createDocument();
- doc->documentInfo()->setAboutInfo("title", i18n("Untitled"));
- KisImageSP image = new KisImage(doc->createUndoStore(),
- rect.width(),
- rect.height(),
- clip->colorSpace(),
- i18n("Pasted"));
- KisPaintLayerSP layer =
- new KisPaintLayer(image.data(), image->nextLayerName() + " " + i18n("(pasted)"),
- OPACITY_OPAQUE_U8, clip->colorSpace());
-
- KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), rect);
-
- image->addNode(layer.data(), image->rootLayer());
- doc->setCurrentImage(image);
- KisPart::instance()->addDocument(doc);
-
- KisMainWindow *win = viewManager->mainWindow();
- win->addViewAndNotifyLoadingCompleted(doc);
-}
-
void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config)
{
KisSelectionFilter* filter = new KisInvertSelectionFilter();
runFilter(filter, view, config);
}
void KisSelectionToVectorActionFactory::run(KisViewManager *view)
{
KisSelectionSP selection = view->selection();
if (selection->hasShapeSelection()) {
view->showFloatingMessage(i18nc("floating message",
"Selection is already in a vector format "),
QIcon(), 2000, KisFloatingMessage::Low);
return;
}
if (!selection->outlineCacheValid()) {
view->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, false, Qt::transparent));
if (!view->blockUntilOperationsFinished(view->image())) {
return;
}
}
QPainterPath selectionOutline = selection->outlineCache();
QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform();
KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline));
shape->setShapeId(KoPathShapeId);
/**
* Mark a shape that it belongs to a shape selection
*/
if(!shape->userData()) {
shape->setUserData(new KisShapeSelectionMarker);
}
KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection"));
ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisSelectionToRasterActionFactory::run(KisViewManager *view)
{
KisSelectionSP selection = view->selection();
if (!selection->hasShapeSelection()) {
view->showFloatingMessage(i18nc("floating message",
"Selection is already in a raster format "),
QIcon(), 2000, KisFloatingMessage::Low);
return;
}
KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection"));
struct RasterizeSelection : public KisTransactionBasedCommand {
RasterizeSelection(KisSelectionSP sel)
: m_sel(sel) {}
KisSelectionSP m_sel;
KUndo2Command* paint() override {
// just create an empty transaction: it will rasterize the
// selection and emit the necessary signals
KisTransaction transaction(m_sel->pixelSelection());
return transaction.endAndTake();
}
};
ap->applyCommand(new RasterizeSelection(selection),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view)
{
const QList<KoShape*> originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes();
bool hasSelectionShapes = false;
QList<KoShape*> clonedShapes;
Q_FOREACH (KoShape *shape, originalShapes) {
if (dynamic_cast<KisShapeSelectionMarker*>(shape->userData())) {
hasSelectionShapes = true;
continue;
}
clonedShapes << shape->cloneShape();
}
if (clonedShapes.isEmpty()) {
if (hasSelectionShapes) {
view->showFloatingMessage(i18nc("floating message",
"The shape already belongs to a selection"),
QIcon(), 2000, KisFloatingMessage::Low);
}
return;
}
KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection"));
helper.addSelectionShapes(clonedShapes);
}
void KisSelectionToShapeActionFactory::run(KisViewManager *view)
{
KisSelectionSP selection = view->selection();
if (!selection->outlineCacheValid()) {
return;
}
QPainterPath selectionOutline = selection->outlineCache();
QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform();
KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline));
shape->setShapeId(KoPathShapeId);
KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>();
KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor()));
shape->setStroke(border);
view->document()->shapeController()->addShape(shape);
}
void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params)
{
KisImageWSP image = view->image();
if (!image) {
return;
}
KisSelectionSP selection = view->selection();
if (!selection) {
return;
}
int size = params.lineSize;
KisPixelSelectionSP pixelSelection = selection->projection();
if (!pixelSelection->outlineCacheValid()) {
pixelSelection->recalculateOutlineCache();
}
QPainterPath outline = pixelSelection->outlineCache();
QColor color = params.color.toQColor();
KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice()) {
KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager();
KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush;
KisPainter::FillStyle fillStyle = params.fillStyle();
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"),
image,
currentNode,
rManager ,
strokeStyle,
fillStyle);
helper.setFGColorOverride(params.color);
helper.setSelectionOverride(0);
QPen pen(Qt::red, size);
pen.setJoinStyle(Qt::RoundJoin);
if (fillStyle != KisPainter::FillStyleNone) {
helper.paintPainterPathQPenFill(outline, pen, params.fillColor);
}
else {
helper.paintPainterPathQPen(outline, pen, params.fillColor);
}
}
else if (currentNode->inherits("KisShapeLayer")) {
QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform();
KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline));
shape->setShapeId(KoPathShapeId);
KoShapeStrokeSP border(new KoShapeStroke(size, color));
shape->setStroke(border);
view->document()->shapeController()->addShape(shape);
}
image->setModified();
}
void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params)
{
KisImageWSP image = view->image();
if (!image) {
return;
}
KisSelectionSP selection = view->selection();
if (!selection) {
return;
}
KisPixelSelectionSP pixelSelection = selection->projection();
if (!pixelSelection->outlineCacheValid()) {
pixelSelection->recalculateOutlineCache();
}
KisNodeSP currentNode = view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
if (!currentNode->inherits("KisShapeLayer") && currentNode->paintDevice())
{
KoCanvasResourceProvider * rManager = view->canvasResourceProvider()->resourceManager();
QPainterPath outline = pixelSelection->outlineCache();
KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush;
KisPainter::FillStyle fillStyle = KisPainter::FillStyleNone;
KoColor color = params.color;
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"),
image,
currentNode,
rManager ,
strokeStyle,
fillStyle);
helper.setFGColorOverride(color);
helper.setSelectionOverride(0);
helper.paintPainterPath(outline);
image->setModified();
}
}
diff --git a/libs/ui/actions/kis_selection_action_factories.h b/libs/ui/actions/kis_selection_action_factories.h
index 8fe9e3d39b..bdd7085084 100644
--- a/libs/ui/actions/kis_selection_action_factories.h
+++ b/libs/ui/actions/kis_selection_action_factories.h
@@ -1,134 +1,120 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_SELECTION_ACTION_FACTORIES_H
#define __KIS_SELECTION_ACTION_FACTORIES_H
+#include "KisNoParameterActionFactory.h"
#include "operations/kis_operation.h"
#include "operations/kis_operation_configuration.h"
#include "operations/kis_filter_selection_operation.h"
#include "dialogs/kis_dlg_stroke_selection_properties.h"
-class KRITAUI_EXPORT KisNoParameterActionFactory : public KisOperation
-{
-public:
- KisNoParameterActionFactory(const QString &id) : KisOperation(id) {}
- void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) override {
- Q_UNUSED(config);
- run(view);
- }
- virtual void run(KisViewManager *view) = 0;
-};
struct KRITAUI_EXPORT KisSelectAllActionFactory : public KisNoParameterActionFactory {
KisSelectAllActionFactory() : KisNoParameterActionFactory("select-all-ui-action") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisDeselectActionFactory : public KisNoParameterActionFactory {
KisDeselectActionFactory() : KisNoParameterActionFactory("deselect-ui-action") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisReselectActionFactory : public KisNoParameterActionFactory {
KisReselectActionFactory() : KisNoParameterActionFactory("reselect-ui-action") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisFillActionFactory : public KisOperation
{
KisFillActionFactory() : KisOperation("fill-ui-action") {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) override {
run(config.getString("fill-source", "fg"), view);
}
/**
* \p fillColor may be one of three variants:
* - "fg" --- foreground color
* - "bg" --- background color
* - "pattern" --- current pattern
*/
void run(const QString &fillSource, KisViewManager *view);
};
struct KRITAUI_EXPORT KisClearActionFactory : public KisNoParameterActionFactory {
KisClearActionFactory() : KisNoParameterActionFactory("clear-ui-action") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisImageResizeToSelectionActionFactory : public KisNoParameterActionFactory {
KisImageResizeToSelectionActionFactory() : KisNoParameterActionFactory("resize-to-selection-ui-action") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisCutCopyActionFactory : public KisOperation {
KisCutCopyActionFactory() : KisOperation("cut-copy-ui-action") {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) override {
run(config.getBool("will-cut", false), config.getBool("use-sharp-clip", false), view);
}
void run(bool willCut, bool makeSharpClip, KisViewManager *view);
};
struct KRITAUI_EXPORT KisCopyMergedActionFactory : public KisNoParameterActionFactory {
KisCopyMergedActionFactory() : KisNoParameterActionFactory("copy-merged-ui-action") {}
void run(KisViewManager *view) override;
};
-struct KRITAUI_EXPORT KisPasteNewActionFactory : public KisNoParameterActionFactory {
- KisPasteNewActionFactory() : KisNoParameterActionFactory("paste-new-ui-action") {}
- void run(KisViewManager *view) override;
-};
-
struct KisInvertSelectionOperation : public KisFilterSelectionOperation {
KisInvertSelectionOperation() : KisFilterSelectionOperation("invertselection") {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) override;
};
struct KRITAUI_EXPORT KisSelectionToVectorActionFactory : public KisNoParameterActionFactory {
KisSelectionToVectorActionFactory() : KisNoParameterActionFactory("selection-to-vector") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisSelectionToRasterActionFactory : public KisNoParameterActionFactory {
KisSelectionToRasterActionFactory() : KisNoParameterActionFactory("selection-to-raster") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisShapesToVectorSelectionActionFactory : public KisNoParameterActionFactory {
KisShapesToVectorSelectionActionFactory() : KisNoParameterActionFactory("shapes-to-vector-selection") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisSelectionToShapeActionFactory : public KisNoParameterActionFactory {
KisSelectionToShapeActionFactory() : KisNoParameterActionFactory("selection-to-shape-action") {}
void run(KisViewManager *view) override;
};
struct KRITAUI_EXPORT KisStrokeSelectionActionFactory : public KisOperation {
KisStrokeSelectionActionFactory() : KisOperation("selection-to-shape-action") {}
void run(KisViewManager *view, StrokeSelectionOptions params);
};
struct KRITAUI_EXPORT KisStrokeBrushSelectionActionFactory : public KisOperation {
KisStrokeBrushSelectionActionFactory() : KisOperation("selection-to-shape-action") {}
void run(KisViewManager *view, StrokeSelectionOptions params);
};
#endif /* __KIS_SELECTION_ACTION_FACTORIES_H */
diff --git a/libs/ui/canvas/KisMirrorAxisConfig.cpp b/libs/ui/canvas/KisMirrorAxisConfig.cpp
index 2a316f9ee5..c99e74f133 100644
--- a/libs/ui/canvas/KisMirrorAxisConfig.cpp
+++ b/libs/ui/canvas/KisMirrorAxisConfig.cpp
@@ -1,250 +1,251 @@
/*
* Copyright (c) 2019 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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_dom_utils.h>
#include <QPointF>
#include "KisMirrorAxisConfig.h"
class Q_DECL_HIDDEN KisMirrorAxisConfig::Private
{
public:
Private()
: mirrorHorizontal(false)
, mirrorVertical(false)
, lockHorizontal(false)
, lockVertical(false)
, hideVerticalDecoration(false)
, hideHorizontalDecoration(false)
, handleSize(32.f)
, horizontalHandlePosition(64.f)
, verticalHandlePosition(64.f)
, axisPosition(QPointF(0.f,0.f))
{}
bool operator==(const Private& rhs) {
return mirrorHorizontal == rhs.mirrorHorizontal &&
mirrorVertical == rhs.mirrorVertical &&
lockHorizontal == rhs.lockHorizontal &&
lockVertical == rhs.lockVertical &&
hideHorizontalDecoration == rhs.hideHorizontalDecoration &&
hideVerticalDecoration == rhs.hideVerticalDecoration &&
handleSize == rhs.handleSize &&
horizontalHandlePosition == rhs.horizontalHandlePosition &&
verticalHandlePosition == rhs.verticalHandlePosition &&
axisPosition == rhs.axisPosition;
}
bool mirrorHorizontal;
bool mirrorVertical;
bool lockHorizontal;
bool lockVertical;
bool hideVerticalDecoration;
bool hideHorizontalDecoration;
float handleSize;
float horizontalHandlePosition;
float verticalHandlePosition;
QPointF axisPosition;
};
KisMirrorAxisConfig::KisMirrorAxisConfig()
: QObject()
, d(new Private())
{
}
KisMirrorAxisConfig::~KisMirrorAxisConfig()
{
}
KisMirrorAxisConfig::KisMirrorAxisConfig(const KisMirrorAxisConfig &rhs)
: QObject()
, d(new Private(*rhs.d))
{
}
KisMirrorAxisConfig &KisMirrorAxisConfig::operator=(const KisMirrorAxisConfig &rhs)
{
if (&rhs != this) {
*d = *rhs.d;
}
return *this;
}
bool KisMirrorAxisConfig::operator==(const KisMirrorAxisConfig &rhs) const
{
KIS_ASSERT(d);
KIS_ASSERT(rhs.d);
return *d == *rhs.d;
}
bool KisMirrorAxisConfig::mirrorHorizontal()
{
return d->mirrorHorizontal;
}
void KisMirrorAxisConfig::setMirrorHorizontal(bool state)
{
d->mirrorHorizontal = state;
}
bool KisMirrorAxisConfig::mirrorVertical()
{
return d->mirrorVertical;
}
void KisMirrorAxisConfig::setMirrorVertical(bool state)
{
d->mirrorVertical = state;
}
bool KisMirrorAxisConfig::lockHorizontal()
{
return d->lockHorizontal;
}
void KisMirrorAxisConfig::setLockHorizontal(bool state)
{
d->lockHorizontal = state;
}
bool KisMirrorAxisConfig::lockVertical()
{
return d->lockVertical;
}
void KisMirrorAxisConfig::setLockVertical(bool state)
{
d->lockVertical = state;
}
bool KisMirrorAxisConfig::hideVerticalDecoration()
{
return d->hideVerticalDecoration;
}
void KisMirrorAxisConfig::setHideVerticalDecoration(bool state)
{
d->hideVerticalDecoration = state;
}
bool KisMirrorAxisConfig::hideHorizontalDecoration()
{
return d->hideHorizontalDecoration;
}
void KisMirrorAxisConfig::setHideHorizontalDecoration(bool state)
{
d->hideHorizontalDecoration = state;
}
float KisMirrorAxisConfig::handleSize()
{
return d->handleSize;
}
void KisMirrorAxisConfig::setHandleSize(float size)
{
d->handleSize = size;
}
float KisMirrorAxisConfig::horizontalHandlePosition()
{
return d->horizontalHandlePosition;
}
void KisMirrorAxisConfig::setHorizontalHandlePosition(float position)
{
d->horizontalHandlePosition = position;
}
float KisMirrorAxisConfig::verticalHandlePosition()
{
return d->verticalHandlePosition;
}
void KisMirrorAxisConfig::setVerticalHandlePosition(float position)
{
d->verticalHandlePosition = position;
}
QPointF KisMirrorAxisConfig::axisPosition()
{
return d->axisPosition;
}
void KisMirrorAxisConfig::setAxisPosition(QPointF position)
{
d->axisPosition = position;
}
QDomElement KisMirrorAxisConfig::saveToXml(QDomDocument &doc, const QString &tag) const
{
QDomElement mirrorAxisElement = doc.createElement(tag);
KisDomUtils::saveValue(&mirrorAxisElement, "mirrorHorizontal", d->mirrorHorizontal);
KisDomUtils::saveValue(&mirrorAxisElement, "mirrorVertical", d->mirrorVertical);
KisDomUtils::saveValue(&mirrorAxisElement, "lockHorizontal", d->lockHorizontal);
KisDomUtils::saveValue(&mirrorAxisElement, "lockVertical", d->lockVertical);
KisDomUtils::saveValue(&mirrorAxisElement, "hideHorizontalDecoration", d->hideHorizontalDecoration);
KisDomUtils::saveValue(&mirrorAxisElement, "hideVerticalDecoration", d->hideVerticalDecoration);
KisDomUtils::saveValue(&mirrorAxisElement, "handleSize", d->handleSize);
KisDomUtils::saveValue(&mirrorAxisElement, "horizontalHandlePosition", d->horizontalHandlePosition);
KisDomUtils::saveValue(&mirrorAxisElement, "verticalHandlePosition", d->verticalHandlePosition);
KisDomUtils::saveValue(&mirrorAxisElement, "axisPosition", d->axisPosition);
return mirrorAxisElement;
}
bool KisMirrorAxisConfig::loadFromXml(const QDomElement &parent)
{
bool result = true;
result &= KisDomUtils::loadValue(parent, "mirrorHorizontal", &d->mirrorHorizontal);
result &= KisDomUtils::loadValue(parent, "mirrorVertical", &d->mirrorVertical);
result &= KisDomUtils::loadValue(parent, "lockHorizontal", &d->lockHorizontal);
result &= KisDomUtils::loadValue(parent, "lockVertical", &d->lockVertical);
result &= KisDomUtils::loadValue(parent, "hideHorizontalDecoration", &d->hideHorizontalDecoration);
result &= KisDomUtils::loadValue(parent, "hideVerticalDecoration", &d->hideVerticalDecoration);
result &= KisDomUtils::loadValue(parent, "handleSize", &d->handleSize);
result &= KisDomUtils::loadValue(parent, "horizontalHandlePosition", &d->horizontalHandlePosition);
result &= KisDomUtils::loadValue(parent, "verticalHandlePosition", &d->verticalHandlePosition);
result &= KisDomUtils::loadValue(parent, "axisPosition", &d->axisPosition);
return result;
}
bool KisMirrorAxisConfig::isDefault() const
{
KisMirrorAxisConfig defaultConfig;
return *this == defaultConfig;
}
diff --git a/libs/ui/canvas/KisMirrorAxisConfig.h b/libs/ui/canvas/KisMirrorAxisConfig.h
index d2421297c3..53cb3ab075 100644
--- a/libs/ui/canvas/KisMirrorAxisConfig.h
+++ b/libs/ui/canvas/KisMirrorAxisConfig.h
@@ -1,103 +1,104 @@
/*
* Copyright (c) 2019 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 KISMIRRORAXISCONFIG_H
#define KISMIRRORAXISCONFIG_H
#include <QScopedPointer>
#include "kritaui_export.h"
#include <boost/operators.hpp>
class QDomElement;
class QDomDocument;
/**
* @brief The KisMirrorAxisConfig class stores configuration for the KisMirrorAxis
* canvas decoration. Contents are saved to/loaded from KRA documents.
*/
class KRITAUI_EXPORT KisMirrorAxisConfig : public QObject, boost::equality_comparable<KisMirrorAxisConfig>
{
Q_OBJECT
public:
KisMirrorAxisConfig();
~KisMirrorAxisConfig();
KisMirrorAxisConfig(const KisMirrorAxisConfig &rhs);
KisMirrorAxisConfig& operator=(const KisMirrorAxisConfig& rhs);
bool operator==(const KisMirrorAxisConfig& rhs) const;
bool mirrorHorizontal();
void setMirrorHorizontal(bool state);
bool mirrorVertical();
void setMirrorVertical(bool state);
bool lockHorizontal();
void setLockHorizontal(bool state);
bool lockVertical();
void setLockVertical(bool state);
bool hideVerticalDecoration();
void setHideVerticalDecoration(bool state);
bool hideHorizontalDecoration();
void setHideHorizontalDecoration(bool state);
float handleSize();
void setHandleSize(float size);
float horizontalHandlePosition();
void setHorizontalHandlePosition(float position);
float verticalHandlePosition();
void setVerticalHandlePosition(float position);
QPointF axisPosition();
void setAxisPosition(QPointF position);
/**
* @brief saveToXml() function for KisKraSaver
* @param doc
* @param tag
* @return
*/
QDomElement saveToXml(QDomDocument& doc, const QString &tag) const;
/**
* @brief loadFromXml() function for KisKraLoader
* @param parent element
* @return
*/
bool loadFromXml(const QDomElement &parent);
/**
* @brief Check whether the config object was changed, or is the class default.
* @return true, if the object is default; false, if the config was changed
*/
bool isDefault() const;
private:
class Private;
const QScopedPointer<Private> d;
};
#endif // KISMIRRORAXISCONFIG_H
diff --git a/libs/ui/canvas/KisSnapPixelStrategy.cpp b/libs/ui/canvas/KisSnapPixelStrategy.cpp
index d924e19962..cdb79db7a6 100644
--- a/libs/ui/canvas/KisSnapPixelStrategy.cpp
+++ b/libs/ui/canvas/KisSnapPixelStrategy.cpp
@@ -1,56 +1,57 @@
/*
* Copyright (c) 2019 Kuntal Majumder <hellozee@disroot.org>
*
* 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.
+ * 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 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 "KisSnapPixelStrategy.h"
#include <QPainterPath>
#include "kis_global.h"
#include "kis_canvas2.h"
#include "KoSnapProxy.h"
KisSnapPixelStrategy::KisSnapPixelStrategy(KoSnapGuide::Strategy type):
KoSnapStrategy(type)
{
}
KisSnapPixelStrategy::~KisSnapPixelStrategy()
{
}
bool KisSnapPixelStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
{
Q_UNUSED(maxSnapDistance);
KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(proxy->canvas());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas2, false);
const QPointF imagePos = canvas2->coordinatesConverter()->documentToImage(mousePosition);
const QPointF alignedDocPoint = canvas2->coordinatesConverter()->imageToDocument(imagePos.toPoint());
setSnappedPosition(alignedDocPoint);
return true;
}
QPainterPath KisSnapPixelStrategy::decoration(const KoViewConverter &converter) const
{
QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
QPainterPath decoration;
decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0));
decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0));
decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height()));
decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height()));
return decoration;
}
diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp
index 3bf44fcbdf..f246978d85 100644
--- a/libs/ui/canvas/kis_canvas2.cpp
+++ b/libs/ui/canvas/kis_canvas2.cpp
@@ -1,1274 +1,1293 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) Lukáš Tvrdý <lukast.dev@gmail.com>, (C) 2010
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* 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_canvas2.h"
#include <functional>
#include <numeric>
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QTime>
#include <QLabel>
#include <QMouseEvent>
#include <QDesktopWidget>
+#include <QScreen>
+#include <QWindow>
#include <kis_debug.h>
#include <KoUnit.h>
#include <KoShapeManager.h>
#include <KisSelectedShapesProxy.h>
#include <KoColorProfile.h>
#include <KoCanvasControllerWidget.h>
#include <KisDocument.h>
#include <KoSelection.h>
#include <KoShapeController.h>
#include <kis_lod_transform.h>
#include "kis_tool_proxy.h"
#include "kis_coordinates_converter.h"
#include "kis_prescaled_projection.h"
#include "kis_image.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_adapter.h"
#include "flake/kis_shape_layer.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_abstract_canvas_widget.h"
#include "kis_qpainter_canvas.h"
#include "kis_group_layer.h"
#include "flake/kis_shape_controller.h"
#include "kis_node_manager.h"
#include "kis_selection.h"
#include "kis_selection_component.h"
#include "flake/kis_shape_selection.h"
#include "kis_selection_mask.h"
#include "kis_image_config.h"
#include "kis_infinity_manager.h"
#include "kis_signal_compressor.h"
#include "kis_display_color_converter.h"
#include "kis_exposure_gamma_correction_interface.h"
#include "KisView.h"
#include "kis_canvas_controller.h"
#include "kis_grid_config.h"
#include "kis_animation_player.h"
#include "kis_animation_frame_cache.h"
#include "opengl/kis_opengl_canvas2.h"
#include "opengl/kis_opengl.h"
#include "kis_fps_decoration.h"
#include "KoColorConversionTransformation.h"
#include "KisProofingConfiguration.h"
#include <kis_favorite_resource_manager.h>
#include <kis_popup_palette.h>
#include "input/kis_input_manager.h"
#include "kis_painting_assistants_decoration.h"
#include "kis_canvas_updates_compressor.h"
#include "KoZoomController.h"
#include <KisStrokeSpeedMonitor.h>
#include "opengl/kis_opengl_canvas_debugger.h"
#include "kis_algebra_2d.h"
#include "kis_image_signal_router.h"
#include "KisSnapPixelStrategy.h"
class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private
{
public:
KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer<KisView> view, KoCanvasResourceProvider* resourceManager)
: coordinatesConverter(coordConverter)
, view(view)
, shapeManager(parent)
, selectedShapesProxy(&shapeManager)
, toolProxy(parent)
, displayColorConverter(resourceManager, view)
, regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
{
}
KisCoordinatesConverter *coordinatesConverter;
QPointer<KisView>view;
KisAbstractCanvasWidget *canvasWidget = 0;
KoShapeManager shapeManager;
KisSelectedShapesProxy selectedShapesProxy;
bool currentCanvasIsOpenGL;
int openGLFilterMode;
KisToolProxy toolProxy;
KisPrescaledProjectionSP prescaledProjection;
bool vastScrolling;
KisSignalCompressor canvasUpdateCompressor;
QRect savedUpdateRect;
QBitArray channelFlags;
KisProofingConfigurationSP proofingConfig;
bool softProofing = false;
bool gamutCheck = false;
bool proofingConfigUpdated = false;
KisPopupPalette *popupPalette = 0;
KisDisplayColorConverter displayColorConverter;
KisCanvasUpdatesCompressor projectionUpdatesCompressor;
KisAnimationPlayer *animationPlayer;
KisAnimationFrameCacheSP frameCache;
bool lodAllowedInImage = false;
bool bootstrapLodBlocked;
QPointer<KoShapeManager> currentlyActiveShapeManager;
KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup;
KisSignalCompressor frameRenderStartCompressor;
KisSignalCompressor regionOfInterestUpdateCompressor;
QRect regionOfInterest;
QRect renderingLimit;
int isBatchUpdateActive = 0;
bool effectiveLodAllowedInImage() {
return lodAllowedInImage && !bootstrapLodBlocked;
}
void setActiveShapeManager(KoShapeManager *shapeManager);
};
namespace {
KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node)
{
KoShapeManager *shapeManager = 0;
KisSelectionSP selection;
if (KisLayer *layer = dynamic_cast<KisLayer*>(node.data())) {
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(layer);
if (shapeLayer) {
shapeManager = shapeLayer->shapeManager();
}
} else if (KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(node.data())) {
selection = mask->selection();
}
if (!shapeManager && selection && selection->hasShapeSelection()) {
KisShapeSelection *shapeSelection = dynamic_cast<KisShapeSelection*>(selection->shapeSelection());
KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0);
shapeManager = shapeSelection->shapeManager();
}
return shapeManager;
}
}
KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceProvider *resourceManager, KisView *view, KoShapeControllerBase *sc)
: KoCanvasBase(sc, resourceManager)
, m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager))
{
/**
* While loading LoD should be blocked. Only when GUI has finished
* loading and zoom level settled down, LoD is given a green
* light.
*/
m_d->bootstrapLodBlocked = true;
connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished()));
connect(view->mainWindow(), SIGNAL(screenChanged()), SLOT(slotConfigChanged()));
KisImageConfig config(false);
m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit());
m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit());
m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
snapGuide()->overrideSnapStrategy(KoSnapGuide::PixelSnapping, new KisSnapPixelStrategy());
}
void KisCanvas2::setup()
{
// a bit of duplication from slotConfigChanged()
KisConfig cfg(true);
m_d->vastScrolling = cfg.vastScrolling();
m_d->lodAllowedInImage = cfg.levelOfDetailEnabled();
createCanvas(cfg.useOpenGL());
setLodAllowedInCanvas(m_d->lodAllowedInImage);
m_d->animationPlayer = new KisAnimationPlayer(this);
connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
/**
* We switch the shape manager every time vector layer or
* shape selection is activated. Flake does not expect this
* and connects all the signals of the global shape manager
* to the clients in the constructor. To workaround this we
* forward the signals of local shape managers stored in the
* vector layers to the signals of global shape manager. So the
* sequence of signal deliveries is the following:
*
* shapeLayer.m_d.canvas.m_shapeManager.selection() ->
* shapeLayer ->
* shapeController ->
* globalShapeManager.selection()
*/
KisShapeController *kritaShapeController = static_cast<KisShapeController*>(shapeController()->documentBase());
connect(kritaShapeController, SIGNAL(selectionChanged()),
this, SLOT(slotSelectionChanged()));
connect(kritaShapeController, SIGNAL(selectionContentChanged()),
selectedShapesProxy(), SIGNAL(selectionContentChanged()));
connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)),
selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)));
connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate()));
connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start()));
connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection()));
connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32)));
connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest()));
connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged()));
initializeFpsDecoration();
}
void KisCanvas2::initializeFpsDecoration()
{
KisConfig cfg(true);
const bool shouldShowDebugOverlay =
(canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) ||
cfg.enableBrushSpeedLogging();
if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) {
addDecoration(new KisFpsDecoration(imageView()));
if (cfg.enableBrushSpeedLogging()) {
connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
}
} else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) {
m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag);
disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
}
}
KisCanvas2::~KisCanvas2()
{
if (m_d->animationPlayer->isPlaying()) {
m_d->animationPlayer->forcedStopOnExit();
}
delete m_d;
}
void KisCanvas2::setCanvasWidget(KisAbstractCanvasWidget *widget)
{
if (m_d->popupPalette) {
m_d->popupPalette->setParent(widget->widget());
}
if (m_d->canvasWidget != 0) {
widget->setDecorations(m_d->canvasWidget->decorations());
// Redundant check for the constructor case, see below
if(viewManager() != 0)
viewManager()->inputManager()->removeTrackedCanvas(this);
}
m_d->canvasWidget = widget;
// Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView
// constructor, so the view manager still doesn't exists.
if(m_d->canvasWidget != 0 && viewManager() != 0)
viewManager()->inputManager()->addTrackedCanvas(this);
if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) {
KisInfinityManager *manager = new KisInfinityManager(m_d->view, this);
manager->setVisible(true);
m_d->canvasWidget->addDecoration(manager);
}
widget->widget()->setAutoFillBackground(false);
widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent);
widget->widget()->setMouseTracking(true);
widget->widget()->setAcceptDrops(true);
KoCanvasControllerWidget *controller = dynamic_cast<KoCanvasControllerWidget*>(canvasController());
if (controller && controller->canvas() == this) {
controller->changeCanvasWidget(widget->widget());
}
}
bool KisCanvas2::canvasIsOpenGL() const
{
return m_d->currentCanvasIsOpenGL;
}
KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const
{
return KisOpenGL::FilterMode(m_d->openGLFilterMode);
}
void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const
{
QTransform transform = coordinatesConverter()->imageToDocumentTransform();
const QPoint intSpacing = m_d->view->document()->gridConfig().spacing();
const QPoint intOffset = m_d->view->document()->gridConfig().offset();
QPointF size = transform.map(QPointF(intSpacing));
spacing->rwidth() = size.x();
spacing->rheight() = size.y();
*offset = transform.map(QPointF(intOffset));
}
bool KisCanvas2::snapToGrid() const
{
return m_d->view->document()->gridConfig().snapToGrid();
}
qreal KisCanvas2::rotationAngle() const
{
return m_d->coordinatesConverter->rotationAngle();
}
bool KisCanvas2::xAxisMirrored() const
{
return m_d->coordinatesConverter->xAxisMirrored();
}
bool KisCanvas2::yAxisMirrored() const
{
return m_d->coordinatesConverter->yAxisMirrored();
}
void KisCanvas2::channelSelectionChanged()
{
KisImageSP image = this->image();
m_d->channelFlags = image->rootLayer()->channelFlags();
m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags);
startUpdateInPatches(image->bounds());
image->unlock();
}
void KisCanvas2::addCommand(KUndo2Command *command)
{
// This method exists to support flake-related operations
m_d->view->document()->addCommand(command);
}
void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager)
{
if (shapeManager != currentlyActiveShapeManager) {
currentlyActiveShapeManager = shapeManager;
selectedShapesProxy.setShapeManager(shapeManager);
}
}
KoShapeManager* KisCanvas2::shapeManager() const
{
KoShapeManager *localShapeManager = this->localShapeManager();
// sanity check for consistency of the local shape manager
KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) {
localShapeManager = globalShapeManager();
}
return localShapeManager ? localShapeManager : globalShapeManager();
}
KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const
{
return &m_d->selectedShapesProxy;
}
KoShapeManager* KisCanvas2::globalShapeManager() const
{
return &m_d->shapeManager;
}
KoShapeManager *KisCanvas2::localShapeManager() const
{
KisNodeSP node = m_d->view->currentNode();
KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node);
if (localShapeManager != m_d->currentlyActiveShapeManager) {
m_d->setActiveShapeManager(localShapeManager);
}
return localShapeManager;
}
void KisCanvas2::updateInputMethodInfo()
{
// TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget...
}
const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const
{
return m_d->coordinatesConverter;
}
KoViewConverter* KisCanvas2::viewConverter() const
{
return m_d->coordinatesConverter;
}
KisInputManager* KisCanvas2::globalInputManager() const
{
return m_d->view->globalInputManager();
}
KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const
{
return m_d->inputActionGroupsMask;
}
void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask)
{
m_d->inputActionGroupsMask = mask;
}
QWidget* KisCanvas2::canvasWidget()
{
return m_d->canvasWidget->widget();
}
const QWidget* KisCanvas2::canvasWidget() const
{
return m_d->canvasWidget->widget();
}
KoUnit KisCanvas2::unit() const
{
KoUnit unit(KoUnit::Pixel);
KisImageWSP image = m_d->view->image();
if (image) {
if (!qFuzzyCompare(image->xRes(), image->yRes())) {
warnKrita << "WARNING: resolution of the image is anisotropic"
<< ppVar(image->xRes())
<< ppVar(image->yRes());
}
const qreal resolution = image->xRes();
unit.setFactor(resolution);
}
return unit;
}
KoToolProxy * KisCanvas2::toolProxy() const
{
return &m_d->toolProxy;
}
void KisCanvas2::createQPainterCanvas()
{
m_d->currentCanvasIsOpenGL = false;
KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view);
m_d->prescaledProjection = new KisPrescaledProjection();
m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter);
m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(),
m_d->displayColorConverter.renderingIntent(),
m_d->displayColorConverter.conversionFlags());
m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter());
canvasWidget->setPrescaledProjection(m_d->prescaledProjection);
setCanvasWidget(canvasWidget);
}
void KisCanvas2::createOpenGLCanvas()
{
KisConfig cfg(true);
m_d->openGLFilterMode = cfg.openGLFilteringMode();
m_d->currentCanvasIsOpenGL = true;
KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter);
m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures());
setCanvasWidget(canvasWidget);
}
void KisCanvas2::createCanvas(bool useOpenGL)
{
// deinitialize previous canvas structures
m_d->prescaledProjection = 0;
m_d->frameCache = 0;
KisConfig cfg(true);
QDesktopWidget dw;
const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView()));
m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL && KisOpenGL::hasOpenGL());
m_d->displayColorConverter.setMonitorProfile(profile);
if (useOpenGL && !KisOpenGL::hasOpenGL()) {
warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n";
useOpenGL = false;
}
m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL);
if (useOpenGL) {
createOpenGLCanvas();
if (cfg.canvasState() == "OPENGL_FAILED") {
// Creating the opengl canvas failed, fall back
warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter.";
m_d->displayColorConverter.notifyOpenGLCanvasIsActive(false);
createQPainterCanvas();
}
} else {
createQPainterCanvas();
}
if (m_d->popupPalette) {
m_d->popupPalette->setParent(m_d->canvasWidget->widget());
}
}
void KisCanvas2::initializeImage()
{
KisImageSP image = m_d->view->image();
m_d->displayColorConverter.setImageColorSpace(image->colorSpace());
m_d->coordinatesConverter->setImage(image);
m_d->toolProxy.initializeImage(image);
connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection);
connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateStarted()), SLOT(slotBeginUpdatesBatch()), Qt::DirectConnection);
connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateEnded()), SLOT(slotEndUpdatesBatch()), Qt::DirectConnection);
connect(image->signalRouter(), SIGNAL(sigRequestLodPlanesSyncBlocked(bool)), SLOT(slotSetLodUpdatesBlocked(bool)), Qt::DirectConnection);
connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig()));
connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(startResizingImage()), Qt::DirectConnection);
connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager()));
connect(image, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged()));
connect(image, SIGNAL(sigProfileChanged(const KoColorProfile*)), SLOT(slotImageColorSpaceChanged()));
connectCurrentCanvas();
}
void KisCanvas2::connectCurrentCanvas()
{
KisImageWSP image = m_d->view->image();
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->setImage(image);
}
startResizingImage();
setLodAllowedInCanvas(m_d->lodAllowedInImage);
emit sigCanvasEngineChanged();
}
void KisCanvas2::resetCanvas(bool useOpenGL)
{
// we cannot reset the canvas before it's created, but this method might be called,
// for instance when setting the monitor profile.
if (!m_d->canvasWidget) {
return;
}
KisConfig cfg(true);
bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) ||
(m_d->currentCanvasIsOpenGL &&
m_d->openGLFilterMode != cfg.openGLFilteringMode());
if (needReset) {
createCanvas(useOpenGL);
connectCurrentCanvas();
notifyZoomChanged();
}
updateCanvasWidgetImpl();
}
void KisCanvas2::startUpdateInPatches(const QRect &imageRect)
{
/**
* We don't do patched loading for openGL canvas, becasue it loads
* the tiles, which are bascially "patches". Therefore, big chunks
* of memory are never allocated.
*/
if (m_d->currentCanvasIsOpenGL) {
startUpdateCanvasProjection(imageRect);
} else {
KisImageConfig imageConfig(true);
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < imageRect.height(); y += patchHeight) {
for (int x = 0; x < imageRect.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
startUpdateCanvasProjection(patchRect);
}
}
}
}
void KisCanvas2::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
{
m_d->displayColorConverter.setDisplayFilter(displayFilter);
KisImageSP image = this->image();
m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->setDisplayFilter(displayFilter);
image->unlock();
}
QSharedPointer<KisDisplayFilter> KisCanvas2::displayFilter() const
{
return m_d->displayColorConverter.displayFilter();
}
void KisCanvas2::slotImageColorSpaceChanged()
{
KisImageSP image = this->image();
m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
m_d->displayColorConverter.setImageColorSpace(image->colorSpace());
image->barrierLock();
m_d->canvasWidget->notifyImageColorSpaceChanged(image->colorSpace());
image->unlock();
}
KisDisplayColorConverter* KisCanvas2::displayColorConverter() const
{
return &m_d->displayColorConverter;
}
KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const
{
QSharedPointer<KisDisplayFilter> displayFilter = m_d->displayColorConverter.displayFilter();
return displayFilter ?
displayFilter->correctionInterface() :
KisDumbExposureGammaCorrectionInterface::instance();
}
void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck)
{
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
KisImageConfig cfg(false);
m_d->proofingConfig = cfg.defaultProofingconfiguration();
}
KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags;
#if QT_VERSION >= 0x050700
if (this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof);
if (softProof) {
conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck);
}
}
#else
if (this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::SoftProofing;
} else {
conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing;
}
if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::GamutCheck;
} else {
conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck;
}
#endif
m_d->proofingConfig->conversionFlags = conversionFlags;
m_d->proofingConfigUpdated = true;
startUpdateInPatches(this->image()->bounds());
}
void KisCanvas2::slotSoftProofing(bool softProofing)
{
m_d->softProofing = softProofing;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotGamutCheck(bool gamutCheck)
{
m_d->gamutCheck = gamutCheck;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotChangeProofingConfig()
{
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::setProofingConfigUpdated(bool updated)
{
m_d->proofingConfigUpdated = updated;
}
bool KisCanvas2::proofingConfigUpdated()
{
return m_d->proofingConfigUpdated;
}
KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const
{
if (!m_d->proofingConfig) {
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
m_d->proofingConfig = KisImageConfig(true).defaultProofingconfiguration();
}
}
return m_d->proofingConfig;
}
void KisCanvas2::startResizingImage()
{
KisImageWSP image = this->image();
qint32 w = image->width();
qint32 h = image->height();
emit sigContinueResizeImage(w, h);
QRect imageBounds(0, 0, w, h);
startUpdateInPatches(imageBounds);
}
void KisCanvas2::finishResizingImage(qint32 w, qint32 h)
{
m_d->canvasWidget->finishResizingImage(w, h);
}
void KisCanvas2::startUpdateCanvasProjection(const QRect & rc)
{
KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags);
if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) {
emit sigCanvasCacheUpdated();
}
}
void KisCanvas2::updateCanvasProjection()
{
auto tryIssueCanvasUpdates = [this](const QRect &vRect) {
if (!m_d->isBatchUpdateActive) {
// TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas
if (m_d->currentCanvasIsOpenGL) {
m_d->savedUpdateRect = QRect();
// we already had a compression in frameRenderStartCompressor, so force the update directly
slotDoCanvasUpdate();
} else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) {
m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect();
// we already had a compression in frameRenderStartCompressor, so force the update directly
slotDoCanvasUpdate();
}
}
};
auto uploadData = [this, tryIssueCanvasUpdates](const QVector<KisUpdateInfoSP> &infoObjects) {
QVector<QRect> viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects);
const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(),
QRect(), std::bit_or<QRect>());
tryIssueCanvasUpdates(vRect);
};
bool shouldExplicitlyIssueUpdates = false;
QVector<KisUpdateInfoSP> infoObjects;
KisUpdateInfoList originalInfoObjects;
m_d->projectionUpdatesCompressor.takeUpdateInfo(originalInfoObjects);
for (auto it = originalInfoObjects.constBegin();
it != originalInfoObjects.constEnd();
++it) {
KisUpdateInfoSP info = *it;
const KisMarkerUpdateInfo *batchInfo = dynamic_cast<const KisMarkerUpdateInfo*>(info.data());
if (batchInfo) {
if (!infoObjects.isEmpty()) {
uploadData(infoObjects);
infoObjects.clear();
}
if (batchInfo->type() == KisMarkerUpdateInfo::StartBatch) {
m_d->isBatchUpdateActive++;
} else if (batchInfo->type() == KisMarkerUpdateInfo::EndBatch) {
m_d->isBatchUpdateActive--;
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->isBatchUpdateActive >= 0);
if (m_d->isBatchUpdateActive == 0) {
shouldExplicitlyIssueUpdates = true;
}
} else if (batchInfo->type() == KisMarkerUpdateInfo::BlockLodUpdates) {
m_d->canvasWidget->setLodResetInProgress(true);
} else if (batchInfo->type() == KisMarkerUpdateInfo::UnblockLodUpdates) {
m_d->canvasWidget->setLodResetInProgress(false);
shouldExplicitlyIssueUpdates = true;
}
} else {
infoObjects << info;
}
}
if (!infoObjects.isEmpty()) {
uploadData(infoObjects);
} else if (shouldExplicitlyIssueUpdates) {
tryIssueCanvasUpdates(m_d->coordinatesConverter->imageRectInImagePixels());
}
}
void KisCanvas2::slotBeginUpdatesBatch()
{
KisUpdateInfoSP info =
new KisMarkerUpdateInfo(KisMarkerUpdateInfo::StartBatch,
m_d->coordinatesConverter->imageRectInImagePixels());
m_d->projectionUpdatesCompressor.putUpdateInfo(info);
emit sigCanvasCacheUpdated();
}
void KisCanvas2::slotEndUpdatesBatch()
{
KisUpdateInfoSP info =
new KisMarkerUpdateInfo(KisMarkerUpdateInfo::EndBatch,
m_d->coordinatesConverter->imageRectInImagePixels());
m_d->projectionUpdatesCompressor.putUpdateInfo(info);
emit sigCanvasCacheUpdated();
}
void KisCanvas2::slotSetLodUpdatesBlocked(bool value)
{
KisUpdateInfoSP info =
new KisMarkerUpdateInfo(value ?
KisMarkerUpdateInfo::BlockLodUpdates :
KisMarkerUpdateInfo::UnblockLodUpdates,
m_d->coordinatesConverter->imageRectInImagePixels());
m_d->projectionUpdatesCompressor.putUpdateInfo(info);
emit sigCanvasCacheUpdated();
}
void KisCanvas2::slotDoCanvasUpdate()
{
/**
* WARNING: in isBusy() we access openGL functions without making the painting
* context current. We hope that currently active context will be Qt's one,
* which is shared with our own.
*/
if (m_d->canvasWidget->isBusy()) {
// just restarting the timer
updateCanvasWidgetImpl(m_d->savedUpdateRect);
return;
}
if (m_d->savedUpdateRect.isEmpty()) {
m_d->canvasWidget->widget()->update();
emit updateCanvasRequested(m_d->canvasWidget->widget()->rect());
} else {
emit updateCanvasRequested(m_d->savedUpdateRect);
m_d->canvasWidget->widget()->update(m_d->savedUpdateRect);
}
m_d->savedUpdateRect = QRect();
}
void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc)
{
if (!m_d->canvasUpdateCompressor.isActive() ||
!m_d->savedUpdateRect.isEmpty()) {
m_d->savedUpdateRect |= rc;
}
m_d->canvasUpdateCompressor.start();
}
void KisCanvas2::updateCanvas()
{
updateCanvasWidgetImpl();
}
void KisCanvas2::updateCanvas(const QRectF& documentRect)
{
if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) {
updateCanvasWidgetImpl();
}
else {
// updateCanvas is called from tools, never from the projection
// updates, so no need to prescale!
QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect();
widgetRect.adjust(-2, -2, 2, 2);
if (!widgetRect.isEmpty()) {
updateCanvasWidgetImpl(widgetRect);
}
}
}
void KisCanvas2::disconnectCanvasObserver(QObject *object)
{
KoCanvasBase::disconnectCanvasObserver(object);
m_d->view->disconnect(object);
}
void KisCanvas2::notifyZoomChanged()
{
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->notifyZoomChanged();
}
notifyLevelOfDetailChange();
updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction
m_d->regionOfInterestUpdateCompressor.start();
}
QRect KisCanvas2::regionOfInterest() const
{
return m_d->regionOfInterest;
}
void KisCanvas2::slotUpdateRegionOfInterest()
{
const QRect oldRegionOfInterest = m_d->regionOfInterest;
const qreal ratio = 0.25;
const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect();
const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels();
m_d->regionOfInterest = imageRect.contains(proposedRoi) ? proposedRoi : imageRect;
if (m_d->regionOfInterest != oldRegionOfInterest) {
emit sigRegionOfInterestChanged(m_d->regionOfInterest);
}
}
void KisCanvas2::slotReferenceImagesChanged()
{
canvasController()->resetScrollBars();
}
void KisCanvas2::setRenderingLimit(const QRect &rc)
{
m_d->renderingLimit = rc;
}
QRect KisCanvas2::renderingLimit() const
{
return m_d->renderingLimit;
}
void KisCanvas2::slotTrySwitchShapeManager()
{
KisNodeSP node = m_d->view->currentNode();
QPointer<KoShapeManager> newManager;
newManager = fetchShapeManagerFromNode(node);
m_d->setActiveShapeManager(newManager);
}
void KisCanvas2::notifyLevelOfDetailChange()
{
if (!m_d->effectiveLodAllowedInImage()) return;
const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom();
KisConfig cfg(true);
const int maxLod = cfg.numMipmapLevels();
const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod);
if (m_d->effectiveLodAllowedInImage()) {
KisImageSP image = this->image();
image->setDesiredLevelOfDetail(lod);
}
}
const KoColorProfile * KisCanvas2::monitorProfile()
{
return m_d->displayColorConverter.monitorProfile();
}
KisViewManager* KisCanvas2::viewManager() const
{
if (m_d->view) {
return m_d->view->viewManager();
}
return 0;
}
QPointer<KisView>KisCanvas2::imageView() const
{
return m_d->view;
}
KisImageWSP KisCanvas2::image() const
{
return m_d->view->image();
}
KisImageWSP KisCanvas2::currentImage() const
{
return m_d->view->image();
}
void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset)
{
QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
// The given offset is in widget logical pixels. In order to prevent fuzzy
// canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio
// is not integral, we adjusts the offset to map to whole device pixels.
//
// FIXME: This is a temporary hack for fixing the canvas under fractional
// DPI scaling before a new coordinate system is introduced.
QPointF offsetAdjusted = m_d->coordinatesConverter->snapToDevicePixel(documentOffset);
m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted);
QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
QPointF moveOffset = offsetAfter - offsetBefore;
if (!m_d->currentCanvasIsOpenGL)
m_d->prescaledProjection->viewportMoved(moveOffset);
emit documentOffsetUpdateFinished();
updateCanvas();
m_d->regionOfInterestUpdateCompressor.start();
}
void KisCanvas2::slotConfigChanged()
{
KisConfig cfg(true);
m_d->vastScrolling = cfg.vastScrolling();
resetCanvas(cfg.useOpenGL());
- setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget())));
+
+ // HACK: Sometimes screenNumber(this->canvasWidget()) is not able to get the
+ // proper screenNumber when moving the window across screens. Using
+ // the coordinates should be able to work around this.
+ // FIXME: We should change to associate the display profiles with the screen
+ // model and serial number instead. See https://bugs.kde.org/show_bug.cgi?id=407498
+ QScreen *canvasScreen = this->canvasWidget()->window()->windowHandle()->screen();
+ QPoint canvasScreenCenter = canvasScreen->geometry().center();
+ int canvasScreenNumber = QApplication::desktop()->screenNumber(canvasScreenCenter);
+ if (canvasScreenNumber == -1) {
+ // Fall back to the old way of getting the screenNumber
+ canvasScreenNumber = QApplication::desktop()->screenNumber(this->canvasWidget());
+ }
+ if (canvasScreenNumber != -1) {
+ setDisplayProfile(cfg.displayProfile(canvasScreenNumber));
+ } else {
+ warnUI << "Failed to get screenNumber for updating display profile.";
+ }
initializeFpsDecoration();
}
void KisCanvas2::refetchDataFromImage()
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
startUpdateInPatches(image->bounds());
}
void KisCanvas2::setDisplayProfile(const KoColorProfile *monitorProfile)
{
if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return;
m_d->displayColorConverter.setMonitorProfile(monitorProfile);
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
m_d->canvasWidget->setDisplayColorConverter(&m_d->displayColorConverter);
}
refetchDataFromImage();
}
void KisCanvas2::addDecoration(KisCanvasDecorationSP deco)
{
m_d->canvasWidget->addDecoration(deco);
}
KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const
{
return m_d->canvasWidget->decoration(id);
}
QPoint KisCanvas2::documentOrigin() const
{
/**
* In Krita we don't use document origin anymore.
* All the centering when needed (vastScrolling < 0.5) is done
* automatically by the KisCoordinatesConverter.
*/
return QPoint();
}
QPoint KisCanvas2::documentOffset() const
{
return m_d->coordinatesConverter->documentOffset();
}
void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager)
{
m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(),
m_d->view->resourceProvider(), m_d->canvasWidget->widget());
connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int)));
connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas()));
connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons()));
m_d->popupPalette->showPopupPalette(false);
}
void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) {
m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom
notifyZoomChanged();
}
void KisCanvas2::setCursor(const QCursor &cursor)
{
canvasWidget()->setCursor(cursor);
}
KisAnimationFrameCacheSP KisCanvas2::frameCache() const
{
return m_d->frameCache;
}
KisAnimationPlayer *KisCanvas2::animationPlayer() const
{
return m_d->animationPlayer;
}
void KisCanvas2::slotSelectionChanged()
{
KisShapeLayer* shapeLayer = dynamic_cast<KisShapeLayer*>(viewManager()->activeLayer().data());
if (!shapeLayer) {
return;
}
m_d->shapeManager.selection()->deselectAll();
Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) {
m_d->shapeManager.selection()->select(shape);
}
}
bool KisCanvas2::isPopupPaletteVisible() const
{
if (!m_d->popupPalette) {
return false;
}
return m_d->popupPalette->isVisible();
}
void KisCanvas2::setWrapAroundViewingMode(bool value)
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
infinityDecoration->setVisible(!value);
}
m_d->canvasWidget->setWrapAroundViewingMode(value);
}
bool KisCanvas2::wrapAroundViewingMode() const
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
return !(infinityDecoration->visible());
}
return false;
}
void KisCanvas2::bootstrapFinished()
{
if (!m_d->bootstrapLodBlocked) return;
m_d->bootstrapLodBlocked = false;
setLodAllowedInCanvas(m_d->lodAllowedInImage);
}
void KisCanvas2::setLodAllowedInCanvas(bool value)
{
if (!KisOpenGL::supportsLoD()) {
qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support";
}
m_d->lodAllowedInImage =
value &&
m_d->currentCanvasIsOpenGL &&
KisOpenGL::supportsLoD() &&
(m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode ||
m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering);
KisImageSP image = this->image();
if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) {
image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage());
}
notifyLevelOfDetailChange();
KisConfig cfg(false);
cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage);
}
bool KisCanvas2::lodAllowedInCanvas() const
{
return m_d->lodAllowedInImage;
}
void KisCanvas2::slotShowPopupPalette(const QPoint &p)
{
if (!m_d->popupPalette) {
return;
}
m_d->popupPalette->showPopupPalette(p);
}
KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const
{
KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration");
return qobject_cast<KisPaintingAssistantsDecoration*>(deco.data());
}
KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const
{
KisCanvasDecorationSP deco = decoration("referenceImagesDecoration");
return qobject_cast<KisReferenceImagesDecoration*>(deco.data());
}
diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp
index a7d5c36b5f..2ab25dec3b 100644
--- a/libs/ui/canvas/kis_canvas_controller.cpp
+++ b/libs/ui/canvas/kis_canvas_controller.cpp
@@ -1,398 +1,398 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_canvas_controller.h"
#include <QMouseEvent>
#include <QScrollBar>
#include <QTabletEvent>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include "kis_canvas_decoration.h"
#include "kis_paintop_transformation_connector.h"
#include "kis_coordinates_converter.h"
#include "kis_canvas2.h"
#include "opengl/kis_opengl_canvas2.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "KisViewManager.h"
#include "KisView.h"
#include "krita_utils.h"
#include "kis_config.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_config_notifier.h"
static const int gRulersUpdateDelay = 80 /* ms */;
struct KisCanvasController::Private {
Private(KisCanvasController *qq)
: q(qq),
paintOpTransformationConnector(0)
{
using namespace std::placeholders;
std::function<void (QPoint)> callback(
std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1));
mousePositionCompressor.reset(
new KisSignalCompressorWithParam<QPoint>(
gRulersUpdateDelay,
callback,
KisSignalCompressor::FIRST_ACTIVE));
}
QPointer<KisView> view;
KisCoordinatesConverter *coordinatesConverter;
KisCanvasController *q;
KisPaintopTransformationConnector *paintOpTransformationConnector;
QScopedPointer<KisSignalCompressorWithParam<QPoint> > mousePositionCompressor;
void emitPointerPositionChangedSignals(QPoint pointerPos);
void updateDocumentSizeAfterTransform();
void showRotationValueOnCanvas();
void showMirrorStateOnCanvas();
};
void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos)
{
if (!coordinatesConverter) return;
QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos);
q->proxyObject->emitDocumentMousePositionChanged(documentPos);
q->proxyObject->emitCanvasMousePositionChanged(pointerPos);
}
void KisCanvasController::Private::updateDocumentSizeAfterTransform()
{
// round the size of the area to the nearest integer instead of getting aligned rect
QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size();
q->updateDocumentSize(widgetSize, true);
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(q->canvas());
Q_ASSERT(kritaCanvas);
kritaCanvas->notifyZoomChanged();
}
KisCanvasController::KisCanvasController(QPointer<KisView>parent, KActionCollection * actionCollection)
: KoCanvasControllerWidget(actionCollection, parent),
m_d(new Private(this))
{
m_d->view = parent;
}
KisCanvasController::~KisCanvasController()
{
delete m_d;
}
void KisCanvasController::setCanvas(KoCanvasBase *canvas)
{
if (canvas) {
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas);
KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas);
m_d->coordinatesConverter =
const_cast<KisCoordinatesConverter*>(kritaCanvas->coordinatesConverter());
m_d->paintOpTransformationConnector =
new KisPaintopTransformationConnector(kritaCanvas, this);
} else {
m_d->coordinatesConverter = 0;
delete m_d->paintOpTransformationConnector;
m_d->paintOpTransformationConnector = 0;
}
KoCanvasControllerWidget::setCanvas(canvas);
}
void KisCanvasController::activate()
{
KoCanvasControllerWidget::activate();
}
QPointF KisCanvasController::currentCursorPosition() const
{
KoCanvasBase *canvas = m_d->view->canvasBase();
QWidget *canvasWidget = canvas->canvasWidget();
const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos());
return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget);
}
void KisCanvasController::keyPressEvent(QKeyEvent *event)
{
/**
* Dirty Hack Alert:
* Do not call the KoCanvasControllerWidget::keyPressEvent()
* to avoid activation of Pan and Default tool activation shortcuts
*/
Q_UNUSED(event);
}
void KisCanvasController::wheelEvent(QWheelEvent *event)
{
/**
* Dirty Hack Alert:
* Do not call the KoCanvasControllerWidget::wheelEvent()
* to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea
*/
Q_UNUSED(event);
}
bool KisCanvasController::eventFilter(QObject *watched, QEvent *event)
{
KoCanvasBase *canvas = this->canvas();
if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false;
if (event->type() == QEvent::MouseMove) {
QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
m_d->mousePositionCompressor->start(mevent->pos());
} else if (event->type() == QEvent::TabletMove) {
QTabletEvent *tevent = static_cast<QTabletEvent*>(event);
m_d->mousePositionCompressor->start(tevent->pos());
} else if (event->type() == QEvent::FocusIn) {
m_d->view->syncLastActiveNodeToDocument();
}
return false;
}
-void KisCanvasController::updateDocumentSize(const QSize &sz, bool recalculateCenter)
+void KisCanvasController::updateDocumentSize(const QSizeF &sz, bool recalculateCenter)
{
KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter);
emit documentSizeChanged();
}
void KisCanvasController::Private::showMirrorStateOnCanvas()
{
bool isXMirrored = coordinatesConverter->xAxisMirrored();
view->viewManager()->
showFloatingMessage(
i18nc("floating message about mirroring",
"Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")),
QIcon(), 500, KisFloatingMessage::Low);
}
void KisCanvasController::mirrorCanvas(bool enable)
{
QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false);
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showMirrorStateOnCanvas();
}
void KisCanvasController::Private::showRotationValueOnCanvas()
{
qreal rotationAngle = coordinatesConverter->rotationAngle();
view->viewManager()->
showFloatingMessage(
i18nc("floating message about rotation", "Rotation: %1° ",
KritaUtils::prettyFormatReal(rotationAngle)),
QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter);
}
void KisCanvasController::rotateCanvas(qreal angle, const QPointF &center)
{
QPoint newOffset = m_d->coordinatesConverter->rotate(center, angle);
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showRotationValueOnCanvas();
}
void KisCanvasController::rotateCanvas(qreal angle)
{
rotateCanvas(angle, m_d->coordinatesConverter->widgetCenterPoint());
}
void KisCanvasController::rotateCanvasRight15()
{
rotateCanvas(15.0);
}
void KisCanvasController::rotateCanvasLeft15()
{
rotateCanvas(-15.0);
}
qreal KisCanvasController::rotation() const
{
return m_d->coordinatesConverter->rotationAngle();
}
void KisCanvasController::resetCanvasRotation()
{
QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint());
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showRotationValueOnCanvas();
}
void KisCanvasController::slotToggleWrapAroundMode(bool value)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
if (!canvas()->canvasIsOpenGL() && value) {
m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n"
"To visualize wrap-around mode, enable OpenGL."), QIcon());
}
kritaCanvas->setWrapAroundViewingMode(value);
kritaCanvas->image()->setWrapAroundModePermitted(value);
}
bool KisCanvasController::wrapAroundMode() const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->wrapAroundViewingMode();
}
void KisCanvasController::slotTogglePixelGrid(bool value)
{
KisConfig cfg(false);
cfg.enablePixelGrid(value);
KisConfigNotifier::instance()->notifyPixelGridModeChanged();
}
void KisCanvasController::slotToggleLevelOfDetailMode(bool value)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
kritaCanvas->setLodAllowedInCanvas(value);
bool result = levelOfDetailMode();
if (!value || result) {
m_d->view->viewManager()->showFloatingMessage(
i18n("Instant Preview Mode: %1", result ?
i18n("ON") : i18n("OFF")),
QIcon(), 500, KisFloatingMessage::Low);
} else {
QString reason;
if (!kritaCanvas->canvasIsOpenGL()) {
reason = i18n("Instant Preview is only supported with OpenGL activated");
}
else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ||
kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) {
QString filteringMode =
kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ?
i18n("Bilinear") : i18n("Nearest Neighbour");
reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode);
}
m_d->view->viewManager()->showFloatingMessage(
i18n("Failed activating Instant Preview mode!\n\n%1", reason),
QIcon(), 5000, KisFloatingMessage::Low);
}
}
bool KisCanvasController::levelOfDetailMode() const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->lodAllowedInCanvas();
}
void KisCanvasController::saveCanvasState(KisPropertiesConfiguration &config) const
{
const QPointF &center = preferredCenter();
config.setProperty("panX", center.x());
config.setProperty("panY", center.y());
config.setProperty("rotation", rotation());
config.setProperty("mirror", m_d->coordinatesConverter->xAxisMirrored());
config.setProperty("wrapAround", wrapAroundMode());
config.setProperty("enableInstantPreview", levelOfDetailMode());
}
void KisCanvasController::restoreCanvasState(const KisPropertiesConfiguration &config)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
mirrorCanvas(config.getBool("mirror", false));
rotateCanvas(config.getFloat("rotation", 0.0f));
const QPointF &center = preferredCenter();
float panX = config.getFloat("panX", center.x());
float panY = config.getFloat("panY", center.y());
setPreferredCenter(QPointF(panX, panY));
slotToggleWrapAroundMode(config.getBool("wrapAround", false));
kritaCanvas->setLodAllowedInCanvas(config.getBool("enableInstantPreview", false));
}
void KisCanvasController::resetScrollBars()
{
// The scrollbar value always points at the top-left corner of the
// bit of image we paint.
KisDocument *doc = m_d->view->document();
if (!doc) return;
QRectF documentBounds = doc->documentBounds();
QRectF viewRect = m_d->coordinatesConverter->imageToWidget(documentBounds);
// Cancel out any existing pan
const QRectF imageBounds = m_d->view->image()->bounds();
const QRectF imageBB = m_d->coordinatesConverter->imageToWidget(imageBounds);
QPointF pan = imageBB.topLeft();
viewRect.translate(-pan);
int drawH = viewport()->height();
int drawW = viewport()->width();
qreal horizontalReserve = vastScrollingFactor() * drawW;
qreal verticalReserve = vastScrollingFactor() * drawH;
qreal xMin = viewRect.left() - horizontalReserve;
qreal yMin = viewRect.top() - verticalReserve;
qreal xMax = viewRect.right() - drawW + horizontalReserve;
qreal yMax = viewRect.bottom() - drawH + verticalReserve;
QScrollBar *hScroll = horizontalScrollBar();
QScrollBar *vScroll = verticalScrollBar();
hScroll->setRange(static_cast<int>(xMin), static_cast<int>(xMax));
vScroll->setRange(static_cast<int>(yMin), static_cast<int>(yMax));
int fontHeight = QFontMetrics(font()).height();
vScroll->setPageStep(drawH);
vScroll->setSingleStep(fontHeight);
hScroll->setPageStep(drawW);
hScroll->setSingleStep(fontHeight);
}
diff --git a/libs/ui/canvas/kis_canvas_controller.h b/libs/ui/canvas/kis_canvas_controller.h
index 9ac0e26f69..c1226313be 100644
--- a/libs/ui/canvas/kis_canvas_controller.h
+++ b/libs/ui/canvas/kis_canvas_controller.h
@@ -1,77 +1,77 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_CANVAS_CONTROLLER_H
#define KIS_CANVAS_CONTROLLER_H
#include <KoCanvasControllerWidget.h>
#include "kritaui_export.h"
#include "kis_types.h"
class KConfigGroup;
class KisView;
class KRITAUI_EXPORT KisCanvasController : public KoCanvasControllerWidget
{
Q_OBJECT
public:
KisCanvasController(QPointer<KisView>parent, KActionCollection * actionCollection);
~KisCanvasController() override;
void setCanvas(KoCanvasBase *canvas) override;
void keyPressEvent(QKeyEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
- void updateDocumentSize(const QSize &sz, bool recalculateCenter) override;
+ void updateDocumentSize(const QSizeF &sz, bool recalculateCenter) override;
void activate() override;
QPointF currentCursorPosition() const override;
public:
using KoCanvasController::documentSize;
bool wrapAroundMode() const;
bool levelOfDetailMode() const;
void saveCanvasState(KisPropertiesConfiguration &config) const;
void restoreCanvasState(const KisPropertiesConfiguration &config);
void resetScrollBars() override;
public Q_SLOTS:
void mirrorCanvas(bool enable);
void rotateCanvas(qreal angle, const QPointF &center);
void rotateCanvas(qreal angle);
void rotateCanvasRight15();
void rotateCanvasLeft15();
qreal rotation() const;
void resetCanvasRotation();
void slotToggleWrapAroundMode(bool value);
void slotTogglePixelGrid(bool value);
void slotToggleLevelOfDetailMode(bool value);
Q_SIGNALS:
void documentSizeChanged();
private:
struct Private;
Private * const m_d;
};
#endif /* KIS_CANVAS_CONTROLLER_H */
diff --git a/libs/ui/canvas/kis_change_guides_command.cpp b/libs/ui/canvas/kis_change_guides_command.cpp
index 06706f5ad2..d8e37f8b25 100644
--- a/libs/ui/canvas/kis_change_guides_command.cpp
+++ b/libs/ui/canvas/kis_change_guides_command.cpp
@@ -1,77 +1,147 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_change_guides_command.h"
#include "kis_guides_config.h"
#include "KisDocument.h"
#include <kis_image.h>
+#include <QList>
+#include <QListIterator>
struct KisChangeGuidesCommand::Private
{
- Private(KisDocument *_doc) : doc(_doc) {}
+ Private(KisDocument *_doc, KisChangeGuidesCommand *q) : doc(_doc), q(q), firstRedo(true) {}
+
+ bool sameOrOnlyMovedOneGuideBetween(const KisGuidesConfig &first, const KisGuidesConfig &second);
+ enum Status {
+ NO_DIFF = 0,
+ ONE_DIFF = 1,
+ ADDITION = 4,
+ REMOVAL = 16,
+ OTHER_DIFF = 1024
+ };
+ Status diff(const QList<qreal> &first, const QList<qreal> &second);
+
+ void switchTo(const KisGuidesConfig &config);
KisDocument *doc;
+ KisChangeGuidesCommand *q;
KisGuidesConfig oldGuides;
KisGuidesConfig newGuides;
+
+ bool firstRedo;
};
+bool KisChangeGuidesCommand::Private::sameOrOnlyMovedOneGuideBetween(const KisGuidesConfig &first, const KisGuidesConfig &second)
+{
+ int ret = diff(first.horizontalGuideLines(), second.horizontalGuideLines()) +
+ diff(first.verticalGuideLines(), second.verticalGuideLines());
+
+ if (ret == ADDITION) {
+ q->setText(kundo2_i18n("Add Guide"));
+ } else if (ret == REMOVAL) {
+ q->setText(kundo2_i18n("Remove Guide"));
+ } else if (ret == NO_DIFF || ret == ONE_DIFF) { // meaning we will still merge it
+ // XXX: how to deal with NO_DIFF (the command "should" be removed -- how?)
+ q->setText(kundo2_i18n("Edit Guides"));
+ } else {
+ return false;
+ }
+ return true;
+}
+
+KisChangeGuidesCommand::Private::Status KisChangeGuidesCommand::Private::diff(const QList<qreal> &first, const QList<qreal> &second)
+{
+ if (first.size() == second.size()) {
+ int diffCount = 0;
+ for (int i = 0; i < first.size(); ++i) {
+ if (first[i] != second[i]) {
+ ++diffCount;
+ if (diffCount > 1) {
+ return OTHER_DIFF;
+ }
+ }
+ }
+ return diffCount == 0 ? NO_DIFF : ONE_DIFF;
+ } else if (first.size() - second.size() == -1) { // added a guide
+ QList<qreal> beforeRemoval = second;
+ beforeRemoval.takeLast();
+ return first == beforeRemoval ? ADDITION : OTHER_DIFF;
+ } else if (first.size() - second.size() == 1) { // removed a guide
+ bool skippedItem = false;
+ for (QListIterator<qreal> i(first), j(second); i.hasNext() && j.hasNext(); ) {
+ qreal curFirst = i.next();
+ qreal curSecond = j.next();
+ if (!skippedItem && curFirst != curSecond) {
+ curFirst = i.next(); // try to go to the next item and see if it matches
+ }
+ if (curFirst != curSecond) {
+ return OTHER_DIFF;
+ }
+ }
+ // here we conclude only one guide is removed
+ return REMOVAL;
+ } else {
+ return OTHER_DIFF;
+ }
+}
+
+void KisChangeGuidesCommand::Private::switchTo(const KisGuidesConfig &config)
+{
+ KisGuidesConfig curConfig = doc->guidesConfig();
+ curConfig.setHorizontalGuideLines(config.horizontalGuideLines());
+ curConfig.setVerticalGuideLines(config.verticalGuideLines());
+ doc->setGuidesConfig(curConfig);
+}
-KisChangeGuidesCommand::KisChangeGuidesCommand(KisDocument *doc, const KisGuidesConfig &newGuides)
+KisChangeGuidesCommand::KisChangeGuidesCommand(KisDocument *doc, const KisGuidesConfig &oldGuides, const KisGuidesConfig &newGuides)
: KUndo2Command(kundo2_i18n("Edit Guides")),
- m_d(new Private(doc))
+ m_d(new Private(doc, this))
{
- m_d->oldGuides = doc->guidesConfig();
+ m_d->oldGuides = oldGuides;
m_d->newGuides = newGuides;
+ // update the undo command text
+ m_d->sameOrOnlyMovedOneGuideBetween(m_d->oldGuides, m_d->newGuides);
}
KisChangeGuidesCommand::~KisChangeGuidesCommand()
{
}
void KisChangeGuidesCommand::undo()
{
- m_d->doc->setGuidesConfig(m_d->oldGuides);
+ m_d->switchTo(m_d->oldGuides);
}
void KisChangeGuidesCommand::redo()
{
- m_d->doc->setGuidesConfig(m_d->newGuides);
+ if (m_d->firstRedo) {
+ m_d->firstRedo = false;
+ return;
+ }
+ m_d->switchTo(m_d->newGuides);
}
int KisChangeGuidesCommand::id() const
{
return 1863;
}
-bool KisChangeGuidesCommand::mergeWith(const KUndo2Command *command)
-{
- bool result = false;
-
- const KisChangeGuidesCommand *rhs =
- dynamic_cast<const KisChangeGuidesCommand*>(command);
-
- if (rhs) {
- m_d->newGuides = rhs->m_d->newGuides;
- result = true;
- }
-
- return result;
-}
diff --git a/libs/ui/canvas/kis_change_guides_command.h b/libs/ui/canvas/kis_change_guides_command.h
index a66649b346..3ba8727d6a 100644
--- a/libs/ui/canvas/kis_change_guides_command.h
+++ b/libs/ui/canvas/kis_change_guides_command.h
@@ -1,48 +1,46 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_CHANGE_GUIDES_COMMAND_H
#define __KIS_CHANGE_GUIDES_COMMAND_H
#include <QScopedPointer>
#include <kundo2command.h>
class KisDocument;
class KUndo2Command;
class KisGuidesConfig;
class KisChangeGuidesCommand : public KUndo2Command
{
public:
- KisChangeGuidesCommand(KisDocument *doc, const KisGuidesConfig &newGuides);
- KisChangeGuidesCommand(KisDocument *document);
+ KisChangeGuidesCommand(KisDocument *doc, const KisGuidesConfig &oldGuides, const KisGuidesConfig &newGuides);
~KisChangeGuidesCommand() override;
void undo() override;
void redo() override;
int id() const override;
- bool mergeWith(const KUndo2Command *command) override;
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_CHANGE_GUIDES_COMMAND_H */
diff --git a/libs/ui/canvas/kis_coordinates_converter.cpp b/libs/ui/canvas/kis_coordinates_converter.cpp
index 401d850187..9470fd866b 100644
--- a/libs/ui/canvas/kis_coordinates_converter.cpp
+++ b/libs/ui/canvas/kis_coordinates_converter.cpp
@@ -1,486 +1,486 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* 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 <cmath>
#include "kis_coordinates_converter.h"
#include <QtMath>
#include <QTransform>
#include <KoViewConverter.h>
#include <kis_config.h>
#include <kis_image.h>
struct KisCoordinatesConverter::Private {
Private():
isXAxisMirrored(false),
isYAxisMirrored(false),
rotationAngle(0.0),
devicePixelRatio(1.0)
{
}
KisImageWSP image;
bool isXAxisMirrored;
bool isYAxisMirrored;
qreal rotationAngle;
QSizeF canvasWidgetSize;
qreal devicePixelRatio;
QPointF documentOffset;
QTransform flakeToWidget;
QTransform imageToDocument;
QTransform documentToFlake;
QTransform widgetToViewport;
};
/**
* When vastScrolling value is less than 0.5 it is possible
* that the whole scrolling area (viewport) will be smaller than
* the size of the widget. In such cases the image should be
* centered in the widget. Previously we used a special parameter
* documentOrigin for this purpose, now the value for this
* centering is calculated dynamically, helping the offset to
* center the image inside the widget
*
* Note that the correction is null when the size of the document
* plus vast scrolling reserve is larger than the widget. This
* is always true for vastScrolling parameter > 0.5.
*/
QPointF KisCoordinatesConverter::centeringCorrection() const
{
KisConfig cfg(true);
QSize documentSize = imageRectInWidgetPixels().toAlignedRect().size();
QPointF dPoint(documentSize.width(), documentSize.height());
QPointF wPoint(m_d->canvasWidgetSize.width(), m_d->canvasWidgetSize.height());
QPointF minOffset = -cfg.vastScrolling() * wPoint;
QPointF maxOffset = dPoint - wPoint + cfg.vastScrolling() * wPoint;
QPointF range = maxOffset - minOffset;
range.rx() = qMin(range.x(), (qreal)0.0);
range.ry() = qMin(range.y(), (qreal)0.0);
range /= 2;
return -range;
}
/**
* The document offset and the position of the top left corner of the
* image must always coincide, that is why we need to correct them to
* and fro.
*
* When we change zoom level, the calculation of the new offset is
* done by KoCanvasControllerWidget, that is why we just passively fix
* the flakeToWidget transform to conform the offset and wait until
* the canvas controller will recenter us.
*
* But when we do our own transformations of the canvas, like rotation
* and mirroring, we cannot rely on the centering of the canvas
* controller and we do it ourselves. Then we just set new offset and
* return its value to be set in the canvas controller explicitly.
*/
void KisCoordinatesConverter::correctOffsetToTransformation()
{
- m_d->documentOffset = -(imageRectInWidgetPixels().topLeft() -
- centeringCorrection()).toPoint();
+ m_d->documentOffset = snapToDevicePixel(-(imageRectInWidgetPixels().topLeft() -
+ centeringCorrection()));
}
void KisCoordinatesConverter::correctTransformationToOffset()
{
QPointF topLeft = imageRectInWidgetPixels().topLeft();
QPointF diff = (-topLeft) - m_d->documentOffset;
diff += centeringCorrection();
m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y());
}
void KisCoordinatesConverter::recalculateTransformations()
{
if(!m_d->image) return;
m_d->imageToDocument = QTransform::fromScale(1 / m_d->image->xRes(),
1 / m_d->image->yRes());
qreal zoomX, zoomY;
KoZoomHandler::zoom(&zoomX, &zoomY);
m_d->documentToFlake = QTransform::fromScale(zoomX, zoomY);
correctTransformationToOffset();
QRectF irect = imageRectInWidgetPixels();
QRectF wrect = QRectF(QPoint(0,0), m_d->canvasWidgetSize);
QRectF rrect = irect & wrect;
QTransform reversedTransform = flakeToWidgetTransform().inverted();
QRectF canvasBounds = reversedTransform.mapRect(rrect);
QPointF offset = canvasBounds.topLeft();
m_d->widgetToViewport = reversedTransform * QTransform::fromTranslate(-offset.x(), -offset.y());
}
KisCoordinatesConverter::KisCoordinatesConverter()
: m_d(new Private) { }
KisCoordinatesConverter::~KisCoordinatesConverter()
{
delete m_d;
}
void KisCoordinatesConverter::setCanvasWidgetSize(QSizeF size)
{
m_d->canvasWidgetSize = size;
recalculateTransformations();
}
void KisCoordinatesConverter::setDevicePixelRatio(qreal value)
{
m_d->devicePixelRatio = value;
}
void KisCoordinatesConverter::setImage(KisImageWSP image)
{
m_d->image = image;
recalculateTransformations();
}
void KisCoordinatesConverter::setDocumentOffset(const QPointF& offset)
{
QPointF diff = m_d->documentOffset - offset;
m_d->documentOffset = offset;
m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y());
recalculateTransformations();
}
qreal KisCoordinatesConverter::devicePixelRatio() const
{
return m_d->devicePixelRatio;
}
QPoint KisCoordinatesConverter::documentOffset() const
{
return QPoint(int(m_d->documentOffset.x()), int(m_d->documentOffset.y()));
}
qreal KisCoordinatesConverter::rotationAngle() const
{
return m_d->rotationAngle;
}
void KisCoordinatesConverter::setZoom(qreal zoom)
{
KoZoomHandler::setZoom(zoom);
recalculateTransformations();
}
qreal KisCoordinatesConverter::effectiveZoom() const
{
qreal scaleX, scaleY;
this->imageScale(&scaleX, &scaleY);
if (scaleX != scaleY) {
qWarning() << "WARNING: Zoom is not isotropic!" << ppVar(scaleX) << ppVar(scaleY) << ppVar(qFuzzyCompare(scaleX, scaleY));
}
// zoom by average of x and y
return 0.5 * (scaleX + scaleY);
}
QPoint KisCoordinatesConverter::rotate(QPointF center, qreal angle)
{
QTransform rot;
rot.rotate(angle);
m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y());
m_d->flakeToWidget *= rot;
m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y());
m_d->rotationAngle = std::fmod(m_d->rotationAngle + angle, 360.0);
correctOffsetToTransformation();
recalculateTransformations();
return m_d->documentOffset.toPoint();
}
QPoint KisCoordinatesConverter::mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis)
{
bool keepOrientation = false; // XXX: Keep here for now, maybe some day we can restore the parameter again.
bool doXMirroring = m_d->isXAxisMirrored ^ mirrorXAxis;
bool doYMirroring = m_d->isYAxisMirrored ^ mirrorYAxis;
qreal scaleX = doXMirroring ? -1.0 : 1.0;
qreal scaleY = doYMirroring ? -1.0 : 1.0;
QTransform mirror = QTransform::fromScale(scaleX, scaleY);
QTransform rot;
rot.rotate(m_d->rotationAngle);
m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y());
if (keepOrientation) {
m_d->flakeToWidget *= rot.inverted();
}
m_d->flakeToWidget *= mirror;
if (keepOrientation) {
m_d->flakeToWidget *= rot;
}
m_d->flakeToWidget *= QTransform::fromTranslate(center.x(),center.y());
if (!keepOrientation && (doXMirroring ^ doYMirroring)) {
m_d->rotationAngle = -m_d->rotationAngle;
}
m_d->isXAxisMirrored = mirrorXAxis;
m_d->isYAxisMirrored = mirrorYAxis;
correctOffsetToTransformation();
recalculateTransformations();
return m_d->documentOffset.toPoint();
}
bool KisCoordinatesConverter::xAxisMirrored() const
{
return m_d->isXAxisMirrored;
}
bool KisCoordinatesConverter::yAxisMirrored() const
{
return m_d->isYAxisMirrored;
}
QPoint KisCoordinatesConverter::resetRotation(QPointF center)
{
QTransform rot;
rot.rotate(-m_d->rotationAngle);
m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(), -center.y());
m_d->flakeToWidget *= rot;
m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y());
m_d->rotationAngle = 0.0;
correctOffsetToTransformation();
recalculateTransformations();
return m_d->documentOffset.toPoint();
}
QTransform KisCoordinatesConverter::imageToWidgetTransform() const{
return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget;
}
QTransform KisCoordinatesConverter::imageToDocumentTransform() const {
return m_d->imageToDocument;
}
QTransform KisCoordinatesConverter::documentToFlakeTransform() const {
return m_d->documentToFlake;
}
QTransform KisCoordinatesConverter::flakeToWidgetTransform() const {
return m_d->flakeToWidget;
}
QTransform KisCoordinatesConverter::documentToWidgetTransform() const
{
return m_d->documentToFlake * m_d->flakeToWidget;
}
QTransform KisCoordinatesConverter::viewportToWidgetTransform() const {
return m_d->widgetToViewport.inverted();
}
QTransform KisCoordinatesConverter::imageToViewportTransform() const {
return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget * m_d->widgetToViewport;
}
void KisCoordinatesConverter::getQPainterCheckersInfo(QTransform *transform,
QPointF *brushOrigin,
QPolygonF *polygon,
const bool scrollCheckers) const
{
/**
* Qt has different rounding for QPainter::drawRect/drawImage.
* The image is rounded mathematically, while rect in aligned
* to the next integer. That causes transparent line appear on
* the canvas.
*
* See: https://bugreports.qt.nokia.com/browse/QTBUG-22827
*/
QRectF imageRect = imageRectInViewportPixels();
imageRect.adjust(0,0,-0.5,-0.5);
if (scrollCheckers) {
*transform = viewportToWidgetTransform();
*polygon = imageRect;
*brushOrigin = imageToViewport(QPointF(0,0));
}
else {
*transform = QTransform();
*polygon = viewportToWidgetTransform().map(imageRect);
*brushOrigin = QPoint(0,0);
}
}
void KisCoordinatesConverter::getOpenGLCheckersInfo(const QRectF &viewportRect,
QTransform *textureTransform,
QTransform *modelTransform,
QRectF *textureRect,
QRectF *modelRect,
const bool scrollCheckers) const
{
if(scrollCheckers) {
*textureTransform = QTransform();
*textureRect = QRectF(0, 0, viewportRect.width(),viewportRect.height());
}
else {
*textureTransform = viewportToWidgetTransform();
*textureRect = viewportRect;
}
*modelTransform = viewportToWidgetTransform();
*modelRect = viewportRect;
}
QPointF KisCoordinatesConverter::imageCenterInWidgetPixel() const
{
if(!m_d->image)
return QPointF();
QPolygonF poly = imageToWidget(QPolygon(m_d->image->bounds()));
return (poly[0] + poly[1] + poly[2] + poly[3]) / 4.0;
}
// these functions return a bounding rect if the canvas is rotated
QRectF KisCoordinatesConverter::imageRectInWidgetPixels() const
{
if(!m_d->image) return QRectF();
return imageToWidget(m_d->image->bounds());
}
QRectF KisCoordinatesConverter::imageRectInViewportPixels() const
{
if(!m_d->image) return QRectF();
return imageToViewport(m_d->image->bounds());
}
QRect KisCoordinatesConverter::imageRectInImagePixels() const
{
if(!m_d->image) return QRect();
return m_d->image->bounds();
}
QRectF KisCoordinatesConverter::imageRectInDocumentPixels() const
{
if(!m_d->image) return QRectF();
return imageToDocument(m_d->image->bounds());
}
QSizeF KisCoordinatesConverter::imageSizeInFlakePixels() const
{
if(!m_d->image) return QSizeF();
qreal scaleX, scaleY;
imageScale(&scaleX, &scaleY);
QSize imageSize = m_d->image->size();
return QSizeF(imageSize.width() * scaleX, imageSize.height() * scaleY);
}
QRectF KisCoordinatesConverter::widgetRectInFlakePixels() const
{
return widgetToFlake(QRectF(QPoint(0,0), m_d->canvasWidgetSize));
}
QRectF KisCoordinatesConverter::widgetRectInImagePixels() const
{
return widgetToImage(QRectF(QPoint(0,0), m_d->canvasWidgetSize));
}
QPointF KisCoordinatesConverter::flakeCenterPoint() const
{
QRectF widgetRect = widgetRectInFlakePixels();
return QPointF(widgetRect.left() + widgetRect.width() / 2,
widgetRect.top() + widgetRect.height() / 2);
}
QPointF KisCoordinatesConverter::widgetCenterPoint() const
{
return QPointF(m_d->canvasWidgetSize.width() / 2.0, m_d->canvasWidgetSize.height() / 2.0);
}
void KisCoordinatesConverter::imageScale(qreal *scaleX, qreal *scaleY) const
{
if(!m_d->image) {
*scaleX = 1.0;
*scaleY = 1.0;
return;
}
// get the x and y zoom level of the canvas
qreal zoomX, zoomY;
KoZoomHandler::zoom(&zoomX, &zoomY);
// Get the KisImage resolution
qreal resX = m_d->image->xRes();
qreal resY = m_d->image->yRes();
// Compute the scale factors
*scaleX = zoomX / resX;
*scaleY = zoomY / resY;
}
void KisCoordinatesConverter::imagePhysicalScale(qreal *scaleX, qreal *scaleY) const
{
imageScale(scaleX, scaleY);
*scaleX *= m_d->devicePixelRatio;
*scaleY *= m_d->devicePixelRatio;
}
/**
* @brief Adjust a given pair of coordinates to the nearest device pixel
* according to the value of `devicePixelRatio`.
* @param point a point in logical pixel space
* @return The point in logical pixel space but adjusted to the nearest device
* pixel
*/
QPointF KisCoordinatesConverter::snapToDevicePixel(const QPointF &point) const
{
QPoint devicePixel = (point * m_d->devicePixelRatio).toPoint();
// These adjusted coords will be in logical pixel but is aligned in device
// pixel space for pixel-perfect rendering.
return QPointF(devicePixel) / m_d->devicePixelRatio;
}
diff --git a/libs/ui/canvas/kis_grid_manager.cpp b/libs/ui/canvas/kis_grid_manager.cpp
index 2efdce6d07..1e4132a644 100644
--- a/libs/ui/canvas/kis_grid_manager.cpp
+++ b/libs/ui/canvas/kis_grid_manager.cpp
@@ -1,132 +1,152 @@
/*
* This file is part of Krita
*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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_grid_manager.h"
#include <QAction>
#include <ktoggleaction.h>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <kis_icon.h>
#include "kis_canvas2.h"
#include "kis_coordinates_converter.h"
#include "kis_config.h"
#include "kis_grid_decoration.h"
#include "kis_image.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_signals_blocker.h"
+#include <kis_signal_auto_connection.h>
+struct KisGridManager::Private
+{
+ KisAction *toggleGrid;
+ KisAction* toggleSnapToGrid;
+
+ QPointer<KisView> imageView;
+ KisGridDecoration* gridDecoration;
+
+ bool blockModifiedSignal;
+ KisSignalAutoConnectionsStore connections;
+};
KisGridManager::KisGridManager(KisViewManager * parent)
: QObject(parent)
+ , m_d(new Private)
{
}
KisGridManager::~KisGridManager()
{
}
void KisGridManager::setGridConfig(const KisGridConfig &config)
{
setGridConfigImpl(config, true);
}
void KisGridManager::setGridConfigImpl(const KisGridConfig &config, bool /*emitModified*/)
{
- if (!m_imageView) return;
+ if (!m_d->imageView) return;
config.saveStaticData();
- m_imageView->document()->setGridConfig(config);
+ m_d->imageView->document()->setGridConfig(config);
- m_gridDecoration->setGridConfig(config);
- m_gridDecoration->setVisible(config.showGrid());
+ m_d->gridDecoration->setGridConfig(config);
+ m_d->gridDecoration->setVisible(config.showGrid());
- m_toggleGrid->setChecked(config.showGrid());
- m_toggleSnapToGrid->setChecked(config.snapToGrid());
+ m_d->toggleGrid->setChecked(config.showGrid());
+ m_d->toggleSnapToGrid->setChecked(config.snapToGrid());
}
void KisGridManager::setup(KisActionManager* actionManager)
{
- m_toggleGrid = actionManager->createAction("view_grid");
- connect(m_toggleGrid, SIGNAL(toggled(bool)), this, SLOT(slotChangeGridVisibilityTriggered(bool)));
+ m_d->toggleGrid = actionManager->createAction("view_grid");
+ connect(m_d->toggleGrid, SIGNAL(toggled(bool)), this, SLOT(slotChangeGridVisibilityTriggered(bool)));
- m_toggleSnapToGrid = actionManager->createAction("view_snap_to_grid");
- connect(m_toggleSnapToGrid, SIGNAL(toggled(bool)), this, SLOT(slotSnapToGridTriggered(bool)));
+ m_d->toggleSnapToGrid = actionManager->createAction("view_snap_to_grid");
+ connect(m_d->toggleSnapToGrid, SIGNAL(toggled(bool)), this, SLOT(slotSnapToGridTriggered(bool)));
}
void KisGridManager::updateGUI()
{
}
void KisGridManager::setView(QPointer<KisView> imageView)
{
- if (m_imageView) {
- m_gridDecoration = 0;
+ if (m_d->imageView) {
+ m_d->connections.clear();
+ m_d->gridDecoration = 0;
}
- m_imageView = imageView;
+ m_d->imageView = imageView;
if (imageView) {
- m_gridDecoration = qobject_cast<KisGridDecoration*>(imageView->canvasBase()->decoration("grid").data());
- if (!m_gridDecoration) {
- m_gridDecoration = new KisGridDecoration(imageView);
- imageView->canvasBase()->addDecoration(m_gridDecoration);
+ if (!imageView->document()) {
+ return;
+ }
+
+ m_d->gridDecoration = qobject_cast<KisGridDecoration*>(imageView->canvasBase()->decoration("grid").data());
+ if (!m_d->gridDecoration) {
+ m_d->gridDecoration = new KisGridDecoration(imageView);
+ imageView->canvasBase()->addDecoration(m_d->gridDecoration);
}
+ m_d->connections.addConnection(imageView->document(), SIGNAL(sigGridConfigChanged(KisGridConfig)),
+ this, SIGNAL(sigRequestUpdateGridConfig(KisGridConfig)));
KisGridConfig config = imageView->document()->gridConfig();
setGridConfigImpl(config, false);
- KisSignalsBlocker blocker(m_toggleGrid, m_toggleSnapToGrid);
+ KisSignalsBlocker blocker(m_d->toggleGrid, m_d->toggleSnapToGrid);
Q_UNUSED(blocker);
- m_toggleGrid->setChecked(config.showGrid());
- m_toggleSnapToGrid->setChecked(config.snapToGrid());
+ m_d->toggleGrid->setChecked(config.showGrid());
+ m_d->toggleSnapToGrid->setChecked(config.snapToGrid());
}
}
void KisGridManager::slotChangeGridVisibilityTriggered(bool value)
{
- if (!m_imageView) return;
+ if (!m_d->imageView) return;
- KisGridConfig config = m_imageView->document()->gridConfig();
+ KisGridConfig config = m_d->imageView->document()->gridConfig();
config.setShowGrid(value);
setGridConfig(config);
emit sigRequestUpdateGridConfig(config);
}
void KisGridManager::slotSnapToGridTriggered(bool value)
{
- if (!m_imageView) return;
+ if (!m_d->imageView) return;
- KisGridConfig config = m_imageView->document()->gridConfig();
+ KisGridConfig config = m_d->imageView->document()->gridConfig();
config.setSnapToGrid(value);
setGridConfig(config);
emit sigRequestUpdateGridConfig(config);
}
diff --git a/libs/ui/canvas/kis_grid_manager.h b/libs/ui/canvas/kis_grid_manager.h
index 51ff7a45d8..84650530f4 100644
--- a/libs/ui/canvas/kis_grid_manager.h
+++ b/libs/ui/canvas/kis_grid_manager.h
@@ -1,76 +1,72 @@
/*
* This file is part of Krita
*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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_GRID_MANAGER_H
#define KIS_GRID_MANAGER_H
#include <QPainter>
#include "kis_types.h"
#include <kritaui_export.h>
#include "kis_action_manager.h"
#include "kis_action.h"
class KisGridDecoration;
class KisViewManager;
class KisGridConfig;
class KRITAUI_EXPORT KisGridManager : public QObject
{
Q_OBJECT
public:
KisGridManager(KisViewManager * parent);
~KisGridManager() override;
public:
void setup(KisActionManager * actionManager);
void setView(QPointer<KisView>imageView);
void setGridConfig(const KisGridConfig &config);
Q_SIGNALS:
void sigRequestUpdateGridConfig(const KisGridConfig &config);
public Q_SLOTS:
void updateGUI();
private Q_SLOTS:
void slotChangeGridVisibilityTriggered(bool value);
void slotSnapToGridTriggered(bool value);
private:
void setGridConfigImpl(const KisGridConfig &config, bool emitModified);
private:
void setFastConfig(int size);
- KisAction *m_toggleGrid;
- KisAction* m_toggleSnapToGrid;
-
- QPointer<KisView> m_imageView;
- KisGridDecoration* m_gridDecoration;
-
- bool m_blockModifiedSignal;
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
};
#endif
diff --git a/libs/ui/canvas/kis_guides_config.cpp b/libs/ui/canvas/kis_guides_config.cpp
index 16ea47d9fc..53bd58a9a0 100644
--- a/libs/ui/canvas/kis_guides_config.cpp
+++ b/libs/ui/canvas/kis_guides_config.cpp
@@ -1,296 +1,302 @@
/* This file is part of the KDE project
Copyright (C) 2006 Laurent Montel <montel@kde.org>
Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_guides_config.h"
#include <QDomDocument>
#include <QColor>
#include <QPen>
#include "kis_config.h"
#include "kis_dom_utils.h"
class Q_DECL_HIDDEN KisGuidesConfig::Private
{
public:
Private()
: showGuides(false)
, snapToGuides(false)
, lockGuides(false)
, rulersMultiple2(false)
, unitType(KoUnit::Pixel)
{}
bool operator==(const Private &rhs) {
return horzGuideLines == rhs.horzGuideLines &&
vertGuideLines == rhs.vertGuideLines &&
showGuides == rhs.showGuides &&
snapToGuides == rhs.snapToGuides &&
lockGuides == rhs.lockGuides &&
guidesColor == rhs.guidesColor &&
guidesLineType == rhs.guidesLineType &&
rulersMultiple2 == rhs.rulersMultiple2 &&
unitType == rhs.unitType;
}
QList<qreal> horzGuideLines;
QList<qreal> vertGuideLines;
bool showGuides;
bool snapToGuides;
bool lockGuides;
bool rulersMultiple2;
KoUnit::Type unitType;
QColor guidesColor;
LineTypeInternal guidesLineType;
Qt::PenStyle toPenStyle(LineTypeInternal type);
};
KisGuidesConfig::KisGuidesConfig()
: d(new Private())
{
loadStaticData();
}
KisGuidesConfig::~KisGuidesConfig()
{
}
KisGuidesConfig::KisGuidesConfig(const KisGuidesConfig &rhs)
: d(new Private(*rhs.d))
{
}
KisGuidesConfig& KisGuidesConfig::operator=(const KisGuidesConfig &rhs)
{
if (&rhs != this) {
*d = *rhs.d;
}
return *this;
}
bool KisGuidesConfig::operator==(const KisGuidesConfig &rhs) const
{
return *d == *rhs.d;
}
+bool KisGuidesConfig::hasSamePositionAs(const KisGuidesConfig &rhs) const
+{
+ return horizontalGuideLines() == rhs.horizontalGuideLines() &&
+ verticalGuideLines() == rhs.verticalGuideLines();
+}
+
void KisGuidesConfig::setHorizontalGuideLines(const QList<qreal> &lines)
{
d->horzGuideLines = lines;
}
void KisGuidesConfig::setVerticalGuideLines(const QList<qreal> &lines)
{
d->vertGuideLines = lines;
}
void KisGuidesConfig::addGuideLine(Qt::Orientation o, qreal pos)
{
if (o == Qt::Horizontal) {
d->horzGuideLines.append(pos);
} else {
d->vertGuideLines.append(pos);
}
}
bool KisGuidesConfig::showGuideLines() const
{
return d->showGuides;
}
void KisGuidesConfig::setShowGuideLines(bool show)
{
d->showGuides = show;
}
bool KisGuidesConfig::showGuides() const
{
return d->showGuides;
}
void KisGuidesConfig::setShowGuides(bool value)
{
d->showGuides = value;
}
bool KisGuidesConfig::lockGuides() const
{
return d->lockGuides;
}
void KisGuidesConfig::setLockGuides(bool value)
{
d->lockGuides = value;
}
bool KisGuidesConfig::snapToGuides() const
{
return d->snapToGuides;
}
void KisGuidesConfig::setSnapToGuides(bool value)
{
d->snapToGuides = value;
}
bool KisGuidesConfig::rulersMultiple2() const
{
return d->rulersMultiple2;
}
void KisGuidesConfig::setRulersMultiple2(bool value)
{
d->rulersMultiple2 = value;
}
KoUnit::Type KisGuidesConfig::unitType() const
{
return d->unitType;
}
void KisGuidesConfig::setUnitType(const KoUnit::Type type)
{
d->unitType = type;
}
KisGuidesConfig::LineTypeInternal
KisGuidesConfig::guidesLineType() const
{
return d->guidesLineType;
}
void KisGuidesConfig::setGuidesLineType(LineTypeInternal value)
{
d->guidesLineType = value;
}
QColor KisGuidesConfig::guidesColor() const
{
return d->guidesColor;
}
void KisGuidesConfig::setGuidesColor(const QColor &value)
{
d->guidesColor = value;
}
Qt::PenStyle KisGuidesConfig::Private::toPenStyle(LineTypeInternal type) {
return type == LINE_SOLID ? Qt::SolidLine :
type == LINE_DASHED ? Qt::DashLine :
type == LINE_DOTTED ? Qt::DotLine :
Qt::DashDotDotLine;
}
QPen KisGuidesConfig::guidesPen() const
{
return QPen(d->guidesColor, 0, d->toPenStyle(d->guidesLineType));
}
const QList<qreal>& KisGuidesConfig::horizontalGuideLines() const
{
return d->horzGuideLines;
}
const QList<qreal>& KisGuidesConfig::verticalGuideLines() const
{
return d->vertGuideLines;
}
bool KisGuidesConfig::hasGuides() const
{
return !d->horzGuideLines.isEmpty() || !d->vertGuideLines.isEmpty();
}
void KisGuidesConfig::loadStaticData()
{
KisConfig cfg(true);
d->guidesLineType = LineTypeInternal(cfg.guidesLineStyle());
d->guidesColor = cfg.guidesColor();
}
void KisGuidesConfig::saveStaticData() const
{
KisConfig cfg(false);
cfg.setGuidesLineStyle(d->guidesLineType);
cfg.setGuidesColor(d->guidesColor);
}
QDomElement KisGuidesConfig::saveToXml(QDomDocument& doc, const QString &tag) const
{
QDomElement guidesElement = doc.createElement(tag);
KisDomUtils::saveValue(&guidesElement, "showGuides", d->showGuides);
KisDomUtils::saveValue(&guidesElement, "snapToGuides", d->snapToGuides);
KisDomUtils::saveValue(&guidesElement, "lockGuides", d->lockGuides);
KisDomUtils::saveValue(&guidesElement, "horizontalGuides", d->horzGuideLines.toVector());
KisDomUtils::saveValue(&guidesElement, "verticalGuides", d->vertGuideLines.toVector());
KisDomUtils::saveValue(&guidesElement, "rulersMultiple2", d->rulersMultiple2);
KoUnit tmp(d->unitType);
KisDomUtils::saveValue(&guidesElement, "unit", tmp.symbol());
return guidesElement;
}
bool KisGuidesConfig::loadFromXml(const QDomElement &parent)
{
bool result = true;
result &= KisDomUtils::loadValue(parent, "showGuides", &d->showGuides);
result &= KisDomUtils::loadValue(parent, "snapToGuides", &d->snapToGuides);
result &= KisDomUtils::loadValue(parent, "lockGuides", &d->lockGuides);
QVector<qreal> hGuides;
QVector<qreal> vGuides;
result &= KisDomUtils::loadValue(parent, "horizontalGuides", &hGuides);
result &= KisDomUtils::loadValue(parent, "verticalGuides", &vGuides);
d->horzGuideLines = QList<qreal>::fromVector(hGuides);
d->vertGuideLines = QList<qreal>::fromVector(vGuides);
result &= KisDomUtils::loadValue(parent, "rulersMultiple2", &d->rulersMultiple2);
QString unit;
result &= KisDomUtils::loadValue(parent, "unit", &unit);
bool ok = false;
KoUnit tmp = KoUnit::fromSymbol(unit, &ok);
if (ok) {
d->unitType = tmp.type();
}
result &= ok;
return result;
}
bool KisGuidesConfig::isDefault() const
{
KisGuidesConfig defaultObject;
defaultObject.loadStaticData();
return *this == defaultObject;
}
diff --git a/libs/ui/canvas/kis_guides_config.h b/libs/ui/canvas/kis_guides_config.h
index ed97a51333..ca4748f80b 100644
--- a/libs/ui/canvas/kis_guides_config.h
+++ b/libs/ui/canvas/kis_guides_config.h
@@ -1,130 +1,131 @@
/* This file is part of the KDE project
Copyright (C) 2006 Laurent Montel <montel@kde.org>
Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
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 KOGUIDESDATA_H
#define KOGUIDESDATA_H
#include "kritaui_export.h"
#include <QScopedPointer>
#include <QList>
#include <boost/operators.hpp>
#include <KoUnit.h>
class QDomElement;
class QDomDocument;
class QColor;
class QPen;
class KRITAUI_EXPORT KisGuidesConfig : boost::equality_comparable<KisGuidesConfig>
{
public:
enum LineTypeInternal {
LINE_SOLID = 0,
LINE_DASHED,
LINE_DOTTED
};
public:
KisGuidesConfig();
~KisGuidesConfig();
KisGuidesConfig(const KisGuidesConfig &rhs);
KisGuidesConfig& operator=(const KisGuidesConfig &rhs);
bool operator==(const KisGuidesConfig &rhs) const;
+ bool hasSamePositionAs(const KisGuidesConfig &rhs) const;
/**
* @brief Set the positions of the horizontal guide lines
*
* @param lines a list of positions of the horizontal guide lines
*/
void setHorizontalGuideLines(const QList<qreal> &lines);
/**
* @brief Set the positions of the vertical guide lines
*
* @param lines a list of positions of the vertical guide lines
*/
void setVerticalGuideLines(const QList<qreal> &lines);
/**
* @brief Add a guide line to the canvas.
*
* @param orientation the orientation of the guide line
* @param position the position in document coordinates of the guide line
*/
void addGuideLine(Qt::Orientation orientation, qreal position);
/**
* @brief Display or not guide lines
*/
bool showGuideLines() const;
/**
* @param show display or not guide line
*/
void setShowGuideLines(bool show);
bool showGuides() const;
void setShowGuides(bool value);
bool lockGuides() const;
void setLockGuides(bool value);
bool snapToGuides() const;
void setSnapToGuides(bool value);
bool rulersMultiple2() const;
void setRulersMultiple2(bool value);
KoUnit::Type unitType() const;
void setUnitType(KoUnit::Type type);
LineTypeInternal guidesLineType() const;
void setGuidesLineType(LineTypeInternal value);
QColor guidesColor() const;
void setGuidesColor(const QColor &value);
QPen guidesPen() const;
/// Returns the list of horizontal guide lines.
const QList<qreal>& horizontalGuideLines() const;
/// Returns the list of vertical guide lines.
const QList<qreal>& verticalGuideLines() const;
bool hasGuides() const;
void loadStaticData();
void saveStaticData() const;
QDomElement saveToXml(QDomDocument& doc, const QString &tag) const;
bool loadFromXml(const QDomElement &parent);
bool isDefault() const;
private:
class Private;
const QScopedPointer<Private> d;
};
#endif
diff --git a/libs/ui/canvas/kis_guides_manager.cpp b/libs/ui/canvas/kis_guides_manager.cpp
index b6e1246435..1325063826 100644
--- a/libs/ui/canvas/kis_guides_manager.cpp
+++ b/libs/ui/canvas/kis_guides_manager.cpp
@@ -1,814 +1,833 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_guides_manager.h"
#include <QMenu>
#include <QGuiApplication>
#include "kis_guides_decoration.h"
#include <KoRuler.h>
#include "kis_guides_config.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include "kis_signals_blocker.h"
#include "input/kis_input_manager.h"
#include "kis_coordinates_converter.h"
#include "kis_zoom_manager.h"
#include "kis_signal_auto_connection.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_algebra_2d.h"
#include <KoSnapGuide.h>
#include "kis_snap_line_strategy.h"
#include "kis_change_guides_command.h"
#include "kis_snap_config.h"
#include "kis_canvas2.h"
#include "kis_signal_compressor.h"
struct KisGuidesManager::Private
{
Private(KisGuidesManager *_q)
: q(_q),
decoration(0),
invalidGuide(Qt::Horizontal, -1),
currentGuide(invalidGuide),
cursorSwitched(false),
dragStartGuidePos(0),
- updateDocumentCompressor(40, KisSignalCompressor::FIRST_ACTIVE),
shouldSetModified(false) {}
KisGuidesManager *q;
KisGuidesDecoration *decoration;
KisGuidesConfig guidesConfig;
+ KisGuidesConfig oldGuidesConfig;
KisSnapConfig snapConfig;
QPointer<KisView> view;
typedef QPair<Qt::Orientation, int> GuideHandle;
GuideHandle findGuide(const QPointF &docPos);
bool isGuideValid(const GuideHandle &h);
qreal guideValue(const GuideHandle &h);
void setGuideValue(const GuideHandle &h, qreal value);
void deleteGuide(const GuideHandle &h);
const GuideHandle invalidGuide;
bool updateCursor(const QPointF &docPos, bool forceDisableCursor = false);
void initDragStart(const GuideHandle &guide,
const QPointF &dragStart,
qreal guideValue,
bool snapToStart);
bool mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers);
bool mouseReleaseHandler(const QPointF &docPos);
void updateSnappingStatus(const KisGuidesConfig &value);
QPointF alignToPixels(const QPointF docPoint);
QPointF getDocPointFromEvent(QEvent *event);
Qt::MouseButton getButtonFromEvent(QEvent *event);
QAction* createShortenedAction(const QString &text, const QString &parentId, QObject *parent);
void syncAction(const QString &actionName, bool value);
+ bool needsUndoCommand();
+ void createUndoCommandIfNeeded();
GuideHandle currentGuide;
bool cursorSwitched;
QCursor oldCursor;
QPointF dragStartDoc;
QPointF dragPointerOffset;
qreal dragStartGuidePos;
KisSignalAutoConnectionsStore viewConnections;
- KisSignalCompressor updateDocumentCompressor;
bool shouldSetModified;
};
KisGuidesManager::KisGuidesManager(QObject *parent)
: QObject(parent),
m_d(new Private(this))
{
- connect(&m_d->updateDocumentCompressor, SIGNAL(timeout()), SLOT(slotUploadConfigToDocument()));
}
KisGuidesManager::~KisGuidesManager()
{
}
void KisGuidesManager::setGuidesConfig(const KisGuidesConfig &config)
{
if (config == m_d->guidesConfig) return;
- setGuidesConfigImpl(config, true);
+ setGuidesConfigImpl(config, !config.hasSamePositionAs(m_d->guidesConfig));
+ slotUploadConfigToDocument();
}
void KisGuidesManager::slotDocumentRequestedConfig(const KisGuidesConfig &config)
{
if (config == m_d->guidesConfig) return;
setGuidesConfigImpl(config, false);
}
void KisGuidesManager::slotUploadConfigToDocument()
{
const KisGuidesConfig &value = m_d->guidesConfig;
KisDocument *doc = m_d->view ? m_d->view->document() : 0;
if (doc) {
KisSignalsBlocker b(doc);
- if (m_d->shouldSetModified) {
- KUndo2Command *cmd = new KisChangeGuidesCommand(doc, value);
- doc->addCommand(cmd);
- } else {
- doc->setGuidesConfig(value);
- }
+ // we've made KisChangeGuidesCommand post-exec, so in all situations
+ // we will replace the whole config
+ doc->setGuidesConfig(value);
value.saveStaticData();
}
m_d->shouldSetModified = false;
}
void KisGuidesManager::setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified)
{
m_d->guidesConfig = value;
if (m_d->decoration && value != m_d->decoration->guidesConfig()) {
m_d->decoration->setVisible(value.showGuides());
m_d->decoration->setGuidesConfig(value);
}
m_d->shouldSetModified |= emitModified;
- m_d->updateDocumentCompressor.start();
const bool shouldFilterEvent =
value.showGuides() && !value.lockGuides() && value.hasGuides();
attachEventFilterImpl(shouldFilterEvent);
syncActionsStatus();
if (!m_d->isGuideValid(m_d->currentGuide)) {
m_d->updateSnappingStatus(value);
}
if (m_d->view) {
m_d->view->document()->setUnit(KoUnit(m_d->guidesConfig.unitType()));
m_d->view->viewManager()->actionManager()->actionByName("ruler_pixel_multiple2")->setChecked(value.rulersMultiple2());
}
emit sigRequestUpdateGuidesConfig(m_d->guidesConfig);
}
void KisGuidesManager::attachEventFilterImpl(bool value)
{
if (!m_d->view) return;
KisInputManager *inputManager = m_d->view->globalInputManager();
if (inputManager) {
if (value) {
inputManager->attachPriorityEventFilter(this, 100);
} else {
inputManager->detachPriorityEventFilter(this);
}
}
}
void KisGuidesManager::Private::syncAction(const QString &actionName, bool value)
{
KisActionManager *actionManager = view->viewManager()->actionManager();
KisAction *action = actionManager->actionByName(actionName);
KIS_ASSERT_RECOVER_RETURN(action);
KisSignalsBlocker b(action);
action->setChecked(value);
}
+bool KisGuidesManager::Private::needsUndoCommand()
+{
+ return !(oldGuidesConfig.hasSamePositionAs(guidesConfig));
+}
+
+void KisGuidesManager::Private::createUndoCommandIfNeeded()
+{
+ KisDocument *doc = view ? view->document() : 0;
+ if (doc && needsUndoCommand()) {
+ KUndo2Command *cmd = new KisChangeGuidesCommand(doc, oldGuidesConfig, guidesConfig);
+ doc->addCommand(cmd);
+ }
+}
+
void KisGuidesManager::syncActionsStatus()
{
if (!m_d->view) return;
m_d->syncAction("view_show_guides", m_d->guidesConfig.showGuides());
m_d->syncAction("view_lock_guides", m_d->guidesConfig.lockGuides());
m_d->syncAction("view_snap_to_guides", m_d->guidesConfig.snapToGuides());
m_d->syncAction("view_snap_orthogonal", m_d->snapConfig.orthogonal());
m_d->syncAction("view_snap_node", m_d->snapConfig.node());
m_d->syncAction("view_snap_extension", m_d->snapConfig.extension());
m_d->syncAction("view_snap_intersection", m_d->snapConfig.intersection());
m_d->syncAction("view_snap_bounding_box", m_d->snapConfig.boundingBox());
m_d->syncAction("view_snap_image_bounds", m_d->snapConfig.imageBounds());
m_d->syncAction("view_snap_image_center", m_d->snapConfig.imageCenter());
m_d->syncAction("view_snap_to_pixel",m_d->snapConfig.toPixel());
}
void KisGuidesManager::Private::updateSnappingStatus(const KisGuidesConfig &value)
{
if (!view) return;
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
KisSnapLineStrategy *guidesSnap = 0;
if (value.snapToGuides()) {
guidesSnap = new KisSnapLineStrategy(KoSnapGuide::GuideLineSnapping);
guidesSnap->setHorizontalLines(value.horizontalGuideLines());
guidesSnap->setVerticalLines(value.verticalGuideLines());
}
snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap);
snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap);
snapGuide->enableSnapStrategy(KoSnapGuide::OrthogonalSnapping, snapConfig.orthogonal());
snapGuide->enableSnapStrategy(KoSnapGuide::NodeSnapping, snapConfig.node());
snapGuide->enableSnapStrategy(KoSnapGuide::ExtensionSnapping, snapConfig.extension());
snapGuide->enableSnapStrategy(KoSnapGuide::IntersectionSnapping, snapConfig.intersection());
snapGuide->enableSnapStrategy(KoSnapGuide::BoundingBoxSnapping, snapConfig.boundingBox());
snapGuide->enableSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, snapConfig.imageBounds());
snapGuide->enableSnapStrategy(KoSnapGuide::DocumentCenterSnapping, snapConfig.imageCenter());
snapGuide->enableSnapStrategy(KoSnapGuide::PixelSnapping, snapConfig.toPixel());
snapConfig.saveStaticData();
}
bool KisGuidesManager::showGuides() const
{
return m_d->guidesConfig.showGuides();
}
void KisGuidesManager::setShowGuides(bool value)
{
m_d->guidesConfig.setShowGuides(value);
setGuidesConfigImpl(m_d->guidesConfig);
+ slotUploadConfigToDocument();
}
bool KisGuidesManager::lockGuides() const
{
return m_d->guidesConfig.lockGuides();
}
void KisGuidesManager::setLockGuides(bool value)
{
m_d->guidesConfig.setLockGuides(value);
setGuidesConfigImpl(m_d->guidesConfig);
+ slotUploadConfigToDocument();
}
bool KisGuidesManager::snapToGuides() const
{
return m_d->guidesConfig.snapToGuides();
}
void KisGuidesManager::setSnapToGuides(bool value)
{
m_d->guidesConfig.setSnapToGuides(value);
setGuidesConfigImpl(m_d->guidesConfig);
+ slotUploadConfigToDocument();
}
bool KisGuidesManager::rulersMultiple2() const
{
return m_d->guidesConfig.rulersMultiple2();
}
void KisGuidesManager::setRulersMultiple2(bool value)
{
m_d->guidesConfig.setRulersMultiple2(value);
setGuidesConfigImpl(m_d->guidesConfig);
+ slotUploadConfigToDocument();
}
KoUnit::Type KisGuidesManager::unitType() const
{
return m_d->guidesConfig.unitType();
}
void KisGuidesManager::setUnitType(const KoUnit::Type type)
{
m_d->guidesConfig.setUnitType(type);
setGuidesConfigImpl(m_d->guidesConfig, false);
+ slotUploadConfigToDocument();
}
void KisGuidesManager::setup(KisActionManager *actionManager)
{
KisAction *action = 0;
action = actionManager->createAction("view_show_guides");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setShowGuides(bool)));
action = actionManager->createAction("view_lock_guides");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setLockGuides(bool)));
action = actionManager->createAction("view_snap_to_guides");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToGuides(bool)));
action = actionManager->createAction("show_snap_options_popup");
connect(action, SIGNAL(triggered()), this, SLOT(slotShowSnapOptions()));
action = actionManager->createAction("view_snap_orthogonal");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapOrthogonal(bool)));
action = actionManager->createAction("view_snap_node");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapNode(bool)));
action = actionManager->createAction("view_snap_extension");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapExtension(bool)));
action = actionManager->createAction("view_snap_intersection");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapIntersection(bool)));
action = actionManager->createAction("view_snap_bounding_box");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapBoundingBox(bool)));
action = actionManager->createAction("view_snap_image_bounds");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageBounds(bool)));
action = actionManager->createAction("view_snap_image_center");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageCenter(bool)));
action = actionManager->createAction("view_snap_to_pixel");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToPixel(bool)));
m_d->updateSnappingStatus(m_d->guidesConfig);
syncActionsStatus();
}
void KisGuidesManager::setView(QPointer<KisView> view)
{
if (m_d->view) {
KoSnapGuide *snapGuide = m_d->view->canvasBase()->snapGuide();
snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, 0);
snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, false);
- if (m_d->updateDocumentCompressor.isActive()) {
- m_d->updateDocumentCompressor.stop();
- slotUploadConfigToDocument();
- }
+ slotUploadConfigToDocument();
m_d->decoration = 0;
m_d->viewConnections.clear();
attachEventFilterImpl(false);
}
m_d->view = view;
if (m_d->view) {
KisGuidesDecoration* decoration = qobject_cast<KisGuidesDecoration*>(m_d->view->canvasBase()->decoration(GUIDES_DECORATION_ID).data());
if (!decoration) {
decoration = new KisGuidesDecoration(m_d->view);
m_d->view->canvasBase()->addDecoration(decoration);
}
m_d->decoration = decoration;
m_d->guidesConfig = m_d->view->document()->guidesConfig();
setGuidesConfigImpl(m_d->guidesConfig, false);
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation,QPoint)),
this, SLOT(slotGuideCreationInProgress(Qt::Orientation,QPoint)));
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation,QPoint)),
this, SLOT(slotGuideCreationFinished(Qt::Orientation,QPoint)));
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation,QPoint)),
this, SLOT(slotGuideCreationInProgress(Qt::Orientation,QPoint)));
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation,QPoint)),
this, SLOT(slotGuideCreationFinished(Qt::Orientation,QPoint)));
m_d->viewConnections.addUniqueConnection(
m_d->view->document(), SIGNAL(sigGuidesConfigChanged(KisGuidesConfig)),
this, SLOT(slotDocumentRequestedConfig(KisGuidesConfig)));
}
}
KisGuidesManager::Private::GuideHandle
KisGuidesManager::Private::findGuide(const QPointF &docPos)
{
const int snapRadius = 16;
GuideHandle nearestGuide = invalidGuide;
qreal nearestRadius = std::numeric_limits<int>::max();
for (int i = 0; i < guidesConfig.horizontalGuideLines().size(); i++) {
const qreal guide = guidesConfig.horizontalGuideLines()[i];
const qreal radius = qAbs(docPos.y() - guide);
if (radius < snapRadius && radius < nearestRadius) {
nearestGuide = GuideHandle(Qt::Horizontal, i);
nearestRadius = radius;
}
}
for (int i = 0; i < guidesConfig.verticalGuideLines().size(); i++) {
const qreal guide = guidesConfig.verticalGuideLines()[i];
const qreal radius = qAbs(docPos.x() - guide);
if (radius < snapRadius && radius < nearestRadius) {
nearestGuide = GuideHandle(Qt::Vertical, i);
nearestRadius = radius;
}
}
return nearestGuide;
}
bool KisGuidesManager::Private::isGuideValid(const GuideHandle &h)
{
return h.second >= 0;
}
qreal KisGuidesManager::Private::guideValue(const GuideHandle &h)
{
return h.first == Qt::Horizontal ?
guidesConfig.horizontalGuideLines()[h.second] :
guidesConfig.verticalGuideLines()[h.second];
}
void KisGuidesManager::Private::setGuideValue(const GuideHandle &h, qreal value)
{
if (h.first == Qt::Horizontal) {
QList<qreal> guides = guidesConfig.horizontalGuideLines();
guides[h.second] = value;
guidesConfig.setHorizontalGuideLines(guides);
} else {
QList<qreal> guides = guidesConfig.verticalGuideLines();
guides[h.second] = value;
guidesConfig.setVerticalGuideLines(guides);
}
}
void KisGuidesManager::Private::deleteGuide(const GuideHandle &h)
{
if (h.first == Qt::Horizontal) {
QList<qreal> guides = guidesConfig.horizontalGuideLines();
guides.removeAt(h.second);
guidesConfig.setHorizontalGuideLines(guides);
} else {
QList<qreal> guides = guidesConfig.verticalGuideLines();
guides.removeAt(h.second);
guidesConfig.setVerticalGuideLines(guides);
}
}
bool KisGuidesManager::Private::updateCursor(const QPointF &docPos, bool forceDisableCursor)
{
KisCanvas2 *canvas = view->canvasBase();
const GuideHandle guide = findGuide(docPos);
const bool guideValid = isGuideValid(guide) && !forceDisableCursor;
if (guideValid && !cursorSwitched) {
oldCursor = canvas->canvasWidget()->cursor();
}
if (guideValid) {
cursorSwitched = true;
QCursor newCursor = guide.first == Qt::Horizontal ?
Qt::SizeVerCursor : Qt::SizeHorCursor;
canvas->canvasWidget()->setCursor(newCursor);
}
if (!guideValid && cursorSwitched) {
canvas->canvasWidget()->setCursor(oldCursor);
cursorSwitched = false;
}
return guideValid;
}
void KisGuidesManager::Private::initDragStart(const GuideHandle &guide,
const QPointF &dragStart,
qreal guideValue,
bool snapToStart)
{
currentGuide = guide;
dragStartDoc = dragStart;
dragStartGuidePos = guideValue;
dragPointerOffset =
guide.first == Qt::Horizontal ?
QPointF(0, dragStartGuidePos - dragStartDoc.y()) :
QPointF(dragStartGuidePos - dragStartDoc.x(), 0);
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
snapGuide->reset();
if (snapToStart) {
KisSnapLineStrategy *strategy = new KisSnapLineStrategy();
strategy->addLine(guide.first, guideValue);
snapGuide->addCustomSnapStrategy(strategy);
}
}
QPointF KisGuidesManager::Private::alignToPixels(const QPointF docPoint)
{
KisCanvas2 *canvas = view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
QPoint imagePoint = converter->documentToImage(docPoint).toPoint();
return converter->imageToDocument(imagePoint);
}
bool KisGuidesManager::Private::mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers)
{
if (isGuideValid(currentGuide)) {
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
const QPointF snappedPos = snapGuide->snap(docPos, dragPointerOffset, modifiers);
const QPointF offset = snappedPos - dragStartDoc;
const qreal newValue = dragStartGuidePos +
(currentGuide.first == Qt::Horizontal ?
offset.y() : offset.x());
setGuideValue(currentGuide, newValue);
q->setGuidesConfigImpl(guidesConfig);
}
return updateCursor(docPos);
}
bool KisGuidesManager::Private::mouseReleaseHandler(const QPointF &docPos)
{
bool result = false;
KisCanvas2 *canvas = view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
if (isGuideValid(currentGuide)) {
const QRectF docRect = converter->imageRectInDocumentPixels();
// TODO: enable work rect after we fix painting guides
// outside canvas in openGL mode
const QRectF workRect = KisAlgebra2D::blowRect(docRect, 0 /*0.2*/);
if (!workRect.contains(docPos)) {
deleteGuide(currentGuide);
q->setGuidesConfigImpl(guidesConfig);
/**
* When we delete a guide, it might happen that we are
* deleting the last guide. Therefore we should eat the
* corresponding event so that the event filter would stop
* the filter processing.
*/
result = true;
}
currentGuide = invalidGuide;
dragStartDoc = QPointF();
dragPointerOffset = QPointF();
dragStartGuidePos = 0;
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
snapGuide->reset();
updateSnappingStatus(guidesConfig);
}
+ q->slotUploadConfigToDocument();
+ createUndoCommandIfNeeded();
+
return updateCursor(docPos) | result;
}
QPointF KisGuidesManager::Private::getDocPointFromEvent(QEvent *event)
{
QPointF result;
KisCanvas2 *canvas = view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
if (event->type() == QEvent::Enter) {
QEnterEvent *enterEvent = static_cast<QEnterEvent*>(event);
result = alignToPixels(converter->widgetToDocument(enterEvent->pos()));
} else if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
result = alignToPixels(converter->widgetToDocument(mouseEvent->pos()));
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
result = alignToPixels(converter->widgetToDocument(tabletEvent->pos()));
} else {
// we shouldn't silently return QPointF(0,0), higher level code may
// snap to some unexpected guide
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "event type is not supported!");
}
return result;
}
Qt::MouseButton KisGuidesManager::Private::getButtonFromEvent(QEvent *event)
{
Qt::MouseButton button = Qt::NoButton;
if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
button = mouseEvent->button();
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
button = tabletEvent->button();
}
return button;
}
bool KisGuidesManager::eventFilter(QObject *obj, QEvent *event)
{
if (!m_d->view || obj != m_d->view->canvasBase()->canvasWidget()) return false;
bool retval = false;
switch (event->type()) {
case QEvent::Leave:
m_d->updateCursor(QPointF(), true);
break;
case QEvent::Enter:
case QEvent::TabletMove:
case QEvent::MouseMove: {
const QPointF docPos = m_d->getDocPointFromEvent(event);
const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers();
// we should never eat Enter events, input manager may get crazy about it
retval = m_d->mouseMoveHandler(docPos, modifiers) && event->type() != QEvent::Enter;
break;
}
case QEvent::TabletPress:
case QEvent::MouseButtonPress: {
if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break;
const QPointF docPos = m_d->getDocPointFromEvent(event);
const Private::GuideHandle guide = m_d->findGuide(docPos);
const bool guideValid = m_d->isGuideValid(guide);
if (guideValid) {
+ m_d->oldGuidesConfig = m_d->guidesConfig;
m_d->initDragStart(guide, docPos, m_d->guideValue(guide), true);
}
retval = m_d->updateCursor(docPos);
break;
}
case QEvent::TabletRelease:
case QEvent::MouseButtonRelease: {
if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break;
const QPointF docPos = m_d->getDocPointFromEvent(event);
retval = m_d->mouseReleaseHandler(docPos);
break;
}
default:
break;
}
return !retval ? QObject::eventFilter(obj, event) : true;
}
void KisGuidesManager::slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos)
{
if (m_d->guidesConfig.lockGuides()) return;
KisCanvas2 *canvas = m_d->view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos);
const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos));
if (m_d->isGuideValid(m_d->currentGuide)) {
const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers();
m_d->mouseMoveHandler(docPos, modifiers);
} else {
m_d->guidesConfig.setShowGuides(true);
+ m_d->oldGuidesConfig = m_d->guidesConfig;
+
if (orientation == Qt::Horizontal) {
QList<qreal> guides = m_d->guidesConfig.horizontalGuideLines();
guides.append(docPos.y());
m_d->currentGuide.first = orientation;
m_d->currentGuide.second = guides.size() - 1;
m_d->guidesConfig.setHorizontalGuideLines(guides);
m_d->initDragStart(m_d->currentGuide, docPos, docPos.y(), false);
} else {
QList<qreal> guides = m_d->guidesConfig.verticalGuideLines();
guides.append(docPos.x());
m_d->currentGuide.first = orientation;
m_d->currentGuide.second = guides.size() - 1;
m_d->guidesConfig.setVerticalGuideLines(guides);
m_d->initDragStart(m_d->currentGuide, docPos, docPos.x(), false);
}
setGuidesConfigImpl(m_d->guidesConfig);
}
}
void KisGuidesManager::slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos)
{
Q_UNUSED(orientation);
if (m_d->guidesConfig.lockGuides()) return;
KisCanvas2 *canvas = m_d->view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos);
const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos));
m_d->mouseReleaseHandler(docPos);
}
QAction* KisGuidesManager::Private::createShortenedAction(const QString &text, const QString &parentId, QObject *parent)
{
KisActionManager *actionManager = view->viewManager()->actionManager();
QAction *action = 0;
KisAction *parentAction = 0;
action = new QAction(text, parent);
action->setCheckable(true);
parentAction = actionManager->actionByName(parentId);
action->setChecked(parentAction->isChecked());
connect(action, SIGNAL(toggled(bool)), parentAction, SLOT(setChecked(bool)));
return action;
}
void KisGuidesManager::slotShowSnapOptions()
{
const QPoint pos = QCursor::pos();
QMenu menu;
menu.addSection(i18n("Snap to:"));
menu.addAction(m_d->createShortenedAction(i18n("Grid"), "view_snap_to_grid", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Guides"), "view_snap_to_guides", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Pixel"), "view_snap_to_pixel", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Orthogonal"), "view_snap_orthogonal", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Node"), "view_snap_node", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Extension"), "view_snap_extension", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Intersection"), "view_snap_intersection", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Bounding Box"), "view_snap_bounding_box", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Image Bounds"), "view_snap_image_bounds", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Image Center"), "view_snap_image_center", &menu));
menu.exec(pos);
}
void KisGuidesManager::setSnapOrthogonal(bool value)
{
m_d->snapConfig.setOrthogonal(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapNode(bool value)
{
m_d->snapConfig.setNode(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapExtension(bool value)
{
m_d->snapConfig.setExtension(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapIntersection(bool value)
{
m_d->snapConfig.setIntersection(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapBoundingBox(bool value)
{
m_d->snapConfig.setBoundingBox(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapImageBounds(bool value)
{
m_d->snapConfig.setImageBounds(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapImageCenter(bool value)
{
m_d->snapConfig.setImageCenter(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapToPixel(bool value)
{
m_d->snapConfig.setToPixel(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
diff --git a/libs/ui/canvas/kis_infinity_manager.cpp b/libs/ui/canvas/kis_infinity_manager.cpp
index 2d0b826839..1ce57aa65a 100644
--- a/libs/ui/canvas/kis_infinity_manager.cpp
+++ b/libs/ui/canvas/kis_infinity_manager.cpp
@@ -1,305 +1,312 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_infinity_manager.h"
#include <QPainter>
#include <klocalizedstring.h>
#include <KoCanvasController.h>
#include <kis_debug.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <input/kis_input_manager.h>
#include <kis_config.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_canvas_controller.h>
#include <KisView.h>
#include <kis_algebra_2d.h>
KisInfinityManager::KisInfinityManager(QPointer<KisView>view, KisCanvas2 *canvas)
: KisCanvasDecoration(INFINITY_DECORATION_ID, view),
m_filteringEnabled(false),
m_cursorSwitched(false),
m_sideRects(NSides),
m_canvas(canvas)
{
connect(canvas, SIGNAL(documentOffsetUpdateFinished()), SLOT(imagePositionChanged()));
}
inline void KisInfinityManager::addDecoration(const QRect &areaRect, const QPointF &handlePoint, qreal angle, Side side)
{
QTransform t;
t.rotate(angle);
t = t * QTransform::fromTranslate(handlePoint.x(), handlePoint.y());
m_handleTransform << t;
m_decorationPath.addRect(areaRect);
m_sideRects[side] = areaRect;
}
void KisInfinityManager::imagePositionChanged()
{
const QRect imageRect = m_canvas->coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect();
const QRect widgetRect = m_canvas->canvasWidget()->rect();
KisConfig cfg(true);
qreal vastScrolling = cfg.vastScrolling();
int xReserve = vastScrolling * widgetRect.width();
int yReserve = vastScrolling * widgetRect.height();
int xThreshold = imageRect.width() - 0.4 * xReserve;
int yThreshold = imageRect.height() - 0.4 * yReserve;
const int stripeWidth = 48;
int xCut = widgetRect.width() - stripeWidth;
int yCut = widgetRect.height() - stripeWidth;
m_decorationPath = QPainterPath();
m_decorationPath.setFillRule(Qt::WindingFill);
m_handleTransform.clear();
m_sideRects.clear();
m_sideRects.resize(NSides);
bool visible = false;
if (imageRect.x() <= -xThreshold) {
QRect areaRect(widgetRect.adjusted(xCut, 0, 0, 0));
QPointF pt = areaRect.center() + QPointF(-0.1 * stripeWidth, 0);
addDecoration(areaRect, pt, 0, Right);
visible = true;
}
if (imageRect.y() <= -yThreshold) {
QRect areaRect(widgetRect.adjusted(0, yCut, 0, 0));
QPointF pt = areaRect.center() + QPointF(0, -0.1 * stripeWidth);
addDecoration(areaRect, pt, 90, Bottom);
visible = true;
}
if (imageRect.right() > widgetRect.width() + xThreshold) {
QRect areaRect(widgetRect.adjusted(0, 0, -xCut, 0));
QPointF pt = areaRect.center() + QPointF(0.1 * stripeWidth, 0);
addDecoration(areaRect, pt, 180, Left);
visible = true;
}
if (imageRect.bottom() > widgetRect.height() + yThreshold) {
QRect areaRect(widgetRect.adjusted(0, 0, 0, -yCut));
QPointF pt = areaRect.center() + QPointF(0, 0.1 * stripeWidth);
addDecoration(areaRect, pt, 270, Top);
visible = true;
}
if (!m_filteringEnabled && visible && this->visible()) {
KisInputManager *inputManager = m_canvas->globalInputManager();
if (inputManager) {
inputManager->attachPriorityEventFilter(this);
}
m_filteringEnabled = true;
}
if (m_filteringEnabled && (!visible || !this->visible())) {
KisInputManager *inputManager = m_canvas->globalInputManager();
if (inputManager) {
inputManager->detachPriorityEventFilter(this);
}
m_filteringEnabled = false;
}
}
void KisInfinityManager::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
{
Q_UNUSED(updateArea);
Q_UNUSED(converter);
Q_UNUSED(canvas);
if (!m_filteringEnabled) return;
gc.save();
gc.setTransform(QTransform(), false);
KisConfig cfg(true);
QColor color = cfg.canvasBorderColor();
gc.fillPath(m_decorationPath, color.darker(115));
QPainterPath p = KisAlgebra2D::smallArrow();
Q_FOREACH (const QTransform &t, m_handleTransform) {
gc.fillPath(t.map(p), color);
}
gc.restore();
}
inline int expandLeft(int x0, int x1, int maxExpand)
{
return qMax(x0 - maxExpand, qMin(x0, x1));
}
inline int expandRight(int x0, int x1, int maxExpand)
{
return qMin(x0 + maxExpand, qMax(x0, x1));
}
inline QPoint getPointFromEvent(QEvent *event)
{
QPoint result;
if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
- event->type() == QEvent::MouseButtonRelease) {
+ event->type() == QEvent::MouseButtonRelease ||
+ event->type() == QEvent::Enter) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
result = mouseEvent->pos();
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
result = tabletEvent->pos();
}
return result;
}
inline Qt::MouseButton getButtonFromEvent(QEvent *event)
{
Qt::MouseButton button = Qt::NoButton;
if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
button = mouseEvent->button();
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
button = tabletEvent->button();
}
return button;
}
bool KisInfinityManager::eventFilter(QObject *obj, QEvent *event)
{
/**
* We connect our event filter to the global InputManager which is
* shared among all the canvases. Ideally we should disconnect our
* event filter whin this canvas is not active, but for now we can
* just check the destination of the event, if it is correct.
*/
if (m_canvas == NULL || obj != m_canvas->canvasWidget()) return false;
KIS_ASSERT_RECOVER_NOOP(m_filteringEnabled);
bool retval = false;
switch (event->type()) {
case QEvent::Enter:
- case QEvent::Leave:
case QEvent::MouseMove:
case QEvent::TabletMove: {
QPoint pos = getPointFromEvent(event);
if (m_decorationPath.contains(pos)) {
if (!m_cursorSwitched) {
m_oldCursor = m_canvas->canvasWidget()->cursor();
m_cursorSwitched = true;
}
m_canvas->canvasWidget()->setCursor(Qt::PointingHandCursor);
retval = true;
} else if (m_cursorSwitched) {
m_canvas->canvasWidget()->setCursor(m_oldCursor);
m_cursorSwitched = false;
}
break;
}
+ case QEvent::Leave: {
+ if (m_cursorSwitched) {
+ m_canvas->canvasWidget()->setCursor(m_oldCursor);
+ m_cursorSwitched = false;
+ }
+ break;
+ }
case QEvent::MouseButtonPress:
case QEvent::TabletPress: {
Qt::MouseButton button = getButtonFromEvent(event);
retval = button == Qt::LeftButton && m_cursorSwitched;
if (button == Qt::RightButton) {
imagePositionChanged();
}
break;
}
case QEvent::MouseButtonRelease:
case QEvent::TabletRelease: {
Qt::MouseButton button = getButtonFromEvent(event);
retval = button == Qt::LeftButton && m_cursorSwitched;
if (retval) {
QPoint pos = getPointFromEvent(event);
const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
QRect widgetRect = converter->widgetToImage(m_canvas->canvasWidget()->rect()).toAlignedRect();
KisImageWSP image = view()->image();
QRect cropRect = image->bounds();
const int hLimit = cropRect.width();
const int vLimit = cropRect.height();
if (m_sideRects[Right].contains(pos)) {
cropRect.setRight(expandRight(cropRect.right(), widgetRect.right(), hLimit));
}
if (m_sideRects[Bottom].contains(pos)) {
cropRect.setBottom(expandRight(cropRect.bottom(), widgetRect.bottom(), vLimit));
}
if (m_sideRects[Left].contains(pos)) {
cropRect.setLeft(expandLeft(cropRect.left(), widgetRect.left(), hLimit));
}
if (m_sideRects[Top].contains(pos)) {
cropRect.setTop(expandLeft(cropRect.top(), widgetRect.top(), vLimit));
}
image->resizeImage(cropRect);
// since resizing the image can cause the cursor to end up on the canvas without a move event,
// it can get stuck in an overridden state until it is changed by another event,
// and we don't want that.
if (m_cursorSwitched) {
m_canvas->canvasWidget()->setCursor(m_oldCursor);
m_cursorSwitched = false;
}
}
break;
}
default:
break;
}
return !retval ? KisCanvasDecoration::eventFilter(obj, event) : true;
}
diff --git a/libs/ui/canvas/kis_mirror_axis.cpp b/libs/ui/canvas/kis_mirror_axis.cpp
index fe3222ba7f..3c8a870db1 100644
--- a/libs/ui/canvas/kis_mirror_axis.cpp
+++ b/libs/ui/canvas/kis_mirror_axis.cpp
@@ -1,470 +1,473 @@
/*
* Copyright (c) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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_mirror_axis.h"
#include "KoConfig.h"
#ifdef HAS_ONLY_OPENGL_ES
#define GL_MULTISAMPLE 0x809D
#endif
#include <QPainter>
#include <QToolButton>
#include <QApplication>
#include <QPaintEngine>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QAction>
#include <kis_icon.h>
#include "kis_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisView.h"
#include "kis_image.h"
#include "canvas/kis_canvas_controller.h"
#include "input/kis_input_manager.h"
#include "kis_algebra_2d.h"
#include <KisMirrorAxisConfig.h>
#include <kis_signals_blocker.h>
#include <kactioncollection.h>
class KisMirrorAxis::Private
{
public:
Private(KisMirrorAxis* qq)
: q(qq)
, resourceProvider(0)
, xActive(false)
, yActive(false)
, sideMargin(10.f)
, minHandlePosition(10.f + 32.f)
, horizontalContainsCursor(false)
, verticalContainsCursor(false)
, horizontalAxis(QLineF())
, verticalAxis(QLineF())
, config(KisMirrorAxisConfig())
{ }
void setAxisPosition(float x, float y);
void recomputeVisibleAxes(QRect viewport);
KisMirrorAxis* q;
KisCanvasResourceProvider* resourceProvider;
KisImageWSP image;
QPixmap horizontalHandleIcon;
QPixmap verticalHandleIcon;
QPixmap horizontalIcon;
QPixmap verticalIcon;
QRectF horizontalHandle;
QRectF verticalHandle;
bool xActive;
bool yActive;
float sideMargin;
float minHandlePosition;
bool horizontalContainsCursor;
bool verticalContainsCursor;
QLineF horizontalAxis;
QLineF verticalAxis;
KisMirrorAxisConfig config;
};
KisMirrorAxis::KisMirrorAxis(KisCanvasResourceProvider* provider, QPointer<KisView>parent)
: KisCanvasDecoration("mirror_axis", parent)
, d(new Private(this))
{
d->resourceProvider = provider;
connect(d->resourceProvider, SIGNAL(mirrorModeChanged()), SLOT(mirrorModeChanged()));
connect(d->resourceProvider, SIGNAL(moveMirrorVerticalCenter()), SLOT(moveVerticalAxisToCenter()));
connect(d->resourceProvider, SIGNAL(moveMirrorHorizontalCenter()), SLOT(moveHorizontalAxisToCenter()));
d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal());
d->config.setMirrorVertical(d->resourceProvider->mirrorVertical());
d->horizontalIcon = KisIconUtils::loadIcon("mirrorAxis-HorizontalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->verticalIcon = KisIconUtils::loadIcon("mirrorAxis-VerticalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
d->image = parent->canvasBase()->image();
}
KisMirrorAxis::~KisMirrorAxis()
{
}
float KisMirrorAxis::handleSize() const
{
return d->config.handleSize();
}
void KisMirrorAxis::setHandleSize(float newSize)
{
if(d->config.handleSize() != newSize) {
d->config.setHandleSize(newSize);
d->horizontalIcon = KisIconUtils::loadIcon("symmetry-horizontal").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->verticalIcon = KisIconUtils::loadIcon("symmetry-vertical").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->minHandlePosition = d->sideMargin + newSize;
emit handleSizeChanged();
emit sigConfigChanged();
}
}
void KisMirrorAxis::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas)
{
Q_UNUSED(updateArea);
Q_UNUSED(converter);
Q_UNUSED(canvas);
if (!view()->isCurrent()) {
return;
}
gc.save();
gc.setPen(QPen(QColor(0, 0, 0, 128), 1));
gc.setBrush(Qt::white);
gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
QOpenGLContext *ctx = QOpenGLContext::currentContext();
- bool hasMultisample = ((gc.paintEngine()->type() == QPaintEngine::OpenGL2) &&
- (ctx->hasExtension("GL_ARB_multisample")));
-
- // QPainter cannot anti-alias the edges of circles etc. when using OpenGL
- // So instead, use native OpenGL anti-aliasing when available.
- if (hasMultisample) {
- gc.beginNativePainting();
- ctx->functions()->glEnable(GL_MULTISAMPLE);
- gc.endNativePainting();
+ bool hasMultisample = false;
+ if (ctx) {
+ hasMultisample = ((gc.paintEngine()->type() == QPaintEngine::OpenGL2) &&
+ (ctx->hasExtension("GL_ARB_multisample")));
+
+ // QPainter cannot anti-alias the edges of circles etc. when using OpenGL
+ // So instead, use native OpenGL anti-aliasing when available.
+ if (hasMultisample) {
+ gc.beginNativePainting();
+ ctx->functions()->glEnable(GL_MULTISAMPLE);
+ gc.endNativePainting();
+ }
}
float halfHandleSize = d->config.handleSize() / 2;
d->recomputeVisibleAxes(gc.viewport());
if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) {
if (!d->horizontalAxis.isNull()) {
- // QPointF horizontalIndicatorCenter = d->horizontalAxis.unitVector().pointAt(15);
- // QRectF horizontalIndicator = QRectF(horizontalIndicatorCenter.x() - halfHandleSize, horizontalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
+ // QPointF horizontalIndicatorCenter = d->horizontalAxis.unitVector().pointAt(15);
+ // QRectF horizontalIndicator = QRectF(horizontalIndicatorCenter.x() - halfHandleSize, horizontalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
float horizontalHandlePosition = qBound<float>(d->minHandlePosition, d->config.horizontalHandlePosition(), d->horizontalAxis.length() - d->minHandlePosition);
QPointF horizontalHandleCenter = d->horizontalAxis.unitVector().pointAt(horizontalHandlePosition);
d->horizontalHandle = QRectF(horizontalHandleCenter.x() - halfHandleSize, horizontalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
gc.setPen(QPen(QColor(0, 0, 0, 64), 2, Qt::DashDotDotLine, Qt::RoundCap, Qt::RoundJoin));
gc.drawLine(d->horizontalAxis);
- // gc.drawEllipse(horizontalIndicator);
- // gc.drawPixmap(horizontalIndicator.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon);
+ // gc.drawEllipse(horizontalIndicator);
+ // gc.drawPixmap(horizontalIndicator.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon);
// don't draw the handles if we are locking the axis for movement
if (!d->config.lockHorizontal()) {
gc.setPen(QPen(QColor(0, 0, 0, 128), 2));
gc.drawEllipse(d->horizontalHandle);
gc.drawPixmap(d->horizontalHandle.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon);
}
} else {
d->horizontalHandle = QRectF();
}
}
if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) {
if (!d->verticalAxis.isNull()) {
gc.setPen(QPen(QColor(0, 0, 0, 64), 2, Qt::DashDotDotLine, Qt::RoundCap, Qt::RoundJoin));
gc.drawLine(d->verticalAxis);
- // QPointF verticalIndicatorCenter = d->verticalAxis.unitVector().pointAt(15);
- // QRectF verticalIndicator = QRectF(verticalIndicatorCenter.x() - halfHandleSize, verticalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
+ // QPointF verticalIndicatorCenter = d->verticalAxis.unitVector().pointAt(15);
+ // QRectF verticalIndicator = QRectF(verticalIndicatorCenter.x() - halfHandleSize, verticalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
float verticalHandlePosition = qBound<float>(d->minHandlePosition, d->config.verticalHandlePosition(), d->verticalAxis.length() - d->minHandlePosition);
QPointF verticalHandleCenter = d->verticalAxis.unitVector().pointAt(verticalHandlePosition);
d->verticalHandle = QRectF(verticalHandleCenter.x() - halfHandleSize, verticalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
// don't draw the handles if we are locking the axis for movement
if (!d->config.lockVertical()) {
gc.setPen(QPen(QColor(0, 0, 0, 128), 2));
gc.drawEllipse(d->verticalHandle);
gc.drawPixmap(d->verticalHandle.adjusted(5, 5, -5, -5).toRect(), d->verticalIcon);
}
} else {
d->verticalHandle = QRectF();
}
}
if (hasMultisample) {
gc.beginNativePainting();
ctx->functions()->glDisable(GL_MULTISAMPLE);
gc.endNativePainting();
}
gc.restore();
}
bool KisMirrorAxis::eventFilter(QObject* target, QEvent* event)
{
if (!visible()) return false;
QObject *expectedCanvasWidget = view() ?
- view()->canvasBase()->canvasWidget() : 0;
+ view()->canvasBase()->canvasWidget() : 0;
if (!expectedCanvasWidget || target != expectedCanvasWidget) return false;
if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TabletPress) {
QMouseEvent *me = dynamic_cast<QMouseEvent*>(event);
QTabletEvent *te = dynamic_cast<QTabletEvent*>(event);
QPoint pos = me ? me->pos() : (te ? te->pos() : QPoint(77,77));
if(d->config.mirrorHorizontal() && d->horizontalHandle.contains(pos) && !d->config.lockHorizontal() && !d->config.hideHorizontalDecoration() ) {
d->xActive = true;
QApplication::setOverrideCursor(Qt::ClosedHandCursor);
event->accept();
return true;
}
if(d->config.mirrorVertical() && d->verticalHandle.contains(pos) && !d->config.lockVertical() && !d->config.hideVerticalDecoration()) {
d->yActive = true;
QApplication::setOverrideCursor(Qt::ClosedHandCursor);
event->accept();
return true;
}
}
if(event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove) {
QMouseEvent *me = dynamic_cast<QMouseEvent*>(event);
QTabletEvent *te = dynamic_cast<QTabletEvent*>(event);
QPoint pos = me ? me->pos() : (te ? te->pos() : QPoint(77,77));
if(d->xActive) {
float axisX = view()->viewConverter()->widgetToImage<QPoint>(pos).x();
d->setAxisPosition(axisX, d->config.axisPosition().y());
d->config.setHorizontalHandlePosition(KisAlgebra2D::dotProduct<QPointF>(pos - d->horizontalAxis.p1(), d->horizontalAxis.unitVector().p2() - d->horizontalAxis.p1()));
emit sigConfigChanged();
event->accept();
return true;
}
if(d->yActive) {
float axisY = view()->viewConverter()->widgetToImage<QPoint>(pos).y();
d->setAxisPosition(d->config.axisPosition().x(), axisY);
d->config.setVerticalHandlePosition(KisAlgebra2D::dotProduct<QPointF>(pos - d->verticalAxis.p1(), d->verticalAxis.unitVector().p2() - d->verticalAxis.p1()));
emit sigConfigChanged();
event->accept();
return true;
}
if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) {
if(d->horizontalHandle.contains(pos) && !d->config.lockHorizontal()) {
if(!d->horizontalContainsCursor) {
QApplication::setOverrideCursor(Qt::OpenHandCursor);
d->horizontalContainsCursor = true;
}
} else if(d->horizontalContainsCursor) {
QApplication::restoreOverrideCursor();
d->horizontalContainsCursor = false;
}
}
if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) {
if(d->verticalHandle.contains(pos) && !d->config.lockVertical()) {
if(!d->verticalContainsCursor) {
QApplication::setOverrideCursor(Qt::OpenHandCursor);
d->verticalContainsCursor = true;
}
} else if(d->verticalContainsCursor) {
QApplication::restoreOverrideCursor();
d->verticalContainsCursor = false;
}
}
}
if(event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::TabletRelease) {
if(d->xActive) {
QApplication::restoreOverrideCursor();
d->xActive = false;
event->accept();
return true;
}
if(d->yActive) {
QApplication::restoreOverrideCursor();
d->yActive = false;
event->accept();
return true;
}
}
return QObject::eventFilter(target, event);
}
void KisMirrorAxis::mirrorModeChanged()
{
if (!view()->isCurrent()) {
return;
}
d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal());
d->config.setMirrorVertical(d->resourceProvider->mirrorVertical());
d->config.setLockHorizontal(d->resourceProvider->mirrorHorizontalLock());
d->config.setLockVertical(d->resourceProvider->mirrorVerticalLock());
d->config.setHideHorizontalDecoration(d->resourceProvider->mirrorHorizontalHideDecorations());
d->config.setHideVerticalDecoration(d->resourceProvider->mirrorVerticalHideDecorations());
setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
emit sigConfigChanged();
}
void KisMirrorAxis::setVisible(bool v)
{
KisCanvasDecoration::setVisible(v);
KisInputManager *inputManager = view() ? view()->canvasBase()->globalInputManager() : 0;
if (!inputManager) return;
if (v) {
inputManager->attachPriorityEventFilter(this);
} else {
inputManager->detachPriorityEventFilter(this);
}
}
void KisMirrorAxis::setMirrorAxisConfig(const KisMirrorAxisConfig &config)
{
if (config != d->config) {
KisSignalsBlocker blocker(d->resourceProvider);
d->config = config;
d->setAxisPosition(d->config.axisPosition().x(), d->config.axisPosition().y());
d->resourceProvider->setMirrorHorizontal(d->config.mirrorHorizontal());
d->resourceProvider->setMirrorVertical(d->config.mirrorVertical());
d->resourceProvider->setMirrorHorizontalLock(d->config.lockHorizontal());
d->resourceProvider->setMirrorVerticalLock(d->config.lockVertical());
d->resourceProvider->setMirrorHorizontal(d->config.mirrorHorizontal());
d->resourceProvider->setMirrorVertical(d->config.mirrorVertical());
d->resourceProvider->setMirrorHorizontalHideDecorations(d->config.hideHorizontalDecoration());
d->resourceProvider->setMirrorVerticalHideDecorations(d->config.hideVerticalDecoration());
}
toggleMirrorActions();
setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
}
const KisMirrorAxisConfig &KisMirrorAxis::mirrorAxisConfig() const
{
return d->config;
}
void KisMirrorAxis::toggleMirrorActions()
{
KActionCollection* collection = view()->viewManager()->actionCollection();
// first uncheck the action, then set according to config;
// otherwise the connected KisHighlightedToolButton's highlight color is not
// properly set
collection->action("hmirror_action")->setChecked(false);
collection->action("vmirror_action")->setChecked(false);
if (d->config.mirrorHorizontal()) {
collection->action("hmirror_action")->setChecked(d->config.mirrorHorizontal());
}
if (d->config.mirrorVertical()) {
collection->action("vmirror_action")->setChecked(d->config.mirrorVertical());
}
collection->action("mirrorX-lock")->setChecked(d->config.lockHorizontal());
collection->action("mirrorY-lock")->setChecked(d->config.lockVertical());
collection->action("mirrorX-hideDecorations")->setChecked(d->config.hideHorizontalDecoration());
collection->action("mirrorY-hideDecorations")->setChecked(d->config.hideVerticalDecoration());
}
void KisMirrorAxis::moveHorizontalAxisToCenter()
{
if (!view()->isCurrent()) {
return;
}
d->setAxisPosition(d->image->width()/2, d->config.axisPosition().y());
emit sigConfigChanged();
}
void KisMirrorAxis::moveVerticalAxisToCenter()
{
if (!view()->isCurrent()) {
return;
}
d->setAxisPosition(d->config.axisPosition().x(), d->image->height()/2 );
emit sigConfigChanged();
}
void KisMirrorAxis::Private::setAxisPosition(float x, float y)
{
QPointF newPosition = QPointF(x, y);
config.setAxisPosition(newPosition);
const QPointF relativePosition = KisAlgebra2D::absoluteToRelative(newPosition, image->bounds());
image->setMirrorAxesCenter(relativePosition);
q->view()->canvasBase()->updateCanvas();
}
void KisMirrorAxis::Private::recomputeVisibleAxes(QRect viewport)
{
KisCoordinatesConverter *converter = q->view()->viewConverter();
config.setAxisPosition(KisAlgebra2D::relativeToAbsolute(image->mirrorAxesCenter(), image->bounds()));
QPointF samplePt1 = converter->imageToWidget<QPointF>(config.axisPosition());
QPointF samplePt2 = converter->imageToWidget<QPointF>(QPointF(config.axisPosition().x(), config.axisPosition().y() - 100));
horizontalAxis = QLineF(samplePt1, samplePt2);
if (!KisAlgebra2D::intersectLineRect(horizontalAxis, viewport)) horizontalAxis = QLineF();
samplePt2 = converter->imageToWidget<QPointF>(QPointF(config.axisPosition().x() - 100, config.axisPosition().y()));
verticalAxis = QLineF(samplePt1, samplePt2);
if (!KisAlgebra2D::intersectLineRect(verticalAxis, viewport)) verticalAxis = QLineF();
}
diff --git a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp
index 9a11a28160..451c5819a6 100644
--- a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp
+++ b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp
@@ -1,385 +1,389 @@
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KisAsyncAnimationRenderDialogBase.h"
#include <QEventLoop>
#include <QProgressDialog>
#include <QElapsedTimer>
#include <QApplication>
#include <QThread>
#include <QTime>
#include <QList>
#include <QtMath>
#include <klocalizedstring.h>
#include "KisViewManager.h"
#include "KisAsyncAnimationRendererBase.h"
#include "kis_time_range.h"
#include "kis_image.h"
#include "kis_image_config.h"
#include "kis_memory_statistics_server.h"
#include "kis_signal_compressor.h"
#include <boost/optional.hpp>
#include <vector>
#include <memory>
namespace {
struct RendererPair {
std::unique_ptr<KisAsyncAnimationRendererBase> renderer;
KisImageSP image;
RendererPair() {}
RendererPair(KisAsyncAnimationRendererBase *_renderer, KisImageSP _image)
: renderer(_renderer),
image(_image)
{
}
RendererPair(RendererPair &&rhs)
: renderer(std::move(rhs.renderer)),
image(rhs.image)
{
}
};
int calculateNumberMemoryAllowedClones(KisImageSP image)
{
KisMemoryStatisticsServer::Statistics stats =
KisMemoryStatisticsServer::instance()
->fetchMemoryStatistics(image);
const qint64 allowedMemory = 0.8 * stats.tilesHardLimit - stats.realMemorySize;
const qint64 cloneSize = stats.projectionsSize;
- return cloneSize > 0 ? allowedMemory / cloneSize : 0;
+ if (cloneSize > 0 && allowedMemory > 0) {
+ return allowedMemory / cloneSize;
+ }
+
+ return 0; // will become 1; either when the cloneSize = 0 or the allowedMemory is 0 or below
}
}
struct KisAsyncAnimationRenderDialogBase::Private
{
Private(const QString &_actionTitle, KisImageSP _image, int _busyWait)
: actionTitle(_actionTitle),
image(_image),
busyWait(_busyWait),
progressDialogCompressor(40, KisSignalCompressor::FIRST_ACTIVE)
{
}
QString actionTitle;
KisImageSP image;
int busyWait;
bool isBatchMode = false;
std::vector<RendererPair> asyncRenderers;
bool memoryLimitReached = false;
QElapsedTimer processingTime;
QScopedPointer<QProgressDialog> progressDialog;
QEventLoop waitLoop;
QList<int> stillDirtyFrames;
QList<int> framesInProgress;
int dirtyFramesCount = 0;
Result result = RenderComplete;
QRegion regionOfInterest;
KisSignalCompressor progressDialogCompressor;
using ProgressData = QPair<int, QString>;
boost::optional<ProgressData> progressData;
int progressDialogReentrancyCounter = 0;
int numDirtyFramesLeft() const {
return stillDirtyFrames.size() + framesInProgress.size();
}
};
KisAsyncAnimationRenderDialogBase::KisAsyncAnimationRenderDialogBase(const QString &actionTitle, KisImageSP image, int busyWait)
: m_d(new Private(actionTitle, image, busyWait))
{
connect(&m_d->progressDialogCompressor, SIGNAL(timeout()),
SLOT(slotUpdateCompressedProgressData()), Qt::QueuedConnection);
}
KisAsyncAnimationRenderDialogBase::~KisAsyncAnimationRenderDialogBase()
{
}
KisAsyncAnimationRenderDialogBase::Result
KisAsyncAnimationRenderDialogBase::regenerateRange(KisViewManager *viewManager)
{
{
/**
* Since this method can be called from the places where no
* view manager is available, we need this manually crafted
* ugly construction to "try-lock-cancel" the image.
*/
bool imageIsIdle = true;
if (viewManager) {
imageIsIdle = viewManager->blockUntilOperationsFinished(m_d->image);
} else {
imageIsIdle = false;
if (m_d->image->tryBarrierLock(true)) {
m_d->image->unlock();
imageIsIdle = true;
}
}
if (!imageIsIdle) {
return RenderCancelled;
}
}
m_d->stillDirtyFrames = calcDirtyFrames();
m_d->framesInProgress.clear();
m_d->result = RenderComplete;
m_d->dirtyFramesCount = m_d->stillDirtyFrames.size();
if (!m_d->isBatchMode) {
QWidget *parentWidget = viewManager ? viewManager->mainWindow() : 0;
m_d->progressDialog.reset(new QProgressDialog(m_d->actionTitle, i18n("Cancel"), 0, 0, parentWidget));
m_d->progressDialog->setWindowModality(Qt::ApplicationModal);
m_d->progressDialog->setMinimum(0);
m_d->progressDialog->setMaximum(m_d->dirtyFramesCount);
m_d->progressDialog->setMinimumDuration(m_d->busyWait);
connect(m_d->progressDialog.data(), SIGNAL(canceled()), SLOT(slotCancelRegeneration()));
}
if (m_d->dirtyFramesCount <= 0) return m_d->result;
m_d->processingTime.start();
KisImageConfig cfg(true);
const int maxThreads = cfg.maxNumberOfThreads();
const int numAllowedWorker = 1 + calculateNumberMemoryAllowedClones(m_d->image);
const int proposedNumWorkers = qMin(m_d->dirtyFramesCount, cfg.frameRenderingClones());
const int numWorkers = qMin(proposedNumWorkers, numAllowedWorker);
const int numThreadsPerWorker = qMax(1, qCeil(qreal(maxThreads) / numWorkers));
m_d->memoryLimitReached = numWorkers < proposedNumWorkers;
const int oldWorkingThreadsLimit = m_d->image->workingThreadsLimit();
ENTER_FUNCTION() << ppVar(numWorkers) << ppVar(numThreadsPerWorker);
for (int i = 0; i < numWorkers; i++) {
// reuse the image for one of the workers
KisImageSP image = i == numWorkers - 1 ? m_d->image : m_d->image->clone(true);
image->setWorkingThreadsLimit(numThreadsPerWorker);
KisAsyncAnimationRendererBase *renderer = createRenderer(image);
connect(renderer, SIGNAL(sigFrameCompleted(int)), SLOT(slotFrameCompleted(int)));
connect(renderer, SIGNAL(sigFrameCancelled(int)), SLOT(slotFrameCancelled(int)));
m_d->asyncRenderers.push_back(RendererPair(renderer, image));
}
ENTER_FUNCTION() << "Copying done in" << m_d->processingTime.elapsed();
tryInitiateFrameRegeneration();
updateProgressLabel();
if (m_d->numDirtyFramesLeft() > 0) {
m_d->waitLoop.exec();
}
ENTER_FUNCTION() << "Full regeneration done in" << m_d->processingTime.elapsed();
for (auto &pair : m_d->asyncRenderers) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!pair.renderer->isActive());
if (viewManager) {
viewManager->blockUntilOperationsFinishedForced(pair.image);
} else {
pair.image->barrierLock(true);
pair.image->unlock();
}
}
m_d->asyncRenderers.clear();
if (viewManager) {
viewManager->blockUntilOperationsFinishedForced(m_d->image);
} else {
m_d->image->barrierLock(true);
m_d->image->unlock();
}
m_d->image->setWorkingThreadsLimit(oldWorkingThreadsLimit);
m_d->progressDialog.reset();
return m_d->result;
}
void KisAsyncAnimationRenderDialogBase::setRegionOfInterest(const QRegion &roi)
{
m_d->regionOfInterest = roi;
}
QRegion KisAsyncAnimationRenderDialogBase::regionOfInterest() const
{
return m_d->regionOfInterest;
}
void KisAsyncAnimationRenderDialogBase::slotFrameCompleted(int frame)
{
Q_UNUSED(frame);
m_d->framesInProgress.removeOne(frame);
tryInitiateFrameRegeneration();
updateProgressLabel();
}
void KisAsyncAnimationRenderDialogBase::slotFrameCancelled(int frame)
{
Q_UNUSED(frame);
cancelProcessingImpl(false);
}
void KisAsyncAnimationRenderDialogBase::slotCancelRegeneration()
{
cancelProcessingImpl(true);
}
void KisAsyncAnimationRenderDialogBase::cancelProcessingImpl(bool isUserCancelled)
{
for (auto &pair : m_d->asyncRenderers) {
if (pair.renderer->isActive()) {
pair.renderer->cancelCurrentFrameRendering();
}
KIS_SAFE_ASSERT_RECOVER_NOOP(!pair.renderer->isActive());
}
m_d->stillDirtyFrames.clear();
m_d->framesInProgress.clear();
m_d->result = isUserCancelled ? RenderCancelled : RenderFailed;
updateProgressLabel();
}
void KisAsyncAnimationRenderDialogBase::tryInitiateFrameRegeneration()
{
bool hadWorkOnPreviousCycle = false;
while (!m_d->stillDirtyFrames.isEmpty()) {
for (auto &pair : m_d->asyncRenderers) {
if (!pair.renderer->isActive()) {
const int currentDirtyFrame = m_d->stillDirtyFrames.takeFirst();
initializeRendererForFrame(pair.renderer.get(), pair.image, currentDirtyFrame);
pair.renderer->startFrameRegeneration(pair.image, currentDirtyFrame, m_d->regionOfInterest);
hadWorkOnPreviousCycle = true;
m_d->framesInProgress.append(currentDirtyFrame);
break;
}
}
if (!hadWorkOnPreviousCycle) break;
hadWorkOnPreviousCycle = false;
}
}
void KisAsyncAnimationRenderDialogBase::updateProgressLabel()
{
const int processedFramesCount = m_d->dirtyFramesCount - m_d->numDirtyFramesLeft();
const qint64 elapsedMSec = m_d->processingTime.elapsed();
const qint64 estimatedMSec =
!processedFramesCount ? 0 :
elapsedMSec * m_d->dirtyFramesCount / processedFramesCount;
const QTime elapsedTime = QTime::fromMSecsSinceStartOfDay(elapsedMSec);
const QTime estimatedTime = QTime::fromMSecsSinceStartOfDay(estimatedMSec);
const QString timeFormat = estimatedTime.hour() > 0 ? "HH:mm:ss" : "mm:ss";
const QString elapsedTimeString = elapsedTime.toString(timeFormat);
const QString estimatedTimeString = estimatedTime.toString(timeFormat);
const QString memoryLimitMessage(
- i18n("\n\nMemory limit is reached!\nThe number of clones is limited to %1\n\n",
+ i18n("\n\nThe memory limit has been reached.\nThe number of frames saved simultaneously is limited to %1\n\n",
m_d->asyncRenderers.size()));
const QString progressLabel(i18n("%1\n\nElapsed: %2\nEstimated: %3\n\n%4",
m_d->actionTitle,
elapsedTimeString,
estimatedTimeString,
m_d->memoryLimitReached ? memoryLimitMessage : QString()));
if (m_d->progressDialog) {
/**
* We should avoid reentrancy caused by explicit
* QApplication::processEvents() in QProgressDialog::setValue(), so use
* a compressor instead
*/
m_d->progressData = Private::ProgressData(processedFramesCount, progressLabel);
m_d->progressDialogCompressor.start();
}
if (!m_d->numDirtyFramesLeft()) {
m_d->waitLoop.quit();
}
}
void KisAsyncAnimationRenderDialogBase::slotUpdateCompressedProgressData()
{
/**
* Qt's implementation of QProgressDialog is a bit weird: it calls
* QApplication::processEvents() from inside setValue(), which means
* that our update method may reenter multiple times.
*
* This code avoids reentering by using a compresson and an explicit
* entrance counter.
*/
if (m_d->progressDialogReentrancyCounter > 0) {
m_d->progressDialogCompressor.start();
return;
}
if (m_d->progressDialog && m_d->progressData) {
m_d->progressDialogReentrancyCounter++;
m_d->progressDialog->setLabelText(m_d->progressData->second);
m_d->progressDialog->setValue(m_d->progressData->first);
m_d->progressData = boost::none;
m_d->progressDialogReentrancyCounter--;
}
}
void KisAsyncAnimationRenderDialogBase::setBatchMode(bool value)
{
m_d->isBatchMode = value;
}
bool KisAsyncAnimationRenderDialogBase::batchMode() const
{
return m_d->isBatchMode;
}
diff --git a/libs/ui/dialogs/KisDlgChangeCloneSource.cpp b/libs/ui/dialogs/KisDlgChangeCloneSource.cpp
new file mode 100644
index 0000000000..d3c67421f7
--- /dev/null
+++ b/libs/ui/dialogs/KisDlgChangeCloneSource.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "KisDlgChangeCloneSource.h"
+
+#include <kis_clone_layer.h>
+#include <kis_image.h>
+#include <kis_undo_adapter.h>
+#include <kis_processing_applicator.h>
+#include <KisImageSignals.h>
+#include <kis_signals_blocker.h>
+
+#include "KisViewManager.h"
+#include "KisChangeCloneLayersCommand.h"
+
+struct KisDlgChangeCloneSource::Private
+{
+ Private(QList<KisCloneLayerSP> layers, KisViewManager *view)
+ : cloneLayers(layers)
+ , view(view)
+ , image(view->image())
+ , applicator(new KisProcessingApplicator(image, 0,
+ KisProcessingApplicator::NONE,
+ /* emitSignals = */ KisImageSignalVector() << ModifiedSignal,
+ kundo2_i18n("Change Clone Layers")))
+ , modified(false) {}
+
+ QList<KisCloneLayerSP> cloneLayers;
+ KisViewManager *view;
+ KisImageSP image;
+ QList<KisLayerSP> validTargets;
+ Ui::WdgChangeCloneSource ui;
+ QScopedPointer<KisProcessingApplicator> applicator;
+ bool modified;
+
+ void addToTargetListRecursively(KisNodeSP node, bool addSelf = true);
+ void filterOutAncestorsAndClonesRecursively(KisLayerSP layer);
+ KisLayerSP getSelectedTargetLayer();
+ KUndo2Command *createCommand(KisLayerSP targetLayer);
+};
+
+void KisDlgChangeCloneSource::Private::addToTargetListRecursively(KisNodeSP node, bool addSelf)
+{
+ if (!node) {
+ return;
+ }
+ if (addSelf) {
+ KisLayerSP layer(qobject_cast<KisLayer *>(node.data()));
+ if (layer) {
+ validTargets << layer;
+ }
+ }
+ for (KisNodeSP childNode = node->lastChild(); childNode; childNode = childNode->prevSibling()) {
+ KisLayerSP childLayer(qobject_cast<KisLayer *>(childNode.data()));
+ if (childLayer) {
+ addToTargetListRecursively(childLayer);
+ }
+ }
+
+}
+
+void KisDlgChangeCloneSource::Private::filterOutAncestorsAndClonesRecursively(KisLayerSP layer)
+{
+ validTargets.removeOne(layer);
+ // remove `layer` and its ancestors
+ KisLayerSP parent = qobject_cast<KisLayer *>(layer->parent().data());
+ if (parent) {
+ filterOutAncestorsAndClonesRecursively(parent);
+ }
+
+ // remove all clones of `layer`, and their ancestors
+ Q_FOREACH (KisCloneLayerSP clone, layer->registeredClones()) {
+ filterOutAncestorsAndClonesRecursively(clone);
+ }
+}
+
+KisLayerSP KisDlgChangeCloneSource::Private::getSelectedTargetLayer()
+{
+ int index = ui.cmbSourceLayer->currentIndex();
+ if (index != -1) {
+ return validTargets.at(index);
+ } else {
+ return 0;
+ }
+}
+
+KUndo2Command *KisDlgChangeCloneSource::Private::createCommand(KisLayerSP targetLayer)
+{
+ return new KisChangeCloneLayersCommand(cloneLayers, targetLayer);
+}
+
+KisDlgChangeCloneSource::KisDlgChangeCloneSource(QList<KisCloneLayerSP> layers, KisViewManager *view, QWidget *parent)
+ : KoDialog(parent)
+ , d(new Private(layers, view))
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!layers.isEmpty());
+
+ connect(d->image.data(), &KisImage::sigStrokeCancellationRequested,
+ this, &KisDlgChangeCloneSource::reject);
+ connect(d->image.data(), &KisImage::sigUndoDuringStrokeRequested,
+ this, &KisDlgChangeCloneSource::reject);
+ connect(d->image.data(), &KisImage::sigStrokeEndRequested,
+ this, &KisDlgChangeCloneSource::accept);
+
+ setButtons(Ok | Cancel);
+ setDefaultButton(Ok);
+
+ QWidget *widget = new QWidget(this);
+ d->ui.setupUi(widget);
+ setMainWidget(widget);
+
+ connect(d->ui.cmbSourceLayer, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ this, &KisDlgChangeCloneSource::slotCancelChangesAndSetNewTarget);
+
+ updateTargetLayerList();
+}
+
+KisDlgChangeCloneSource::~KisDlgChangeCloneSource()
+{
+ dbgUI << "dialog destroyed";
+ if (d->applicator) {
+ if (result() == QDialog::Accepted && d->modified) {
+ dbgUI << "Accepted";
+ d->applicator->end();
+ } else {
+ dbgUI << "Rejected";
+ d->applicator->cancel();
+ }
+ }
+}
+
+void KisDlgChangeCloneSource::updateTargetLayerList()
+{
+ KisSignalsBlocker b(d->ui.cmbSourceLayer);
+
+ if (!d->image) {
+ return;
+ }
+ KisNodeSP root = d->image->root();
+ d->validTargets.clear();
+ d->addToTargetListRecursively(root, /* addSelf = */ false);
+
+ KisLayerSP commonCopyFrom(d->cloneLayers.first()->copyFrom());
+
+ Q_FOREACH (KisCloneLayerSP clone, d->cloneLayers) {
+ // filter out invalid targets:
+ // selected clone layers, their ancestors;
+ // the clone layers' registered clone, the clones' ancestors.
+ d->filterOutAncestorsAndClonesRecursively(clone);
+
+ // assume that clone->copyFrom() != 0
+ if (clone->copyFrom() != commonCopyFrom) {
+ commonCopyFrom = 0;
+ }
+ }
+
+ d->ui.cmbSourceLayer->clear();
+ Q_FOREACH (KisNodeSP node, d->validTargets) {
+ d->ui.cmbSourceLayer->addItem(node->name());
+ }
+
+ if (commonCopyFrom) {
+ d->ui.cmbSourceLayer->setCurrentIndex(d->validTargets.indexOf(commonCopyFrom));
+ } else {
+ d->ui.cmbSourceLayer->setCurrentIndex(-1);
+ }
+}
+
+void KisDlgChangeCloneSource::slotCancelChangesAndSetNewTarget()
+{
+ KisLayerSP targetLayer = d->getSelectedTargetLayer();
+ if (targetLayer) {
+ d->applicator->applyCommand(d->createCommand(targetLayer));
+ d->modified = true;
+ }
+}
+
diff --git a/plugins/impex/psd/psd_loader.h b/libs/ui/dialogs/KisDlgChangeCloneSource.h
similarity index 58%
copy from plugins/impex/psd/psd_loader.h
copy to libs/ui/dialogs/KisDlgChangeCloneSource.h
index 0618f803f5..3957a40d6e 100644
--- a/plugins/impex/psd/psd_loader.h
+++ b/libs/ui/dialogs/KisDlgChangeCloneSource.h
@@ -1,59 +1,50 @@
/*
- * Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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 _PSD_LOADER_H_
-#define _PSD_LOADER_H_
-#include <stdio.h>
-
-#include <QObject>
-
-#include <QFileInfo>
+#ifndef KIS_DLG_CHANGE_CLONE_SOURCE_H_
+#define KIS_DLG_CHANGE_CLONE_SOURCE_H_
#include "kis_types.h"
-#include <KisImageBuilderResult.h>
-class KisDocument;
+#include <KoDialog.h>
+
+#include "ui_wdgchangeclonesource.h"
-class PSDLoader : public QObject {
+class QWidget;
+class KisViewManager;
+class KisDlgChangeCloneSource : public KoDialog
+{
Q_OBJECT
public:
+ KisDlgChangeCloneSource(QList<KisCloneLayerSP> layers, KisViewManager *view, QWidget *parent = 0);
- PSDLoader(KisDocument *doc);
- ~PSDLoader() override;
-
- KisImageBuilder_Result buildImage(QIODevice *io);
-
- KisImageSP image();
-
-public Q_SLOTS:
-
- virtual void cancel();
+ ~KisDlgChangeCloneSource() override;
private:
+ void updateTargetLayerList();
- KisImageBuilder_Result decode(QIODevice *io);
+public Q_SLOTS:
+ void slotCancelChangesAndSetNewTarget();
private:
-
- KisImageSP m_image;
- KisDocument *m_doc;
- bool m_stop;
+ struct Private;
+ const QScopedPointer<Private> d;
};
-#endif
+#endif // KIS_DLG_CHANGE_CLONE_SOURCE_H_
diff --git a/libs/ui/dialogs/kis_dlg_adj_layer_props.cc b/libs/ui/dialogs/kis_dlg_adj_layer_props.cc
index c5f8b4abc3..4446f4fc2b 100644
--- a/libs/ui/dialogs/kis_dlg_adj_layer_props.cc
+++ b/libs/ui/dialogs/kis_dlg_adj_layer_props.cc
@@ -1,144 +1,143 @@
/*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_adj_layer_props.h"
#include <klocalizedstring.h>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
-#include <klineedit.h>
-
+#include <QLineEdit>
#include "kis_config_widget.h"
#include "kis_transaction.h"
#include "filter/kis_filter.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "kis_layer.h"
#include "kis_adjustment_layer.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_group_layer.h"
#include "kis_node_filter_interface.h"
KisDlgAdjLayerProps::KisDlgAdjLayerProps(KisNodeSP node,
KisNodeFilterInterface* nfi,
KisPaintDeviceSP paintDevice,
KisViewManager *view,
KisFilterConfigurationSP configuration,
const QString & layerName,
const QString & caption,
QWidget *parent,
const char *name)
: KoDialog(parent)
, m_node(node)
, m_paintDevice(paintDevice)
, m_currentConfigWidget(0)
, m_currentFilter(0)
, m_currentConfiguration(0)
, m_nodeFilterInterface(nfi)
{
setButtons(Ok | Cancel);
setDefaultButton(Ok);
setObjectName(name);
m_currentConfiguration = configuration;
if (m_currentConfiguration) {
m_currentFilter = KisFilterRegistry::instance()->get(m_currentConfiguration->name()).data();
}
setCaption(caption);
QWidget * page = new QWidget(this);
page->setObjectName("page widget");
QHBoxLayout * layout = new QHBoxLayout(page);
layout->setMargin(0);
setMainWidget(page);
QVBoxLayout *v1 = new QVBoxLayout();
layout->addLayout(v1);
QHBoxLayout *hl = new QHBoxLayout();
v1->addLayout(hl);
QLabel * lblName = new QLabel(i18n("Layer name:"), page);
lblName->setObjectName("lblName");
hl->addWidget(lblName, 0);
- m_layerName = new KLineEdit(page);
+ m_layerName = new QLineEdit(page);
m_layerName->setObjectName("m_layerName");
m_layerName->setText(layerName);
m_layerName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
hl->addWidget(m_layerName, 10);
connect(m_layerName, SIGNAL(textChanged(QString)), this, SLOT(slotNameChanged(QString)));
if (m_currentFilter) {
m_currentConfigWidget = m_currentFilter->createConfigurationWidget(page, paintDevice, true);
if (m_currentConfigWidget) {
m_currentConfigWidget->setView(view);
m_currentConfigWidget->setConfiguration(m_currentConfiguration);
}
}
if (m_currentFilter == 0 || m_currentConfigWidget == 0) {
QLabel * labelNoConfigWidget = new QLabel(i18n("No configuration options are available for this filter"), page);
v1->addWidget(labelNoConfigWidget);
}
else {
v1->addWidget(m_currentConfigWidget);
connect(m_currentConfigWidget, SIGNAL(sigConfigurationUpdated()), SLOT(slotConfigChanged()));
}
enableButtonOk(!m_layerName->text().isEmpty());
}
void KisDlgAdjLayerProps::slotNameChanged(const QString & text)
{
enableButtonOk(!text.isEmpty());
}
KisFilterConfigurationSP KisDlgAdjLayerProps::filterConfiguration() const
{
if (m_currentConfigWidget) {
KisFilterConfigurationSP config = dynamic_cast<KisFilterConfiguration*>(m_currentConfigWidget->configuration().data());
if (config) {
return config;
}
}
return m_currentFilter->defaultConfiguration();
}
QString KisDlgAdjLayerProps::layerName() const
{
return m_layerName->text();
}
void KisDlgAdjLayerProps::slotConfigChanged()
{
enableButtonOk(true);
KisFilterConfigurationSP config = filterConfiguration();
if (config) {
m_nodeFilterInterface->setFilter(config);
}
m_node->setDirty();
}
diff --git a/libs/ui/dialogs/kis_dlg_adjustment_layer.cc b/libs/ui/dialogs/kis_dlg_adjustment_layer.cc
index 2a35ddc66f..564c2efac2 100644
--- a/libs/ui/dialogs/kis_dlg_adjustment_layer.cc
+++ b/libs/ui/dialogs/kis_dlg_adjustment_layer.cc
@@ -1,140 +1,140 @@
/*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_adjustment_layer.h"
#include <klocalizedstring.h>
#include <QGroupBox>
#include <QLabel>
#include <QLayout>
#include <QGridLayout>
#include <QTimer>
#include <QIcon>
#include <QImage>
#include <QPixmap>
#include <QPushButton>
#include <QDialogButtonBox>
#include "filter/kis_filter.h"
#include "kis_config_widget.h"
#include "filter/kis_filter_configuration.h"
#include "kis_paint_device.h"
#include "kis_transaction.h"
#include "kis_node.h"
#include "kis_node_filter_interface.h"
#include <kis_config.h>
#include "KisViewManager.h"
KisDlgAdjustmentLayer::KisDlgAdjustmentLayer(KisNodeSP node,
KisNodeFilterInterface* nfi,
KisPaintDeviceSP paintDevice,
const QString &layerName,
const QString &caption,
KisViewManager *view, QWidget *parent)
- : KoDialog(parent)
+ : KoDialog(parent, Qt::Dialog)
, m_node(node)
, m_nodeFilterInterface(nfi)
, m_currentFilter(0)
, m_customName(false)
, m_layerName(layerName)
{
setCaption(caption);
setButtons(None);
QWidget * page = new QWidget(this);
wdgFilterNodeCreation.setupUi(page);
setMainWidget(page);
wdgFilterNodeCreation.filterGalleryToggle->setChecked(wdgFilterNodeCreation.filterSelector->isFilterGalleryVisible());
wdgFilterNodeCreation.filterGalleryToggle->setIcon(QPixmap(":/pics/sidebaricon.png"));
wdgFilterNodeCreation.filterGalleryToggle->setMaximumWidth(wdgFilterNodeCreation.filterGalleryToggle->height());
connect(wdgFilterNodeCreation.filterSelector, SIGNAL(sigFilterGalleryToggled(bool)), wdgFilterNodeCreation.filterGalleryToggle, SLOT(setChecked(bool)));
connect(wdgFilterNodeCreation.filterGalleryToggle, SIGNAL(toggled(bool)), wdgFilterNodeCreation.filterSelector, SLOT(showFilterGallery(bool)));
connect(wdgFilterNodeCreation.filterSelector, SIGNAL(sigSizeChanged()), this, SLOT(slotFilterWidgetSizeChanged()));
connect(wdgFilterNodeCreation.buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(wdgFilterNodeCreation.buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
wdgFilterNodeCreation.filterSelector->setView(view);
wdgFilterNodeCreation.filterSelector->showFilterGallery(KisConfig(true).showFilterGalleryLayerMaskDialog());
wdgFilterNodeCreation.filterSelector->setPaintDevice(false, paintDevice);
wdgFilterNodeCreation.layerName->setText(layerName);
connect(wdgFilterNodeCreation.filterSelector, SIGNAL(configurationChanged()), SLOT(slotConfigChanged()));
connect(wdgFilterNodeCreation.layerName, SIGNAL(textChanged(QString)), SLOT(slotNameChanged(QString)));
slotConfigChanged();
}
KisDlgAdjustmentLayer::~KisDlgAdjustmentLayer()
{
KisConfig(true).setShowFilterGalleryLayerMaskDialog(wdgFilterNodeCreation.filterSelector->isFilterGalleryVisible());
}
void KisDlgAdjustmentLayer::slotNameChanged(const QString &text)
{
Q_UNUSED(text);
m_customName = !text.isEmpty();
enableButtonOk(m_currentFilter);
}
KisFilterConfigurationSP KisDlgAdjustmentLayer::filterConfiguration() const
{
KisFilterConfigurationSP config = wdgFilterNodeCreation.filterSelector->configuration();
Q_ASSERT(config);
return config;
}
QString KisDlgAdjustmentLayer::layerName() const
{
return wdgFilterNodeCreation.layerName->text();
}
void KisDlgAdjustmentLayer::slotConfigChanged()
{
m_currentFilter = filterConfiguration();
enableButtonOk(m_currentFilter);
if (m_currentFilter) {
m_nodeFilterInterface->setFilter(m_currentFilter);
if (!m_customName) {
wdgFilterNodeCreation.layerName->blockSignals(true);
wdgFilterNodeCreation.layerName->setText(m_layerName + " (" + wdgFilterNodeCreation.filterSelector->currentFilter()->name() + ")");
wdgFilterNodeCreation.layerName->blockSignals(false);
}
}
m_node->setDirty();
}
void KisDlgAdjustmentLayer::adjustSize()
{
QWidget::adjustSize();
}
void KisDlgAdjustmentLayer::slotFilterWidgetSizeChanged()
{
QMetaObject::invokeMethod(this, "adjustSize", Qt::QueuedConnection);
}
diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp
index 43c774b97c..88f71cf267 100644
--- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp
+++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp
@@ -1,395 +1,395 @@
/*
* Copyright (c) 2016 Kapustin Alexey <akapust1n@yandex.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_stroke_selection_properties.h"
#include <QPushButton>
#include <QRadioButton>
#include <QLayout>
#include <QLabel>
#include <QSpinBox>
#include <QSlider>
#include <QCheckBox>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <klocalizedstring.h>
#include <KoColorSpace.h>
#include "KoColorProfile.h"
#include "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoColorConversionTransformation.h"
#include "KoColorPopupAction.h"
#include "kis_icon_utils.h"
#include "KoID.h"
#include "kis_image.h"
#include "kis_annotation.h"
#include "kis_config.h"
#include "kis_signal_compressor.h"
#include "widgets/kis_cmb_idlist.h"
#include <KisSqueezedComboBox.h>
#include "kis_layer_utils.h"
#include <kis_ls_utils.h>
#include "kis_canvas_resource_provider.h"
#include "KoUnit.h"
#include "kis_display_color_converter.h"
KisDlgStrokeSelection::KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer)
: KoDialog(view->mainWindow())
{
m_resourceManager = view->mainWindow()->resourceManager();
converter = view->canvasBase()->displayColorConverter();
setButtons(Ok | Cancel);
setDefaultButton(Ok);
setCaption(i18nc("@title:window", "Stroke Selection Properties"));
m_page = new WdgStrokeSelection(this);
m_image = image;
setMainWidget(m_page);
resize(m_page->sizeHint());
KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection");
auto &m_options = m_page->m_options;
m_options.color = cfg->getColor("color");
m_options.lineColorSource = cfg->getInt("lineColorSource");
m_page->lineColorBox->setCurrentIndex(m_options.lineColorSource);
m_page->colorSelector->setColor(getSelectedColor().toQColor());
m_options.brushSelected = cfg->getBool("useBrush", 0);
m_page->typeBox->setCurrentIndex(m_options.brushSelected? 0 : 1);
m_options._colorFillSource = cfg->getInt("colorFillSource", 0);
m_page->fillBox->setCurrentIndex(m_options._colorFillSource);
m_options.customColor = cfg->getColor("customColor");
if (m_options._colorFillSource == static_cast<int>(colorFillSource::CustomColor)) {
m_page->colorFillSelector->setColor(m_options.customColor.toQColor());
}
else {
m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor());
}
m_options.fillColor = cfg->getColor("fillColor");
if (m_options._colorFillSource == static_cast<int>(colorFillSource::None)) {
m_page->colorFillSelector->setDisabled(true);
}
else {
m_page->colorFillSelector->setDisabled(false); }
m_options.lineSize = cfg->getInt("lineSize", 1);
m_page->lineSize->setValue(m_options.lineSize);
if (m_options.brushSelected) {
m_page->lineSize->setDisabled(true);
m_page->fillBox->setDisabled(true);
m_page->colorFillSelector->setDisabled(true);
m_page->sizeBox->setDisabled(true);
}
m_options.lineDimension = cfg->getInt("lineDimension", 0);
m_page->sizeBox->setCurrentIndex(m_options.lineDimension);
connect(m_page, SIGNAL(colorSelectorChanged()), SLOT(setColorButton()));
connect(m_page, SIGNAL(colorFillSelectorChanged()), SLOT(setColorFillButton()));
connect(m_page->colorFillSelector, SIGNAL(changed(QColor)), SLOT(colorFillChanged(QColor)));
connect(m_page->colorSelector, SIGNAL(changed(QColor)), SLOT(colorChanged(QColor)));
if (isVectorLayer) {
lockVectorLayerFunctions();
}
}
KisDlgStrokeSelection::~KisDlgStrokeSelection()
{
auto &m_options = m_page->m_options;
m_options.lineSize = m_page->lineSize->value();
m_options.lineDimension = m_page->sizeBox->currentIndex();
m_options.lineColorSource = m_page->lineColorBox->currentIndex();
KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration());
cfg->setProperty("lineSize", m_options.lineSize);
cfg->setProperty("colorFillSource", m_options._colorFillSource);
cfg->setProperty("useBrush", m_options.brushSelected);
cfg->setProperty("lineDimension", m_options.lineDimension);
cfg->setProperty("lineColorSource", m_options.lineColorSource);
QVariant colorVariant("KoColor");
colorVariant.setValue(m_options.customColor);
cfg->setProperty("customColor", colorVariant);
QVariant _colorVariant("KoColor");
_colorVariant.setValue(m_options.color);
cfg->setProperty("color", _colorVariant);
QVariant _cVariant("KoColor");
_cVariant.setValue(m_options.fillColor);
cfg->setProperty("fillColor", _cVariant);
KisConfig(false).setExportConfiguration("StrokeSelection", cfg);
delete m_page;
}
KoColor KisDlgStrokeSelection::getSelectedColor() const
{
KoColor color;
QString currentSource = m_page->lineColorBox->currentText();
if (currentSource == "Foreground color") {
color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>();
}
else if (currentSource == "Background color") {
color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>();
}
else {
color = m_page->m_options.color;
}
return color;
}
KoColor KisDlgStrokeSelection::getFillSelectedColor() const
{
KoColor color;
colorFillSource currentSource = static_cast<colorFillSource>(m_page->fillBox->currentIndex());
if (currentSource == colorFillSource::FGColor) {
color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>();
}
else if (currentSource == colorFillSource::BGColor) {
color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>();
}
else if (currentSource == colorFillSource::PaintColor) {
color = converter->approximateFromRenderedQColor(m_page->colorSelector->color());
}
else {
color = m_page->m_options.customColor;
}
return color;
}
bool KisDlgStrokeSelection::isBrushSelected() const
{
int index = m_page->typeBox->currentIndex();
drawType type = static_cast<drawType>(index);
if (type == drawType::brushDraw){
return true;
}
else {
return false;
}
}
StrokeSelectionOptions KisDlgStrokeSelection::getParams() const
{
StrokeSelectionOptions params;
params.lineSize = getLineSize();
params.color = getSelectedColor();
params.brushSelected = isBrushSelected();
params.fillColor = getFillSelectedColor();
params._colorFillSource = m_page->m_options._colorFillSource;
return params;
}
void KisDlgStrokeSelection::lockVectorLayerFunctions()
{
m_page->colorFillSelector->setEnabled(false);
m_page->lineSize->setEnabled(false);
m_page->sizeBox->setEnabled(false);
m_page->fillBox->setEnabled(false);
m_page->typeBox->setEnabled(false);
}
void KisDlgStrokeSelection::unlockVectorLayerFunctions()
{
m_page->colorFillSelector->setEnabled(true);
m_page->lineSize->setEnabled(true);
m_page->sizeBox->setEnabled(true);
m_page->fillBox->setEnabled(true);
m_page->typeBox->setEnabled(true);
}
void KisDlgStrokeSelection::setColorFillButton()
{
m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor());
}
void KisDlgStrokeSelection::setColorButton()
{
m_page->colorSelector->setColor(getSelectedColor().toQColor());
}
int KisDlgStrokeSelection::getLineSize() const
{
int value = m_page->lineSize->value();
- if (m_page->sizeBox->currentText() == "px") {
+ if (m_page->sizeBox->currentText() == i18n("px")) {
return value;
}
- else if (m_page->sizeBox->currentText() == "mm"){
+ else if (m_page->sizeBox->currentText() == i18n("mm")) {
int pixels = static_cast<int>(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel)));
return pixels;
}
else {
int pixels = static_cast<int>(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel)));
return pixels;
}
}
linePosition KisDlgStrokeSelection::getLinePosition() const
{/* TODO
int index = m_page->linePosition->currentIndex();
switch(index)
{
case(0):
return linePosition::OUTSIDE;
case(1):
return linePosition::INSIDE;
case(2):
return linePosition::CENTER;
default:
return linePosition::CENTER;
}*/
return linePosition::CENTER;
}
void KisDlgStrokeSelection::colorChanged(const QColor &newColor)
{
if (m_page->fillBox->currentText() == "Paint color") {
m_page->colorFillSelector->setColor(newColor);
}
QColor BGColor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>().toQColor();
QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>().toQColor();
KoColor tempColor= converter->approximateFromRenderedQColor(newColor);
if (!(newColor == BGColor) && !(newColor == FGColor)) {
m_page->m_options.color = tempColor;
m_page->lineColorBox->setCurrentIndex(2); //custom color
}
}
void KisDlgStrokeSelection::colorFillChanged(const QColor &newColor)
{
QColor PaintColor = m_page->colorSelector->color();
QColor BGcolor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>().toQColor();
QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>().toQColor();
KoColor tempColor= converter->approximateFromRenderedQColor(newColor);
if (!(newColor == FGColor) && !(newColor == BGcolor) && !(newColor == PaintColor)) {
m_page->m_options.customColor = tempColor;
m_page->fillBox->setCurrentIndex(static_cast<int>(colorFillSource::CustomColor));
}
m_page->m_options.fillColor = tempColor;
}
WdgStrokeSelection::WdgStrokeSelection(QWidget *parent) : QWidget(parent)
{
setupUi(this);
}
void WdgStrokeSelection::on_fillBox_currentIndexChanged(int index)
{
if (index == static_cast<int>(colorFillSource::None)) {
colorFillSelector->setDisabled(true);
}
else {
colorFillSelector->setDisabled(false);
emit colorFillSelectorChanged();
}
m_options._colorFillSource = index;
}
void WdgStrokeSelection::on_typeBox_currentIndexChanged(const QString &arg1)
{
if (arg1 == "Current Brush") {
m_options.brushSelected = true;
lineSize->setDisabled(true);
fillBox->setDisabled(true);
colorFillSelector->setDisabled(true);
sizeBox->setDisabled(true);
}
else {
m_options.brushSelected = false;
lineSize->setDisabled(false);
fillBox->setDisabled(false);
colorFillSelector->setDisabled(false);
sizeBox->setDisabled(false);
}
}
void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(const QString &/*arg1*/)
{
emit colorSelectorChanged();
}
StrokeSelectionOptions ::StrokeSelectionOptions():
lineSize(1),
brushSelected(false),
_colorFillSource(0),
lineColorSource(0),
lineDimension(0)
{
color.fromQColor(Qt::black);
fillColor.fromQColor(Qt::black);
customColor.fromQColor(Qt::black);
}
KisPainter::FillStyle StrokeSelectionOptions::fillStyle() const
{
colorFillSource tempColor = static_cast<colorFillSource>(_colorFillSource);
KisPainter::FillStyle style;
switch (tempColor) {
case colorFillSource::PaintColor:
style = KisPainter::FillStyleForegroundColor;
break;
case colorFillSource::BGColor:
style = KisPainter::FillStyleBackgroundColor;
break;
case colorFillSource::CustomColor:
style = KisPainter::FillStyleBackgroundColor;
break;
case colorFillSource::None:
style = KisPainter::FillStyleNone;
break;
case colorFillSource::FGColor:
style = KisPainter::FillStyleBackgroundColor;
break;
default:
style = KisPainter::FillStyleBackgroundColor;
}
return style;
}
diff --git a/libs/ui/flake/KisReferenceImagesLayer.cpp b/libs/ui/flake/KisReferenceImagesLayer.cpp
index 787d639c88..cfb78dc163 100644
--- a/libs/ui/flake/KisReferenceImagesLayer.cpp
+++ b/libs/ui/flake/KisReferenceImagesLayer.cpp
@@ -1,210 +1,218 @@
/*
* Copyright (C) 2017 Jouni Pentikäinen <joupent@gmail.com>
*
* 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 <KoShapeCreateCommand.h>
#include <KoShapeDeleteCommand.h>
#include <kis_node_visitor.h>
#include <kis_processing_visitor.h>
#include <kis_shape_layer_canvas.h>
#include "KisReferenceImagesLayer.h"
#include "KisReferenceImage.h"
#include "KisDocument.h"
struct AddReferenceImagesCommand : KoShapeCreateCommand
{
AddReferenceImagesCommand(KisDocument *document, KisSharedPtr<KisReferenceImagesLayer> layer, const QList<KoShape*> referenceImages)
: KoShapeCreateCommand(layer->shapeController(), referenceImages, layer.data(), nullptr, kundo2_i18n("Add reference image"))
, m_document(document)
, m_layer(layer)
{}
void redo() override {
auto layer = m_document->referenceImagesLayer();
KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer)
if (!layer) {
m_document->setReferenceImagesLayer(m_layer, true);
}
KoShapeCreateCommand::redo();
}
void undo() override {
KoShapeCreateCommand::undo();
if (m_layer->shapeCount() == 0) {
m_document->setReferenceImagesLayer(nullptr, true);
}
}
private:
KisDocument *m_document;
KisSharedPtr<KisReferenceImagesLayer> m_layer;
};
struct RemoveReferenceImagesCommand : KoShapeDeleteCommand
{
RemoveReferenceImagesCommand(KisDocument *document, KisSharedPtr<KisReferenceImagesLayer> layer, QList<KoShape *> referenceImages)
: KoShapeDeleteCommand(layer->shapeController(), referenceImages)
, m_document(document)
, m_layer(layer)
{}
void redo() override {
KoShapeDeleteCommand::redo();
if (m_layer->shapeCount() == 0) {
m_document->setReferenceImagesLayer(nullptr, true);
}
}
void undo() override {
auto layer = m_document->referenceImagesLayer();
KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer)
if (!layer) {
m_document->setReferenceImagesLayer(m_layer, true);
}
KoShapeDeleteCommand::undo();
}
private:
KisDocument *m_document;
KisSharedPtr<KisReferenceImagesLayer> m_layer;
};
class ReferenceImagesCanvas : public KisShapeLayerCanvasBase
{
public:
ReferenceImagesCanvas(KisReferenceImagesLayer *parent, KisImageWSP image)
: KisShapeLayerCanvasBase(parent, image)
, m_layer(parent)
{}
void updateCanvas(const QRectF &rect) override
{
if (!m_layer->image() || m_isDestroying) {
return;
}
QRectF r = m_viewConverter->documentToView(rect);
m_layer->signalUpdate(r);
}
void forceRepaint() override
{
m_layer->signalUpdate(m_layer->boundingImageRect());
}
bool hasPendingUpdates() const override
{
return false;
}
void rerenderAfterBeingInvisible() override {}
void resetCache() override {}
- void setImage(KisImageWSP /*image*/) override {}
+ void setImage(KisImageWSP image) override
+ {
+ m_viewConverter->setImage(image);
+ }
private:
KisReferenceImagesLayer *m_layer;
};
KisReferenceImagesLayer::KisReferenceImagesLayer(KoShapeControllerBase* shapeController, KisImageWSP image)
: KisShapeLayer(shapeController, image, i18n("Reference images"), OPACITY_OPAQUE_U8, new ReferenceImagesCanvas(this, image))
{}
KisReferenceImagesLayer::KisReferenceImagesLayer(const KisReferenceImagesLayer &rhs)
: KisShapeLayer(rhs, rhs.shapeController(), new ReferenceImagesCanvas(this, rhs.image()))
{}
KUndo2Command * KisReferenceImagesLayer::addReferenceImages(KisDocument *document, const QList<KoShape*> referenceImages)
{
KisSharedPtr<KisReferenceImagesLayer> layer = document->referenceImagesLayer();
if (!layer) {
layer = new KisReferenceImagesLayer(document->shapeController(), document->image());
}
return new AddReferenceImagesCommand(document, layer, referenceImages);
}
KUndo2Command * KisReferenceImagesLayer::removeReferenceImages(KisDocument *document, QList<KoShape*> referenceImages)
{
return new RemoveReferenceImagesCommand(document, this, referenceImages);
}
QVector<KisReferenceImage*> KisReferenceImagesLayer::referenceImages() const
{
QVector<KisReferenceImage*> references;
Q_FOREACH(auto shape, shapes()) {
KisReferenceImage *referenceImage = dynamic_cast<KisReferenceImage*>(shape);
if (referenceImage) {
references.append(referenceImage);
}
}
return references;
}
void KisReferenceImagesLayer::paintReferences(QPainter &painter) {
shapeManager()->paint(painter, *converter(), false);
}
bool KisReferenceImagesLayer::allowAsChild(KisNodeSP) const
{
return false;
}
bool KisReferenceImagesLayer::accept(KisNodeVisitor &visitor)
{
return visitor.visit(this);
}
void KisReferenceImagesLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
visitor.visit(this, undoAdapter);
}
+bool KisReferenceImagesLayer::isFakeNode() const
+{
+ return true;
+}
+
void KisReferenceImagesLayer::signalUpdate(const QRectF &rect)
{
emit sigUpdateCanvas(rect);
}
QRectF KisReferenceImagesLayer::boundingImageRect() const
{
return converter()->documentToView(boundingRect());
}
QColor KisReferenceImagesLayer::getPixel(QPointF position) const
{
const QPointF docPoint = converter()->viewToDocument(position);
KoShape *shape = shapeManager()->shapeAt(docPoint);
if (shape) {
auto *reference = dynamic_cast<KisReferenceImage*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, QColor());
return reference->getPixel(docPoint);
}
return QColor();
}
diff --git a/libs/ui/flake/KisReferenceImagesLayer.h b/libs/ui/flake/KisReferenceImagesLayer.h
index 4b49d03573..302f4fe7f7 100644
--- a/libs/ui/flake/KisReferenceImagesLayer.h
+++ b/libs/ui/flake/KisReferenceImagesLayer.h
@@ -1,70 +1,72 @@
/*
* Copyright (C) 2017 Jouni Pentikäinen <joupent@gmail.com>
*
* 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 KRITA_KISREFERENCEIMAGESLAYER_H
#define KRITA_KISREFERENCEIMAGESLAYER_H
#include "kis_shape_layer.h"
#include <kis_types.h>
class KisDocument;
class KRITAUI_EXPORT KisReferenceImagesLayer : public KisShapeLayer
{
Q_OBJECT
public:
KisReferenceImagesLayer(KoShapeControllerBase* shapeController, KisImageWSP image);
KisReferenceImagesLayer(const KisReferenceImagesLayer &rhs);
static KUndo2Command * addReferenceImages(KisDocument *document, QList<KoShape*> referenceImages);
KUndo2Command * removeReferenceImages(KisDocument *document, QList<KoShape*> referenceImages);
QVector<KisReferenceImage*> referenceImages() const;
QRectF boundingImageRect() const;
QColor getPixel(QPointF position) const;
void paintReferences(QPainter &painter);
bool allowAsChild(KisNodeSP) const override;
bool accept(KisNodeVisitor&) override;
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
KisNodeSP clone() const override {
return new KisReferenceImagesLayer(*this);
}
+ bool isFakeNode() const override;
+
Q_SIGNALS:
/**
* The content of the layer has changed, and the canvas decoration
* needs to update.
*/
void sigUpdateCanvas(const QRectF &rect);
private:
void signalUpdate(const QRectF &rect);
friend struct AddReferenceImagesCommand;
friend struct RemoveReferenceImagesCommand;
friend class ReferenceImagesCanvas;
};
#endif //KRITA_KISREFERENCEIMAGESLAYER_H
diff --git a/libs/ui/flake/kis_dummies_facade_base.h b/libs/ui/flake/kis_dummies_facade_base.h
index 00325d377e..5f9539ed91 100644
--- a/libs/ui/flake/kis_dummies_facade_base.h
+++ b/libs/ui/flake/kis_dummies_facade_base.h
@@ -1,103 +1,103 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_DUMMIES_FACADE_BASE_H
#define __KIS_DUMMIES_FACADE_BASE_H
#include <QObject>
#include "kis_types.h"
#include "kritaui_export.h"
class KisNodeDummy;
/**
* Keeps track of the node stack and manages local (UI-wide) representation
* of the node stack. It uses KisNodeDummy objects to represent the stack.
* This is done to break synchronization tie between UI and Image threads,
* caused by the fact that KisNodeModel must be synchronously notified
* when a node is removed/deleted.
*/
class KRITAUI_EXPORT KisDummiesFacadeBase : public QObject
{
Q_OBJECT
public:
KisDummiesFacadeBase(QObject *parent = 0);
~KisDummiesFacadeBase() override;
- void setImage(KisImageWSP image);
+ virtual void setImage(KisImageWSP image);
virtual bool hasDummyForNode(KisNodeSP node) const = 0;
virtual KisNodeDummy* dummyForNode(KisNodeSP node) const = 0;
virtual KisNodeDummy* rootDummy() const = 0;
virtual int dummiesCount() const = 0;
protected:
KisImageWSP image() const;
virtual void addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) = 0;
virtual void removeNodeImpl(KisNodeSP node) = 0;
Q_SIGNALS:
void sigContinueAddNode(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis);
void sigContinueRemoveNode(KisNodeSP node);
/**
* The signals for controlling the node model
*/
void sigBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType);
void sigEndInsertDummy(KisNodeDummy *dummy);
void sigBeginRemoveDummy(KisNodeDummy *dummy);
void sigEndRemoveDummy();
void sigDummyChanged(KisNodeDummy *dummy);
/**
* This signal is emitted when the shape controller wants to request
* the change of an active layer. E.g. when a new layer is added or
* when the root layer of the image is changed. It should be forwarded
* through a signal to allow queueing and synchronization of threads.
*/
void sigActivateNode(KisNodeSP node);
private Q_SLOTS:
void slotLayersChanged();
void slotNodeChanged(KisNodeSP node);
void slotNodeActivationRequested(KisNodeSP node);
void slotNodeAdded(KisNodeSP node);
void slotRemoveNode(KisNodeSP node);
void slotContinueAddNode(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis);
void slotContinueRemoveNode(KisNodeSP node);
private:
static KisNodeSP findFirstLayer(KisNodeSP root);
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_DUMMIES_FACADE_BASE_H */
diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp
index ca15103691..efd7c9c96a 100644
--- a/libs/ui/flake/kis_shape_controller.cpp
+++ b/libs/ui/flake/kis_shape_controller.cpp
@@ -1,261 +1,287 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_shape_controller.h"
#include <klocalizedstring.h>
#include <KoShape.h>
#include <KoShapeContainer.h>
#include <KoShapeManager.h>
#include <KoCanvasBase.h>
#include <KoToolManager.h>
#include <KisView.h>
#include <KoSelection.h>
#include <KoShapeLayer.h>
#include <KoPathShape.h>
#include <KoColorSpaceConstants.h>
#include <KoCanvasController.h>
#include "kis_node_manager.h"
#include "kis_shape_selection.h"
#include "kis_selection.h"
#include "kis_selection_component.h"
#include "kis_adjustment_layer.h"
#include "kis_clone_layer.h"
#include "canvas/kis_canvas2.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_group_layer.h"
#include "kis_node_shape.h"
#include "kis_node_shapes_graph.h"
#include "kis_name_server.h"
#include "kis_mask.h"
#include "kis_shape_layer.h"
#include "KisViewManager.h"
#include "kis_node.h"
#include <KoDocumentResourceManager.h>
#include <KoDataCenterBase.h>
#include <commands/kis_image_layer_add_command.h>
#include <kis_undo_adapter.h>
#include "KoSelectedShapesProxy.h"
+#include "kis_signal_auto_connection.h"
struct KisShapeController::Private
{
public:
KisDocument *doc;
KisNameServer *nameServer;
+ KisSignalAutoConnectionsStore imageConnections;
KisNodeShapesGraph shapesGraph;
};
KisShapeController::KisShapeController(KisDocument *doc, KisNameServer *nameServer)
: KisDummiesFacadeBase(doc)
, m_d(new Private())
{
m_d->doc = doc;
m_d->nameServer = nameServer;
resourceManager()->setUndoStack(doc->undoStack());
}
KisShapeController::~KisShapeController()
{
KisNodeDummy *node = m_d->shapesGraph.rootDummy();
if (node) {
m_d->shapesGraph.removeNode(node->node());
}
delete m_d;
}
+void KisShapeController::slotUpdateDocumentResolution()
+{
+ const qreal pixelsPerInch = m_d->doc->image()->xRes() * 72.0;
+ resourceManager()->setResource(KoDocumentResourceManager::DocumentResolution, pixelsPerInch);
+}
+
+void KisShapeController::slotUpdateDocumentSize()
+{
+ resourceManager()->setResource(KoDocumentResourceManager::DocumentRectInPixels, m_d->doc->image()->bounds());
+}
+
void KisShapeController::addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis)
{
KisNodeShape *newShape =
m_d->shapesGraph.addNode(node, parent, aboveThis);
// XXX: what are we going to do with this shape?
Q_UNUSED(newShape);
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(node.data());
if (shapeLayer) {
/**
* Forward signals for global shape manager
* \see comment in the constructor of KisCanvas2
*/
connect(shapeLayer, SIGNAL(selectionChanged()),
SIGNAL(selectionChanged()));
connect(shapeLayer->shapeManager(), SIGNAL(selectionContentChanged()),
SIGNAL(selectionContentChanged()));
connect(shapeLayer, SIGNAL(currentLayerChanged(const KoShapeLayer*)),
SIGNAL(currentLayerChanged(const KoShapeLayer*)));
}
}
void KisShapeController::removeNodeImpl(KisNodeSP node)
{
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(node.data());
if (shapeLayer) {
shapeLayer->disconnect(this);
}
m_d->shapesGraph.removeNode(node);
}
bool KisShapeController::hasDummyForNode(KisNodeSP node) const
{
return m_d->shapesGraph.containsNode(node);
}
KisNodeDummy* KisShapeController::dummyForNode(KisNodeSP node) const
{
return m_d->shapesGraph.nodeToDummy(node);
}
KisNodeDummy* KisShapeController::rootDummy() const
{
return m_d->shapesGraph.rootDummy();
}
int KisShapeController::dummiesCount() const
{
return m_d->shapesGraph.shapesCount();
}
static inline bool belongsToShapeSelection(KoShape* shape) {
return dynamic_cast<KisShapeSelectionMarker*>(shape->userData());
}
void KisShapeController::addShapes(const QList<KoShape*> shapes)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty());
KisCanvas2 *canvas = dynamic_cast<KisCanvas2*>(KoToolManager::instance()->activeCanvasController()->canvas());
KIS_SAFE_ASSERT_RECOVER_RETURN(canvas);
const KoShape *baseShapeParent = shapes.first()->parent();
const bool baseBelongsToSelection = belongsToShapeSelection(shapes.first());
bool allSameParent = true;
bool allSameBelongsToShapeSelection = true;
bool hasNullParent = false;
Q_FOREACH (KoShape *shape, shapes) {
hasNullParent |= !shape->parent();
allSameParent &= shape->parent() == baseShapeParent;
allSameBelongsToShapeSelection &= belongsToShapeSelection(shape) == baseBelongsToSelection;
}
KIS_SAFE_ASSERT_RECOVER_RETURN(!baseBelongsToSelection || allSameBelongsToShapeSelection);
if (!allSameParent || hasNullParent) {
if (baseBelongsToSelection && allSameBelongsToShapeSelection) {
KisSelectionSP selection = canvas->viewManager()->selection();
if (selection) {
if (!selection->shapeSelection()) {
selection->setShapeSelection(new KisShapeSelection(this, image(), selection));
}
KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
Q_FOREACH(KoShape *shape, shapes) {
shapeSelection->addShape(shape);
}
}
} else {
KisShapeLayer *shapeLayer =
dynamic_cast<KisShapeLayer*>(
canvas->selectedShapesProxy()->selection()->activeLayer());
if (!shapeLayer) {
shapeLayer = new KisShapeLayer(this, image(),
i18n("Vector Layer %1", m_d->nameServer->number()),
OPACITY_OPAQUE_U8);
image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount()));
}
QRectF updateRect;
Q_FOREACH(KoShape *shape, shapes) {
shapeLayer->addShape(shape);
updateRect |= shape->boundingRect();
}
canvas->shapeManager()->update(updateRect);
}
}
m_d->doc->setModified(true);
}
void KisShapeController::removeShape(KoShape* shape)
{
/**
* Krita layers have their own destruction path.
* It goes through slotRemoveNode()
*/
Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID &&
shape->shapeId() != KIS_SHAPE_LAYER_ID);
QRectF updateRect = shape->boundingRect();
shape->setParent(0);
KisCanvas2 *canvas = dynamic_cast<KisCanvas2*>(KoToolManager::instance()->activeCanvasController()->canvas());
KIS_SAFE_ASSERT_RECOVER_RETURN(canvas);
canvas->shapeManager()->update(updateRect);
m_d->doc->setModified(true);
}
QRectF KisShapeController::documentRectInPixels() const
{
return m_d->doc->image()->bounds();
}
qreal KisShapeController::pixelsPerInch() const
{
return m_d->doc->image()->xRes() * 72.0;
}
void KisShapeController::setInitialShapeForCanvas(KisCanvas2 *canvas)
{
if (!image()) return;
KisNodeSP rootNode = image()->root();
if (m_d->shapesGraph.containsNode(rootNode)) {
Q_ASSERT(canvas);
Q_ASSERT(canvas->shapeManager());
KoSelection *selection = canvas->shapeManager()->selection();
if (selection && m_d->shapesGraph.nodeToShape(rootNode)) {
selection->select(m_d->shapesGraph.nodeToShape(rootNode));
KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(selection->selectedShapes()));
}
}
}
+void KisShapeController::setImage(KisImageWSP image)
+{
+ m_d->imageConnections.clear();
+
+ m_d->imageConnections.addConnection(m_d->doc->image(), SIGNAL(sigResolutionChanged(double, double)), this, SLOT(slotUpdateDocumentResolution()));
+ m_d->imageConnections.addConnection(m_d->doc->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)), this, SLOT(slotUpdateDocumentSize()));
+
+ slotUpdateDocumentResolution();
+ slotUpdateDocumentSize();
+
+ KisDummiesFacadeBase::setImage(image);
+}
+
KoShapeLayer* KisShapeController::shapeForNode(KisNodeSP node) const
{
if (node) {
return m_d->shapesGraph.nodeToShape(node);
}
return 0;
}
diff --git a/libs/ui/flake/kis_shape_controller.h b/libs/ui/flake/kis_shape_controller.h
index 24871940b5..296f714fa2 100644
--- a/libs/ui/flake/kis_shape_controller.h
+++ b/libs/ui/flake/kis_shape_controller.h
@@ -1,87 +1,93 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SHAPE_CONTROLLER
#define KIS_SHAPE_CONTROLLER
#include <QMap>
#include "kis_dummies_facade_base.h"
#include <KoShapeControllerBase.h>
class KisNodeDummy;
class KoShapeLayer;
class KisCanvas2;
class KisDocument;
class KisNameServer;
/**
* KisShapeController keeps track of new layers, shapes, masks and
* selections -- everything that needs to be wrapped as a shape for
* the tools to work on.
*/
class KRITAUI_EXPORT KisShapeController : public KisDummiesFacadeBase, public KoShapeControllerBase
{
Q_OBJECT
public:
KisShapeController(KisDocument *doc, KisNameServer *nameServer);
~KisShapeController() override;
bool hasDummyForNode(KisNodeSP node) const override;
KisNodeDummy* dummyForNode(KisNodeSP layer) const override;
KisNodeDummy* rootDummy() const override;
int dummiesCount() const override;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
void setInitialShapeForCanvas(KisCanvas2 *canvas);
+ void setImage(KisImageWSP image) override;
+
private:
void addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) override;
void removeNodeImpl(KisNodeSP node) override;
+private Q_SLOTS:
+ void slotUpdateDocumentResolution();
+ void slotUpdateDocumentSize();
+
Q_SIGNALS:
/**
* These three signals are forwarded from the local shape manager of
* KisShapeLayer. This is done because we switch KoShapeManager and
* therefore KoSelection in KisCanvas2, so we need to connect local
* managers to the UI as well.
*
* \see comment in the constructor of KisCanvas2
*/
void selectionChanged();
void selectionContentChanged();
void currentLayerChanged(const KoShapeLayer*);
public:
void addShapes(const QList<KoShape*> shapes) override;
void removeShape(KoShape* shape) override;
QRectF documentRectInPixels() const override;
qreal pixelsPerInch() const override;
private:
struct Private;
Private * const m_d;
};
#endif
diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc
index dadfd0f31d..8c4f9e2e3d 100644
--- a/libs/ui/flake/kis_shape_layer.cc
+++ b/libs/ui/flake/kis_shape_layer.cc
@@ -1,724 +1,724 @@
/*
* Copyright (c) 2006-2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Thomas Zander <zander@kde.org>
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Jan Hambrecht <jaham@gmx.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_shape_layer.h"
#include <QPainter>
#include <QPainterPath>
#include <QRect>
#include <QDomElement>
#include <QDomDocument>
#include <QString>
#include <QList>
#include <QMap>
#include <kis_debug.h>
#include <kundo2command.h>
#include <commands_new/kis_node_move_command2.h>
#include <QMimeData>
#include <kis_icon.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
#include <KisDocument.h>
#include <KoUnit.h>
#include <KoOdf.h>
#include <KoOdfReadStore.h>
#include <KoOdfStylesReader.h>
#include <KoOdfLoadingContext.h>
#include <KoPageLayout.h>
#include <KoShapeContainer.h>
#include <KoShapeLayer.h>
#include <KoShapeGroup.h>
#include <KoShapeLoadingContext.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoShapeRegistry.h>
#include <KoShapeSavingContext.h>
#include <KoStore.h>
#include <KoShapeControllerBase.h>
#include <KoStoreDevice.h>
#include <KoViewConverter.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoSelection.h>
#include <KoShapeMoveCommand.h>
#include <KoShapeTransformCommand.h>
#include <KoShapeShadow.h>
#include <KoShapeShadowCommand.h>
#include "SvgWriter.h"
#include "SvgParser.h"
#include <kis_types.h>
#include <kis_image.h>
#include "kis_default_bounds.h"
#include <kis_paint_device.h>
#include "kis_shape_layer_canvas.h"
#include "kis_image_view_converter.h"
#include <kis_painter.h>
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_effect_mask.h"
#include "commands/KoShapeReorderCommand.h"
#include <SimpleShapeContainerModel.h>
class ShapeLayerContainerModel : public SimpleShapeContainerModel
{
public:
ShapeLayerContainerModel(KisShapeLayer *parent)
: q(parent)
{}
void add(KoShape *child) override {
SimpleShapeContainerModel::add(child);
/**
* The shape is always added with the absolute transformation set appropriately.
* Here we should just squeeze it into the layer's transformation.
*/
KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child));
if (inheritsTransform(child)) {
QTransform parentTransform = q->absoluteTransformation(0);
child->applyAbsoluteTransformation(parentTransform.inverted());
}
}
void remove(KoShape *child) override {
KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child));
if (inheritsTransform(child)) {
QTransform parentTransform = q->absoluteTransformation(0);
child->applyAbsoluteTransformation(parentTransform);
}
SimpleShapeContainerModel::remove(child);
}
void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override {
q->shapeManager()->addShape(shape);
SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree);
}
void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override {
q->shapeManager()->remove(shape);
SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree);
}
private:
KisShapeLayer *q;
};
struct KisShapeLayer::Private
{
public:
Private()
: canvas(0)
, controller(0)
, x(0)
, y(0)
{}
KisPaintDeviceSP paintDevice;
KisShapeLayerCanvasBase * canvas;
KoShapeControllerBase* controller;
int x;
int y;
};
KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller,
KisImageWSP image,
const QString &name,
quint8 opacity)
: KisExternalLayer(image, name, opacity),
KoShapeLayer(new ShapeLayerContainerModel(this)),
m_d(new Private())
{
initShapeLayer(controller);
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs)
: KisShapeLayer(rhs, rhs.m_d->controller)
{
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas)
: KisExternalLayer(_rhs)
, KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel
, m_d(new Private())
{
// copy the projection to avoid extra round of updates!
initShapeLayer(controller, _rhs.m_d->paintDevice, canvas);
/**
* The transformaitons of the added shapes are automatically merged into the transformation
* of the layer, so we should apply this extra transform separately
*/
const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted();
m_d->canvas->setUpdatesBlocked(true);
Q_FOREACH (KoShape *shape, _rhs.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
addShape(clonedShape);
}
- m_d->canvas->setUpdatesBlocked(true);
+ m_d->canvas->setUpdatesBlocked(false);
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes)
: KisExternalLayer(_rhs)
, KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel
, m_d(new Private())
{
// Make sure our new layer is visible otherwise the shapes cannot be painted.
setVisible(true);
initShapeLayer(_rhs.m_d->controller);
/**
* With current implementation this matrix will always be an identity, because
* we do not copy the transformation from any of the source layers. But we should
* handle this anyway, to not be caught by this in the future.
*/
const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted();
QList<KoShape *> shapesAbove;
QList<KoShape *> shapesBelow;
// copy in _rhs's shapes
Q_FOREACH (KoShape *shape, _rhs.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
shapesBelow.append(clonedShape);
}
// copy in _addShapes's shapes
Q_FOREACH (KoShape *shape, _addShapes.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
shapesAbove.append(clonedShape);
}
QList<KoShapeReorderCommand::IndexedShape> shapes =
KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove);
KoShapeReorderCommand cmd(shapes);
cmd.redo();
Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) {
addShape(shape);
}
}
KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller,
KisImageWSP image,
const QString &name,
quint8 opacity,
KisShapeLayerCanvasBase *canvas)
: KisExternalLayer(image, name, opacity)
, KoShapeLayer(new ShapeLayerContainerModel(this))
, m_d(new Private())
{
initShapeLayer(controller, nullptr, canvas);
}
KisShapeLayer::~KisShapeLayer()
{
/**
* Small hack alert: we should avoid updates on shape deletion
*/
m_d->canvas->prepareForDestroying();
Q_FOREACH (KoShape *shape, shapes()) {
shape->setParent(0);
delete shape;
}
delete m_d->canvas;
delete m_d;
}
void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas)
{
setSupportsLodMoves(false);
setShapeId(KIS_SHAPE_LAYER_ID);
KIS_ASSERT_RECOVER_NOOP(this->image());
if (!copyFromProjection) {
m_d->paintDevice = new KisPaintDevice(image()->colorSpace());
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image()));
m_d->paintDevice->setParentNode(this);
} else {
m_d->paintDevice = new KisPaintDevice(*copyFromProjection);
}
if (!canvas) {
auto *slCanvas = new KisShapeLayerCanvas(this, image());
slCanvas->setProjection(m_d->paintDevice);
canvas = slCanvas;
}
m_d->canvas = canvas;
m_d->canvas->moveToThread(this->thread());
m_d->controller = controller;
m_d->canvas->shapeManager()->selection()->disconnect(this);
connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)),
this, SIGNAL(currentLayerChanged(const KoShapeLayer*)));
connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF)));
}
bool KisShapeLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
void KisShapeLayer::setImage(KisImageWSP _image)
{
KisLayer::setImage(_image);
m_d->canvas->setImage(_image);
m_d->paintDevice->convertTo(_image->colorSpace());
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image));
}
KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer)
{
KisShapeLayer *prevShape = dynamic_cast<KisShapeLayer*>(prevLayer.data());
if (prevShape)
return new KisShapeLayer(*prevShape, *this);
else
return KisExternalLayer::createMergedLayerTemplate(prevLayer);
}
void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer)
{
if (!dynamic_cast<KisShapeLayer*>(dstLayer.data())) {
KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer);
}
}
void KisShapeLayer::setParent(KoShapeContainer *parent)
{
Q_UNUSED(parent)
KIS_ASSERT_RECOVER_RETURN(0)
}
QIcon KisShapeLayer::icon() const
{
return KisIconUtils::loadIcon("vectorLayer");
}
KisPaintDeviceSP KisShapeLayer::original() const
{
return m_d->paintDevice;
}
KisPaintDeviceSP KisShapeLayer::paintDevice() const
{
return 0;
}
qint32 KisShapeLayer::x() const
{
return m_d->x;
}
qint32 KisShapeLayer::y() const
{
return m_d->y;
}
void KisShapeLayer::setX(qint32 x)
{
qint32 delta = x - this->x();
QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0);
emit sigMoveShapes(diff);
// Save new value to satisfy LSP
m_d->x = x;
}
void KisShapeLayer::setY(qint32 y)
{
qint32 delta = y - this->y();
QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta));
emit sigMoveShapes(diff);
// Save new value to satisfy LSP
m_d->y = y;
}
namespace {
void filterTransformableShapes(QList<KoShape*> &shapes)
{
auto it = shapes.begin();
while (it != shapes.end()) {
if (shapes.size() == 1) break;
if ((*it)->inheritsTransformFromAny(shapes)) {
it = shapes.erase(it);
} else {
++it;
}
}
}
}
QList<KoShape *> KisShapeLayer::shapesToBeTransformed()
{
QList<KoShape*> shapes = shapeManager()->shapes();
// We expect that **all** the shapes inherit the transform from its parent
// SANITY_CHECK: we expect all the shapes inside the
// shape layer to inherit transform!
Q_FOREACH (KoShape *shape, shapes) {
if (shape->parent()) {
KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) {
break;
}
}
}
shapes << this;
filterTransformableShapes(shapes);
return shapes;
}
void KisShapeLayer::slotMoveShapes(const QPointF &diff)
{
QList<KoShape*> shapes = shapesToBeTransformed();
if (shapes.isEmpty()) return;
KoShapeMoveCommand cmd(shapes, diff);
cmd.redo();
}
bool KisShapeLayer::accept(KisNodeVisitor& visitor)
{
return visitor.visit(this);
}
void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
KoShapeManager* KisShapeLayer::shapeManager() const
{
return m_d->canvas->shapeManager();
}
KoViewConverter* KisShapeLayer::converter() const
{
return m_d->canvas->viewConverter();
}
bool KisShapeLayer::visible(bool recursive) const
{
return KisExternalLayer::visible(recursive);
}
void KisShapeLayer::setVisible(bool visible, bool isLoading)
{
const bool oldVisible = this->visible(false);
KoShapeLayer::setVisible(visible);
KisExternalLayer::setVisible(visible, isLoading);
if (visible && !oldVisible &&
m_d->canvas->hasChangedWhileBeingInvisible()) {
m_d->canvas->rerenderAfterBeingInvisible();
}
}
void KisShapeLayer::setUserLocked(bool value)
{
KoShapeLayer::setGeometryProtected(value);
KisExternalLayer::setUserLocked(value);
}
bool KisShapeLayer::isShapeEditable(bool recursive) const
{
return KoShapeLayer::isShapeEditable(recursive) && isEditable(true);
}
// we do not override KoShape::setGeometryProtected() as we consider
// the user not being able to access the layer shape from Krita UI!
void KisShapeLayer::forceUpdateTimedNode()
{
m_d->canvas->forceRepaint();
}
bool KisShapeLayer::hasPendingTimedUpdates() const
{
return m_d->canvas->hasPendingUpdates();
}
bool KisShapeLayer::saveShapesToStore(KoStore *store, QList<KoShape *> shapes, const QSizeF &sizeInPt)
{
if (!store->open("content.svg")) {
return false;
}
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::WriteOnly);
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
SvgWriter writer(shapes);
writer.save(storeDev, sizeInPt);
if (!store->close()) {
return false;
}
return true;
}
QList<KoShape *> KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize)
{
QString errorMsg;
int errorLine = 0;
int errorColumn;
KoXmlDocument doc = SvgParser::createDocumentFromSvg(device, &errorMsg, &errorLine, &errorColumn);
if (doc.isNull()) {
errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3"
, errorLine , errorColumn , errorMsg);
}
SvgParser parser(resourceManager);
parser.setXmlBaseDir(baseXmlDir);
parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */);
return parser.parseSvg(doc.documentElement(), fragmentSize);
}
bool KisShapeLayer::saveLayer(KoStore * store) const
{
// FIXME: we handle xRes() only!
const QSizeF sizeInPx = image()->bounds().size();
const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes());
return saveShapesToStore(store, this->shapes(), sizeInPt);
}
bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir)
{
QSizeF fragmentSize; // unused!
KisImageSP image = this->image();
// FIXME: we handle xRes() only!
KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes()));
const qreal resolutionPPI = 72.0 * image->xRes();
QList<KoShape*> shapes =
createShapesFromSvg(device, baseXmlDir,
image->bounds(), resolutionPPI,
m_d->controller->resourceManager(),
&fragmentSize);
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape);
}
return true;
}
bool KisShapeLayer::loadLayer(KoStore* store)
{
if (!store) {
warnKrita << i18n("No store backend");
return false;
}
if (store->open("content.svg")) {
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::ReadOnly);
loadSvg(&storeDev, "");
store->close();
return true;
}
KoOdfReadStore odfStore(store);
QString errorMessage;
odfStore.loadAndParse(errorMessage);
if (!errorMessage.isEmpty()) {
warnKrita << errorMessage;
return false;
}
KoXmlElement contents = odfStore.contentDoc().documentElement();
// dbgKrita <<"Start loading OASIS document..." << contents.text();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement();
KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body"));
if (body.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) );
return false;
}
body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing");
if (body.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) );
return false;
}
KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page"));
if (page.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) );
return false;
}
KoXmlElement * master = 0;
if (odfStore.styles().masterPages().contains("Standard"))
master = odfStore.styles().masterPages().value("Standard");
else if (odfStore.styles().masterPages().contains("Default"))
master = odfStore.styles().masterPages().value("Default");
else if (! odfStore.styles().masterPages().empty())
master = odfStore.styles().masterPages().begin().value();
if (master) {
const KoXmlElement *style = odfStore.styles().findStyle(
master->attributeNS(KoXmlNS::style, "page-layout-name", QString()));
KoPageLayout pageLayout;
pageLayout.loadOdf(*style);
setSize(QSizeF(pageLayout.width, pageLayout.height));
}
// We work fine without a master page
KoOdfLoadingContext context(odfStore.styles(), odfStore.store());
context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml");
KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager());
KoXmlElement layerElement;
forEachElement(layerElement, context.stylesReader().layerSet()) {
// FIXME: investigate what is this
// KoShapeLayer * l = new KoShapeLayer();
if (!loadOdf(layerElement, shapeContext)) {
dbgKrita << "Could not load vector layer!";
return false;
}
}
KoXmlElement child;
forEachElement(child, page) {
KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext);
if (shape) {
addShape(shape);
}
}
return true;
}
void KisShapeLayer::resetCache()
{
m_d->canvas->resetCache();
}
KUndo2Command* KisShapeLayer::crop(const QRect & rect)
{
QPoint oldPos(x(), y());
QPoint newPos = oldPos - rect.topLeft();
return new KisNodeMoveCommand2(this, oldPos, newPos);
}
KUndo2Command* KisShapeLayer::transform(const QTransform &transform) {
QList<KoShape*> shapes = shapesToBeTransformed();
if (shapes.isEmpty()) return 0;
KisImageViewConverter *converter = dynamic_cast<KisImageViewConverter*>(this->converter());
QTransform realTransform = converter->documentToView() *
transform * converter->viewToDocument();
QList<QTransform> oldTransformations;
QList<QTransform> newTransformations;
QList<KoShapeShadow*> newShadows;
const qreal transformBaseScale = KoUnit::approxTransformScale(transform);
Q_FOREACH (const KoShape* shape, shapes) {
QTransform oldTransform = shape->transformation();
oldTransformations.append(oldTransform);
QTransform globalTransform = shape->absoluteTransformation(0);
QTransform localTransform = globalTransform * realTransform * globalTransform.inverted();
newTransformations.append(localTransform * oldTransform);
KoShapeShadow *shadow = 0;
if (shape->shadow()) {
shadow = new KoShapeShadow(*shape->shadow());
shadow->setOffset(transformBaseScale * shadow->offset());
shadow->setBlur(transformBaseScale * shadow->blur());
}
newShadows.append(shadow);
}
KUndo2Command *parentCommand = new KUndo2Command();
new KoShapeTransformCommand(shapes,
oldTransformations,
newTransformations,
parentCommand);
new KoShapeShadowCommand(shapes,
newShadows,
parentCommand);
return parentCommand;
}
KoShapeControllerBase *KisShapeLayer::shapeController() const
{
return m_d->controller;
}
diff --git a/libs/ui/flake/kis_shape_layer_canvas.h b/libs/ui/flake/kis_shape_layer_canvas.h
index 19319291f9..594ba07447 100644
--- a/libs/ui/flake/kis_shape_layer_canvas.h
+++ b/libs/ui/flake/kis_shape_layer_canvas.h
@@ -1,141 +1,140 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SHAPE_LAYER_CANVAS_H
#define KIS_SHAPE_LAYER_CANVAS_H
#include <QMutex>
#include <QRegion>
#include <KoCanvasBase.h>
#include <kis_types.h>
#include "kis_thread_safe_signal_compressor.h"
#include <KoSelectedShapesProxy.h>
#include <KoShapeManager.h>
#include <kis_image_view_converter.h>
class KoShapeManager;
class KoToolProxy;
class KoViewConverter;
class KUndo2Command;
class QWidget;
class KoUnit;
class KisImageViewConverter;
-class KisSignalCompressor;
class KisShapeLayerCanvasBase : public KoCanvasBase
{
public:
KisShapeLayerCanvasBase(KisShapeLayer *parent, KisImageWSP image);
virtual void setImage(KisImageWSP image) = 0;
void prepareForDestroying();
virtual void forceRepaint() = 0;
virtual bool hasPendingUpdates() const = 0;
bool hasChangedWhileBeingInvisible();
virtual void rerenderAfterBeingInvisible() = 0;
virtual void resetCache() = 0;
KoShapeManager *shapeManager() const override;
KoViewConverter *viewConverter() const override;
void gridSize(QPointF *offset, QSizeF *spacing) const override;
bool snapToGrid() const override;
void addCommand(KUndo2Command *command) override;
KoSelectedShapesProxy *selectedShapesProxy() const override;
KoToolProxy * toolProxy() const override;
QWidget* canvasWidget() override;
const QWidget* canvasWidget() const override;
KoUnit unit() const override;
void updateInputMethodInfo() override {}
void setCursor(const QCursor &) override {}
void setUpdatesBlocked(bool value);
bool updatesBlocked() const;
protected:
QScopedPointer<KisImageViewConverter> m_viewConverter;
QScopedPointer<KoShapeManager> m_shapeManager;
QScopedPointer<KoSelectedShapesProxy> m_selectedShapesProxy;
bool m_hasChangedWhileBeingInvisible {false};
bool m_isDestroying {false};
bool m_updatesBlocked {false};
};
/**
* KisShapeLayerCanvas is a special canvas implementation that Krita
* uses for non-krita shapes to request updates on.
*
* Do NOT give this canvas to tools or to the KoCanvasController, it's
* not made for that.
*/
class KisShapeLayerCanvas : public KisShapeLayerCanvasBase
{
Q_OBJECT
public:
KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image);
~KisShapeLayerCanvas() override;
/// This canvas won't render onto a widget, but a projection
void setProjection(KisPaintDeviceSP projection) {
m_projection = projection;
}
void setImage(KisImageWSP image) override;
void updateCanvas(const QRectF& rc) override;
void updateCanvas(const QVector<QRectF> &region);
void forceRepaint() override;
bool hasPendingUpdates() const override;
void resetCache() override;
void rerenderAfterBeingInvisible() override;
private Q_SLOTS:
friend class KisRepaintShapeLayerLayerJob;
void repaint();
void slotStartAsyncRepaint();
void slotStartDirectSyncRepaint();
void slotImageSizeChanged();
Q_SIGNALS:
void forwardRepaint();
private:
void updateUpdateCompressorDelay();
private:
KisPaintDeviceSP m_projection;
KisShapeLayer *m_parentLayer {0};
- KisSignalCompressor m_canvasUpdateCompressor;
+ KisThreadSafeSignalCompressor m_canvasUpdateCompressor;
KisThreadSafeSignalCompressor m_asyncUpdateSignalCompressor;
volatile bool m_hasUpdateInCompressor = false;
volatile bool m_hasDirectSyncRepaintInitiated = false;
QRegion m_dirtyRegion;
QMutex m_dirtyRegionMutex;
QRect m_cachedImageRect;
KisImageWSP m_image;
};
#endif
diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp
index d675959ca9..e5f12e3ff6 100644
--- a/libs/ui/flake/kis_shape_selection.cpp
+++ b/libs/ui/flake/kis_shape_selection.cpp
@@ -1,400 +1,408 @@
/*
* Copyright (c) 2010 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2011 Jan Hambrecht <jaham@gmx.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_shape_selection.h"
#include <QPainter>
#include <QTimer>
#include <kundo2command.h>
#include <QMimeData>
+#include <QApplication>
+#include <QThread>
#include <KoShapeStroke.h>
#include <KoPathShape.h>
#include <KoShapeGroup.h>
#include <KoCompositeOp.h>
#include <KoShapeManager.h>
#include <KisDocument.h>
#include <KoEmbeddedDocumentSaver.h>
#include <KoGenStyles.h>
#include <KoOdfLoadingContext.h>
#include <KoOdfReadStore.h>
#include <KoOdfStylesReader.h>
#include <KoOdfWriteStore.h>
#include <KoXmlNS.h>
#include <KoShapeRegistry.h>
#include <KoShapeLoadingContext.h>
#include <KoXmlWriter.h>
#include <KoStore.h>
#include <KoShapeController.h>
#include <KoShapeSavingContext.h>
#include <KoStoreDevice.h>
#include <KoShapeTransformCommand.h>
#include <KoElementReference.h>
#include <kis_painter.h>
#include <kis_paint_device.h>
#include <kis_image.h>
#include <kis_iterator_ng.h>
#include <kis_selection.h>
#include "kis_shape_selection_model.h"
#include "kis_shape_selection_canvas.h"
#include "kis_take_all_shapes_command.h"
#include "kis_image_view_converter.h"
#include "kis_shape_layer.h"
#include <kis_debug.h>
KisShapeSelection::KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection)
: KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this))
, m_image(image)
, m_shapeControllerBase(shapeControllerBase)
{
Q_ASSERT(m_image);
setShapeId("KisShapeSelection");
setSelectable(false);
m_converter = new KisImageViewConverter(image);
m_canvas = new KisShapeSelectionCanvas(shapeControllerBase);
m_canvas->shapeManager()->addShape(this);
m_model->setObjectName("KisShapeSelectionModel");
m_model->moveToThread(image->thread());
m_canvas->setObjectName("KisShapeSelectionCanvas");
m_canvas->moveToThread(image->thread());
connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF)));
}
KisShapeSelection::~KisShapeSelection()
{
m_model->setShapeSelection(0);
delete m_canvas;
delete m_converter;
}
KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection)
: KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this))
{
m_image = rhs.m_image;
m_shapeControllerBase = rhs.m_shapeControllerBase;
m_converter = new KisImageViewConverter(m_image);
m_canvas = new KisShapeSelectionCanvas(m_shapeControllerBase);
m_canvas->shapeManager()->addShape(this);
Q_FOREACH (KoShape *shape, rhs.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
this->addShape(clonedShape);
}
}
KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection)
{
+ /**
+ * TODO: make cloning of vector selections safe! Right now it crashes
+ * on Windows because of manipulations with timers from non-gui thread.
+ */
+ KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread());
+
return new KisShapeSelection(*this, selection);
}
bool KisShapeSelection::saveSelection(KoStore * store) const
{
const QSizeF sizeInPx = m_image->bounds().size();
const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes());
return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt);
}
bool KisShapeSelection::loadSelection(KoStore* store)
{
QSizeF fragmentSize; // unused!
// FIXME: we handle xRes() only!
KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_image->xRes(), m_image->yRes()));
const qreal resolutionPPI = 72.0 * m_image->xRes();
QList<KoShape*> shapes;
if (store->open("content.svg")) {
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::ReadOnly);
shapes = KisShapeLayer::createShapesFromSvg(&storeDev,
"", m_image->bounds(),
resolutionPPI, m_canvas->shapeController()->resourceManager(),
&fragmentSize);
store->close();
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape);
}
return true;
}
KoOdfReadStore odfStore(store);
QString errorMessage;
odfStore.loadAndParse(errorMessage);
if (!errorMessage.isEmpty()) {
dbgKrita << errorMessage;
return false;
}
KoXmlElement contents = odfStore.contentDoc().documentElement();
// dbgKrita <<"Start loading OASIS document..." << contents.text();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement();
KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body"));
if (body.isNull()) {
dbgKrita << "No office:body found!";
//setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) );
return false;
}
body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing");
if (body.isNull()) {
dbgKrita << "No office:drawing found!";
//setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) );
return false;
}
KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page"));
if (page.isNull()) {
dbgKrita << "No office:drawing found!";
//setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) );
return false;
}
KoXmlElement * master = 0;
if (odfStore.styles().masterPages().contains("Standard"))
master = odfStore.styles().masterPages().value("Standard");
else if (odfStore.styles().masterPages().contains("Default"))
master = odfStore.styles().masterPages().value("Default");
else if (! odfStore.styles().masterPages().empty())
master = odfStore.styles().masterPages().begin().value();
if (master) {
const KoXmlElement *style = odfStore.styles().findStyle(
master->attributeNS(KoXmlNS::style, "page-layout-name", QString()));
KoPageLayout pageLayout;
pageLayout.loadOdf(*style);
setSize(QSizeF(pageLayout.width, pageLayout.height));
} else {
dbgKrita << "No master page found!";
return false;
}
KoOdfLoadingContext context(odfStore.styles(), odfStore.store());
KoShapeLoadingContext shapeContext(context, 0);
KoXmlElement layerElement;
forEachElement(layerElement, context.stylesReader().layerSet()) {
if (!loadOdf(layerElement, shapeContext)) {
dbgKrita << "Could not load vector layer!";
return false;
}
}
KoXmlElement child;
forEachElement(child, page) {
KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext);
if (shape) {
addShape(shape);
}
}
return true;
}
void KisShapeSelection::setUpdatesEnabled(bool enabled)
{
m_model->setUpdatesEnabled(enabled);
}
bool KisShapeSelection::updatesEnabled() const
{
return m_model->updatesEnabled();
}
KUndo2Command* KisShapeSelection::resetToEmpty()
{
return new KisTakeAllShapesCommand(this, true);
}
bool KisShapeSelection::isEmpty() const
{
return !m_model->count();
}
QPainterPath KisShapeSelection::outlineCache() const
{
return m_outline;
}
bool KisShapeSelection::outlineCacheValid() const
{
return true;
}
void KisShapeSelection::recalculateOutlineCache()
{
QList<KoShape*> shapesList = shapes();
QPainterPath outline;
Q_FOREACH (KoShape * shape, shapesList) {
QTransform shapeMatrix = shape->absoluteTransformation(0);
outline = outline.united(shapeMatrix.map(shape->outline()));
}
QTransform resolutionMatrix;
resolutionMatrix.scale(m_image->xRes(), m_image->yRes());
m_outline = resolutionMatrix.map(outline);
}
void KisShapeSelection::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection)
{
Q_ASSERT(projection);
Q_ASSERT(m_image);
QRectF boundingRect = outlineCache().boundingRect();
renderSelection(projection, boundingRect.toAlignedRect());
}
void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r)
{
Q_ASSERT(projection);
renderSelection(projection, r);
}
void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(projection);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_image);
const qint32 MASK_IMAGE_WIDTH = 256;
const qint32 MASK_IMAGE_HEIGHT = 256;
const QPainterPath selectionOutline = outlineCache();
if (*projection->defaultPixel().data() == OPACITY_TRANSPARENT_U8) {
projection->clear(requestedRect);
} else {
KoColor transparentColor(Qt::transparent, projection->colorSpace());
projection->fill(requestedRect, transparentColor);
}
const QRect r = requestedRect & selectionOutline.boundingRect().toAlignedRect();
QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
QPainter maskPainter(&polygonMaskImage);
maskPainter.setRenderHint(QPainter::Antialiasing, true);
// Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) {
for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) {
maskPainter.fillRect(polygonMaskImage.rect(), Qt::black);
maskPainter.translate(-x, -y);
maskPainter.fillPath(selectionOutline, Qt::white);
maskPainter.translate(x, y);
qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH);
qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT);
KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight));
while (it.nextPixel()) {
(*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y));
}
}
}
}
KoShapeManager* KisShapeSelection::shapeManager() const
{
return m_canvas->shapeManager();
}
KisShapeSelectionFactory::KisShapeSelectionFactory()
: KoShapeFactoryBase("KisShapeSelection", "selection shape container")
{
setHidden(true);
}
void KisShapeSelection::moveX(qint32 x)
{
const QPointF diff(x / m_image->xRes(), 0);
emit sigMoveShapes(diff);
}
void KisShapeSelection::moveY(qint32 y)
{
const QPointF diff(0, y / m_image->yRes());
emit sigMoveShapes(diff);
}
void KisShapeSelection::slotMoveShapes(const QPointF &diff)
{
Q_FOREACH (KoShape* shape, shapeManager()->shapes()) {
if (shape != this) {
QPointF pos = shape->position();
shape->setPosition(pos + diff);
}
}
}
// TODO same code as in vector layer, refactor!
KUndo2Command* KisShapeSelection::transform(const QTransform &transform) {
QList<KoShape*> shapes = m_canvas->shapeManager()->shapes();
if(shapes.isEmpty()) return 0;
QTransform realTransform = m_converter->documentToView() *
transform * m_converter->viewToDocument();
QList<QTransform> oldTransformations;
QList<QTransform> newTransformations;
// this code won't work if there are shapes, that inherit the transformation from the parent container.
// the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that.
Q_FOREACH (const KoShape* shape, shapes) {
QTransform oldTransform = shape->transformation();
oldTransformations.append(oldTransform);
if (dynamic_cast<const KoShapeGroup*>(shape)) {
newTransformations.append(oldTransform);
} else {
QTransform globalTransform = shape->absoluteTransformation(0);
QTransform localTransform = globalTransform * realTransform * globalTransform.inverted();
newTransformations.append(localTransform*oldTransform);
}
}
return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations);
}
diff --git a/libs/ui/forms/wdgchangeclonesource.ui b/libs/ui/forms/wdgchangeclonesource.ui
new file mode 100644
index 0000000000..17fae627af
--- /dev/null
+++ b/libs/ui/forms/wdgchangeclonesource.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>WdgChangeCloneSource</class>
+ <widget class="QWidget" name="WdgChangeCloneSource">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>358</width>
+ <height>166</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblSourceLayer">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Copy from:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cmbSourceLayer">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp
index 061547d1e4..88d2331ab4 100644
--- a/libs/ui/input/kis_input_manager.cpp
+++ b/libs/ui/input/kis_input_manager.cpp
@@ -1,754 +1,761 @@
/* This file is part of the KDE project
*
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_input_manager.h"
#include <kis_debug.h>
#include <QQueue>
#include <klocalizedstring.h>
#include <QApplication>
#include <QTouchEvent>
#include <QElapsedTimer>
#include <KoToolManager.h>
#include "kis_tool_proxy.h"
#include <kis_config.h>
#include <kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_image.h>
#include <kis_canvas_resource_provider.h>
#include <kis_favorite_resource_manager.h>
#include "kis_abstract_input_action.h"
#include "kis_tool_invocation_action.h"
#include "kis_pan_action.h"
#include "kis_alternate_invocation_action.h"
#include "kis_rotate_canvas_action.h"
#include "kis_zoom_action.h"
#include "kis_show_palette_action.h"
#include "kis_change_primary_setting_action.h"
#include "kis_shortcut_matcher.h"
#include "kis_stroke_shortcut.h"
#include "kis_single_action_shortcut.h"
#include "kis_touch_shortcut.h"
#include "kis_input_profile.h"
#include "kis_input_profile_manager.h"
#include "kis_shortcut_configuration.h"
#include <input/kis_tablet_debugger.h>
#include <kis_signal_compressor.h>
#include "kis_extended_modifiers_mapper.h"
#include "kis_input_manager_p.h"
template <typename T>
uint qHash(QPointer<T> value) {
return reinterpret_cast<quintptr>(value.data());
}
KisInputManager::KisInputManager(QObject *parent)
: QObject(parent), d(new Private(this))
{
d->setupActions();
connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool()));
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged()));
connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent()));
QApplication::instance()->
installEventFilter(new Private::ProximityNotifier(d, this));
}
KisInputManager::~KisInputManager()
{
delete d;
}
void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas)
{
d->canvasSwitcher.addCanvas(canvas);
}
void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas)
{
d->canvasSwitcher.removeCanvas(canvas);
}
void KisInputManager::toggleTabletLogger()
{
KisTabletDebugger::instance()->toggleDebugging();
}
void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority)
{
Private::PriorityList::iterator begin = d->priorityEventFilter.begin();
Private::PriorityList::iterator it = begin;
Private::PriorityList::iterator end = d->priorityEventFilter.end();
it = std::find_if(begin, end,
[filter] (const Private::PriorityPair &a) { return a.second == filter; });
if (it != end) return;
it = std::find_if(begin, end,
[priority] (const Private::PriorityPair &a) { return a.first > priority; });
d->priorityEventFilter.insert(it, qMakePair(priority, filter));
d->priorityEventFilterSeqNo++;
}
void KisInputManager::detachPriorityEventFilter(QObject *filter)
{
Private::PriorityList::iterator it = d->priorityEventFilter.begin();
Private::PriorityList::iterator end = d->priorityEventFilter.end();
it = std::find_if(it, end,
[filter] (const Private::PriorityPair &a) { return a.second == filter; });
if (it != end) {
d->priorityEventFilter.erase(it);
}
}
void KisInputManager::setupAsEventFilter(QObject *receiver)
{
if (d->eventsReceiver) {
d->eventsReceiver->removeEventFilter(this);
}
d->eventsReceiver = receiver;
if (d->eventsReceiver) {
d->eventsReceiver->installEventFilter(this);
}
}
void KisInputManager::stopIgnoringEvents()
{
d->allowMouseEvents();
}
#if defined (__clang__)
#pragma GCC diagnostic ignored "-Wswitch"
#endif
bool KisInputManager::eventFilter(QObject* object, QEvent* event)
{
if (object != d->eventsReceiver) return false;
if (d->eventEater.eventFilter(object, event)) return false;
if (!d->matcher.hasRunningShortcut()) {
int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo;
for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) {
const QPointer<QObject> &filter = it->second;
if (filter.isNull()) {
it = d->priorityEventFilter.erase(it);
d->priorityEventFilterSeqNo++;
savedPriorityEventFilterSeqNo++;
continue;
}
if (filter->eventFilter(object, event)) return true;
/**
* If the filter removed itself from the filters list or
* added something there, just exit the loop
*/
if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) {
return true;
}
++it;
}
// KoToolProxy needs to pre-process some events to ensure the
// global shortcuts (not the input manager's ones) are not
// executed, in particular, this line will accept events when the
// tool is in text editing, preventing shortcut triggering
if (d->toolProxy) {
d->toolProxy->processEvent(event);
}
}
// Continue with the actual switch statement...
return eventFilterImpl(event);
}
// Qt's events do not have copy-ctors yes, so we should emulate them
// See https://bugreports.qt.io/browse/QTBUG-72488
template <class Event> void copyEventHack(Event *src, QScopedPointer<QEvent> &dst);
template<> void copyEventHack(QMouseEvent *src, QScopedPointer<QEvent> &dst) {
QMouseEvent *tmp = new QMouseEvent(src->type(),
src->localPos(), src->windowPos(), src->screenPos(),
src->button(), src->buttons(), src->modifiers(),
src->source());
tmp->setTimestamp(src->timestamp());
dst.reset(tmp);
}
template<> void copyEventHack(QTabletEvent *src, QScopedPointer<QEvent> &dst) {
QTabletEvent *tmp = new QTabletEvent(src->type(),
src->posF(), src->globalPosF(),
src->device(), src->pointerType(),
src->pressure(),
src->xTilt(), src->yTilt(),
src->tangentialPressure(),
src->rotation(),
src->z(),
src->modifiers(),
src->uniqueId(),
src->button(), src->buttons());
tmp->setTimestamp(src->timestamp());
dst.reset(tmp);
}
template <class Event>
bool KisInputManager::compressMoveEventCommon(Event *event)
{
/**
* We construct a copy of this event object, so we must ensure it
* has a correct type.
*/
static_assert(std::is_same<Event, QMouseEvent>::value ||
std::is_same<Event, QTabletEvent>::value,
"event should be a mouse or a tablet event");
bool retval = false;
/**
* Compress the events if the tool doesn't need high resolution input
*/
if ((event->type() == QEvent::MouseMove ||
event->type() == QEvent::TabletMove) &&
(!d->matcher.supportsHiResInputEvents() ||
d->testingCompressBrushEvents)) {
copyEventHack(event, d->compressedMoveEvent);
d->moveEventCompressor.start();
/**
* On Linux Qt eats the rest of unneeded events if we
* ignore the first of the chunk of tablet events. So
* generally we should never activate this feature. Only
* for testing purposes!
*/
if (d->testingAcceptCompressedTabletEvents) {
event->setAccepted(true);
}
retval = true;
} else {
slotCompressedMoveEvent();
retval = d->handleCompressedTabletEvent(event);
}
return retval;
}
bool shouldResetWheelDelta(QEvent * event)
{
return
event->type() == QEvent::FocusIn ||
event->type() == QEvent::FocusOut ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease ||
event->type() == QEvent::MouseButtonDblClick ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease ||
event->type() == QEvent::Enter ||
event->type() == QEvent::Leave ||
event->type() == QEvent::TouchBegin ||
event->type() == QEvent::TouchEnd ||
event->type() == QEvent::TouchCancel ||
event->type() == QEvent::NativeGesture;
}
bool KisInputManager::eventFilterImpl(QEvent * event)
{
bool retval = false;
if (shouldResetWheelDelta(event)) {
d->accumulatedScrollDelta = 0;
}
switch (event->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick: {
d->debugEvent<QMouseEvent, true>(event);
if (d->touchHasBlockedPressEvents) break;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (d->tryHidePopupPalette()) {
retval = true;
} else {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent);
}
//Reset signal compressor to prevent processing events before press late
d->resetCompressor();
event->setAccepted(retval);
break;
}
case QEvent::MouseButtonRelease: {
d->debugEvent<QMouseEvent, true>(event);
if (d->touchHasBlockedPressEvents) break;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent);
event->setAccepted(retval);
break;
}
case QEvent::ShortcutOverride: {
d->debugEvent<QKeyEvent, false>(event);
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
if (!keyEvent->isAutoRepeat()) {
retval = d->matcher.keyPressed(key);
} else {
retval = d->matcher.autoRepeatedKeyPressed(key);
}
/**
* Workaround for temporary switching of tools by
* KoCanvasControllerWidget. We don't need this switch because
* we handle it ourselves.
*/
retval |= !d->forwardAllEventsToTool &&
(keyEvent->key() == Qt::Key_Space ||
keyEvent->key() == Qt::Key_Escape);
break;
}
case QEvent::KeyRelease: {
d->debugEvent<QKeyEvent, false>(event);
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if (!keyEvent->isAutoRepeat()) {
Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
retval = d->matcher.keyReleased(key);
}
break;
}
case QEvent::MouseMove: {
d->debugEvent<QMouseEvent, true>(event);
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
retval = compressMoveEventCommon(mouseEvent);
break;
}
case QEvent::Wheel: {
d->debugEvent<QWheelEvent, false>(event);
QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
#ifdef Q_OS_MACOS
// Some QT wheel events are actually touch pad pan events. From the QT docs:
// "Wheel events are generated for both mouse wheels and trackpad scroll gestures."
// We differentiate between touchpad events and real mouse wheels by inspecting the
// event source.
if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) {
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent);
break;
}
#endif
d->accumulatedScrollDelta += wheelEvent->delta();
KisSingleActionShortcut::WheelAction action;
/**
* Ignore delta 0 events on OSX, since they are triggered by tablet
* proximity when using Wacom devices.
*/
#ifdef Q_OS_MACOS
if(wheelEvent->delta() == 0) {
retval = true;
break;
}
#endif
if (wheelEvent->orientation() == Qt::Horizontal) {
if(wheelEvent->delta() < 0) {
action = KisSingleActionShortcut::WheelRight;
}
else {
action = KisSingleActionShortcut::WheelLeft;
}
}
else {
if(wheelEvent->delta() > 0) {
action = KisSingleActionShortcut::WheelUp;
}
else {
action = KisSingleActionShortcut::WheelDown;
}
}
if (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.wheelEvent(action, wheelEvent);
d->accumulatedScrollDelta = 0;
}
else {
retval = true;
}
break;
}
case QEvent::Enter:
d->debugEvent<QEvent, false>(event);
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
if (!d->containsPointer) {
d->containsPointer = true;
d->allowMouseEvents();
d->touchHasBlockedPressEvents = false;
}
d->matcher.enterEvent();
break;
case QEvent::Leave:
d->debugEvent<QEvent, false>(event);
d->containsPointer = false;
/**
* We won't get a TabletProximityLeave event when the tablet
* is hovering above some other widget, so restore cursor
* events processing right now.
*/
d->allowMouseEvents();
d->touchHasBlockedPressEvents = false;
d->matcher.leaveEvent();
break;
case QEvent::FocusIn:
d->debugEvent<QEvent, false>(event);
KisAbstractInputAction::setInputManager(this);
//Clear all state so we don't have half-matched shortcuts dangling around.
d->matcher.reinitialize();
{ // Emulate pressing of the key that are already pressed
KisExtendedModifiersMapper mapper;
Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers();
Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) {
QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
eventFilterImpl(&kevent);
}
}
d->allowMouseEvents();
break;
case QEvent::FocusOut: {
d->debugEvent<QEvent, false>(event);
KisAbstractInputAction::setInputManager(this);
QPointF currentLocalPos =
canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
d->matcher.lostFocusEvent(currentLocalPos);
break;
}
case QEvent::TabletPress: {
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
if (d->tryHidePopupPalette()) {
retval = true;
} else {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent);
if (!d->containsPointer) {
d->containsPointer = true;
d->touchHasBlockedPressEvents = false;
}
}
event->setAccepted(true);
retval = true;
d->blockMouseEvents();
//Reset signal compressor to prevent processing events before press late
d->resetCompressor();
- d->eatOneMousePress();
+
#if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH
// remove this hack when this patch is integrated:
// https://codereview.qt-project.org/#/c/255384/
event->setAccepted(false);
+ d->eatOneMousePress();
+#elif defined Q_OS_WIN32
+ /**
+ * Windows is the only platform that synthesizes mouse events for
+ * the tablet on OS-level, that is, even when we accept the event
+ */
+ d->eatOneMousePress();
#endif
break;
}
case QEvent::TabletMove: {
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
retval = compressMoveEventCommon(tabletEvent);
if (d->tabletLatencyTracker) {
d->tabletLatencyTracker->push(tabletEvent->timestamp());
}
/**
* The flow of tablet events means the tablet is in the
* proximity area, so activate it even when the
* TabletEnterProximity event was missed (may happen when
* changing focus of the window with tablet in the proximity
* area)
*/
d->blockMouseEvents();
#if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH
// remove this hack when this patch is integrated:
// https://codereview.qt-project.org/#/c/255384/
event->setAccepted(false);
#endif
break;
}
case QEvent::TabletRelease: {
#ifdef Q_OS_MAC
d->allowMouseEvents();
#endif
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent);
retval = true;
event->setAccepted(true);
#if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH
// remove this hack when this patch is integrated:
// https://codereview.qt-project.org/#/c/255384/
event->setAccepted(false);
#endif
break;
}
case QEvent::TouchBegin:
{
if (startTouch(retval)) {
QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.touchBeginEvent(tevent);
event->accept();
}
break;
}
case QEvent::TouchUpdate:
{
QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
#ifdef Q_OS_MAC
int count = 0;
Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) {
if (point.state() != Qt::TouchPointReleased) {
count++;
}
}
if (count < 2 && tevent->touchPoints().length() > count) {
d->touchHasBlockedPressEvents = false;
retval = d->matcher.touchEndEvent(tevent);
} else {
#endif
d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas();
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.touchUpdateEvent(tevent);
#ifdef Q_OS_MACOS
}
#endif
event->accept();
break;
}
case QEvent::TouchEnd:
{
endTouch();
QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
retval = d->matcher.touchEndEvent(tevent);
event->accept();
break;
}
case QEvent::NativeGesture:
{
QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event);
switch (gevent->gestureType()) {
case Qt::BeginNativeGesture:
{
if (startTouch(retval)) {
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.nativeGestureBeginEvent(gevent);
event->accept();
}
break;
}
case Qt::EndNativeGesture:
{
endTouch();
retval = d->matcher.nativeGestureEndEvent(gevent);
event->accept();
break;
}
default:
{
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.nativeGestureEvent(gevent);
event->accept();
break;
}
}
break;
}
default:
break;
}
return !retval ? d->processUnhandledEvent(event) : true;
}
bool KisInputManager::startTouch(bool &retval)
{
d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas();
// Touch rejection: if touch is disabled on canvas, no need to block mouse press events
if (KisConfig(true).disableTouchOnCanvas()) {
d->eatOneMousePress();
}
if (d->tryHidePopupPalette()) {
retval = true;
return false;
} else {
return true;
}
}
void KisInputManager::endTouch()
{
d->touchHasBlockedPressEvents = false;
}
void KisInputManager::slotCompressedMoveEvent()
{
if (d->compressedMoveEvent) {
// d->touchHasBlockedPressEvents = false;
(void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data());
d->compressedMoveEvent.reset();
//dbgInput << "Compressed move event received.";
} else {
//dbgInput << "Unexpected empty move event";
}
}
KisCanvas2* KisInputManager::canvas() const
{
return d->canvas;
}
QPointer<KisToolProxy> KisInputManager::toolProxy() const
{
return d->toolProxy;
}
void KisInputManager::slotAboutToChangeTool()
{
QPointF currentLocalPos;
if (canvas() && canvas()->canvasWidget()) {
currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
}
d->matcher.lostFocusEvent(currentLocalPos);
}
void KisInputManager::slotToolChanged()
{
if (!d->canvas) return;
KoToolManager *toolManager = KoToolManager::instance();
KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId());
if (tool) {
d->setMaskSyntheticEvents(tool->maskSyntheticEvents());
if (tool->isInTextMode()) {
d->forwardAllEventsToTool = true;
d->matcher.suppressAllActions(true);
} else {
d->forwardAllEventsToTool = false;
d->matcher.suppressAllActions(false);
}
}
}
void KisInputManager::profileChanged()
{
d->matcher.clearShortcuts();
KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile();
if (profile) {
const QList<KisShortcutConfiguration*> shortcuts = profile->allShortcuts();
for (KisShortcutConfiguration * const shortcut : shortcuts) {
dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name();
switch(shortcut->type()) {
case KisShortcutConfiguration::KeyCombinationType:
d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys());
break;
case KisShortcutConfiguration::MouseButtonType:
d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons());
break;
case KisShortcutConfiguration::MouseWheelType:
d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel());
break;
case KisShortcutConfiguration::GestureType:
if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) {
d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture());
}
break;
default:
break;
}
}
}
else {
dbgInput << "No Input Profile Found: canvas interaction will be impossible";
}
}
diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp
index 0ba70e0db3..352bb2f653 100644
--- a/libs/ui/input/kis_shortcut_matcher.cpp
+++ b/libs/ui/input/kis_shortcut_matcher.cpp
@@ -1,690 +1,687 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_shortcut_matcher.h"
#include <QEvent>
#include <QMouseEvent>
#include <QTabletEvent>
#include "kis_assert.h"
#include "kis_abstract_input_action.h"
#include "kis_stroke_shortcut.h"
#include "kis_touch_shortcut.h"
#include "kis_native_gesture_shortcut.h"
#ifdef DEBUG_MATCHER
#include <kis_debug.h>
#define DEBUG_ACTION(text) dbgInput << __FUNCTION__ << "-" << text;
#define DEBUG_SHORTCUT(text, shortcut) dbgInput << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name();
#define DEBUG_KEY(text) dbgInput << __FUNCTION__ << "-" << text << "keys:" << m_d->keys;
#define DEBUG_BUTTON_ACTION(text, button) dbgInput << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys;
#define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInput << __FUNCTION__ << "-" << text << "type:" << event->type();}
#else
#define DEBUG_ACTION(text)
#define DEBUG_KEY(text)
#define DEBUG_SHORTCUT(text, shortcut)
#define DEBUG_BUTTON_ACTION(text, button)
#define DEBUG_EVENT_ACTION(text, event)
#endif
class Q_DECL_HIDDEN KisShortcutMatcher::Private
{
public:
Private()
: runningShortcut(0)
, readyShortcut(0)
, touchShortcut(0)
, nativeGestureShortcut(0)
, actionGroupMask([] () { return AllActionGroup; })
, suppressAllActions(false)
, cursorEntered(false)
, usingTouch(false)
, usingNativeGesture(false)
{}
~Private()
{
qDeleteAll(singleActionShortcuts);
qDeleteAll(strokeShortcuts);
qDeleteAll(touchShortcuts);
}
QList<KisSingleActionShortcut*> singleActionShortcuts;
QList<KisStrokeShortcut*> strokeShortcuts;
QList<KisTouchShortcut*> touchShortcuts;
QList<KisNativeGestureShortcut*> nativeGestureShortcuts;
QSet<Qt::Key> keys; // Model of currently pressed keys
QSet<Qt::MouseButton> buttons; // Model of currently pressed buttons
KisStrokeShortcut *runningShortcut;
KisStrokeShortcut *readyShortcut;
QList<KisStrokeShortcut*> candidateShortcuts;
KisTouchShortcut *touchShortcut;
KisNativeGestureShortcut *nativeGestureShortcut;
std::function<KisInputActionGroupsMask()> actionGroupMask;
bool suppressAllActions;
bool cursorEntered;
bool usingTouch;
bool usingNativeGesture;
inline bool actionsSuppressed() const {
return suppressAllActions || !cursorEntered;
}
inline bool actionsSuppressedIgnoreFocus() const {
return suppressAllActions;
}
inline bool isUsingTouch() const {
return usingTouch || usingNativeGesture;
}
};
KisShortcutMatcher::KisShortcutMatcher()
: m_d(new Private)
{}
KisShortcutMatcher::~KisShortcutMatcher()
{
delete m_d;
}
bool KisShortcutMatcher::hasRunningShortcut() const
{
return m_d->runningShortcut;
}
void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut)
{
m_d->singleActionShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut)
{
m_d->strokeShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut )
{
m_d->touchShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut(KisNativeGestureShortcut *shortcut) {
m_d->nativeGestureShortcuts.append(shortcut);
}
bool KisShortcutMatcher::supportsHiResInputEvents()
{
return
m_d->runningShortcut &&
m_d->runningShortcut->action() &&
m_d->runningShortcut->action()->supportsHiResInputEvents();
}
bool KisShortcutMatcher::keyPressed(Qt::Key key)
{
bool retval = false;
if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); }
if (!m_d->runningShortcut) {
retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys);
}
m_d->keys.insert(key);
DEBUG_KEY("Pressed");
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key)
{
bool retval = false;
if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); }
if (!m_d->runningShortcut) {
// Autorepeated key should not be included in the shortcut
QSet<Qt::Key> filteredKeys = m_d->keys;
filteredKeys.remove(key);
retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys);
}
return retval;
}
bool KisShortcutMatcher::keyReleased(Qt::Key key)
{
if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed");
else m_d->keys.remove(key);
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return false;
}
bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event)
{
DEBUG_BUTTON_ACTION("entered", button);
bool retval = false;
if (m_d->isUsingTouch()) {
return retval;
}
if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); }
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
retval = tryRunReadyShortcut(button, event);
}
m_d->buttons.insert(button);
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event)
{
DEBUG_BUTTON_ACTION("entered", button);
bool retval = false;
if (m_d->isUsingTouch()) {
return retval;
}
if (m_d->runningShortcut && !m_d->readyShortcut) {
retval = tryEndRunningShortcut(button, event);
DEBUG_BUTTON_ACTION("ended", button);
}
if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed");
else m_d->buttons.remove(button);
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
{
if (m_d->runningShortcut || m_d->isUsingTouch()) {
DEBUG_ACTION("Wheel event canceled.");
return false;
}
return tryRunWheelShortcut(wheelAction, event);
}
bool KisShortcutMatcher::pointerMoved(QEvent *event)
{
if (m_d->isUsingTouch() || !m_d->runningShortcut) {
return false;
}
m_d->runningShortcut->action()->inputEvent(event);
return true;
}
void KisShortcutMatcher::enterEvent()
{
m_d->cursorEntered = true;
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
void KisShortcutMatcher::leaveEvent()
{
m_d->cursorEntered = false;
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event )
{
Q_UNUSED(event)
return true;
}
bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event )
{
bool retval = false;
if (m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) {
retval = tryEndTouchShortcut( event );
}
if (!m_d->touchShortcut ) {
retval = tryRunTouchShortcut( event );
}
else {
m_d->touchShortcut->action()->inputEvent( event );
retval = true;
}
return retval;
}
bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event )
{
m_d->usingTouch = false; // we need to say we are done because qt will not send further event
// we should try and end the shortcut too (it might be that there is none? (sketch))
if (tryEndTouchShortcut(event)) {
return true;
}
return false;
}
bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event)
{
Q_UNUSED(event)
return true;
}
bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event)
{
bool retval = false;
-
- if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) {
- retval = tryEndNativeGestureShortcut( event );
- }
-
if ( !m_d->nativeGestureShortcut ) {
retval = tryRunNativeGestureShortcut( event );
}
else {
m_d->nativeGestureShortcut->action()->inputEvent( event );
retval = true;
}
return retval;
}
bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event)
{
- Q_UNUSED(event)
+ if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) {
+ tryEndNativeGestureShortcut( event );
+ }
m_d->usingNativeGesture = false;
return true;
}
Qt::MouseButtons listToFlags(const QList<Qt::MouseButton> &list) {
Qt::MouseButtons flags;
Q_FOREACH (Qt::MouseButton b, list) {
flags |= b;
}
return flags;
}
void KisShortcutMatcher::reinitialize()
{
reset("reinitialize");
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
void KisShortcutMatcher::recoveryModifiersWithoutFocus(const QVector<Qt::Key> &keys)
{
Q_FOREACH (Qt::Key key, m_d->keys) {
if (!keys.contains(key)) {
keyReleased(key);
}
}
Q_FOREACH (Qt::Key key, keys) {
if (!m_d->keys.contains(key)) {
keyPressed(key);
}
}
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
DEBUG_ACTION("recoverySyncModifiers");
}
void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos)
{
if (m_d->runningShortcut) {
forceEndRunningShortcut(localPos);
}
}
void KisShortcutMatcher::reset()
{
m_d->keys.clear();
m_d->buttons.clear();
DEBUG_ACTION("reset!");
}
void KisShortcutMatcher::reset(QString msg)
{
m_d->keys.clear();
m_d->buttons.clear();
Q_UNUSED(msg);
DEBUG_ACTION(msg);
}
void KisShortcutMatcher::suppressAllActions(bool value)
{
m_d->suppressAllActions = value;
}
void KisShortcutMatcher::clearShortcuts()
{
reset("Clearing shortcuts");
qDeleteAll(m_d->singleActionShortcuts);
m_d->singleActionShortcuts.clear();
qDeleteAll(m_d->strokeShortcuts);
qDeleteAll(m_d->touchShortcuts);
m_d->strokeShortcuts.clear();
m_d->candidateShortcuts.clear();
m_d->touchShortcuts.clear();
m_d->runningShortcut = 0;
m_d->readyShortcut = 0;
}
void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function<KisInputActionGroupsMask ()> func)
{
m_d->actionGroupMask = func;
}
bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
{
return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys);
}
// Note: sometimes event can be zero!!
template<typename T, typename U>
bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState)
{
if (m_d->actionsSuppressedIgnoreFocus()) {
DEBUG_EVENT_ACTION("Event suppressed", event)
return false;
}
KisSingleActionShortcut *goodCandidate = 0;
Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
if(s->isAvailable(m_d->actionGroupMask()) &&
s->match(keysState, param) &&
(!goodCandidate || s->priority() > goodCandidate->priority())) {
goodCandidate = s;
}
}
if (goodCandidate) {
DEBUG_EVENT_ACTION("Beginning action for event", event)
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
goodCandidate->action()->end(0);
} else {
DEBUG_EVENT_ACTION("Could not match a candidate for event", event)
}
return goodCandidate;
}
void KisShortcutMatcher::prepareReadyShortcuts()
{
m_d->candidateShortcuts.clear();
if (m_d->actionsSuppressed()) return;
Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) {
if (s->matchReady(m_d->keys, m_d->buttons)) {
m_d->candidateShortcuts.append(s);
}
}
}
bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event )
{
KisStrokeShortcut *goodCandidate = 0;
Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
if (s->isAvailable(m_d->actionGroupMask()) &&
s->matchBegin(button) &&
(!goodCandidate || s->priority() > goodCandidate->priority())) {
goodCandidate = s;
}
}
if (goodCandidate) {
if (m_d->readyShortcut) {
if (m_d->readyShortcut != goodCandidate) {
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
}
m_d->readyShortcut = 0;
} else {
DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
}
DEBUG_SHORTCUT("Starting new action", goodCandidate);
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
m_d->runningShortcut = goodCandidate;
}
return goodCandidate;
}
void KisShortcutMatcher::tryActivateReadyShortcut()
{
KisStrokeShortcut *goodCandidate = 0;
Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
if (!goodCandidate || s->priority() > goodCandidate->priority()) {
goodCandidate = s;
}
}
if (goodCandidate) {
if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut);
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
m_d->readyShortcut = 0;
}
if (!m_d->readyShortcut) {
DEBUG_SHORTCUT("Preparing new ready action", goodCandidate);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
m_d->readyShortcut = goodCandidate;
}
} else if (m_d->readyShortcut) {
DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut);
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
m_d->readyShortcut = 0;
}
}
bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event )
{
Q_ASSERT(m_d->runningShortcut);
Q_ASSERT(!m_d->readyShortcut);
if (m_d->runningShortcut->matchBegin(button)) {
// first reset running shortcut to avoid infinite recursion via end()
KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
m_d->runningShortcut = 0;
if (runningShortcut->action()) {
DEBUG_EVENT_ACTION("Ending running shortcut at event", event);
KisAbstractInputAction* action = runningShortcut->action();
int shortcutIndex = runningShortcut->shortcutIndex();
action->end(event);
action->deactivate(shortcutIndex);
}
}
return !m_d->runningShortcut;
}
void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut);
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->readyShortcut);
// first reset running shortcut to avoid infinite recursion via end()
KisStrokeShortcut *runningShortcut = m_d->runningShortcut;
m_d->runningShortcut = 0;
if (runningShortcut->action()) {
DEBUG_ACTION("Forced ending running shortcut at event");
KisAbstractInputAction* action = runningShortcut->action();
int shortcutIndex = runningShortcut->shortcutIndex();
QMouseEvent event = runningShortcut->fakeEndEvent(localPos);
action->end(&event);
action->deactivate(shortcutIndex);
}
}
bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event )
{
KisTouchShortcut *goodCandidate = 0;
if (m_d->actionsSuppressed())
return false;
Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) {
if (shortcut->isAvailable(m_d->actionGroupMask()) &&
shortcut->match( event ) &&
(!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) {
goodCandidate = shortcut;
}
}
if( goodCandidate ) {
if( m_d->runningShortcut ) {
QMouseEvent mouseEvent(QEvent::MouseButtonRelease,
event->touchPoints().at(0).pos().toPoint(),
Qt::LeftButton,
Qt::LeftButton,
event->modifiers());
tryEndRunningShortcut(Qt::LeftButton, &mouseEvent);
}
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
m_d->touchShortcut = goodCandidate;
m_d->usingTouch = true;
}
return goodCandidate;
}
bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event )
{
if(m_d->touchShortcut) {
// first reset running shortcut to avoid infinite recursion via end()
KisTouchShortcut *touchShortcut = m_d->touchShortcut;
touchShortcut->action()->end(event);
touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex());
m_d->touchShortcut = 0; // empty it out now that we are done with it
return true;
}
return false;
}
bool KisShortcutMatcher::tryRunNativeGestureShortcut(QNativeGestureEvent* event)
{
KisNativeGestureShortcut *goodCandidate = 0;
if (m_d->actionsSuppressed())
return false;
Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) {
if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) {
goodCandidate = shortcut;
}
}
if (goodCandidate) {
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
m_d->nativeGestureShortcut = goodCandidate;
m_d->usingNativeGesture = true;
return true;
}
return false;
}
bool KisShortcutMatcher::tryEndNativeGestureShortcut(QNativeGestureEvent* event)
{
if (m_d->nativeGestureShortcut) {
// first reset running shortcut to avoid infinite recursion via end()
KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut;
nativeGestureShortcut->action()->end(event);
nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex());
m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it
return true;
}
return false;
}
diff --git a/libs/ui/kis_animation_importer.cpp b/libs/ui/kis_animation_importer.cpp
index f43cb43677..a4c69aab8a 100644
--- a/libs/ui/kis_animation_importer.cpp
+++ b/libs/ui/kis_animation_importer.cpp
@@ -1,130 +1,130 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_animation_importer.h"
#include <QStatusBar>
#include "KoColorSpace.h"
#include <KoUpdater.h>
#include <QApplication>
#include "KisPart.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_undo_adapter.h"
#include "kis_paint_layer.h"
#include "kis_group_layer.h"
#include "kis_raster_keyframe_channel.h"
#include "commands/kis_image_layer_add_command.h"
struct KisAnimationImporter::Private
{
KisImageSP image;
KisDocument *document;
bool stop;
KoUpdaterPtr updater;
};
KisAnimationImporter::KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater)
: m_d(new Private())
{
m_d->document = 0;
m_d->image = image;
m_d->stop = false;
m_d->updater = updater;
}
KisAnimationImporter::KisAnimationImporter(KisDocument* document)
: m_d(new Private())
{
m_d->document= document;
m_d->image = document->image();
m_d->stop = false;
}
KisAnimationImporter::~KisAnimationImporter()
{}
-KisImportExportFilter::ConversionStatus KisAnimationImporter::import(QStringList files, int firstFrame, int step)
+KisImportExportErrorCode KisAnimationImporter::import(QStringList files, int firstFrame, int step)
{
Q_ASSERT(step > 0);
m_d->image->lock();
KisUndoAdapter *undo = m_d->image->undoAdapter();
undo->beginMacro(kundo2_i18n("Import animation"));
QScopedPointer<KisDocument> importDoc(KisPart::instance()->createDocument());
importDoc->setFileBatchMode(true);
- KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
+ KisImportExportErrorCode status = ImportExportCodes::OK;
int frame = firstFrame;
int filesProcessed = 0;
if (m_d->updater) {
m_d->updater->setRange(0, files.size());
}
KisRasterKeyframeChannel *contentChannel = 0;
Q_FOREACH(QString file, files) {
bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::DontAddToRecent);
if (!successfullyLoaded) {
- status = KisImportExportFilter::InternalError;
+ status = ImportExportCodes::InternalError;
break;
}
if (frame == firstFrame) {
const KoColorSpace *cs = importDoc->image()->colorSpace();
KisPaintLayerSP paintLayer = new KisPaintLayer(m_d->image, m_d->image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
undo->addCommand(new KisImageLayerAddCommand(m_d->image, paintLayer, m_d->image->rootLayer(), m_d->image->rootLayer()->childCount()));
paintLayer->enableAnimation();
contentChannel = qobject_cast<KisRasterKeyframeChannel*>(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true));
}
if (m_d->updater) {
if (m_d->updater->interrupted()) {
m_d->stop = true;
} else {
m_d->updater->setValue(filesProcessed + 1);
// the updater doesn't call that automatically,
// it is "threaded" by default
qApp->processEvents();
}
}
if (m_d->stop) {
- status = KisImportExportFilter::ProgressCancelled;
+ status = ImportExportCodes::Cancelled;
break;
}
contentChannel->importFrame(frame, importDoc->image()->projection(), NULL);
frame += step;
filesProcessed++;
}
undo->endMacro();
m_d->image->unlock();
return status;
}
void KisAnimationImporter::cancel()
{
m_d->stop = true;
}
diff --git a/libs/ui/kis_animation_importer.h b/libs/ui/kis_animation_importer.h
index 565db0ecd7..22514080d4 100644
--- a/libs/ui/kis_animation_importer.h
+++ b/libs/ui/kis_animation_importer.h
@@ -1,48 +1,49 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
*
* 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_ANIMATION_IMPORTER_H
#define KIS_ANIMATION_IMPORTER_H
#include "kis_types.h"
#include "kritaui_export.h"
#include <KisImportExportFilter.h>
+#include <KisImportExportErrorCode.h>
class KisDocument;
class KisMainWindow;
class KRITAUI_EXPORT KisAnimationImporter : public QObject
{
Q_OBJECT
public:
KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater = 0);
KisAnimationImporter(KisDocument* document);
~KisAnimationImporter() override;
- KisImportExportFilter::ConversionStatus import(QStringList files, int firstFrame, int step);
+ KisImportExportErrorCode import(QStringList files, int firstFrame, int step);
private Q_SLOTS:
void cancel();
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif
diff --git a/libs/ui/kis_async_action_feedback.cpp b/libs/ui/kis_async_action_feedback.cpp
index 255e758d48..e5af581bc2 100644
--- a/libs/ui/kis_async_action_feedback.cpp
+++ b/libs/ui/kis_async_action_feedback.cpp
@@ -1,87 +1,87 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_async_action_feedback.h"
#include <QtConcurrent>
#include <QProgressDialog>
struct KisAsyncActionFeedback::Private
{
QScopedPointer<QProgressDialog> progress;
};
KisAsyncActionFeedback::KisAsyncActionFeedback(const QString &message, QWidget *parent)
: m_d(new Private)
{
m_d->progress.reset(new QProgressDialog(message, "", 0, 0, parent));
m_d->progress->setWindowModality(Qt::ApplicationModal);
m_d->progress->setCancelButton(0);
m_d->progress->setMinimumDuration(1000);
m_d->progress->setValue(0);
// disable close button
m_d->progress->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint);
}
KisAsyncActionFeedback::~KisAsyncActionFeedback()
{
}
template <typename T>
T runActionImpl(std::function<T()> func)
{
QFuture<T> result = QtConcurrent::run(func);
QFutureWatcher<T> watcher;
watcher.setFuture(result);
while (watcher.isRunning()) {
qApp->processEvents();
}
watcher.waitForFinished();
return watcher.result();
}
-KisImportExportFilter::ConversionStatus KisAsyncActionFeedback::runAction(std::function<KisImportExportFilter::ConversionStatus()> func)
+KisImportExportErrorCode KisAsyncActionFeedback::runAction(std::function<KisImportExportErrorCode()> func)
{
return runActionImpl(func);
}
void KisAsyncActionFeedback::runVoidAction(std::function<void()> func)
{
QFuture<void> result = QtConcurrent::run(func);
QFutureWatcher<void> watcher;
watcher.setFuture(result);
while (watcher.isRunning()) {
qApp->processEvents();
}
watcher.waitForFinished();
}
void KisAsyncActionFeedback::waitForMutex(QMutex *mutex)
{
while (!mutex->tryLock()) {
qApp->processEvents();
}
mutex->unlock();
}
diff --git a/libs/ui/kis_async_action_feedback.h b/libs/ui/kis_async_action_feedback.h
index a615a45585..59e070fd84 100644
--- a/libs/ui/kis_async_action_feedback.h
+++ b/libs/ui/kis_async_action_feedback.h
@@ -1,44 +1,44 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_ASYNC_ACTION_FEEDBACK_H
#define __KIS_ASYNC_ACTION_FEEDBACK_H
#include <QScopedPointer>
#include <functional>
#include "KisImportExportFilter.h"
class QWidget;
class QMutex;
class KisAsyncActionFeedback
{
public:
KisAsyncActionFeedback(const QString &message, QWidget *parent);
~KisAsyncActionFeedback();
- KisImportExportFilter::ConversionStatus runAction(std::function<KisImportExportFilter::ConversionStatus()> func);
+ KisImportExportErrorCode runAction(std::function<KisImportExportErrorCode()> func);
void runVoidAction(std::function<void()> func);
void waitForMutex(QMutex *mutex);
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_ASYNC_ACTION_FEEDBACK_H */
diff --git a/libs/ui/kis_autogradient.cc b/libs/ui/kis_autogradient.cc
index 5cb5fb3c0b..5d33501c5b 100644
--- a/libs/ui/kis_autogradient.cc
+++ b/libs/ui/kis_autogradient.cc
@@ -1,172 +1,172 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2004 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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_autogradient.h"
#include <QPainter>
#include <QComboBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <KoColorSpace.h>
#include <resources/KoSegmentGradient.h>
#include "kis_debug.h"
#include "KisGradientSliderWidget.h"
/****************************** KisAutogradient ******************************/
-KisAutogradient::KisAutogradient(KoSegmentGradient* gradient, QWidget *parent, const char* name, const QString& caption)
+KisAutogradientEditor::KisAutogradientEditor(KoSegmentGradient* gradient, QWidget *parent, const char* name, const QString& caption)
: QWidget(parent)
, m_autogradientResource(gradient)
{
setObjectName(name);
setupUi(this);
setWindowTitle(caption);
gradientSlider->setGradientResource(m_autogradientResource);
nameedit->setText(gradient->name());
KoGradientSegment* segment = gradientSlider->selectedSegment();
if (segment) {
slotSelectedSegment(segment);
}
connect(nameedit, SIGNAL(editingFinished()), this, SLOT(slotChangedName()));
connect(gradientSlider, SIGNAL(sigSelectedSegment(KoGradientSegment*)), SLOT(slotSelectedSegment(KoGradientSegment*)));
connect(gradientSlider, SIGNAL(sigChangedSegment(KoGradientSegment*)), SLOT(slotChangedSegment(KoGradientSegment*)));
connect(comboBoxColorInterpolationType, SIGNAL(activated(int)), SLOT(slotChangedColorInterpolation(int)));
connect(comboBoxInterpolationType, SIGNAL(activated(int)), SLOT(slotChangedInterpolation(int)));
connect(leftColorButton, SIGNAL(changed(KoColor)), SLOT(slotChangedLeftColor(KoColor)));
connect(rightColorButton, SIGNAL(changed(KoColor)), SLOT(slotChangedRightColor(KoColor)));
connect(intNumInputLeftOpacity, SIGNAL(valueChanged(int)), SLOT(slotChangedLeftOpacity(int)));
connect(intNumInputRightOpacity, SIGNAL(valueChanged(int)), SLOT(slotChangedRightOpacity(int)));
}
-void KisAutogradient::activate()
+void KisAutogradientEditor::activate()
{
paramChanged();
}
-void KisAutogradient::slotSelectedSegment(KoGradientSegment* segment)
+void KisAutogradientEditor::slotSelectedSegment(KoGradientSegment* segment)
{
leftColorButton->setColor(segment->startColor());
rightColorButton->setColor(segment->endColor());
comboBoxColorInterpolationType->setCurrentIndex(segment->colorInterpolation());
comboBoxInterpolationType->setCurrentIndex(segment->interpolation());
int leftOpacity = segment->startColor().opacityF();
intNumInputLeftOpacity->setValue(leftOpacity * 100);
intNumInputLeftOpacity->setSuffix(i18n(" %"));
int rightOpacity = segment->endColor().opacityF();
intNumInputRightOpacity->setValue(rightOpacity * 100);
intNumInputRightOpacity->setSuffix(i18n(" %"));
paramChanged();
}
-void KisAutogradient::slotChangedSegment(KoGradientSegment*)
+void KisAutogradientEditor::slotChangedSegment(KoGradientSegment*)
{
paramChanged();
}
-void KisAutogradient::slotChangedInterpolation(int type)
+void KisAutogradientEditor::slotChangedInterpolation(int type)
{
KoGradientSegment* segment = gradientSlider->selectedSegment();
if (segment)
segment->setInterpolation(type);
gradientSlider->update();
paramChanged();
}
-void KisAutogradient::slotChangedColorInterpolation(int type)
+void KisAutogradientEditor::slotChangedColorInterpolation(int type)
{
KoGradientSegment* segment = gradientSlider->selectedSegment();
if (segment)
segment->setColorInterpolation(type);
gradientSlider->update();
paramChanged();
}
-void KisAutogradient::slotChangedLeftColor(const KoColor& color)
+void KisAutogradientEditor::slotChangedLeftColor(const KoColor& color)
{
KoGradientSegment* segment = gradientSlider->selectedSegment();
if (segment) {
KoColor c(color, segment->startColor().colorSpace());
c.setOpacity(segment->startColor().opacityU8());
segment->setStartColor(c);
}
gradientSlider->update();
paramChanged();
}
-void KisAutogradient::slotChangedRightColor(const KoColor& color)
+void KisAutogradientEditor::slotChangedRightColor(const KoColor& color)
{
KoGradientSegment* segment = gradientSlider->selectedSegment();
if (segment) {
KoColor c(color, segment->endColor().colorSpace());
c.setOpacity(segment->endColor().opacityU8());
segment->setEndColor(c);
}
gradientSlider->repaint();
paramChanged();
}
-void KisAutogradient::slotChangedLeftOpacity(int value)
+void KisAutogradientEditor::slotChangedLeftOpacity(int value)
{
KoGradientSegment* segment = gradientSlider->selectedSegment();
if (segment) {
KoColor c(segment->startColor(), segment->startColor().colorSpace());
c.setOpacity(qreal(value) / qreal(100.0));
segment->setStartColor(c);
}
gradientSlider->repaint();
paramChanged();
}
-void KisAutogradient::slotChangedRightOpacity(int value)
+void KisAutogradientEditor::slotChangedRightOpacity(int value)
{
KoGradientSegment* segment = gradientSlider->selectedSegment();
if (segment) {
KoColor c(segment->endColor(), segment->endColor().colorSpace());
c.setOpacity(quint8((value *OPACITY_OPAQUE_U8) / 100));
segment->setEndColor(c);
}
gradientSlider->repaint();
paramChanged();
}
-void KisAutogradient::slotChangedName()
+void KisAutogradientEditor::slotChangedName()
{
m_autogradientResource->setName(nameedit->text());
}
-void KisAutogradient::paramChanged()
+void KisAutogradientEditor::paramChanged()
{
m_autogradientResource->updatePreview();
}
diff --git a/libs/ui/kis_autogradient.h b/libs/ui/kis_autogradient.h
index 0684fe7a80..7ba6b680a4 100644
--- a/libs/ui/kis_autogradient.h
+++ b/libs/ui/kis_autogradient.h
@@ -1,50 +1,50 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2004 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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_AUTOGRADIENT_H_
#define _KIS_AUTOGRADIENT_H_
#include "ui_wdgautogradient.h"
class KoGradientSegment;
class KoSegmentGradient;
-class KisAutogradient : public QWidget, public Ui::KisWdgAutogradient
+class KisAutogradientEditor : public QWidget, public Ui::KisWdgAutogradient
{
Q_OBJECT
public:
- KisAutogradient(KoSegmentGradient* gradient, QWidget *parent, const char* name, const QString& caption);
+ KisAutogradientEditor(KoSegmentGradient* gradient, QWidget *parent, const char* name, const QString& caption);
void activate();
private:
KoSegmentGradient* m_autogradientResource;
private Q_SLOTS:
void slotSelectedSegment(KoGradientSegment* segment);
void slotChangedSegment(KoGradientSegment* segment);
void slotChangedInterpolation(int type);
void slotChangedColorInterpolation(int type);
void slotChangedLeftColor(const KoColor& color);
void slotChangedRightColor(const KoColor& color);
void slotChangedLeftOpacity(int value);
void slotChangedRightOpacity(int value);
void slotChangedName();
void paramChanged();
};
#endif
diff --git a/libs/ui/kis_clipboard.cc b/libs/ui/kis_clipboard.cc
index 1a41ff20df..0936f51a2e 100644
--- a/libs/ui/kis_clipboard.cc
+++ b/libs/ui/kis_clipboard.cc
@@ -1,527 +1,464 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_clipboard.h"
#include <QApplication>
#include <QClipboard>
#include <QDesktopWidget>
#include <QMimeData>
#include <QObject>
#include <QImage>
#include <QMessageBox>
#include <QCheckBox>
#include <QBuffer>
#include <QGlobalStatic>
#include <klocalizedstring.h>
#include "KoColorSpace.h"
#include "KoStore.h"
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KisMimeDatabase.h>
// kritaimage
#include <kis_types.h>
#include <kis_paint_device.h>
#include <kis_debug.h>
#include <kis_annotation.h>
#include <kis_node.h>
#include <kis_image.h>
#include <kis_time_range.h>
+#include <utils/KisClipboardUtil.h>
// local
#include "kis_config.h"
#include "kis_store_paintdevice_writer.h"
#include "kis_mimedata.h"
Q_GLOBAL_STATIC(KisClipboard, s_instance)
KisClipboard::KisClipboard()
{
m_pushedClipboard = false;
m_hasClip = false;
// Check that we don't already have a clip ready
clipboardDataChanged();
// Make sure we are notified when clipboard changes
connect(QApplication::clipboard(), SIGNAL(dataChanged()),
this, SLOT(clipboardDataChanged()));
}
KisClipboard::~KisClipboard()
{
dbgRegistry << "deleting KisClipBoard";
}
KisClipboard* KisClipboard::instance()
{
return s_instance;
}
void KisClipboard::setClip(KisPaintDeviceSP dev, const QPoint& topLeft, const KisTimeRange &range)
{
if (!dev)
return;
m_hasClip = true;
// We'll create a store (ZIP format) in memory
QBuffer buffer;
QByteArray mimeType("application/x-krita-selection");
KoStore* store = KoStore::createStore(&buffer, KoStore::Write, mimeType);
KisStorePaintDeviceWriter writer(store);
Q_ASSERT(store);
Q_ASSERT(!store->bad());
// Layer data
if (store->open("layerdata")) {
if (!dev->write(writer)) {
dev->disconnect();
store->close();
delete store;
return;
}
store->close();
}
// copied frame time limits
if (range.isValid() && store->open("timeRange")) {
store->write(QString("%1 %2").arg(range.start()).arg(range.end()).toLatin1());
store->close();
}
// Coordinates
if (store->open("topLeft")) {
store->write(QString("%1 %2").arg(topLeft.x()).arg(topLeft.y()).toLatin1());
store->close();
}
// ColorSpace id of layer data
if (store->open("colormodel")) {
QString csName = dev->colorSpace()->colorModelId().id();
store->write(csName.toLatin1());
store->close();
}
if (store->open("colordepth")) {
QString csName = dev->colorSpace()->colorDepthId().id();
store->write(csName.toLatin1());
store->close();
}
if (dev->colorSpace()->profile()) {
const KoColorProfile *profile = dev->colorSpace()->profile();
KisAnnotationSP annotation;
if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) {
annotation = new KisAnnotation("icc", profile->name(), profile->rawData());
if (annotation) {
// save layer profile
if (store->open("profile.icc")) {
store->write(annotation->annotation());
store->close();
}
}
}
}
delete store;
QMimeData *mimeData = new QMimeData;
Q_CHECK_PTR(mimeData);
if (mimeData) {
mimeData->setData(mimeType, buffer.buffer());
}
// We also create a QImage so we can interchange with other applications
QImage qimage;
KisConfig cfg(true);
const KoColorProfile *monitorProfile = cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow()));
qimage = dev->convertToQImage(monitorProfile, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
if (!qimage.isNull() && mimeData) {
mimeData->setImageData(qimage);
}
if (mimeData) {
m_pushedClipboard = true;
QClipboard *cb = QApplication::clipboard();
cb->setMimeData(mimeData);
}
}
void KisClipboard::setClip(KisPaintDeviceSP dev, const QPoint& topLeft)
{
setClip(dev, topLeft, KisTimeRange());
}
KisPaintDeviceSP KisClipboard::clip(const QRect &imageBounds, bool showPopup, KisTimeRange *clipRange)
{
QByteArray mimeType("application/x-krita-selection");
if (clipRange) {
*clipRange = KisTimeRange();
}
QClipboard *cb = QApplication::clipboard();
const QMimeData *cbData = cb->mimeData();
KisPaintDeviceSP clip;
if (cbData && cbData->hasFormat(mimeType)) {
QByteArray encodedData = cbData->data(mimeType);
QBuffer buffer(&encodedData);
KoStore* store = KoStore::createStore(&buffer, KoStore::Read, mimeType);
const KoColorProfile *profile = 0;
QString csDepth, csModel;
// ColorSpace id of layer data
if (store->hasFile("colormodel")) {
store->open("colormodel");
csModel = QString(store->read(store->size()));
store->close();
}
if (store->hasFile("colordepth")) {
store->open("colordepth");
csDepth = QString(store->read(store->size()));
store->close();
}
if (store->hasFile("profile.icc")) {
QByteArray data;
store->open("profile.icc");
data = store->read(store->size());
store->close();
profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data);
}
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile);
if (cs) {
clip = new KisPaintDevice(cs);
if (store->hasFile("layerdata")) {
store->open("layerdata");
if (!clip->read(store->device())) {
clip = 0;
}
store->close();
}
if (clip && !imageBounds.isEmpty()) {
// load topLeft
if (store->hasFile("topLeft")) {
store->open("topLeft");
QString str = store->read(store->size());
store->close();
QStringList list = str.split(' ');
if (list.size() == 2) {
QPoint topLeft(list[0].toInt(), list[1].toInt());
clip->setX(topLeft.x());
clip->setY(topLeft.y());
}
}
QRect clipBounds = clip->exactBounds();
if (!imageBounds.contains(clipBounds) &&
!imageBounds.intersects(clipBounds)) {
QPoint diff = imageBounds.center() - clipBounds.center();
clip->setX(clip->x() + diff.x());
clip->setY(clip->y() + diff.y());
}
if (store->hasFile("timeRange") && clipRange) {
store->open("timeRange");
QString str = store->read(store->size());
store->close();
QStringList list = str.split(' ');
if (list.size() == 2) {
KisTimeRange range(list[0].toInt(), list[1].toInt(), true);
*clipRange = range;
qDebug() << "Pasted time range" << range;
}
}
}
}
delete store;
}
if (!clip) {
- QStringList formats = cb->mimeData()->formats();
-
- QMap<int, QImage> qimages;
-
- Q_FOREACH(const QString format, formats) {
-// qDebug() << "Format" << format;
- QImage image;
- if (format.startsWith("image")) {
- int priority = -1;
-
- QByteArray ba = cb->mimeData()->data(format);
-
-// qDebug() << "data" << ba.size() << "mime" << KisMimeDatabase::mimeTypeForData(ba);
-
- if (ba.isEmpty()) {
- continue;
- }
-
- QString qimageioFormat;
-
- if (format == "image/png") {
- qimageioFormat = "PNG";
- priority = 0;
- }
- else if (format == "image/tiff") {
- qimageioFormat = "TIFF";
- priority = 1;
- }
- else if (format == "image/bmp"
- || format == "image/x-bmp"
- || format == "image/x-MS-bmp"
- || format == "image/x-win-bitmap") {
-
- qimageioFormat = "BMP";
- priority = 2;
- }
- else if (format == "image/jpeg") {
- qimageioFormat = "JPG";
- priority = 3;
- }
-
- if (!qimageioFormat.isEmpty()) {
- image.loadFromData(ba, qimageioFormat.toLatin1());
- }
-
- if (!image.isNull()) {
-// qDebug() << "Loaded image succesfully" << image.format() << image.hasAlphaChannel();
- qimages[priority] = image;
- }
-// else {
-// qDebug() << "Could not load image of type" << format;
-// }
- }
- }
-
- QImage qimage = cb->image();
-
- for(int i = 0; i < 4; i++) {
- if (qimages.contains(i)) {
- qimage = qimages[i];
- break;
- }
- }
-
-// qDebug() << "Final QImage" << qimage.format() << qimage.hasAlphaChannel();
+ QImage qimage = KisClipboardUtil::getImageFromClipboard();
if (qimage.isNull()) {
return KisPaintDeviceSP(0);
}
KisConfig cfg(true);
quint32 behaviour = cfg.pasteBehaviour();
bool saveColorSetting = false;
if (behaviour == PASTE_ASK && showPopup) {
// Ask user each time.
QMessageBox mb(qApp->activeWindow());
QCheckBox dontPrompt(i18n("Remember"), &mb);
dontPrompt.blockSignals(true);
mb.setWindowTitle(i18nc("@title:window", "Missing Color Profile"));
mb.setText(i18n("The image data you are trying to paste has no color profile information. How do you want to interpret these data? \n\n As Web (sRGB) - Use standard colors that are displayed from computer monitors. This is the most common way that images are stored. \n\nAs on Monitor - If you know a bit about color management and want to use your monitor to determine the color profile.\n\n"));
// the order of how you add these buttons matters as it determines the index.
mb.addButton(i18n("As &Web"), QMessageBox::AcceptRole);
mb.addButton(i18n("As on &Monitor"), QMessageBox::AcceptRole);
mb.addButton(i18n("Cancel"), QMessageBox::RejectRole);
mb.addButton(&dontPrompt, QMessageBox::ActionRole);
behaviour = mb.exec();
if (behaviour > 1) {
return 0;
}
saveColorSetting = dontPrompt.isChecked(); // should we save this option to the config for next time?
}
const KoColorSpace * cs;
const KoColorProfile *profile = 0;
if (behaviour == PASTE_ASSUME_MONITOR)
profile = cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow()));
cs = KoColorSpaceRegistry::instance()->rgb8(profile);
if (!cs) {
cs = KoColorSpaceRegistry::instance()->rgb8();
profile = cs->profile();
}
clip = new KisPaintDevice(cs);
Q_CHECK_PTR(clip);
clip->convertFromQImage(qimage, profile);
QRect clipBounds = clip->exactBounds();
QPoint diff = imageBounds.center() - clipBounds.center();
clip->setX(diff.x());
clip->setY(diff.y());
// save the persion's selection to the configuration if the option is checked
if (saveColorSetting) {
cfg.setPasteBehaviour(behaviour);
}
}
return clip;
}
void KisClipboard::clipboardDataChanged()
{
if (!m_pushedClipboard) {
m_hasClip = false;
QClipboard *cb = QApplication::clipboard();
if (cb->mimeData()->hasImage()) {
QImage qimage = cb->image();
if (!qimage.isNull())
m_hasClip = true;
const QMimeData *cbData = cb->mimeData();
QByteArray mimeType("application/x-krita-selection");
if (cbData && cbData->hasFormat(mimeType))
m_hasClip = true;
}
}
if (m_hasClip) {
emit clipCreated();
}
m_pushedClipboard = false;
emit clipChanged();
}
bool KisClipboard::hasClip() const
{
return m_hasClip;
}
QSize KisClipboard::clipSize() const
{
QClipboard *cb = QApplication::clipboard();
QByteArray mimeType("application/x-krita-selection");
const QMimeData *cbData = cb->mimeData();
KisPaintDeviceSP clip;
if (cbData && cbData->hasFormat(mimeType)) {
QByteArray encodedData = cbData->data(mimeType);
QBuffer buffer(&encodedData);
KoStore* store = KoStore::createStore(&buffer, KoStore::Read, mimeType);
const KoColorProfile *profile = 0;
QString csDepth, csModel;
// ColorSpace id of layer data
if (store->hasFile("colormodel")) {
store->open("colormodel");
csModel = QString(store->read(store->size()));
store->close();
}
if (store->hasFile("colordepth")) {
store->open("colordepth");
csDepth = QString(store->read(store->size()));
store->close();
}
if (store->hasFile("profile.icc")) {
QByteArray data;
store->open("profile.icc");
data = store->read(store->size());
store->close();
profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data);
}
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile);
if (!cs) {
cs = KoColorSpaceRegistry::instance()->rgb8();
}
clip = new KisPaintDevice(cs);
if (store->hasFile("layerdata")) {
store->open("layerdata");
clip->read(store->device());
store->close();
}
delete store;
return clip->exactBounds().size();
} else {
if (cb->mimeData()->hasImage()) {
QImage qimage = cb->image();
return qimage.size();
}
}
return QSize();
}
void KisClipboard::setLayers(KisNodeList nodes, KisImageSP image, bool forceCopy)
{
/**
* See a comment in KisMimeData::deepCopyNodes()
*/
QMimeData *data = KisMimeData::mimeForLayersDeepCopy(nodes, image, forceCopy);
if (!data) return;
QClipboard *cb = QApplication::clipboard();
cb->setMimeData(data);
}
bool KisClipboard::hasLayers() const
{
QClipboard *cb = QApplication::clipboard();
const QMimeData *cbData = cb->mimeData();
return cbData->hasFormat("application/x-krita-node");
}
const QMimeData* KisClipboard::layersMimeData() const
{
QClipboard *cb = QApplication::clipboard();
const QMimeData *cbData = cb->mimeData();
return cbData->hasFormat("application/x-krita-node") ? cbData : 0;
}
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc
index 62fa1804a2..b81b35d856 100644
--- a/libs/ui/kis_config.cc
+++ b/libs/ui/kis_config.cc
@@ -1,2149 +1,2151 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_config.h"
#include <limits.h>
#include <QtGlobal>
#include <QApplication>
#include <QDesktopWidget>
#include <QMutex>
#include <QFont>
#include <QThread>
#include <QStringList>
#include <QSettings>
#include <QStandardPaths>
#include <kconfig.h>
#include <KisDocument.h>
#include <KoColor.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoColorProfile.h>
#include <kis_debug.h>
#include <kis_types.h>
#include "kis_canvas_resource_provider.h"
#include "kis_config_notifier.h"
#include "kis_snap_config.h"
#include <config-ocio.h>
#include <kis_color_manager.h>
#include <KisOcioConfiguration.h>
#ifdef Q_OS_WIN
#include "config_use_qt_tablet_windows.h"
#endif
KisConfig::KisConfig(bool readOnly)
: m_cfg( KSharedConfig::openConfig()->group(""))
, m_readOnly(readOnly)
{
if (!readOnly) {
KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread());
}
}
KisConfig::~KisConfig()
{
if (m_readOnly) return;
if (qApp && qApp->thread() != QThread::currentThread()) {
dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace();
return;
}
m_cfg.sync();
}
bool KisConfig::disableTouchOnCanvas(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false));
}
void KisConfig::setDisableTouchOnCanvas(bool value) const
{
m_cfg.writeEntry("disableTouchOnCanvas", value);
}
bool KisConfig::useProjections(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useProjections", true));
}
void KisConfig::setUseProjections(bool useProj) const
{
m_cfg.writeEntry("useProjections", useProj);
}
bool KisConfig::undoEnabled(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true));
}
void KisConfig::setUndoEnabled(bool undo) const
{
m_cfg.writeEntry("undoEnabled", undo);
}
int KisConfig::undoStackLimit(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30));
}
void KisConfig::setUndoStackLimit(int limit) const
{
m_cfg.writeEntry("undoStackLimit", limit);
}
bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false));
}
void KisConfig::setCumulativeUndoRedo(bool value)
{
m_cfg.writeEntry("useCumulativeUndoRedo", value);
}
qreal KisConfig::stackT1(bool defaultValue) const
{
return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5));
}
void KisConfig::setStackT1(int T1)
{
m_cfg.writeEntry("stackT1", T1);
}
qreal KisConfig::stackT2(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1));
}
void KisConfig::setStackT2(int T2)
{
m_cfg.writeEntry("stackT2", T2);
}
int KisConfig::stackN(bool defaultValue) const
{
return (defaultValue ? 5 : m_cfg.readEntry("stackN",5));
}
void KisConfig::setStackN(int N)
{
m_cfg.writeEntry("stackN", N);
}
qint32 KisConfig::defImageWidth(bool defaultValue) const
{
return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600));
}
qint32 KisConfig::defImageHeight(bool defaultValue) const
{
return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200));
}
qreal KisConfig::defImageResolution(bool defaultValue) const
{
return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0;
}
QString KisConfig::defColorModel(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id()
: m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id()));
}
void KisConfig::defColorModel(const QString & model) const
{
m_cfg.writeEntry("colorModelDef", model);
}
QString KisConfig::defaultColorDepth(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id()
: m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id()));
}
void KisConfig::setDefaultColorDepth(const QString & depth) const
{
m_cfg.writeEntry("colorDepthDef", depth);
}
QString KisConfig::defColorProfile(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() :
m_cfg.readEntry("colorProfileDef",
KoColorSpaceRegistry::instance()->rgb8()->profile()->name()));
}
void KisConfig::defColorProfile(const QString & profile) const
{
m_cfg.writeEntry("colorProfileDef", profile);
}
void KisConfig::defImageWidth(qint32 width) const
{
m_cfg.writeEntry("imageWidthDef", width);
}
void KisConfig::defImageHeight(qint32 height) const
{
m_cfg.writeEntry("imageHeightDef", height);
}
void KisConfig::defImageResolution(qreal res) const
{
m_cfg.writeEntry("imageResolutionDef", res*72.0);
}
int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const
{
return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0);
}
void KisConfig::setPreferredVectorImportResolutionPPI(int value) const
{
m_cfg.writeEntry("preferredVectorImportResolution", value);
}
void cleanOldCursorStyleKeys(KConfigGroup &cfg)
{
if (cfg.hasKey("newCursorStyle") &&
cfg.hasKey("newOutlineStyle")) {
cfg.deleteEntry("cursorStyleDef");
}
}
CursorStyle KisConfig::newCursorStyle(bool defaultValue) const
{
if (defaultValue) {
return CURSOR_STYLE_NO_CURSOR;
}
int style = m_cfg.readEntry("newCursorStyle", int(-1));
if (style < 0) {
// old style format
style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE));
switch (style) {
case OLD_CURSOR_STYLE_TOOLICON:
style = CURSOR_STYLE_TOOLICON;
break;
case OLD_CURSOR_STYLE_CROSSHAIR:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS:
style = CURSOR_STYLE_CROSSHAIR;
break;
case OLD_CURSOR_STYLE_POINTER:
style = CURSOR_STYLE_POINTER;
break;
case OLD_CURSOR_STYLE_OUTLINE:
case OLD_CURSOR_STYLE_NO_CURSOR:
style = CURSOR_STYLE_NO_CURSOR;
break;
case OLD_CURSOR_STYLE_SMALL_ROUND:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT:
style = CURSOR_STYLE_SMALL_ROUND;
break;
case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED:
style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED;
break;
case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED:
style = CURSOR_STYLE_TRIANGLE_LEFTHANDED;
break;
default:
style = -1;
}
}
cleanOldCursorStyleKeys(m_cfg);
// compatibility with future versions
if (style < 0 || style >= N_CURSOR_STYLE_SIZE) {
style = CURSOR_STYLE_NO_CURSOR;
}
return (CursorStyle) style;
}
void KisConfig::setNewCursorStyle(CursorStyle style)
{
m_cfg.writeEntry("newCursorStyle", (int)style);
}
QColor KisConfig::getCursorMainColor(bool defaultValue) const
{
QColor col;
col.setRgbF(0.501961, 1.0, 0.501961);
return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col));
}
void KisConfig::setCursorMainColor(const QColor &v) const
{
m_cfg.writeEntry("cursorMaincColor", v);
}
OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const
{
if (defaultValue) {
return OUTLINE_FULL;
}
int style = m_cfg.readEntry("newOutlineStyle", int(-1));
if (style < 0) {
// old style format
style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE));
switch (style) {
case OLD_CURSOR_STYLE_TOOLICON:
case OLD_CURSOR_STYLE_CROSSHAIR:
case OLD_CURSOR_STYLE_POINTER:
case OLD_CURSOR_STYLE_NO_CURSOR:
case OLD_CURSOR_STYLE_SMALL_ROUND:
case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED:
style = OUTLINE_NONE;
break;
case OLD_CURSOR_STYLE_OUTLINE:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED:
style = OUTLINE_FULL;
break;
default:
style = -1;
}
}
cleanOldCursorStyleKeys(m_cfg);
// compatibility with future versions
if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) {
style = OUTLINE_FULL;
}
return (OutlineStyle) style;
}
void KisConfig::setNewOutlineStyle(OutlineStyle style)
{
m_cfg.writeEntry("newOutlineStyle", (int)style);
}
QRect KisConfig::colorPreviewRect() const
{
return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect();
}
void KisConfig::setColorPreviewRect(const QRect &rect)
{
m_cfg.writeEntry("colorPreviewRect", QVariant(rect));
}
bool KisConfig::useDirtyPresets(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false));
}
void KisConfig::setUseDirtyPresets(bool value)
{
m_cfg.writeEntry("useDirtyPresets",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::useEraserBrushSize(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false));
}
void KisConfig::setUseEraserBrushSize(bool value)
{
m_cfg.writeEntry("useEraserBrushSize",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::useEraserBrushOpacity(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false));
}
void KisConfig::setUseEraserBrushOpacity(bool value)
{
m_cfg.writeEntry("useEraserBrushOpacity",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
QString KisConfig::getMDIBackgroundColor(bool defaultValue) const
{
QColor col(77, 77, 77);
KoColor kol(KoColorSpaceRegistry::instance()->rgb8());
kol.fromQColor(col);
QString xml = kol.toXML();
return (defaultValue ? xml : m_cfg.readEntry("mdiBackgroundColorXML", xml));
}
void KisConfig::setMDIBackgroundColor(const QString &v) const
{
m_cfg.writeEntry("mdiBackgroundColorXML", v);
}
QString KisConfig::getMDIBackgroundImage(bool defaultValue) const
{
return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", ""));
}
void KisConfig::setMDIBackgroundImage(const QString &filename) const
{
m_cfg.writeEntry("mdiBackgroundImage", filename);
}
QString KisConfig::monitorProfile(int screen) const
{
// Note: keep this in sync with the default profile for the RGB colorspaces!
QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc");
//dbgKrita << "KisConfig::monitorProfile()" << profile;
return profile;
}
QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const
{
return (defaultValue ? defaultMonitor
: m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor));
}
void KisConfig::setMonitorForScreen(int screen, const QString& monitor)
{
m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor);
}
void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const
{
m_cfg.writeEntry("monitorProfile/OverrideX11", override);
m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile);
}
const KoColorProfile *KisConfig::getScreenProfile(int screen)
{
if (screen < 0) return 0;
KisConfig cfg(true);
QString monitorId;
if (KisColorManager::instance()->devices().size() > screen) {
monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]);
}
//dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId;
if (monitorId.isEmpty()) {
return 0;
}
QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId);
//dbgKrita << "\tgetScreenProfile()" << bytes.size();
if (bytes.length() > 0) {
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes);
//dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name();
return profile;
}
else {
//dbgKrita << "\tCould not get a system monitor profile";
return 0;
}
}
const KoColorProfile *KisConfig::displayProfile(int screen) const
{
if (screen < 0) return 0;
// if the user plays with the settings, they can override the display profile, in which case
// we don't want the system setting.
bool override = useSystemMonitorProfile();
//dbgKrita << "KisConfig::displayProfile(). Override X11:" << override;
const KoColorProfile *profile = 0;
if (override) {
//dbgKrita << "\tGoing to get the screen profile";
profile = KisConfig::getScreenProfile(screen);
}
// if it fails. check the configuration
if (!profile || !profile->isSuitableForDisplay()) {
//dbgKrita << "\tGoing to get the monitor profile";
QString monitorProfileName = monitorProfile(screen);
//dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName;
if (!monitorProfileName.isEmpty()) {
profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName);
}
if (profile) {
//dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay();
}
else {
//dbgKrita << "\t\tstill no profile";
}
}
// if we still don't have a profile, or the profile isn't suitable for display,
// we need to get a last-resort profile. the built-in sRGB is a good choice then.
if (!profile || !profile->isSuitableForDisplay()) {
//dbgKrita << "\tnothing worked, going to get sRGB built-in";
profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in");
}
if (profile) {
//dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name();
}
else {
//dbgKrita << "\tCouldn't get a display profile at all";
}
return profile;
}
QString KisConfig::workingColorSpace(bool defaultValue) const
{
return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA"));
}
void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const
{
m_cfg.writeEntry("workingColorSpace", workingColorSpace);
}
QString KisConfig::printerColorSpace(bool /*defaultValue*/) const
{
//TODO currently only rgb8 is supported
//return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA"));
return QString("RGBA");
}
void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const
{
m_cfg.writeEntry("printerColorSpace", printerColorSpace);
}
QString KisConfig::printerProfile(bool defaultValue) const
{
return (defaultValue ? "" : m_cfg.readEntry("printerProfile", ""));
}
void KisConfig::setPrinterProfile(const QString & printerProfile) const
{
m_cfg.writeEntry("printerProfile", printerProfile);
}
bool KisConfig::useBlackPointCompensation(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true));
}
void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const
{
m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation);
}
bool KisConfig::allowLCMSOptimization(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true));
}
void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization)
{
m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization);
}
bool KisConfig::forcePaletteColors(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("colorsettings/forcepalettecolors", false));
}
void KisConfig::setForcePaletteColors(bool forcePaletteColors)
{
m_cfg.writeEntry("colorsettings/forcepalettecolors", forcePaletteColors);
}
bool KisConfig::showRulers(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showrulers", false));
}
void KisConfig::setShowRulers(bool rulers) const
{
m_cfg.writeEntry("showrulers", rulers);
}
bool KisConfig::forceShowSaveMessages(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false));
}
void KisConfig::setForceShowSaveMessages(bool value) const
{
m_cfg.writeEntry("forceShowSaveMessages", value);
}
bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false));
}
void KisConfig::setForceShowAutosaveMessages(bool value) const
{
m_cfg.writeEntry("forceShowAutosaveMessages", value);
}
bool KisConfig::rulersTrackMouse(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true));
}
void KisConfig::setRulersTrackMouse(bool value) const
{
m_cfg.writeEntry("rulersTrackMouse", value);
}
qint32 KisConfig::pasteBehaviour(bool defaultValue) const
{
return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2));
}
void KisConfig::setPasteBehaviour(qint32 renderIntent) const
{
m_cfg.writeEntry("pasteBehaviour", renderIntent);
}
qint32 KisConfig::monitorRenderIntent(bool defaultValue) const
{
qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL);
if (intent > 3) intent = 3;
if (intent < 0) intent = 0;
return (defaultValue ? INTENT_PERCEPTUAL : intent);
}
void KisConfig::setRenderIntent(qint32 renderIntent) const
{
if (renderIntent > 3) renderIntent = 3;
if (renderIntent < 0) renderIntent = 0;
m_cfg.writeEntry("renderIntent", renderIntent);
}
bool KisConfig::useOpenGL(bool defaultValue) const
{
if (defaultValue) {
return true;
}
//dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS");
QString cs = canvasState();
#ifdef Q_OS_WIN
return (m_cfg.readEntry("useOpenGLWindows", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL"));
#else
return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL"));
#endif
}
void KisConfig::setUseOpenGL(bool useOpenGL) const
{
#ifdef Q_OS_WIN
m_cfg.writeEntry("useOpenGLWindows", useOpenGL);
#else
m_cfg.writeEntry("useOpenGL", useOpenGL);
#endif
}
int KisConfig::openGLFilteringMode(bool defaultValue) const
{
return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3));
}
void KisConfig::setOpenGLFilteringMode(int filteringMode)
{
m_cfg.writeEntry("OpenGLFilterMode", filteringMode);
}
bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true));
}
void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer)
{
m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer);
}
int KisConfig::openGLTextureSize(bool defaultValue) const
{
return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256));
}
bool KisConfig::disableVSync(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("disableVSync", true));
}
void KisConfig::setDisableVSync(bool disableVSync)
{
m_cfg.writeEntry("disableVSync", disableVSync);
}
bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false));
}
bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false));
}
int KisConfig::numMipmapLevels(bool defaultValue) const
{
return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4));
}
int KisConfig::textureOverlapBorder() const
{
return 1 << qMax(0, numMipmapLevels());
}
quint32 KisConfig::getGridMainStyle(bool defaultValue) const
{
int v = m_cfg.readEntry("gridmainstyle", 0);
v = qBound(0, v, 2);
return (defaultValue ? 0 : v);
}
void KisConfig::setGridMainStyle(quint32 v) const
{
m_cfg.writeEntry("gridmainstyle", v);
}
quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const
{
quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1);
if (v > 2) v = 2;
return (defaultValue ? 1 : v);
}
void KisConfig::setGridSubdivisionStyle(quint32 v) const
{
m_cfg.writeEntry("gridsubdivisionstyle", v);
}
QColor KisConfig::getGridMainColor(bool defaultValue) const
{
QColor col(99, 99, 99);
return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col));
}
void KisConfig::setGridMainColor(const QColor & v) const
{
m_cfg.writeEntry("gridmaincolor", v);
}
QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const
{
QColor col(150, 150, 150);
return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col));
}
void KisConfig::setGridSubdivisionColor(const QColor & v) const
{
m_cfg.writeEntry("gridsubdivisioncolor", v);
}
QColor KisConfig::getPixelGridColor(bool defaultValue) const
{
QColor col(255, 255, 255);
return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col));
}
void KisConfig::setPixelGridColor(const QColor & v) const
{
m_cfg.writeEntry("pixelGridColor", v);
}
qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const
{
qreal border = 24.0f;
return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border));
}
void KisConfig::setPixelGridDrawingThreshold(qreal v) const
{
m_cfg.writeEntry("pixelGridDrawingThreshold", v);
}
bool KisConfig::pixelGridEnabled(bool defaultValue) const
{
bool enabled = true;
return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled));
}
void KisConfig::enablePixelGrid(bool v) const
{
m_cfg.writeEntry("pixelGridEnabled", v);
}
quint32 KisConfig::guidesLineStyle(bool defaultValue) const
{
int v = m_cfg.readEntry("guidesLineStyle", 0);
v = qBound(0, v, 2);
return (defaultValue ? 0 : v);
}
void KisConfig::setGuidesLineStyle(quint32 v) const
{
m_cfg.writeEntry("guidesLineStyle", v);
}
QColor KisConfig::guidesColor(bool defaultValue) const
{
QColor col(99, 99, 99);
return (defaultValue ? col : m_cfg.readEntry("guidesColor", col));
}
void KisConfig::setGuidesColor(const QColor & v) const
{
m_cfg.writeEntry("guidesColor", v);
}
void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const
{
KisSnapConfig defaultConfig(false);
if (defaultValue) {
*config = defaultConfig;
return;
}
config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal()));
config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node()));
config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension()));
config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection()));
config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox()));
config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds()));
config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter()));
config->setToPixel(m_cfg.readEntry("globalSnapToPixel", defaultConfig.toPixel()));
}
void KisConfig::saveSnapConfig(const KisSnapConfig &config)
{
m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal());
m_cfg.writeEntry("globalSnapNode", config.node());
m_cfg.writeEntry("globalSnapExtension", config.extension());
m_cfg.writeEntry("globalSnapIntersection", config.intersection());
m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox());
m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds());
m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter());
m_cfg.writeEntry("globalSnapToPixel", config.toPixel());
}
qint32 KisConfig::checkSize(bool defaultValue) const
{
return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32));
}
void KisConfig::setCheckSize(qint32 checksize) const
{
m_cfg.writeEntry("checksize", checksize);
}
bool KisConfig::scrollCheckers(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false));
}
void KisConfig::setScrollingCheckers(bool sc) const
{
m_cfg.writeEntry("scrollingcheckers", sc);
}
QColor KisConfig::canvasBorderColor(bool defaultValue) const
{
QColor color(QColor(128,128,128));
return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color));
}
void KisConfig::setCanvasBorderColor(const QColor& color) const
{
m_cfg.writeEntry("canvasBorderColor", color);
}
bool KisConfig::hideScrollbars(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false));
}
void KisConfig::setHideScrollbars(bool value) const
{
m_cfg.writeEntry("hideScrollbars", value);
}
QColor KisConfig::checkersColor1(bool defaultValue) const
{
QColor col(220, 220, 220);
return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col));
}
void KisConfig::setCheckersColor1(const QColor & v) const
{
m_cfg.writeEntry("checkerscolor", v);
}
QColor KisConfig::checkersColor2(bool defaultValue) const
{
return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white)));
}
void KisConfig::setCheckersColor2(const QColor & v) const
{
m_cfg.writeEntry("checkerscolor2", v);
}
bool KisConfig::antialiasCurves(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true));
}
void KisConfig::setAntialiasCurves(bool v) const
{
m_cfg.writeEntry("antialiascurves", v);
}
bool KisConfig::antialiasSelectionOutline(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false));
}
void KisConfig::setAntialiasSelectionOutline(bool v) const
{
m_cfg.writeEntry("AntialiasSelectionOutline", v);
}
bool KisConfig::showRootLayer(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false));
}
void KisConfig::setShowRootLayer(bool showRootLayer) const
{
m_cfg.writeEntry("ShowRootLayer", showRootLayer);
}
bool KisConfig::showGlobalSelection(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false));
}
void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const
{
m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection);
}
bool KisConfig::showOutlineWhilePainting(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true));
}
void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const
{
m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting);
}
bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false));
}
void KisConfig::setForceAlwaysFullSizedOutline(bool value) const
{
m_cfg.writeEntry("forceAlwaysFullSizedOutline", value);
}
KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const
{
int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession);
return (KisConfig::SessionOnStartup)value;
}
void KisConfig::setSessionOnStartup(SessionOnStartup value)
{
m_cfg.writeEntry("sessionOnStartup", (int)value);
}
bool KisConfig::saveSessionOnQuit(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false);
}
void KisConfig::setSaveSessionOnQuit(bool value)
{
m_cfg.writeEntry("saveSessionOnQuit", value);
}
qreal KisConfig::outlineSizeMinimum(bool defaultValue) const
{
return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0));
}
void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const
{
m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum);
}
qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const
{
return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0));
}
void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const
{
m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum);
}
int KisConfig::autoSaveInterval(bool defaultValue) const
{
return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60));
}
void KisConfig::setAutoSaveInterval(int seconds) const
{
return m_cfg.writeEntry("AutoSaveInterval", seconds);
}
bool KisConfig::backupFile(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true));
}
void KisConfig::setBackupFile(bool backupFile) const
{
m_cfg.writeEntry("CreateBackupFile", backupFile);
}
bool KisConfig::showFilterGallery(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false));
}
void KisConfig::setShowFilterGallery(bool showFilterGallery) const
{
m_cfg.writeEntry("showFilterGallery", showFilterGallery);
}
bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true));
}
void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const
{
m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery);
}
QString KisConfig::canvasState(bool defaultValue) const
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString());
}
void KisConfig::setCanvasState(const QString& state) const
{
static QStringList acceptableStates;
if (acceptableStates.isEmpty()) {
acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED";
}
if (acceptableStates.contains(state)) {
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("canvasState", state);
}
}
bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false));
}
void KisConfig::setToolOptionsPopupDetached(bool detached) const
{
m_cfg.writeEntry("ToolOptionsPopupDetached", detached);
}
bool KisConfig::paintopPopupDetached(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false));
}
void KisConfig::setPaintopPopupDetached(bool detached) const
{
m_cfg.writeEntry("PaintopPopupDetached", detached);
}
QString KisConfig::pressureTabletCurve(bool defaultValue) const
{
return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;"));
}
void KisConfig::setPressureTabletCurve(const QString& curveString) const
{
m_cfg.writeEntry("tabletPressureCurve", curveString);
}
bool KisConfig::useWin8PointerInput(bool defaultValue) const
{
#ifdef Q_OS_WIN
#ifdef USE_QT_TABLET_WINDOWS
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return useWin8PointerInputNoApp(&kritarc, defaultValue);
#else
return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false));
#endif
#else
Q_UNUSED(defaultValue);
return false;
#endif
}
void KisConfig::setUseWin8PointerInput(bool value)
{
#ifdef Q_OS_WIN
// Special handling: Only set value if changed
// I don't want it to be set if the user hasn't touched it
if (useWin8PointerInput() != value) {
#ifdef USE_QT_TABLET_WINDOWS
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
setUseWin8PointerInputNoApp(&kritarc, value);
#else
m_cfg.writeEntry("useWin8PointerInput", value);
#endif
}
#else
Q_UNUSED(value)
#endif
}
bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue)
{
return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool();
}
void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value)
{
settings->setValue("useWin8PointerInput", value);
}
bool KisConfig::useRightMiddleTabletButtonWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useRightMiddleTabletButtonWorkaround", false));
}
void KisConfig::setUseRightMiddleTabletButtonWorkaround(bool value)
{
m_cfg.writeEntry("useRightMiddleTabletButtonWorkaround", value);
}
qreal KisConfig::vastScrolling(bool defaultValue) const
{
return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9));
}
void KisConfig::setVastScrolling(const qreal factor) const
{
m_cfg.writeEntry("vastScrolling", factor);
}
int KisConfig::presetChooserViewMode(bool defaultValue) const
{
return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0));
}
void KisConfig::setPresetChooserViewMode(const int mode) const
{
m_cfg.writeEntry("presetChooserViewMode", mode);
}
int KisConfig::presetIconSize(bool defaultValue) const
{
return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60));
}
void KisConfig::setPresetIconSize(const int value) const
{
m_cfg.writeEntry("presetIconSize", value);
}
bool KisConfig::firstRun(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("firstRun", true));
}
void KisConfig::setFirstRun(const bool first) const
{
m_cfg.writeEntry("firstRun", first);
}
int KisConfig::horizontalSplitLines(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1));
}
void KisConfig::setHorizontalSplitLines(const int numberLines) const
{
m_cfg.writeEntry("horizontalSplitLines", numberLines);
}
int KisConfig::verticalSplitLines(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1));
}
void KisConfig::setVerticalSplitLines(const int numberLines) const
{
m_cfg.writeEntry("verticalSplitLines", numberLines);
}
bool KisConfig::clicklessSpacePan(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true));
}
void KisConfig::setClicklessSpacePan(const bool toggle) const
{
m_cfg.writeEntry("clicklessSpacePan", toggle);
}
bool KisConfig::hideDockersFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true));
}
void KisConfig::setHideDockersFullscreen(const bool value) const
{
m_cfg.writeEntry("hideDockersFullScreen", value);
}
bool KisConfig::showDockers(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showDockers", true));
}
void KisConfig::setShowDockers(const bool value) const
{
m_cfg.writeEntry("showDockers", value);
}
bool KisConfig::showStatusBar(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true));
}
void KisConfig::setShowStatusBar(const bool value) const
{
m_cfg.writeEntry("showStatusBar", value);
}
bool KisConfig::hideMenuFullscreen(bool defaultValue) const
{
return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true));
}
void KisConfig::setHideMenuFullscreen(const bool value) const
{
m_cfg.writeEntry("hideMenuFullScreen", value);
}
bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true));
}
void KisConfig::setHideScrollbarsFullscreen(const bool value) const
{
m_cfg.writeEntry("hideScrollbarsFullScreen", value);
}
bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const
{
return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true));
}
void KisConfig::setHideStatusbarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideStatusbarFullScreen", value);
}
bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true));
}
void KisConfig::setHideTitlebarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideTitleBarFullscreen", value);
}
bool KisConfig::hideToolbarFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true));
}
void KisConfig::setHideToolbarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideToolbarFullscreen", value);
}
bool KisConfig::fullscreenMode(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true));
}
void KisConfig::setFullscreenMode(const bool value) const
{
m_cfg.writeEntry("fullscreenMode", value);
}
QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const
{
- return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList()));
+ return (defaultValue ? QStringList() :
+ m_cfg.readEntry("favoriteCompositeOps",
+ QString("normal,erase,multiply,burn,darken,add,dodge,screen,overlay,soft_light_svg,luminize,lighten,saturation,color,divide").split(',')));
}
void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const
{
m_cfg.writeEntry("favoriteCompositeOps", compositeOps);
}
QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString()));
}
KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
const QString xmlData = exportConfigurationXML(filterId, defaultValue);
cfg->fromXML(xmlData);
return cfg;
}
void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const
{
QString exportConfig = properties->toXML();
m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig);
}
QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString()));
}
void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const
{
QString importConfig = properties->toXML();
m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig);
}
bool KisConfig::useOcio(bool defaultValue) const
{
#ifdef HAVE_OCIO
return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false));
#else
Q_UNUSED(defaultValue);
return false;
#endif
}
void KisConfig::setUseOcio(bool useOCIO) const
{
m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO);
}
int KisConfig::favoritePresets(bool defaultValue) const
{
return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10));
}
void KisConfig::setFavoritePresets(const int value)
{
m_cfg.writeEntry("numFavoritePresets", value);
}
bool KisConfig::levelOfDetailEnabled(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false));
}
void KisConfig::setLevelOfDetailEnabled(bool value)
{
m_cfg.writeEntry("levelOfDetailEnabled", value);
}
KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const
{
KisOcioConfiguration cfg;
if (!defaultValue) {
cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0);
cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString());
cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString());
cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString());
cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString());
cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString());
cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString());
}
return cfg;
}
void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg)
{
m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode);
m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath);
m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath);
m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace);
m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice);
m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView);
m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look);
}
KisConfig::OcioColorManagementMode
KisConfig::ocioColorManagementMode(bool defaultValue) const
{
// FIXME: this option duplicates ocioConfiguration(), please deprecate it
return (OcioColorManagementMode)(defaultValue ? INTERNAL
: m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL));
}
void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const
{
// FIXME: this option duplicates ocioConfiguration(), please deprecate it
m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode);
}
int KisConfig::ocioLutEdgeSize(bool defaultValue) const
{
return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64));
}
void KisConfig::setOcioLutEdgeSize(int value)
{
m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value);
}
bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false));
}
void KisConfig::setOcioLockColorVisualRepresentation(bool value)
{
m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value);
}
QString KisConfig::defaultPalette(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default"));
}
void KisConfig::setDefaultPalette(const QString& name) const
{
m_cfg.writeEntry("defaultPalette", name);
}
QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const
{
QString def = "flow";
if (sliderNumber == 1) {
def = "opacity";
}
if (sliderNumber == 2) {
def = "size";
}
return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def));
}
void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider)
{
m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider);
}
int KisConfig::layerThumbnailSize(bool defaultValue) const
{
return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20));
}
void KisConfig::setLayerThumbnailSize(int size)
{
m_cfg.writeEntry("layerThumbnailSize", size);
}
bool KisConfig::sliderLabels(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true));
}
void KisConfig::setSliderLabels(bool enabled)
{
m_cfg.writeEntry("sliderLabels", enabled);
}
QString KisConfig::currentInputProfile(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString()));
}
void KisConfig::setCurrentInputProfile(const QString& name)
{
m_cfg.writeEntry("currentInputProfile", name);
}
bool KisConfig::useSystemMonitorProfile(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false));
}
void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const
{
m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile);
}
bool KisConfig::presetStripVisible(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true));
}
void KisConfig::setPresetStripVisible(bool visible)
{
m_cfg.writeEntry("presetStripVisible", visible);
}
bool KisConfig::scratchpadVisible(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true));
}
void KisConfig::setScratchpadVisible(bool visible)
{
m_cfg.writeEntry("scratchpadVisible", visible);
}
bool KisConfig::showSingleChannelAsColor(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false));
}
void KisConfig::setShowSingleChannelAsColor(bool asColor)
{
m_cfg.writeEntry("showSingleChannelAsColor", asColor);
}
bool KisConfig::hidePopups(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("hidePopups", false));
}
void KisConfig::setHidePopups(bool hidepopups)
{
m_cfg.writeEntry("hidePopups", hidepopups);
}
int KisConfig::numDefaultLayers(bool defaultValue) const
{
return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2));
}
void KisConfig::setNumDefaultLayers(int num)
{
m_cfg.writeEntry("NumberOfLayersForNewImage", num);
}
quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const
{
return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8));
}
void KisConfig::setDefaultBackgroundOpacity(quint8 value)
{
m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value);
}
QColor KisConfig::defaultBackgroundColor(bool defaultValue) const
{
return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white)));
}
void KisConfig::setDefaultBackgroundColor(QColor value)
{
m_cfg.writeEntry("BackgroundColorForNewImage", value);
}
KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const
{
return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_LAYER));
}
void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value)
{
m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value);
}
int KisConfig::lineSmoothingType(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1));
}
void KisConfig::setLineSmoothingType(int value)
{
m_cfg.writeEntry("LineSmoothingType", value);
}
qreal KisConfig::lineSmoothingDistance(bool defaultValue) const
{
return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0));
}
void KisConfig::setLineSmoothingDistance(qreal value)
{
m_cfg.writeEntry("LineSmoothingDistance", value);
}
qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const
{
return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15));
}
void KisConfig::setLineSmoothingTailAggressiveness(qreal value)
{
m_cfg.writeEntry("LineSmoothingTailAggressiveness", value);
}
bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false));
}
void KisConfig::setLineSmoothingSmoothPressure(bool value)
{
m_cfg.writeEntry("LineSmoothingSmoothPressure", value);
}
bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true));
}
void KisConfig::setLineSmoothingScalableDistance(bool value)
{
m_cfg.writeEntry("LineSmoothingScalableDistance", value);
}
qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const
{
return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0));
}
void KisConfig::setLineSmoothingDelayDistance(qreal value)
{
m_cfg.writeEntry("LineSmoothingDelayDistance", value);
}
bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true));
}
void KisConfig::setLineSmoothingUseDelayDistance(bool value)
{
m_cfg.writeEntry("LineSmoothingUseDelayDistance", value);
}
bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true));
}
void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value)
{
m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value);
}
bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true));
}
void KisConfig::setLineSmoothingStabilizeSensors(bool value)
{
m_cfg.writeEntry("LineSmoothingStabilizeSensors", value);
}
int KisConfig::tabletEventsDelay(bool defaultValue) const
{
return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10));
}
void KisConfig::setTabletEventsDelay(int value)
{
m_cfg.writeEntry("tabletEventsDelay", value);
}
bool KisConfig::trackTabletEventLatency(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false));
}
void KisConfig::setTrackTabletEventLatency(bool value)
{
m_cfg.writeEntry("trackTabletEventLatency", value);
}
bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false));
}
void KisConfig::setTestingAcceptCompressedTabletEvents(bool value)
{
m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value);
}
bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false));
}
bool KisConfig::testingCompressBrushEvents(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false));
}
void KisConfig::setTestingCompressBrushEvents(bool value)
{
m_cfg.writeEntry("testingCompressBrushEvents", value);
}
int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const
{
return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0));
}
bool KisConfig::showCanvasMessages(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true));
}
void KisConfig::setShowCanvasMessages(bool show)
{
m_cfg.writeEntry("showOnCanvasMessages", show);
}
bool KisConfig::compressKra(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false));
}
void KisConfig::setCompressKra(bool compress)
{
m_cfg.writeEntry("compressLayersInKra", compress);
}
bool KisConfig::toolOptionsInDocker(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true));
}
void KisConfig::setToolOptionsInDocker(bool inDocker)
{
m_cfg.writeEntry("ToolOptionsInDocker", inDocker);
}
bool KisConfig::kineticScrollingEnabled(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true));
}
void KisConfig::setKineticScrollingEnabled(bool value)
{
m_cfg.writeEntry("KineticScrollingEnabled", value);
}
int KisConfig::kineticScrollingGesture(bool defaultValue) const
{
return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2));
}
void KisConfig::setKineticScrollingGesture(int gesture)
{
m_cfg.writeEntry("KineticScrollingGesture", gesture);
}
int KisConfig::kineticScrollingSensitivity(bool defaultValue) const
{
return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75));
}
void KisConfig::setKineticScrollingSensitivity(int sensitivity)
{
m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity);
}
bool KisConfig::kineticScrollingHiddenScrollbars(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("KineticScrollingHideScrollbar", false));
}
void KisConfig::setKineticScrollingHideScrollbars(bool scrollbar)
{
m_cfg.writeEntry("KineticScrollingHideScrollbar", scrollbar);
}
const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const
{
const KoColorSpace *cs = 0;
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) {
KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance();
QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA");
QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8");
QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)");
if (profile == "default") {
// qDebug() << "Falling back to default color profile.";
profile = "sRGB built-in - (lcms internal)";
}
cs = csr->colorSpace(modelID, depthID, profile);
}
return cs;
}
void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs)
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
cfg.writeEntry("useCustomColorSpace", bool(cs));
if(cs) {
cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id());
cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id());
cfg.writeEntry("customColorSpaceProfile", cs->profile()->name());
}
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false));
}
void KisConfig::setEnableOpenGLFramerateLogging(bool value) const
{
m_cfg.writeEntry("enableOpenGLFramerateLogging", value);
}
bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false));
}
void KisConfig::setEnableBrushSpeedLogging(bool value) const
{
m_cfg.writeEntry("enableBrushSpeedLogging", value);
}
void KisConfig::setEnableAmdVectorizationWorkaround(bool value)
{
m_cfg.writeEntry("amdDisableVectorWorkaround", value);
}
bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false));
}
void KisConfig::setDisableAVXOptimizations(bool value)
{
m_cfg.writeEntry("disableAVXOptimizations", value);
}
bool KisConfig::disableAVXOptimizations(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("disableAVXOptimizations", false));
}
void KisConfig::setAnimationDropFrames(bool value)
{
bool oldValue = animationDropFrames();
if (value == oldValue) return;
m_cfg.writeEntry("animationDropFrames", value);
KisConfigNotifier::instance()->notifyDropFramesModeChanged();
}
bool KisConfig::animationDropFrames(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true));
}
int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30));
}
void KisConfig::setScrubbingUpdatesDelay(int value)
{
m_cfg.writeEntry("scrubbingUpdatesDelay", value);
}
int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const
{
return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1));
}
void KisConfig::setScrubbingAudioUpdatesDelay(int value)
{
m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value);
}
int KisConfig::audioOffsetTolerance(bool defaultValue) const
{
return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1));
}
void KisConfig::setAudioOffsetTolerance(int value)
{
m_cfg.writeEntry("audioOffsetTolerance", value);
}
bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false);
}
void KisConfig::setSwitchSelectionCtrlAlt(bool value)
{
m_cfg.writeEntry("switchSelectionCtrlAlt", value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false);
}
void KisConfig::setConvertToImageColorspaceOnImport(bool value)
{
m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value);
}
int KisConfig::stabilizerSampleSize(bool defaultValue) const
{
#ifdef Q_OS_WIN
const int defaultSampleSize = 50;
#else
const int defaultSampleSize = 15;
#endif
return defaultValue ?
defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize);
}
void KisConfig::setStabilizerSampleSize(int value)
{
m_cfg.writeEntry("stabilizerSampleSize", value);
}
bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const
{
const bool defaultEnabled = true;
return defaultValue ?
defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled);
}
void KisConfig::setStabilizerDelayedPaint(bool value)
{
m_cfg.writeEntry("stabilizerDelayedPaint", value);
}
bool KisConfig::showBrushHud(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("showBrushHud", false);
}
void KisConfig::setShowBrushHud(bool value)
{
m_cfg.writeEntry("showBrushHud", value);
}
QString KisConfig::brushHudSetting(bool defaultValue) const
{
QString defaultDoc = "<!DOCTYPE hud_properties>\n<hud_properties>\n <version value=\"1\" type=\"value\"/>\n <paintbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"angle\" type=\"value\"/>\n </properties_list>\n </paintbrush>\n <colorsmudge>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"smudge_mode\" type=\"value\"/>\n <item_3 value=\"smudge_length\" type=\"value\"/>\n <item_4 value=\"smudge_color_rate\" type=\"value\"/>\n </properties_list>\n </colorsmudge>\n <sketchbrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"size\" type=\"value\"/>\n </properties_list>\n </sketchbrush>\n <hairybrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </hairybrush>\n <experimentbrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"shape_windingfill\" type=\"value\"/>\n </properties_list>\n </experimentbrush>\n <spraybrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"spray_particlecount\" type=\"value\"/>\n <item_3 value=\"spray_density\" type=\"value\"/>\n </properties_list>\n </spraybrush>\n <hatchingbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"hatching_angle\" type=\"value\"/>\n <item_3 value=\"hatching_thickness\" type=\"value\"/>\n <item_4 value=\"hatching_separation\" type=\"value\"/>\n </properties_list>\n </hatchingbrush>\n <gridbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"grid_divisionlevel\" type=\"value\"/>\n </properties_list>\n </gridbrush>\n <curvebrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"curve_historysize\" type=\"value\"/>\n <item_2 value=\"curve_linewidth\" type=\"value\"/>\n <item_3 value=\"curve_lineopacity\" type=\"value\"/>\n <item_4 value=\"curve_connectionline\" type=\"value\"/>\n </properties_list>\n </curvebrush>\n <dynabrush>\n <properties_list type=\"array\">\n <item_0 value=\"dyna_diameter\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"dyna_mass\" type=\"value\"/>\n <item_3 value=\"dyna_drag\" type=\"value\"/>\n </properties_list>\n </dynabrush>\n <particlebrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"particle_particles\" type=\"value\"/>\n <item_2 value=\"particle_opecityweight\" type=\"value\"/>\n <item_3 value=\"particle_iterations\" type=\"value\"/>\n </properties_list>\n </particlebrush>\n <duplicate>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"clone_healing\" type=\"value\"/>\n <item_3 value=\"clone_movesource\" type=\"value\"/>\n </properties_list>\n </duplicate>\n <deformbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"deform_amount\" type=\"value\"/>\n <item_3 value=\"deform_mode\" type=\"value\"/>\n </properties_list>\n </deformbrush>\n <tangentnormal>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </tangentnormal>\n <filter>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </filter>\n <roundmarker>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"size\" type=\"value\"/>\n </properties_list>\n </roundmarker>\n</hud_properties>\n";
return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc);
}
void KisConfig::setBrushHudSetting(const QString &value) const
{
m_cfg.writeEntry("brushHudSettings", value);
}
bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const
{
return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true);
}
void KisConfig::setCalculateAnimationCacheInBackground(bool value)
{
m_cfg.writeEntry("calculateAnimationCacheInBackground", value);
}
QColor KisConfig::defaultAssistantsColor(bool defaultValue) const
{
static const QColor defaultColor = QColor(176, 176, 176, 255);
return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor);
}
void KisConfig::setDefaultAssistantsColor(const QColor &color) const
{
m_cfg.writeEntry("defaultAssistantsColor", color);
}
bool KisConfig::autoSmoothBezierCurves(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("autoSmoothBezierCurves", false);
}
void KisConfig::setAutoSmoothBezierCurves(bool value)
{
m_cfg.writeEntry("autoSmoothBezierCurves", value);
}
bool KisConfig::activateTransformToolAfterPaste(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("activateTransformToolAfterPaste", false);
}
void KisConfig::setActivateTransformToolAfterPaste(bool value)
{
m_cfg.writeEntry("activateTransformToolAfterPaste", value);
}
KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(bool defaultValue) const
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return rootSurfaceFormat(&kritarc, defaultValue);
}
void KisConfig::setRootSurfaceFormat(KisConfig::RootSurfaceFormat value)
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
setRootSurfaceFormat(&kritarc, value);
}
KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(QSettings *displayrc, bool defaultValue)
{
QString textValue = "bt709-g22";
if (!defaultValue) {
textValue = displayrc->value("rootSurfaceFormat", textValue).toString();
}
return textValue == "bt709-g10" ? BT709_G10 :
textValue == "bt2020-pq" ? BT2020_PQ :
BT709_G22;
}
void KisConfig::setRootSurfaceFormat(QSettings *displayrc, KisConfig::RootSurfaceFormat value)
{
const QString textValue =
value == BT709_G10 ? "bt709-g10" :
value == BT2020_PQ ? "bt2020-pq" :
"bt709-g22";
displayrc->setValue("rootSurfaceFormat", textValue);
}
bool KisConfig::useZip64(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("UseZip64", false);
}
void KisConfig::setUseZip64(bool value)
{
m_cfg.writeEntry("UseZip64", value);
}
#include <QDomDocument>
#include <QDomElement>
void KisConfig::writeKoColor(const QString& name, const KoColor& color) const
{
QDomDocument doc = QDomDocument(name);
QDomElement el = doc.createElement(name);
doc.appendChild(el);
color.toXML(doc, el);
m_cfg.writeEntry(name, doc.toString());
}
//ported from kispropertiesconfig.
KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const
{
QDomDocument doc;
if (!m_cfg.readEntry(name).isNull()) {
doc.setContent(m_cfg.readEntry(name));
QDomElement e = doc.documentElement().firstChild().toElement();
return KoColor::fromXML(e, Integer16BitsColorDepthID.id());
} else {
QString blackColor = "<!DOCTYPE Color>\n<Color>\n <RGB r=\"0\" space=\"sRGB-elle-V2-srgbtrc.icc\" b=\"0\" g=\"0\"/>\n</Color>\n";
doc.setContent(blackColor);
QDomElement e = doc.documentElement().firstChild().toElement();
return KoColor::fromXML(e, Integer16BitsColorDepthID.id());
}
return color;
}
diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc
index 0b1b4fe53f..c5c25ae838 100644
--- a/libs/ui/kis_layer_manager.cc
+++ b/libs/ui/kis_layer_manager.cc
@@ -1,969 +1,999 @@
/*
* Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer_manager.h"
#include <QRect>
#include <QApplication>
#include <QCursor>
#include <QString>
#include <QDialog>
#include <QVBoxLayout>
#include <QFileInfo>
#include <QStandardPaths>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <QMessageBox>
#include <QUrl>
#include <kis_file_name_requester.h>
#include <kis_icon.h>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KoPointerEvent.h>
#include <KoColorProfile.h>
#include <KoSelection.h>
#include <KisPart.h>
#include <KisMainWindow.h>
#include <filter/kis_filter_configuration.h>
#include <filter/kis_filter.h>
#include <kis_filter_strategy.h>
#include <generator/kis_generator_layer.h>
#include <kis_file_layer.h>
#include <kis_adjustment_layer.h>
#include <kis_mask.h>
#include <kis_clone_layer.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_paint_device.h>
#include <kis_selection.h>
#include <flake/kis_shape_layer.h>
#include <kis_undo_adapter.h>
#include <kis_painter.h>
#include <kis_meta_data_store.h>
#include <kis_meta_data_merge_strategy_registry.h>
#include <kis_psd_layer_style.h>
#include <KisMimeDatabase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "dialogs/kis_dlg_adj_layer_props.h"
#include "dialogs/kis_dlg_adjustment_layer.h"
#include "dialogs/kis_dlg_layer_properties.h"
#include "dialogs/kis_dlg_generator_layer.h"
#include "dialogs/kis_dlg_file_layer.h"
#include "dialogs/kis_dlg_layer_style.h"
+#include "dialogs/KisDlgChangeCloneSource.h"
#include "kis_filter_manager.h"
#include "kis_node_visitor.h"
#include "kis_paint_layer.h"
#include "commands/kis_image_commands.h"
#include "commands/kis_node_commands.h"
#include "kis_change_file_layer_command.h"
#include "kis_canvas_resource_provider.h"
#include "kis_selection_manager.h"
#include "kis_statusbar.h"
#include "KisViewManager.h"
#include "kis_zoom_manager.h"
#include "canvas/kis_canvas2.h"
#include "widgets/kis_meta_data_merge_strategy_chooser_widget.h"
#include "widgets/kis_wdg_generator.h"
#include "kis_progress_widget.h"
#include "kis_node_commands_adapter.h"
#include "kis_node_manager.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_abstract_projection_plane.h"
#include "commands_new/kis_set_layer_style_command.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_selection_mask.h"
#include "kis_layer_utils.h"
#include "lazybrush/kis_colorize_mask.h"
#include "kis_processing_applicator.h"
#include "KisSaveGroupVisitor.h"
KisLayerManager::KisLayerManager(KisViewManager * view)
: m_view(view)
, m_imageView(0)
, m_imageFlatten(0)
, m_imageMergeLayer(0)
, m_groupLayersSave(0)
, m_imageResizeToLayer(0)
, m_flattenLayer(0)
, m_rasterizeLayer(0)
, m_commandsAdapter(new KisNodeCommandsAdapter(m_view))
, m_layerStyle(0)
{
}
KisLayerManager::~KisLayerManager()
{
delete m_commandsAdapter;
}
void KisLayerManager::setView(QPointer<KisView>view)
{
m_imageView = view;
}
KisLayerSP KisLayerManager::activeLayer()
{
if (m_imageView) {
return m_imageView->currentLayer();
}
return 0;
}
KisPaintDeviceSP KisLayerManager::activeDevice()
{
if (activeLayer()) {
return activeLayer()->paintDevice();
}
return 0;
}
void KisLayerManager::activateLayer(KisLayerSP layer)
{
if (m_imageView) {
emit sigLayerActivated(layer);
layersUpdated();
if (layer) {
m_view->canvasResourceProvider()->slotNodeActivated(layer.data());
}
}
}
void KisLayerManager::setup(KisActionManager* actionManager)
{
m_imageFlatten = actionManager->createAction("flatten_image");
connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage()));
m_imageMergeLayer = actionManager->createAction("merge_layer");
connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer()));
m_flattenLayer = actionManager->createAction("flatten_layer");
connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer()));
m_rasterizeLayer = actionManager->createAction("rasterize_layer");
connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer()));
m_groupLayersSave = actionManager->createAction("save_groups_as_images");
connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers()));
m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated");
connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated()));
m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer");
connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer()));
KisAction *action = actionManager->createAction("trim_to_image");
connect(action, SIGNAL(triggered()), this, SLOT(trimToImage()));
m_layerStyle = actionManager->createAction("layer_style");
connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle()));
}
void KisLayerManager::updateGUI()
{
KisImageSP image = m_view->image();
KisLayerSP layer = activeLayer();
const bool isGroupLayer = layer && layer->inherits("KisGroupLayer");
m_imageMergeLayer->setText(
isGroupLayer ?
i18nc("@action:inmenu", "Merge Group") :
i18nc("@action:inmenu", "Merge with Layer Below"));
m_flattenLayer->setVisible(!isGroupLayer);
if (m_view->statusBar())
m_view->statusBar()->setProfile(image);
}
void KisLayerManager::imageResizeToActiveLayer()
{
KisLayerSP layer;
KisImageWSP image = m_view->image();
if (image && (layer = activeLayer())) {
QRect cropRect = layer->projection()->nonDefaultPixelArea();
if (!cropRect.isEmpty()) {
image->cropImage(cropRect);
} else {
m_view->showFloatingMessage(
i18nc("floating message in layer manager",
"Layer is empty "),
QIcon(), 2000, KisFloatingMessage::Low);
}
}
}
void KisLayerManager::trimToImage()
{
KisImageWSP image = m_view->image();
if (image) {
image->cropImage(image->bounds());
}
}
void KisLayerManager::layerProperties()
{
if (!m_view) return;
if (!m_view->document()) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
const bool multipleLayersSelected = selectedNodes.size() > 1;
KisAdjustmentLayerSP adjustmentLayer = KisAdjustmentLayerSP(dynamic_cast<KisAdjustmentLayer*>(layer.data()));
KisGeneratorLayerSP groupLayer = KisGeneratorLayerSP(dynamic_cast<KisGeneratorLayer*>(layer.data()));
KisFileLayerSP filterLayer = KisFileLayerSP(dynamic_cast<KisFileLayer*>(layer.data()));
if (adjustmentLayer && !multipleLayersSelected) {
KisPaintDeviceSP dev = adjustmentLayer->projection();
KisDlgAdjLayerProps dlg(adjustmentLayer, adjustmentLayer.data(), dev, m_view, adjustmentLayer->filter().data(), adjustmentLayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops");
dlg.resize(dlg.minimumSizeHint());
KisFilterConfigurationSP configBefore(adjustmentLayer->filter());
KIS_ASSERT_RECOVER_RETURN(configBefore);
QString xmlBefore = configBefore->toXML();
if (dlg.exec() == QDialog::Accepted) {
adjustmentLayer->setName(dlg.layerName());
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
KisChangeFilterCmd *cmd
= new KisChangeFilterCmd(adjustmentLayer,
configBefore->name(),
xmlBefore,
configAfter->name(),
xmlAfter,
false);
// FIXME: check whether is needed
cmd->redo();
m_view->undoAdapter()->addCommand(cmd);
m_view->document()->setModified(true);
}
}
else {
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
adjustmentLayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data()));
adjustmentLayer->setDirty();
}
}
}
else if (groupLayer && !multipleLayersSelected) {
KisFilterConfigurationSP configBefore(groupLayer->filter());
Q_ASSERT(configBefore);
QString xmlBefore = configBefore->toXML();
KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(groupLayer->name(), m_view, m_view->mainWindow(), groupLayer, configBefore);
dlg->setCaption(i18n("Fill Layer Properties"));
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setConfiguration(configBefore.data());
dlg->resize(dlg->minimumSizeHint());
Qt::WindowFlags flags = dlg->windowFlags();
- dlg->setWindowFlags(flags | Qt::WindowStaysOnTopHint | Qt::Dialog);
+ dlg->setWindowFlags(flags | Qt::Tool | Qt::Dialog);
dlg->show();
}
else if (filterLayer && !multipleLayersSelected){
QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath();
QString fileNameOld = filterLayer->fileName();
KisFileLayer::ScalingMethod scalingMethodOld = filterLayer->scalingMethod();
KisDlgFileLayer dlg(basePath, filterLayer->name(), m_view->mainWindow());
dlg.setCaption(i18n("File Layer Properties"));
dlg.setFileName(fileNameOld);
dlg.setScalingMethod(scalingMethodOld);
if (dlg.exec() == QDialog::Accepted) {
const QString fileNameNew = dlg.fileName();
KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution();
if(fileNameNew.isEmpty()){
QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified"));
return;
}
filterLayer->setName(dlg.layerName());
if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) {
KisChangeFileLayerCmd *cmd
= new KisChangeFileLayerCmd(filterLayer,
basePath,
fileNameOld,
scalingMethodOld,
basePath,
fileNameNew,
scalingMethodNew);
m_view->undoAdapter()->addCommand(cmd);
}
}
} else { // If layer == normal painting layer, vector layer, or group layer
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view);
dialog->resize(dialog->minimumSizeHint());
dialog->setAttribute(Qt::WA_DeleteOnClose);
Qt::WindowFlags flags = dialog->windowFlags();
- dialog->setWindowFlags(flags | Qt::WindowStaysOnTopHint | Qt::Dialog);
+ dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog);
dialog->show();
}
}
+void KisLayerManager::changeCloneSource()
+{
+ QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
+ if (selectedNodes.isEmpty()) {
+ return;
+ }
+
+ QList<KisCloneLayerSP> cloneLayers;
+ KisNodeSP node;
+ Q_FOREACH (node, selectedNodes) {
+ KisCloneLayerSP cloneLayer(qobject_cast<KisCloneLayer *>(node.data()));
+ if (cloneLayer) {
+ cloneLayers << cloneLayer;
+ }
+ }
+
+ if (cloneLayers.isEmpty()) {
+ return;
+ }
+
+ KisDlgChangeCloneSource *dialog = new KisDlgChangeCloneSource(cloneLayers, m_view);
+ dialog->setCaption(i18n("Change Clone Layer"));
+ dialog->resize(dialog->minimumSizeHint());
+ dialog->setAttribute(Qt::WA_DeleteOnClose);
+ Qt::WindowFlags flags = dialog->windowFlags();
+ dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog);
+ dialog->show();
+}
+
void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source)
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayer *srcLayer = qobject_cast<KisLayer*>(source.data());
if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) {
image->flattenLayer(srcLayer);
return;
}
KisPaintDeviceSP srcDevice =
source->paintDevice() ? source->projection() : source->original();
bool putBehind = false;
QString newCompositeOp = source->compositeOpId();
KisColorizeMask *colorizeMask = dynamic_cast<KisColorizeMask*>(source.data());
if (colorizeMask) {
srcDevice = colorizeMask->coloringProjection();
putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND;
if (putBehind) {
newCompositeOp = COMPOSITE_OVER;
}
}
if (!srcDevice) return;
KisPaintDeviceSP clone;
if (*srcDevice->colorSpace() !=
*srcDevice->compositionSourceColorSpace()) {
clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace());
QRect rc(srcDevice->extent());
KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc);
} else {
clone = new KisPaintDevice(*srcDevice);
}
KisLayerSP layer = new KisPaintLayer(image,
source->name(),
source->opacity(),
clone);
layer->setCompositeOpId(newCompositeOp);
KisNodeSP parent = source->parent();
KisNodeSP above = source->prevSibling();
while (parent && !parent->allowAsChild(layer)) {
above = above ? above->parent() : source->parent();
parent = above ? above->parent() : 0;
}
if (putBehind && above == source->parent()) {
above = above->prevSibling();
}
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer"));
m_commandsAdapter->removeNode(source);
m_commandsAdapter->addNode(layer, parent, above);
m_commandsAdapter->endMacro();
}
void KisLayerManager::convertGroupToAnimated()
{
KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(activeLayer().data());
if (group.isNull()) return;
KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8);
animatedLayer->enableAnimation();
KisRasterKeyframeChannel *contentChannel = dynamic_cast<KisRasterKeyframeChannel*>(
animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true));
KIS_ASSERT_RECOVER_RETURN(contentChannel);
KisNodeSP child = group->firstChild();
int time = 0;
while (child) {
contentChannel->importFrame(time, child->projection(), NULL);
time++;
child = child->nextSibling();
}
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer"));
m_commandsAdapter->addNode(animatedLayer, group->parent(), group);
m_commandsAdapter->removeNode(group);
m_commandsAdapter->endMacro();
}
void KisLayerManager::convertLayerToFileLayer(KisNodeSP source)
{
KisImageSP image = m_view->image();
if (!image) return;
QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
KoDialog dlg;
QWidget *page = new QWidget(&dlg);
dlg.setMainWidget(page);
QBoxLayout *layout = new QVBoxLayout(page);
dlg.setWindowTitle(i18n("Save layers to..."));
QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location."));
lbl->setWordWrap(true);
layout->addWidget(lbl);
KisFileNameRequester *urlRequester = new KisFileNameRequester(page);
urlRequester->setMode(KoFileDialog::SaveFile);
urlRequester->setMimeTypeFilters(listMimeFilter);
urlRequester->setFileName(m_view->document()->url().toLocalFile());
if (m_view->document()->url().isLocalFile()) {
QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).baseName();
location.setFile(location.dir(), location.baseName() + "_" + source->name() + ".png");
urlRequester->setFileName(location.absoluteFilePath());
}
else {
const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png");
urlRequester->setFileName(proposedFileName);
}
// We don't want .kra files as file layers, Krita cannot handle the load.
QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
int i = mimes.indexOf(KIS_MIME_TYPE);
if (i >= 0 && i < mimes.size()) {
mimes.removeAt(i);
}
urlRequester->setMimeTypeFilters(mimes);
layout->addWidget(urlRequester);
if (!dlg.exec()) return;
QString path = urlRequester->fileName();
if (path.isEmpty()) return;
QFileInfo f(path);
QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName());
if (mimeType.isEmpty()) {
mimeType = "image/png";
}
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
QRect bounds = source->exactBounds();
KisImageSP dst = new KisImage(doc->createUndoStore(),
image->width(),
image->height(),
image->projection()->compositionSourceColorSpace(),
source->name());
dst->setResolution(image->xRes(), image->yRes());
doc->setFileBatchMode(false);
doc->setCurrentImage(dst);
KisNodeSP node = source->clone();
dst->addNode(node);
dst->initialRefreshGraph();
dst->cropImage(bounds);
dst->waitForDone();
bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1());
if (!r) {
qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << doc->errorMessage();
} else {
QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath();
QString relativePath = QDir(basePath).relativeFilePath(path);
KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8);
fileLayer->setX(bounds.x());
fileLayer->setY(bounds.y());
KisNodeSP dstParent = source->parent();
KisNodeSP dstAboveThis = source->prevSibling();
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer"));
m_commandsAdapter->removeNode(source);
m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis);
m_commandsAdapter->endMacro();
}
doc->closeUrl(false);
}
void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above)
{
Q_ASSERT(activeNode);
parent = activeNode;
above = parent->lastChild();
if (parent->inherits("KisGroupLayer") && parent->collapsed()) {
above = parent;
parent = parent->parent();
return;
}
while (parent &&
(!parent->allowAsChild(node) || parent->userLocked())) {
above = parent;
parent = parent->parent();
}
if (!parent) {
warnKrita << "KisLayerManager::adjustLayerPosition:"
<< "No node accepted newly created node";
parent = m_view->image()->root();
above = parent->lastChild();
}
}
void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage, KisProcessingApplicator *applicator)
{
KisNodeSP parent;
KisNodeSP above;
adjustLayerPosition(layer, activeNode, parent, above);
KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(parent.data());
const bool parentForceUpdate = group && !group->projectionIsValid();
updateImage |= parentForceUpdate;
m_commandsAdapter->addNodeAsync(layer, parent, above, updateImage, updateImage, applicator);
}
KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
addLayerCommon(activeNode, layer, false, 0);
return layer;
}
KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, group, false, 0);
return group;
}
KisNodeSP KisLayerManager::addCloneLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisNodeSP node = new KisCloneLayer(activeLayer(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, node, true, 0);
return node;
}
KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode)
{
if (!m_view) return 0;
if (!m_view->document()) return 0;
KisImageWSP image = m_view->image();
KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, layer, false, 0);
return layer;
}
KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisSelectionSP selection = m_view->selection();
KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Add Layer"));
KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection, &applicator);
KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original());
- KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view);
+ KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view, qApp->activeWindow());
dlg.resize(dlg.minimumSizeHint());
// ensure that the device may be free'd by the dialog
// when it is not needed anymore
previewDevice = 0;
if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) {
// XXX: add messagebox warning if there's no filter set!
applicator.cancel();
} else {
adjl->setName(dlg.layerName());
applicator.end();
}
return adjl;
}
KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name,
KisFilterConfigurationSP filter,
KisSelectionSP selection,
KisProcessingApplicator *applicator)
{
KisImageWSP image = m_view->image();
KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection);
addLayerCommon(activeNode, layer, true, applicator);
return layer;
}
KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
QColor currentForeground = m_view->canvasResourceProvider()->fgColor().toQColor();
KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0);
KisFilterConfigurationSP defaultConfig = dlg.configuration();
defaultConfig->setProperty("color", currentForeground);
dlg.setConfiguration(defaultConfig);
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
KisSelectionSP selection = m_view->selection();
KisFilterConfigurationSP generator = dlg.configuration();
QString name = dlg.layerName();
KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection);
addLayerCommon(activeNode, node, true, 0);
return node;
}
return 0;
}
void KisLayerManager::flattenImage()
{
KisImageSP image = m_view->image();
if (!m_view->blockUntilOperationsFinished(image)) return;
if (image) {
bool doIt = true;
if (image->nHiddenLayers() > 0) {
int answer = QMessageBox::warning(m_view->mainWindow(),
i18nc("@title:window", "Flatten Image"),
i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (answer != QMessageBox::Yes) {
doIt = false;
}
}
if (doIt) {
image->flatten(m_view->activeNode());
}
}
}
inline bool isSelectionMask(KisNodeSP node) {
return dynamic_cast<KisSelectionMask*>(node.data());
}
bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image)
{
bool result = false;
KisNodeSP prevNode = currentNode->prevSibling();
if (isSelectionMask(currentNode) &&
prevNode && isSelectionMask(prevNode)) {
QList<KisNodeSP> mergedNodes;
mergedNodes.append(currentNode);
mergedNodes.append(prevNode);
image->mergeMultipleLayers(mergedNodes, currentNode);
result = true;
}
return result;
}
bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image)
{
bool result = false;
if (currentNode->inherits("KisGroupLayer")) {
KisGroupLayer *layer = qobject_cast<KisGroupLayer*>(currentNode.data());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false);
image->flattenLayer(layer);
result = true;
}
return result;
}
void KisLayerManager::mergeLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
if (selectedNodes.size() > 1) {
image->mergeMultipleLayers(selectedNodes, m_view->activeNode());
}
else if (tryMergeSelectionMasks(m_view->activeNode(), image)) {
// already done!
} else if (tryFlattenGroupLayer(m_view->activeNode(), image)) {
// already done!
} else {
if (!layer->prevSibling()) return;
KisLayer *prevLayer = qobject_cast<KisLayer*>(layer->prevSibling().data());
if (!prevLayer) return;
if (prevLayer->userLocked()) {
m_view->showFloatingMessage(
i18nc("floating message in layer manager",
"Layer is locked "),
QIcon(), 2000, KisFloatingMessage::Low);
}
else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) {
image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
}
else {
const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow());
if (!strategy) return;
image->mergeDown(layer, strategy);
}
}
m_view->updateGUI();
}
void KisLayerManager::flattenLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
convertNodeToPaintLayer(layer);
m_view->updateGUI();
}
void KisLayerManager::rasterizeLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity());
KisPainter gc(paintLayer->paintDevice());
QRect rc = layer->projection()->exactBounds();
gc.bitBlt(rc.topLeft(), layer->projection(), rc);
m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer"));
m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data());
int childCount = layer->childCount();
for (int i = 0; i < childCount; i++) {
m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild());
}
m_commandsAdapter->removeNode(layer);
m_commandsAdapter->endMacro();
updateGUI();
}
void KisLayerManager::layersUpdated()
{
KisLayerSP layer = activeLayer();
if (!layer) return;
m_view->updateGUI();
}
void KisLayerManager::saveGroupLayers()
{
QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
KoDialog dlg;
QWidget *page = new QWidget(&dlg);
dlg.setMainWidget(page);
QBoxLayout *layout = new QVBoxLayout(page);
KisFileNameRequester *urlRequester = new KisFileNameRequester(page);
urlRequester->setMode(KoFileDialog::SaveFile);
if (m_view->document()->url().isLocalFile()) {
urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath());
}
urlRequester->setMimeTypeFilters(listMimeFilter);
urlRequester->setFileName(m_view->document()->url().toLocalFile());
layout->addWidget(urlRequester);
QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page);
chkInvisible->setChecked(false);
layout->addWidget(chkInvisible);
QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page);
chkDepth->setChecked(true);
layout->addWidget(chkDepth);
if (!dlg.exec()) return;
QString path = urlRequester->fileName();
if (path.isEmpty()) return;
QFileInfo f(path);
QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName(), false);
if (mimeType.isEmpty()) {
mimeType = "image/png";
}
QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first();
QString basename = f.baseName();
KisImageSP image = m_view->image();
if (!image) return;
KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType);
image->rootLayer()->accept(v);
}
bool KisLayerManager::activeLayerHasSelection()
{
return (activeLayer()->selection() != 0);
}
KisNodeSP KisLayerManager::addFileLayer(KisNodeSP activeNode)
{
QString basePath;
QUrl url = m_view->document()->url();
if (url.isLocalFile()) {
basePath = QFileInfo(url.toLocalFile()).absolutePath();
}
KisImageWSP image = m_view->image();
KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow());
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
QString name = dlg.layerName();
QString fileName = dlg.fileName();
if(fileName.isEmpty()){
QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified"));
return 0;
}
KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution();
KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, node, true, 0);
return node;
}
return 0;
}
void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg)
{
KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone());
}
void KisLayerManager::layerStyle()
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
KisPSDLayerStyleSP oldStyle;
if (layer->layerStyle()) {
oldStyle = layer->layerStyle()->clone();
}
else {
oldStyle = toQShared(new KisPSDLayerStyle());
}
KisDlgLayerStyle dlg(oldStyle->clone(), m_view->canvasResourceProvider());
std::function<void ()> updateCall(std::bind(updateLayerStyles, layer, &dlg));
SignalToFunctionProxy proxy(updateCall);
connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start()));
if (dlg.exec() == QDialog::Accepted) {
KisPSDLayerStyleSP newStyle = dlg.style();
KUndo2CommandSP command = toQShared(
new KisSetLayerStyleCommand(layer, oldStyle, newStyle));
image->postExecutionUndoAdapter()->addCommand(command);
}
}
diff --git a/libs/ui/kis_layer_manager.h b/libs/ui/kis_layer_manager.h
index cbf813fc88..6bc545405a 100644
--- a/libs/ui/kis_layer_manager.h
+++ b/libs/ui/kis_layer_manager.h
@@ -1,134 +1,136 @@
/*
* Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_LAYER_MANAGER
#define KIS_LAYER_MANAGER
#include <QObject>
#include <QPointer>
#include <QList>
#include "kis_adjustment_layer.h"
#include "kis_types.h"
#include "KisView.h"
#include <filter/kis_filter_configuration.h>
class KisViewManager;
class KisNodeCommandsAdapter;
class KisAction;
class KisActionManager;
class KisProcessingApplicator;
/**
* KisLayerManager takes care of the gui around working with layers:
* adding, removing, editing. It also keeps track of the active layer
* for this view.
*/
class KisLayerManager : public QObject
{
Q_OBJECT
public:
KisLayerManager(KisViewManager * view);
~KisLayerManager() override;
void setView(QPointer<KisView>view);
Q_SIGNALS:
void sigLayerActivated(KisLayerSP layer);
private:
friend class KisNodeManager;
/**
* Activate the specified layer. The layer may be 0.
*/
void activateLayer(KisLayerSP layer);
KisLayerSP activeLayer();
KisPaintDeviceSP activeDevice();
void setup(KisActionManager *actionManager);
void updateGUI();
private Q_SLOTS:
void mergeLayer();
void imageResizeToActiveLayer();
void trimToImage();
void layerProperties();
void flattenImage();
void flattenLayer();
void rasterizeLayer();
void layersUpdated();
void saveGroupLayers();
bool activeLayerHasSelection();
void convertNodeToPaintLayer(KisNodeSP source);
void convertGroupToAnimated();
void convertLayerToFileLayer(KisNodeSP source);
KisLayerSP addPaintLayer(KisNodeSP activeNode);
KisNodeSP addGroupLayer(KisNodeSP activeNode);
KisNodeSP addCloneLayer(KisNodeSP activeNode);
KisNodeSP addShapeLayer(KisNodeSP activeNode);
KisNodeSP addAdjustmentLayer(KisNodeSP activeNode);
KisAdjustmentLayerSP addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator);
KisNodeSP addGeneratorLayer(KisNodeSP activeNode);
KisNodeSP addFileLayer(KisNodeSP activeNode);
void layerStyle();
+ void changeCloneSource();
+
private:
void adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above);
void addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage = true, KisProcessingApplicator *applicator = 0);
private:
KisViewManager * m_view;
QPointer<KisView>m_imageView;
KisAction *m_imageFlatten;
KisAction *m_imageMergeLayer;
KisAction *m_groupLayersSave;
KisAction *m_convertGroupAnimated;
KisAction *m_imageResizeToLayer;
KisAction *m_flattenLayer;
KisAction *m_rasterizeLayer;
KisNodeCommandsAdapter* m_commandsAdapter;
KisAction *m_layerStyle;
};
#endif
diff --git a/libs/ui/kis_mask_manager.cc b/libs/ui/kis_mask_manager.cc
index 762383e79d..ff32ca0636 100644
--- a/libs/ui/kis_mask_manager.cc
+++ b/libs/ui/kis_mask_manager.cc
@@ -1,365 +1,366 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2006
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_mask_manager.h"
#include <kactioncollection.h>
#include <KoProperties.h>
#include <kis_transaction.h>
#include <filter/kis_filter_configuration.h>
#include <commands/kis_node_commands.h>
#include <kis_undo_adapter.h>
#include <kis_paint_layer.h>
#include "KisDocument.h"
#include "KisViewManager.h"
#include <kis_layer.h>
#include <kis_clone_layer.h>
#include <kis_group_layer.h>
#include <kis_filter_mask.h>
#include <lazybrush/kis_colorize_mask.h>
#include <kis_transform_mask.h>
#include <kis_transparency_mask.h>
#include <kis_selection_mask.h>
#include <kis_effect_mask.h>
#include "dialogs/kis_dlg_adjustment_layer.h"
#include "widgets/kis_mask_widgets.h"
#include <kis_selection.h>
#include <kis_selection_manager.h>
#include <kis_pixel_selection.h>
#include "dialogs/kis_dlg_adj_layer_props.h"
#include <kis_image.h>
#include <kis_transform_worker.h>
#include <KoColorSpace.h>
#include <KoColor.h>
#include "kis_node_commands_adapter.h"
#include "commands/kis_deselect_global_selection_command.h"
#include "kis_iterator_ng.h"
KisMaskManager::KisMaskManager(KisViewManager * view)
: m_view(view)
, m_imageView(0)
, m_commandsAdapter(new KisNodeCommandsAdapter(m_view))
{
}
void KisMaskManager::setView(QPointer<KisView>imageView)
{
m_imageView = imageView;
}
void KisMaskManager::setup(KActionCollection *actionCollection, KisActionManager *actionManager)
{
Q_UNUSED(actionCollection);
Q_UNUSED(actionManager);
}
void KisMaskManager::updateGUI()
{
// XXX: enable/disable menu items according to whether there's a mask selected currently
// XXX: disable the selection mask item if there's already a selection mask
// YYY: doesn't KisAction do that already?
}
KisMaskSP KisMaskManager::activeMask()
{
if (m_imageView) {
return m_imageView->currentMask();
}
return 0;
}
KisPaintDeviceSP KisMaskManager::activeDevice()
{
KisMaskSP mask = activeMask();
return mask ? mask->paintDevice() : 0;
}
void KisMaskManager::activateMask(KisMaskSP mask)
{
Q_UNUSED(mask);
}
void KisMaskManager::masksUpdated()
{
m_view->updateGUI();
}
void KisMaskManager::adjustMaskPosition(KisNodeSP node, KisNodeSP activeNode, bool avoidActiveNode, KisNodeSP &parent, KisNodeSP &above)
{
Q_ASSERT(node);
Q_ASSERT(activeNode);
if (!avoidActiveNode && activeNode->allowAsChild(node)) {
parent = activeNode;
above = activeNode->lastChild();
- } else if (activeNode->parent() && activeNode->parent()->allowAsChild(node)) {
+ } else if (activeNode->parent() && activeNode->parent()->allowAsChild(node)
+ && activeNode->parent()->parent() /* we don't want to add masks to root */) {
parent = activeNode->parent();
above = activeNode;
} else {
KisNodeSP t = activeNode;
while ((t = t->nextSibling())) {
if (t->allowAsChild(node)) {
parent = t;
above = t->lastChild();
break;
}
}
if (!t) {
t = activeNode;
while ((t = t->prevSibling())) {
if (t->allowAsChild(node)) {
parent = t;
above = t->lastChild();
break;
}
}
}
if (!t && activeNode->parent()) {
adjustMaskPosition(node, activeNode->parent(), true, parent, above);
} else if (!t) {
KisImageWSP image = m_view->image();
KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
m_commandsAdapter->addNode(layer, activeNode, 0);
parent = layer;
above = 0;
}
}
}
void KisMaskManager::createMaskCommon(KisMaskSP mask,
KisNodeSP activeNode,
KisPaintDeviceSP copyFrom,
const KUndo2MagicString& macroName,
const QString &nodeType,
const QString &nodeName,
bool suppressSelection,
bool avoidActiveNode,
bool updateImage)
{
m_commandsAdapter->beginMacro(macroName);
KisNodeSP parent;
KisNodeSP above;
adjustMaskPosition(mask, activeNode, avoidActiveNode, parent, above);
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent.data());
Q_ASSERT(parentLayer);
bool shouldDeselectGlobalSelection = false;
if (!suppressSelection) {
if (copyFrom) {
mask->initSelection(copyFrom, parentLayer);
} else {
mask->initSelection(m_view->selection(), parentLayer);
shouldDeselectGlobalSelection = m_view->selection();
}
}
//counting number of KisSelectionMask
QList<KisNodeSP> masks = parentLayer->childNodes(QStringList(nodeType),KoProperties());
int number = masks.count() + 1;
mask->setName(nodeName + QString(" ") + QString::number(number));
m_commandsAdapter->addNode(mask, parentLayer, above, updateImage, updateImage);
if (shouldDeselectGlobalSelection) {
m_commandsAdapter->addExtraCommand(new KisDeselectGlobalSelectionCommand(m_imageView->image()));
}
m_commandsAdapter->endMacro();
masksUpdated();
}
KisNodeSP KisMaskManager::createSelectionMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool convertActiveNode)
{
if (!activeNode->isEditable()) {
return 0;
}
KisSelectionMaskSP mask = new KisSelectionMask(m_view->image());
createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Selection Mask"), "KisSelectionMask", i18n("Selection"), false, convertActiveNode, false);
mask->setActive(true);
if (convertActiveNode) {
m_commandsAdapter->removeNode(activeNode);
}
return mask;
}
KisNodeSP KisMaskManager::createTransparencyMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool convertActiveNode)
{
if (!activeNode->isEditable()) {
return 0;
}
KisMaskSP mask = new KisTransparencyMask();
createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Transparency Mask"), "KisTransparencyMask", i18n("Transparency Mask"), false, convertActiveNode);
if (convertActiveNode) {
m_commandsAdapter->removeNode(activeNode);
}
return mask;
}
KisNodeSP KisMaskManager::createFilterMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool quiet, bool convertActiveNode)
{
if (!activeNode->isEditable()) {
return 0;
}
KisFilterMaskSP mask = new KisFilterMask();
createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Filter Mask"), "KisFilterMask", i18n("Filter Mask"), false, convertActiveNode);
if (convertActiveNode) {
m_commandsAdapter->removeNode(activeNode);
}
/**
* FIXME: We'll use layer's original for creation of a thumbnail.
* Actually, we can't use it's projection as newly created mask
* may be going to be inserted in the middle of the masks stack
*/
KisPaintDeviceSP originalDevice = mask->parent()->original();
KisDlgAdjustmentLayer dialog(mask, mask.data(), originalDevice,
mask->name(), i18n("New Filter Mask"),
- m_view);
+ m_view, qApp->activeWindow());
// If we are supposed to not disturb the user, don't start asking them about things.
if(quiet) {
KisFilterConfigurationSP filter = KisFilterRegistry::instance()->values().first()->defaultConfiguration();
if (filter) {
mask->setFilter(filter);
mask->setName(mask->name());
}
return mask;
}
if (dialog.exec() == QDialog::Accepted) {
KisFilterConfigurationSP filter = dialog.filterConfiguration();
if (filter) {
QString name = dialog.layerName();
mask->setFilter(filter);
mask->setName(name);
}
return mask;
} else {
m_commandsAdapter->undoLastCommand();
}
return 0;
}
KisNodeSP KisMaskManager::createColorizeMask(KisNodeSP activeNode)
{
if (!activeNode->isEditable()) {
return 0;
}
KisColorizeMaskSP mask = new KisColorizeMask();
createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Colorize Mask"), "KisColorizeMask", i18n("Colorize Mask"), true, false);
mask->setImage(m_view->image());
mask->initializeCompositeOp();
delete mask->setColorSpace(mask->parent()->colorSpace());
return mask;
}
KisNodeSP KisMaskManager::createTransformMask(KisNodeSP activeNode)
{
if (!activeNode->isEditable()) {
return 0;
}
KisTransformMaskSP mask = new KisTransformMask();
createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Transform Mask"), "KisTransformMask", i18n("Transform Mask"), true, false);
return mask;
}
void KisMaskManager::maskProperties()
{
if (!activeMask()) return;
if (activeMask()->inherits("KisFilterMask")) {
KisFilterMask *mask = static_cast<KisFilterMask*>(activeMask().data());
KisLayerSP layer = qobject_cast<KisLayer*>(mask->parent().data());
if (! layer)
return;
KisPaintDeviceSP dev = layer->original();
if (!dev) {
return;
}
KisDlgAdjLayerProps dlg(layer, mask, dev, m_view, mask->filter().data(), mask->name(), i18n("Filter Mask Properties"), m_view->mainWindow(), "dlgeffectmaskprops");
KisFilterConfigurationSP configBefore(mask->filter());
Q_ASSERT(configBefore);
QString xmlBefore = configBefore->toXML();
if (dlg.exec() == QDialog::Accepted) {
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
mask->setName(dlg.layerName());
if(xmlBefore != xmlAfter) {
KisChangeFilterCmd *cmd
= new KisChangeFilterCmd(mask,
configBefore->name(),
xmlBefore,
configAfter->name(),
xmlAfter,
false);
// FIXME: check whether is needed
cmd->redo();
m_view->undoAdapter()->addCommand(cmd);
m_view->document()->setModified(true);
}
}
else {
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
mask->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data()));
mask->setDirty();
}
}
} else {
// Not much to show for transparency or selection masks?
}
}
diff --git a/libs/ui/kis_mimedata.cpp b/libs/ui/kis_mimedata.cpp
index 00db4f09b4..e30f77c7bf 100644
--- a/libs/ui/kis_mimedata.cpp
+++ b/libs/ui/kis_mimedata.cpp
@@ -1,470 +1,471 @@
/*
* Copyright (c) 2011 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_mimedata.h"
#include "kis_config.h"
#include "kis_node.h"
#include "kis_paint_device.h"
#include "kis_shared_ptr.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_shape_layer.h"
#include "kis_paint_layer.h"
#include "KisDocument.h"
#include "kis_shape_controller.h"
#include "KisPart.h"
#include "kis_layer_utils.h"
#include "kis_node_insertion_adapter.h"
#include "kis_dummies_facade_base.h"
#include "kis_node_dummies_graph.h"
#include "KisImportExportManager.h"
#include "KisImageBarrierLockerWithFeedback.h"
#include <KoProperties.h>
#include <KoStore.h>
#include <KoColorProfile.h>
#include <KoColorSpaceRegistry.h>
#include <QApplication>
#include <QImage>
#include <QByteArray>
#include <QBuffer>
#include <QDomDocument>
#include <QDomElement>
#include <QDesktopWidget>
#include <QDir>
KisMimeData::KisMimeData(QList<KisNodeSP> nodes, KisImageSP image, bool forceCopy)
: QMimeData()
, m_nodes(nodes)
, m_forceCopy(forceCopy)
, m_image(image)
{
Q_ASSERT(m_nodes.size() > 0);
}
void KisMimeData::deepCopyNodes()
{
KisNodeList newNodes;
{
KisImageBarrierLockerWithFeedbackAllowNull locker(m_image);
Q_FOREACH (KisNodeSP node, m_nodes) {
newNodes << node->clone();
}
}
m_nodes = newNodes;
m_image = 0;
}
QList<KisNodeSP> KisMimeData::nodes() const
{
return m_nodes;
}
QStringList KisMimeData::formats () const
{
QStringList f = QMimeData::formats();
if (m_nodes.size() > 0) {
f << "application/x-krita-node"
<< "application/x-krita-node-url"
<< "application/x-qt-image"
<< "application/zip"
<< "application/x-krita-node-internal-pointer";
}
return f;
}
KisDocument *createDocument(QList<KisNodeSP> nodes, KisImageSP srcImage)
{
KisDocument *doc = KisPart::instance()->createDocument();
QRect rc;
Q_FOREACH (KisNodeSP node, nodes) {
rc |= node->exactBounds();
}
KisImageSP image = new KisImage(0, rc.width(), rc.height(), nodes.first()->colorSpace(), nodes.first()->name());
+ image->setAllowMasksOnRootNode(true);
{
KisImageBarrierLockerWithFeedbackAllowNull locker(srcImage);
Q_FOREACH (KisNodeSP node, nodes) {
image->addNode(node->clone());
}
}
doc->setCurrentImage(image);
return doc;
}
QByteArray serializeToByteArray(QList<KisNodeSP> nodes, KisImageSP srcImage)
{
QScopedPointer<KisDocument> doc(createDocument(nodes, srcImage));
QByteArray result = doc->serializeToNativeByteArray();
// avoid a sanity check failure caused by the fact that the image outlives
// the document (and it does)
doc->setCurrentImage(0);
return result;
}
QVariant KisMimeData::retrieveData(const QString &mimetype, QVariant::Type preferredType) const
{
/**
* HACK ALERT:
*
* Sometimes Qt requests the data *after* destruction of Krita,
* we cannot load the nodes in that case, because we need signals
* and timers. So we just skip serializing.
*/
if (!QApplication::instance()) return QVariant();
Q_ASSERT(m_nodes.size() > 0);
if (mimetype == "application/x-qt-image") {
KisConfig cfg(true);
KisDocument *doc = createDocument(m_nodes, m_image);
return doc->image()->projection()->convertToQImage(cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow())),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
else if (mimetype == "application/x-krita-node" ||
mimetype == "application/zip") {
QByteArray ba = serializeToByteArray(m_nodes, m_image);
return ba;
}
else if (mimetype == "application/x-krita-node-url") {
QByteArray ba = serializeToByteArray(m_nodes, m_image);
QString temporaryPath =
QDir::tempPath() + QDir::separator() +
QString("krita_tmp_dnd_layer_%1_%2.kra")
.arg(QApplication::applicationPid())
.arg(qrand());
QFile file(temporaryPath);
file.open(QFile::WriteOnly);
file.write(ba);
file.flush();
file.close();
return QUrl::fromLocalFile(temporaryPath).toEncoded();
}
else if (mimetype == "application/x-krita-node-internal-pointer") {
QDomDocument doc("krita_internal_node_pointer");
QDomElement root = doc.createElement("pointer");
root.setAttribute("application_pid", (qint64)QApplication::applicationPid());
root.setAttribute("force_copy", m_forceCopy);
root.setAttribute("image_pointer_value", (qint64)m_image.data());
doc.appendChild(root);
Q_FOREACH (KisNodeSP node, m_nodes) {
QDomElement element = doc.createElement("node");
element.setAttribute("pointer_value", (qint64)node.data());
root.appendChild(element);
}
return doc.toByteArray();
}
else {
return QMimeData::retrieveData(mimetype, preferredType);
}
}
void KisMimeData::initializeExternalNode(KisNodeSP *node,
KisImageWSP image,
KisShapeController *shapeController)
{
// adjust the link to a correct image object
(*node)->setImage(image);
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(node->data());
if (shapeLayer) {
// attach the layer to a new shape controller
KisShapeLayer *shapeLayer2 = new KisShapeLayer(*shapeLayer, shapeController);
*node = shapeLayer2;
}
}
QList<KisNodeSP> KisMimeData::tryLoadInternalNodes(const QMimeData *data,
KisImageSP image,
KisShapeController *shapeController,
bool /* IN-OUT */ &copyNode)
{
QList<KisNodeSP> nodes;
bool forceCopy = false;
KisImageSP sourceImage;
// Qt 4.7 and Qt 5.5 way
const KisMimeData *mimedata = qobject_cast<const KisMimeData*>(data);
if (mimedata) {
nodes = mimedata->nodes();
forceCopy = mimedata->m_forceCopy;
sourceImage = mimedata->m_image;
}
// Qt 4.8 way
if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-internal-pointer")) {
QByteArray nodeXml = data->data("application/x-krita-node-internal-pointer");
QDomDocument doc;
doc.setContent(nodeXml);
QDomElement element = doc.documentElement();
qint64 pid = element.attribute("application_pid").toLongLong();
forceCopy = element.attribute("force_copy").toInt();
qint64 imagePointerValue = element.attribute("image_pointer_value").toLongLong();
sourceImage = reinterpret_cast<KisImage*>(imagePointerValue);
if (pid == QApplication::applicationPid()) {
QDomNode n = element.firstChild();
while (!n.isNull()) {
QDomElement e = n.toElement();
if (!e.isNull()) {
qint64 pointerValue = e.attribute("pointer_value").toLongLong();
if (pointerValue) {
nodes << reinterpret_cast<KisNode*>(pointerValue);
}
}
n = n.nextSibling();
}
}
}
if (!nodes.isEmpty() && (forceCopy || copyNode || sourceImage != image)) {
KisImageBarrierLockerWithFeedbackAllowNull locker(sourceImage);
QList<KisNodeSP> clones;
Q_FOREACH (KisNodeSP node, nodes) {
node = node->clone();
if ((forceCopy || copyNode) && sourceImage == image) {
KisLayerUtils::addCopyOfNameTag(node);
}
initializeExternalNode(&node, image, shapeController);
clones << node;
}
nodes = clones;
copyNode = true;
}
return nodes;
}
QList<KisNodeSP> KisMimeData::loadNodes(const QMimeData *data,
const QRect &imageBounds,
const QPoint &preferredCenter,
bool forceRecenter,
KisImageWSP image,
KisShapeController *shapeController)
{
bool alwaysRecenter = false;
QList<KisNodeSP> nodes;
if (data->hasFormat("application/x-krita-node")) {
KisDocument *tempDoc = KisPart::instance()->createDocument();
QByteArray ba = data->data("application/x-krita-node");
QBuffer buf(&ba);
KisImportExportFilter *filter = tempDoc->importExportManager()->filterForMimeType(tempDoc->nativeFormatMimeType(), KisImportExportManager::Import);
filter->setBatchMode(true);
- bool result = (filter->convert(tempDoc, &buf) == KisImportExportFilter::OK);
+ bool result = (filter->convert(tempDoc, &buf).isOk());
if (result) {
KisImageWSP tempImage = tempDoc->image();
Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) {
tempImage->removeNode(node);
initializeExternalNode(&node, image, shapeController);
nodes << node;
}
}
delete filter;
delete tempDoc;
}
if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-url")) {
QByteArray ba = data->data("application/x-krita-node-url");
KisDocument *tempDoc = KisPart::instance()->createDocument();
Q_ASSERT(QUrl::fromEncoded(ba).isLocalFile());
bool result = tempDoc->openUrl(QUrl::fromEncoded(ba));
if (result) {
KisImageWSP tempImage = tempDoc->image();
Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) {
tempImage->removeNode(node);
initializeExternalNode(&node, image, shapeController);
nodes << node;
}
}
delete tempDoc;
QFile::remove(QUrl::fromEncoded(ba).toLocalFile());
}
if (nodes.isEmpty() && data->hasImage()) {
QImage qimage = qvariant_cast<QImage>(data->imageData());
KisPaintDeviceSP device = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
device->convertFromQImage(qimage, 0);
if (image) {
nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device);
}
alwaysRecenter = true;
}
if (!nodes.isEmpty()) {
Q_FOREACH (KisNodeSP node, nodes) {
QRect bounds = node->projection()->exactBounds();
if (alwaysRecenter || forceRecenter ||
(!imageBounds.contains(bounds) &&
!imageBounds.intersects(bounds))) {
QPoint pt = preferredCenter - bounds.center();
node->setX(pt.x());
node->setY(pt.y());
}
}
}
return nodes;
}
QMimeData* KisMimeData::mimeForLayers(const KisNodeList &nodes, KisImageSP image, bool forceCopy)
{
KisNodeList inputNodes = nodes;
KisNodeList sortedNodes;
KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes);
if (sortedNodes.isEmpty()) return 0;
KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy);
return data;
}
QMimeData* KisMimeData::mimeForLayersDeepCopy(const KisNodeList &nodes, KisImageSP image, bool forceCopy)
{
KisNodeList inputNodes = nodes;
KisNodeList sortedNodes;
KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes);
if (sortedNodes.isEmpty()) return 0;
KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy);
data->deepCopyNodes();
return data;
}
bool nodeAllowsAsChild(KisNodeSP parent, KisNodeList nodes)
{
bool result = true;
Q_FOREACH (KisNodeSP node, nodes) {
if (!parent->allowAsChild(node)) {
result = false;
break;
}
}
return result;
}
bool correctNewNodeLocation(KisNodeList nodes,
KisNodeDummy* &parentDummy,
KisNodeDummy* &aboveThisDummy)
{
KisNodeSP parentNode = parentDummy->node();
bool result = true;
if(!nodeAllowsAsChild(parentDummy->node(), nodes)) {
aboveThisDummy = parentDummy;
parentDummy = parentDummy->parent();
result = (!parentDummy) ? false :
correctNewNodeLocation(nodes, parentDummy, aboveThisDummy);
}
return result;
}
KisNodeList KisMimeData::loadNodesFast(
const QMimeData *data,
KisImageSP image,
KisShapeController *shapeController,
bool &copyNode)
{
QList<KisNodeSP> nodes =
KisMimeData::tryLoadInternalNodes(data,
image,
shapeController,
copyNode /* IN-OUT */);
if (nodes.isEmpty()) {
QRect imageBounds = image->bounds();
nodes = KisMimeData::loadNodes(data,
imageBounds, imageBounds.center(),
false,
image, shapeController);
/**
* Don't try to move a node originating from another image,
* just copy it.
*/
copyNode = true;
}
return nodes;
}
bool KisMimeData::insertMimeLayers(const QMimeData *data,
KisImageSP image,
KisShapeController *shapeController,
KisNodeDummy *parentDummy,
KisNodeDummy *aboveThisDummy,
bool copyNode,
KisNodeInsertionAdapter *nodeInsertionAdapter)
{
QList<KisNodeSP> nodes = loadNodesFast(data, image, shapeController, copyNode /* IN-OUT */);
if (nodes.isEmpty()) return false;
bool result = true;
if (!correctNewNodeLocation(nodes, parentDummy, aboveThisDummy)) {
return false;
}
KIS_ASSERT_RECOVER(nodeInsertionAdapter) { return false; }
Q_ASSERT(parentDummy);
KisNodeSP aboveThisNode = aboveThisDummy ? aboveThisDummy->node() : 0;
if (copyNode) {
nodeInsertionAdapter->addNodes(nodes, parentDummy->node(), aboveThisNode);
}
else {
Q_ASSERT(nodes.first()->graphListener() == image.data());
nodeInsertionAdapter->moveNodes(nodes, parentDummy->node(), aboveThisNode);
}
return result;
}
diff --git a/libs/ui/kis_model_index_converter.cpp b/libs/ui/kis_model_index_converter.cpp
index e116da0bf0..cc347d64aa 100644
--- a/libs/ui/kis_model_index_converter.cpp
+++ b/libs/ui/kis_model_index_converter.cpp
@@ -1,195 +1,192 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_model_index_converter.h"
#include "kis_selection_mask.h"
#include "kis_node_dummies_graph.h"
#include "kis_dummies_facade_base.h"
#include "kis_node_model.h"
#include "kis_node_manager.h"
#include "KisReferenceImagesLayer.h"
KisModelIndexConverter::KisModelIndexConverter(KisDummiesFacadeBase *dummiesFacade,
KisNodeModel *model,
bool showGlobalSelection)
: m_dummiesFacade(dummiesFacade),
m_model(model),
m_showGlobalSelection(showGlobalSelection)
{
}
inline bool KisModelIndexConverter::checkDummyType(KisNodeDummy *dummy)
{
return !KisNodeManager::isNodeHidden(dummy->node(), !m_showGlobalSelection);
}
inline bool KisModelIndexConverter::checkDummyMetaObjectType(const QString &type)
{
- // TODO: refactor too?
- if (m_showGlobalSelection) return true;
-
- QString selectionMaskType = KisSelectionMask::staticMetaObject.className();
- QString referencesLayerType = KisReferenceImagesLayer::staticMetaObject.className();
- return type != selectionMaskType && type != referencesLayerType;
+ const QString selectionMaskType = KisSelectionMask::staticMetaObject.className();
+ const QString referencesLayerType = KisReferenceImagesLayer::staticMetaObject.className();
+ return (type != selectionMaskType || m_showGlobalSelection) && type != referencesLayerType;
}
KisNodeDummy* KisModelIndexConverter::dummyFromRow(int row, QModelIndex parent)
{
KisNodeDummy *parentDummy = parent.isValid() ?
dummyFromIndex(parent) : m_dummiesFacade->rootDummy();
if(!parentDummy) return 0;
KisNodeDummy *resultDummy = 0;
// a child of the root node
if(!parentDummy->parent()) {
KisNodeDummy *currentDummy = parentDummy->lastChild();
while(currentDummy) {
if(checkDummyType(currentDummy)) {
if(!row) {
resultDummy = currentDummy;
break;
}
row--;
}
currentDummy = currentDummy->prevSibling();
}
}
// a child of other layer
else {
int rowCount = parentDummy->childCount();
int index = rowCount - row - 1;
resultDummy = parentDummy->at(index);
}
return resultDummy;
}
KisNodeDummy* KisModelIndexConverter::dummyFromIndex(QModelIndex index)
{
Q_ASSERT(index.isValid());
Q_ASSERT(index.internalPointer());
return static_cast<KisNodeDummy*>(index.internalPointer());
}
QModelIndex KisModelIndexConverter::indexFromDummy(KisNodeDummy *dummy)
{
Q_ASSERT(dummy);
KisNodeDummy *parentDummy = dummy->parent();
// a root node
if(!parentDummy) return QModelIndex();
int row = 0;
// a child of the root node
if(!parentDummy->parent()) {
if(!checkDummyType(dummy)) return QModelIndex();
KisNodeDummy *currentDummy = parentDummy->lastChild();
while(currentDummy && currentDummy != dummy) {
if(checkDummyType(currentDummy)) {
row++;
}
currentDummy = currentDummy->prevSibling();
}
}
// a child of other layer
else {
int rowCount = parentDummy->childCount();
int index = parentDummy->indexOf(dummy);
row = rowCount - index - 1;
}
return m_model->createIndex(row, 0, (void*)dummy);
}
bool KisModelIndexConverter::indexFromAddedDummy(KisNodeDummy *parentDummy,
int index,
const QString &newNodeMetaObjectType,
QModelIndex &parentIndex,
int &row)
{
// adding a root node
if(!parentDummy) {
Q_ASSERT(!index);
return false;
}
// adding a child of the root node
if(!parentDummy->parent()) {
if(!checkDummyMetaObjectType(newNodeMetaObjectType)) {
return false;
}
row = 0;
parentIndex = QModelIndex();
KisNodeDummy *dummy = parentDummy->lastChild();
int toScan = parentDummy->childCount() - index;
while(dummy && toScan > 0) {
if(checkDummyType(dummy)) {
row++;
}
dummy = dummy->prevSibling();
toScan--;
}
}
// everything else
else {
parentIndex = indexFromDummy(parentDummy);
int rowCount = parentDummy->childCount();
row = rowCount - index;
}
return true;
}
int KisModelIndexConverter::rowCount(QModelIndex parent)
{
KisNodeDummy *dummy = parent.isValid() ?
dummyFromIndex(parent) : m_dummiesFacade->rootDummy();
// a root node (hidden)
if(!dummy) return 0;
int numChildren = 0;
// children of the root node
if(!dummy->parent()) {
KisNodeDummy *currentDummy = dummy->lastChild();
while(currentDummy) {
if(checkDummyType(currentDummy)) {
numChildren++;
}
currentDummy = currentDummy->prevSibling();
}
}
// children of other nodes
else {
numChildren = dummy->childCount();
}
return numChildren;
}
diff --git a/libs/ui/kis_node_juggler_compressed.cpp b/libs/ui/kis_node_juggler_compressed.cpp
index 5ff9e11e63..8809d2fa09 100644
--- a/libs/ui/kis_node_juggler_compressed.cpp
+++ b/libs/ui/kis_node_juggler_compressed.cpp
@@ -1,901 +1,902 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_juggler_compressed.h"
#include <QHash>
#include <QSharedPointer>
#include <QPointer>
#include "kis_global.h"
#include "kis_image.h"
#include "kis_processing_applicator.h"
#include "commands/kis_image_layer_move_command.h"
#include "commands/kis_image_layer_add_command.h"
#include "kis_signal_compressor.h"
#include "kis_command_utils.h"
#include "kis_layer_utils.h"
#include "kis_node_manager.h"
#include "kis_layer.h"
#include "kis_selection_mask.h"
/**
* A special structure that stores information about a node that was
* moved. The purpose of the object is twofold:
*
* 1) When the reordering stroke is already started than the
* parent and sibling nodes may be not consistent anymore. So
* we store it separately.
*
* 2) This objects allows merging (compressing) multiple moves of
* a layer into a single action. This behavior is implemented
* in tryMerge() method.
*/
struct MoveNodeStruct {
MoveNodeStruct(KisImageSP _image, KisNodeSP _node, KisNodeSP _parent, KisNodeSP _above)
: image(_image),
node(_node),
newParent(_parent),
newAbove(_above),
oldParent(_node->parent()),
oldAbove(_node->prevSibling()),
suppressNewParentRefresh(false),
suppressOldParentRefresh(false)
{
}
bool tryMerge(const MoveNodeStruct &rhs) {
if (rhs.node != node) return false;
bool result = true;
// qDebug() << "Merging";
// qDebug() << ppVar(node);
// qDebug() << ppVar(oldParent) << ppVar(newParent);
// qDebug() << ppVar(oldAbove) << ppVar(newAbove);
// qDebug() << ppVar(rhs.oldParent) << ppVar(rhs.newParent);
// qDebug() << ppVar(rhs.oldAbove) << ppVar(rhs.newAbove);
if (newParent == rhs.oldParent) {
// 'rhs' is newer
newParent = rhs.newParent;
newAbove = rhs.newAbove;
} else if (oldParent == rhs.newParent) {
// 'this' is newer
oldParent = rhs.oldParent;
oldAbove = rhs.oldAbove;
} else {
warnKrita << "MoveNodeStruct: Trying to merge unsequential moves!";
result = false;
}
return result;
}
void doRedoUpdates() {
if (oldParent && !suppressOldParentRefresh) {
image->refreshGraphAsync(oldParent);
}
if (newParent && oldParent != newParent) {
node->setDirty(image->bounds());
}
}
void doUndoUpdates() {
if (newParent && !suppressNewParentRefresh) {
image->refreshGraphAsync(newParent);
}
if (oldParent && oldParent != newParent) {
node->setDirty(image->bounds());
}
}
void resolveParentCollisions(MoveNodeStruct *rhs) const {
if (rhs->newParent == newParent) {
rhs->suppressNewParentRefresh = true;
}
if (rhs->oldParent == oldParent) {
rhs->suppressOldParentRefresh = true;
}
}
KisImageSP image;
KisNodeSP node;
KisNodeSP newParent;
KisNodeSP newAbove;
KisNodeSP oldParent;
KisNodeSP oldAbove;
bool suppressNewParentRefresh;
bool suppressOldParentRefresh;
};
typedef QSharedPointer<MoveNodeStruct> MoveNodeStructSP;
typedef QHash<KisNodeSP, MoveNodeStructSP> MovedNodesHash;
/**
* All the commands executed bythe stroke system are running in the
* background asynchronously. But, at the same time, they emit updates
* in parallel to the ones emitted by the juggler. Therefore, the
* juggler and all its commands should share some data: which updates
* have been requested, but not yet dispatched (m_movedNodesInitial),
* and what updates have already been processed and executed
* (m_movedNodesUpdated). This object is shared via a shared pointer
* and guarantees safe (including thread-safe) access to the shared
* data.
*/
class BatchMoveUpdateData {
MovedNodesHash m_movedNodesInitial;
MovedNodesHash m_movedNodesUpdated;
QMutex m_mutex;
QPointer<KisNodeJugglerCompressed> m_parentJuggler;
public:
BatchMoveUpdateData(KisNodeJugglerCompressed *parentJuggler)
: m_parentJuggler(parentJuggler) {}
private:
static void addToHashLazy(MovedNodesHash *hash, MoveNodeStructSP moveStruct) {
if (hash->contains(moveStruct->node)) {
bool result = hash->value(moveStruct->node)->tryMerge(*moveStruct);
KIS_ASSERT_RECOVER_NOOP(result);
} else {
MovedNodesHash::const_iterator it = hash->constBegin();
MovedNodesHash::const_iterator end = hash->constEnd();
for (; it != end; ++it) {
it.value()->resolveParentCollisions(moveStruct.data());
}
hash->insert(moveStruct->node, moveStruct);
}
}
public:
void processUnhandledUpdates() {
QMutexLocker l(&m_mutex);
if (m_movedNodesInitial.isEmpty()) return;
MovedNodesHash::const_iterator it = m_movedNodesInitial.constBegin();
MovedNodesHash::const_iterator end = m_movedNodesInitial.constEnd();
for (; it != end; ++it) {
it.value()->doRedoUpdates();
addToHashLazy(&m_movedNodesUpdated, it.value());
}
m_movedNodesInitial.clear();
}
void addInitialUpdate(MoveNodeStructSP moveStruct) {
{
QMutexLocker l(&m_mutex);
addToHashLazy(&m_movedNodesInitial, moveStruct);
// the juggler might directly forward the signal to processUnhandledUpdates,
// which would also like to get a lock, so we should release it beforehand
}
if (m_parentJuggler) {
emit m_parentJuggler->requestUpdateAsyncFromCommand();
}
}
void emitFinalUpdates(KisCommandUtils::FlipFlopCommand::State state) {
QMutexLocker l(&m_mutex);
if (m_movedNodesUpdated.isEmpty()) return;
MovedNodesHash::const_iterator it = m_movedNodesUpdated.constBegin();
MovedNodesHash::const_iterator end = m_movedNodesUpdated.constEnd();
for (; it != end; ++it) {
if (state == KisCommandUtils::FlipFlopCommand::State::FINALIZING) {
it.value()->doRedoUpdates();
} else {
it.value()->doUndoUpdates();
}
}
}
};
typedef QSharedPointer<BatchMoveUpdateData> BatchMoveUpdateDataSP;
/**
* A command that emits a update signals on the compressed move undo
* or redo.
*/
class UpdateMovedNodesCommand : public KisCommandUtils::FlipFlopCommand
{
public:
UpdateMovedNodesCommand(BatchMoveUpdateDataSP updateData, bool finalize, KUndo2Command *parent = 0)
: FlipFlopCommand(finalize, parent),
m_updateData(updateData)
{
}
void partB() override {
State currentState = getState();
if (currentState == FINALIZING && isFirstRedo()) {
/**
* When doing the first redo() some of the updates might
* have already been executed by the juggler itself, so we
* should process'unhandled' updates only
*/
m_updateData->processUnhandledUpdates();
} else {
/**
* When being executed by real undo/redo operations, we
* should emit all the update signals. No one else will do
* that for us (juggler, which did it in the previous
* case, might have already died).
*/
m_updateData->emitFinalUpdates(currentState);
}
}
private:
BatchMoveUpdateDataSP m_updateData;
};
/**
* A command to activate newly created selection masks after any action
*/
class ActivateSelectionMasksCommand : public KisCommandUtils::FlipFlopCommand
{
public:
ActivateSelectionMasksCommand(const QList<KisSelectionMaskSP> &activeBefore,
const QList<KisSelectionMaskSP> &activeAfter,
bool finalize, KUndo2Command *parent = 0)
: FlipFlopCommand(finalize, parent),
m_activeBefore(activeBefore),
m_activeAfter(activeAfter)
{
}
void partA() override {
QList<KisSelectionMaskSP> *newActiveMasks;
if (getState() == FINALIZING) {
newActiveMasks = &m_activeAfter;
} else {
newActiveMasks = &m_activeBefore;
}
Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) {
mask->setActive(false);
}
}
void partB() override {
QList<KisSelectionMaskSP> *newActiveMasks;
if (getState() == FINALIZING) {
newActiveMasks = &m_activeAfter;
} else {
newActiveMasks = &m_activeBefore;
}
Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) {
mask->setActive(true);
}
}
private:
QList<KisSelectionMaskSP> m_activeBefore;
QList<KisSelectionMaskSP> m_activeAfter;
};
KisNodeList sortAndFilterNodes(const KisNodeList &nodes, KisImageSP image) {
KisNodeList filteredNodes = nodes;
KisNodeList sortedNodes;
KisLayerUtils::filterMergableNodes(filteredNodes, true);
bool haveExternalNodes = false;
Q_FOREACH (KisNodeSP node, nodes) {
if (node->graphListener() != image->root()->graphListener()) {
haveExternalNodes = true;
break;
}
}
if (!haveExternalNodes) {
KisLayerUtils::sortMergableNodes(image->root(), filteredNodes, sortedNodes);
} else {
sortedNodes = filteredNodes;
}
return sortedNodes;
}
/**
* A generalized command to muve up/down a set of layer
*/
struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand {
LowerRaiseLayer(BatchMoveUpdateDataSP updateData,
KisImageSP image,
const KisNodeList &nodes,
KisNodeSP activeNode,
bool lower)
: m_updateData(updateData),
m_image(image),
m_nodes(nodes),
m_activeNode(activeNode),
m_lower (lower) {}
enum NodesType {
AllLayers,
Mixed,
AllMasks
};
NodesType getNodesType(KisNodeList nodes) {
bool hasLayers = false;
bool hasMasks = false;
Q_FOREACH (KisNodeSP node, nodes) {
hasLayers |= bool(qobject_cast<KisLayer*>(node.data()));
hasMasks |= bool(qobject_cast<KisMask*>(node.data()));
}
return hasLayers && hasMasks ? Mixed :
hasLayers ? AllLayers :
AllMasks;
}
void populateChildCommands() override {
KisNodeList sortedNodes = sortAndFilterNodes(m_nodes, m_image);
KisNodeSP headNode = m_lower ? sortedNodes.first() : sortedNodes.last();
const NodesType nodesType = getNodesType(sortedNodes);
KisNodeSP parent = headNode->parent();
KisNodeSP grandParent = parent ? parent->parent() : 0;
KisNodeSP newAbove;
KisNodeSP newParent;
if (m_lower) {
KisNodeSP prevNode = headNode->prevSibling();
if (prevNode) {
if ((prevNode->inherits("KisGroupLayer") &&
!prevNode->collapsed())
||
(nodesType == AllMasks &&
prevNode->inherits("KisLayer"))) {
newAbove = prevNode->lastChild();
newParent = prevNode;
} else {
newAbove = prevNode->prevSibling();
newParent = parent;
}
} else if ((nodesType == AllLayers && grandParent) ||
(nodesType == AllMasks && grandParent && grandParent->parent())) {
newAbove = parent->prevSibling();
newParent = grandParent;
} else if (nodesType == AllMasks &&
grandParent && !grandParent->parent() &&
(prevNode = parent->prevSibling()) &&
prevNode->inherits("KisLayer")) {
newAbove = prevNode->lastChild();
newParent = prevNode; // NOTE: this is an updated 'prevNode'!
}
} else {
KisNodeSP nextNode = headNode->nextSibling();
if (nextNode) {
if ((nextNode->inherits("KisGroupLayer") &&
!nextNode->collapsed())
||
(nodesType == AllMasks &&
nextNode->inherits("KisLayer"))) {
newAbove = 0;
newParent = nextNode;
} else {
newAbove = nextNode;
newParent = parent;
}
} else if ((nodesType == AllLayers && grandParent) ||
(nodesType == AllMasks && grandParent && grandParent->parent())) {
newAbove = parent;
newParent = grandParent;
} else if (nodesType == AllMasks &&
grandParent && !grandParent->parent() &&
(nextNode = parent->nextSibling()) &&
nextNode->inherits("KisLayer")) {
newAbove = 0;
newParent = nextNode; // NOTE: this is an updated 'nextNode'!
}
}
if (!newParent) return;
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes,
m_activeNode, m_activeNode,
m_image, false));
KisNodeSP currentAbove = newAbove;
Q_FOREACH (KisNodeSP node, sortedNodes) {
if (node->parent() != newParent && !newParent->allowAsChild(node)) {
continue;
}
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, newParent, currentAbove));
addCommand(new KisImageLayerMoveCommand(m_image, node, newParent, currentAbove, false));
m_updateData->addInitialUpdate(moveStruct);
currentAbove = node;
}
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes,
m_activeNode, m_activeNode,
m_image, true));
}
private:
BatchMoveUpdateDataSP m_updateData;
KisImageSP m_image;
KisNodeList m_nodes;
KisNodeSP m_activeNode;
bool m_lower;
};
struct DuplicateLayers : public KisCommandUtils::AggregateCommand {
enum Mode {
MOVE,
COPY,
ADD
};
DuplicateLayers(BatchMoveUpdateDataSP updateData,
KisImageSP image,
const KisNodeList &nodes,
KisNodeSP dstParent,
KisNodeSP dstAbove,
KisNodeSP activeNode,
Mode mode)
: m_updateData(updateData),
m_image(image),
m_nodes(nodes),
m_dstParent(dstParent),
m_dstAbove(dstAbove),
m_activeNode(activeNode),
m_mode(mode) {}
void populateChildCommands() override {
KisNodeList filteredNodes = sortAndFilterNodes(m_nodes, m_image);
if (filteredNodes.isEmpty()) return;
KisNodeSP newAbove = filteredNodes.last();
KisNodeSP newParent = newAbove->parent();
// override parent if provided externally
if (m_dstParent) {
newAbove = m_dstAbove;
newParent = m_dstParent;
}
const int indexOfActiveNode = filteredNodes.indexOf(m_activeNode);
QList<KisSelectionMaskSP> activeMasks = findActiveSelectionMasks(filteredNodes);
// we will deactivate the masks before processing, so we should
// save their list in a convenient form
QSet<KisNodeSP> activeMaskNodes;
Q_FOREACH (KisSelectionMaskSP mask, activeMasks) {
activeMaskNodes.insert(mask);
}
const bool haveActiveMasks = !activeMasks.isEmpty();
if (!newParent) return;
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
m_activeNode, KisNodeSP(),
m_image, false));
if (haveActiveMasks) {
/**
* We should first disable the currently active masks, after the operation
* completed their cloned counterparts will be activated instead.
*
* HINT: we should deactivate the masks before cloning, because otherwise
* KisGroupLayer::allowAsChild() will not let the second mask to be
* added to the list of child nodes. See bug 382315.
*/
addCommand(new ActivateSelectionMasksCommand(activeMasks,
QList<KisSelectionMaskSP>(),
false));
}
KisNodeList newNodes;
QList<KisSelectionMaskSP> newActiveMasks;
KisNodeSP currentAbove = newAbove;
Q_FOREACH (KisNodeSP node, filteredNodes) {
if (m_mode == COPY || m_mode == ADD) {
KisNodeSP newNode;
if (m_mode == COPY) {
newNode = node->clone();
KisLayerUtils::addCopyOfNameTag(newNode);
} else {
newNode = node;
}
newNodes << newNode;
if (haveActiveMasks && activeMaskNodes.contains(node)) {
KisSelectionMaskSP mask = dynamic_cast<KisSelectionMask*>(newNode.data());
newActiveMasks << mask;
}
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove));
m_updateData->addInitialUpdate(moveStruct);
addCommand(new KisImageLayerAddCommand(m_image, newNode,
newParent,
currentAbove,
false, false));
currentAbove = newNode;
} else if (m_mode == MOVE) {
KisNodeSP newNode = node;
newNodes << newNode;
if (haveActiveMasks && activeMaskNodes.contains(node)) {
KisSelectionMaskSP mask = dynamic_cast<KisSelectionMask*>(newNode.data());
newActiveMasks << mask;
}
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove));
m_updateData->addInitialUpdate(moveStruct);
addCommand(new KisImageLayerMoveCommand(m_image, newNode,
newParent,
currentAbove,
false));
currentAbove = newNode;
}
}
if (haveActiveMasks) {
/**
* Activate the cloned counterparts of the masks after the operation
* is complete.
*/
addCommand(new ActivateSelectionMasksCommand(QList<KisSelectionMaskSP>(),
newActiveMasks,
true));
}
KisNodeSP newActiveNode = newNodes[qBound(0, indexOfActiveNode, newNodes.size() - 1)];
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(KisNodeList(), newNodes,
KisNodeSP(), newActiveNode,
m_image, true));
}
private:
KisSelectionMaskSP toActiveSelectionMask(KisNodeSP node) {
KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(node.data());
return mask && mask->active() ? mask : 0;
}
QList<KisSelectionMaskSP> findActiveSelectionMasks(KisNodeList nodes) {
QList<KisSelectionMaskSP> masks;
foreach (KisNodeSP node, nodes) {
KisSelectionMaskSP mask = toActiveSelectionMask(node);
if (mask) {
masks << mask;
}
}
return masks;
}
private:
BatchMoveUpdateDataSP m_updateData;
KisImageSP m_image;
KisNodeList m_nodes;
KisNodeSP m_dstParent;
KisNodeSP m_dstAbove;
KisNodeSP m_activeNode;
Mode m_mode;
};
struct RemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
RemoveLayers(BatchMoveUpdateDataSP updateData,
KisImageSP image,
const KisNodeList &nodes,
KisNodeSP activeNode)
: m_updateData(updateData),
m_image(image),
m_nodes(nodes),
m_activeNode(activeNode){}
void populateChildCommands() override {
KisNodeList filteredNodes = m_nodes;
KisLayerUtils::filterMergableNodes(filteredNodes, true);
+ KisLayerUtils::filterUnlockedNodes(filteredNodes);
if (filteredNodes.isEmpty()) return;
Q_FOREACH (KisNodeSP node, filteredNodes) {
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, KisNodeSP(), KisNodeSP()));
m_updateData->addInitialUpdate(moveStruct);
}
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
m_activeNode, KisNodeSP(),
m_image, false));
safeRemoveMultipleNodes(filteredNodes, m_image);
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
m_activeNode, KisNodeSP(),
m_image, true));
}
protected:
void addCommandImpl(KUndo2Command *cmd) override {
addCommand(cmd);
}
private:
BatchMoveUpdateDataSP m_updateData;
KisImageSP m_image;
KisNodeList m_nodes;
KisNodeSP m_activeNode;
};
struct KisNodeJugglerCompressed::Private
{
Private(KisNodeJugglerCompressed *juggler, const KUndo2MagicString &_actionName, KisImageSP _image, KisNodeManager *_nodeManager, int _timeout)
: actionName(_actionName),
image(_image),
nodeManager(_nodeManager),
compressor(_timeout, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
selfDestructionCompressor(3 * _timeout, KisSignalCompressor::POSTPONE),
updateData(new BatchMoveUpdateData(juggler)),
autoDelete(false),
isStarted(false)
{}
KUndo2MagicString actionName;
KisImageSP image;
KisNodeManager *nodeManager;
QScopedPointer<KisProcessingApplicator> applicator;
KisSignalCompressor compressor;
KisSignalCompressor selfDestructionCompressor;
BatchMoveUpdateDataSP updateData;
bool autoDelete;
bool isStarted;
};
KisNodeJugglerCompressed::KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, KisNodeManager *nodeManager, int timeout)
: m_d(new Private(this, actionName, image, nodeManager, timeout))
{
connect(m_d->image, SIGNAL(sigStrokeCancellationRequested()), SLOT(slotEndStrokeRequested()));
connect(m_d->image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(slotCancelStrokeRequested()));
connect(m_d->image, SIGNAL(sigStrokeEndRequestedActiveNodeFiltered()), SLOT(slotEndStrokeRequested()));
connect(m_d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(slotImageAboutToBeDeleted()));
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
m_d->applicator.reset(
new KisProcessingApplicator(m_d->image, 0,
KisProcessingApplicator::NONE,
emitSignals,
actionName));
connect(this, SIGNAL(requestUpdateAsyncFromCommand()), SLOT(startTimers()));
connect(&m_d->compressor, SIGNAL(timeout()), SLOT(slotUpdateTimeout()));
m_d->applicator->applyCommand(
new UpdateMovedNodesCommand(m_d->updateData, false));
m_d->isStarted = true;
}
KisNodeJugglerCompressed::~KisNodeJugglerCompressed()
{
KIS_ASSERT_RECOVER(!m_d->applicator) {
m_d->applicator->end();
m_d->applicator.reset();
}
}
bool KisNodeJugglerCompressed::canMergeAction(const KUndo2MagicString &actionName)
{
return actionName == m_d->actionName;
}
void KisNodeJugglerCompressed::lowerNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new LowerRaiseLayer(m_d->updateData,
m_d->image,
nodes, activeNode, true));
}
void KisNodeJugglerCompressed::raiseNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new LowerRaiseLayer(m_d->updateData,
m_d->image,
nodes, activeNode, false));
}
void KisNodeJugglerCompressed::removeNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new RemoveLayers(m_d->updateData,
m_d->image,
nodes, activeNode));
}
void KisNodeJugglerCompressed::duplicateNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
KisNodeSP(), KisNodeSP(),
activeNode,
DuplicateLayers::COPY));
}
void KisNodeJugglerCompressed::copyNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
dstParent, dstAbove,
activeNode,
DuplicateLayers::COPY));
}
void KisNodeJugglerCompressed::moveNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
dstParent, dstAbove,
activeNode,
DuplicateLayers::MOVE));
}
void KisNodeJugglerCompressed::addNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
dstParent, dstAbove,
activeNode,
DuplicateLayers::ADD));
}
void KisNodeJugglerCompressed::moveNode(KisNodeSP node, KisNodeSP parent, KisNodeSP above)
{
m_d->applicator->applyCommand(new KisImageLayerMoveCommand(m_d->image, node, parent, above, false));
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_d->image, node, parent, above));
m_d->updateData->addInitialUpdate(moveStruct);
}
void KisNodeJugglerCompressed::startTimers()
{
m_d->compressor.start();
if (m_d->autoDelete) {
m_d->selfDestructionCompressor.start();
}
}
void KisNodeJugglerCompressed::slotUpdateTimeout()
{
m_d->updateData->processUnhandledUpdates();
}
void KisNodeJugglerCompressed::end()
{
if (!m_d->isStarted) return;
m_d->applicator->applyCommand(
new UpdateMovedNodesCommand(m_d->updateData, true));
m_d->applicator->end();
cleanup();
}
void KisNodeJugglerCompressed::cleanup()
{
m_d->applicator.reset();
m_d->compressor.stop();
m_d->isStarted = false;
if (m_d->autoDelete) {
m_d->selfDestructionCompressor.stop();
this->deleteLater();
}
}
void KisNodeJugglerCompressed::setAutoDelete(bool value)
{
m_d->autoDelete = value;
connect(&m_d->selfDestructionCompressor, SIGNAL(timeout()), SLOT(end()));
}
void KisNodeJugglerCompressed::slotEndStrokeRequested()
{
if (!m_d->isStarted) return;
end();
}
void KisNodeJugglerCompressed::slotCancelStrokeRequested()
{
if (!m_d->isStarted) return;
m_d->applicator->cancel();
cleanup();
}
void KisNodeJugglerCompressed::slotImageAboutToBeDeleted()
{
if (!m_d->isStarted) return;
m_d->applicator->end();
cleanup();
}
bool KisNodeJugglerCompressed::isEnded() const
{
return !m_d->isStarted;
}
diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp
index d72f73319b..8c93b0d20d 100644
--- a/libs/ui/kis_node_manager.cpp
+++ b/libs/ui/kis_node_manager.cpp
@@ -1,1567 +1,1572 @@
/*
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_manager.h"
#include <QStandardPaths>
#include <QMessageBox>
#include <QSignalMapper>
#include <QApplication>
#include <QMessageBox>
#include <kactioncollection.h>
#include <QKeySequence>
#include <kis_icon.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoShape.h>
#include <KoShapeLayer.h>
#include <KisImportExportManager.h>
#include <KoFileDialog.h>
#include <KoToolManager.h>
#include <KoProperties.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <kis_types.h>
#include <kis_node.h>
#include <kis_selection.h>
#include <kis_selection_mask.h>
#include <kis_layer.h>
#include <kis_mask.h>
#include <kis_image.h>
#include <kis_painter.h>
#include <kis_paint_layer.h>
#include <KisMimeDatabase.h>
#include <KisReferenceImagesLayer.h>
#include "KisPart.h"
#include "canvas/kis_canvas2.h"
#include "kis_shape_controller.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_mask_manager.h"
#include "kis_group_layer.h"
#include "kis_layer_manager.h"
#include "kis_selection_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_processing_applicator.h"
#include "kis_sequential_iterator.h"
#include "kis_transaction.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_node_juggler_compressed.h"
#include "KisNodeDisplayModeAdapter.h"
#include "kis_clipboard.h"
#include "kis_node_dummies_graph.h"
#include "kis_mimedata.h"
#include "kis_layer_utils.h"
#include "krita_utils.h"
#include "kis_shape_layer.h"
#include "processing/kis_mirror_processing_visitor.h"
#include "KisView.h"
#include <kis_signals_blocker.h>
#include <libs/image/kis_layer_properties_icons.h>
#include <libs/image/commands/kis_node_property_list_command.h>
struct KisNodeManager::Private {
Private(KisNodeManager *_q, KisViewManager *v)
: q(_q)
, view(v)
, imageView(0)
, layerManager(v)
, maskManager(v)
, commandsAdapter(v)
, nodeSelectionAdapter(new KisNodeSelectionAdapter(q))
, nodeInsertionAdapter(new KisNodeInsertionAdapter(q))
, nodeDisplayModeAdapter(new KisNodeDisplayModeAdapter())
, lastRequestedIsolatedModeStatus(false)
{
}
KisNodeManager * q;
KisViewManager * view;
QPointer<KisView>imageView;
KisLayerManager layerManager;
KisMaskManager maskManager;
KisNodeCommandsAdapter commandsAdapter;
QScopedPointer<KisNodeSelectionAdapter> nodeSelectionAdapter;
QScopedPointer<KisNodeInsertionAdapter> nodeInsertionAdapter;
QScopedPointer<KisNodeDisplayModeAdapter> nodeDisplayModeAdapter;
KisAction *showInTimeline;
KisNodeList selectedNodes;
QPointer<KisNodeJugglerCompressed> nodeJuggler;
KisNodeWSP previouslyActiveNode;
bool activateNodeImpl(KisNodeSP node);
QSignalMapper nodeCreationSignalMapper;
QSignalMapper nodeConversionSignalMapper;
bool lastRequestedIsolatedModeStatus;
void saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity);
void mergeTransparencyMaskAsAlpha(bool writeToLayers);
KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName);
};
bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
{
Q_ASSERT(view);
Q_ASSERT(view->canvasBase());
Q_ASSERT(view->canvasBase()->globalShapeManager());
Q_ASSERT(imageView);
if (node && node == q->activeNode()) {
return false;
}
// Set the selection on the shape manager to the active layer
// and set call KoSelection::setActiveLayer( KoShapeLayer* layer )
// with the parent of the active layer.
KoSelection *selection = view->canvasBase()->globalShapeManager()->selection();
Q_ASSERT(selection);
selection->deselectAll();
if (!node) {
selection->setActiveLayer(0);
imageView->setCurrentNode(0);
maskManager.activateMask(0);
layerManager.activateLayer(0);
previouslyActiveNode = q->activeNode();
} else {
previouslyActiveNode = q->activeNode();
KoShape * shape = view->document()->shapeForNode(node);
//if (!shape) return false;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false);
selection->select(shape);
KoShapeLayer * shapeLayer = dynamic_cast<KoShapeLayer*>(shape);
//if (!shapeLayer) return false;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false);
// shapeLayer->setGeometryProtected(node->userLocked());
// shapeLayer->setVisible(node->visible());
selection->setActiveLayer(shapeLayer);
imageView->setCurrentNode(node);
if (KisLayerSP layer = qobject_cast<KisLayer*>(node.data())) {
maskManager.activateMask(0);
layerManager.activateLayer(layer);
} else if (KisMaskSP mask = dynamic_cast<KisMask*>(node.data())) {
maskManager.activateMask(mask);
// XXX_NODE: for now, masks cannot be nested.
layerManager.activateLayer(static_cast<KisLayer*>(node->parent().data()));
}
}
return true;
}
KisNodeManager::KisNodeManager(KisViewManager *view)
: m_d(new Private(this, view))
{
connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP)));
}
KisNodeManager::~KisNodeManager()
{
delete m_d;
}
void KisNodeManager::setView(QPointer<KisView>imageView)
{
m_d->maskManager.setView(imageView);
m_d->layerManager.setView(imageView);
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this);
m_d->imageView->image()->disconnect(this);
}
m_d->imageView = imageView;
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP)));
connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeActionImageStatusChange()));
connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP,KisNodeList)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP,KisNodeList)));
m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode());
}
}
#define NEW_LAYER_ACTION(id, layerType) \
{ \
action = actionManager->createAction(id); \
m_d->nodeCreationSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeCreationSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION_2(id, layerType, exclude) \
{ \
action = actionManager->createAction(id); \
action->setExcludedNodeTypes(QStringList(exclude)); \
actionManager->addAction(id, action); \
m_d->nodeConversionSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeConversionSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION(id, layerType) \
CONVERT_NODE_ACTION_2(id, layerType, layerType)
void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager)
{
m_d->layerManager.setup(actionManager);
m_d->maskManager.setup(actionCollection, actionManager);
KisAction * action = 0;
action = actionManager->createAction("mirrorNodeX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX()));
action = actionManager->createAction("mirrorNodeY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY()));
action = actionManager->createAction("mirrorAllNodesX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesX()));
action = actionManager->createAction("mirrorAllNodesY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesY()));
action = actionManager->createAction("activateNextLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode()));
action = actionManager->createAction("activatePreviousLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode()));
action = actionManager->createAction("switchToPreviouslyActiveNode");
connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode()));
action = actionManager->createAction("save_node_as_image");
connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage()));
action = actionManager->createAction("save_vector_node_to_svg");
connect(action, SIGNAL(triggered()), this, SLOT(saveVectorLayerAsImage()));
action->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER);
action = actionManager->createAction("duplicatelayer");
connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode()));
action = actionManager->createAction("copy_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard()));
action = actionManager->createAction("cut_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard()));
action = actionManager->createAction("paste_layer_from_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard()));
action = actionManager->createAction("create_quick_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup()));
action = actionManager->createAction("create_quick_clipping_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup()));
action = actionManager->createAction("quick_ungroup");
connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup()));
action = actionManager->createAction("select_all_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes()));
action = actionManager->createAction("select_visible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes()));
action = actionManager->createAction("select_locked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes()));
action = actionManager->createAction("select_invisible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes()));
action = actionManager->createAction("select_unlocked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes()));
action = actionManager->createAction("new_from_visible");
connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible()));
action = actionManager->createAction("show_in_timeline");
action->setCheckable(true);
connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool)));
m_d->showInTimeline = action;
NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer");
NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer");
NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer");
NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer");
NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer");
NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer");
NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer");
NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask");
NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask");
NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask");
NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask");
NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask");
connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(QString)),
this, SLOT(createNode(QString)));
CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer");
CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask");
CONVERT_NODE_ACTION("convert_to_animated", "animated");
CONVERT_NODE_ACTION_2("convert_to_file_layer", "KisFileLayer", QStringList() << "KisGroupLayer" << "KisFileLayer" << "KisCloneLayer");
connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(QString)),
this, SLOT(convertNode(QString)));
action = actionManager->createAction("isolate_layer");
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool)));
action = actionManager->createAction("toggle_layer_visibility");
connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility()));
action = actionManager->createAction("toggle_layer_lock");
connect(action, SIGNAL(triggered()), this, SLOT(toggleLock()));
action = actionManager->createAction("toggle_layer_inherit_alpha");
connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha()));
action = actionManager->createAction("toggle_layer_alpha_lock");
connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock()));
action = actionManager->createAction("split_alpha_into_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask()));
action = actionManager->createAction("split_alpha_write");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite()));
// HINT: we can save even when the nodes are not editable
action = actionManager->createAction("split_alpha_save_merged");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryRestartIsolatedMode()));
}
void KisNodeManager::updateGUI()
{
// enable/disable all relevant actions
m_d->layerManager.updateGUI();
m_d->maskManager.updateGUI();
}
KisNodeSP KisNodeManager::activeNode()
{
if (m_d->imageView) {
return m_d->imageView->currentNode();
}
return 0;
}
KisLayerSP KisNodeManager::activeLayer()
{
return m_d->layerManager.activeLayer();
}
const KoColorSpace* KisNodeManager::activeColorSpace()
{
if (m_d->maskManager.activeDevice()) {
return m_d->maskManager.activeDevice()->colorSpace();
} else {
Q_ASSERT(m_d->layerManager.activeLayer());
if (m_d->layerManager.activeLayer()->parentLayer())
return m_d->layerManager.activeLayer()->parentLayer()->colorSpace();
else
return m_d->view->image()->colorSpace();
}
}
void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index)
{
if (parent->allowAsChild(node)) {
if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) {
KisSelectionMask *m = dynamic_cast<KisSelectionMask*>(node.data());
KisLayer *l = qobject_cast<KisLayer*>(parent.data());
if (m && m->active() && l && l->selectionMask()) {
l->selectionMask()->setActive(false);
}
}
m_d->commandsAdapter.moveNode(node, parent, index);
}
}
void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Move Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, aboveThis);
}
void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Copy Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->copyNode(nodes, parent, aboveThis);
}
void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Add Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->addNode(nodes, parent, aboveThis);
}
void KisNodeManager::toggleIsolateActiveNode()
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode == image->isolatedModeRoot()) {
toggleIsolateMode(false);
} else {
toggleIsolateMode(true);
}
}
void KisNodeManager::toggleIsolateMode(bool checked)
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
if (checked && activeNode) {
// Transform and colorize masks don't have pixel data...
if (activeNode->inherits("KisTransformMask") ||
activeNode->inherits("KisColorizeMask")) return;
if (!image->startIsolatedMode(activeNode)) {
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
action->setChecked(false);
}
} else {
image->stopIsolatedMode();
}
m_d->lastRequestedIsolatedModeStatus = checked;
}
void KisNodeManager::slotUpdateIsolateModeActionImageStatusChange()
{
slotUpdateIsolateModeAction();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (this->activeNode() &&
bool(isolatedRootNode) != m_d->lastRequestedIsolatedModeStatus) {
slotTryRestartIsolatedMode();
}
}
void KisNodeManager::slotUpdateIsolateModeAction()
{
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
Q_ASSERT(action);
KisNodeSP activeNode = this->activeNode();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
action->setChecked(isolatedRootNode && isolatedRootNode == activeNode);
}
void KisNodeManager::slotTryRestartIsolatedMode()
{
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (!isolatedRootNode && !m_d->lastRequestedIsolatedModeStatus) return;
this->toggleIsolateMode(true);
}
KisNodeSP KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom)
{
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) {
return 0;
}
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
KIS_ASSERT_RECOVER_RETURN_VALUE(activeNode, 0);
// XXX: make factories for this kind of stuff,
// with a registry
if (nodeType == "KisPaintLayer") {
return m_d->layerManager.addPaintLayer(activeNode);
} else if (nodeType == "KisGroupLayer") {
return m_d->layerManager.addGroupLayer(activeNode);
} else if (nodeType == "KisAdjustmentLayer") {
return m_d->layerManager.addAdjustmentLayer(activeNode);
} else if (nodeType == "KisGeneratorLayer") {
return m_d->layerManager.addGeneratorLayer(activeNode);
} else if (nodeType == "KisShapeLayer") {
return m_d->layerManager.addShapeLayer(activeNode);
} else if (nodeType == "KisCloneLayer") {
return m_d->layerManager.addCloneLayer(activeNode);
} else if (nodeType == "KisTransparencyMask") {
return m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFilterMask") {
return m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false);
} else if (nodeType == "KisColorizeMask") {
return m_d->maskManager.createColorizeMask(activeNode);
} else if (nodeType == "KisTransformMask") {
return m_d->maskManager.createTransformMask(activeNode);
} else if (nodeType == "KisSelectionMask") {
return m_d->maskManager.createSelectionMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFileLayer") {
return m_d->layerManager.addFileLayer(activeNode);
}
return 0;
}
void KisNodeManager::createFromVisible()
{
KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild());
}
void KisNodeManager::slotShowHideTimeline(bool value)
{
Q_FOREACH (KisNodeSP node, selectedNodes()) {
node->setUseInTimeline(value);
}
}
KisLayerSP KisNodeManager::createPaintLayer()
{
KisNodeSP node = createNode("KisPaintLayer");
return dynamic_cast<KisLayer*>(node.data());
}
void KisNodeManager::convertNode(const QString &nodeType)
{
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) {
return;
}
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
if (nodeType == "KisPaintLayer") {
m_d->layerManager.convertNodeToPaintLayer(activeNode);
} else if (nodeType == "KisSelectionMask" ||
nodeType == "KisFilterMask" ||
nodeType == "KisTransparencyMask") {
KisPaintDeviceSP copyFrom = activeNode->paintDevice() ?
activeNode->paintDevice() : activeNode->projection();
m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask"));
bool result = false;
if (nodeType == "KisSelectionMask") {
result = !m_d->maskManager.createSelectionMask(activeNode, copyFrom, true).isNull();
} else if (nodeType == "KisFilterMask") {
result = !m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true).isNull();
} else if (nodeType == "KisTransparencyMask") {
result = !m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true).isNull();
}
m_d->commandsAdapter.endMacro();
if (!result) {
m_d->view->blockUntilOperationsFinishedForced(m_d->imageView->image());
m_d->commandsAdapter.undoLastCommand();
}
} else if (nodeType == "KisFileLayer") {
m_d->layerManager.convertLayerToFileLayer(activeNode);
} else {
warnKrita << "Unsupported node conversion type:" << nodeType;
}
}
void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node)
{
KisDummiesFacadeBase *dummiesFacade = dynamic_cast<KisDummiesFacadeBase*>(m_d->imageView->document()->shapeController());
KIS_SAFE_ASSERT_RECOVER_RETURN(dummiesFacade);
const bool nodeVisible = !isNodeHidden(node, !m_d->nodeDisplayModeAdapter->showGlobalSelectionMask());
if (!nodeVisible) {
return;
}
KIS_ASSERT_RECOVER_RETURN(node != activeNode());
if (m_d->activateNodeImpl(node)) {
emit sigUiNeedChangeActiveNode(node);
emit sigNodeActivated(node);
nodesUpdated();
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
}
void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
{
// the node must still be in the graph, some asynchronous
// signals may easily break this requirement
if (node && !node->graphListener()) {
node = 0;
}
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
{
// the node must still be in the graph, some asynchronous
// signals may easily break this requirement
if (node && !node->graphListener()) {
node = 0;
}
if (node) {
QStringList vectorTools = QStringList()
<< "InteractionTool"
<< "KarbonPatternTool"
<< "KarbonGradientTool"
<< "KarbonCalligraphyTool"
<< "CreateShapesTool"
<< "PathTool";
QStringList pixelTools = QStringList()
<< "KritaShape/KisToolBrush"
<< "KritaShape/KisToolDyna"
<< "KritaShape/KisToolMultiBrush"
<< "KritaFill/KisToolFill"
<< "KritaFill/KisToolGradient";
KisSelectionMask *selectionMask = dynamic_cast<KisSelectionMask*>(node.data());
const bool nodeHasVectorAbilities = node->inherits("KisShapeLayer") ||
(selectionMask && selectionMask->selection()->hasShapeSelection());
if (nodeHasVectorAbilities) {
if (pixelTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
}
else {
if (vectorTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
}
}
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
}
void KisNodeManager::nodesUpdated()
{
KisNodeSP node = activeNode();
if (!node) return;
m_d->layerManager.layersUpdated();
m_d->maskManager.masksUpdated();
m_d->view->updateGUI();
m_d->view->selectionManager()->selectionChanged();
{
KisSignalsBlocker b(m_d->showInTimeline);
m_d->showInTimeline->setChecked(node->useInTimeline());
}
}
KisPaintDeviceSP KisNodeManager::activePaintDevice()
{
return m_d->maskManager.activeMask() ?
m_d->maskManager.activeDevice() :
m_d->layerManager.activeDevice();
}
void KisNodeManager::nodeProperties(KisNodeSP node)
{
if ((selectedNodes().size() > 1 && node->inherits("KisLayer")) || node->inherits("KisLayer")) {
m_d->layerManager.layerProperties();
}
else if (node->inherits("KisMask")) {
m_d->maskManager.maskProperties();
}
}
+void KisNodeManager::changeCloneSource()
+{
+ m_d->layerManager.changeCloneSource();
+}
+
qint32 KisNodeManager::convertOpacityToInt(qreal opacity)
{
/**
* Scales opacity from the range 0...100
* to the integer range 0...255
*/
return qMin(255, int(opacity * 2.55 + 0.5));
}
void KisNodeManager::setNodeName(KisNodeSP node, const QString &name)
{
if (!node) return;
if (node->name() == name) return;
m_d->commandsAdapter.setNodeName(node, name);
}
void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity)
{
if (!node) return;
if (node->opacity() == opacity) return;
m_d->commandsAdapter.setOpacity(node, opacity);
}
void KisNodeManager::setNodeCompositeOp(KisNodeSP node,
const KoCompositeOp* compositeOp)
{
if (!node) return;
if (node->compositeOp() == compositeOp) return;
m_d->commandsAdapter.setCompositeOp(node, compositeOp);
}
void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes)
{
if (activeNode) {
slotNonUiActivatedNode(activeNode);
}
if (!selectedNodes.isEmpty()) {
slotSetSelectedNodes(selectedNodes);
}
}
void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes)
{
m_d->selectedNodes = nodes;
emit sigUiNeedChangeSelectedNodes(nodes);
}
KisNodeList KisNodeManager::selectedNodes()
{
return m_d->selectedNodes;
}
KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const
{
return m_d->nodeSelectionAdapter.data();
}
KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const
{
return m_d->nodeInsertionAdapter.data();
}
KisNodeDisplayModeAdapter *KisNodeManager::nodeDisplayModeAdapter() const
{
return m_d->nodeDisplayModeAdapter.data();
}
bool KisNodeManager::isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden)
{
- if (dynamic_cast<KisReferenceImagesLayer *>(node.data())) {
+ if (node && node->isFakeNode()) {
return true;
}
if (isGlobalSelectionHidden && dynamic_cast<KisSelectionMask *>(node.data()) &&
(!node->parent() || !node->parent()->parent())) {
return true;
}
return false;
}
bool KisNodeManager::trySetNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const
{
const KisPaintLayer *paintLayer = dynamic_cast<KisPaintLayer*>(node.data());
if (paintLayer) {
const auto onionSkinOn = KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::onionSkins, true);
if (properties.contains(onionSkinOn)) {
const KisPaintDeviceSP &paintDevice = paintLayer->paintDevice();
if (paintDevice && paintDevice->defaultPixel().opacityU8() == 255) {
m_d->view->showFloatingMessage(i18n("Onion skins require a layer with transparent background."), QIcon());
return false;
}
}
}
KisNodePropertyListCommand::setNodePropertiesNoUndo(node, image, properties);
return true;
}
void KisNodeManager::nodeOpacityChanged(qreal opacity)
{
KisNodeSP node = activeNode();
setNodeOpacity(node, convertOpacityToInt(opacity));
}
void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op)
{
KisNodeSP node = activeNode();
setNodeCompositeOp(node, op);
}
void KisNodeManager::duplicateActiveNode()
{
KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->duplicateNode(selectedNodes());
}
KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName)
{
KisImageWSP image = view->image();
if (!nodeJuggler ||
(nodeJuggler &&
(nodeJuggler->isEnded() ||
!nodeJuggler->canMergeAction(actionName)))) {
nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750);
nodeJuggler->setAutoDelete(true);
}
return nodeJuggler;
}
void KisNodeManager::raiseNode()
{
KUndo2MagicString actionName = kundo2_i18n("Raise Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->raiseNode(selectedNodes());
}
void KisNodeManager::lowerNode()
{
KUndo2MagicString actionName = kundo2_i18n("Lower Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->lowerNode(selectedNodes());
}
void KisNodeManager::removeSingleNode(KisNodeSP node)
{
if (!node || !node->parent()) {
return;
}
KisNodeList nodes;
nodes << node;
removeSelectedNodes(nodes);
}
void KisNodeManager::removeSelectedNodes(KisNodeList nodes)
{
KUndo2MagicString actionName = kundo2_i18n("Remove Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::removeNode()
{
removeSelectedNodes(selectedNodes());
}
void KisNodeManager::mirrorNodeX()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer X");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask X");
}
mirrorNode(node, commandName, Qt::Horizontal, m_d->view->selection());
}
void KisNodeManager::mirrorNodeY()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer Y");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask Y");
}
mirrorNode(node, commandName, Qt::Vertical, m_d->view->selection());
}
void KisNodeManager::mirrorAllNodesX()
{
KisNodeSP node = m_d->view->image()->root();
mirrorNode(node, kundo2_i18n("Mirror All Layers X"),
Qt::Vertical, m_d->view->selection());
}
void KisNodeManager::mirrorAllNodesY()
{
KisNodeSP node = m_d->view->image()->root();
mirrorNode(node, kundo2_i18n("Mirror All Layers Y"),
Qt::Vertical, m_d->view->selection());
}
void KisNodeManager::activateNextNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node = activeNode->nextSibling();
while (node && node->childCount() > 0) {
node = node->firstChild();
}
if (!node && activeNode->parent() && activeNode->parent()->parent()) {
node = activeNode->parent();
}
while(node && isNodeHidden(node, true)) {
node = node->nextSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::activatePreviousNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node;
if (activeNode->childCount() > 0) {
node = activeNode->lastChild();
}
else {
node = activeNode->prevSibling();
}
while (!node && activeNode->parent()) {
node = activeNode->parent()->prevSibling();
activeNode = activeNode->parent();
}
while(node && isNodeHidden(node, true)) {
node = node->prevSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::switchToPreviouslyActiveNode()
{
if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) {
slotNonUiActivatedNode(m_d->previouslyActiveNode);
}
}
void KisNodeManager::mirrorNode(KisNodeSP node,
const KUndo2MagicString& actionName,
Qt::Orientation orientation,
KisSelectionSP selection)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(m_d->view->image(), node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor;
if (selection) {
visitor = new KisMirrorProcessingVisitor(selection, orientation);
} else {
visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation);
}
if (!selection) {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.end();
nodesUpdated();
}
void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity)
{
KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", defaultName));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export));
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename, false);
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
KisImageSP dst = new KisImage(doc->createUndoStore(),
bounds.width(),
bounds.height(),
device->compositionSourceColorSpace(),
defaultName);
dst->setResolution(xRes, yRes);
doc->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity);
paintLayer->paintDevice()->makeCloneFrom(device, bounds);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
if (!doc->exportDocumentSync(url, mimefilter.toLatin1())) {
QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("Could not save the layer. %1", doc->errorMessage().toUtf8().data()),
QMessageBox::Ok);
}
}
void KisNodeManager::saveNodeAsImage()
{
KisNodeSP node = activeNode();
if (!node) {
warnKrita << "BUG: Save Node As Image was called without any node selected";
return;
}
KisImageWSP image = m_d->view->image();
QRect saveRect = image->bounds() | node->exactBounds();
m_d->saveDeviceAsImage(node->projection(),
node->name(),
saveRect,
image->xRes(), image->yRes(),
node->opacity());
}
#include "SvgWriter.h"
void KisNodeManager::saveVectorLayerAsImage()
{
KisShapeLayerSP shapeLayer = qobject_cast<KisShapeLayer*>(activeNode().data());
if (!shapeLayer) {
return;
}
KoFileDialog dialog(m_d->view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18nc("@title:window", "Export to SVG"));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QStringList() << "image/svg+xml", "image/svg+xml");
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
const QSizeF sizeInPx = m_d->view->image()->bounds().size();
const QSizeF sizeInPt(sizeInPx.width() / m_d->view->image()->xRes(),
sizeInPx.height() / m_d->view->image()->yRes());
QList<KoShape*> shapes = shapeLayer->shapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
SvgWriter writer(shapes);
if (!writer.save(filename, sizeInPt, true)) {
QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save to svg: %1", filename));
}
}
void KisNodeManager::slotSplitAlphaIntoMask()
{
KisNodeSP node = activeNode();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice());
KisPaintDeviceSP srcDevice = node->paintDevice();
const KoColorSpace *srcCS = srcDevice->colorSpace();
const QRect processRect =
srcDevice->exactBounds() |
srcDevice->defaultBounds()->bounds();
KisPaintDeviceSP selectionDevice =
new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask"));
KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice);
KisSequentialIterator srcIt(srcDevice, processRect);
KisSequentialIterator dstIt(selectionDevice, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
quint8 *srcPtr = srcIt.rawData();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1);
}
m_d->commandsAdapter.addExtraCommand(transaction.endAndTake());
createNode("KisTransparencyMask", false, selectionDevice);
m_d->commandsAdapter.endMacro();
}
void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers)
{
KisNodeSP node = q->activeNode();
KisNodeSP parentNode = node->parent();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask"));
if (writeToLayers && !parentNode->hasEditablePaintDevice()) {
QMessageBox::information(view->mainWindow(),
i18nc("@title:window", "Layer %1 is not editable", parentNode->name()),
i18n("Cannot write alpha channel of "
"the parent layer \"%1\".\n"
"The operation will be cancelled.", parentNode->name()));
return;
}
KisPaintDeviceSP dstDevice;
if (writeToLayers) {
KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice());
dstDevice = parentNode->paintDevice();
} else {
KisPaintDeviceSP copyDevice = parentNode->paintDevice();
if (!copyDevice) {
copyDevice = parentNode->original();
}
dstDevice = new KisPaintDevice(*copyDevice);
}
const KoColorSpace *dstCS = dstDevice->colorSpace();
KisPaintDeviceSP selectionDevice = node->paintDevice();
KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1);
const QRect processRect =
selectionDevice->exactBounds() |
dstDevice->exactBounds() |
selectionDevice->defaultBounds()->bounds();
QScopedPointer<KisTransaction> transaction;
if (writeToLayers) {
commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer"));
transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice));
}
KisSequentialIterator srcIt(selectionDevice, processRect);
KisSequentialIterator dstIt(dstDevice, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
quint8 *alpha8Ptr = srcIt.rawData();
quint8 *dstPtr = dstIt.rawData();
dstCS->setOpacity(dstPtr, *alpha8Ptr, 1);
}
if (writeToLayers) {
commandsAdapter.addExtraCommand(transaction->endAndTake());
commandsAdapter.removeNode(node);
commandsAdapter.endMacro();
} else {
KisImageWSP image = view->image();
QRect saveRect = image->bounds();
saveDeviceAsImage(dstDevice, parentNode->name(),
saveRect,
image->xRes(), image->yRes(),
OPACITY_OPAQUE_U8);
}
}
void KisNodeManager::slotSplitAlphaWrite()
{
m_d->mergeTransparencyMaskAsAlpha(true);
}
void KisNodeManager::slotSplitAlphaSaveMerged()
{
m_d->mergeTransparencyMaskAsAlpha(false);
}
void KisNodeManager::toggleLock()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
bool isLocked = active->userLocked();
for (auto &node : nodes) {
node->setUserLocked(!isLocked);
}
}
void KisNodeManager::toggleVisibility()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
bool isVisible = active->visible();
for (auto &node : nodes) {
node->setVisible(!isVisible);
node->setDirty();
}
}
void KisNodeManager::toggleAlphaLock()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
auto layer = qobject_cast<KisPaintLayer*>(active.data());
if (!layer) {
return;
}
bool isAlphaLocked = layer->alphaLocked();
for (auto &node : nodes) {
auto layer = qobject_cast<KisPaintLayer*>(node.data());
if (layer) {
layer->setAlphaLocked(!isAlphaLocked);
}
}
}
void KisNodeManager::toggleInheritAlpha()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
auto layer = qobject_cast<KisLayer*>(active.data());
if (!layer) {
return;
}
bool isAlphaDisabled = layer->alphaChannelDisabled();
for (auto &node : nodes) {
auto layer = qobject_cast<KisLayer*>(node.data());
if (layer) {
layer->disableAlphaChannel(!isAlphaDisabled);
node->setDirty();
}
}
}
void KisNodeManager::cutLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
if (nodes.isEmpty()) return;
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false);
KUndo2MagicString actionName = kundo2_i18n("Cut Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::copyLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true);
}
void KisNodeManager::pasteLayersFromClipboard()
{
const QMimeData *data = KisClipboard::instance()->layersMimeData();
if (!data) return;
KisNodeSP activeNode = this->activeNode();
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
KisDummiesFacadeBase *dummiesFacade = dynamic_cast<KisDummiesFacadeBase*>(m_d->imageView->document()->shapeController());
Q_ASSERT(dummiesFacade);
const bool copyNode = false;
KisImageSP image = m_d->view->image();
KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode);
KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0;
KisMimeData::insertMimeLayers(data,
image,
shapeController,
parentDummy,
aboveThisDummy,
copyNode,
nodeInsertionAdapter());
}
void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild)
{
KisNodeSP active = activeNode();
if (!active) return;
KisImageSP image = m_d->view->image();
QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName();
KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8);
KisNodeList nodes1;
nodes1 << group;
KisNodeList nodes2;
nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes());
KisLayerUtils::filterMergableNodes(nodes2);
if (nodes2.size() == 0) return;
if (KisLayerUtils::checkIsChildOf(active, nodes2)) {
active = nodes2.first();
}
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
juggler->addNode(nodes1, parent, aboveThis);
juggler->moveNode(nodes2, group, 0);
*newGroup = group;
*newLastChild = nodes2.last();
}
void KisNodeManager::createQuickGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
createQuickGroupImpl(juggler, "", &parent, &above);
}
void KisNodeManager::createQuickClippingGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
KisImageSP image = m_d->view->image();
createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above);
KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace());
maskLayer->disableAlphaChannel(true);
juggler->addNode(KisNodeList() << maskLayer, parent, above);
}
void KisNodeManager::quickUngroup()
{
KisNodeSP active = activeNode();
if (!active) return;
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup");
if (parent && dynamic_cast<KisGroupLayer*>(active.data())) {
KisNodeList nodes = active->childNodes(QStringList(), KoProperties());
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, active);
juggler->removeNode(KisNodeList() << active);
} else if (parent && parent->parent()) {
KisNodeSP grandParent = parent->parent();
KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties());
KisNodeList allSelectedNodes = selectedNodes();
const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes);
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(allSelectedNodes, grandParent, parent);
if (removeParent) {
juggler->removeNode(KisNodeList() << parent);
}
}
}
void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps)
{
KisImageSP image = m_d->view->image();
KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true);
KisNodeList selectedNodes = this->selectedNodes();
if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) {
nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true);
}
if (!nodes.isEmpty()) {
slotImageRequestNodeReselection(nodes.last(), nodes);
}
}
void KisNodeManager::selectAllNodes()
{
KoProperties props;
selectLayersImpl(props, props);
}
void KisNodeManager::selectVisibleNodes()
{
KoProperties props;
props.setProperty("visible", true);
KoProperties invertedProps;
invertedProps.setProperty("visible", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectLockedNodes()
{
KoProperties props;
props.setProperty("locked", true);
KoProperties invertedProps;
invertedProps.setProperty("locked", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectInvisibleNodes()
{
KoProperties props;
props.setProperty("visible", false);
KoProperties invertedProps;
invertedProps.setProperty("visible", true);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectUnlockedNodes()
{
KoProperties props;
props.setProperty("locked", false);
KoProperties invertedProps;
invertedProps.setProperty("locked", true);
selectLayersImpl(props, invertedProps);
}
diff --git a/libs/ui/kis_node_manager.h b/libs/ui/kis_node_manager.h
index 9848bbdf4f..2686269ce7 100644
--- a/libs/ui/kis_node_manager.h
+++ b/libs/ui/kis_node_manager.h
@@ -1,269 +1,271 @@
/*
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_NODE_MANAGER
#define KIS_NODE_MANAGER
#include <QObject>
#include <QList>
#include "kis_types.h"
#include "kis_base_node.h"
#include <kritaui_export.h>
class KActionCollection;
class KoCompositeOp;
class KoColorSpace;
class KUndo2MagicString;
class KisFilterStrategy;
class KisViewManager;
class KisActionManager;
class KisView;
class KisNodeSelectionAdapter;
class KisNodeInsertionAdapter;
class KisNodeDisplayModeAdapter;
class KisNodeJugglerCompressed;
class KoProperties;
/**
* The node manager passes requests for new layers or masks on to the mask and layer
* managers.
*/
class KRITAUI_EXPORT KisNodeManager : public QObject
{
Q_OBJECT
public:
KisNodeManager(KisViewManager * view);
~KisNodeManager() override;
void setView(QPointer<KisView>imageView);
Q_SIGNALS:
/// emitted whenever a node is selected.
void sigNodeActivated(KisNodeSP node);
/// emitted whenever a different layer is selected.
void sigLayerActivated(KisLayerSP layer);
/// for the layer box: this sets the current node in the layerbox
/// without telling the node manager that the node is activated,
/// preventing loops (I think...)
void sigUiNeedChangeActiveNode(KisNodeSP node);
void sigUiNeedChangeSelectedNodes(const KisNodeList &nodes);
public:
void setup(KActionCollection * collection, KisActionManager* actionManager);
void updateGUI();
/// Convenience function to get the active layer or mask
KisNodeSP activeNode();
/// convenience function to get the active layer. If a mask is
/// active, it's parent layer is the active layer.
KisLayerSP activeLayer();
/// Get the paint device the user wants to paint on now
KisPaintDeviceSP activePaintDevice();
/**
* @return the active color space used for composition, meaning the color space
* of the active mask, or the color space of the parent of the active layer
*/
const KoColorSpace* activeColorSpace();
/**
* Sets the name for the node in a universal way (masks/layers)
*/
void setNodeName(KisNodeSP node, const QString &name);
/**
* Sets opacity for the node in a universal way (masks/layers)
*/
void setNodeOpacity(KisNodeSP node, qint32 opacity);
/**
* Sets compositeOp for the node in a universal way (masks/layers)
*/
void setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp);
KisNodeList selectedNodes();
KisNodeSelectionAdapter* nodeSelectionAdapter() const;
KisNodeInsertionAdapter* nodeInsertionAdapter() const;
KisNodeDisplayModeAdapter* nodeDisplayModeAdapter() const;
static bool isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden);
bool trySetNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const;
public Q_SLOTS:
/**
* Explicitly activates \p node
* The UI will be noticed that active node has been changed.
* Both sigNodeActivated and sigUiNeedChangeActiveNode are emitted.
*
* WARNING: normally you needn't call this method manually. It is
* automatically called when a node is added to the graph. If you
* have some special cases when you need to activate a node, consider
* adding them to KisDummiesFacadeBase instead. Calling this method
* directly should be the last resort.
*
* \see slotUiActivatedNode for comparison
*/
void slotNonUiActivatedNode(KisNodeSP node);
/**
* Activates \p node.
* All non-ui listeners are notified with sigNodeActivated,
* sigUiNeedChangeActiveNode is *not* emitted.
*
* \see activateNode
*/
void slotUiActivatedNode(KisNodeSP node);
/**
* Adds a list of nodes without searching appropriate position for
* it. You *must* ensure that the nodes are allowed to be added
* to the parent, otherwise you'll get an assert.
*/
void addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis);
/**
* Moves a list of nodes without searching appropriate position
* for it. You *must* ensure that the nodes are allowed to be
* added to the parent, otherwise you'll get an assert.
*/
void moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis);
/**
* Copies a list of nodes without searching appropriate position
* for it. You *must* ensure that the nodes are allowed to be
* added to the parent, otherwise you'll get an assert.
*/
void copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis);
/**
* Create new layer from actually visible
*/
void createFromVisible();
void slotShowHideTimeline(bool value);
void toggleIsolateActiveNode();
void toggleIsolateMode(bool checked);
void slotUpdateIsolateModeActionImageStatusChange();
void slotUpdateIsolateModeAction();
void slotTryRestartIsolatedMode();
void moveNodeAt(KisNodeSP node, KisNodeSP parent, int index);
KisNodeSP createNode(const QString& nodeType, bool quiet = false, KisPaintDeviceSP copyFrom = 0);
void convertNode(const QString &nodeType);
void nodesUpdated();
void nodeProperties(KisNodeSP node);
+ /// pop up a window for changing the source of the selected Clone Layers
+ void changeCloneSource();
void nodeOpacityChanged(qreal opacity);
void nodeCompositeOpChanged(const KoCompositeOp* op);
void duplicateActiveNode();
void removeNode();
void mirrorNodeX();
void mirrorNodeY();
void mirrorAllNodesX();
void mirrorAllNodesY();
void mirrorNode(KisNodeSP node, const KUndo2MagicString& commandName, Qt::Orientation orientation, KisSelectionSP selection);
void activateNextNode();
void activatePreviousNode();
void switchToPreviouslyActiveNode();
/**
* move the active node up the nodestack.
*/
void raiseNode();
/**
* move the active node down the nodestack
*/
void lowerNode();
void saveNodeAsImage();
void saveVectorLayerAsImage();
void slotSplitAlphaIntoMask();
void slotSplitAlphaWrite();
void slotSplitAlphaSaveMerged();
void toggleLock();
void toggleVisibility();
void toggleAlphaLock();
void toggleInheritAlpha();
/**
* @brief slotSetSelectedNodes set the list of nodes selected in the layerbox. Selected nodes are not necessarily active nodes.
* @param nodes the selected nodes
*/
void slotSetSelectedNodes(const KisNodeList &nodes);
void slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes);
void cutLayersToClipboard();
void copyLayersToClipboard();
void pasteLayersFromClipboard();
void createQuickGroup();
void createQuickClippingGroup();
void quickUngroup();
void selectAllNodes();
void selectVisibleNodes();
void selectLockedNodes();
void selectInvisibleNodes();
void selectUnlockedNodes();
public:
void removeSingleNode(KisNodeSP node);
KisLayerSP createPaintLayer();
private:
/**
* Scales opacity from the range 0...1
* to the integer range 0...255
*/
qint32 convertOpacityToInt(qreal opacity);
void removeSelectedNodes(KisNodeList selectedNodes);
void slotSomethingActivatedNodeImpl(KisNodeSP node);
void createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild);
void selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps);
struct Private;
Private * const m_d;
};
#endif
diff --git a/libs/ui/kis_node_model.cpp b/libs/ui/kis_node_model.cpp
index 8d75260a2c..161d9dbdb8 100644
--- a/libs/ui/kis_node_model.cpp
+++ b/libs/ui/kis_node_model.cpp
@@ -1,740 +1,741 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_model.h"
#include <iostream>
#include <QMimeData>
#include <QBuffer>
#include <QPointer>
#include <KoColorSpaceConstants.h>
#include <klocalizedstring.h>
#include "kis_mimedata.h"
#include <kis_debug.h>
#include <kis_node.h>
#include <kis_node_progress_proxy.h>
#include <kis_image.h>
#include <kis_selection.h>
#include <kis_selection_mask.h>
#include <kis_undo_adapter.h>
#include <commands/kis_node_property_list_command.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_projection_leaf.h>
#include <kis_shape_controller.h>
#include "kis_dummies_facade_base.h"
#include "kis_node_dummies_graph.h"
#include "kis_model_index_converter.h"
#include "kis_model_index_converter_show_all.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_node_manager.h"
#include <KisSelectionActionsAdapter.h>
#include <KisNodeDisplayModeAdapter.h>
#include "kis_config.h"
#include "kis_config_notifier.h"
-#include <QTimer>
#include "kis_signal_auto_connection.h"
+#include "kis_signal_compressor.h"
struct KisNodeModel::Private
{
+ Private() : updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE) {}
+
KisImageWSP image;
KisShapeController *shapeController = 0;
KisNodeSelectionAdapter *nodeSelectionAdapter = 0;
KisNodeInsertionAdapter *nodeInsertionAdapter = 0;
KisSelectionActionsAdapter *selectionActionsAdapter = 0;
KisNodeDisplayModeAdapter *nodeDisplayModeAdapter = 0;
KisNodeManager *nodeManager = 0;
KisSignalAutoConnectionsStore nodeDisplayModeAdapterConnections;
QList<KisNodeDummy*> updateQueue;
- QTimer updateTimer;
+ KisSignalCompressor updateCompressor;
KisModelIndexConverterBase *indexConverter = 0;
QPointer<KisDummiesFacadeBase> dummiesFacade = 0;
bool needFinishRemoveRows = false;
bool needFinishInsertRows = false;
bool showRootLayer = false;
bool showGlobalSelection = false;
QPersistentModelIndex activeNodeIndex;
QPointer<KisNodeDummy> parentOfRemovedNode = 0;
QSet<quintptr> dropEnabled;
};
KisNodeModel::KisNodeModel(QObject * parent)
: QAbstractItemModel(parent)
, m_d(new Private)
{
- m_d->updateTimer.setSingleShot(true);
- connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue()));
+ connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(processUpdateQueue()));
}
KisNodeModel::~KisNodeModel()
{
delete m_d->indexConverter;
delete m_d;
}
KisNodeSP KisNodeModel::nodeFromIndex(const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index);
if (dummy) {
return dummy->node();
}
return 0;
}
QModelIndex KisNodeModel::indexFromNode(KisNodeSP node) const
{
KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node);
if(dummy)
return m_d->indexConverter->indexFromDummy(dummy);
return QModelIndex();
}
bool KisNodeModel::belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade)
{
KisNodeSP isolatedRoot = image->isolatedModeRoot();
if (!isolatedRoot) return true;
KisNodeDummy *isolatedRootDummy =
dummiesFacade->dummyForNode(isolatedRoot);
KisNodeDummy *dummy =
dummiesFacade->dummyForNode(node);
while (dummy) {
if (dummy == isolatedRootDummy) {
return true;
}
dummy = dummy->parent();
}
return false;
}
bool KisNodeModel::belongsToIsolatedGroup(KisNodeSP node) const
{
return belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade);
}
void KisNodeModel::resetIndexConverter()
{
delete m_d->indexConverter;
m_d->indexConverter = 0;
if(m_d->dummiesFacade) {
m_d->indexConverter = createIndexConverter();
}
}
KisModelIndexConverterBase *KisNodeModel::createIndexConverter()
{
if(m_d->showRootLayer) {
return new KisModelIndexConverterShowAll(m_d->dummiesFacade, this);
} else {
return new KisModelIndexConverter(m_d->dummiesFacade, this, m_d->showGlobalSelection);
}
}
void KisNodeModel::regenerateItems(KisNodeDummy *dummy)
{
const QModelIndex &index = m_d->indexConverter->indexFromDummy(dummy);
emit dataChanged(index, index);
dummy = dummy->firstChild();
while (dummy) {
regenerateItems(dummy);
dummy = dummy->nextSibling();
}
}
void KisNodeModel::slotIsolatedModeChanged()
{
regenerateItems(m_d->dummiesFacade->rootDummy());
}
bool KisNodeModel::showGlobalSelection() const
{
return m_d->nodeDisplayModeAdapter ?
m_d->nodeDisplayModeAdapter->showGlobalSelectionMask() :
false;
}
void KisNodeModel::setShowGlobalSelection(bool value)
{
if (m_d->nodeDisplayModeAdapter) {
m_d->nodeDisplayModeAdapter->setShowGlobalSelectionMask(value);
}
}
void KisNodeModel::slotNodeDisplayModeChanged(bool showRootNode, bool showGlobalSelectionMask)
{
const bool oldShowRootLayer = m_d->showRootLayer;
const bool oldShowGlobalSelection = m_d->showGlobalSelection;
m_d->showRootLayer = showRootNode;
m_d->showGlobalSelection = showGlobalSelectionMask;
if (m_d->showRootLayer != oldShowRootLayer || m_d->showGlobalSelection != oldShowGlobalSelection) {
resetIndexConverter();
beginResetModel();
endResetModel();
}
}
void KisNodeModel::progressPercentageChanged(int, const KisNodeSP node)
{
if(!m_d->dummiesFacade) return;
// Need to check here as the node might already be removed, but there might
// still be some signals arriving from another thread
if (m_d->dummiesFacade->hasDummyForNode(node)) {
QModelIndex index = indexFromNode(node);
emit dataChanged(index, index);
}
}
KisModelIndexConverterBase * KisNodeModel::indexConverter() const
{
return m_d->indexConverter;
}
KisDummiesFacadeBase *KisNodeModel::dummiesFacade() const
{
return m_d->dummiesFacade;
}
void KisNodeModel::connectDummy(KisNodeDummy *dummy, bool needConnect)
{
KisNodeSP node = dummy->node();
if (!node) {
qWarning() << "Dummy node has no node!" << dummy << dummy->node();
return;
}
KisNodeProgressProxy *progressProxy = node->nodeProgressProxy();
if(progressProxy) {
if(needConnect) {
connect(progressProxy, SIGNAL(percentageChanged(int,KisNodeSP)),
SLOT(progressPercentageChanged(int,KisNodeSP)));
} else {
progressProxy->disconnect(this);
}
}
}
void KisNodeModel::connectDummies(KisNodeDummy *dummy, bool needConnect)
{
connectDummy(dummy, needConnect);
dummy = dummy->firstChild();
while(dummy) {
connectDummies(dummy, needConnect);
dummy = dummy->nextSibling();
}
}
void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade,
KisImageWSP image,
KisShapeController *shapeController,
KisSelectionActionsAdapter *selectionActionsAdapter,
KisNodeManager *nodeManager)
{
QPointer<KisDummiesFacadeBase> oldDummiesFacade(m_d->dummiesFacade);
KisShapeController *oldShapeController = m_d->shapeController;
m_d->shapeController = shapeController;
m_d->nodeManager = nodeManager;
m_d->nodeSelectionAdapter = nodeManager ? nodeManager->nodeSelectionAdapter() : nullptr;
m_d->nodeInsertionAdapter = nodeManager ? nodeManager->nodeInsertionAdapter() : nullptr;
m_d->selectionActionsAdapter = selectionActionsAdapter;
m_d->nodeDisplayModeAdapterConnections.clear();
m_d->nodeDisplayModeAdapter = nodeManager ? nodeManager->nodeDisplayModeAdapter() : nullptr;
if (m_d->nodeDisplayModeAdapter) {
m_d->nodeDisplayModeAdapterConnections.addConnection(
m_d->nodeDisplayModeAdapter, SIGNAL(sigNodeDisplayModeChanged(bool,bool)),
this, SLOT(slotNodeDisplayModeChanged(bool,bool)));
// cold initialization
m_d->showGlobalSelection = m_d->nodeDisplayModeAdapter->showGlobalSelectionMask();
m_d->showRootLayer = m_d->showRootLayer;
}
if (oldDummiesFacade && m_d->image) {
m_d->image->disconnect(this);
oldDummiesFacade->disconnect(this);
connectDummies(m_d->dummiesFacade->rootDummy(), false);
}
m_d->image = image;
m_d->dummiesFacade = dummiesFacade;
m_d->parentOfRemovedNode = 0;
resetIndexConverter();
if (m_d->dummiesFacade) {
KisNodeDummy *rootDummy = m_d->dummiesFacade->rootDummy();
if (rootDummy) {
connectDummies(rootDummy, true);
}
connect(m_d->dummiesFacade, SIGNAL(sigBeginInsertDummy(KisNodeDummy*,int,QString)),
SLOT(slotBeginInsertDummy(KisNodeDummy*,int,QString)));
connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)),
SLOT(slotEndInsertDummy(KisNodeDummy*)));
connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)),
SLOT(slotBeginRemoveDummy(KisNodeDummy*)));
connect(m_d->dummiesFacade, SIGNAL(sigEndRemoveDummy()),
SLOT(slotEndRemoveDummy()));
connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)),
SLOT(slotDummyChanged(KisNodeDummy*)));
if (m_d->image.isValid()) {
connect(m_d->image, SIGNAL(sigIsolatedModeChanged()), SLOT(slotIsolatedModeChanged()));
}
}
if (m_d->dummiesFacade != oldDummiesFacade || m_d->shapeController != oldShapeController) {
beginResetModel();
endResetModel();
}
}
void KisNodeModel::slotBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType)
{
int row = 0;
QModelIndex parentIndex;
bool willAdd =
m_d->indexConverter->indexFromAddedDummy(parent, index,
metaObjectType,
parentIndex, row);
if(willAdd) {
beginInsertRows(parentIndex, row, row);
m_d->needFinishInsertRows = true;
}
}
void KisNodeModel::slotEndInsertDummy(KisNodeDummy *dummy)
{
if(m_d->needFinishInsertRows) {
connectDummy(dummy, true);
endInsertRows();
m_d->needFinishInsertRows = false;
}
}
void KisNodeModel::slotBeginRemoveDummy(KisNodeDummy *dummy)
{
if (!dummy) return;
// FIXME: is it really what we want?
- m_d->updateTimer.stop();
+ m_d->updateCompressor.stop();
m_d->updateQueue.clear();
m_d->parentOfRemovedNode = dummy->parent();
QModelIndex parentIndex;
if (m_d->parentOfRemovedNode) {
parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode);
}
QModelIndex itemIndex = m_d->indexConverter->indexFromDummy(dummy);
if (itemIndex.isValid()) {
connectDummy(dummy, false);
beginRemoveRows(parentIndex, itemIndex.row(), itemIndex.row());
m_d->needFinishRemoveRows = true;
}
}
void KisNodeModel::slotEndRemoveDummy()
{
if(m_d->needFinishRemoveRows) {
endRemoveRows();
m_d->needFinishRemoveRows = false;
}
}
void KisNodeModel::slotDummyChanged(KisNodeDummy *dummy)
{
if (!m_d->updateQueue.contains(dummy)) {
m_d->updateQueue.append(dummy);
}
- m_d->updateTimer.start(1000);
+ m_d->updateCompressor.start();
}
void addChangedIndex(const QModelIndex &idx, QSet<QModelIndex> *indexes)
{
if (!idx.isValid() || indexes->contains(idx)) return;
indexes->insert(idx);
const int rowCount = idx.model()->rowCount(idx);
for (int i = 0; i < rowCount; i++) {
addChangedIndex(idx.model()->index(i, 0, idx), indexes);
}
}
void KisNodeModel::processUpdateQueue()
{
QSet<QModelIndex> indexes;
Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) {
QModelIndex index = m_d->indexConverter->indexFromDummy(dummy);
addChangedIndex(index, &indexes);
}
Q_FOREACH (const QModelIndex &index, indexes) {
emit dataChanged(index, index);
}
m_d->updateQueue.clear();
}
QModelIndex KisNodeModel::index(int row, int col, const QModelIndex &parent) const
{
if(!m_d->dummiesFacade || !hasIndex(row, col, parent)) return QModelIndex();
QModelIndex itemIndex;
KisNodeDummy *dummy = m_d->indexConverter->dummyFromRow(row, parent);
if(dummy) {
itemIndex = m_d->indexConverter->indexFromDummy(dummy);
}
return itemIndex;
}
int KisNodeModel::rowCount(const QModelIndex &parent) const
{
if(!m_d->dummiesFacade) return 0;
return m_d->indexConverter->rowCount(parent);
}
int KisNodeModel::columnCount(const QModelIndex&) const
{
return 1;
}
QModelIndex KisNodeModel::parent(const QModelIndex &index) const
{
if(!m_d->dummiesFacade || !index.isValid()) return QModelIndex();
KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index);
KisNodeDummy *parentDummy = dummy->parent();
QModelIndex parentIndex;
if(parentDummy) {
parentIndex = m_d->indexConverter->indexFromDummy(parentDummy);
}
return parentIndex;
}
QVariant KisNodeModel::data(const QModelIndex &index, int role) const
{
if (!m_d->dummiesFacade || !index.isValid() || !m_d->image.isValid()) return QVariant();
KisNodeSP node = nodeFromIndex(index);
switch (role) {
case Qt::DisplayRole: return node->name();
case Qt::DecorationRole: return node->icon();
case Qt::EditRole: return node->name();
case Qt::SizeHintRole: return m_d->image->size(); // FIXME
case Qt::TextColorRole:
return belongsToIsolatedGroup(node) &&
!node->projectionLeaf()->isDroppedMask() ? QVariant() : QVariant(QColor(Qt::gray));
case Qt::FontRole: {
QFont baseFont;
if (node->projectionLeaf()->isDroppedMask()) {
baseFont.setStrikeOut(true);
}
if (m_d->activeNodeIndex == index) {
baseFont.setBold(true);
}
return baseFont;
}
case KisNodeModel::PropertiesRole: return QVariant::fromValue(node->sectionModelProperties());
case KisNodeModel::AspectRatioRole: return double(m_d->image->width()) / m_d->image->height();
case KisNodeModel::ProgressRole: {
KisNodeProgressProxy *proxy = node->nodeProgressProxy();
return proxy ? proxy->percentage() : -1;
}
case KisNodeModel::ActiveRole: {
return m_d->activeNodeIndex == index;
}
case KisNodeModel::ShouldGrayOutRole: {
return !node->visible(true);
}
case KisNodeModel::ColorLabelIndexRole: {
return node->colorLabelIndex();
}
default:
if (role >= int(KisNodeModel::BeginThumbnailRole) && belongsToIsolatedGroup(node)) {
const int maxSize = role - int(KisNodeModel::BeginThumbnailRole);
QSize size = node->extent().size();
size.scale(maxSize, maxSize, Qt::KeepAspectRatio);
if (size.width() == 0 || size.height() == 0) {
// No thumbnail can be shown if there isn't width or height...
return QVariant();
}
return node->createThumbnail(size.width(), size.height());
} else {
return QVariant();
}
}
return QVariant();
}
Qt::ItemFlags KisNodeModel::flags(const QModelIndex &index) const
{
if(!m_d->dummiesFacade || !index.isValid()) return Qt::ItemIsDropEnabled;
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable;
if (m_d->dropEnabled.contains(index.internalId())) {
flags |= Qt::ItemIsDropEnabled;
}
return flags;
}
bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == KisNodeModel::DropEnabled) {
const QMimeData *mimeData = static_cast<const QMimeData*>(value.value<void*>());
setDropEnabled(mimeData);
return true;
}
if (role == KisNodeModel::ActiveRole || role == KisNodeModel::AlternateActiveRole) {
QModelIndex parentIndex;
if (!index.isValid() && m_d->parentOfRemovedNode && m_d->dummiesFacade && m_d->indexConverter) {
parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode);
m_d->parentOfRemovedNode = 0;
}
KisNodeSP activatedNode;
if (index.isValid() && value.toBool()) {
activatedNode = nodeFromIndex(index);
}
else if (parentIndex.isValid() && value.toBool()) {
activatedNode = nodeFromIndex(parentIndex);
}
else {
activatedNode = 0;
}
QModelIndex newActiveNode = activatedNode ? indexFromNode(activatedNode) : QModelIndex();
if (role == KisNodeModel::ActiveRole && value.toBool() &&
m_d->activeNodeIndex == newActiveNode) {
return true;
}
m_d->activeNodeIndex = newActiveNode;
if (m_d->nodeSelectionAdapter) {
m_d->nodeSelectionAdapter->setActiveNode(activatedNode);
}
if (role == KisNodeModel::AlternateActiveRole) {
emit toggleIsolateActiveNode();
}
emit dataChanged(index, index);
return true;
}
if(!m_d->dummiesFacade || !index.isValid()) return false;
bool result = true;
bool shouldUpdate = true;
bool shouldUpdateRecursively = false;
KisNodeSP node = nodeFromIndex(index);
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
m_d->nodeManager->setNodeName(node, value.toString());
break;
case KisNodeModel::PropertiesRole:
{
// don't record undo/redo for visibility, locked or alpha locked changes
KisBaseNode::PropertyList proplist = value.value<KisBaseNode::PropertyList>();
m_d->nodeManager->trySetNodeProperties(node, m_d->image, proplist);
shouldUpdateRecursively = true;
break;
}
case KisNodeModel::SelectOpaqueRole:
if (node && m_d->selectionActionsAdapter) {
SelectionAction action = SelectionAction(value.toInt());
m_d->selectionActionsAdapter->selectOpaqueOnNode(node, action);
}
shouldUpdate = false;
break;
default:
result = false;
}
if (result && shouldUpdate) {
if (shouldUpdateRecursively) {
QSet<QModelIndex> indexes;
addChangedIndex(index, &indexes);
Q_FOREACH (const QModelIndex &index, indexes) {
emit dataChanged(index, index);
}
} else {
emit dataChanged(index, index);
}
}
return result;
}
Qt::DropActions KisNodeModel::supportedDragActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::DropActions KisNodeModel::supportedDropActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
bool KisNodeModel::hasDummiesFacade()
{
return m_d->dummiesFacade != 0;
}
QStringList KisNodeModel::mimeTypes() const
{
QStringList types;
types << QLatin1String("application/x-krita-node");
types << QLatin1String("application/x-qt-image");
return types;
}
QMimeData * KisNodeModel::mimeData(const QModelIndexList &indexes) const
{
KisNodeList nodes;
Q_FOREACH (const QModelIndex &idx, indexes) {
nodes << nodeFromIndex(idx);
}
return KisMimeData::mimeForLayers(nodes, m_d->image);
}
bool KisNodeModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
Q_UNUSED(column);
bool copyNode = (action == Qt::CopyAction);
KisNodeDummy *parentDummy = 0;
KisNodeDummy *aboveThisDummy = 0;
parentDummy = parent.isValid() ?
m_d->indexConverter->dummyFromIndex(parent) :
m_d->dummiesFacade->rootDummy();
if (row == -1) {
aboveThisDummy = parent.isValid() ? parentDummy->lastChild() : 0;
}
else {
aboveThisDummy = row < m_d->indexConverter->rowCount(parent) ? m_d->indexConverter->dummyFromRow(row, parent) : 0;
}
return KisMimeData::insertMimeLayers(data,
m_d->image,
m_d->shapeController,
parentDummy,
aboveThisDummy,
copyNode,
m_d->nodeInsertionAdapter);
}
bool KisNodeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const {
if (parent.isValid()) {
// drop occurred on an item. always return true as returning false will mess up
// QT5's drag handling (see KisNodeModel::setDropEnabled).
return true;
} else {
return QAbstractItemModel::canDropMimeData(data, action, row, column, parent);
}
}
void KisNodeModel::setDropEnabled(const QMimeData *data) {
// what happens here should really happen in KisNodeModel::canDropMimeData(), but QT5
// will mess up if an item's Qt::ItemIsDropEnabled does not match what is returned by
// canDropMimeData; specifically, if we set the flag, but decide in canDropMimeData()
// later on that an "onto" drag is not allowed, QT will display an drop indicator for
// insertion, but not perform any drop when the mouse is released.
// the only robust implementation seems to set all flags correctly, which is done here.
bool copyNode = false;
KisNodeList nodes = KisMimeData::loadNodesFast(data, m_d->image, m_d->shapeController, copyNode);
m_d->dropEnabled.clear();
updateDropEnabled(nodes);
}
void KisNodeModel::updateDropEnabled(const QList<KisNodeSP> &nodes, QModelIndex parent) {
for (int r = 0; r < rowCount(parent); r++) {
QModelIndex idx = index(r, 0, parent);
KisNodeSP target = nodeFromIndex(idx);
bool dropEnabled = true;
Q_FOREACH (const KisNodeSP &node, nodes) {
if (!target->allowAsChild(node)) {
dropEnabled = false;
break;
}
}
if (dropEnabled) {
m_d->dropEnabled.insert(idx.internalId());
}
emit dataChanged(idx, idx); // indicate to QT that flags() have changed
if (hasChildren(idx)) {
updateDropEnabled(nodes, idx);
}
}
}
diff --git a/libs/ui/kis_painting_assistant.cc b/libs/ui/kis_painting_assistant.cc
index 584f43196e..6ab2106630 100644
--- a/libs/ui/kis_painting_assistant.cc
+++ b/libs/ui/kis_painting_assistant.cc
@@ -1,822 +1,896 @@
/*
* Copyright (c) 2008,2011 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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 <QXmlStreamReader>
#include "kis_painting_assistant.h"
#include "kis_coordinates_converter.h"
#include "kis_debug.h"
#include "kis_dom_utils.h"
#include <kis_canvas2.h>
#include "kis_tool.h"
#include "kis_config.h"
#include <KoStore.h>
#include <QGlobalStatic>
#include <QPen>
#include <QPainter>
#include <QPixmapCache>
#include <QDomElement>
#include <QDomDocument>
Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance)
struct KisPaintingAssistantHandle::Private {
QList<KisPaintingAssistant*> assistants;
char handle_type;
};
KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private)
{
}
KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private)
{
}
KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs)
: QPointF(rhs)
, KisShared()
, d(new Private)
{
+ dbgUI << "KisPaintingAssistantHandle ctor";
}
KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt)
{
setX(pt.x());
setY(pt.y());
return *this;
}
void KisPaintingAssistantHandle::setType(char type)
{
d->handle_type = type;
}
-char KisPaintingAssistantHandle::handleType()
+char KisPaintingAssistantHandle::handleType() const
{
return d->handle_type;
}
KisPaintingAssistantHandle::~KisPaintingAssistantHandle()
{
Q_ASSERT(d->assistants.empty());
delete d;
}
void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant)
{
Q_ASSERT(!d->assistants.contains(assistant));
d->assistants.append(assistant);
}
void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant)
{
d->assistants.removeOne(assistant);
Q_ASSERT(!d->assistants.contains(assistant));
}
-bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant)
+bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant) const
{
return d->assistants.contains(assistant);
}
void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle)
{
if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) {
return;
}
Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) {
if (!assistant->handles().contains(this)) {
assistant->replaceHandle(handle, this);
}
}
}
void KisPaintingAssistantHandle::uncache()
{
Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) {
assistant->uncache();
}
}
struct KisPaintingAssistant::Private {
- QString id;
- QString name;
- bool isSnappingActive;
- bool outlineVisible;
- QList<KisPaintingAssistantHandleSP> handles,sideHandles;
- QPixmapCache::Key cached;
- QRect cachedRect; // relative to boundingRect().topLeft()
+ Private();
+ explicit Private(const Private &rhs);
+ KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q);
+ QList<KisPaintingAssistantHandleSP> handles, sideHandles;
KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle;
- KisCanvas2* m_canvas = 0;
-
- struct TranslationInvariantTransform {
- qreal m11, m12, m21, m22;
- TranslationInvariantTransform() { }
- TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { }
- bool operator==(const TranslationInvariantTransform& b) {
- return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22;
- }
- } cachedTransform;
+ // share everything except handles between the clones
+ struct SharedData {
+ QString id;
+ QString name;
+ bool isSnappingActive;
+ bool outlineVisible;
+ KisCanvas2* m_canvas = 0;
+
+ QPixmapCache::Key cached;
+ QRect cachedRect; // relative to boundingRect().topLeft()
+
+ struct TranslationInvariantTransform {
+ qreal m11, m12, m21, m22;
+ TranslationInvariantTransform() { }
+ TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { }
+ bool operator==(const TranslationInvariantTransform& b) {
+ return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22;
+ }
+ } cachedTransform;
+
+ QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set
- QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set
+ bool useCustomColor = false;
+ QColor assistantCustomColor = KisConfig(true).defaultAssistantsColor();
+ };
- bool useCustomColor = false;
- QColor assistantCustomColor = KisConfig(true).defaultAssistantsColor();
+ QSharedPointer<SharedData> s;
};
+KisPaintingAssistant::Private::Private()
+ : s(new SharedData)
+{
+}
+
+KisPaintingAssistant::Private::Private(const Private &rhs)
+ : s(rhs.s)
+{
+}
+
+KisPaintingAssistantHandleSP KisPaintingAssistant::Private::reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q)
+{
+ KisPaintingAssistantHandleSP mappedHandle = handleMap.value(origHandle);
+ if (!mappedHandle) {
+ if (origHandle) {
+ dbgUI << "handle not found in the map, creating a new one...";
+ mappedHandle = KisPaintingAssistantHandleSP(new KisPaintingAssistantHandle(*origHandle));
+ dbgUI << "done";
+ mappedHandle->setType(origHandle->handleType());
+ handleMap.insert(origHandle, mappedHandle);
+ } else {
+ dbgUI << "orig handle is null, not doing anything";
+ mappedHandle = KisPaintingAssistantHandleSP();
+ }
+ }
+ if (mappedHandle) {
+ mappedHandle->registerAssistant(q);
+ }
+ return mappedHandle;
+}
+
bool KisPaintingAssistant::useCustomColor()
{
- return d->useCustomColor;
+ return d->s->useCustomColor;
}
void KisPaintingAssistant::setUseCustomColor(bool useCustomColor)
{
- d->useCustomColor = useCustomColor;
+ d->s->useCustomColor = useCustomColor;
}
void KisPaintingAssistant::setAssistantCustomColor(QColor color)
{
- d->assistantCustomColor = color;
+ d->s->assistantCustomColor = color;
}
QColor KisPaintingAssistant::assistantCustomColor()
{
- return d->assistantCustomColor;
+ return d->s->assistantCustomColor;
}
void KisPaintingAssistant::setAssistantGlobalColorCache(const QColor &color)
{
- d->assistantGlobalColorCache = color;
+ d->s->assistantGlobalColorCache = color;
}
QColor KisPaintingAssistant::effectiveAssistantColor() const
{
- return d->useCustomColor ? d->assistantCustomColor : d->assistantGlobalColorCache;
+ return d->s->useCustomColor ? d->s->assistantCustomColor : d->s->assistantGlobalColorCache;
}
KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private)
{
- d->id = id;
- d->name = name;
- d->isSnappingActive = true;
- d->outlineVisible = true;
+ d->s->id = id;
+ d->s->name = name;
+ d->s->isSnappingActive = true;
+ d->s->outlineVisible = true;
+}
+
+KisPaintingAssistant::KisPaintingAssistant(const KisPaintingAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : d(new Private(*(rhs.d)))
+{
+ dbgUI << "creating handles...";
+ Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->handles) {
+ d->handles << d->reuseOrCreateHandle(handleMap, origHandle, this);
+ }
+ Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->sideHandles) {
+ d->sideHandles << d->reuseOrCreateHandle(handleMap, origHandle, this);
+ }
+#define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this)
+ _REUSE_H(topLeft);
+ _REUSE_H(bottomLeft);
+ _REUSE_H(topRight);
+ _REUSE_H(bottomRight);
+ _REUSE_H(topMiddle);
+ _REUSE_H(bottomMiddle);
+ _REUSE_H(rightMiddle);
+ _REUSE_H(leftMiddle);
+#undef _REUSE_H
+ dbgUI << "done";
}
bool KisPaintingAssistant::isSnappingActive() const
{
- return d->isSnappingActive;
+ return d->s->isSnappingActive;
}
void KisPaintingAssistant::setSnappingActive(bool set)
{
- d->isSnappingActive = set;
+ d->s->isSnappingActive = set;
}
void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn)
{
QColor paintingColor = effectiveAssistantColor();
if (!isSnappingOn) {
paintingColor.setAlpha(0.2 * paintingColor.alpha());
}
painter.save();
QPen pen_a(paintingColor, 2);
pen_a.setCosmetic(true);
painter.setPen(pen_a);
painter.drawPath(path);
painter.restore();
}
void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path)
{
painter.save();
QPen pen_a(effectiveAssistantColor(), 1);
pen_a.setStyle(Qt::SolidLine);
pen_a.setCosmetic(true);
painter.setPen(pen_a);
painter.drawPath(path);
painter.restore();
}
void KisPaintingAssistant::initHandles(QList<KisPaintingAssistantHandleSP> _handles)
{
Q_ASSERT(d->handles.isEmpty());
d->handles = _handles;
Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) {
handle->registerAssistant(this);
}
}
KisPaintingAssistant::~KisPaintingAssistant()
{
Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) {
handle->unregisterAssistant(this);
}
if(!d->sideHandles.isEmpty()) {
Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
handle->unregisterAssistant(this);
}
}
delete d;
}
const QString& KisPaintingAssistant::id() const
{
- return d->id;
+ return d->s->id;
}
const QString& KisPaintingAssistant::name() const
{
- return d->name;
+ return d->s->name;
}
void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with)
{
Q_ASSERT(d->handles.contains(_handle));
d->handles.replace(d->handles.indexOf(_handle), _with);
Q_ASSERT(!d->handles.contains(_handle));
_handle->unregisterAssistant(this);
_with->registerAssistant(this);
}
void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle, HandleType type)
{
Q_ASSERT(!d->handles.contains(handle));
if (HandleType::SIDE == type) {
d->sideHandles.append(handle);
} else {
d->handles.append(handle);
}
handle->registerAssistant(this);
handle.data()->setType(type);
}
void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
Q_UNUSED(updateRect);
Q_UNUSED(previewVisible);
findPerspectiveAssistantHandleLocation();
if (!useCache) {
gc.save();
drawCache(gc, converter, assistantVisible);
gc.restore();
return;
}
const QRect bound = boundingRect();
if (bound.isEmpty()) {
return;
}
const QTransform transform = converter->documentToWidgetTransform();
const QRect widgetBound = transform.mapRect(bound);
const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport());
if (paintRect.isEmpty()) return;
QPixmap cached;
- bool found = QPixmapCache::find(d->cached, &cached);
+ bool found = QPixmapCache::find(d->s->cached, &cached);
if (!(found &&
- d->cachedTransform == transform &&
- d->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) {
+ d->s->cachedTransform == transform &&
+ d->s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) {
const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound);
Q_ASSERT(!cacheRect.isEmpty());
if (cached.isNull() || cached.size() != cacheRect.size()) {
cached = QPixmap(cacheRect.size());
}
cached.fill(Qt::transparent);
QPainter painter(&cached);
painter.setRenderHint(QPainter::Antialiasing);
painter.setWindow(cacheRect);
drawCache(painter, converter, assistantVisible);
painter.end();
- d->cachedTransform = transform;
- d->cachedRect = cacheRect.translated(-widgetBound.topLeft());
- d->cached = QPixmapCache::insert(cached);
+ d->s->cachedTransform = transform;
+ d->s->cachedRect = cacheRect.translated(-widgetBound.topLeft());
+ d->s->cached = QPixmapCache::insert(cached);
}
- gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->cachedRect.topLeft()));
+ gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->s->cachedRect.topLeft()));
if (canvas) {
- d->m_canvas = canvas;
+ d->s->m_canvas = canvas;
}
}
void KisPaintingAssistant::uncache()
{
- d->cached = QPixmapCache::Key();
+ d->s->cached = QPixmapCache::Key();
}
QRect KisPaintingAssistant::boundingRect() const
{
QRectF r;
Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) {
r = r.united(QRectF(*h, QSizeF(1,1)));
}
return r.adjusted(-2, -2, 2, 2).toAlignedRect();
}
bool KisPaintingAssistant::isAssistantComplete() const
{
return true;
}
QByteArray KisPaintingAssistant::saveXml(QMap<KisPaintingAssistantHandleSP, int> &handleMap)
{
QByteArray data;
QXmlStreamWriter xml(&data);
xml.writeStartDocument();
xml.writeStartElement("assistant");
- xml.writeAttribute("type",d->id);
- xml.writeAttribute("active", QString::number(d->isSnappingActive));
- xml.writeAttribute("useCustomColor", QString::number(d->useCustomColor));
- xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->assistantCustomColor));
+ xml.writeAttribute("type",d->s->id);
+ xml.writeAttribute("active", QString::number(d->s->isSnappingActive));
+ xml.writeAttribute("useCustomColor", QString::number(d->s->useCustomColor));
+ xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->s->assistantCustomColor));
saveCustomXml(&xml); // if any specific assistants have custom XML data to save to
// write individual handle data
xml.writeStartElement("handles");
Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) {
int id = handleMap.size();
if (!handleMap.contains(handle)){
handleMap.insert(handle, id);
}
id = handleMap.value(handle);
xml.writeStartElement("handle");
xml.writeAttribute("id", QString::number(id));
xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeEndElement();
xml.writeEndDocument();
return data;
}
void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml)
{
Q_UNUSED(xml);
}
void KisPaintingAssistant::loadXml(KoStore* store, QMap<int, KisPaintingAssistantHandleSP> &handleMap, QString path)
{
int id = 0;
double x = 0.0, y = 0.0;
store->open(path);
QByteArray data = store->read(store->size());
QXmlStreamReader xml(data);
while (!xml.atEnd()) {
switch (xml.readNext()) {
case QXmlStreamReader::StartElement:
if (xml.name() == "assistant") {
QStringRef active = xml.attributes().value("active");
setSnappingActive( (active != "0") );
// load custom shared assistant properties
if ( xml.attributes().hasAttribute("useCustomColor")) {
QStringRef useCustomColor = xml.attributes().value("useCustomColor");
bool usingColor = false;
if (useCustomColor.toString() == "1") {
usingColor = true;
}
setUseCustomColor(usingColor);
}
if ( xml.attributes().hasAttribute("customColor")) {
QStringRef customColor = xml.attributes().value("customColor");
setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) );
}
}
loadCustomXml(&xml);
if (xml.name() == "handle") {
QString strId = xml.attributes().value("id").toString(),
strX = xml.attributes().value("x").toString(),
strY = xml.attributes().value("y").toString();
if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
id = strId.toInt();
x = strX.toDouble();
y = strY.toDouble();
if (!handleMap.contains(id)) {
handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
}
}
addHandle(handleMap.value(id), HandleType::NORMAL);
}
break;
default:
break;
}
}
store->close();
}
bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml)
{
Q_UNUSED(xml);
return true;
}
void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count)
{
- if (d->id == "ellipse"){
+ if (d->s->id == "ellipse"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "ellipse");
assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "spline"){
+ else if (d->s->id == "spline"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "spline");
assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "perspective"){
+ else if (d->s->id == "perspective"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "perspective");
assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "vanishing point"){
+ else if (d->s->id == "vanishing point"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "vanishing point");
assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "infinite ruler"){
+ else if (d->s->id == "infinite ruler"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "infinite ruler");
assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "parallel ruler"){
+ else if (d->s->id == "parallel ruler"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "parallel ruler");
assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "concentric ellipse"){
+ else if (d->s->id == "concentric ellipse"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "concentric ellipse");
assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "fisheye-point"){
+ else if (d->s->id == "fisheye-point"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "fisheye-point");
assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
- else if (d->id == "ruler"){
+ else if (d->s->id == "ruler"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "ruler");
assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
}
void KisPaintingAssistant::findPerspectiveAssistantHandleLocation() {
QList<KisPaintingAssistantHandleSP> hHandlesList;
QList<KisPaintingAssistantHandleSP> vHandlesList;
uint vHole = 0,hHole = 0;
KisPaintingAssistantHandleSP oppHandle;
- if (d->handles.size() == 4 && d->id == "perspective") {
+ if (d->handles.size() == 4 && d->s->id == "perspective") {
//get the handle opposite to the first handle
oppHandle = oppHandleOne();
//Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively.
Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) {
hHandlesList.append(handle);
hHole = hHandlesList.size() - 1;
vHandlesList.append(handle);
vHole = vHandlesList.size() - 1;
/*
sort handles on the basis of X-coordinate
*/
while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) {
hHandlesList.swap(hHole-1, hHole);
hHole = hHole - 1;
}
/*
sort handles on the basis of Y-coordinate
*/
while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) {
vHandlesList.swap(vHole-1, vHole);
vHole = vHole - 1;
}
}
/*
give the handles their respective positions
*/
if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) {
d->topLeft = vHandlesList.at(1);
d->topRight= vHandlesList.at(0);
}
else {
d->topLeft = vHandlesList.at(0);
d->topRight = vHandlesList.at(1);
}
if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) {
d->bottomLeft = vHandlesList.at(3);
d->bottomRight = vHandlesList.at(2);
}
else {
d->bottomLeft= vHandlesList.at(2);
d->bottomRight = vHandlesList.at(3);
}
/*
find if the handles that should be opposite are actually oppositely positioned
*/
if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) ||
(d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) ||
(d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) ||
(d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) )
{}
else {
if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) {
d->topLeft = hHandlesList.at(1);
d->bottomLeft= hHandlesList.at(0);
}
else {
d->topLeft = hHandlesList.at(0);
d->bottomLeft = hHandlesList.at(1);
}
if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) {
d->topRight = hHandlesList.at(3);
d->bottomRight = hHandlesList.at(2);
}
else {
d->topRight= hHandlesList.at(2);
d->bottomRight = hHandlesList.at(3);
}
}
/*
Setting the middle handles as needed
*/
if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) {
d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5);
d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
(d->topLeft.data()->y() + d->topRight.data()->y())*0.5);
d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5,
(d->topRight.data()->y() + d->bottomRight.data()->y())*0.5);
d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5);
addHandle(d->rightMiddle.data(), HandleType::SIDE);
addHandle(d->leftMiddle.data(), HandleType::SIDE);
addHandle(d->bottomMiddle.data(), HandleType::SIDE);
addHandle(d->topMiddle.data(), HandleType::SIDE);
}
else
{
d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5));
d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
(d->topLeft.data()->y() + d->topRight.data()->y())*0.5));
d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5,
(d->topRight.data()->y() + d->bottomRight.data()->y())*0.5));
d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5));
}
}
}
KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne()
{
QPointF intersection(0,0);
if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
&& (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
{
return d->handles.at(1);
}
else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
&& (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
{
return d->handles.at(2);
}
else
{
return d->handles.at(3);
}
}
KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft()
{
return d->topLeft;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const
{
return d->topLeft;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft()
{
return d->bottomLeft;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const
{
return d->bottomLeft;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::topRight()
{
return d->topRight;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const
{
return d->topRight;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight()
{
return d->bottomRight;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const
{
return d->bottomRight;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle()
{
return d->topMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const
{
return d->topMiddle;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle()
{
return d->bottomMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const
{
return d->bottomMiddle;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle()
{
return d->rightMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const
{
return d->rightMiddle;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle()
{
return d->leftMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const
{
return d->leftMiddle;
}
const QList<KisPaintingAssistantHandleSP>& KisPaintingAssistant::handles() const
{
return d->handles;
}
QList<KisPaintingAssistantHandleSP> KisPaintingAssistant::handles()
{
return d->handles;
}
const QList<KisPaintingAssistantHandleSP>& KisPaintingAssistant::sideHandles() const
{
return d->sideHandles;
}
QList<KisPaintingAssistantHandleSP> KisPaintingAssistant::sideHandles()
{
return d->sideHandles;
}
bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo)
{
int m_handleSize = 16;
QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize));
return handlerect.contains(pointOne);
}
KisPaintingAssistantHandleSP KisPaintingAssistant::closestCornerHandleFromPoint(QPointF point)
{
- if (!d->m_canvas) {
+ if (!d->s->m_canvas) {
return 0;
}
if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) {
return topLeft();
} else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) {
return topRight();
} else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) {
return bottomLeft();
} else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) {
return bottomRight();
}
return 0;
}
QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const
{
- QPointF documentCoord = d->m_canvas->image()->pixelToDocument(pixelCoords);
- return d->m_canvas->viewConverter()->documentToView(documentCoord);
+ QPointF documentCoord = d->s->m_canvas->image()->pixelToDocument(pixelCoords);
+ return d->s->m_canvas->viewConverter()->documentToView(documentCoord);
}
double KisPaintingAssistant::norm2(const QPointF& p)
{
return p.x() * p.x() + p.y() * p.y();
}
+QList<KisPaintingAssistantSP> KisPaintingAssistant::cloneAssistantList(const QList<KisPaintingAssistantSP> &list)
+{
+ QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap;
+ QList<KisPaintingAssistantSP> clonedList;
+ for (auto i = list.begin(); i != list.end(); ++i) {
+ clonedList << (*i)->clone(handleMap);
+ }
+ return clonedList;
+}
+
/*
* KisPaintingAssistantFactory classes
*/
KisPaintingAssistantFactory::KisPaintingAssistantFactory()
{
}
KisPaintingAssistantFactory::~KisPaintingAssistantFactory()
{
}
KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry()
{
}
KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry()
{
Q_FOREACH (const QString &id, keys()) {
delete get(id);
}
dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry ";
}
KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance()
{
return s_instance;
}
diff --git a/libs/ui/kis_painting_assistant.h b/libs/ui/kis_painting_assistant.h
index ca67dab819..741194f449 100644
--- a/libs/ui/kis_painting_assistant.h
+++ b/libs/ui/kis_painting_assistant.h
@@ -1,232 +1,242 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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_PAINTING_ASSISTANT_H_
#define _KIS_PAINTING_ASSISTANT_H_
#include <QString>
#include <QPointF>
#include <QRect>
#include <QFile>
#include <QObject>
#include <QColor>
#include <QXmlStreamWriter>
+#include <QMap>
#include <kritaui_export.h>
#include <kis_shared.h>
+#include <kis_types.h>
class QPainter;
class QRect;
class QRectF;
class KoStore;
class KisCoordinatesConverter;
class KisCanvas2;
class QDomDocument;
class QDomElement;
#include <kis_shared_ptr.h>
#include <KoGenericRegistry.h>
class KisPaintingAssistantHandle;
typedef KisSharedPtr<KisPaintingAssistantHandle> KisPaintingAssistantHandleSP;
class KisPaintingAssistant;
class QPainterPath;
enum HandleType {
NORMAL,
SIDE,
CORNER,
VANISHING_POINT,
ANCHOR
};
/**
* Represent an handle of the assistant, used to edit the parameters
* of an assistants. Handles can be shared between assistants.
*/
class KRITAUI_EXPORT KisPaintingAssistantHandle : public QPointF, public KisShared
{
friend class KisPaintingAssistant;
public:
KisPaintingAssistantHandle(double x, double y);
explicit KisPaintingAssistantHandle(QPointF p);
KisPaintingAssistantHandle(const KisPaintingAssistantHandle&);
~KisPaintingAssistantHandle();
void mergeWith(KisPaintingAssistantHandleSP);
void uncache();
KisPaintingAssistantHandle& operator=(const QPointF&);
void setType(char type);
- char handleType();
+ char handleType() const;
private:
void registerAssistant(KisPaintingAssistant*);
void unregisterAssistant(KisPaintingAssistant*);
- bool containsAssistant(KisPaintingAssistant*);
+ bool containsAssistant(KisPaintingAssistant*) const;
private:
struct Private;
Private* const d;
};
/**
* A KisPaintingAssistant is an object that assist the drawing on the canvas.
* With this class you can implement virtual equivalent to ruler or compas.
*/
class KRITAUI_EXPORT KisPaintingAssistant
{
public:
KisPaintingAssistant(const QString& id, const QString& name);
virtual ~KisPaintingAssistant();
+ virtual KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const = 0;
const QString& id() const;
const QString& name() const;
bool isSnappingActive() const;
void setSnappingActive(bool set);
/**
* Adjust the position given in parameter.
* @param point the coordinates in point in the document reference
* @param strokeBegin the coordinates of the beginning of the stroke
*/
virtual QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) = 0;
virtual void endStroke() { }
virtual QPointF buttonPosition() const = 0;
virtual int numHandles() const = 0;
void replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with);
void addHandle(KisPaintingAssistantHandleSP handle, HandleType type);
QColor effectiveAssistantColor() const;
/// should this assistant use a custom color for the display? global color will be used if this is false
bool useCustomColor();
void setUseCustomColor(bool useCustomColor);
/// getter and setter for assistant's custom color
void setAssistantCustomColor(QColor color);
QColor assistantCustomColor();
void setAssistantGlobalColorCache(const QColor &color);
virtual void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, bool cached = true,KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true);
void uncache();
const QList<KisPaintingAssistantHandleSP>& handles() const;
QList<KisPaintingAssistantHandleSP> handles();
const QList<KisPaintingAssistantHandleSP>& sideHandles() const;
QList<KisPaintingAssistantHandleSP> sideHandles();
QByteArray saveXml( QMap<KisPaintingAssistantHandleSP, int> &handleMap);
virtual void saveCustomXml(QXmlStreamWriter* xml); //in case specific assistants have custom properties (like vanishing point)
void loadXml(KoStore *store, QMap<int, KisPaintingAssistantHandleSP> &handleMap, QString path);
virtual bool loadCustomXml(QXmlStreamReader* xml);
void saveXmlList(QDomDocument& doc, QDomElement& ssistantsElement, int count);
void findPerspectiveAssistantHandleLocation();
KisPaintingAssistantHandleSP oppHandleOne();
/**
* Get the topLeft, bottomLeft, topRight and BottomRight corners of the assistant
* Some assistants like the perspective grid have custom logic built around certain handles
*/
const KisPaintingAssistantHandleSP topLeft() const;
KisPaintingAssistantHandleSP topLeft();
const KisPaintingAssistantHandleSP topRight() const;
KisPaintingAssistantHandleSP topRight();
const KisPaintingAssistantHandleSP bottomLeft() const;
KisPaintingAssistantHandleSP bottomLeft();
const KisPaintingAssistantHandleSP bottomRight() const;
KisPaintingAssistantHandleSP bottomRight();
const KisPaintingAssistantHandleSP topMiddle() const;
KisPaintingAssistantHandleSP topMiddle();
const KisPaintingAssistantHandleSP rightMiddle() const;
KisPaintingAssistantHandleSP rightMiddle();
const KisPaintingAssistantHandleSP leftMiddle() const;
KisPaintingAssistantHandleSP leftMiddle();
const KisPaintingAssistantHandleSP bottomMiddle() const;
KisPaintingAssistantHandleSP bottomMiddle();
// calculates whether a point is near one of the corner points of the assistant
// returns: a corner point from the perspective assistant if the given node is close
// only called once in code when calculating the perspective assistant
KisPaintingAssistantHandleSP closestCornerHandleFromPoint(QPointF point);
// determines if two points are close to each other
// only used by the nodeNearPoint function (perspective grid assistant).
bool areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo);
/// determines if the assistant has enough handles to be considered created
/// new assistants get in a "creation" phase where they are currently being made on the canvas
/// it will return false if we are in the middle of creating the assistant.
virtual bool isAssistantComplete() const;
public:
/**
* This will render the final output. The drawCache does rendering most of the time so be sure to check that
*/
void drawPath(QPainter& painter, const QPainterPath& path, bool drawActive=true);
void drawPreview(QPainter& painter, const QPainterPath& path);
static double norm2(const QPointF& p);
protected:
+ explicit KisPaintingAssistant(const KisPaintingAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
+
virtual QRect boundingRect() const;
/// performance layer where the graphics can be drawn from a cache instead of generated every render update
virtual void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) = 0;
void initHandles(QList<KisPaintingAssistantHandleSP> _handles);
QList<KisPaintingAssistantHandleSP> m_handles;
QPointF pixelToView(const QPoint pixelCoords) const;
+public:
+ /// clones the list of assistants
+ /// the originally shared handles will still be shared
+ /// the cloned assistants do not share any handle with the original assistants
+ static QList<KisPaintingAssistantSP> cloneAssistantList(const QList<KisPaintingAssistantSP> &list);
private:
struct Private;
Private* const d;
};
/**
* Allow to create a painting assistant.
*/
class KRITAUI_EXPORT KisPaintingAssistantFactory
{
public:
KisPaintingAssistantFactory();
virtual ~KisPaintingAssistantFactory();
virtual QString id() const = 0;
virtual QString name() const = 0;
virtual KisPaintingAssistant* createPaintingAssistant() const = 0;
};
class KRITAUI_EXPORT KisPaintingAssistantFactoryRegistry : public KoGenericRegistry<KisPaintingAssistantFactory*>
{
public:
KisPaintingAssistantFactoryRegistry();
~KisPaintingAssistantFactoryRegistry() override;
static KisPaintingAssistantFactoryRegistry* instance();
};
#endif
diff --git a/libs/ui/kis_painting_assistants_decoration.cpp b/libs/ui/kis_painting_assistants_decoration.cpp
index a782900435..dfeaca7ec0 100644
--- a/libs/ui/kis_painting_assistants_decoration.cpp
+++ b/libs/ui/kis_painting_assistants_decoration.cpp
@@ -1,490 +1,501 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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_painting_assistants_decoration.h"
#include <cfloat>
#include <QList>
#include <QPointF>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <ktoggleaction.h>
#include "kis_debug.h"
#include "KisDocument.h"
#include "kis_canvas2.h"
#include "kis_icon_utils.h"
#include "KisViewManager.h"
#include <QPainter>
#include <QApplication>
struct KisPaintingAssistantsDecoration::Private {
Private()
: assistantVisible(false)
, outlineVisible(false)
, snapOnlyOneAssistant(true)
, firstAssistant(0)
, aFirstStroke(false)
, m_handleSize(14)
{}
bool assistantVisible;
bool outlineVisible;
bool snapOnlyOneAssistant;
KisPaintingAssistantSP firstAssistant;
KisPaintingAssistantSP selectedAssistant;
bool aFirstStroke;
bool m_isEditingAssistants = false;
bool m_outlineVisible = false;
int m_handleSize; // size of editor handles on assistants
// move, visibility, delete icons for each assistant. These only display while the assistant tool is active
// these icons will be covered by the kis_paintint_assistant_decoration with things like the perspective assistant
AssistantEditorData toolData;
QPixmap m_iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(toolData.deleteIconSize, toolData.deleteIconSize);
QPixmap m_iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(toolData.snapIconSize, toolData.snapIconSize);
QPixmap m_iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(toolData.snapIconSize, toolData.snapIconSize);
QPixmap m_iconMove = KisIconUtils::loadIcon("transform-move").pixmap(toolData.moveIconSize, toolData.moveIconSize);
KisCanvas2 * m_canvas = 0;
};
KisPaintingAssistantsDecoration::KisPaintingAssistantsDecoration(QPointer<KisView> parent) :
KisCanvasDecoration("paintingAssistantsDecoration", parent),
d(new Private)
{
setAssistantVisible(true);
setOutlineVisible(true);
setPriority(95);
d->snapOnlyOneAssistant = true; //turn on by default.
}
KisPaintingAssistantsDecoration::~KisPaintingAssistantsDecoration()
{
delete d;
}
void KisPaintingAssistantsDecoration::addAssistant(KisPaintingAssistantSP assistant)
{
QList<KisPaintingAssistantSP> assistants = view()->document()->assistants();
if (assistants.contains(assistant)) return;
assistants.append(assistant);
assistant->setAssistantGlobalColorCache(view()->document()->assistantsGlobalColor());
view()->document()->setAssistants(assistants);
setVisible(!assistants.isEmpty());
emit assistantChanged();
}
void KisPaintingAssistantsDecoration::removeAssistant(KisPaintingAssistantSP assistant)
{
QList<KisPaintingAssistantSP> assistants = view()->document()->assistants();
KIS_ASSERT_RECOVER_NOOP(assistants.contains(assistant));
if (assistants.removeAll(assistant)) {
view()->document()->setAssistants(assistants);
setVisible(!assistants.isEmpty());
emit assistantChanged();
}
}
void KisPaintingAssistantsDecoration::removeAll()
{
QList<KisPaintingAssistantSP> assistants = view()->document()->assistants();
assistants.clear();
view()->document()->setAssistants(assistants);
setVisible(!assistants.isEmpty());
emit assistantChanged();
}
+void KisPaintingAssistantsDecoration::setAssistants(const QList<KisPaintingAssistantSP> &assistants)
+{
+ Q_FOREACH (KisPaintingAssistantSP assistant, assistants) {
+ assistant->setAssistantGlobalColorCache(view()->document()->assistantsGlobalColor());
+ }
+ view()->document()->setAssistants(assistants);
+ setVisible(!assistants.isEmpty());
+
+ emit assistantChanged();
+}
+
QPointF KisPaintingAssistantsDecoration::adjustPosition(const QPointF& point, const QPointF& strokeBegin)
{
if (assistants().empty()) {
return point;
}
if (assistants().count() == 1) {
if(assistants().first()->isSnappingActive() == true){
QPointF newpoint = assistants().first()->adjustPosition(point, strokeBegin);
// check for NaN
if (newpoint.x() != newpoint.x()) return point;
return newpoint;
}
}
QPointF best = point;
double distance = DBL_MAX;
//the following tries to find the closest point to stroke-begin. It checks all assistants for the closest point//
if(!d->snapOnlyOneAssistant){
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on//
QPointF pt = assistant->adjustPosition(point, strokeBegin);
if (pt.x() != pt.x()) continue;
double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y());
if (dist < distance) {
best = pt;
distance = dist;
}
}
}
} else if (d->aFirstStroke==false) {
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on//
QPointF pt = assistant->adjustPosition(point, strokeBegin);
if (pt.x() != pt.x()) continue;
double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y());
if (dist < distance) {
best = pt;
distance = dist;
d->firstAssistant = assistant;
}
}
}
} else if(d->firstAssistant) {
//make sure there's a first assistant to begin with.//
QPointF newpoint = d->firstAssistant->adjustPosition(point, strokeBegin);
// BUGFIX: 402535
// assistants might return (NaN,NaN), must always check for that
if (newpoint.x() == newpoint.x()) {
// not a NaN
best = newpoint;
}
} else {
d->aFirstStroke=false;
}
//this is here to be compatible with the movement in the perspective tool.
qreal dx = point.x() - strokeBegin.x(), dy = point.y() - strokeBegin.y();
if (dx * dx + dy * dy >= 4.0) {
// allow some movement before snapping
d->aFirstStroke=true;
}
return best;
}
void KisPaintingAssistantsDecoration::endStroke()
{
d->aFirstStroke = false;
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
assistant->endStroke();
}
}
void KisPaintingAssistantsDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas)
{
if(assistants().length() == 0) {
return; // no assistants to worry about, ok to exit
}
if (!canvas) {
dbgFile<<"canvas does not exist in painting assistant decoration, you may have passed arguments incorrectly:"<<canvas;
} else {
d->m_canvas = canvas;
}
// the preview functionality for assistants. do not show while editing
if (d->m_isEditingAssistants) {
d->m_outlineVisible = false;
}
else {
d->m_outlineVisible = outlineVisibility();
}
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
assistant->drawAssistant(gc, updateRect, converter, true, canvas, assistantVisibility(), d->m_outlineVisible);
if (isEditingAssistants()) {
drawHandles(assistant, gc, converter);
}
}
// draw editor controls on top of all assistant lines (why this code is last)
if (isEditingAssistants()) {
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
drawEditorWidget(assistant, gc, converter);
}
}
}
void KisPaintingAssistantsDecoration::drawHandles(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter)
{
QTransform initialTransform = converter->documentToWidgetTransform();
QColor colorToPaint = assistant->effectiveAssistantColor();
Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) {
QPointF transformedHandle = initialTransform.map(*handle);
QRectF ellipse(transformedHandle - QPointF(handleSize() * 0.5, handleSize() * 0.5), QSizeF(handleSize(), handleSize()));
QPainterPath path;
path.addEllipse(ellipse);
gc.save();
gc.setPen(Qt::NoPen);
gc.setBrush(colorToPaint);
gc.drawPath(path);
gc.restore();
}
// some assistants have side handles like the vanishing point assistant
Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) {
QPointF transformedHandle = initialTransform.map(*handle);
QRectF ellipse(transformedHandle - QPointF(handleSize() * 0.5, handleSize() * 0.5), QSizeF(handleSize(), handleSize()));
QPainterPath path;
path.addEllipse(ellipse);
gc.save();
gc.setPen(Qt::NoPen);
gc.setBrush(colorToPaint);
gc.drawPath(path);
gc.restore();
}
}
int KisPaintingAssistantsDecoration::handleSize()
{
return d->m_handleSize;
}
void KisPaintingAssistantsDecoration::setHandleSize(int handleSize)
{
d->m_handleSize = handleSize;
}
QList<KisPaintingAssistantHandleSP> KisPaintingAssistantsDecoration::handles()
{
QList<KisPaintingAssistantHandleSP> hs;
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) {
if (!hs.contains(handle)) {
hs.push_back(handle);
}
}
Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) {
if (!hs.contains(handle)) {
hs.push_back(handle);
}
}
}
return hs;
}
QList<KisPaintingAssistantSP> KisPaintingAssistantsDecoration::assistants() const
{
QList<KisPaintingAssistantSP> assistants = view()->document()->assistants();
return assistants;
}
KisPaintingAssistantSP KisPaintingAssistantsDecoration::selectedAssistant()
{
return d->selectedAssistant;
}
void KisPaintingAssistantsDecoration::setSelectedAssistant(KisPaintingAssistantSP assistant)
{
d->selectedAssistant = assistant;
emit selectedAssistantChanged();
}
void KisPaintingAssistantsDecoration::deselectAssistant()
{
d->selectedAssistant.clear();
}
void KisPaintingAssistantsDecoration::setAssistantVisible(bool set)
{
d->assistantVisible=set;
}
void KisPaintingAssistantsDecoration::setOutlineVisible(bool set)
{
d->outlineVisible=set;
}
void KisPaintingAssistantsDecoration::setOnlyOneAssistantSnap(bool assistant)
{
d->snapOnlyOneAssistant = assistant;
}
bool KisPaintingAssistantsDecoration::assistantVisibility()
{
return d->assistantVisible;
}
bool KisPaintingAssistantsDecoration::outlineVisibility()
{
return d->outlineVisible;
}
void KisPaintingAssistantsDecoration::uncache()
{
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
assistant->uncache();
}
}
void KisPaintingAssistantsDecoration::toggleAssistantVisible()
{
setAssistantVisible(!assistantVisibility());
uncache();
}
void KisPaintingAssistantsDecoration::toggleOutlineVisible()
{
setOutlineVisible(!outlineVisibility());
}
QColor KisPaintingAssistantsDecoration::globalAssistantsColor()
{
return view()->document()->assistantsGlobalColor();
}
void KisPaintingAssistantsDecoration::setGlobalAssistantsColor(QColor color)
{
// view()->document() is referenced multiple times in this class
// it is used to later store things in the KRA file when saving.
view()->document()->setAssistantsGlobalColor(color);
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
assistant->setAssistantGlobalColorCache(color);
}
uncache();
}
void KisPaintingAssistantsDecoration::activateAssistantsEditor()
{
setVisible(true); // this turns on the decorations in general. we leave it on at this point
d->m_isEditingAssistants = true;
uncache(); // updates visuals when editing
}
void KisPaintingAssistantsDecoration::deactivateAssistantsEditor()
{
if (!d->m_canvas) {
return;
}
d->m_isEditingAssistants = false; // some elements are hidden when we aren't editing
uncache(); // updates visuals when not editing
}
bool KisPaintingAssistantsDecoration::isEditingAssistants()
{
return d->m_isEditingAssistants;
}
QPointF KisPaintingAssistantsDecoration::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers)
{
if (!d->m_canvas || !d->m_canvas->currentImage()) {
return e->point;
}
KoSnapGuide *snapGuide = d->m_canvas->snapGuide();
QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier);
return pos;
}
QPointF KisPaintingAssistantsDecoration::snapToGuide(const QPointF& pt, const QPointF &offset)
{
if (!d->m_canvas) {
return pt;
}
KoSnapGuide *snapGuide = d->m_canvas->snapGuide();
QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier);
return pos;
}
/*
* functions only used internally in this class
* we potentially could make some of these inline to speed up performance
*/
void KisPaintingAssistantsDecoration::drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter)
{
if (!assistant->isAssistantComplete()) {
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
// We are going to put all of the assistant actions below the bounds of the assistant
// so they are out of the way
// assistant->buttonPosition() gets the center X/Y position point
QPointF actionsPosition = initialTransform.map(assistant->buttonPosition());
AssistantEditorData toolData; // shared const data for positioning and sizing
QPointF iconMovePosition(actionsPosition + toolData.moveIconPosition);
QPointF iconSnapPosition(actionsPosition + toolData.snapIconPosition);
QPointF iconDeletePosition(actionsPosition + toolData.deleteIconPosition);
// Background container for helpers
QBrush backgroundColor = d->m_canvas->viewManager()->mainWindow()->palette().window();
QPointF actionsBGRectangle(actionsPosition + QPointF(10, 10));
gc.setRenderHint(QPainter::Antialiasing);
QPainterPath bgPath;
bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 110, 40), 6, 6);
QPen stroke(QColor(60, 60, 60, 80), 2);
// if the assistant is selected, make outline stroke fatter and use theme's highlight color
// for better visual feedback
if (selectedAssistant()) { // there might not be a selected assistant, so do not seg fault
if (assistant->buttonPosition() == selectedAssistant()->buttonPosition()) {
stroke.setWidth(4);
stroke.setColor(qApp->palette().color(QPalette::Highlight));
}
}
// draw the final result
gc.setPen(stroke);
gc.fillPath(bgPath, backgroundColor);
gc.drawPath(bgPath);
// Move Assistant Tool helper
gc.drawPixmap(iconMovePosition, d->m_iconMove);
// active toggle
if (assistant->isSnappingActive() == true) {
gc.drawPixmap(iconSnapPosition, d->m_iconSnapOn);
}
else {
gc.drawPixmap(iconSnapPosition, d->m_iconSnapOff);
}
gc.drawPixmap(iconDeletePosition, d->m_iconDelete);
}
diff --git a/libs/ui/kis_painting_assistants_decoration.h b/libs/ui/kis_painting_assistants_decoration.h
index 33b6bee48d..87b497eb3c 100644
--- a/libs/ui/kis_painting_assistants_decoration.h
+++ b/libs/ui/kis_painting_assistants_decoration.h
@@ -1,142 +1,143 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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_PAINTING_ASSISTANTS_MANAGER_H_
#define _KIS_PAINTING_ASSISTANTS_MANAGER_H_
#include <QPointF>
#include <QColor>
#include "KoPointerEvent.h"
#include "KoSnapGuide.h"
#include "canvas/kis_canvas_decoration.h"
#include "kis_painting_assistant.h"
#include <kritaui_export.h>
class KisView;
class KisPaintingAssistantsDecoration;
typedef KisSharedPtr<KisPaintingAssistantsDecoration> KisPaintingAssistantsDecorationSP;
/// data for editor widget. This is shared between the decoration and assistant tool which needs hit box information
struct AssistantEditorData {
const int moveIconSize = 32;
const int deleteIconSize = 24;
const int snapIconSize = 20;
const QPointF moveIconPosition = QPointF(15, 15);
const QPointF snapIconPosition = QPointF(54, 20);
const QPointF deleteIconPosition = QPointF(83, 18);
};
/**
* KisPaintingAssistantsDecoration draws the assistants stored in the document on
* the canvas.
* In the application flow, each canvas holds one of these classes to manage the assistants
* There is an assistants manager, but that is higher up in the flow and makes sure each view gets one of these
* Since this is off the canvas level, the decoration can be seen across all tools. The contents from here will be in
* front of the kis_assistant_tool, which hold and displays the editor controls.
*
* Many of the events this receives such as adding and removing assistants comes from kis_assistant_tool
*/
class KRITAUI_EXPORT KisPaintingAssistantsDecoration : public KisCanvasDecoration
{
Q_OBJECT
public:
KisPaintingAssistantsDecoration(QPointer<KisView> parent);
~KisPaintingAssistantsDecoration() override;
void addAssistant(KisPaintingAssistantSP assistant);
void removeAssistant(KisPaintingAssistantSP assistant);
void removeAll();
+ void setAssistants(const QList<KisPaintingAssistantSP> &assistants);
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin);
void endStroke();
QList<KisPaintingAssistantHandleSP> handles();
QList<KisPaintingAssistantSP> assistants() const;
/// getter and setter functions for what assistant is currently selected
/// this is used to control some tool options that are specific to a assistant
KisPaintingAssistantSP selectedAssistant();
void setSelectedAssistant(KisPaintingAssistantSP assistant);
void deselectAssistant();
/// called when assistant editor is activated
/// right now this happens when the assistants tool is selected
void activateAssistantsEditor();
/// called when assistant editor is deactivated
/// right now this happens when the assistants tool is un-selected
void deactivateAssistantsEditor();
/// brings back if we are currently editing assistants or not
/// useful for some assistants (like spline) that draw bezier curves
bool isEditingAssistants();
/// sets whether the main assistant is visible
void setAssistantVisible(bool set);
/// sets whether the preview is visible
void setOutlineVisible(bool set);
/// sets whether we snap to only one assistant
void setOnlyOneAssistantSnap(bool assistant);
/// returns assistant visibility
bool assistantVisibility();
/// returns preview visibility
bool outlineVisibility();
/// uncache all assistants
void uncache();
int handleSize();
void setHandleSize(int handleSize);
QColor globalAssistantsColor();
void setGlobalAssistantsColor(QColor color);
Q_SIGNALS:
void assistantChanged();
void selectedAssistantChanged();
public Q_SLOTS:
/// toggles whether the assistant is active or not
void toggleAssistantVisible();
/// toggles whether there will be a preview of the assistant result when painting
void toggleOutlineVisible();
QPointF snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers);
QPointF snapToGuide(const QPointF& pt, const QPointF &offset);
protected:
void drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas) override;
void drawHandles(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter);
void drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter);
private:
struct Private;
Private* const d;
};
#endif
diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc
index 9c839c4370..8732fbdac6 100644
--- a/libs/ui/kis_paintop_box.cc
+++ b/libs/ui/kis_paintop_box.cc
@@ -1,1359 +1,1373 @@
/*
* kis_paintop_box.cc - part of KImageShop/Krayon/Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
* Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com)
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
* Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
*
* 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_paintop_box.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
#include <QPixmap>
#include <QWidgetAction>
#include <QApplication>
#include <QMenu>
#include <QTime>
#include <kis_debug.h>
#include <kactioncollection.h>
#include <kacceleratormanager.h>
#include <QKeySequence>
#include <kis_icon.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KoResourceServerAdapter.h>
#include <KoToolManager.h>
#include <KoColorSpaceRegistry.h>
#include <kis_paint_device.h>
#include <brushengine/kis_paintop_registry.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_paintop_settings_update_proxy.h>
#include <kis_config_widget.h>
#include <kis_image.h>
#include <kis_node.h>
#include <brushengine/kis_paintop_config_widget.h>
#include <kis_action.h>
#include "kis_canvas2.h"
#include "kis_node_manager.h"
#include "KisViewManager.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include "kis_favorite_resource_manager.h"
#include "kis_config.h"
#include "kis_popup_button.h"
#include "widgets/kis_iconwidget.h"
#include "widgets/kis_tool_options_popup.h"
#include "widgets/kis_paintop_presets_popup.h"
#include "widgets/kis_paintop_presets_chooser_popup.h"
#include "widgets/kis_workspace_chooser.h"
#include "widgets/kis_paintop_list_widget.h"
#include "widgets/kis_slider_spin_box.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_widget_chooser.h"
#include "tool/kis_tool.h"
#include "kis_signals_blocker.h"
#include "kis_action_manager.h"
-#include "kis_highlighted_button.h"
+#include "KisHighlightedToolButton.h"
typedef KoResourceServerSimpleConstruction<KisPaintOpPreset, SharedPointerStoragePolicy<KisPaintOpPresetSP> > KisPaintOpPresetResourceServer;
typedef KoResourceServerAdapter<KisPaintOpPreset, SharedPointerStoragePolicy<KisPaintOpPresetSP> > KisPaintOpPresetResourceServerAdapter;
KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name)
: QWidget(parent)
, m_resourceProvider(view->canvasResourceProvider())
, m_optionWidget(0)
, m_toolOptionsPopupButton(0)
, m_brushEditorPopupButton(0)
, m_presetSelectorPopupButton(0)
, m_toolOptionsPopup(0)
, m_viewManager(view)
, m_previousNode(0)
, m_currTabletToolID(KoInputDevice::invalid())
, m_presetsEnabled(true)
, m_blockUpdate(false)
, m_dirtyPresetsEnabled(false)
, m_eraserBrushSizeEnabled(false)
, m_eraserBrushOpacityEnabled(false)
{
Q_ASSERT(view != 0);
setObjectName(name);
KisConfig cfg(true);
m_dirtyPresetsEnabled = cfg.useDirtyPresets();
m_eraserBrushSizeEnabled = cfg.useEraserBrushSize();
m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity();
KAcceleratorManager::setNoAccel(this);
setWindowTitle(i18n("Painter's Toolchest"));
m_favoriteResourceManager = new KisFavoriteResourceManager(this);
KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff");
int iconsize = grp.readEntry("IconSize", 32);
if (!cfg.toolOptionsInDocker()) {
m_toolOptionsPopupButton = new KisPopupButton(this);
m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure"));
m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings"));
m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize);
}
m_brushEditorPopupButton = new KisIconWidget(this);
m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02"));
m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings"));
m_brushEditorPopupButton->setFixedSize(iconsize, iconsize);
m_presetSelectorPopupButton = new KisPopupButton(this);
m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01"));
m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset"));
m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize);
m_eraseModeButton = new KisHighlightedToolButton(this);
m_eraseModeButton->setFixedSize(iconsize, iconsize);
m_eraseModeButton->setCheckable(true);
m_eraseAction = m_viewManager->actionManager()->createAction("erase_action");
m_eraseModeButton->setDefaultAction(m_eraseAction);
m_reloadButton = new QToolButton(this);
m_reloadButton->setFixedSize(iconsize, iconsize);
m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action");
m_reloadButton->setDefaultAction(m_reloadAction);
m_alphaLockButton = new KisHighlightedToolButton(this);
m_alphaLockButton->setFixedSize(iconsize, iconsize);
m_alphaLockButton->setCheckable(true);
KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha");
m_alphaLockButton->setDefaultAction(alphaLockAction);
// horizontal and vertical mirror toolbar buttons
// mirror tool options for the X Mirror
QMenu *toolbarMenuXMirror = new QMenu();
hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations");
toolbarMenuXMirror->addAction(hideCanvasDecorationsX);
lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock");
toolbarMenuXMirror->addAction(lockActionX);
moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter");
toolbarMenuXMirror->addAction(moveToCenterActionX);
// mirror tool options for the Y Mirror
QMenu *toolbarMenuYMirror = new QMenu();
hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations");
toolbarMenuYMirror->addAction(hideCanvasDecorationsY);
lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock");
toolbarMenuYMirror->addAction(lockActionY);
moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter");
toolbarMenuYMirror->addAction(moveToCenterActionY);
// create horizontal and vertical mirror buttons
m_hMirrorButton = new KisHighlightedToolButton(this);
int menuPadding = 10;
m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize);
m_hMirrorButton->setCheckable(true);
m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action");
m_hMirrorButton->setDefaultAction(m_hMirrorAction);
m_hMirrorButton->setMenu(toolbarMenuXMirror);
m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup);
m_vMirrorButton = new KisHighlightedToolButton(this);
m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize);
m_vMirrorButton->setCheckable(true);
m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action");
m_vMirrorButton->setDefaultAction(m_vMirrorAction);
m_vMirrorButton->setMenu(toolbarMenuYMirror);
m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup);
// add connections for horizontal and mirrror buttons
connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool)));
connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool)));
connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX()));
connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY()));
connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool)));
connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool)));
const bool sliderLabels = cfg.sliderLabels();
int sliderWidth;
if (sliderLabels) {
sliderWidth = 150 * logicalDpiX() / 96;
}
else {
sliderWidth = 120 * logicalDpiX() / 96;
}
for (int i = 0; i < 3; ++i) {
m_sliderChooser[i] = new KisWidgetChooser(i + 1);
KisDoubleSliderSpinBox* slOpacity;
KisDoubleSliderSpinBox* slFlow;
KisDoubleSliderSpinBox* slSize;
if (sliderLabels) {
slOpacity = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("opacity");
slFlow = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("flow");
slSize = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("size");
slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:")));
slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:")));
slSize->setPrefix(QString("%1 ").arg(i18n("Size:")));
}
else {
slOpacity = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("opacity", i18n("Opacity:"));
slFlow = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("flow", i18n("Flow:"));
slSize = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("size", i18n("Size:"));
}
slOpacity->setRange(0, 100, 0);
slOpacity->setValue(100);
slOpacity->setSingleStep(5);
slOpacity->setSuffix("%");
slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width()));
slOpacity->setFixedHeight(iconsize);
slOpacity->setBlockUpdateSignalOnDrag(true);
slFlow->setRange(0, 100, 0);
slFlow->setValue(100);
slFlow->setSingleStep(5);
slFlow->setSuffix("%");
slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width()));
slFlow->setFixedHeight(iconsize);
slFlow->setBlockUpdateSignalOnDrag(true);
- slSize->setRange(0, cfg.readEntry("maximumBrushSize", 1000), 2);
+ slSize->setRange(0.01, cfg.readEntry("maximumBrushSize", 1000), 2);
slSize->setValue(100);
slSize->setSingleStep(1);
slSize->setExponentRatio(3.0);
slSize->setSuffix(i18n(" px"));
slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width()));
slSize->setFixedHeight(iconsize);
slSize->setBlockUpdateSignalOnDrag(true);
m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1));
}
m_cmbCompositeOp = new KisCompositeOpComboBox();
m_cmbCompositeOp->setFixedHeight(iconsize);
Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) {
m_viewManager->actionManager()->addAction(a->text(), a);
}
m_workspaceWidget = new KisPopupButton(this);
m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose"));
m_workspaceWidget->setToolTip(i18n("Choose workspace"));
m_workspaceWidget->setFixedSize(iconsize, iconsize);
m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view));
QHBoxLayout* baseLayout = new QHBoxLayout(this);
m_paintopWidget = new QWidget(this);
baseLayout->addWidget(m_paintopWidget);
baseLayout->setSpacing(4);
baseLayout->setContentsMargins(0, 0, 0, 0);
m_layout = new QHBoxLayout(m_paintopWidget);
if (!cfg.toolOptionsInDocker()) {
m_layout->addWidget(m_toolOptionsPopupButton);
}
m_layout->addWidget(m_brushEditorPopupButton);
m_layout->addWidget(m_presetSelectorPopupButton);
m_layout->setSpacing(4);
m_layout->setContentsMargins(0, 0, 0, 0);
QWidget* compositeActions = new QWidget(this);
QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions);
compositeLayout->addWidget(m_cmbCompositeOp);
compositeLayout->addWidget(m_eraseModeButton);
compositeLayout->addWidget(m_alphaLockButton);
compositeLayout->setSpacing(4);
compositeLayout->setContentsMargins(0, 0, 0, 0);
compositeLayout->addWidget(m_reloadButton);
QWidgetAction * action;
action = new QWidgetAction(this);
view->actionCollection()->addAction("composite_actions", action);
action->setText(i18n("Brush composite"));
action->setDefaultWidget(compositeActions);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider1", action);
view->actionCollection()->addAction("brushslider1", action);
action->setDefaultWidget(m_sliderChooser[0]);
connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider2", action);
view->actionCollection()->addAction("brushslider2", action);
action->setDefaultWidget(m_sliderChooser[1]);
connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider3", action);
view->actionCollection()->addAction("brushslider3", action);
action->setDefaultWidget(m_sliderChooser[2]);
connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action);
view->actionCollection()->addAction("next_favorite_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action);
view->actionCollection()->addAction("previous_favorite_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("previous_preset", action);
view->actionCollection()->addAction("previous_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset()));
if (!cfg.toolOptionsInDocker()) {
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_tool_options", action);
view->actionCollection()->addAction("show_tool_options", action);
connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget()));
}
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_brush_editor", action);
view->actionCollection()->addAction("show_brush_editor", action);
connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_brush_presets", action);
view->actionCollection()->addAction("show_brush_presets", action);
connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget()));
QWidget* mirrorActions = new QWidget(this);
QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions);
mirrorLayout->addWidget(m_hMirrorButton);
mirrorLayout->addWidget(m_vMirrorButton);
mirrorLayout->setSpacing(4);
mirrorLayout->setContentsMargins(0, 0, 0, 0);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("mirror_actions", action);
action->setDefaultWidget(mirrorActions);
view->actionCollection()->addAction("mirror_actions", action);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("workspaces", action);
view->actionCollection()->addAction("workspaces", action);
action->setDefaultWidget(m_workspaceWidget);
if (!cfg.toolOptionsInDocker()) {
m_toolOptionsPopup = new KisToolOptionsPopup();
m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup);
m_toolOptionsPopup->switchDetached(false);
}
m_savePresetWidget = new KisPresetSaveWidget(this);
m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget);
m_brushEditorPopupButton->setPopupWidget(m_presetsPopup);
m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor"));
connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons()));
m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup();
m_presetsChooserPopup->setMinimumHeight(550);
m_presetsChooserPopup->setMinimumWidth(450);
m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup);
m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
slotNodeChanged(view->activeNode());
// Get all the paintops
QList<QString> keys = KisPaintOpRegistry::instance()->keys();
QList<KisPaintOpFactory*> factoryList;
Q_FOREACH (const QString & paintopId, keys) {
factoryList.append(KisPaintOpRegistry::instance()->get(paintopId));
}
m_presetsPopup->setPaintOpList(factoryList);
connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString)));
connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset()));
connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*)));
connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset()));
connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool)));
connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool)));
connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool)));
connect(m_presetsPopup, SIGNAL(createPresetFromScratch(QString)), this, SLOT(slotCreatePresetFromScratch(QString)));
connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*)));
connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*)));
connect(m_resourceProvider , SIGNAL(sigNodeChanged(KisNodeSP)) , SLOT(slotNodeChanged(KisNodeSP)));
connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int)));
connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool)));
connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool)));
m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure");
connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool)));
m_disablePressureAction->setChecked(true);
connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool)));
connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool)));
connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset()));
connect(m_sliderChooser[0]->getWidget<KisDoubleSliderSpinBox>("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[0]->getWidget<KisDoubleSliderSpinBox>("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[0]->getWidget<KisDoubleSliderSpinBox>("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[1]->getWidget<KisDoubleSliderSpinBox>("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[1]->getWidget<KisDoubleSliderSpinBox>("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[1]->getWidget<KisDoubleSliderSpinBox>("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[2]->getWidget<KisDoubleSliderSpinBox>("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
connect(m_sliderChooser[2]->getWidget<KisDoubleSliderSpinBox>("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
connect(m_sliderChooser[2]->getWidget<KisDoubleSliderSpinBox>("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
//Needed to connect canvas to favorite resource manager
connect(m_viewManager->canvasResourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode()));
connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor)));
connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor)));
connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor)));
// cold initialization
m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor());
m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor());
connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor)));
connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor)));
connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool)));
connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon()));
connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(slotCanvasResourceChanged(int,QVariant)));
slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice());
KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
m_eraserName = "eraser_circle";
m_defaultPresetName = "basic_tip_default";
bool foundEraser = false;
bool foundTip = false;
for (int i=0; i<rserver->resourceCount(); i++) {
KisPaintOpPresetSP resource = rserver->resources().at(i);
if (resource->name().toLower().contains("eraser_circle")) {
m_eraserName = resource->name();
foundEraser = true;
} else if (foundEraser == false && (resource->name().toLower().contains("eraser") ||
resource->filename().toLower().contains("eraser"))) {
m_eraserName = resource->name();
foundEraser = true;
}
if (resource->name().toLower().contains("basic_tip_default")) {
m_defaultPresetName = resource->name();
foundTip = true;
} else if (foundTip == false && (resource->name().toLower().contains("default") ||
resource->filename().toLower().contains("default"))) {
m_defaultPresetName = resource->name();
foundTip = true;
}
}
}
KisPaintopBox::~KisPaintopBox()
{
KisConfig cfg(false);
QMapIterator<TabletToolID, TabletToolData> iter(m_tabletToolMap);
while (iter.hasNext()) {
iter.next();
- //qDebug() << "Writing last used preset for" << iter.key().pointer << iter.key().uniqueID << iter.value().preset->name();
if ((iter.key().pointer) == QTabletEvent::Eraser) {
- cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name());
+ cfg.writeEntry(QString("LastEraser") , iter.value().preset->name());
}
else {
- cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name());
+ cfg.writeEntry(QString("LastPreset"), iter.value().preset->name());
}
}
// Do not delete the widget, since it is global to the application, not owned by the view
m_presetsPopup->setPaintOpSettingsWidget(0);
qDeleteAll(m_paintopOptionWidgets);
delete m_favoriteResourceManager;
for (int i = 0; i < 3; ++i) {
delete m_sliderChooser[i];
}
}
void KisPaintopBox::restoreResource(KoResource* resource)
{
KisPaintOpPreset* preset = dynamic_cast<KisPaintOpPreset*>(resource);
if (preset) {
setCurrentPaintop(preset);
m_presetsPopup->setPresetImage(preset->image());
m_presetsPopup->resourceSelected(resource);
}
}
void KisPaintopBox::newOptionWidgets(const QList<QPointer<QWidget> > &optionWidgetList)
{
if (m_toolOptionsPopup) {
m_toolOptionsPopup->newOptionWidgets(optionWidgetList);
}
}
void KisPaintopBox::resourceSelected(KoResource* resource)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget);
m_presetsPopup->setCreatingBrushFromScratch(false); // show normal UI elements when we are not creating
KisPaintOpPreset* preset = dynamic_cast<KisPaintOpPreset*>(resource);
if (preset && preset != m_resourceProvider->currentPreset()) {
if (!preset->settings()->isLoadable())
return;
if (!m_dirtyPresetsEnabled) {
KisSignalsBlocker blocker(m_optionWidget);
if (!preset->load()) {
warnKrita << "failed to load the preset.";
}
}
//qDebug() << "resourceSelected" << resource->name();
setCurrentPaintop(preset);
m_presetsPopup->setPresetImage(preset->image());
m_presetsPopup->resourceSelected(resource);
}
}
void KisPaintopBox::setCurrentPaintop(const KoID& paintop)
{
KisPaintOpPresetSP preset = activePreset(paintop);
Q_ASSERT(preset && preset->settings());
//qDebug() << "setCurrentPaintop();" << paintop << preset;
setCurrentPaintop(preset);
}
void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset)
{
//qDebug() << "setCurrentPaintop(); " << preset->name();
if (preset == m_resourceProvider->currentPreset()) {
if (preset == m_tabletToolMap[m_currTabletToolID].preset) {
return;
}
}
Q_ASSERT(preset);
const KoID& paintop = preset->paintOp();
m_presetConnections.clear();
if (m_resourceProvider->currentPreset()) {
m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset());
if (m_optionWidget) {
m_optionWidget->hide();
}
}
if (!m_paintopOptionWidgets.contains(paintop))
m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this);
m_optionWidget = m_paintopOptionWidgets[paintop];
KisSignalsBlocker b(m_optionWidget);
preset->setOptionsWidget(m_optionWidget);
m_optionWidget->setImage(m_viewManager->image());
m_optionWidget->setNode(m_viewManager->activeNode());
m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget);
m_resourceProvider->setPaintOpPreset(preset);
Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton);
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset()));
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP)));
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP)));
// load the current brush engine icon for the brush editor toolbar button
m_brushEditorPopupButton->setResource(preset.data());
m_presetsPopup->setCurrentPaintOpId(paintop.id());
////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name();
m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset;
m_tabletToolMap[m_currTabletToolID].preset = preset;
m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp();
if (m_presetsPopup->currentPaintOpId() != paintop.id()) {
// Must change the paintop as the current one is not supported
// by the new colorspace.
dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace";
}
m_currCompositeOpID = preset->settings()->paintOpCompositeOp();
updateCompositeOp(m_currCompositeOpID);
}
void KisPaintopBox::slotUpdateOptionsWidgetPopup()
{
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
KIS_SAFE_ASSERT_RECOVER_RETURN(preset);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget);
m_optionWidget->setConfigurationSafe(preset->settings());
m_presetsPopup->resourceSelected(preset.data());
m_presetsPopup->updateViewSettings();
// the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed
// need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation
m_optionWidget->setImage(m_viewManager->image());
}
KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp)
{
QString defaultName = paintOp.id() + ".kpp";
QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName);
KisPaintOpPresetSP preset = new KisPaintOpPreset(path);
if (!preset->load()) {
preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp);
}
Q_ASSERT(preset);
Q_ASSERT(preset->valid());
return preset;
}
KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp)
{
if (m_paintOpPresetMap[paintOp] == 0) {
m_paintOpPresetMap[paintOp] = defaultPreset(paintOp);
}
return m_paintOpPresetMap[paintOp];
}
void KisPaintopBox::updateCompositeOp(QString compositeOpID)
{
if (!m_optionWidget) return;
KisSignalsBlocker blocker(m_optionWidget);
KisNodeSP node = m_resourceProvider->currentNode();
if (node && node->paintDevice()) {
if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID))
compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
{
KisSignalsBlocker b1(m_cmbCompositeOp);
m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID));
}
if (compositeOpID != m_currCompositeOpID) {
m_currCompositeOpID = compositeOpID;
}
if (compositeOpID == COMPOSITE_ERASE || m_resourceProvider->eraserMode()) {
m_eraseModeButton->setChecked(true);
}
else {
m_eraseModeButton->setChecked(false);
}
}
else if (!node) {
KisSignalsBlocker b1(m_cmbCompositeOp);
m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID));
m_currCompositeOpID = compositeOpID;
}
}
void KisPaintopBox::setWidgetState(int flags)
{
if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) {
m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP);
m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP);
}
if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) {
m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS);
m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS);
}
for (int i = 0; i < 3; ++i) {
if (flags & (ENABLE_OPACITY | DISABLE_OPACITY))
m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY);
if (flags & (ENABLE_FLOW | DISABLE_FLOW))
m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW);
if (flags & (ENABLE_SIZE | DISABLE_SIZE))
m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE);
}
}
void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value)
{
for (int i = 0; i < 3; ++i) {
KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget<KisDoubleSliderSpinBox>(sliderID);
KisSignalsBlocker b(slider);
if (sliderID == "opacity" || sliderID == "flow") { // opacity and flows UI stored at 0-100%
slider->setValue(value*100);
} else {
slider->setValue(value); // brush size
}
}
}
void KisPaintopBox::slotSetPaintop(const QString& paintOpId)
{
if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) {
KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name());
//qDebug() << "slotsetpaintop" << id;
setCurrentPaintop(id);
}
}
void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice)
{
TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice);
//qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId();
m_currTabletToolID = TabletToolID(inputDevice);
if (toolData == m_tabletToolMap.end()) {
KisConfig cfg(true);
KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
KisPaintOpPresetSP preset;
if (inputDevice.pointer() == QTabletEvent::Eraser) {
preset = rserver->resourceByName(cfg.readEntry<QString>(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), m_eraserName));
}
else {
preset = rserver->resourceByName(cfg.readEntry<QString>(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), m_defaultPresetName));
//if (preset)
//qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId();
//else
//qDebug() << "no preset found for" << inputDevice.uniqueTabletId();
}
if (!preset) {
preset = rserver->resourceByName(m_defaultPresetName);
}
if (preset) {
//qDebug() << "inputdevicechanged 1" << preset;
setCurrentPaintop(preset);
}
}
else {
if (toolData->preset) {
//qDebug() << "inputdevicechanged 2" << toolData->preset;
setCurrentPaintop(toolData->preset);
}
else {
//qDebug() << "inputdevicechanged 3" << toolData->paintOpID;
setCurrentPaintop(toolData->paintOpID);
}
}
}
void KisPaintopBox::slotCreatePresetFromScratch(QString paintop)
{
//First try to select an available default preset for that engine. If it doesn't exist, then
//manually set the engine to use a new preset.
KoID id(paintop, KisPaintOpRegistry::instance()->get(paintop)->name());
KisPaintOpPresetSP preset = defaultPreset(id);
slotSetPaintop(paintop); // change the paintop settings area and update the UI
if (!preset) {
m_presetsPopup->setCreatingBrushFromScratch(true); // disable UI elements while creating from scratch
preset = m_resourceProvider->currentPreset();
} else {
m_resourceProvider->setPaintOpPreset(preset);
preset->setOptionsWidget(m_optionWidget);
}
m_presetsPopup->resourceSelected(preset.data()); // this helps update the UI on the brush editor
}
void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value)
{
if (m_viewManager) {
sender()->blockSignals(true);
KisPaintOpPresetSP preset = m_viewManager->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) {
QString compositeOp = preset->settings()->getString("CompositeOp");
updateCompositeOp(compositeOp);
resourceSelected(preset.data());
}
/**
* Update currently selected preset in both the popup widgets
*/
m_presetsChooserPopup->canvasResourceChanged(preset);
m_presetsPopup->currentPresetChanged(preset);
if (key == KisCanvasResourceProvider::CurrentCompositeOp) {
if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) {
updateCompositeOp(m_resourceProvider->currentCompositeOp());
}
}
if (key == KisCanvasResourceProvider::Size) {
setSliderValue("size", m_resourceProvider->size());
}
if (key == KisCanvasResourceProvider::Opacity) {
setSliderValue("opacity", m_resourceProvider->opacity());
}
if (key == KisCanvasResourceProvider::Flow) {
setSliderValue("flow", m_resourceProvider->flow());
}
if (key == KisCanvasResourceProvider::EraserMode) {
m_eraseAction->setChecked(value.toBool());
}
if (key == KisCanvasResourceProvider::DisablePressure) {
m_disablePressureAction->setChecked(value.toBool());
}
if (key == KisCanvasResourceProvider::MirrorHorizontal) {
m_hMirrorAction->setChecked(value.toBool());
}
if (key == KisCanvasResourceProvider::MirrorVertical) {
m_vMirrorAction->setChecked(value.toBool());
}
sender()->blockSignals(false);
}
}
void KisPaintopBox::slotSetupDefaultPreset()
{
KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp());
preset->setOptionsWidget(m_optionWidget);
m_resourceProvider->setPaintOpPreset(preset);
// tell the brush editor that the resource has changed
// so it can update everything
m_presetsPopup->resourceSelected(preset.data());
}
void KisPaintopBox::slotNodeChanged(const KisNodeSP node)
{
if (m_previousNode.isValid() && m_previousNode->paintDevice())
disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*)));
// Reconnect colorspace change of node
if (node && node->paintDevice()) {
connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*)));
m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID);
m_previousNode = node;
slotColorSpaceChanged(node->colorSpace());
}
if (m_optionWidget) {
m_optionWidget->setNode(node);
}
}
void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace)
{
m_cmbCompositeOp->validate(colorSpace);
}
void KisPaintopBox::slotToggleEraseMode(bool checked)
{
const bool oldEraserMode = m_resourceProvider->eraserMode();
m_resourceProvider->setEraserMode(checked);
if (oldEraserMode != checked && m_eraserBrushSizeEnabled) {
const qreal currentSize = m_resourceProvider->size();
KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings();
// remember brush size. set the eraser size to the normal brush size if not set
if (checked) {
settings->setSavedBrushSize(currentSize);
if (qFuzzyIsNull(settings->savedEraserSize())) {
settings->setSavedEraserSize(currentSize);
}
} else {
settings->setSavedEraserSize(currentSize);
if (qFuzzyIsNull(settings->savedBrushSize())) {
settings->setSavedBrushSize(currentSize);
}
}
//update value in UI (this is the main place the value is 'stored' in memory)
qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize();
m_resourceProvider->setSize(newSize);
}
if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) {
const qreal currentOpacity = m_resourceProvider->opacity();
KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings();
// remember brush opacity. set the eraser opacity to the normal brush opacity if not set
if (checked) {
settings->setSavedBrushOpacity(currentOpacity);
if (qFuzzyIsNull(settings->savedEraserOpacity())) {
settings->setSavedEraserOpacity(currentOpacity);
}
} else {
settings->setSavedEraserOpacity(currentOpacity);
if (qFuzzyIsNull(settings->savedBrushOpacity())) {
settings->setSavedBrushOpacity(currentOpacity);
}
}
//update value in UI (this is the main place the value is 'stored' in memory)
qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity();
m_resourceProvider->setOpacity(newOpacity);
}
}
void KisPaintopBox::slotSetCompositeMode(int index)
{
Q_UNUSED(index);
QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id();
m_resourceProvider->setCurrentCompositeOp(compositeOp);
}
void KisPaintopBox::slotHorizontalMirrorChanged(bool value)
{
m_resourceProvider->setMirrorHorizontal(value);
}
void KisPaintopBox::slotVerticalMirrorChanged(bool value)
{
m_resourceProvider->setMirrorVertical(value);
}
void KisPaintopBox::sliderChanged(int n)
{
if (!m_optionWidget) // widget will not exist if the are no documents open
return;
KisSignalsBlocker blocker(m_optionWidget);
// flow and opacity are shown as 0-100% on the UI, but their data is actually 0-1. Convert those two values
// back for further work
qreal opacity = m_sliderChooser[n]->getWidget<KisDoubleSliderSpinBox>("opacity")->value()/100;
qreal flow = m_sliderChooser[n]->getWidget<KisDoubleSliderSpinBox>("flow")->value()/100;
qreal size = m_sliderChooser[n]->getWidget<KisDoubleSliderSpinBox>("size")->value();
setSliderValue("opacity", opacity);
setSliderValue("flow" , flow);
setSliderValue("size" , size);
if (m_presetsEnabled) {
// IMPORTANT: set the PaintOp size before setting the other properties
// it won't work the other way
// TODO: why?!
m_resourceProvider->setSize(size);
m_resourceProvider->setOpacity(opacity);
m_resourceProvider->setFlow(flow);
KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings());
propertiesProxy->setProperty("OpacityValue", opacity);
propertiesProxy->setProperty("FlowValue", flow);
m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data());
} else {
m_resourceProvider->setOpacity(opacity);
}
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
}
void KisPaintopBox::slotSlider1Changed()
{
sliderChanged(0);
}
void KisPaintopBox::slotSlider2Changed()
{
sliderChanged(1);
}
void KisPaintopBox::slotSlider3Changed()
{
sliderChanged(2);
}
void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId)
{
Q_UNUSED(canvas);
Q_UNUSED(toolId);
if (!m_viewManager->canvasBase()) return;
QString id = KoToolManager::instance()->activeToolId();
KisTool* tool = dynamic_cast<KisTool*>(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id));
if (tool) {
int flags = tool->flags();
if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) {
setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY);
} else {
setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY);
}
if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) {
setWidgetState(ENABLE_PRESETS);
if (!m_resourceProvider->currentPreset()) return;
// block updates of avoid some over updating of the option widget
m_blockUpdate = true;
setSliderValue("size", m_resourceProvider->size());
{
qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity();
m_resourceProvider->setOpacity(opacity);
setSliderValue("opacity", opacity);
setWidgetState(ENABLE_OPACITY);
}
{
setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow());
setWidgetState(ENABLE_FLOW);
}
{
updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp());
setWidgetState(ENABLE_COMPOSITEOP);
}
m_blockUpdate = false;
m_presetsEnabled = true;
} else {
setWidgetState(DISABLE_PRESETS);
m_presetsEnabled = false;
}
if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) {
setWidgetState(ENABLE_SIZE | ENABLE_FLOW);
} else {
setWidgetState(DISABLE_SIZE | DISABLE_FLOW);
}
} else setWidgetState(DISABLE_ALL);
}
void KisPaintopBox::slotPreviousFavoritePreset()
{
if (!m_favoriteResourceManager) return;
QVector<KisPaintOpPresetSP> presets = m_favoriteResourceManager->favoritePresetList();
for (int i=0; i < presets.size(); ++i) {
if (m_resourceProvider->currentPreset() &&
m_resourceProvider->currentPreset()->name() == presets[i]->name()) {
if (i > 0) {
m_favoriteResourceManager->slotChangeActivePaintop(i - 1);
} else {
m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1);
}
//floating message should have least 2 lines, otherwise
//preset thumbnail will be too small to distinguish
//(because size of image on floating message depends on amount of lines in msg)
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
return;
}
}
}
void KisPaintopBox::slotNextFavoritePreset()
{
if (!m_favoriteResourceManager) return;
QVector<KisPaintOpPresetSP> presets = m_favoriteResourceManager->favoritePresetList();
for(int i = 0; i < presets.size(); ++i) {
if (m_resourceProvider->currentPreset()->name() == presets[i]->name()) {
if (i < m_favoriteResourceManager->numFavoritePresets() - 1) {
m_favoriteResourceManager->slotChangeActivePaintop(i + 1);
} else {
m_favoriteResourceManager->slotChangeActivePaintop(0);
}
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
return;
}
}
}
void KisPaintopBox::slotSwitchToPreviousPreset()
{
if (m_resourceProvider->previousPreset()) {
//qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset();
setCurrentPaintop(m_resourceProvider->previousPreset());
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
}
}
void KisPaintopBox::slotUnsetEraseMode()
{
m_eraseAction->setChecked(false);
}
void KisPaintopBox::slotToggleAlphaLockMode(bool checked)
{
if (checked) {
m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked"));
} else {
m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked"));
}
m_resourceProvider->setGlobalAlphaLock(checked);
}
void KisPaintopBox::slotDisablePressureMode(bool checked)
{
if (checked) {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
} else {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked"));
}
m_resourceProvider->setDisablePressure(checked);
}
void KisPaintopBox::slotReloadPreset()
{
KisSignalsBlocker blocker(m_optionWidget);
//Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading.
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name());
if (preset) {
preset->load();
}
+
+ if (m_resourceProvider->currentPreset() != preset) {
+ m_resourceProvider->setPaintOpPreset(preset);
+ } else {
+ /**
+ * HACK ALERT: here we emit a private signal from the resource manager to
+ * ensure that all the subscribers of resource-changed signal got the
+ * notification. That is especially important for
+ * KisPaintopTransformationConnector. See bug 392622.
+ */
+
+ emit m_resourceProvider->resourceManager()->canvasResourceChanged(
+ KisCanvasResourceProvider::CurrentPaintOpPreset,
+ QVariant::fromValue(preset));
+ }
}
void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed
{
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
{
/**
* Here we postpone all the settings updates events until the entire writing
* operation will be finished. As soon as it is finished, the updates will be
* emitted happily (if there were any).
*/
KisPaintOpPreset::UpdatedPostponer postponer(preset.data());
QStringList preserveProperties;
preserveProperties << "lodUserAllowed";
preserveProperties << "lodSizeThreshold";
// clear all the properties before dumping the stuff into the preset,
// some of the options add the values incrementally
// (e.g. KisPaintOpUtils::RequiredBrushFilesListTag), therefore they
// may add up if we pass the same preset multiple times
preset->settings()->resetSettings(preserveProperties);
m_optionWidget->writeConfigurationSafe(const_cast<KisPaintOpSettings*>(preset->settings().data()));
}
// we should also update the preset strip to update the status of the "dirty" mark
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
// TODO!!!!!!!!
//m_presetsPopup->updateViewSettings();
}
void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p)
{
QMapIterator<QString, QVariant> i(p->getProperties());
while (i.hasNext()) {
i.next();
m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value()));
if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) {
m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous");
}
}
slotGuiChangedCurrentPreset();
}
void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p)
{
KisSignalsBlocker blocker(m_optionWidget);
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
{
KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data());
QMapIterator<QString, QVariant> i(p->getProperties());
while (i.hasNext()) {
i.next();
if (preset->settings()->hasProperty(i.key() + "_previous")) {
preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous"));
preset->settings()->removeProperty(i.key() + "_previous");
}
}
}
}
void KisPaintopBox::slotDirtyPresetToggled(bool value)
{
if (!value) {
slotReloadPreset();
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
m_presetsPopup->updateViewSettings();
}
m_dirtyPresetsEnabled = value;
KisConfig cfg(false);
cfg.setUseDirtyPresets(m_dirtyPresetsEnabled);
}
void KisPaintopBox::slotEraserBrushSizeToggled(bool value)
{
m_eraserBrushSizeEnabled = value;
KisConfig cfg(false);
cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled);
}
void KisPaintopBox::slotEraserBrushOpacityToggled(bool value)
{
m_eraserBrushOpacityEnabled = value;
KisConfig cfg(false);
cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled);
}
void KisPaintopBox::slotUpdateSelectionIcon()
{
m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal"));
m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical"));
KisConfig cfg(true);
if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) {
m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure"));
}
m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01"));
m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02"));
m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose"));
m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser"));
m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh"));
if (m_disablePressureAction->isChecked()) {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
} else {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked"));
}
}
void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) {
m_resourceProvider->setMirrorHorizontalLock(toggleLock);
}
void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) {
m_resourceProvider->setMirrorVerticalLock(toggleLock);
}
void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) {
m_resourceProvider->setMirrorHorizontalHideDecorations(toggled);
}
void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) {
m_resourceProvider->setMirrorVerticalHideDecorations(toggled);
}
void KisPaintopBox::slotMoveToCenterMirrorX() {
m_resourceProvider->mirrorHorizontalMoveCanvasToCenter();
}
void KisPaintopBox::slotMoveToCenterMirrorY() {
m_resourceProvider->mirrorVerticalMoveCanvasToCenter();
}
diff --git a/libs/ui/kis_paintop_box.h b/libs/ui/kis_paintop_box.h
index 6423cc835a..173f67f23a 100644
--- a/libs/ui/kis_paintop_box.h
+++ b/libs/ui/kis_paintop_box.h
@@ -1,270 +1,266 @@
/*
* kis_paintop_box.h - part of KImageShop/Krayon/Krita
*
* Copyright (c) 2004-2008 Boudewijn Rempt (boud@valdyas.org)
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* 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_PAINTOP_BOX_H_
#define KIS_PAINTOP_BOX_H_
#include <QMap>
#include <QWidget>
#include <QList>
#include <KoID.h>
#include <KoInputDevice.h>
#include <kis_types.h>
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_locked_properties_proxy.h>
#include <brushengine/kis_locked_properties_server.h>
#include <brushengine/kis_locked_properties.h>
#include "kritaui_export.h"
#include "kis_signal_auto_connection.h"
#include "kis_signal_compressor.h"
class QToolButton;
class QString;
class QHBoxLayout;
class KoColorSpace;
class KoResource;
class KoCanvasController;
class KisViewManager;
class KisCanvasResourceProvider;
class KisPopupButton;
class KisIconWidget;
class KisToolOptionsPopup;
class KisPaintOpPresetsPopup;
class KisPaintOpPresetsChooserPopup;
class KisPaintOpConfigWidget;
class KisCompositeOpComboBox;
class KisWidgetChooser;
class KisFavoriteResourceManager;
class KisAction;
class KisPresetSaveWidget;
/**
* This widget presents all paintops that a user can paint with.
* Paintops represent real-world tools or the well-known Shoup
* computer equivalents that do nothing but change color.
*
* To incorporate the dirty preset functionality and locked settings
* the following slots are added
* void slotReloadPreset();
void slotGuiChangedCurrentPreset();
void slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p);
void slotDropLockedOption(KisPropertiesConfigurationSP p);
void slotDirtyPresetToggled(bool);
Every time a value is changed in a preset, the preset is made dirty through the onChange() function.
For Locked Settings however, a changed Locked Setting will not cause a preset to become dirty. That is
because it borrows its values from the KisLockedPropertiesServer.
Hence the dirty state of the Preset is kept consistent before and after a writeConfiguration operation in most cases.
* XXX: When we have a lot of paintops, replace the listbox
* with a table, and for every category a combobox.
*
* XXX: instead of text, use pretty pictures.
*/
class KRITAUI_EXPORT KisPaintopBox : public QWidget
{
Q_OBJECT
enum {
ENABLE_PRESETS = 0x0001,
DISABLE_PRESETS = 0x0002,
ENABLE_COMPOSITEOP = 0x0004,
DISABLE_COMPOSITEOP = 0x0008,
ENABLE_OPACITY = 0x0010,
DISABLE_OPACITY = 0x0020,
ENABLE_FLOW = 0x0040,
DISABLE_FLOW = 0x0080,
ENABLE_SIZE = 0x0100,
DISABLE_SIZE = 0x0200,
ENABLE_ALL = 0x5555,
DISABLE_ALL = 0xAAAA
};
public:
KisPaintopBox(KisViewManager* view, QWidget* parent, const char* name);
~KisPaintopBox() override;
void restoreResource(KoResource* resource);
/**
* Update the option widgets to the argument ones, removing the currently set widgets.
*/
void newOptionWidgets(const QList<QPointer<QWidget> > & optionWidgetList);
KisFavoriteResourceManager *favoriteResourcesManager() { return m_favoriteResourceManager; }
public Q_SLOTS:
void slotColorSpaceChanged(const KoColorSpace* colorSpace);
void slotInputDeviceChanged(const KoInputDevice & inputDevice);
void slotCanvasResourceChanged(int key, const QVariant& v);
void resourceSelected(KoResource* resource);
/// This should take care of creating a new brush preset from scratch
/// It will either load the default brush preset for the engine,
/// or create a new empty preset if a default preset does not exist
void slotCreatePresetFromScratch(QString paintop);
private:
void setCurrentPaintop(const KoID& paintop);
void setCurrentPaintop(KisPaintOpPresetSP preset);
KisPaintOpPresetSP defaultPreset(const KoID& paintOp);
KisPaintOpPresetSP activePreset(const KoID& paintOp);
void updateCompositeOp(QString compositeOpID);
void setWidgetState(int flags);
void setSliderValue(const QString& sliderID, qreal value);
void sliderChanged(int n);
private Q_SLOTS:
void slotSetupDefaultPreset();
void slotNodeChanged(const KisNodeSP node);
void slotToggleEraseMode(bool checked);
void slotSetCompositeMode(int index);
void slotSetPaintop(const QString& paintOpId);
void slotHorizontalMirrorChanged(bool value);
void slotVerticalMirrorChanged(bool value);
void slotSlider1Changed();
void slotSlider2Changed();
void slotSlider3Changed();
void slotToolChanged(KoCanvasController* canvas, int toolId);
void slotPreviousFavoritePreset();
void slotNextFavoritePreset();
void slotSwitchToPreviousPreset();
void slotUnsetEraseMode();
void slotToggleAlphaLockMode(bool);
void slotDisablePressureMode(bool);
void slotReloadPreset();
void slotGuiChangedCurrentPreset();
void slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p);
void slotDropLockedOption(KisPropertiesConfigurationSP p);
void slotDirtyPresetToggled(bool);
void slotEraserBrushSizeToggled(bool);
void slotEraserBrushOpacityToggled(bool);
void slotUpdateSelectionIcon();
void slotLockXMirrorToggle(bool);
void slotLockYMirrorToggle(bool);
void slotMoveToCenterMirrorX();
void slotMoveToCenterMirrorY();
void slotHideDecorationMirrorX(bool);
void slotHideDecorationMirrorY(bool);
void slotUpdateOptionsWidgetPopup();
private:
KisCanvasResourceProvider* m_resourceProvider;
QHBoxLayout* m_layout;
QWidget* m_paintopWidget;
KisPaintOpConfigWidget* m_optionWidget;
KisPopupButton* m_toolOptionsPopupButton;
KisPresetSaveWidget* m_savePresetWidget;
KisIconWidget* m_brushEditorPopupButton;
KisPopupButton* m_presetSelectorPopupButton;
KisCompositeOpComboBox* m_cmbCompositeOp;
QToolButton* m_eraseModeButton;
QToolButton* m_alphaLockButton;
QToolButton* m_hMirrorButton;
QToolButton* m_vMirrorButton;
KisToolOptionsPopup* m_toolOptionsPopup;
KisPaintOpPresetsPopup* m_presetsPopup;
KisPaintOpPresetsChooserPopup* m_presetsChooserPopup;
KisViewManager* m_viewManager;
KisPopupButton* m_workspaceWidget;
KisWidgetChooser* m_sliderChooser[3];
QMap<KoID, KisPaintOpConfigWidget*> m_paintopOptionWidgets;
KisFavoriteResourceManager* m_favoriteResourceManager;
QToolButton* m_reloadButton;
KisAction* m_eraseAction;
KisAction* m_reloadAction;
KisAction* m_disablePressureAction;
QString m_currCompositeOpID;
KisNodeWSP m_previousNode;
KisAction* m_hMirrorAction;
KisAction* m_vMirrorAction;
KisAction* hideCanvasDecorationsX;
KisAction* lockActionX;
KisAction* moveToCenterActionX;
KisAction* hideCanvasDecorationsY;
KisAction* lockActionY;
KisAction* moveToCenterActionY;
struct TabletToolID {
TabletToolID(const KoInputDevice& dev) {
- uniqueID = dev.uniqueTabletId();
// Only the eraser is special, and we don't look at Cursor
pointer = QTabletEvent::Pen;
if (dev.pointer() == QTabletEvent::Eraser) {
pointer = QTabletEvent::Eraser;
}
}
bool operator == (const TabletToolID& id) const {
- return pointer == id.pointer && uniqueID == id.uniqueID;
+ return pointer == id.pointer;
}
bool operator < (const TabletToolID& id) const {
- if (uniqueID == id.uniqueID)
- return pointer < id.pointer;
- return uniqueID < id.uniqueID;
+ return pointer < id.pointer;
}
QTabletEvent::PointerType pointer;
- qint64 uniqueID;
};
struct TabletToolData {
KoID paintOpID;
KisPaintOpPresetSP preset;
};
typedef QMap<TabletToolID, TabletToolData> TabletToolMap;
typedef QMap<KoID, KisPaintOpPresetSP> PaintOpPresetMap;
TabletToolMap m_tabletToolMap;
PaintOpPresetMap m_paintOpPresetMap;
TabletToolID m_currTabletToolID;
bool m_presetsEnabled;
bool m_blockUpdate;
bool m_dirtyPresetsEnabled;
bool m_eraserBrushSizeEnabled;
bool m_eraserBrushOpacityEnabled;
KisSignalAutoConnectionsStore m_presetConnections;
QString m_eraserName;
QString m_defaultPresetName;
};
#endif //KIS_PAINTOP_BOX_H_
diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp
index 5a9c9b0896..bd2440242b 100644
--- a/libs/ui/kis_png_converter.cpp
+++ b/libs/ui/kis_png_converter.cpp
@@ -1,1393 +1,1394 @@
/*
* Copyright (c) 2005-2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_png_converter.h"
// A big thank to Glenn Randers-Pehrson for his wonderful
// documentation of libpng available at
// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html
#ifndef PNG_MAX_UINT // Removed in libpng 1.4
#define PNG_MAX_UINT PNG_UINT_31_MAX
#endif
#include <KoConfig.h> // WORDS_BIGENDIAN
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <limits.h>
#include <stdio.h>
#include <zlib.h>
#include <QBuffer>
#include <QFile>
#include <QApplication>
#include <klocalizedstring.h>
#include <QUrl>
#include <KoColorSpace.h>
#include <KoDocumentInfo.h>
#include <KoID.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoColor.h>
#include <KoUnit.h>
#include <kis_config.h>
#include <kis_painter.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_iterator_ng.h>
#include <kis_layer.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_meta_data_io_backend.h>
#include <kis_meta_data_store.h>
#include <KoColorModelStandardIds.h>
#include "dialogs/kis_dlg_png_import.h"
#include "kis_clipboard.h"
#include <kis_cursor_override_hijacker.h>
#include "kis_undo_stores.h"
+#include <kis_assert.h>
+
namespace
{
int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha)
{
QString id = cs->id();
if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") {
return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY;
}
if (id == "RGBA" || id == "RGBA16") {
return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
}
return -1;
}
bool colorSpaceIdSupported(const QString &id)
{
return id == "RGBA" || id == "RGBA16" ||
id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16";
}
QPair<QString, QString> getColorSpaceForColorType(int color_type, int color_nb_bits)
{
QPair<QString, QString> r;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
r.first = RGBAColorModelID.id();
r.second = Integer8BitsColorDepthID.id();
} else {
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
r.first = GrayAColorModelID.id();
} else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) {
r.first = RGBAColorModelID.id();
}
if (color_nb_bits == 16) {
r.second = Integer16BitsColorDepthID.id();
} else if (color_nb_bits <= 8) {
r.second = Integer8BitsColorDepthID.id();
}
}
return r;
}
void fillText(png_text* p_text, const char* key, QString& text)
{
p_text->compression = PNG_TEXT_COMPRESSION_zTXt;
p_text->key = const_cast<char *>(key);
char* textc = new char[text.length()+1];
strcpy(textc, text.toLatin1());
p_text->text = textc;
p_text->text_length = text.length() + 1;
}
long formatStringList(char *string, const size_t length, const char *format, va_list operands)
{
int n = vsnprintf(string, length, format, operands);
if (n < 0)
string[length-1] = '\0';
return((long) n);
}
long formatString(char *string, const size_t length, const char *format, ...)
{
long n;
va_list operands;
va_start(operands, format);
n = (long) formatStringList(string, length, format, operands);
va_end(operands);
return(n);
}
void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data)
{
png_textp text;
png_uint_32 allocated_length, description_length;
const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl;
text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text));
description_length = profile_type.length();
allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length);
text[0].text = (png_charp) png_malloc(ping, allocated_length);
QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1();
QByteArray keyData = key.toLatin1();
text[0].key = keyData.data();
uchar* sp = (uchar*)profile_data.data();
png_charp dp = text[0].text;
*dp++ = '\n';
memcpy(dp, profile_type.toLatin1().constData(), profile_type.length());
dp += description_length;
*dp++ = '\n';
formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length());
dp += 8;
for (long i = 0; i < (long) profile_data.length(); i++) {
if (i % 36 == 0)
*dp++ = '\n';
*(dp++) = (char) hex[((*sp >> 4) & 0x0f)];
*(dp++) = (char) hex[((*sp++) & 0x0f)];
}
*dp++ = '\n';
*dp = '\0';
text[0].text_length = (png_size_t)(dp - text[0].text);
text[0].compression = -1;
if (text[0].text_length <= allocated_length)
png_set_text(ping, ping_info, text, 1);
png_free(ping, text[0].text);
png_free(ping, text);
}
QByteArray png_read_raw_profile(png_textp text)
{
QByteArray profile;
static const unsigned char unhex[103] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
13, 14, 15
};
png_charp sp = text[0].text + 1;
/* look for newline */
while (*sp != '\n')
sp++;
/* look for length */
while (*sp == '\0' || *sp == ' ' || *sp == '\n')
sp++;
png_uint_32 length = (png_uint_32) atol(sp);
while (*sp != ' ' && *sp != '\n')
sp++;
if (length == 0) {
return profile;
}
profile.resize(length);
/* copy profile, skipping white space and column 1 "=" signs */
unsigned char *dp = (unsigned char*)profile.data();
png_uint_32 nibbles = length * 2;
for (png_uint_32 i = 0; i < nibbles; i++) {
while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
if (*sp == '\0') {
return QByteArray();
}
sp++;
}
if (i % 2 == 0)
*dp = (unsigned char)(16 * unhex[(int) *sp++]);
else
(*dp++) += unhex[(int) *sp++];
}
return profile;
}
void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize)
{
dbgFile << "Decoding " << type << " " << text[0].key;
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type);
Q_ASSERT(exifIO);
QByteArray rawProfile = png_read_raw_profile(text);
if (headerSize > 0) {
rawProfile.remove(0, headerSize);
}
if (rawProfile.size() > 0) {
QBuffer buffer;
buffer.setData(rawProfile);
exifIO->loadFrom(store, &buffer);
} else {
dbgFile << "Decoding failed";
}
}
}
KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode)
{
// Q_ASSERT(doc);
// Q_ASSERT(adapter);
m_doc = doc;
m_stop = false;
m_max_row = 0;
m_image = 0;
m_batchMode = batchMode;
}
KisPNGConverter::~KisPNGConverter()
{
}
class KisPNGReadStream
{
public:
KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
}
int nextValue() {
if (m_posinc == 0) {
m_posinc = 8;
m_buf++;
}
m_posinc -= m_depth;
return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1));
}
private:
quint32 m_posinc, m_depth;
quint8* m_buf;
};
class KisPNGWriteStream
{
public:
KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
*m_buf = 0;
}
void setNextValue(int v) {
if (m_posinc == 0) {
m_posinc = 8;
m_buf++;
*m_buf = 0;
}
m_posinc -= m_depth;
*m_buf = (v << m_posinc) | *m_buf;
}
private:
quint32 m_posinc, m_depth;
quint8* m_buf;
};
class KisPNGReaderAbstract
{
public:
KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {}
virtual ~KisPNGReaderAbstract() {}
virtual png_bytep readLine() = 0;
protected:
png_structp png_ptr;
int width, height;
};
class KisPNGReaderLineByLine : public KisPNGReaderAbstract
{
public:
KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) {
png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
row_pointer = new png_byte[rowbytes];
}
~KisPNGReaderLineByLine() override {
delete[] row_pointer;
}
png_bytep readLine() override {
png_read_row(png_ptr, row_pointer, 0);
return row_pointer;
}
private:
png_bytep row_pointer;
};
class KisPNGReaderFullImage : public KisPNGReaderAbstract
{
public:
KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) {
row_pointers = new png_bytep[height];
png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
for (int i = 0; i < height; i++) {
row_pointers[i] = new png_byte[rowbytes];
}
png_read_image(png_ptr, row_pointers);
}
~KisPNGReaderFullImage() override {
for (int i = 0; i < height; i++) {
delete[] row_pointers[i];
}
delete[] row_pointers;
}
png_bytep readLine() override {
return row_pointers[y++];
}
private:
png_bytepp row_pointers;
int y;
};
static
void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr);
while (length) {
int nr = in->read((char*)data, length);
if (nr <= 0) {
png_error(png_ptr, "Read Error");
return;
}
length -= nr;
}
}
static
void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr);
uint nr = out->write((char*)data, length);
if (nr != length) {
png_error(png_ptr, "Write Error");
return;
}
}
static
void _flush_fn(png_structp png_ptr)
{
Q_UNUSED(png_ptr);
}
-KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod)
+KisImportExportErrorCode KisPNGConverter::buildImage(QIODevice* iod)
{
dbgFile << "Start decoding PNG File";
png_byte signature[8];
iod->peek((char*)signature, 8);
#if PNG_LIBPNG_VER < 10400
if (!png_check_sig(signature, 8)) {
#else
if (png_sig_cmp(signature, 0, 8) != 0) {
#endif
iod->close();
- return (KisImageBuilder_RESULT_BAD_FETCH);
+ return (ImportExportCodes::FileFormatIncorrect);
}
// Initialize the internal structures
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png_ptr) {
iod->close();
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
iod->close();
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::Failure);
}
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
iod->close();
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::Failure);
}
// Catch errors
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
iod->close();
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::Failure);
}
// Initialize the special
png_set_read_fn(png_ptr, iod, _read_fn);
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
// read all PNG info up to image data
png_read_info(png_ptr, info_ptr);
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) {
png_set_expand(png_ptr);
}
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) {
png_set_packing(png_ptr);
}
if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE &&
(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) {
png_set_expand(png_ptr);
}
png_read_update_info(png_ptr, info_ptr);
// Read information about the png
png_uint_32 width, height;
int color_nb_bits, color_type, interlace_type;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0);
dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl;
// swap byteorder on little endian machines.
#ifndef WORDS_BIGENDIAN
if (color_nb_bits > 8)
png_set_swap(png_ptr);
#endif
// Determine the colorspace
QPair<QString, QString> csName = getColorSpaceForColorType(color_type, color_nb_bits);
if (csName.first.isEmpty()) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
iod->close();
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA);
// Read image profile
png_charp profile_name;
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
png_bytep profile_data;
#else
png_charp profile_data;
#endif
int compression_type;
png_uint_32 proflen;
// Get the various optional chunks
// https://www.w3.org/TR/PNG/#11cHRM
#if defined(PNG_cHRM_SUPPORTED)
double whitePointX, whitePointY;
double redX, redY;
double greenX, greenY;
double blueX, blueY;
png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY);
dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY;
#endif
// https://www.w3.org/TR/PNG/#11gAMA
#if defined(PNG_GAMMA_SUPPORTED)
double gamma;
png_get_gAMA(png_ptr, info_ptr, &gamma);
dbgFile << "gAMA" << gamma;
#endif
// https://www.w3.org/TR/PNG/#11sRGB
#if defined(PNG_sRGB_SUPPORTED)
int sRGBIntent;
png_get_sRGB(png_ptr, info_ptr, &sRGBIntent);
dbgFile << "sRGB" << sRGBIntent;
#endif
bool fromBlender = false;
png_text* text_ptr;
int num_comments;
png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
for (int i = 0; i < num_comments; i++) {
QString key = QString(text_ptr[i].key).toLower();
if (key == "file") {
QString relatedFile = text_ptr[i].text;
if (relatedFile.contains(".blend", Qt::CaseInsensitive)){
fromBlender=true;
}
}
}
bool loadedImageIsHDR = false;
const KoColorProfile* profile = 0;
if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) {
QByteArray profile_rawdata;
// XXX: Hardcoded for icc type -- is that correct for us?
profile_rawdata.resize(proflen);
memcpy(profile_rawdata.data(), profile_data, proflen);
profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata);
Q_CHECK_PTR(profile);
if (profile) {
// dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo();
if (!profile->isSuitableForOutput()) {
dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user
}
}
loadedImageIsHDR = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0;
}
else {
dbgFile << "no embedded profile, will use the default profile";
if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) {
KisConfig cfg(true);
quint32 behaviour = cfg.pasteBehaviour();
if (behaviour == PASTE_ASK) {
KisDlgPngImport dlg(m_path, csName.first, csName.second);
KisCursorOverrideHijacker hijacker;
Q_UNUSED(hijacker);
dlg.exec();
if (!dlg.profile().isEmpty()) {
profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile());
}
}
}
dbgFile << "no embedded profile, will use the default profile";
}
const QString colorSpaceId =
KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second);
// Check that the profile is used by the color space
if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) {
warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second;
profile = 0;
}
// Retrieve a pointer to the colorspace
KoColorConversionTransformation* transform = 0;
const KoColorSpace* cs = 0;
if (loadedImageIsHDR &&
csName.first == RGBAColorModelID.id() &&
csName.second == Integer16BitsColorDepthID.id()) {
const KoColorSpace *p2020PQCS =
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Integer16BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p2020PQProfile());
cs = p2020PQCS;
} else if (profile && profile->isSuitableForOutput()) {
dbgFile << "image has embedded profile: " << profile->name() << "\n";
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile);
}
else {
if (csName.first == RGBAColorModelID.id()) {
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc");
} else if (csName.first == GrayAColorModelID.id()) {
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "Gray-D50-elle-V2-srgbtrc.icc");
} else {
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0);
}
//TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation
// Create the cmsTransform if needed
if (profile) {
transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
}
if (cs == 0) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
// Creating the KisImageSP
if (m_image == 0) {
KisUndoStore *store = m_doc ? m_doc->createUndoStore() : new KisSurrogateUndoStore();
m_image = new KisImage(store, width, height, cs, "built image");
}
// Read resolution
int unit_type;
png_uint_32 x_resolution, y_resolution;
png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) {
m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points
}
double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1);
KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX);
// Read comments/texts...
png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
if (m_doc) {
KoDocumentInfo * info = m_doc->documentInfo();
dbgFile << "There are " << num_comments << " comments in the text";
for (int i = 0; i < num_comments; i++) {
QString key = QString(text_ptr[i].key).toLower();
dbgFile << "key: " << text_ptr[i].key
<< ", containing: " << text_ptr[i].text
<< ": " << (key == "raw profile type exif " ? "isExif" : "something else");
if (key == "title") {
info->setAboutInfo("title", text_ptr[i].text);
} else if (key == "description") {
info->setAboutInfo("comment", text_ptr[i].text);
} else if (key == "author") {
info->setAuthorInfo("creator", text_ptr[i].text);
} else if (key.contains("raw profile type exif")) {
decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6);
} else if (key.contains("raw profile type iptc")) {
decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14);
} else if (key.contains("raw profile type xmp")) {
decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0);
} else if (key == "version") {
m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text)));
} else if (key == "preset") {
m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text)));
}
}
}
// Read image data
QScopedPointer<KisPNGReaderAbstract> reader;
try {
if (interlace_type == PNG_INTERLACE_ADAM7) {
reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height));
} else {
reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height));
}
} catch (const std::bad_alloc& e) {
// new png_byte[] may raise such an exception if the image
// is invalid / to large.
dbgFile << "bad alloc: " << e.what();
// Free only the already allocated png_byte instances.
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::Failure);
}
// Read the palette if the file is indexed
png_colorp palette ;
int num_palette;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
}
// Read the transparency palette
quint8 palette_alpha[256];
memset(palette_alpha, 255, 256);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_bytep alpha_ptr;
int num_alpha;
png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0);
for (int i = 0; i < num_alpha; ++i) {
palette_alpha[i] = alpha_ptr[i];
}
}
}
for (png_uint_32 y = 0; y < height; y++) {
KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width);
png_bytep row_pointer = reader->readLine();
switch (color_type) {
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if (color_nb_bits == 16) {
quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
do {
quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
d[0] = *(src++);
if (hasalpha) {
d[1] = *(src++);
} else {
d[1] = quint16_MAX;
}
if (transform) transform->transformInPlace(reinterpret_cast<quint8*>(d), reinterpret_cast<quint8*>(d), 1);
} while (it->nextPixel());
} else {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
d[0] = (quint8)(stream.nextValue() * coeff);
if (hasalpha) {
d[1] = (quint8)(stream.nextValue() * coeff);
} else {
d[1] = UCHAR_MAX;
}
if (transform) transform->transformInPlace(d, d, 1);
} while (it->nextPixel());
}
// FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits"
break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
if (color_nb_bits == 16) {
quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
do {
quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
d[2] = *(src++);
d[1] = *(src++);
d[0] = *(src++);
if (hasalpha) d[3] = *(src++);
else d[3] = quint16_MAX;
if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
} while (it->nextPixel());
} else {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
d[2] = (quint8)(stream.nextValue() * coeff);
d[1] = (quint8)(stream.nextValue() * coeff);
d[0] = (quint8)(stream.nextValue() * coeff);
if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff);
else d[3] = UCHAR_MAX;
if (transform) transform->transformInPlace(d, d, 1);
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_PALETTE: {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
quint8 index = stream.nextValue();
quint8 alpha = palette_alpha[ index ];
if (alpha == 0) {
memset(d, 0, 4);
} else {
png_color c = palette[ index ];
d[2] = c.red;
d[1] = c.green;
d[0] = c.blue;
d[3] = alpha;
}
} while (it->nextPixel());
}
break;
default:
- return KisImageBuilder_RESULT_UNSUPPORTED;
+ return ImportExportCodes::FormatFeaturesUnsupported;
}
}
m_image->addNode(layer.data(), m_image->rootLayer().data());
png_read_end(png_ptr, end_info);
iod->close();
// Freeing memory
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
-KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename)
+KisImportExportErrorCode KisPNGConverter::buildImage(const QString &filename)
{
m_path = filename;
QFile fp(filename);
if (fp.exists()) {
if (!fp.open(QIODevice::ReadOnly)) {
dbgFile << "Failed to open PNG File";
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::FileFormatIncorrect);
}
return buildImage(&fp);
}
- return (KisImageBuilder_RESULT_NOT_EXIST);
+ return (ImportExportCodes::FileNotExist);
}
KisImageSP KisPNGConverter::image()
{
return m_image;
}
bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData)
{
if (store->open(filename)) {
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly)) {
dbgFile << "Could not open for writing:" << filename;
return false;
}
KisPNGConverter pngconv(0);
vKisAnnotationSP_it annotIt = 0;
KisMetaData::Store* metaDataStore = 0;
if (metaData) {
metaDataStore = new KisMetaData::Store(*metaData);
}
KisPNGOptions options;
options.compression = 0;
options.interlace = false;
options.tryToSaveAsIndexed = false;
options.alpha = true;
options.saveSRGBProfile = false;
if (dev->colorSpace()->id() != "RGBA") {
dev = new KisPaintDevice(*dev.data());
dev->convertTo(KoColorSpaceRegistry::instance()->rgb8());
}
- bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
- if (success != KisImageBuilder_RESULT_OK) {
+ KisImportExportErrorCode success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
+ if (!success.isOk()) {
dbgFile << "Saving PNG failed:" << filename;
delete metaDataStore;
return false;
}
delete metaDataStore;
io.close();
if (!store->close()) {
return false;
}
} else {
dbgFile << "Opening of data file failed :" << filename;
return false;
}
return true;
}
-KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
+KisImportExportErrorCode KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
{
dbgFile << "Start writing PNG File " << filename;
// Open a QIODevice for writing
QFile fp (filename);
if (!fp.open(QIODevice::WriteOnly)) {
dbgFile << "Failed to open PNG File for writing";
- return (KisImageBuilder_RESULT_FAILURE);
+ return (KisImportExportErrorCannotWrite(fp.error()));
}
- KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);
+ KisImportExportErrorCode result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);
return result;
}
-KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
+KisImportExportErrorCode KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
{
- if (!device)
- return KisImageBuilder_RESULT_INVALID_ARG;
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(device, ImportExportCodes::InternalError);
if (!options.alpha) {
KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
KoColor c(options.transparencyFillColor, device->colorSpace());
tmp->fill(imageRect, c);
KisPainter gc(tmp);
gc.bitBlt(imageRect.topLeft(), device, imageRect);
gc.end();
device = tmp;
}
if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
|| device->colorSpace()->colorDepthId() == Float32BitsColorDepthID
|| device->colorSpace()->colorDepthId() == Float64BitsColorDepthID
|| options.saveAsHDR) {
const KoColorSpace *dstCS =
KoColorSpaceRegistry::instance()->colorSpace(
device->colorSpace()->colorModelId().id(),
Integer16BitsColorDepthID.id(),
device->colorSpace()->profile());
if (options.saveAsHDR) {
dstCS =
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Integer16BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p2020PQProfile());
}
KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
tmp->makeCloneFromRough(device, imageRect);
tmp->convertTo(dstCS);
device = tmp;
}
KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.forceSRGB) {
options.forceSRGB = false;
}
KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.tryToSaveAsIndexed) {
options.tryToSaveAsIndexed = false;
}
QStringList colormodels = QStringList() << RGBAColorModelID.id() << GrayAColorModelID.id();
if (options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) {
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)");
device = new KisPaintDevice(*device);
device->convertTo(cs);
}
// Initialize structures
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png_ptr) {
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::Failure);
}
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
png_set_check_for_invalid_index(png_ptr, 0);
#endif
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, (png_infopp)0);
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::Failure);
}
// If an error occurs during writing, libpng will jump here
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
- return (KisImageBuilder_RESULT_FAILURE);
+ return (ImportExportCodes::Failure);
}
// Initialize the writing
// png_init_io(png_ptr, fp);
// Setup the progress function
// XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);"
// setProgressTotalSteps(100/*height*/);
/* set the zlib compression level */
png_set_compression_level(png_ptr, options.compression);
png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn);
/* set other zlib parameters */
png_set_compression_mem_level(png_ptr, 8);
png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
png_set_compression_window_bits(png_ptr, 15);
png_set_compression_method(png_ptr, 8);
png_set_compression_buffer_size(png_ptr, 8192);
int color_nb_bits = 8 * device->pixelSize() / device->channelCount();
int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha);
Q_ASSERT(color_type > -1);
// Try to compute a table of color if the colorspace is RGB8f
QScopedArrayPointer<png_color> palette;
int num_palette = 0;
if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8
palette.reset(new png_color[255]);
KisSequentialIterator it(device, imageRect);
bool toomuchcolor = false;
while (it.nextPixel()) {
const quint8* c = it.oldRawData();
bool findit = false;
for (int i = 0; i < num_palette; i++) {
if (palette[i].red == c[2] &&
palette[i].green == c[1] &&
palette[i].blue == c[0]) {
findit = true;
break;
}
}
if (!findit) {
if (num_palette == 255) {
toomuchcolor = true;
break;
}
palette[num_palette].red = c[2];
palette[num_palette].green = c[1];
palette[num_palette].blue = c[0];
num_palette++;
}
}
if (!toomuchcolor) {
dbgFile << "Found a palette of " << num_palette << " colors";
color_type = PNG_COLOR_TYPE_PALETTE;
if (num_palette <= 2) {
color_nb_bits = 1;
} else if (num_palette <= 4) {
color_nb_bits = 2;
} else if (num_palette <= 16) {
color_nb_bits = 4;
} else {
color_nb_bits = 8;
}
} else {
palette.reset();
}
}
int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, KisImageBuilder_RESULT_FAILURE);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, ImportExportCodes::Failure);
png_set_IHDR(png_ptr, info_ptr,
imageRect.width(),
imageRect.height(),
color_nb_bits,
color_type, interlacetype,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
// set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present
bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive);
/*
* This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's
* color management is bugged, so once you give it any incentive to start color managing an sRGB image it
* will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now.
*/
/*if (!options.saveSRGBProfile && sRGB) {
png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
}*/
/** TODO: Firefox still opens the image incorrectly if there is gAMA+cHRM tags
* present. According to the standard it should use iCCP tag with higher priority,
* but it doesn't:
*
* "When the iCCP chunk is present, PNG decoders that recognize it and are capable
* of colour management [ICC] shall ignore the gAMA and cHRM chunks and use
* the iCCP chunk instead and interpret it according to [ICC-1] and [ICC-1A]"
*/
#if 0
if (options.saveAsHDR) {
// https://www.w3.org/TR/PNG/#11gAMA
#if defined(PNG_GAMMA_SUPPORTED)
// the values are set in accurdance of HDR-PNG standard:
// https://www.w3.org/TR/png-hdr-pq/
png_set_gAMA_fixed(png_ptr, info_ptr, 15000);
dbgFile << "gAMA" << "(Rec 2100)";
#endif
#if defined PNG_cHRM_SUPPORTED
png_set_cHRM_fixed(png_ptr, info_ptr,
31270, 32900, // white point
70800, 29200, // red
17000, 79700, // green
13100, 4600 // blue
);
dbgFile << "cHRM" << "(Rec 2100)";
#endif
}
#endif
// we should ensure we don't access non-existing palette object
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, KisImageBuilder_RESULT_FAILURE);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, ImportExportCodes::Failure);
// set the palette
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_PLTE(png_ptr, info_ptr, palette.data(), num_palette);
}
// Save annotation
vKisAnnotationSP_it it = annotationsStart;
while (it != annotationsEnd) {
if (!(*it) || (*it)->type().isEmpty()) {
dbgFile << "Warning: empty annotation";
it++;
continue;
}
dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size();
if ((*it) -> type().startsWith(QString("krita_attribute:"))) { //
// Attribute
// XXX: it should be possible to save krita_attributes in the \"CHUNKs\""
dbgFile << "cannot save this annotation : " << (*it) -> type();
} else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) {
dbgFile << "Saving preset information " << (*it)->description();
png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text));
QByteArray keyData = (*it)->description().toLatin1();
text[0].key = keyData.data();
text[0].text = (char*)(*it)->annotation().data();
text[0].text_length = (*it)->annotation().size();
text[0].compression = -1;
png_set_text(png_ptr, info_ptr, text, 1);
png_free(png_ptr, text);
}
it++;
}
// Save the color profile
const KoColorProfile* colorProfile = device->colorSpace()->profile();
QByteArray colorProfileData = colorProfile->rawData();
if (!sRGB || options.saveSRGBProfile) {
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
const char *typeString = !options.saveAsHDR ? "icc" : "ITUR_2100_PQ_FULL";
png_set_iCCP(png_ptr, info_ptr, (png_const_charp)typeString, PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size());
#else
// older version of libpng has a problem with constness on the parameters
char typeStringICC[] = "icc";
char typeStringHDR[] = "ITUR_2100_PQ_FULL";
char *typeString = !options.saveAsHDR ? typeStringICC : typeStringHDR;
png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size());
#endif
}
// save comments from the document information
// warning: according to the official png spec, the keys need to be capitalized!
if (m_doc) {
png_text texts[4];
int nbtexts = 0;
KoDocumentInfo * info = m_doc->documentInfo();
QString title = info->aboutInfo("title");
if (!title.isEmpty() && options.storeMetaData) {
fillText(texts + nbtexts, "Title", title);
nbtexts++;
}
QString abstract = info->aboutInfo("subject");
if (abstract.isEmpty()) {
abstract = info->aboutInfo("abstract");
}
if (!abstract.isEmpty() && options.storeMetaData) {
QString keywords = info->aboutInfo("keyword");
if (!keywords.isEmpty()) {
abstract = abstract + " keywords: " + keywords;
}
fillText(texts + nbtexts, "Description", abstract);
nbtexts++;
}
QString license = info->aboutInfo("license");
if (!license.isEmpty() && options.storeMetaData) {
fillText(texts + nbtexts, "Copyright", license);
nbtexts++;
}
QString author = info->authorInfo("creator");
if (!author.isEmpty() && options.storeAuthor) {
if (!info->authorContactInfo().isEmpty()) {
QString contact = info->authorContactInfo().at(0);
if (!contact.isEmpty()) {
author = author+"("+contact+")";
}
}
fillText(texts + nbtexts, "Author", author);
nbtexts++;
}
png_set_text(png_ptr, info_ptr, texts, nbtexts);
}
// Save metadata following imagemagick way
// Save exif
if (metaData && !metaData->empty()) {
if (options.exif) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif");
Q_ASSERT(exifIO);
QBuffer buffer;
exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
writeRawProfile(png_ptr, info_ptr, "exif", buffer.data());
}
// Save IPTC
if (options.iptc) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc");
Q_ASSERT(iptcIO);
QBuffer buffer;
iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
dbgFile << "IPTC information size is" << buffer.data().size();
writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data());
}
// Save XMP
if (options.xmp) {
dbgFile << "Trying to save XMP information";
KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp");
Q_ASSERT(xmpIO);
QBuffer buffer;
xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader);
dbgFile << "XMP information size is" << buffer.data().size();
writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data());
}
}
#if 0 // Unimplemented?
// Save resolution
int unit_type;
png_uint_32 x_resolution, y_resolution;
#endif
png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points
// Save the information to the file
png_write_info(png_ptr, info_ptr);
png_write_flush(png_ptr);
// swap byteorder on little endian machines.
#ifndef WORDS_BIGENDIAN
if (color_nb_bits > 8)
png_set_swap(png_ptr);
#endif
// Write the PNG
// png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
struct RowPointersStruct {
RowPointersStruct(const QSize &size, int pixelSize)
: numRows(size.height())
{
rows = new png_byte*[numRows];
for (int i = 0; i < numRows; i++) {
rows[i] = new png_byte[size.width() * pixelSize];
}
}
~RowPointersStruct() {
for (int i = 0; i < numRows; i++) {
delete[] rows[i];
}
delete[] rows;
}
const int numRows = 0;
png_byte** rows = 0;
};
// Fill the data structure
RowPointersStruct rowPointers(imageRect.size(), device->pixelSize());
int row = 0;
for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) {
KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width());
switch (color_type) {
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if (color_nb_bits == 16) {
quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
do {
const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[1];
} while (it->nextPixel());
} else {
quint8 *dst = rowPointers.rows[row];
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[1];
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
if (color_nb_bits == 16) {
quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
do {
const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
*(dst++) = d[2];
*(dst++) = d[1];
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[3];
} while (it->nextPixel());
} else {
quint8 *dst = rowPointers.rows[row];
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[2];
*(dst++) = d[1];
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[3];
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_PALETTE: {
quint8 *dst = rowPointers.rows[row];
KisPNGWriteStream writestream(dst, color_nb_bits);
do {
const quint8 *d = it->oldRawData();
int i;
for (i = 0; i < num_palette; i++) {
if (palette[i].red == d[2] &&
palette[i].green == d[1] &&
palette[i].blue == d[0]) {
break;
}
}
writestream.setNextValue(i);
} while (it->nextPixel());
}
break;
default:
- return KisImageBuilder_RESULT_UNSUPPORTED;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
}
png_write_image(png_ptr, rowPointers.rows);
// Writing is over
png_write_end(png_ptr, info_ptr);
// Free memory
png_destroy_write_struct(&png_ptr, &info_ptr);
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
void KisPNGConverter::cancel()
{
m_stop = true;
}
void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass)
{
if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return;
// setProgress(row_number);
}
bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs)
{
return colorSpaceIdSupported(cs->id());
}
diff --git a/libs/ui/kis_png_converter.h b/libs/ui/kis_png_converter.h
index 0842928991..e6bb466631 100644
--- a/libs/ui/kis_png_converter.h
+++ b/libs/ui/kis_png_converter.h
@@ -1,146 +1,146 @@
/*
* Copyright (c) 2005, 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_PNG_CONVERTER_H_
#define _KIS_PNG_CONVERTER_H_
#include <png.h>
#include <QColor>
#include <QVector>
#include "kis_types.h"
#include "kis_global.h"
#include "kis_annotation.h"
#include <kritaui_export.h>
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
class KoStore;
class KisDocument;
class KoColorSpace;
namespace KisMetaData
{
class Filter;
class Store;
}
struct KisPNGOptions {
KisPNGOptions()
: compression(0)
, interlace(false)
, alpha(true)
, exif(true)
, iptc(true)
, xmp(true)
, tryToSaveAsIndexed(true)
, saveSRGBProfile(false)
, forceSRGB(false)
, storeMetaData(false)
, storeAuthor(false)
, saveAsHDR(false)
, transparencyFillColor(Qt::white)
{}
int compression;
bool interlace;
bool alpha;
bool exif;
bool iptc;
bool xmp;
bool tryToSaveAsIndexed;
bool saveSRGBProfile;
bool forceSRGB;
bool storeMetaData;
bool storeAuthor;
bool saveAsHDR;
QList<const KisMetaData::Filter*> filters;
QColor transparencyFillColor;
};
/**
* This class allows to import/export a PNG from either a file or a QIODevice.
*/
// XXX_PROGRESS (pass KoUpdater to the png converter)
class KRITAUI_EXPORT KisPNGConverter : public QObject
{
Q_OBJECT
public:
/**
* Initialize the converter.
* @param doc the KisDocument related to the image, can be null if you don't have a KisDocument
* @param batchMode whether to use the batch mode
*/
KisPNGConverter(KisDocument *doc, bool batchMode = false);
~KisPNGConverter() override;
public:
/**
* Load an image from an URL. If the image is not on a local drive, the image is first downloaded to a
* temporary location.
* @param filename the file name of the image
*/
- KisImageBuilder_Result buildImage(const QString &filename);
+ KisImportExportErrorCode buildImage(const QString &filename);
/**
* Load an image from a QIODevice.
* @param iod device to access the data
*/
- KisImageBuilder_Result buildImage(QIODevice* iod);
+ KisImportExportErrorCode buildImage(QIODevice* iod);
/**
* Save a layer to a PNG
* @param filename the name of the destination file
* @param imageRect the image rectangle to save
* @param xRes resolution along x axis
* @param yRes resolution along y axis
* @param device the paint device to save
* @param annotationsStart an iterator on the first annotation
* @param annotationsEnd an iterator on the last annotation
* @param options PNG formatting options
* @param metaData image metadata
*/
- KisImageBuilder_Result buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData);
- KisImageBuilder_Result buildFile(QIODevice*, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData);
+ KisImportExportErrorCode buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData);
+ KisImportExportErrorCode buildFile(QIODevice*, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData);
/**
* Retrieve the constructed image
*/
KisImageSP image();
/**
* @brief saveDeviceToStore saves the given paint device to the KoStore. If the device is not 8 bits sRGB, it will be converted to 8 bits sRGB.
* @return true if the saving succeeds
*/
static bool saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData = 0);
static bool isColorSpaceSupported(const KoColorSpace *cs);
public Q_SLOTS:
virtual void cancel();
private:
void progress(png_structp png_ptr, png_uint_32 row_number, int pass);
private:
png_uint_32 m_max_row;
KisImageSP m_image;
KisDocument *m_doc;
bool m_stop;
bool m_batchMode;
QString m_path;
};
#endif
diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp
index d14aca9ee3..b35e7c769c 100644
--- a/libs/ui/kis_popup_palette.cpp
+++ b/libs/ui/kis_popup_palette.cpp
@@ -1,994 +1,987 @@
/* This file is part of the KDE project
Copyright 2009 Vera Lukman <shicmap@gmail.com>
Copyright 2011 Sven Langkamp <sven.langkamp@gmail.com>
Copyright 2016 Scott Petrovic <scottpetrovic@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kis_canvas2.h"
#include "kis_config.h"
#include "kis_popup_palette.h"
#include "kis_favorite_resource_manager.h"
#include "kis_icon_utils.h"
#include "KisResourceServerProvider.h"
#include <kis_canvas_resource_provider.h>
#include <KoTriangleColorSelector.h>
#include <KisVisualColorSelector.h>
#include <kis_config_notifier.h>
#include <QtGui>
#include <QMenu>
#include <QWhatsThis>
#include <QVBoxLayout>
#include <QElapsedTimer>
#include "kis_signal_compressor.h"
#include "brushhud/kis_brush_hud.h"
#include "brushhud/kis_round_hud_button.h"
#include "kis_signals_blocker.h"
#include "kis_canvas_controller.h"
#include "kis_acyclic_signal_connector.h"
-
+#include "KisMouseClickEater.h"
class PopupColorTriangle : public KoTriangleColorSelector
{
public:
PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent)
: KoTriangleColorSelector(displayRenderer, parent)
- , m_dragging(false) {
+ , m_dragging(false)
+ {
}
~PopupColorTriangle() override {}
void tabletEvent(QTabletEvent* event) override {
event->accept();
QMouseEvent* mouseEvent = 0;
// this will tell the pop-up palette widget to close
if(event->button() == Qt::RightButton) {
emit requestCloseContainer();
}
// ignore any tablet events that are done with the right click
// Tablet move events don't return a "button", so catch that too
if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove)
{
switch (event->type()) {
case QEvent::TabletPress:
mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(),
Qt::LeftButton, Qt::LeftButton, event->modifiers());
m_dragging = true;
mousePressEvent(mouseEvent);
break;
case QEvent::TabletMove:
mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(),
(m_dragging) ? Qt::LeftButton : Qt::NoButton,
(m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers());
mouseMoveEvent(mouseEvent);
break;
case QEvent::TabletRelease:
mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(),
Qt::LeftButton,
Qt::LeftButton,
event->modifiers());
m_dragging = false;
mouseReleaseEvent(mouseEvent);
break;
default: break;
}
}
delete mouseEvent;
}
private:
bool m_dragging;
};
KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager,
const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent)
: QWidget(parent, Qt::FramelessWindowHint)
, m_coordinatesConverter(coordinatesConverter)
, m_viewManager(viewManager)
, m_actionManager(viewManager->actionManager())
, m_resourceManager(manager)
, m_displayRenderer(displayRenderer)
, m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE))
, m_actionCollection(viewManager->actionCollection())
, m_acyclicConnector(new KisAcyclicSignalConnector(this))
+ , m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this))
{
// some UI controls are defined and created based off these variables
const int borderWidth = 3;
if (KisConfig(true).readEntry<bool>("popuppalette/usevisualcolorselector", false)) {
m_triangleColorSelector = new KisVisualColorSelector(this);
}
else {
m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this);
}
m_triangleColorSelector->setDisplayRenderer(displayRenderer);
m_triangleColorSelector->setConfig(true,false);
m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth);
m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2);
m_triangleColorSelector->setVisible(true);
KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8());
if (m_resourceManager) {
fgcolor = provider->fgColor();
}
m_triangleColorSelector->slotSetColor(fgcolor);
+
+ /**
+ * Tablet support code generates a spurious right-click right after opening
+ * the window, so we should ignore it. Next right-click will be used for
+ * closing the popup palette
+ */
+ this->installEventFilter(m_clicksEater);
+ m_triangleColorSelector->installEventFilter(m_clicksEater);
+
QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse );
m_triangleColorSelector->setMask(maskedRegion);
//setAttribute(Qt::WA_TranslucentBackground, true);
connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)),
m_colorChangeCompressor.data(), SLOT(start()));
connect(m_colorChangeCompressor.data(), SIGNAL(timeout()),
SLOT(slotEmitColorChanged()));
connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged()));
m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)),
this, SLOT(slotExternalFgColorChanged(KoColor)));
m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)),
m_resourceManager, SIGNAL(sigSetFGColor(KoColor)));
connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int)));
connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int)));
connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int)));
connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate()));
connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide()));
// This is used to handle a bug:
// If pop up palette is visible and a new colour is selected, the new colour
// will be added when the user clicks on the canvas to hide the palette
// In general, we want to be able to store recent color if the pop up palette
// is not visible
m_timer.setSingleShot(true);
connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer()));
connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor()));
connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool)));
setCursor(Qt::ArrowCursor);
setMouseTracking(true);
setHoveredPreset(-1);
setHoveredColor(-1);
setSelectedColor(-1);
m_brushHud = new KisBrushHud(provider, parent);
m_brushHud->setFixedHeight(int(m_popupPaletteSize));
m_brushHud->setVisible(false);
const int auxButtonSize = 35;
m_settingsButton = new KisRoundHudButton(this);
m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize,
auxButtonSize, auxButtonSize);
connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup()));
KisConfig cfg(true);
m_brushHudButton = new KisRoundHudButton(this);
m_brushHudButton->setCheckable(true);
m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize,
auxButtonSize, auxButtonSize);
connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool)));
m_brushHudButton->setChecked(cfg.showBrushHud());
// add some stuff below the pop-up palette that will make it easier to use for tablet people
QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout
QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom
QHBoxLayout* hLayout = new QHBoxLayout();
vLayout->addLayout(hLayout);
mirrorMode = new KisHighlightedToolButton(this);
mirrorMode->setFixedSize(35, 35);
mirrorMode->setToolTip(i18n("Mirror Canvas"));
mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas"));
canvasOnlyButton = new KisHighlightedToolButton(this);
canvasOnlyButton->setFixedSize(35, 35);
canvasOnlyButton->setToolTip(i18n("Canvas Only"));
canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only"));
zoomToOneHundredPercentButton = new QPushButton(this);
zoomToOneHundredPercentButton->setText(i18n("100%"));
zoomToOneHundredPercentButton->setFixedHeight(35);
zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%"));
connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked()));
zoomCanvasSlider = new QSlider(Qt::Horizontal, this);
zoomSliderMinValue = 10; // set in %
zoomSliderMaxValue = 200; // set in %
zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue);
zoomCanvasSlider->setFixedHeight(35);
zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent());
zoomCanvasSlider->setSingleStep(1);
zoomCanvasSlider->setPageStep(1);
connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int)));
connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed()));
connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased()));
slotUpdateIcons();
hLayout->addWidget(mirrorMode);
hLayout->addWidget(canvasOnlyButton);
hLayout->addWidget(zoomToOneHundredPercentButton);
hLayout->addWidget(zoomCanvasSlider);
setVisible(true);
setVisible(false);
opacityChange = new QGraphicsOpacityEffect(this);
setGraphicsEffect(opacityChange);
// Prevent tablet events from being captured by the canvas
setAttribute(Qt::WA_NoMousePropagation, true);
}
void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color)
{
//hack to get around cmyk for now.
if (color.colorSpace()->colorChannelCount()>3) {
KoColor c(KoColorSpaceRegistry::instance()->rgb8());
c.fromKoColor(color);
m_triangleColorSelector->slotSetColor(c);
} else {
m_triangleColorSelector->slotSetColor(color);
}
}
void KisPopupPalette::slotEmitColorChanged()
{
if (isVisible()) {
update();
emit sigChangefGColor(m_triangleColorSelector->getCurrentColor());
}
}
//setting KisPopupPalette properties
int KisPopupPalette::hoveredPreset() const
{
return m_hoveredPreset;
}
void KisPopupPalette::setHoveredPreset(int x)
{
m_hoveredPreset = x;
}
int KisPopupPalette::hoveredColor() const
{
return m_hoveredColor;
}
void KisPopupPalette::setHoveredColor(int x)
{
m_hoveredColor = x;
}
int KisPopupPalette::selectedColor() const
{
return m_selectedColor;
}
void KisPopupPalette::setSelectedColor(int x)
{
m_selectedColor = x;
}
void KisPopupPalette::slotTriggerTimer()
{
m_timer.start(750);
}
void KisPopupPalette::slotEnableChangeFGColor()
{
emit sigEnableChangeFGColor(true);
}
void KisPopupPalette::slotZoomSliderChanged(int zoom) {
emit zoomLevelChanged(zoom);
}
void KisPopupPalette::slotZoomSliderPressed()
{
m_isZoomingCanvas = true;
}
void KisPopupPalette::slotZoomSliderReleased()
{
m_isZoomingCanvas = false;
}
void KisPopupPalette::adjustLayout(const QPoint &p)
{
KIS_ASSERT_RECOVER_RETURN(m_brushHud);
if (isVisible() && parentWidget()) {
float hudMargin = 30.0;
const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin
const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
QRect paletteRect = rect();
paletteRect.moveTo(p - paletteCenterOffset);
if (m_brushHudButton->isChecked()) {
m_brushHud->updateGeometry();
paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0);
}
paletteRect = kisEnsureInRect(paletteRect, fitRect);
move(paletteRect.topLeft());
m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0));
m_lastCenterPoint = p;
}
}
void KisPopupPalette::slotUpdateIcons()
{
this->setPalette(qApp->palette());
for(int i=0; i<this->children().size(); i++) {
QWidget *w = qobject_cast<QWidget*>(this->children().at(i));
if (w) {
w->setPalette(qApp->palette());
}
}
zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon());
m_brushHud->updateIcons();
m_settingsButton->setIcon(KisIconUtils::loadIcon("tag"));
m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right"));
}
void KisPopupPalette::showHudWidget(bool visible)
{
KIS_ASSERT_RECOVER_RETURN(m_brushHud);
const bool reallyVisible = visible && m_brushHudButton->isChecked();
if (reallyVisible) {
m_brushHud->updateProperties();
}
m_brushHud->setVisible(reallyVisible);
adjustLayout(m_lastCenterPoint);
KisConfig cfg(false);
cfg.setShowBrushHud(visible);
}
void KisPopupPalette::showPopupPalette(const QPoint &p)
{
showPopupPalette(!isVisible());
adjustLayout(p);
}
void KisPopupPalette::showPopupPalette(bool show)
{
if (show) {
- m_hadMousePressSinceOpening = false;
- m_timeSinceOpening.start();
-
-
// don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within
// the bounds and cause the canvas to jump between the slider's min and max
if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue &&
m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){
KisSignalsBlocker b(zoomCanvasSlider);
zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider
}
emit sigEnableChangeFGColor(!show);
} else {
emit sigTriggerTimer();
}
setVisible(show);
m_brushHud->setVisible(show && m_brushHudButton->isChecked());
}
//redefinition of setVariable function to change the scope to private
void KisPopupPalette::setVisible(bool b)
{
QWidget::setVisible(b);
}
void KisPopupPalette::setParent(QWidget *parent) {
m_brushHud->setParent(parent);
QWidget::setParent(parent);
}
QSize KisPopupPalette::sizeHint() const
{
return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below
}
void KisPopupPalette::resizeEvent(QResizeEvent*)
{
}
void KisPopupPalette::paintEvent(QPaintEvent* e)
{
Q_UNUSED(e);
QPainter painter(this);
QPen pen(palette().color(QPalette::Text));
pen.setWidth(3);
painter.setPen(pen);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// painting background color indicator
QPainterPath bgColor;
bgColor.addEllipse(QPoint( 50, 80), 30, 30);
painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor()));
painter.drawPath(bgColor);
// painting foreground color indicator
QPainterPath fgColor;
fgColor.addEllipse(QPoint( 60, 50), 30, 30);
painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor()));
painter.drawPath(fgColor);
// create a circle background that everything else will go into
QPainterPath backgroundContainer;
float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it
QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount,
m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2);
backgroundContainer.addEllipse( circleRect );
painter.fillPath(backgroundContainer,palette().brush(QPalette::Background));
painter.drawPath(backgroundContainer);
// create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas
// with the indicator
QPainterPath rotationTrackPath;
shrinkCircleAmount = 18;
QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount,
m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2);
rotationTrackPath.addEllipse( circleRect2 );
pen.setWidth(1);
painter.setPen(pen);
painter.drawPath(rotationTrackPath);
// this thing will help indicate where the starting brush preset is at.
// also what direction they go to give sor order to the presets populated
/*
pen.setWidth(6);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees
QPainterPath brushDir;
brushDir.arcMoveTo(circleRect, 60);
brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14);
painter.drawPath(brushDir);
brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6);
painter.drawPath(brushDir);
*/
// the following things needs to be based off the center, so let's translate the painter
painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2);
// create the canvas rotation handle
QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true);
painter.fillPath(rotationIndicator,palette().brush(QPalette::Text));
// hover indicator for the canvas rotation
if (m_isOverCanvasRotationIndicator == true) {
painter.save();
QPen pen(palette().color(QPalette::Highlight));
pen.setWidth(2);
painter.setPen(pen);
painter.drawPath(rotationIndicator);
painter.restore();
}
// create a reset canvas rotation indicator to bring the canvas back to 0 degrees
QPainterPath resetRotationIndicator = drawRotationIndicator(0, false);
QPen resetPen(palette().color(QPalette::Text));
resetPen.setWidth(1);
painter.save();
painter.setPen(resetPen);
painter.drawPath(resetRotationIndicator);
painter.restore();
// painting favorite brushes
QList<QImage> images(m_resourceManager->favoritePresetImages());
// painting favorite brushes pixmap/icon
QPainterPath presetPath;
for (int pos = 0; pos < numSlots(); pos++) {
painter.save();
presetPath = createPathFromPresetIndex(pos);
if (pos < images.size()) {
painter.setClipPath(presetPath);
QRect bounds = presetPath.boundingRect().toAlignedRect();
painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
}
else {
painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it
}
QPen pen = painter.pen();
pen.setWidth(1);
painter.setPen(pen);
painter.drawPath(presetPath);
painter.restore();
}
if (hoveredPreset() > -1) {
presetPath = createPathFromPresetIndex(hoveredPreset());
QPen pen(palette().color(QPalette::Highlight));
pen.setWidth(3);
painter.setPen(pen);
painter.drawPath(presetPath);
}
// paint recent colors area.
painter.setPen(Qt::NoPen);
float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal();
// there might be no recent colors at the start, so paint a placeholder
if (m_resourceManager->recentColorsTotal() == 0) {
painter.setBrush(Qt::transparent);
QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
painter.drawPath(emptyRecentColorsPath);
} else {
for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) {
QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
//accessing recent color of index pos
painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) ));
painter.drawPath(recentColorsPath);
painter.rotate(rotationAngle);
}
}
// painting hovered color
if (hoveredColor() > -1) {
painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
if (m_resourceManager->recentColorsTotal() == 1) {
QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
painter.drawPath(path_ColorDonut);
} else {
painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle);
QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
painter.drawPath(path);
painter.rotate(hoveredColor() * -1 * rotationAngle);
}
}
// painting selected color
if (selectedColor() > -1) {
painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
if (m_resourceManager->recentColorsTotal() == 1) {
QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
painter.drawPath(path_ColorDonut);
} else {
painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle);
QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal()));
painter.drawPath(path);
painter.rotate(selectedColor() * -1 * rotationAngle);
}
}
// if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better
if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) {
opacityChange->setOpacity(0.4);
} else {
opacityChange->setOpacity(1.0);
}
}
QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius)
{
QPainterPath path;
path.addEllipse(QPointF(x, y), outer_radius, outer_radius);
path.addEllipse(QPointF(x, y), inner_radius, inner_radius);
path.setFillRule(Qt::OddEvenFill);
return path;
}
QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit)
{
QPainterPath path;
path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit));
path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit,
360.0 / limit);
path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit,
- 360.0 / limit);
path.closeSubpath();
return path;
}
QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag)
{
// used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator,
// and another time by the reset canvas position
float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top
float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius
float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10);
QPainterPath canvasRotationIndicator;
int canvasIndicatorSize = 15;
float canvasIndicatorMiddle = canvasIndicatorSize/2;
QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle,
canvasIndicatorSize, canvasIndicatorSize );
if (canDrag) {
m_canvasRotationIndicatorRect = indicatorRectangle;
} else {
m_resetCanvasRotationIndicatorRect = indicatorRectangle;
}
canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(),
indicatorRectangle.width(), indicatorRectangle.height() );
return canvasRotationIndicator;
}
void KisPopupPalette::mouseMoveEvent(QMouseEvent *event)
{
QPointF point = event->localPos();
event->accept();
setToolTip(QString());
setHoveredPreset(-1);
setHoveredColor(-1);
// calculate if we are over the canvas rotation knob
// before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to
// correct them first before looking for a click event intersection
float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2);
float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2);
QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos,
m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height());
if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) {
m_isOverCanvasRotationIndicator = true;
} else {
m_isOverCanvasRotationIndicator = false;
}
if (m_isRotatingCanvasIndicator) {
// we are rotating the canvas, so calculate the rotation angle based off the center
// calculate the angle we are at first
QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2);
float dX = point.x() - widgetCenterPoint.x();
float dY = point.y() - widgetCenterPoint.y();
float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle
finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up
float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out
KisCanvasController *canvasController =
dynamic_cast<KisCanvasController*>(m_viewManager->canvasBase()->canvasController());
canvasController->rotateCanvas(angleDifference);
emit sigUpdateCanvas();
}
// don't highlight the presets if we are in the middle of rotating the canvas
if (m_isRotatingCanvasIndicator == false) {
QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
{
int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets());
if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) {
setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name());
setHoveredPreset(pos);
}
}
if (pathColor.contains(point)) {
int pos = calculateIndex(point, m_resourceManager->recentColorsTotal());
if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
setHoveredColor(pos);
}
}
}
update();
}
void KisPopupPalette::mousePressEvent(QMouseEvent *event)
{
QPointF point = event->localPos();
event->accept();
-
-#ifdef Q_OS_WIN
- const int tableMouseEventsFlowDelay = 500;
-#else
- const int tableMouseEventsFlowDelay = 100;
-#endif
-
- /**
- * Tablet support code generates a spurious right-click right after opening
- * the window, so we should ignore it. Next right-click will be used for
- * closing the popup palette
- */
- if (!m_hadMousePressSinceOpening &&
- m_timeSinceOpening.elapsed() > tableMouseEventsFlowDelay) {
-
- m_hadMousePressSinceOpening = true;
- }
-
if (event->button() == Qt::LeftButton) {
//in favorite brushes area
int pos = calculateIndex(point, m_resourceManager->numFavoritePresets());
if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()
&& isPointInPixmap(point, pos)) {
//setSelectedBrush(pos);
update();
}
if (m_isOverCanvasRotationIndicator) {
m_isRotatingCanvasIndicator = true;
}
// reset the canvas if we are over the reset canvas rotation indicator
float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2);
float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2);
QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos,
m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height());
if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) {
float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs
KisCanvasController *canvasController =
dynamic_cast<KisCanvasController*>(m_viewManager->canvasBase()->canvasController());
canvasController->rotateCanvas(angleDifference);
emit sigUpdateCanvas();
}
}
}
void KisPopupPalette::slotShowTagsPopup()
{
KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer();
QStringList tags = rServer->tagNamesList();
std::sort(tags.begin(), tags.end());
if (!tags.isEmpty()) {
QMenu menu;
Q_FOREACH (const QString& tag, tags) {
menu.addAction(tag);
}
QAction *action = menu.exec(QCursor::pos());
if (action) {
m_resourceManager->setCurrentTag(action->text());
}
} else {
QWhatsThis::showText(QCursor::pos(),
i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here."));
}
}
void KisPopupPalette::slotZoomToOneHundredPercentClicked() {
QAction *action = m_actionCollection->action("zoom_to_100pct");
if (action) {
action->trigger();
}
// also move the zoom slider to 100% position so they are in sync
zoomCanvasSlider->setValue(100);
}
void KisPopupPalette::tabletEvent(QTabletEvent *event) {
event->ignore();
}
+void KisPopupPalette::showEvent(QShowEvent *event)
+{
+ m_clicksEater->reset();
+ QWidget::showEvent(event);
+}
+
void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event)
{
QPointF point = event->localPos();
event->accept();
- // see a comment in KisPopupPalette::mousePressEvent
- if (m_hadMousePressSinceOpening &&
- event->buttons() == Qt::NoButton &&
+ if (event->buttons() == Qt::NoButton &&
event->button() == Qt::RightButton) {
showPopupPalette(false);
return;
}
m_isOverCanvasRotationIndicator = false;
m_isRotatingCanvasIndicator = false;
if (event->button() == Qt::LeftButton) {
QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius));
//in favorite brushes area
if (hoveredPreset() > -1) {
//setSelectedBrush(hoveredBrush());
emit sigChangeActivePaintop(hoveredPreset());
}
if (pathColor.contains(point)) {
int pos = calculateIndex(point, m_resourceManager->recentColorsTotal());
if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) {
emit sigUpdateRecentColor(pos);
}
}
}
}
int KisPopupPalette::calculateIndex(QPointF point, int n)
{
calculatePresetIndex(point, n);
//translate to (0,0)
point.setX(point.x() - m_popupPaletteSize / 2);
point.setY(point.y() - m_popupPaletteSize / 2);
//rotate
float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x());
float radius = sqrt((float)point.x() * point.x() + point.y() * point.y());
point.setX(radius * cos(smallerAngle));
point.setY(radius * sin(smallerAngle));
//calculate brush index
int pos = floor(acos(point.x() / radius) * n / (2 * M_PI));
if (point.y() < 0) pos = n - pos - 1;
return pos;
}
bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos)
{
if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) {
return true;
}
return false;
}
KisPopupPalette::~KisPopupPalette()
{
}
QPainterPath KisPopupPalette::createPathFromPresetIndex(int index)
{
qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get
// the starting angle of the slice we need to draw. the negative sign makes us go clockwise.
// adding 90 degrees makes us start at the top. otherwise we would start at the right
qreal startingAngle = -(index * angleSlice) + 90;
// the radius will get smaller as the amount of presets shown increases. 10 slots == 41
qreal radians = qDegreesToRadians((360.0/10)/2);
qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2;
radians = qDegreesToRadians(angleSlice/2);
qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians));
//If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking.
qreal length = m_colorHistoryOuterRadius + presetRadius;
// can we can fit in a second row? We don't want the preset icons to get too tiny.
if (maxRadius > presetRadius) {
//redo all calculations assuming a second row.
if (numSlots() % 2) {
angleSlice = 360.0/(numSlots()+1);
startingAngle = -(index * angleSlice) + 90;
}
if (numSlots() != m_cachedNumSlots){
qreal tempRadius = presetRadius;
qreal distance = 0;
do{
tempRadius+=0.1;
// Calculate the XY of two adjectant circles using this tempRadius.
qreal length1 = m_colorHistoryOuterRadius + tempRadius;
qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius);
qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius;
qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius;
qreal startingAngle2 = -(index+1 * angleSlice) + 90;
qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius;
qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius;
// Use Pythagorean Theorem to calculate the distance between these two values.
qreal m1 = pathX2-pathX1;
qreal m2 = pathY2-pathY1;
distance = sqrt((m1*m1)+(m2*m2));
}
//As long at there's more distance than the radius of the two presets, continue increasing the radius.
while((tempRadius+1)*2 < distance);
m_cachedRadius = tempRadius;
}
m_cachedNumSlots = numSlots();
presetRadius = m_cachedRadius;
length = m_colorHistoryOuterRadius + presetRadius;
if (index % 2) {
length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius);
}
}
QPainterPath path;
qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius;
qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius;
qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size
path.addEllipse(pathX, pathY, pathDiameter, pathDiameter);
return path;
}
int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/)
{
for(int i = 0; i < numSlots(); i++)
{
QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2);
if(createPathFromPresetIndex(i).contains(adujustedPoint))
{
return i;
}
}
return -1;
}
int KisPopupPalette::numSlots()
{
KisConfig config(true);
return qMax(config.favoritePresets(), 10);
}
diff --git a/libs/ui/kis_popup_palette.h b/libs/ui/kis_popup_palette.h
index 9ef362d139..d2df8e7232 100644
--- a/libs/ui/kis_popup_palette.h
+++ b/libs/ui/kis_popup_palette.h
@@ -1,191 +1,190 @@
/* This file is part of the KDE project
Copyright 2009 Vera Lukman <shicmap@gmail.com>
Copyright 2016 Scott Petrovic <scottpetrovic@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_POPUP_PALETTE_H
#define KIS_POPUP_PALETTE_H
#include <QElapsedTimer>
#include <QPushButton>
#include <QSlider>
#include <QGraphicsOpacityEffect>
#include "KisViewManager.h"
#include "kactioncollection.h"
#include "kis_tool_button.h"
-#include "kis_highlighted_button.h"
+#include "KisHighlightedToolButton.h"
#include <KisColorSelectorInterface.h>
class KisFavoriteResourceManager;
class QWidget;
class KoColor;
class KoTriangleColorSelector;
class KisSignalCompressor;
class KisBrushHud;
class KisRoundHudButton;
class KisCanvasResourceProvider;
class KisVisualColorSelector;
class KisAcyclicSignalConnector;
+class KisMouseClickEater;
class KisPopupPalette : public QWidget
{
Q_OBJECT
Q_PROPERTY(int hoveredPreset READ hoveredPreset WRITE setHoveredPreset)
Q_PROPERTY(int hoveredColor READ hoveredColor WRITE setHoveredColor)
Q_PROPERTY(int selectedColor READ selectedColor WRITE setSelectedColor)
public:
KisPopupPalette(KisViewManager*, KisCoordinatesConverter* ,KisFavoriteResourceManager*, const KoColorDisplayRendererInterface *displayRenderer,
KisCanvasResourceProvider *provider, QWidget *parent = 0);
~KisPopupPalette() override;
QSize sizeHint() const override;
void showPopupPalette(const QPoint&);
void showPopupPalette(bool b);
//functions to set up selectedBrush
void setSelectedBrush(int x);
int selectedBrush() const;
//functions to set up selectedColor
void setSelectedColor(int x);
int selectedColor() const;
void setParent(QWidget *parent);
void tabletEvent(QTabletEvent *event) override;
protected:
+ void showEvent(QShowEvent *event) override;
void paintEvent(QPaintEvent*) override;
void resizeEvent(QResizeEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void mousePressEvent(QMouseEvent*) override;
//functions to calculate index of favorite brush or recent color in array
//n is the total number of favorite brushes or recent colors
int calculateIndex(QPointF, int n);
int calculatePresetIndex(QPointF, int n);
//functions to set up hoveredBrush
void setHoveredPreset(int x);
int hoveredPreset() const;
//functions to set up hoveredColor
void setHoveredColor(int x);
int hoveredColor() const;
private:
void setVisible(bool b) override;
QPainterPath drawDonutPathFull(int, int, int, int);
QPainterPath drawDonutPathAngle(int, int, int);
QPainterPath drawRotationIndicator(qreal rotationAngle, bool canDrag);
bool isPointInPixmap(QPointF&, int pos);
QPainterPath createPathFromPresetIndex(int index);
int numSlots();
void adjustLayout(const QPoint &p);
private:
int m_hoveredPreset {0};
int m_hoveredColor {0};
int m_selectedColor {0};
- QElapsedTimer m_timeSinceOpening;
- bool m_hadMousePressSinceOpening {false};
-
-
KisCoordinatesConverter *m_coordinatesConverter;
KisViewManager *m_viewManager;
KisActionManager *m_actionManager;
KisFavoriteResourceManager *m_resourceManager;
KisColorSelectorInterface *m_triangleColorSelector {0};
const KoColorDisplayRendererInterface *m_displayRenderer;
QScopedPointer<KisSignalCompressor> m_colorChangeCompressor;
KActionCollection *m_actionCollection;
QTimer m_timer;
KisBrushHud *m_brushHud {0};
float m_popupPaletteSize {385.0};
float m_colorHistoryInnerRadius {72.0};
qreal m_colorHistoryOuterRadius {92.0};
KisRoundHudButton *m_settingsButton {0};
KisRoundHudButton *m_brushHudButton {0};
QPoint m_lastCenterPoint;
QRect m_canvasRotationIndicatorRect;
QRect m_resetCanvasRotationIndicatorRect;
bool m_isOverCanvasRotationIndicator {false};
bool m_isRotatingCanvasIndicator {false};
bool m_isZoomingCanvas {false};
KisHighlightedToolButton *mirrorMode {0};
KisHighlightedToolButton *canvasOnlyButton {0};
QPushButton *zoomToOneHundredPercentButton {0};
QSlider *zoomCanvasSlider {0};
int zoomSliderMinValue {10};
int zoomSliderMaxValue {200};
KisAcyclicSignalConnector *m_acyclicConnector = 0;
int m_cachedNumSlots {0};
qreal m_cachedRadius {0.0};
// updates the transparency and effects of the whole widget
QGraphicsOpacityEffect *opacityChange {0};
+ KisMouseClickEater *m_clicksEater;
Q_SIGNALS:
void sigChangeActivePaintop(int);
void sigUpdateRecentColor(int);
void sigChangefGColor(const KoColor&);
void sigUpdateCanvas();
void zoomLevelChanged(int);
// These are used to handle a bug:
// If pop up palette is visible and a new colour is selected, the new colour
// will be added when the user clicks on the canvas to hide the palette
// In general, we want to be able to store recent color if the pop up palette
// is not visible
void sigEnableChangeFGColor(bool);
void sigTriggerTimer();
public Q_SLOTS:
void slotUpdateIcons();
private Q_SLOTS:
void slotExternalFgColorChanged(const KoColor &color);
void slotEmitColorChanged();
void slotSetSelectedColor(int x) { setSelectedColor(x); update(); }
void slotTriggerTimer();
void slotEnableChangeFGColor();
void slotUpdate() { update(); }
void slotHide() { showPopupPalette(false); }
void slotShowTagsPopup();
void showHudWidget(bool visible);
void slotZoomToOneHundredPercentClicked();
void slotZoomSliderChanged(int zoom);
void slotZoomSliderPressed();
void slotZoomSliderReleased();
};
#endif // KIS_POPUP_PALETTE_H
diff --git a/libs/ui/kis_safe_document_loader.cpp b/libs/ui/kis_safe_document_loader.cpp
index 2b240ccf8a..766f116e16 100644
--- a/libs/ui/kis_safe_document_loader.cpp
+++ b/libs/ui/kis_safe_document_loader.cpp
@@ -1,272 +1,274 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_safe_document_loader.h"
#include <QTimer>
#include <QFileSystemWatcher>
#include <QApplication>
#include <QFileInfo>
#include <QDir>
#include <QUrl>
#include <KoStore.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include "KisDocument.h"
#include <kis_image.h>
#include "kis_signal_compressor.h"
#include "KisPart.h"
class FileSystemWatcherWrapper : public QObject
{
Q_OBJECT
public:
FileSystemWatcherWrapper() {
connect(&m_watcher, SIGNAL(fileChanged(QString)), SIGNAL(fileChanged(QString)));
connect(&m_watcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged(QString)));
}
bool addPath(const QString &file) {
bool result = true;
const QString ufile = unifyFilePath(file);
if (m_pathCount.contains(ufile)) {
m_pathCount[ufile]++;
} else {
m_pathCount.insert(ufile, 1);
result = m_watcher.addPath(ufile);
}
return result;
}
bool removePath(const QString &file) {
bool result = true;
const QString ufile = unifyFilePath(file);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_pathCount.contains(ufile), false);
if (m_pathCount[ufile] == 1) {
m_pathCount.remove(ufile);
result = m_watcher.removePath(ufile);
} else {
m_pathCount[ufile]--;
}
return result;
}
QStringList files() const {
return m_watcher.files();
}
private Q_SLOTS:
void slotFileChanged(const QString &path) {
// re-add the file after QSaveFile optimization
if (!m_watcher.files().contains(path) && QFileInfo(path).exists()) {
m_watcher.addPath(path);
}
}
Q_SIGNALS:
void fileChanged(const QString &path);
private:
QString unifyFilePath(const QString &path) {
return QFileInfo(path).absoluteFilePath();
}
private:
QFileSystemWatcher m_watcher;
QHash<QString, int> m_pathCount;
};
Q_GLOBAL_STATIC(FileSystemWatcherWrapper, s_fileSystemWatcher)
struct KisSafeDocumentLoader::Private
{
Private()
: fileChangedSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE)
{
}
QScopedPointer<KisDocument> doc;
KisSignalCompressor fileChangedSignalCompressor;
- QTimer delayedLoadTimer;
bool isLoading = false;
bool fileChangedFlag = false;
QString path;
QString temporaryPath;
qint64 initialFileSize = 0;
QDateTime initialFileTimeStamp;
};
KisSafeDocumentLoader::KisSafeDocumentLoader(const QString &path, QObject *parent)
: QObject(parent),
m_d(new Private())
{
connect(s_fileSystemWatcher, SIGNAL(fileChanged(QString)),
SLOT(fileChanged(QString)));
connect(&m_d->fileChangedSignalCompressor, SIGNAL(timeout()),
SLOT(fileChangedCompressed()));
- connect(&m_d->delayedLoadTimer, SIGNAL(timeout()),
- SLOT(delayedLoadStart()));
-
- m_d->delayedLoadTimer.setSingleShot(true);
- m_d->delayedLoadTimer.setInterval(100 /* ms */);
-
setPath(path);
}
KisSafeDocumentLoader::~KisSafeDocumentLoader()
{
if (!m_d->path.isEmpty()) {
s_fileSystemWatcher->removePath(m_d->path);
}
delete m_d;
}
void KisSafeDocumentLoader::setPath(const QString &path)
{
if (path.isEmpty()) return;
if (!m_d->path.isEmpty()) {
s_fileSystemWatcher->removePath(m_d->path);
}
m_d->path = path;
s_fileSystemWatcher->addPath(m_d->path);
}
void KisSafeDocumentLoader::reloadImage()
{
fileChangedCompressed(true);
}
void KisSafeDocumentLoader::fileChanged(QString path)
{
if (path == m_d->path) {
if (s_fileSystemWatcher->files().contains(path) == false && QFileInfo(path).exists()) {
//When a path is renamed it is removed, so we ought to readd it.
s_fileSystemWatcher->addPath(path);
}
m_d->fileChangedFlag = true;
m_d->fileChangedSignalCompressor.start();
}
}
void KisSafeDocumentLoader::fileChangedCompressed(bool sync)
{
if (m_d->isLoading) return;
QFileInfo initialFileInfo(m_d->path);
m_d->initialFileSize = initialFileInfo.size();
m_d->initialFileTimeStamp = initialFileInfo.lastModified();
// it may happen when the file is flushed by
// so other application
if (!m_d->initialFileSize) return;
m_d->isLoading = true;
m_d->fileChangedFlag = false;
m_d->temporaryPath =
QDir::tempPath() + QDir::separator() +
QString("krita_file_layer_copy_%1_%2.%3")
.arg(QApplication::applicationPid())
.arg(qrand())
.arg(initialFileInfo.suffix());
QFile::copy(m_d->path, m_d->temporaryPath);
if (!sync) {
- m_d->delayedLoadTimer.start();
+ QTimer::singleShot(100, this, SLOT(delayedLoadStart()));
} else {
QApplication::processEvents();
delayedLoadStart();
}
}
void KisSafeDocumentLoader::delayedLoadStart()
{
QFileInfo originalInfo(m_d->path);
QFileInfo tempInfo(m_d->temporaryPath);
bool successfullyLoaded = false;
if (!m_d->fileChangedFlag &&
originalInfo.size() == m_d->initialFileSize &&
originalInfo.lastModified() == m_d->initialFileTimeStamp &&
tempInfo.size() == m_d->initialFileSize) {
m_d->doc.reset(KisPart::instance()->createDocument());
if (m_d->path.toLower().endsWith("ora") || m_d->path.toLower().endsWith("kra")) {
QScopedPointer<KoStore> store(KoStore::createStore(m_d->temporaryPath, KoStore::Read));
- if (store) {
+ if (store && !store->bad()) {
if (store->open(QString("mergedimage.png"))) {
QByteArray bytes = store->read(store->size());
store->close();
QImage mergedImage;
mergedImage.loadFromData(bytes);
+ Q_ASSERT(!mergedImage.isNull());
KisImageSP image = new KisImage(0, mergedImage.width(), mergedImage.height(), KoColorSpaceRegistry::instance()->rgb8(), "");
KisPaintLayerSP layer = new KisPaintLayer(image, "", OPACITY_OPAQUE_U8);
layer->paintDevice()->convertFromQImage(mergedImage, 0);
image->addNode(layer, image->rootLayer());
+ image->initialRefreshGraph();
m_d->doc->setCurrentImage(image);
+ successfullyLoaded = true;
}
+ else {
+ qWarning() << "delayedLoadStart: Could not open mergedimage.png";
+ }
+ }
+ else {
+ qWarning() << "delayedLoadStart: Store was bad";
}
}
else {
successfullyLoaded = m_d->doc->openUrl(QUrl::fromLocalFile(m_d->temporaryPath),
KisDocument::DontAddToRecent);
}
} else {
dbgKrita << "File was modified externally. Restarting.";
dbgKrita << ppVar(m_d->fileChangedFlag);
dbgKrita << ppVar(m_d->initialFileSize);
dbgKrita << ppVar(m_d->initialFileTimeStamp);
dbgKrita << ppVar(originalInfo.size());
dbgKrita << ppVar(originalInfo.lastModified());
dbgKrita << ppVar(tempInfo.size());
}
QFile::remove(m_d->temporaryPath);
m_d->isLoading = false;
if (!successfullyLoaded) {
// Restart the attempt
m_d->fileChangedSignalCompressor.start();
}
else {
KisPaintDeviceSP paintDevice = new KisPaintDevice(m_d->doc->image()->colorSpace());
KisPaintDeviceSP projection = m_d->doc->image()->projection();
paintDevice->makeCloneFrom(projection, projection->extent());
emit loadingFinished(paintDevice, m_d->doc->image()->xRes(), m_d->doc->image()->yRes());
}
m_d->doc.reset();
}
#include "kis_safe_document_loader.moc"
diff --git a/libs/ui/kis_selection_decoration.cc b/libs/ui/kis_selection_decoration.cc
index deecb1a39d..492276fecb 100644
--- a/libs/ui/kis_selection_decoration.cc
+++ b/libs/ui/kis_selection_decoration.cc
@@ -1,228 +1,229 @@
/*
* Copyright (c) 2008 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_selection_decoration.h"
#include <QPainter>
#include <QVarLengthArray>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include "kis_types.h"
#include "KisViewManager.h"
#include "kis_selection.h"
#include "kis_image.h"
#include "flake/kis_shape_selection.h"
#include "kis_pixel_selection.h"
#include "kis_update_outline_job.h"
#include "kis_selection_manager.h"
#include "canvas/kis_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include "kis_coordinates_converter.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_image_config.h"
#include "KisImageConfigNotifier.h"
#include "kis_painting_tweaks.h"
#include "KisView.h"
#include "kis_selection_mask.h"
#include <KisPart.h>
static const unsigned int ANT_LENGTH = 4;
static const unsigned int ANT_SPACE = 4;
static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE;
KisSelectionDecoration::KisSelectionDecoration(QPointer<KisView>view)
: KisCanvasDecoration("selection", view),
m_signalCompressor(500 /*ms*/, KisSignalCompressor::FIRST_INACTIVE),
m_offset(0),
m_mode(Ants)
{
KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen,
ANT_LENGTH, ANT_SPACE);
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
slotConfigChanged();
m_antsTimer = new QTimer(this);
m_antsTimer->setInterval(150);
m_antsTimer->setSingleShot(false);
connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent()));
connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection()));
// selections should be at the top of the stack
setPriority(100);
}
KisSelectionDecoration::~KisSelectionDecoration()
{
}
KisSelectionDecoration::Mode KisSelectionDecoration::mode() const
{
return m_mode;
}
void KisSelectionDecoration::setMode(Mode mode)
{
m_mode = mode;
selectionChanged();
}
bool KisSelectionDecoration::selectionIsActive()
{
KisImageWSP image = view()->image();
Q_ASSERT(image); Q_UNUSED(image);
KisSelectionSP selection = view()->selection();
return visible() && selection &&
(selection->hasPixelSelection() || selection->hasShapeSelection()) &&
selection->isVisible();
}
void KisSelectionDecoration::selectionChanged()
{
KisSelectionMaskSP mask = qobject_cast<KisSelectionMask*>(view()->currentNode().data());
if (!mask || !mask->active() || !mask->visible(true)) {
mask = 0;
}
if (!view()->isCurrent() ||
view()->viewManager()->mainWindow() == KisPart::instance()->currentMainwindow()) {
view()->image()->setOverlaySelectionMask(mask);
}
KisSelectionSP selection = view()->selection();
if (!mask && selection && selectionIsActive()) {
if ((m_mode == Ants && selection->outlineCacheValid()) ||
(m_mode == Mask && selection->thumbnailImageValid())) {
m_signalCompressor.stop();
if (m_mode == Ants) {
m_outlinePath = selection->outlineCache();
m_antsTimer->start();
} else {
m_thumbnailImage = selection->thumbnailImage();
m_thumbnailImageTransform = selection->thumbnailImageTransform();
}
if (view() && view()->canvasBase()) {
view()->canvasBase()->updateCanvas();
}
} else {
m_signalCompressor.start();
}
} else {
m_signalCompressor.stop();
m_outlinePath = QPainterPath();
m_thumbnailImage = QImage();
m_thumbnailImageTransform = QTransform();
view()->canvasBase()->updateCanvas();
m_antsTimer->stop();
}
}
void KisSelectionDecoration::slotStartUpdateSelection()
{
KisSelectionSP selection = view()->selection();
if (!selection) return;
view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, m_maskColor));
}
void KisSelectionDecoration::slotConfigChanged()
{
KisImageConfig imageConfig(true);
KisConfig cfg(true);
m_maskColor = imageConfig.selectionOverlayMaskColor();
m_antialiasSelectionOutline = cfg.antialiasSelectionOutline();
}
void KisSelectionDecoration::antsAttackEvent()
{
KisSelectionSP selection = view()->selection();
if (!selection) return;
if (selectionIsActive()) {
m_offset = (m_offset + 1) % ANT_ADVANCE_WIDTH;
m_antsPen.setDashOffset(m_offset);
view()->canvasBase()->updateCanvas();
}
}
void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
{
Q_UNUSED(updateRect);
Q_UNUSED(canvas);
if (!selectionIsActive()) return;
if ((m_mode == Ants && m_outlinePath.isEmpty()) ||
(m_mode == Mask && m_thumbnailImage.isNull())) return;
QTransform transform = converter->imageToWidgetTransform();
gc.save();
gc.setTransform(transform, false);
if (m_mode == Mask) {
gc.setRenderHints(QPainter::SmoothPixmapTransform |
QPainter::HighQualityAntialiasing, false);
gc.setTransform(m_thumbnailImageTransform, true);
gc.drawImage(QPoint(), m_thumbnailImage);
QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds());
QRect r2 = m_thumbnailImage.rect();
QPainterPath p1;
p1.addRect(r1);
QPainterPath p2;
p2.addRect(r2);
gc.setBrush(m_maskColor);
gc.setPen(Qt::NoPen);
gc.drawPath(p1 - p2);
} else /* if (m_mode == Ants) */ {
gc.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, m_antialiasSelectionOutline);
// render selection outline in white
gc.setPen(m_outlinePen);
gc.drawPath(m_outlinePath);
// render marching ants in black (above the white outline)
gc.setPen(m_antsPen);
gc.drawPath(m_outlinePath);
}
gc.restore();
}
void KisSelectionDecoration::setVisible(bool v)
{
KisCanvasDecoration::setVisible(v);
selectionChanged();
}
diff --git a/libs/ui/kis_selection_decoration.h b/libs/ui/kis_selection_decoration.h
index 7c6a896818..ea90ebb475 100644
--- a/libs/ui/kis_selection_decoration.h
+++ b/libs/ui/kis_selection_decoration.h
@@ -1,75 +1,76 @@
/*
* Copyright (c) 2008 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_SELECTION_DECORATION_H_
#define _KIS_SELECTION_DECORATION_H_
#include <QTimer>
#include <QPolygon>
#include <QPen>
#include <kis_signal_compressor.h>
#include "canvas/kis_canvas_decoration.h"
class KisView;
class KRITAUI_EXPORT KisSelectionDecoration : public KisCanvasDecoration
{
Q_OBJECT
public:
KisSelectionDecoration(QPointer<KisView> view);
~KisSelectionDecoration() override;
enum Mode {
Ants,
Mask
};
Mode mode() const;
void setMode(Mode mode);
void setVisible(bool v) override;
protected:
void drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas) override;
private Q_SLOTS:
void slotStartUpdateSelection();
void slotConfigChanged();
public Q_SLOTS:
void selectionChanged();
void antsAttackEvent();
private:
bool selectionIsActive();
private:
KisSignalCompressor m_signalCompressor;
QPainterPath m_outlinePath;
QImage m_thumbnailImage;
QTransform m_thumbnailImageTransform;
QTimer* m_antsTimer;
int m_offset;
QPen m_antsPen;
QPen m_outlinePen;
Mode m_mode;
QColor m_maskColor;
bool m_antialiasSelectionOutline;
};
#endif
diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc
index 810625ba56..20fd3d9e4a 100644
--- a/libs/ui/kis_selection_manager.cc
+++ b/libs/ui/kis_selection_manager.cc
@@ -1,746 +1,759 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
*
* The outline algorithm uses the limn algorithm of fontutils by
* Karl Berry <karl@cs.umb.edu> and Kathryn Hargreaves <letters@cs.umb.edu>
*
* 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_selection_manager.h"
#include <QApplication>
#include <QClipboard>
#include <QColor>
#include <QTimer>
#include <QMimeData>
#include <QAction>
#include <ktoggleaction.h>
#include <klocalizedstring.h>
#include <kstandardaction.h>
#include <kactioncollection.h>
#include "KoCanvasController.h"
#include "KoChannelInfo.h"
#include "KoIntegerMaths.h"
#include <KisDocument.h>
#include <KisMainWindow.h>
#include <KoViewConverter.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoShapeStroke.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
#include <KoToolProxy.h>
#include <KoSvgPaste.h>
#include <kis_icon.h>
#include "kis_adjustment_layer.h"
#include "kis_node_manager.h"
#include "canvas/kis_canvas2.h"
#include "kis_config.h"
#include "kis_convolution_painter.h"
#include "kis_convolution_kernel.h"
#include "kis_debug.h"
#include "kis_fill_painter.h"
#include "kis_group_layer.h"
#include "kis_layer.h"
#include "kis_statusbar.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_painter.h"
#include "kis_transaction.h"
#include "kis_selection.h"
#include "kis_types.h"
#include "kis_canvas_resource_provider.h"
#include "kis_undo_adapter.h"
#include "kis_pixel_selection.h"
#include "flake/kis_shape_selection.h"
#include "commands/kis_selection_commands.h"
#include "kis_selection_mask.h"
#include "flake/kis_shape_layer.h"
#include "kis_selection_decoration.h"
#include "canvas/kis_canvas_decoration.h"
#include "kis_node_commands_adapter.h"
#include "kis_iterator_ng.h"
#include "kis_clipboard.h"
#include "KisViewManager.h"
#include "kis_selection_filters.h"
#include "kis_figure_painting_tool_helper.h"
#include "KisView.h"
#include "dialogs/kis_dlg_stroke_selection_properties.h"
#include "actions/kis_selection_action_factories.h"
-#include "actions/KisPasteActionFactory.h"
+#include "actions/KisPasteActionFactories.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "operations/kis_operation_configuration.h"
//new
#include "kis_node_query_path.h"
#include "kis_tool_shape.h"
KisSelectionManager::KisSelectionManager(KisViewManager * view)
: m_view(view),
m_doc(0),
m_imageView(0),
m_adapter(new KisNodeCommandsAdapter(view)),
m_copy(0),
m_copyMerged(0),
m_cut(0),
m_paste(0),
m_pasteNew(0),
m_cutToNewLayer(0),
m_selectAll(0),
m_deselect(0),
m_clear(0),
m_reselect(0),
m_invert(0),
m_copyToNewLayer(0),
m_fillForegroundColor(0),
m_fillBackgroundColor(0),
m_fillPattern(0),
m_imageResizeToSelection(0),
m_selectionDecoration(0)
{
m_clipboard = KisClipboard::instance();
}
KisSelectionManager::~KisSelectionManager()
{
}
void KisSelectionManager::setup(KisActionManager* actionManager)
{
m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut()));
m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy()));
m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste()));
KisAction *action = actionManager->createAction("copy_sharp");
connect(action, SIGNAL(triggered()), this, SLOT(copySharp()));
action = actionManager->createAction("cut_sharp");
connect(action, SIGNAL(triggered()), this, SLOT(cutSharp()));
m_pasteNew = actionManager->createAction("paste_new");
connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew()));
m_pasteAt = actionManager->createAction("paste_at");
connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt()));
+ m_pasteAsReference = actionManager->createAction("paste_as_reference");
+ connect(m_pasteAsReference, SIGNAL(triggered()), this, SLOT(pasteAsReference()));
+
m_copyMerged = actionManager->createAction("copy_merged");
connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged()));
m_selectAll = actionManager->createAction("select_all");
connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll()));
m_deselect = actionManager->createAction("deselect");
connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect()));
m_clear = actionManager->createAction("clear");
connect(m_clear, SIGNAL(triggered()), SLOT(clear()));
m_reselect = actionManager->createAction("reselect");
connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect()));
m_invert = actionManager->createAction("invert_selection");
m_invert->setOperationID("invertselection");
actionManager->registerOperation(new KisInvertSelectionOperation);
m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer");
connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer()));
m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer");
connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer()));
m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color");
connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor()));
m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color");
connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor()));
m_fillPattern = actionManager->createAction("fill_selection_pattern");
connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern()));
m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity");
connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity()));
m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity");
connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity()));
m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity");
connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity()));
m_strokeShapes = actionManager->createAction("stroke_shapes");
connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes()));
m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection");
connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection()));
m_toggleDisplaySelection->setChecked(true);
m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection");
connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection()));
action = actionManager->createAction("edit_selection");
connect(action, SIGNAL(triggered()), SLOT(editSelection()));
action = actionManager->createAction("convert_to_vector_selection");
connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection()));
action = actionManager->createAction("convert_to_raster_selection");
connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection()));
action = actionManager->createAction("convert_shapes_to_vector_selection");
connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection()));
action = actionManager->createAction("convert_selection_to_shape");
connect(action, SIGNAL(triggered()), SLOT(convertToShape()));
m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode");
connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration()));
m_strokeSelected = actionManager->createAction("stroke_selection");
connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection()));
QClipboard *cb = QApplication::clipboard();
connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged()));
}
void KisSelectionManager::setView(QPointer<KisView>imageView)
{
if (m_imageView && m_imageView->canvasBase()) {
disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), this, SLOT(clipboardDataChanged()));
KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection();
selection->disconnect(this, SLOT(shapeSelectionChanged()));
KisSelectionDecoration *decoration = qobject_cast<KisSelectionDecoration*>(m_imageView->canvasBase()->decoration("selection").data());
if (decoration) {
disconnect(SIGNAL(currentSelectionChanged()), decoration);
}
m_imageView->image()->undoAdapter()->disconnect(this);
m_selectionDecoration = 0;
}
m_imageView = imageView;
if (m_imageView) {
connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()), Qt::UniqueConnection);
KisSelectionDecoration* decoration = qobject_cast<KisSelectionDecoration*>(m_imageView->canvasBase()->decoration("selection").data());
if (!decoration) {
decoration = new KisSelectionDecoration(m_imageView);
decoration->setVisible(true);
m_imageView->canvasBase()->addDecoration(decoration);
}
m_selectionDecoration = decoration;
connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged()));
connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged()));
connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), SLOT(clipboardDataChanged()));
}
}
void KisSelectionManager::clipboardDataChanged()
{
m_view->updateGUI();
}
bool KisSelectionManager::havePixelsSelected()
{
KisSelectionSP activeSelection = m_view->selection();
return activeSelection && !activeSelection->selectedRect().isEmpty();
}
bool KisSelectionManager::havePixelsInClipboard()
{
return m_clipboard->hasClip();
}
bool KisSelectionManager::haveShapesSelected()
{
if (m_view && m_view->canvasBase()) {
return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0;
}
return false;
}
bool KisSelectionManager::haveShapesInClipboard()
{
KoSvgPaste paste;
return paste.hasShapes();
}
bool KisSelectionManager::haveAnySelectionWithPixels()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasPixelSelection();
}
bool KisSelectionManager::haveShapeSelectionWithShapes()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasShapeSelection();
}
bool KisSelectionManager::haveRasterSelectionWithPixels()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasPixelSelection() && !selection->hasShapeSelection();
}
void KisSelectionManager::updateGUI()
{
Q_ASSERT(m_view);
Q_ASSERT(m_clipboard);
if (!m_view || !m_clipboard) return;
bool havePixelsSelected = this->havePixelsSelected();
bool havePixelsInClipboard = this->havePixelsInClipboard();
bool haveShapesSelected = this->haveShapesSelected();
bool haveShapesInClipboard = this->haveShapesInClipboard();
bool haveDevice = m_view->activeDevice();
KisLayerSP activeLayer = m_view->activeLayer();
KisImageWSP image = activeLayer ? activeLayer->image() : 0;
bool canReselect = image && image->canReselectGlobalSelection();
bool canDeselect = image && image->globalSelection();
m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected);
m_cut->setEnabled(havePixelsSelected || haveShapesSelected);
m_copy->setEnabled(havePixelsSelected || haveShapesSelected);
m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard);
m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard);
// FIXME: how about pasting shapes?
m_pasteNew->setEnabled(havePixelsInClipboard);
+ m_pasteAsReference->setEnabled(haveDevice);
m_selectAll->setEnabled(true);
m_deselect->setEnabled(canDeselect);
m_reselect->setEnabled(canReselect);
// m_load->setEnabled(true);
// m_save->setEnabled(havePixelsSelected);
updateStatusBar();
emit signalUpdateGUI();
}
void KisSelectionManager::updateStatusBar()
{
if (m_view && m_view->statusBar()) {
m_view->statusBar()->setSelection(m_view->image());
}
}
void KisSelectionManager::selectionChanged()
{
m_view->updateGUI();
emit currentSelectionChanged();
}
void KisSelectionManager::cut()
{
KisCutCopyActionFactory factory;
factory.run(true, false, m_view);
}
void KisSelectionManager::copy()
{
KisCutCopyActionFactory factory;
factory.run(false, false, m_view);
}
void KisSelectionManager::cutSharp()
{
KisCutCopyActionFactory factory;
factory.run(true, true, m_view);
}
void KisSelectionManager::copySharp()
{
KisCutCopyActionFactory factory;
factory.run(false, true, m_view);
}
void KisSelectionManager::copyMerged()
{
KisCopyMergedActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::paste()
{
KisPasteActionFactory factory;
factory.run(false, m_view);
}
void KisSelectionManager::pasteAt()
{
KisPasteActionFactory factory;
factory.run(true, m_view);
}
+void KisSelectionManager::pasteAsReference()
+{
+ KisPasteReferenceActionFactory factory;
+ factory.run(m_view);
+}
+
void KisSelectionManager::pasteNew()
{
KisPasteNewActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::selectAll()
{
KisSelectAllActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::deselect()
{
KisDeselectActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::invert()
{
if(m_invert)
m_invert->trigger();
}
void KisSelectionManager::reselect()
{
KisReselectActionFactory factory;
factory.run(m_view);
}
#include <KoToolManager.h>
#include <KoInteractionTool.h>
void KisSelectionManager::editSelection()
{
KisSelectionSP selection = m_view->selection();
if (!selection) return;
KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask");
KIS_SAFE_ASSERT_RECOVER_RETURN(action);
if (!action->isChecked()) {
action->setChecked(true);
emit action->toggled(true);
emit action->triggered(true);
}
KisNodeSP node = selection->parentNode();
KIS_SAFE_ASSERT_RECOVER_RETURN(node);
m_view->nodeManager()->slotNonUiActivatedNode(node);
if (selection->hasShapeSelection()) {
KisShapeSelection *shapeSelection = dynamic_cast<KisShapeSelection*>(selection->shapeSelection());
KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection);
KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID);
QList<KoShape*> shapes = shapeSelection->shapes();
if (shapes.isEmpty()) {
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes");
return;
}
Q_FOREACH (KoShape *shape, shapes) {
m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape);
}
} else {
KoToolManager::instance()->switchToolRequested("KisToolTransform");
}
}
void KisSelectionManager::convertToVectorSelection()
{
KisSelectionToVectorActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertToRasterSelection()
{
KisSelectionToRasterActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertShapesToVectorSelection()
{
KisShapesToVectorSelectionActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertToShape()
{
KisSelectionToShapeActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::clear()
{
KisClearActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::fillForegroundColor()
{
KisFillActionFactory factory;
factory.run("fg", m_view);
}
void KisSelectionManager::fillBackgroundColor()
{
KisFillActionFactory factory;
factory.run("bg", m_view);
}
void KisSelectionManager::fillPattern()
{
KisFillActionFactory factory;
factory.run("pattern", m_view);
}
void KisSelectionManager::fillForegroundColorOpacity()
{
KisFillActionFactory factory;
factory.run("fg_opacity", m_view);
}
void KisSelectionManager::fillBackgroundColorOpacity()
{
KisFillActionFactory factory;
factory.run("bg_opacity", m_view);
}
void KisSelectionManager::fillPatternOpacity()
{
KisFillActionFactory factory;
factory.run("pattern_opacity", m_view);
}
void KisSelectionManager::copySelectionToNewLayer()
{
copy();
paste();
}
void KisSelectionManager::cutToNewLayer()
{
cut();
paste();
}
void KisSelectionManager::toggleDisplaySelection()
{
KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration);
m_selectionDecoration->toggleVisibility();
m_toggleDisplaySelection->blockSignals(true);
m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible());
m_toggleDisplaySelection->blockSignals(false);
emit displaySelectionChanged();
}
bool KisSelectionManager::displaySelection()
{
return m_toggleDisplaySelection->isChecked();
}
void KisSelectionManager::shapeSelectionChanged()
{
KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager();
KoSelection * selection = shapeManager->selection();
QList<KoShape*> selectedShapes = selection->selectedShapes();
KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray));
Q_FOREACH (KoShape* shape, shapeManager->shapes()) {
if (dynamic_cast<KisShapeSelection*>(shape->parent())) {
if (selectedShapes.contains(shape))
shape->setStroke(border);
else
shape->setStroke(KoShapeStrokeSP());
}
}
m_view->updateGUI();
}
void KisSelectionManager::imageResizeToSelection()
{
KisImageResizeToSelectionActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::paintSelectedShapes()
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayerSP layer = m_view->activeLayer();
if (!layer) return;
QList<KoShape*> shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes();
KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8);
KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes");
m_adapter->beginMacro(actionName);
m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data());
KisFigurePaintingToolHelper helper(actionName,
image,
paintLayer.data(),
m_view->canvasResourceProvider()->resourceManager(),
KisPainter::StrokeStyleBrush,
KisPainter::FillStyleNone);
Q_FOREACH (KoShape* shape, shapes) {
QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes());
QPainterPath mapedOutline = matrix.map(shape->outline());
helper.paintPainterPath(mapedOutline);
}
m_adapter->endMacro();
}
void KisSelectionManager::slotToggleSelectionDecoration()
{
KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration);
KisSelectionDecoration::Mode mode =
m_selectionDecoration->mode() ?
KisSelectionDecoration::Ants : KisSelectionDecoration::Mask;
m_selectionDecoration->setMode(mode);
emit displaySelectionChanged();
}
bool KisSelectionManager::showSelectionAsMask() const
{
if (m_selectionDecoration) {
return m_selectionDecoration->mode() == KisSelectionDecoration::Mask;
}
return false;
}
void KisSelectionManager::slotStrokeSelection()
{
KisImageWSP image = m_view->image();
if (!image ) {
return;
}
KisNodeSP currentNode = m_view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
bool isVectorLayer = false;
if (currentNode->inherits("KisShapeLayer")) {
isVectorLayer = true;
}
QPointer<KisDlgStrokeSelection> dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer);
if (dlg->exec() == QDialog::Accepted) {
StrokeSelectionOptions params = dlg->getParams();
if (params.brushSelected){
KisStrokeBrushSelectionActionFactory factory;
factory.run(m_view, params);
}
else {
KisStrokeSelectionActionFactory factory;
factory.run(m_view, params);
}
}
delete dlg;
}
#include "kis_image_barrier_locker.h"
#include "kis_selection_tool_helper.h"
void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action)
{
KisImageSP image = m_view->image();
if (!m_view->blockUntilOperationsFinished(image)) {
return;
}
KUndo2MagicString actionName;
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
KisCanvas2 *canvas = m_view->canvasBase();
{
KisImageBarrierLocker locker(image);
KisPaintDeviceSP device = node->projection();
if (!device) device = node->paintDevice();
if (!device) device = node->original();
- KIS_ASSERT_RECOVER_RETURN(canvas && device);
+
+ if (!device) return;
QRect rc = device->exactBounds();
if (rc.isEmpty()) return;
+ KIS_ASSERT_RECOVER_RETURN(canvas);
+
/**
* If there is nothing selected, just create a new selection
*/
if (!canvas->imageView()->selection()) {
action = SELECTION_REPLACE;
}
switch (action) {
case SELECTION_ADD:
actionName = kundo2_i18n("Select Opaque (Add)");
break;
case SELECTION_SUBTRACT:
actionName = kundo2_i18n("Select Opaque (Subtract)");
break;
case SELECTION_INTERSECT:
actionName = kundo2_i18n("Select Opaque (Intersect)");
break;
case SELECTION_SYMMETRICDIFFERENCE:
actionName = kundo2_i18n("Select Opaque (Symmetric Difference)");
break;
default:
actionName = kundo2_i18n("Select Opaque");
break;
}
qint32 x, y, w, h;
rc.getRect(&x, &y, &w, &h);
const KoColorSpace * cs = device->colorSpace();
KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w);
KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w);
for (int row = y; row < h + y; ++row) {
do {
*selIter->rawData() = cs->opacityU8(deviter->oldRawData());
} while (deviter->nextPixel() && selIter->nextPixel());
deviter->nextRow();
selIter->nextRow();
}
}
KisSelectionToolHelper helper(canvas, actionName);
tmpSel->invalidateOutlineCache();
helper.selectPixelSelection(tmpSel, action);
}
diff --git a/libs/ui/kis_selection_manager.h b/libs/ui/kis_selection_manager.h
index ea9024071d..24c80b7fc9 100644
--- a/libs/ui/kis_selection_manager.h
+++ b/libs/ui/kis_selection_manager.h
@@ -1,178 +1,180 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SELECTION_MANAGER_
#define KIS_SELECTION_MANAGER_
#include <QObject>
#include <QList>
#include <QPointer>
#include <kis_image.h>
#include "KisView.h"
#include <KisSelectionTags.h>
#include <kritaui_export.h>
class KisActionManager;
class KisAction;
class QAction;
class KoViewConverter;
class KisDocument;
class KisViewManager;
class KisClipboard;
class KisNodeCommandsAdapter;
class KisView;
class KisSelectionFilter;
class KisSelectionDecoration;
/**
* The selection manager is responsible selections
* and the clipboard.
*/
class KRITAUI_EXPORT KisSelectionManager : public QObject
{
Q_OBJECT
Q_PROPERTY(bool displaySelection READ displaySelection NOTIFY displaySelectionChanged);
Q_PROPERTY(bool havePixelsSelected READ havePixelsSelected NOTIFY currentSelectionChanged);
public:
KisSelectionManager(KisViewManager * view);
~KisSelectionManager() override;
void setup(KisActionManager* actionManager);
void setView(QPointer<KisView>imageView);
public:
/**
* This function return if the selection should be displayed
*/
bool displaySelection();
bool showSelectionAsMask() const;
public Q_SLOTS:
void updateGUI();
void selectionChanged();
void clipboardDataChanged();
void cut();
void copy();
void cutSharp();
void copySharp();
void copyMerged();
void paste();
void pasteNew();
void pasteAt();
+ void pasteAsReference();
void cutToNewLayer();
void selectAll();
void deselect();
void invert();
void clear();
void fillForegroundColor();
void fillBackgroundColor();
void fillPattern();
void fillForegroundColorOpacity();
void fillBackgroundColorOpacity();
void fillPatternOpacity();
void reselect();
void editSelection();
void convertToVectorSelection();
void convertToRasterSelection();
void convertShapesToVectorSelection();
void convertToShape();
void copySelectionToNewLayer();
void toggleDisplaySelection();
void shapeSelectionChanged();
void imageResizeToSelection();
void paintSelectedShapes();
void slotToggleSelectionDecoration();
void slotStrokeSelection();
void selectOpaqueOnNode(KisNodeSP node, SelectionAction action);
Q_SIGNALS:
void currentSelectionChanged();
void signalUpdateGUI();
void displaySelectionChanged();
void strokeSelected();
public:
bool havePixelsSelected();
bool havePixelsInClipboard();
bool haveShapesSelected();
bool haveShapesInClipboard();
/// Checks if the current selection is editable and has some pixels selected in the pixel selection
bool haveAnySelectionWithPixels();
bool haveShapeSelectionWithShapes();
bool haveRasterSelectionWithPixels();
private:
void fill(const KoColor& color, bool fillWithPattern, const QString& transactionText);
void updateStatusBar();
KisViewManager * m_view;
KisDocument * m_doc;
QPointer<KisView>m_imageView;
KisClipboard * m_clipboard;
KisNodeCommandsAdapter* m_adapter;
KisAction *m_copy;
KisAction *m_copyMerged;
KisAction *m_cut;
KisAction *m_paste;
KisAction *m_pasteAt;
+ KisAction *m_pasteAsReference;
KisAction *m_pasteNew;
KisAction *m_cutToNewLayer;
KisAction *m_selectAll;
KisAction *m_deselect;
KisAction *m_clear;
KisAction *m_reselect;
KisAction *m_invert;
KisAction *m_copyToNewLayer;
KisAction *m_fillForegroundColor;
KisAction *m_fillBackgroundColor;
KisAction *m_fillPattern;
KisAction *m_fillForegroundColorOpacity;
KisAction *m_fillBackgroundColorOpacity;
KisAction *m_fillPatternOpacity;
KisAction *m_imageResizeToSelection;
KisAction *m_strokeShapes;
KisAction *m_toggleDisplaySelection;
KisAction *m_toggleSelectionOverlayMode;
KisAction *m_strokeSelected;
QList<QAction*> m_pluginActions;
QPointer<KisSelectionDecoration> m_selectionDecoration;
};
#endif // KIS_SELECTION_MANAGER_
diff --git a/libs/ui/kis_stopgradient_editor.cpp b/libs/ui/kis_stopgradient_editor.cpp
index f5f1eb50a3..bdfbdd47a9 100644
--- a/libs/ui/kis_stopgradient_editor.cpp
+++ b/libs/ui/kis_stopgradient_editor.cpp
@@ -1,198 +1,197 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2016 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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_stopgradient_editor.h"
#include <QPainter>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <KoColorSpace.h>
#include <resources/KoStopGradient.h>
#include "kis_debug.h"
#include <kis_icon_utils.h>
/****************************** KisStopGradientEditor ******************************/
KisStopGradientEditor::KisStopGradientEditor(QWidget *parent)
: QWidget(parent),
m_gradient(0)
{
setupUi(this);
connect(gradientSlider, SIGNAL(sigSelectedStop(int)), this, SLOT(stopChanged(int)));
connect(nameedit, SIGNAL(editingFinished()), this, SLOT(nameChanged()));
connect(colorButton, SIGNAL(changed(KoColor)), SLOT(colorChanged(KoColor)));
opacitySlider->setPrefix(i18n("Opacity: "));
opacitySlider->setRange(0.0, 1.0, 2);
connect(opacitySlider, SIGNAL(valueChanged(qreal)), this, SLOT(opacityChanged(qreal)));
buttonReverse->setIcon(KisIconUtils::loadIcon("view-refresh"));
buttonReverse->setToolTip(i18n("Flip Gradient"));
KisIconUtils::updateIcon(buttonReverse);
connect(buttonReverse, SIGNAL(pressed()), SLOT(reverse()));
buttonReverseSecond->setIcon(KisIconUtils::loadIcon("view-refresh"));
buttonReverseSecond->setToolTip(i18n("Flip Gradient"));
KisIconUtils::updateIcon(buttonReverseSecond);
connect(buttonReverseSecond, SIGNAL(clicked()), SLOT(reverse()));
setCompactMode(false);
setGradient(0);
stopChanged(-1);
}
KisStopGradientEditor::KisStopGradientEditor(KoStopGradient* gradient, QWidget *parent, const char* name, const QString& caption)
: KisStopGradientEditor(parent)
{
setObjectName(name);
setWindowTitle(caption);
-
setGradient(gradient);
}
void KisStopGradientEditor::setCompactMode(bool value)
{
lblName->setVisible(!value);
buttonReverse->setVisible(!value);
nameedit->setVisible(!value);
buttonReverseSecond->setVisible(value);
}
void KisStopGradientEditor::setGradient(KoStopGradient *gradient)
{
m_gradient = gradient;
setEnabled(m_gradient);
if (m_gradient) {
gradientSlider->setGradientResource(m_gradient);
nameedit->setText(gradient->name());
stopChanged(gradientSlider->selectedStop());
}
emit sigGradientChanged();
}
void KisStopGradientEditor::notifyGlobalColorChanged(const KoColor &color)
{
if (colorButton->isEnabled() &&
color != colorButton->color()) {
colorButton->setColor(color);
}
}
boost::optional<KoColor> KisStopGradientEditor::currentActiveStopColor() const
{
if (!colorButton->isEnabled()) return boost::none;
return colorButton->color();
}
void KisStopGradientEditor::stopChanged(int stop)
{
if (!m_gradient) return;
const bool hasStopSelected = stop >= 0;
opacitySlider->setEnabled(hasStopSelected);
colorButton->setEnabled(hasStopSelected);
stopLabel->setEnabled(hasStopSelected);
if (hasStopSelected) {
KoColor color = m_gradient->stops()[stop].second;
opacitySlider->setValue(color.opacityF());
color.setOpacity(1.0);
colorButton->setColor(color);
}
emit sigGradientChanged();
}
void KisStopGradientEditor::colorChanged(const KoColor& color)
{
if (!m_gradient) return;
QList<KoGradientStop> stops = m_gradient->stops();
int currentStop = gradientSlider->selectedStop();
double t = stops[currentStop].first;
KoColor c(color, stops[currentStop].second.colorSpace());
c.setOpacity(stops[currentStop].second.opacityU8());
stops.removeAt(currentStop);
stops.insert(currentStop, KoGradientStop(t, c));
m_gradient->setStops(stops);
gradientSlider->update();
emit sigGradientChanged();
}
void KisStopGradientEditor::opacityChanged(qreal value)
{
if (!m_gradient) return;
QList<KoGradientStop> stops = m_gradient->stops();
int currentStop = gradientSlider->selectedStop();
double t = stops[currentStop].first;
KoColor c = stops[currentStop].second;
c.setOpacity(value);
stops.removeAt(currentStop);
stops.insert(currentStop, KoGradientStop(t, c));
m_gradient->setStops(stops);
gradientSlider->update();
emit sigGradientChanged();
}
void KisStopGradientEditor::nameChanged()
{
if (!m_gradient) return;
m_gradient->setName(nameedit->text());
emit sigGradientChanged();
}
void KisStopGradientEditor::reverse()
{
if (!m_gradient) return;
QList<KoGradientStop> stops = m_gradient->stops();
QList<KoGradientStop> reversedStops;
for(const KoGradientStop& stop : stops) {
reversedStops.push_front(KoGradientStop(1 - stop.first, stop.second));
}
m_gradient->setStops(reversedStops);
gradientSlider->setSelectedStop(stops.size() - 1 - gradientSlider->selectedStop());
emit sigGradientChanged();
}
diff --git a/libs/ui/kis_tooltip_manager.cpp b/libs/ui/kis_tooltip_manager.cpp
index 1bc93e3db3..c2c26678e3 100644
--- a/libs/ui/kis_tooltip_manager.cpp
+++ b/libs/ui/kis_tooltip_manager.cpp
@@ -1,99 +1,100 @@
/*
* Copyright (c) 2014 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tooltip_manager.h"
#include <QFile>
#include <QAction>
#include <QInputDialog>
#include <QDomDocument>
#include <QMenuBar>
#include <kactioncollection.h>
#include <kis_debug.h>
#include <KisMainWindow.h>
#include "KisViewManager.h"
KisTooltipManager::KisTooltipManager(KisViewManager* view) : QObject(view), m_view(view), m_recording(false)
{
m_view->mainWindow()->menuBar()->installEventFilter(this);
}
KisTooltipManager::~KisTooltipManager()
{
if (m_recording) {
QFile f("tooltips.txt");
f.open(QFile::WriteOnly);
QDomDocument doc;
QDomElement root;
root = doc.createElement("tooltips");
doc.appendChild(root);
QMapIterator<QString, QString> it(m_tooltipMap);
while (it.hasNext()) {
it.next();
QDomElement tooltip = doc.createElement("tooltip");
tooltip.setAttribute("action", it.key());
tooltip.appendChild(doc.createTextNode(it.value()));
root.appendChild(tooltip);
}
QTextStream stream(&f);
stream.setCodec("UTF-8");
stream << doc.toString();
f.close();
}
}
void KisTooltipManager::record()
{
m_recording = true;
QList<QAction*> actions = m_view->actionCollection()->actions();
Q_FOREACH (KXMLGUIClient* client, m_view->mainWindow()->childClients() ) {
actions.append(client->actionCollection()->actions());
}
Q_FOREACH (QAction* action, actions) {
action->disconnect();
connect(action, SIGNAL(triggered()), this, SLOT(captureToolip()));
}
}
void KisTooltipManager::captureToolip()
{
QString id = sender()->objectName();
QString oldTooltip;
if (m_tooltipMap.contains(id)) {
oldTooltip = m_tooltipMap[id];
}
bool ok;
QString tooltip = QInputDialog::getText(m_view->mainWindow(), "Add Tooltip",
"New Tooltip:", QLineEdit::Normal,
oldTooltip, &ok);
if (ok && !tooltip.isEmpty()) {
dynamic_cast<QAction*>(sender())->setToolTip(tooltip);
m_tooltipMap[id] = tooltip;
}
}
diff --git a/libs/ui/kis_tooltip_manager.h b/libs/ui/kis_tooltip_manager.h
index d5a6f88064..74c3e85114 100644
--- a/libs/ui/kis_tooltip_manager.h
+++ b/libs/ui/kis_tooltip_manager.h
@@ -1,44 +1,45 @@
/*
* Copyright (c) 2014 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 KISTOOLTIPMANAGER_H
#define KISTOOLTIPMANAGER_H
#include <QObject>
#include <QMap>
class KisViewManager;
class KisTooltipManager : public QObject
{
Q_OBJECT
public:
KisTooltipManager(KisViewManager* view);
~KisTooltipManager() override;
void record();
private Q_SLOTS:
void captureToolip();
private:
KisViewManager* m_view;
bool m_recording;
QMap<QString, QString> m_tooltipMap;
};
#endif // KISTOOLTIPMANAGER_H
diff --git a/libs/ui/kis_zoom_manager.cc b/libs/ui/kis_zoom_manager.cc
index f54d8a3859..9e5b9c8bfd 100644
--- a/libs/ui/kis_zoom_manager.cc
+++ b/libs/ui/kis_zoom_manager.cc
@@ -1,393 +1,395 @@
/*
* Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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_zoom_manager.h"
#include <QGridLayout>
#include <kactioncollection.h>
#include <ktoggleaction.h>
#include <kis_debug.h>
#include <KisView.h>
#include <KoZoomAction.h>
#include <KoRuler.h>
#include <KoZoomHandler.h>
#include <KoZoomController.h>
#include <KoCanvasControllerWidget.h>
#include <KoUnit.h>
#include <KoDpi.h>
#include "KisDocument.h"
#include "KisViewManager.h"
#include "canvas/kis_canvas2.h"
#include "kis_coordinates_converter.h"
#include "kis_image.h"
#include "kis_statusbar.h"
#include "kis_config.h"
#include "krita_utils.h"
#include "kis_canvas_resource_provider.h"
#include "kis_lod_transform.h"
#include "kis_snap_line_strategy.h"
#include "kis_guides_config.h"
#include "kis_guides_manager.h"
class KisZoomController : public KoZoomController
{
public:
KisZoomController(KoCanvasController *co, KisCoordinatesConverter *zh, KActionCollection *actionCollection, QObject *parent)
: KoZoomController(co, zh, actionCollection, parent),
m_converter(zh)
{
}
protected:
- QSize documentToViewport(const QSizeF &size) override {
+ QSizeF documentToViewport(const QSizeF &size) override {
QRectF docRect(QPointF(), size);
- return m_converter->documentToWidget(docRect).toRect().size();
+ QSizeF viewport = m_converter->documentToWidget(docRect).size();
+ QPointF adjustedViewport = m_converter->snapToDevicePixel(QPointF(viewport.width(), viewport.height()));
+ return QSizeF(adjustedViewport.x(), adjustedViewport.y());
}
private:
KisCoordinatesConverter *m_converter;
};
KisZoomManager::KisZoomManager(QPointer<KisView> view, KoZoomHandler * zoomHandler,
KoCanvasController * canvasController)
: m_view(view)
, m_zoomHandler(zoomHandler)
, m_canvasController(canvasController)
, m_horizontalRuler(0)
, m_verticalRuler(0)
, m_zoomAction(0)
, m_zoomActionWidget(0)
, m_physicalDpiX(72.0)
, m_physicalDpiY(72.0)
, m_devicePixelRatio(1.0)
{
}
KisZoomManager::~KisZoomManager()
{
if (m_zoomActionWidget && !m_zoomActionWidget->parent()) {
delete m_zoomActionWidget;
}
}
void KisZoomManager::updateScreenResolution(QWidget *parentWidget)
{
if (qFuzzyCompare(parentWidget->physicalDpiX(), m_physicalDpiX) &&
qFuzzyCompare(parentWidget->physicalDpiY(), m_physicalDpiY) &&
qFuzzyCompare(parentWidget->devicePixelRatioF(), m_devicePixelRatio)) {
return;
}
m_physicalDpiX = parentWidget->physicalDpiX();
m_physicalDpiY = parentWidget->physicalDpiY();
m_devicePixelRatio = parentWidget->devicePixelRatioF();
KisCoordinatesConverter *converter =
dynamic_cast<KisCoordinatesConverter*>(m_zoomHandler);
converter->setDevicePixelRatio(m_devicePixelRatio);
changeAspectMode(m_aspectMode);
}
void KisZoomManager::setup(KActionCollection * actionCollection)
{
KisImageWSP image = m_view->image();
if (!image) return;
connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(setMinMaxZoom()));
KisCoordinatesConverter *converter =
dynamic_cast<KisCoordinatesConverter*>(m_zoomHandler);
m_zoomController = new KisZoomController(m_canvasController, converter, actionCollection, this);
m_zoomHandler->setZoomMode(KoZoomMode::ZOOM_PIXELS);
m_zoomHandler->setZoom(1.0);
m_zoomController->setPageSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()));
m_zoomController->setDocumentSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()), true);
m_zoomAction = m_zoomController->zoomAction();
setMinMaxZoom();
m_zoomActionWidget = m_zoomAction->createWidget(0);
// Put the canvascontroller in a layout so it resizes with us
QGridLayout * layout = new QGridLayout(m_view);
layout->setSpacing(0);
layout->setMargin(0);
m_view->setLayout(layout);
m_view->document()->setUnit(KoUnit(KoUnit::Pixel));
m_horizontalRuler = new KoRuler(m_view, Qt::Horizontal, m_zoomHandler);
m_horizontalRuler->setShowMousePosition(true);
m_horizontalRuler->createGuideToolConnection(m_view->canvasBase());
m_horizontalRuler->setVisible(false); // this prevents the rulers from flashing on to off when a new document is created
m_verticalRuler = new KoRuler(m_view, Qt::Vertical, m_zoomHandler);
m_verticalRuler->setShowMousePosition(true);
m_verticalRuler->createGuideToolConnection(m_view->canvasBase());
m_verticalRuler->setVisible(false);
QAction *rulerAction = actionCollection->action("ruler_pixel_multiple2");
if (m_view->document()->guidesConfig().rulersMultiple2()) {
m_horizontalRuler->setUnitPixelMultiple2(true);
m_verticalRuler->setUnitPixelMultiple2(true);
}
QList<QAction*> unitActions = m_view->createChangeUnitActions(true);
unitActions.append(rulerAction);
m_horizontalRuler->setPopupActionList(unitActions);
m_verticalRuler->setPopupActionList(unitActions);
connect(m_view->document(), SIGNAL(unitChanged(KoUnit)), SLOT(applyRulersUnit(KoUnit)));
connect(rulerAction, SIGNAL(toggled(bool)), SLOT(setRulersPixelMultiple2(bool)));
layout->addWidget(m_horizontalRuler, 0, 1);
layout->addWidget(m_verticalRuler, 1, 0);
layout->addWidget(static_cast<KoCanvasControllerWidget*>(m_canvasController), 1, 1);
connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetXChanged(int)),
this, SLOT(pageOffsetChanged()));
connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetYChanged(int)),
this, SLOT(pageOffsetChanged()));
connect(m_zoomController, SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
this, SLOT(slotZoomChanged(KoZoomMode::Mode,qreal)));
connect(m_zoomController, SIGNAL(aspectModeChanged(bool)),
this, SLOT(changeAspectMode(bool)));
applyRulersUnit(m_view->document()->unit());
}
void KisZoomManager::updateImageBoundsSnapping()
{
const QRectF docRect = m_view->canvasBase()->coordinatesConverter()->imageRectInDocumentPixels();
const QPointF docCenter = docRect.center();
KoSnapGuide *snapGuide = m_view->canvasBase()->snapGuide();
{
KisSnapLineStrategy *boundsSnap =
new KisSnapLineStrategy(KoSnapGuide::DocumentBoundsSnapping);
boundsSnap->addLine(Qt::Horizontal, docRect.y());
boundsSnap->addLine(Qt::Horizontal, docRect.bottom());
boundsSnap->addLine(Qt::Vertical, docRect.x());
boundsSnap->addLine(Qt::Vertical, docRect.right());
snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, boundsSnap);
}
{
KisSnapLineStrategy *centerSnap =
new KisSnapLineStrategy(KoSnapGuide::DocumentCenterSnapping);
centerSnap->addLine(Qt::Horizontal, docCenter.y());
centerSnap->addLine(Qt::Vertical, docCenter.x());
snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentCenterSnapping, centerSnap);
}
}
void KisZoomManager::updateMouseTrackingConnections()
{
bool value = m_horizontalRuler->isVisible() &&
m_verticalRuler->isVisible() &&
m_horizontalRuler->showMousePosition() &&
m_verticalRuler->showMousePosition();
m_mouseTrackingConnections.clear();
if (value) {
m_mouseTrackingConnections.addConnection(m_canvasController->proxyObject,
SIGNAL(canvasMousePositionChanged(QPoint)),
this,
SLOT(mousePositionChanged(QPoint)));
}
}
KoRuler* KisZoomManager::horizontalRuler() const
{
return m_horizontalRuler;
}
KoRuler* KisZoomManager::verticalRuler() const
{
return m_verticalRuler;
}
qreal KisZoomManager::zoom() const
{
qreal zoomX;
qreal zoomY;
m_zoomHandler->zoom(&zoomX, &zoomY);
return zoomX;
}
void KisZoomManager::mousePositionChanged(const QPoint &viewPos)
{
QPoint pt = viewPos - m_rulersOffset;
m_horizontalRuler->updateMouseCoordinate(pt.x());
m_verticalRuler->updateMouseCoordinate(pt.y());
}
void KisZoomManager::setShowRulers(bool show)
{
m_horizontalRuler->setVisible(show);
m_verticalRuler->setVisible(show);
updateMouseTrackingConnections();
}
void KisZoomManager::setRulersTrackMouse(bool value)
{
m_horizontalRuler->setShowMousePosition(value);
m_verticalRuler->setShowMousePosition(value);
updateMouseTrackingConnections();
}
void KisZoomManager::applyRulersUnit(const KoUnit &baseUnit)
{
if (m_view && m_view->image()) {
m_horizontalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->xRes()));
m_verticalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->yRes()));
}
if (m_view->viewManager()) {
m_view->viewManager()->guidesManager()->setUnitType(baseUnit.type());
}
}
void KisZoomManager::setRulersPixelMultiple2(bool enabled)
{
m_horizontalRuler->setUnitPixelMultiple2(enabled);
m_verticalRuler->setUnitPixelMultiple2(enabled);
if (m_view->viewManager()) {
m_view->viewManager()->guidesManager()->setRulersMultiple2(enabled);
}
}
void KisZoomManager::setMinMaxZoom()
{
KisImageWSP image = m_view->image();
if (!image) return;
QSize imageSize = image->size();
qreal minDimension = qMin(imageSize.width(), imageSize.height());
qreal minZoom = qMin(100.0 / minDimension, 0.1);
m_zoomAction->setMinimumZoom(minZoom);
m_zoomAction->setMaximumZoom(90.0);
}
void KisZoomManager::updateGUI()
{
QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels();
QSize documentSize = m_view->canvasBase()->viewConverter()->viewToDocument(widgetRect).toAlignedRect().size();
m_horizontalRuler->setRulerLength(documentSize.width());
m_verticalRuler->setRulerLength(documentSize.height());
applyRulersUnit(m_horizontalRuler->unit());
}
QWidget *KisZoomManager::zoomActionWidget() const
{
return m_zoomActionWidget;
}
void KisZoomManager::slotZoomChanged(KoZoomMode::Mode mode, qreal zoom)
{
Q_UNUSED(mode);
Q_UNUSED(zoom);
m_view->canvasBase()->notifyZoomChanged();
qreal humanZoom = zoom * 100.0;
// XXX: KOMVC -- this is very irritating in MDI mode
if (m_view->viewManager()) {
m_view->viewManager()->
showFloatingMessage(
i18nc("floating message about zoom", "Zoom: %1 %",
KritaUtils::prettyFormatReal(humanZoom)),
QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter);
}
const qreal effectiveZoom =
m_view->canvasBase()->coordinatesConverter()->effectiveZoom();
m_view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::EffectiveZoom, effectiveZoom);
}
void KisZoomManager::slotScrollAreaSizeChanged()
{
pageOffsetChanged();
updateGUI();
}
void KisZoomManager::changeAspectMode(bool aspectMode)
{
KisImageWSP image = m_view->image();
// changeAspectMode is called with the same aspectMode when the window is
// moved across screens. Preserve the old zoomMode if this is the case.
const KoZoomMode::Mode newMode =
aspectMode == m_aspectMode ? m_zoomHandler->zoomMode() : KoZoomMode::ZOOM_CONSTANT;
const qreal newZoom = m_zoomHandler->zoom();
const qreal resolutionX =
aspectMode ? image->xRes() / m_devicePixelRatio : POINT_TO_INCH(m_physicalDpiX);
const qreal resolutionY =
aspectMode ? image->yRes() / m_devicePixelRatio : POINT_TO_INCH(m_physicalDpiY);
m_aspectMode = aspectMode;
m_zoomController->setZoom(newMode, newZoom, resolutionX, resolutionY);
m_view->canvasBase()->notifyZoomChanged();
}
void KisZoomManager::pageOffsetChanged()
{
QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels();
m_rulersOffset = widgetRect.topLeft().toPoint();
m_horizontalRuler->setOffset(m_rulersOffset.x());
m_verticalRuler->setOffset(m_rulersOffset.y());
}
void KisZoomManager::zoomTo100()
{
m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
m_view->canvasBase()->notifyZoomChanged();
}
diff --git a/libs/ui/kisexiv2/kis_exif_io.cpp b/libs/ui/kisexiv2/kis_exif_io.cpp
index 654f6fca0c..da86e64559 100644
--- a/libs/ui/kisexiv2/kis_exif_io.cpp
+++ b/libs/ui/kisexiv2/kis_exif_io.cpp
@@ -1,639 +1,640 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_exif_io.h"
#include <exiv2/exif.hpp>
#include <exiv2/error.hpp>
#include <qendian.h>
#include <QIODevice>
#include <QByteArray>
#include <QVariant>
#include <QDateTime>
#include <QDate>
#include <QTime>
#include <QTextCodec>
#include <kis_debug.h>
#include "kis_exiv2.h"
#include <kis_meta_data_store.h>
#include <kis_meta_data_entry.h>
#include <kis_meta_data_value.h>
#include <kis_meta_data_schema.h>
#include <kis_meta_data_schema_registry.h>
struct KisExifIO::Private {
};
// ---- Exception conversion functions ---- //
// convert ExifVersion and FlashpixVersion to a KisMetaData value
KisMetaData::Value exifVersionToKMDValue(const Exiv2::Value::AutoPtr value)
{
const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
if (dvalue) {
Q_ASSERT(dvalue);
QByteArray array(dvalue->count(), 0);
dvalue->copy((Exiv2::byte*)array.data());
return KisMetaData::Value(QString(array));
}
else {
Q_ASSERT(value->typeId() == Exiv2::asciiString);
return KisMetaData::Value(QString::fromLatin1(value->toString().c_str()));
}
}
// convert from KisMetaData value to ExifVersion and FlashpixVersion
Exiv2::Value* kmdValueToExifVersion(const KisMetaData::Value& value)
{
Exiv2::DataValue* dvalue = new Exiv2::DataValue;
QString ver = value.asVariant().toString();
dvalue->read((const Exiv2::byte*)ver.toLatin1().constData(), ver.size());
return dvalue;
}
// Convert an exif array of integer string to a KisMetaData array of integer
KisMetaData::Value exifArrayToKMDIntOrderedArray(const Exiv2::Value::AutoPtr value)
{
QList<KisMetaData::Value> v;
const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
if (dvalue) {
QByteArray array(dvalue->count(), 0);
dvalue->copy((Exiv2::byte*)array.data());
for (int i = 0; i < array.size(); i++) {
QChar c((char)array[i]);
v.push_back(KisMetaData::Value(QString(c).toInt(0)));
}
} else {
Q_ASSERT(value->typeId() == Exiv2::asciiString);
QString str = QString::fromLatin1(value->toString().c_str());
v.push_back(KisMetaData::Value(str.toInt()));
}
return KisMetaData::Value(v, KisMetaData::Value::OrderedArray);
}
// Convert a KisMetaData array of integer to an exif array of integer string
Exiv2::Value* kmdIntOrderedArrayToExifArray(const KisMetaData::Value& value)
{
QList<KisMetaData::Value> v = value.asArray();
QByteArray s;
for (QList<KisMetaData::Value>::iterator it = v.begin();
it != v.end(); ++it) {
int val = it->asVariant().toInt(0);
s += QByteArray::number(val);
}
return new Exiv2::DataValue((const Exiv2::byte*)s.data(), s.size());
}
QDateTime exivValueToDateTime(const Exiv2::Value::AutoPtr value)
{
return QDateTime::fromString(value->toString().c_str(), Qt::ISODate);
}
template<typename T>
inline T fixEndianess(T v, Exiv2::ByteOrder order)
{
switch (order) {
case Exiv2::invalidByteOrder:
return v;
case Exiv2::littleEndian:
return qFromLittleEndian<T>(v);
case Exiv2::bigEndian:
return qFromBigEndian<T>(v);
}
warnKrita << "KisExifIO: unknown byte order";
return v;
}
Exiv2::ByteOrder invertByteOrder(Exiv2::ByteOrder order)
{
switch (order) {
case Exiv2::littleEndian:
return Exiv2::bigEndian;
case Exiv2::bigEndian:
return Exiv2::littleEndian;
case Exiv2::invalidByteOrder:
warnKrita << "KisExifIO: Can't invert Exiv2::invalidByteOrder";
return Exiv2::invalidByteOrder;
}
return Exiv2::invalidByteOrder;
}
KisMetaData::Value exifOECFToKMDOECFStructure(const Exiv2::Value::AutoPtr value, Exiv2::ByteOrder order)
{
QMap<QString, KisMetaData::Value> oecfStructure;
const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
Q_ASSERT(dvalue);
QByteArray array(dvalue->count(), 0);
dvalue->copy((Exiv2::byte*)array.data());
int columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], order);
int rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], order);
if ((columns * rows + 4) > dvalue->count()) { // Sometime byteOrder get messed up (especially if metadata got saved with kexiv2 library, or any library that doesn't save back with the same byte order as the camera)
order = invertByteOrder(order);
columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], order);
rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], order);
Q_ASSERT((columns * rows + 4) > dvalue->count());
}
oecfStructure["Columns"] = KisMetaData::Value(columns);
oecfStructure["Rows"] = KisMetaData::Value(rows);
int index = 4;
QList<KisMetaData::Value> names;
for (int i = 0; i < columns; i++) {
int lastIndex = array.indexOf((char)0, index);
QString name = array.mid(index, lastIndex - index);
if (index != lastIndex) {
index = lastIndex + 1;
dbgMetaData << "Name [" << i << "] =" << name;
names.append(KisMetaData::Value(name));
} else {
names.append(KisMetaData::Value(""));
}
}
oecfStructure["Names"] = KisMetaData::Value(names, KisMetaData::Value::OrderedArray);
QList<KisMetaData::Value> values;
qint32* dataIt = reinterpret_cast<qint32*>(array.data() + index);
for (int i = 0; i < columns; i++) {
for (int j = 0; j < rows; j++) {
values.append(KisMetaData::Value(KisMetaData::Rational(fixEndianess<qint32>(dataIt[0], order), fixEndianess<qint32>(dataIt[1], order))));
dataIt += 2;
}
}
oecfStructure["Values"] = KisMetaData::Value(values, KisMetaData::Value::OrderedArray);
dbgMetaData << "OECF: " << ppVar(columns) << ppVar(rows) << ppVar(dvalue->count());
return KisMetaData::Value(oecfStructure);
}
Exiv2::Value* kmdOECFStructureToExifOECF(const KisMetaData::Value& value)
{
QMap<QString, KisMetaData::Value> oecfStructure = value.asStructure();
quint16 columns = oecfStructure["Columns"].asVariant().toInt(0);
quint16 rows = oecfStructure["Rows"].asVariant().toInt(0);
QList<KisMetaData::Value> names = oecfStructure["Names"].asArray();
QList<KisMetaData::Value> values = oecfStructure["Values"].asArray();
Q_ASSERT(columns*rows == values.size());
int length = 4 + rows * columns * 8; // The 4 byte for storing rows/columns and the rows*columns*sizeof(rational)
bool saveNames = (!names.empty() && names[0].asVariant().toString().size() > 0);
if (saveNames) {
for (int i = 0; i < columns; i++) {
length += names[i].asVariant().toString().size() + 1;
}
}
QByteArray array(length, 0);
(reinterpret_cast<quint16*>(array.data()))[0] = columns;
(reinterpret_cast<quint16*>(array.data()))[1] = rows;
int index = 4;
if (saveNames) {
for (int i = 0; i < columns; i++) {
QByteArray name = names[i].asVariant().toString().toLatin1();
name.append((char)0);
memcpy(array.data() + index, name.data(), name.size());
index += name.size();
}
}
qint32* dataIt = reinterpret_cast<qint32*>(array.data() + index);
for (QList<KisMetaData::Value>::iterator it = values.begin();
it != values.end(); ++it) {
dataIt[0] = it->asRational().numerator;
dataIt[1] = it->asRational().denominator;
dataIt += 2;
}
return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size());
}
KisMetaData::Value deviceSettingDescriptionExifToKMD(const Exiv2::Value::AutoPtr value)
{
QMap<QString, KisMetaData::Value> deviceSettingStructure;
QByteArray array;
const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
if(dvalue)
{
array.resize(dvalue->count());
dvalue->copy((Exiv2::byte*)array.data());
} else {
Q_ASSERT(value->typeId() == Exiv2::unsignedShort);
array.resize(2 * value->count());
value->copy((Exiv2::byte*)array.data(), Exiv2::littleEndian);
}
int columns = (reinterpret_cast<quint16*>(array.data()))[0];
int rows = (reinterpret_cast<quint16*>(array.data()))[1];
deviceSettingStructure["Columns"] = KisMetaData::Value(columns);
deviceSettingStructure["Rows"] = KisMetaData::Value(rows);
QList<KisMetaData::Value> settings;
QByteArray null(2, 0);
for (int index = 4; index < array.size(); )
{
const int lastIndex = array.indexOf(null, index);
const int numChars = (lastIndex - index) / 2; // including trailing zero
QString setting = QString::fromUtf16((ushort*)(void*)( array.data() + index), numChars);
index = lastIndex + 2;
dbgMetaData << "Setting << " << setting;
settings.append(KisMetaData::Value(setting));
}
deviceSettingStructure["Settings"] = KisMetaData::Value(settings, KisMetaData::Value::OrderedArray);
return KisMetaData::Value(deviceSettingStructure);
}
Exiv2::Value* deviceSettingDescriptionKMDToExif(const KisMetaData::Value& value)
{
QMap<QString, KisMetaData::Value> deviceSettingStructure = value.asStructure();
quint16 columns = deviceSettingStructure["Columns"].asVariant().toInt(0);
quint16 rows = deviceSettingStructure["Rows"].asVariant().toInt(0);
QTextCodec* codec = QTextCodec::codecForName("UTF-16");
QList<KisMetaData::Value> settings = deviceSettingStructure["Settings"].asArray();
QByteArray array(4, 0);
(reinterpret_cast<quint16*>(array.data()))[0] = columns;
(reinterpret_cast<quint16*>(array.data()))[1] = rows;
for (int i = 0; i < settings.count(); i++) {
QString str = settings[i].asVariant().toString();
QByteArray setting = codec->fromUnicode(str);
array.append(setting);
}
return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size());
}
KisMetaData::Value cfaPatternExifToKMD(const Exiv2::Value::AutoPtr value, Exiv2::ByteOrder order)
{
QMap<QString, KisMetaData::Value> cfaPatternStructure;
const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
Q_ASSERT(dvalue);
QByteArray array(dvalue->count(), 0);
dvalue->copy((Exiv2::byte*)array.data());
int columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], order);
int rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], order);
if ((columns * rows + 4) != dvalue->count()) { // Sometime byteOrder get messed up (especially if metadata got saved with kexiv2 library, or any library that doesn't save back with the same byte order as the camera)
order = invertByteOrder(order);
columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], order);
rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], order);
Q_ASSERT((columns * rows + 4) == dvalue->count());
}
cfaPatternStructure["Columns"] = KisMetaData::Value(columns);
cfaPatternStructure["Rows"] = KisMetaData::Value(rows);
QList<KisMetaData::Value> values;
int index = 4;
for (int i = 0; i < columns * rows; i++) {
values.append(KisMetaData::Value(*(array.data() + index)));
index++;
}
cfaPatternStructure["Values"] = KisMetaData::Value(values, KisMetaData::Value::OrderedArray);
dbgMetaData << "CFAPattern " << ppVar(columns) << " " << ppVar(rows) << ppVar(values.size()) << ppVar(dvalue->count());
return KisMetaData::Value(cfaPatternStructure);
}
Exiv2::Value* cfaPatternKMDToExif(const KisMetaData::Value& value)
{
QMap<QString, KisMetaData::Value> cfaStructure = value.asStructure();
quint16 columns = cfaStructure["Columns"].asVariant().toInt(0);
quint16 rows = cfaStructure["Rows"].asVariant().toInt(0);
QList<KisMetaData::Value> values = cfaStructure["Values"].asArray();
Q_ASSERT(columns*rows == values.size());
QByteArray array(4 + columns*rows, 0);
(reinterpret_cast<quint16*>(array.data()))[0] = columns;
(reinterpret_cast<quint16*>(array.data()))[1] = rows;
for (int i = 0; i < columns * rows; i++) {
quint8 val = values[i].asVariant().toUInt();
*(array.data() + 4 + i) = val;
}
dbgMetaData << "Cfa Array " << ppVar(columns) << ppVar(rows) << ppVar(array.size());
return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size());
}
// Read and write Flash //
KisMetaData::Value flashExifToKMD(const Exiv2::Value::AutoPtr value)
{
uint16_t v = value->toLong();
QMap<QString, KisMetaData::Value> flashStructure;
bool fired = (v & 0x01); // bit 1 is whether flash was fired or not
flashStructure["Fired"] = QVariant(fired);
int ret = ((v >> 1) & 0x03); // bit 2 and 3 are Return
flashStructure["Return"] = QVariant(ret);
int mode = ((v >> 3) & 0x03); // bit 4 and 5 are Mode
flashStructure["Mode"] = QVariant(mode);
bool function = ((v >> 5) & 0x01); // bit 6 if function
flashStructure["Function"] = QVariant(function);
bool redEye = ((v >> 6) & 0x01); // bit 7 if function
flashStructure["RedEyeMode"] = QVariant(redEye);
return KisMetaData::Value(flashStructure);
}
Exiv2::Value* flashKMDToExif(const KisMetaData::Value& value)
{
uint16_t v = 0;
QMap<QString, KisMetaData::Value> flashStructure = value.asStructure();
v = flashStructure["Fired"].asVariant().toBool();
v |= ((flashStructure["Return"].asVariant().toInt() & 0x03) << 1);
v |= ((flashStructure["Mode"].asVariant().toInt() & 0x03) << 3);
v |= ((flashStructure["Function"].asVariant().toInt() & 0x03) << 5);
v |= ((flashStructure["RedEyeMode"].asVariant().toInt() & 0x03) << 6);
return new Exiv2::ValueType<uint16_t>(v);
}
// ---- Implementation of KisExifIO ----//
KisExifIO::KisExifIO() : d(new Private)
{
}
KisExifIO::~KisExifIO()
{
delete d;
}
bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType) const
{
ioDevice->open(QIODevice::WriteOnly);
Exiv2::ExifData exifData;
if (headerType == KisMetaData::IOBackend::JpegHeader) {
QByteArray header(6, 0);
header[0] = 0x45;
header[1] = 0x78;
header[2] = 0x69;
header[3] = 0x66;
header[4] = 0x00;
header[5] = 0x00;
ioDevice->write(header);
}
for (QHash<QString, KisMetaData::Entry>::const_iterator it = store->begin();
it != store->end(); ++it) {
try {
const KisMetaData::Entry& entry = *it;
dbgMetaData << "Trying to save: " << entry.name() << " of " << entry.schema()->prefix() << ":" << entry.schema()->uri();
QString exivKey;
if (entry.schema()->uri() == KisMetaData::Schema::TIFFSchemaUri) {
exivKey = "Exif.Image." + entry.name();
} else if (entry.schema()->uri() == KisMetaData::Schema::EXIFSchemaUri) { // Distinguish between exif and gps
if (entry.name().left(3) == "GPS") {
exivKey = "Exif.GPS." + entry.name();
} else {
exivKey = "Exif.Photo." + entry.name();
}
} else if (entry.schema()->uri() == KisMetaData::Schema::DublinCoreSchemaUri) {
if (entry.name() == "description") {
exivKey = "Exif.Image.ImageDescription";
} else if (entry.name() == "creator") {
exivKey = "Exif.Image.Artist";
} else if (entry.name() == "rights") {
exivKey = "Exif.Image.Copyright";
}
} else if (entry.schema()->uri() == KisMetaData::Schema::XMPSchemaUri) {
if (entry.name() == "ModifyDate") {
exivKey = "Exif.Image.DateTime";
} else if (entry.name() == "CreatorTool") {
exivKey = "Exif.Image.Software";
}
} else if (entry.schema()->uri() == KisMetaData::Schema::MakerNoteSchemaUri) {
if (entry.name() == "RawData") {
exivKey = "Exif.Photo.MakerNote";
}
}
dbgMetaData << "Saving " << entry.name() << " to " << exivKey;
if (exivKey.isEmpty()) {
dbgMetaData << entry.qualifiedName() << " is unsavable to EXIF";
} else {
Exiv2::ExifKey exifKey(qPrintable(exivKey));
Exiv2::Value* v = 0;
if (exivKey == "Exif.Photo.ExifVersion" || exivKey == "Exif.Photo.FlashpixVersion") {
v = kmdValueToExifVersion(entry.value());
} else if (exivKey == "Exif.Photo.FileSource") {
char s[] = { 0x03 };
v = new Exiv2::DataValue((const Exiv2::byte*)s, 1);
} else if (exivKey == "Exif.Photo.SceneType") {
char s[] = { 0x01 };
v = new Exiv2::DataValue((const Exiv2::byte*)s, 1);
} else if (exivKey == "Exif.Photo.ComponentsConfiguration") {
v = kmdIntOrderedArrayToExifArray(entry.value());
} else if (exivKey == "Exif.Image.Artist") { // load as dc:creator
KisMetaData::Value creator = entry.value();
if (entry.value().asArray().size() > 0) {
creator = entry.value().asArray()[0];
}
#if !EXIV2_TEST_VERSION(0,21,0)
v = kmdValueToExivValue(creator, Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));
#else
v = kmdValueToExivValue(creator, exifKey.defaultTypeId());
#endif
} else if (exivKey == "Exif.Photo.OECF") {
v = kmdOECFStructureToExifOECF(entry.value());
} else if (exivKey == "Exif.Photo.DeviceSettingDescription") {
v = deviceSettingDescriptionKMDToExif(entry.value());
} else if (exivKey == "Exif.Photo.CFAPattern") {
v = cfaPatternKMDToExif(entry.value());
} else if (exivKey == "Exif.Photo.Flash") {
v = flashKMDToExif(entry.value());
} else if (exivKey == "Exif.Photo.UserComment") {
Q_ASSERT(entry.value().type() == KisMetaData::Value::LangArray);
QMap<QString, KisMetaData::Value> langArr = entry.value().asLangArray();
if (langArr.contains("x-default")) {
#if !EXIV2_TEST_VERSION(0,21,0)
v = kmdValueToExivValue(langArr.value("x-default"), Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));
#else
v = kmdValueToExivValue(langArr.value("x-default"), exifKey.defaultTypeId());
#endif
} else if (langArr.size() > 0) {
#if !EXIV2_TEST_VERSION(0,21,0)
v = kmdValueToExivValue(langArr.begin().value(), Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));
#else
v = kmdValueToExivValue(langArr.begin().value(), exifKey.defaultTypeId());
#endif
}
} else {
dbgMetaData << exifKey.tag();
#if !EXIV2_TEST_VERSION(0,21,0)
v = kmdValueToExivValue(entry.value(), Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));
#else
v = kmdValueToExivValue(entry.value(), exifKey.defaultTypeId());
#endif
}
if (v && v->typeId() != Exiv2::invalidTypeId) {
dbgMetaData << "Saving key" << exivKey << " of KMD value" << entry.value();
exifData.add(exifKey, v);
} else {
dbgMetaData << "No exif value was created for" << entry.qualifiedName() << " as" << exivKey;// << " of KMD value" << entry.value();
}
}
} catch (Exiv2::AnyError& e) {
dbgMetaData << "exiv error " << e.what();
}
}
#if !EXIV2_TEST_VERSION(0,18,0)
Exiv2::DataBuf rawData = exifData.copy();
ioDevice->write((const char*) rawData.pData_, rawData.size_);
#else
Exiv2::Blob rawData;
Exiv2::ExifParser::encode(rawData, Exiv2::littleEndian, exifData);
ioDevice->write((const char*) &*rawData.begin(), rawData.size());
#endif
ioDevice->close();
return true;
}
bool KisExifIO::canSaveAllEntries(KisMetaData::Store* /*store*/) const
{
return false; // It's a known fact that exif can't save all information, but TODO: write the check
}
bool KisExifIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const
{
ioDevice->open(QIODevice::ReadOnly);
if (!ioDevice->isOpen()) {
return false;
}
QByteArray arr = ioDevice->readAll();
Exiv2::ExifData exifData;
Exiv2::ByteOrder byteOrder;
#if !EXIV2_TEST_VERSION(0,18,0)
exifData.load((const Exiv2::byte*)arr.data(), arr.size());
byteOrder = exifData.byteOrder();
#else
try {
byteOrder = Exiv2::ExifParser::decode(exifData, (const Exiv2::byte*)arr.data(), arr.size());
}
catch (const std::exception& ex) {
warnKrita << "Received exception trying to parse exiv data" << ex.what();
return false;
}
catch (...) {
dbgKrita << "Received unknown exception trying to parse exiv data";
return false;
}
#endif
dbgMetaData << "Byte order = " << byteOrder << ppVar(Exiv2::bigEndian) << ppVar(Exiv2::littleEndian);
dbgMetaData << "There are" << exifData.count() << " entries in the exif section";
const KisMetaData::Schema* tiffSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri);
Q_ASSERT(tiffSchema);
const KisMetaData::Schema* exifSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::EXIFSchemaUri);
Q_ASSERT(exifSchema);
const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri);
Q_ASSERT(dcSchema);
const KisMetaData::Schema* xmpSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri);
Q_ASSERT(xmpSchema);
for (Exiv2::ExifMetadata::const_iterator it = exifData.begin();
it != exifData.end(); ++it) {
if (it->key() == "Exif.Photo.StripOffsets"
|| it->key() == "RowsPerStrip"
|| it->key() == "StripByteCounts"
|| it->key() == "JPEGInterchangeFormat"
|| it->key() == "JPEGInterchangeFormatLength"
|| it->tagName() == "0x0000" ) {
dbgMetaData << it->key().c_str() << " is ignored";
} else if (it->key() == "Exif.Photo.MakerNote") {
const KisMetaData::Schema* makerNoteSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::MakerNoteSchemaUri);
store->addEntry(KisMetaData::Entry(makerNoteSchema, "RawData", exivValueToKMDValue(it->getValue(), false)));
} else if (it->key() == "Exif.Image.DateTime") { // load as xmp:ModifyDate
store->addEntry(KisMetaData::Entry(xmpSchema, "ModifyDate", KisMetaData::Value(exivValueToDateTime(it->getValue()))));
} else if (it->key() == "Exif.Image.ImageDescription") { // load as "dc:description"
store->addEntry(KisMetaData::Entry(dcSchema, "description", exivValueToKMDValue(it->getValue(), false)));
} else if (it->key() == "Exif.Image.Software") { // load as "xmp:CreatorTool"
store->addEntry(KisMetaData::Entry(xmpSchema, "CreatorTool", exivValueToKMDValue(it->getValue(), false)));
} else if (it->key() == "Exif.Image.Artist") { // load as dc:creator
QList<KisMetaData::Value> creators;
creators.push_back(exivValueToKMDValue(it->getValue(), false));
store->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(creators, KisMetaData::Value::OrderedArray)));
} else if (it->key() == "Exif.Image.Copyright") { // load as dc:rights
store->addEntry(KisMetaData::Entry(dcSchema, "rights", exivValueToKMDValue(it->getValue(), false)));
} else if (it->groupName() == "Image") {
// Tiff tags
QString fixedTN = it->tagName().c_str();
if (it->key() == "Exif.Image.ExifTag") {
dbgMetaData << "Ignoring " << it->key().c_str();
} else if (KisMetaData::Entry::isValidName(fixedTN)) {
store->addEntry(KisMetaData::Entry(tiffSchema, fixedTN, exivValueToKMDValue(it->getValue(), false))) ;
} else {
dbgMetaData << "Invalid tag name: " << fixedTN;
}
} else if (it->groupName() == "Photo" || (it->groupName() == "GPS")) {
// Exif tags (and GPS tags)
KisMetaData::Value metaDataValue;
if (it->key() == "Exif.Photo.ExifVersion" || it->key() == "Exif.Photo.FlashpixVersion") {
metaDataValue = exifVersionToKMDValue(it->getValue());
} else if (it->key() == "Exif.Photo.FileSource") {
metaDataValue = KisMetaData::Value(3);
} else if (it->key() == "Exif.Photo.SceneType") {
metaDataValue = KisMetaData::Value(1);
} else if (it->key() == "Exif.Photo.ComponentsConfiguration") {
metaDataValue = exifArrayToKMDIntOrderedArray(it->getValue());
} else if (it->key() == "Exif.Photo.OECF") {
metaDataValue = exifOECFToKMDOECFStructure(it->getValue(), byteOrder);
} else if (it->key() == "Exif.Photo.DateTimeDigitized" || it->key() == "Exif.Photo.DateTimeOriginal") {
metaDataValue = KisMetaData::Value(exivValueToDateTime(it->getValue()));
} else if (it->key() == "Exif.Photo.DeviceSettingDescription") {
metaDataValue = deviceSettingDescriptionExifToKMD(it->getValue());
} else if (it->key() == "Exif.Photo.CFAPattern") {
metaDataValue = cfaPatternExifToKMD(it->getValue(), byteOrder);
} else if (it->key() == "Exif.Photo.Flash") {
metaDataValue = flashExifToKMD(it->getValue());
} else if (it->key() == "Exif.Photo.UserComment") {
if (it->getValue()->typeId() != Exiv2::undefined) {
KisMetaData::Value vUC = exivValueToKMDValue(it->getValue(), false);
Q_ASSERT(vUC.type() == KisMetaData::Value::Variant);
QVariant commentVar = vUC.asVariant();
QString comment;
if (commentVar.type() == QVariant::String) {
comment = commentVar.toString();
} else if (commentVar.type() == QVariant::ByteArray) {
const QByteArray commentString = commentVar.toByteArray();
comment = QString::fromLatin1(commentString.constData(), commentString.size());
} else {
warnKrita << "KisExifIO: Unhandled UserComment value type.";
}
KisMetaData::Value vcomment(comment);
vcomment.addPropertyQualifier("xml:lang", KisMetaData::Value("x-default"));
QList<KisMetaData::Value> alt;
alt.append(vcomment);
metaDataValue = KisMetaData::Value(alt, KisMetaData::Value::LangArray);
}
} else {
bool forceSeq = false;
KisMetaData::Value::ValueType arrayType = KisMetaData::Value::UnorderedArray;
if (it->key() == "Exif.Photo.ISOSpeedRatings") {
forceSeq = true;
arrayType = KisMetaData::Value::OrderedArray;
}
metaDataValue = exivValueToKMDValue(it->getValue(), forceSeq, arrayType);
}
if (it->key() == "Exif.Photo.InteroperabilityTag" || it->key() == "Exif.Photo.0xea1d" || metaDataValue.type() == KisMetaData::Value::Invalid) { // InteroperabilityTag isn't useful for XMP, 0xea1d isn't a valid Exif tag
warnMetaData << "Ignoring " << it->key().c_str();
} else {
store->addEntry(KisMetaData::Entry(exifSchema, it->tagName().c_str(), metaDataValue));
}
} else if (it->groupName() == "Thumbnail") {
dbgMetaData << "Ignoring thumbnail tag :" << it->key().c_str();
} else {
dbgMetaData << "Unknown exif tag, cannot load:" << it->key().c_str();
}
}
ioDevice->close();
return true;
}
diff --git a/libs/ui/kisexiv2/kis_exif_io.h b/libs/ui/kisexiv2/kis_exif_io.h
index d9a133c82f..4a73e6973e 100644
--- a/libs/ui/kisexiv2/kis_exif_io.h
+++ b/libs/ui/kisexiv2/kis_exif_io.h
@@ -1,53 +1,54 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_EXIF_IO_H_
#define _KIS_EXIF_IO_H_
#include <kis_meta_data_io_backend.h>
#include <klocalizedstring.h>
class KisExifIO : public KisMetaData::IOBackend
{
struct Private;
public:
KisExifIO();
~KisExifIO() override;
QString id() const override {
return "exif";
}
QString name() const override {
return i18n("Exif");
}
BackendType type() const override {
return Binary;
}
bool supportSaving() const override {
return true;
}
bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType = NoHeader) const override;
bool canSaveAllEntries(KisMetaData::Store* store) const override;
bool supportLoading() const override {
return true;
}
bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override;
private:
Private* const d;
};
#endif
diff --git a/libs/ui/kisexiv2/kis_xmp_io.cpp b/libs/ui/kisexiv2/kis_xmp_io.cpp
index 97e0d70af3..10631c2abc 100644
--- a/libs/ui/kisexiv2/kis_xmp_io.cpp
+++ b/libs/ui/kisexiv2/kis_xmp_io.cpp
@@ -1,399 +1,400 @@
/*
* Copyright (c) 2008-2010 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_xmp_io.h"
#include <string>
#include "kis_exiv2.h"
#include <kis_meta_data_store.h>
#include <kis_meta_data_entry.h>
#include <kis_meta_data_parser.h>
#include <kis_meta_data_value.h>
#include <kis_meta_data_schema.h>
#include <kis_meta_data_schema_registry.h>
#include <kis_meta_data_type_info.h>
#include <kis_debug.h>
KisXMPIO::KisXMPIO()
{
}
KisXMPIO::~KisXMPIO()
{
}
inline std::string exiv2Prefix(const KisMetaData::Schema* _schema)
{
const QByteArray latin1SchemaUri = _schema->uri().toLatin1();
std::string prefix = Exiv2::XmpProperties::prefix(latin1SchemaUri.constData());
if (prefix.empty()) {
dbgMetaData << "Unknown namespace " << ppVar(_schema->uri()) << ppVar(_schema->prefix());
prefix = _schema->prefix().toLatin1().constData();
Exiv2::XmpProperties::registerNs(latin1SchemaUri.constData(), prefix);
}
return prefix;
}
namespace
{
void saveStructure(Exiv2::XmpData& xmpData_, const QString& name, const std::string& prefix, const QMap<QString, KisMetaData::Value>& structure, const KisMetaData::Schema* structureSchema)
{
std::string structPrefix = exiv2Prefix(structureSchema);
for (QMap<QString, KisMetaData::Value>::const_iterator it = structure.begin();
it != structure.end(); ++it) {
Q_ASSERT(it.value().type() != KisMetaData::Value::Structure); // Can't nest structure
QString key = QString("%1/%2:%3").arg(name).arg(structPrefix.c_str()).arg(it.key());
Exiv2::XmpKey ekey(prefix, key.toLatin1().constData());
dbgMetaData << ppVar(key) << ppVar(ekey.key().c_str());
Exiv2::Value *v = kmdValueToExivXmpValue(it.value());
if (v) {
xmpData_.add(ekey, v);
}
}
}
}
bool KisXMPIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType) const
{
dbgMetaData << "Save XMP Data";
Exiv2::XmpData xmpData_;
for (QHash<QString, KisMetaData::Entry>::const_iterator it = store->begin();
it != store->end(); ++it) {
const KisMetaData::Entry& entry = *it;
// Check whether the prefix and namespace are know to exiv2
std::string prefix = exiv2Prefix(entry.schema());
dbgMetaData << "Saving " << entry.name();
const KisMetaData::Value& value = entry.value();
const KisMetaData::TypeInfo* typeInfo = entry.schema()->propertyType(entry.name());
if (value.type() == KisMetaData::Value::Structure) {
QMap<QString, KisMetaData::Value> structure = value.asStructure();
const KisMetaData::Schema* structureSchema = 0;
if (typeInfo) {
structureSchema = typeInfo->structureSchema();
}
if (!structureSchema) {
dbgMetaData << "Unknown schema for " << entry.name();
structureSchema = entry.schema();
}
Q_ASSERT(structureSchema);
saveStructure(xmpData_, entry.name(), prefix, structure, structureSchema);
} else {
Exiv2::XmpKey key(prefix, entry.name().toLatin1().constData());
if (typeInfo && (typeInfo->propertyType() == KisMetaData::TypeInfo::OrderedArrayType
|| typeInfo->propertyType() == KisMetaData::TypeInfo::UnorderedArrayType
|| typeInfo->propertyType() == KisMetaData::TypeInfo::AlternativeArrayType)
&& typeInfo->embeddedPropertyType()->propertyType() == KisMetaData::TypeInfo::StructureType) {
// Here is the bad part, again we need to do it by hand
Exiv2::XmpTextValue tv;
switch (typeInfo->propertyType()) {
case KisMetaData::TypeInfo::OrderedArrayType:
tv.setXmpArrayType(Exiv2::XmpValue::xaSeq);
break;
case KisMetaData::TypeInfo::UnorderedArrayType:
tv.setXmpArrayType(Exiv2::XmpValue::xaBag);
break;
case KisMetaData::TypeInfo::AlternativeArrayType:
tv.setXmpArrayType(Exiv2::XmpValue::xaAlt);
break;
default:
// Cannot happen
;
}
xmpData_.add(key, &tv); // set the arrya type
const KisMetaData::TypeInfo* stuctureTypeInfo = typeInfo->embeddedPropertyType();
const KisMetaData::Schema* structureSchema = 0;
if (stuctureTypeInfo) {
structureSchema = stuctureTypeInfo->structureSchema();
}
if (!structureSchema) {
dbgMetaData << "Unknown schema for " << entry.name();
structureSchema = entry.schema();
}
Q_ASSERT(structureSchema);
QList<KisMetaData::Value> array = value.asArray();
for (int idx = 0; idx < array.size(); ++idx) {
saveStructure(xmpData_, QString("%1[%2]").arg(entry.name()).arg(idx + 1), prefix, array[idx].asStructure(), structureSchema);
}
} else {
dbgMetaData << ppVar(key.key().c_str());
Exiv2::Value *v = kmdValueToExivXmpValue(value);
if (v) {
xmpData_.add(key, v);
}
}
}
// TODO property qualifier
}
// Serialize data
std::string xmpPacket_;
Exiv2::XmpParser::encode(xmpPacket_, xmpData_);
// Save data into the IO device
ioDevice->open(QIODevice::WriteOnly);
if (headerType == KisMetaData::IOBackend::JpegHeader) {
xmpPacket_ = "http://ns.adobe.com/xap/1.0/\0" + xmpPacket_;
}
ioDevice->write(xmpPacket_.c_str(), xmpPacket_.length());
return true;
}
bool parseTagName(const QString &tagString,
QString &structName,
int &arrayIndex,
QString &tagName,
const KisMetaData::TypeInfo** typeInfo,
const KisMetaData::Schema *schema)
{
arrayIndex = -1;
*typeInfo = 0;
int numSubNames = tagString.count('/') + 1;
if (numSubNames == 1) {
structName.clear();
tagName = tagString;
*typeInfo = schema->propertyType(tagName);
return true;
}
if (numSubNames == 2) {
QRegExp regexp("([A-Za-z]\\w+)/([A-Za-z]\\w+):([A-Za-z]\\w+)");
if (regexp.indexIn(tagString) != -1) {
structName = regexp.capturedTexts()[1];
tagName = regexp.capturedTexts()[3];
*typeInfo = schema->propertyType(structName);
if (*typeInfo && (*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) {
*typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName);
}
return true;
}
QRegExp regexp2("([A-Za-z]\\w+)\\[(\\d+)\\]/([A-Za-z]\\w+):([A-Za-z]\\w+)");
if (regexp2.indexIn(tagString) != -1) {
structName = regexp2.capturedTexts()[1];
arrayIndex = regexp2.capturedTexts()[2].toInt() - 1;
tagName = regexp2.capturedTexts()[4];
if (schema->propertyType(structName)) {
*typeInfo = schema->propertyType(structName)->embeddedPropertyType();
Q_ASSERT(*typeInfo);
if ((*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) {
*typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName);
}
}
return true;
}
}
warnKrita << "WARNING: Unsupported tag. We do not yet support nested tags. The tag will be dropped!";
warnKrita << " Failing tag:" << tagString;
return false;
}
bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const
{
ioDevice->open(QIODevice::ReadOnly);
dbgMetaData << "Load XMP Data";
std::string xmpPacket_;
QByteArray arr = ioDevice->readAll();
xmpPacket_.assign(arr.data(), arr.length());
dbgMetaData << xmpPacket_.length();
// dbgMetaData << xmpPacket_.c_str();
Exiv2::XmpData xmpData_;
Exiv2::XmpParser::decode(xmpData_, xmpPacket_);
QMap< const KisMetaData::Schema*, QMap<QString, QMap<QString, KisMetaData::Value> > > structures;
QMap< const KisMetaData::Schema*, QMap<QString, QVector< QMap<QString, KisMetaData::Value> > > > arraysOfStructures;
for (Exiv2::XmpData::iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) {
dbgMetaData << "Start iteration" << it->key().c_str();
Exiv2::XmpKey key(it->key());
dbgMetaData << key.groupName().c_str() << " " << key.tagName().c_str() << " " << key.ns().c_str();
if ((key.groupName() == "exif" || key.groupName() == "tiff") && key.tagName() == "NativeDigest") { // TODO: someone who has time to lose can look in adding support for NativeDigest, it's undocumented use by the XMP SDK to check if exif data has been changed while XMP hasn't been updated
dbgMetaData << "dropped";
} else {
const KisMetaData::Schema* schema = KisMetaData::SchemaRegistry::instance()->schemaFromPrefix(key.groupName().c_str());
if (!schema) {
schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(key.ns().c_str());
if (!schema) {
schema = KisMetaData::SchemaRegistry::instance()->create(key.ns().c_str(), key.groupName().c_str());
Q_ASSERT(schema);
}
}
const Exiv2::Value::AutoPtr value = it->getValue();
QString structName;
int arrayIndex = -1;
QString tagName;
const KisMetaData::TypeInfo* typeInfo = 0;
if (!parseTagName(key.tagName().c_str(),
structName, arrayIndex, tagName,
&typeInfo, schema)) continue;
bool isStructureEntry = !structName.isEmpty() && arrayIndex == -1;
bool isStructureInArrayEntry = !structName.isEmpty() && arrayIndex != -1;
Q_ASSERT(isStructureEntry != isStructureInArrayEntry || !isStructureEntry);
KisMetaData::Value v;
bool ignoreValue = false;
// Compute the value
if (value->typeId() == Exiv2::xmpBag
|| value->typeId() == Exiv2::xmpSeq
|| value->typeId() == Exiv2::xmpAlt) {
const KisMetaData::TypeInfo* embeddedTypeInfo = 0;
if (typeInfo) {
embeddedTypeInfo = typeInfo->embeddedPropertyType();
}
const KisMetaData::Parser* parser = 0;
if (embeddedTypeInfo) {
parser = embeddedTypeInfo->parser();
}
const Exiv2::XmpArrayValue* xav = dynamic_cast<const Exiv2::XmpArrayValue*>(value.get());
Q_ASSERT(xav);
QList<KisMetaData::Value> array;
for (int i = 0; i < xav->count(); ++i) {
QString value = QString::fromStdString(xav->toString(i));
if (parser) {
array.push_back(parser->parse(value));
} else {
dbgImage << "No parser " << tagName;
array.push_back(KisMetaData::Value(value));
}
}
KisMetaData::Value::ValueType vt = KisMetaData::Value::Invalid;
switch (xav->xmpArrayType()) {
case Exiv2::XmpValue::xaNone:
warnKrita << "KisXMPIO: Unsupported array";
break;
case Exiv2::XmpValue::xaAlt:
vt = KisMetaData::Value::AlternativeArray;
break;
case Exiv2::XmpValue::xaBag:
vt = KisMetaData::Value::UnorderedArray;
break;
case Exiv2::XmpValue::xaSeq:
vt = KisMetaData::Value::OrderedArray;
break;
}
v = KisMetaData::Value(array, vt);
} else if (value->typeId() == Exiv2::langAlt) {
const Exiv2::LangAltValue* xav = dynamic_cast<const Exiv2::LangAltValue*>(value.get());
QList<KisMetaData::Value> alt;
for (std::map< std::string, std::string>::const_iterator it = xav->value_.begin();
it != xav->value_.end(); ++it) {
KisMetaData::Value valt(it->second.c_str());
valt.addPropertyQualifier("xml:lang", KisMetaData::Value(it->first.c_str()));
alt.push_back(valt);
}
v = KisMetaData::Value(alt, KisMetaData::Value::LangArray);
} else {
QString valTxt = value->toString().c_str();
if (typeInfo && typeInfo->parser()) {
v = typeInfo->parser()->parse(valTxt);
} else {
dbgMetaData << "No parser " << tagName;
v = KisMetaData::Value(valTxt);
}
if (valTxt == "type=\"Struct\"") {
if (!typeInfo || typeInfo->propertyType() == KisMetaData::TypeInfo::StructureType) {
ignoreValue = true;
}
}
}
// set the value
if (isStructureEntry) {
structures[schema][structName][tagName] = v;
} else if (isStructureInArrayEntry) {
if (arraysOfStructures[schema][structName].size() <= arrayIndex) {
arraysOfStructures[schema][structName].resize(arrayIndex + 1);
}
if (!arraysOfStructures[schema][structName][arrayIndex].contains(tagName)) {
arraysOfStructures[schema][structName][arrayIndex][tagName] = v;
} else {
warnKrita << "WARNING: trying to overwrite tag" << tagName << "in" << structName << arrayIndex;
}
} else {
if (!ignoreValue) {
store->addEntry(KisMetaData::Entry(schema, tagName, v));
} else {
dbgMetaData << "Ignoring value for " << tagName << " " << v;
}
}
}
}
for (QMap< const KisMetaData::Schema*, QMap<QString, QMap<QString, KisMetaData::Value> > >::iterator it = structures.begin();
it != structures.end(); ++it) {
const KisMetaData::Schema* schema = it.key();
for (QMap<QString, QMap<QString, KisMetaData::Value> >::iterator it2 = it.value().begin();
it2 != it.value().end(); ++it2) {
store->addEntry(KisMetaData::Entry(schema, it2.key(), KisMetaData::Value(it2.value())));
}
}
for (QMap< const KisMetaData::Schema*, QMap<QString, QVector< QMap<QString, KisMetaData::Value> > > >::iterator it = arraysOfStructures.begin(); it != arraysOfStructures.end(); ++it) {
const KisMetaData::Schema* schema = it.key();
for (QMap<QString, QVector<QMap<QString, KisMetaData::Value> > >::iterator it2 = it.value().begin();
it2 != it.value().end(); ++it2) {
KisMetaData::Value::ValueType type = KisMetaData::Value::OrderedArray;
QString entryName = it2.key();
if (schema->propertyType(entryName)) {
switch (schema->propertyType(entryName)->propertyType()) {
case KisMetaData::TypeInfo::OrderedArrayType:
type = KisMetaData::Value::OrderedArray;
break;
case KisMetaData::TypeInfo::UnorderedArrayType:
type = KisMetaData::Value::OrderedArray;
break;
case KisMetaData::TypeInfo::AlternativeArrayType:
type = KisMetaData::Value::AlternativeArray;
break;
default:
type = KisMetaData::Value::Invalid;
break;
}
} else if (store->containsEntry(schema, entryName)) {
KisMetaData::Value value = store->getEntry(schema, entryName).value();
if (value.isArray()) {
type = value.type();
}
}
store->removeEntry(schema, entryName);
if (type != KisMetaData::Value::Invalid) {
QList< KisMetaData::Value > valueList;
for (int i = 0; i < it2.value().size(); ++i) {
valueList.append(it2.value()[i]);
}
store->addEntry(KisMetaData::Entry(schema, entryName, KisMetaData::Value(valueList, type)));
}
}
}
return true;
}
diff --git a/libs/ui/kisexiv2/kis_xmp_io.h b/libs/ui/kisexiv2/kis_xmp_io.h
index e3d92004bc..32d95ac07b 100644
--- a/libs/ui/kisexiv2/kis_xmp_io.h
+++ b/libs/ui/kisexiv2/kis_xmp_io.h
@@ -1,53 +1,54 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_XMP_IO_H_
#define _KIS_XMP_IO_H_
#include <kis_meta_data_io_backend.h>
#include <klocalizedstring.h>
class KisXMPIO : public KisMetaData::IOBackend
{
struct Private;
public:
KisXMPIO();
~KisXMPIO() override;
QString id() const override {
return "xmp";
}
QString name() const override {
return i18n("XMP");
}
BackendType type() const override {
return Text;
}
bool supportSaving() const override {
return true;
}
bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType = NoHeader) const override;
bool canSaveAllEntries(KisMetaData::Store*) const override {
return true;
}
bool supportLoading() const override {
return true;
}
bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override;
};
#endif
diff --git a/libs/ui/opengl/KisOpenGLModeProber.cpp b/libs/ui/opengl/KisOpenGLModeProber.cpp
index 43de57b4be..a946163fc1 100644
--- a/libs/ui/opengl/KisOpenGLModeProber.cpp
+++ b/libs/ui/opengl/KisOpenGLModeProber.cpp
@@ -1,265 +1,272 @@
/*
* Copyright (c) 2017 Alvin Wong <alvinhochun@gmail.com>
* Copyright (c) 2019 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KisOpenGLModeProber.h"
#include <config-hdr.h>
#include <QApplication>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QWindow>
#include <QGlobalStatic>
Q_GLOBAL_STATIC(KisOpenGLModeProber, s_instance)
KisOpenGLModeProber::KisOpenGLModeProber()
{
}
KisOpenGLModeProber::~KisOpenGLModeProber()
{
}
KisOpenGLModeProber *KisOpenGLModeProber::instance()
{
return s_instance;
}
bool KisOpenGLModeProber::useHDRMode() const
{
return isFormatHDR(QSurfaceFormat::defaultFormat());
}
QSurfaceFormat KisOpenGLModeProber::surfaceformatInUse() const
{
// TODO: use information provided by KisOpenGL instead
QOpenGLContext *sharedContext = QOpenGLContext::globalShareContext();
QSurfaceFormat format = sharedContext ? sharedContext->format() : QSurfaceFormat::defaultFormat();
return format;
}
const KoColorProfile *KisOpenGLModeProber::rootSurfaceColorProfile() const
{
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->p709SRGBProfile();
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const KisSurfaceColorSpace surfaceColorSpace = surfaceformatInUse().colorSpace();
if (surfaceColorSpace == KisSurfaceColorSpace::sRGBColorSpace) {
// use the default one!
#ifdef HAVE_HDR
} else if (surfaceColorSpace == KisSurfaceColorSpace::scRGBColorSpace) {
profile = KoColorSpaceRegistry::instance()->p709G10Profile();
} else if (surfaceColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace) {
profile = KoColorSpaceRegistry::instance()->p2020PQProfile();
#endif
}
#endif
return profile;
}
namespace {
struct AppAttributeSetter
{
AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES)
: m_attribute(attribute),
m_oldValue(QCoreApplication::testAttribute(attribute))
{
QCoreApplication::setAttribute(attribute, useOpenGLES);
}
~AppAttributeSetter() {
QCoreApplication::setAttribute(m_attribute, m_oldValue);
}
private:
Qt::ApplicationAttribute m_attribute;
bool m_oldValue = false;
};
struct SurfaceFormatSetter
{
SurfaceFormatSetter(const QSurfaceFormat &format)
: m_oldFormat(QSurfaceFormat::defaultFormat())
{
QSurfaceFormat::setDefaultFormat(format);
}
~SurfaceFormatSetter() {
QSurfaceFormat::setDefaultFormat(m_oldFormat);
}
private:
QSurfaceFormat m_oldFormat;
};
}
boost::optional<KisOpenGLModeProber::Result>
KisOpenGLModeProber::probeFormat(const QSurfaceFormat &format, bool adjustGlobalState)
{
QScopedPointer<AppAttributeSetter> sharedContextSetter;
QScopedPointer<AppAttributeSetter> glSetter;
QScopedPointer<AppAttributeSetter> glesSetter;
QScopedPointer<SurfaceFormatSetter> formatSetter;
- QScopedPointer<QApplication> application;
+ QScopedPointer<QGuiApplication> application;
int argc = 1;
QByteArray probeAppName("krita");
char *argv = probeAppName.data();
if (adjustGlobalState) {
sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false));
if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) {
glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES));
glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES));
}
formatSetter.reset(new SurfaceFormatSetter(format));
- application.reset(new QApplication(argc, &argv));
+
+ QGuiApplication::setDesktopSettingsAware(false);
+ application.reset(new QGuiApplication(argc, &argv));
+ QGuiApplication::setDesktopSettingsAware(true);
}
QWindow surface;
surface.setFormat(format);
surface.setSurfaceType(QSurface::OpenGLSurface);
surface.create();
QOpenGLContext context;
context.setFormat(format);
if (!context.create()) {
dbgOpenGL << "OpenGL context cannot be created";
return boost::none;
}
if (!context.isValid()) {
dbgOpenGL << "OpenGL context is not valid while checking Qt's OpenGL status";
return boost::none;
}
if (!context.makeCurrent(&surface)) {
dbgOpenGL << "OpenGL context cannot be made current";
return boost::none;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) {
dbgOpenGL << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace();
return boost::none;
}
#endif
return Result(context);
}
bool KisOpenGLModeProber::fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs)
{
return lhs == rhs ||
((lhs == KisSurfaceColorSpace::DefaultColorSpace ||
lhs == KisSurfaceColorSpace::sRGBColorSpace) &&
(rhs == KisSurfaceColorSpace::DefaultColorSpace ||
rhs == KisSurfaceColorSpace::sRGBColorSpace));
}
void KisOpenGLModeProber::initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config,
QSurfaceFormat *format)
{
#ifdef HAVE_HDR
if (config == KisConfig::BT2020_PQ) {
format->setRedBufferSize(10);
format->setGreenBufferSize(10);
format->setBlueBufferSize(10);
format->setAlphaBufferSize(2);
format->setColorSpace(KisSurfaceColorSpace::bt2020PQColorSpace);
} else if (config == KisConfig::BT709_G10) {
format->setRedBufferSize(16);
format->setGreenBufferSize(16);
format->setBlueBufferSize(16);
format->setAlphaBufferSize(16);
format->setColorSpace(KisSurfaceColorSpace::scRGBColorSpace);
} else
#else
if (config == KisConfig::BT2020_PQ) {
qWarning() << "WARNING: Bt.2020 PQ surface type is not supoprted by this build of Krita";
} else if (config == KisConfig::BT709_G10) {
qWarning() << "WARNING: scRGB surface type is not supoprted by this build of Krita";
}
#endif
{
format->setRedBufferSize(8);
format->setGreenBufferSize(8);
format->setBlueBufferSize(8);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
format->setAlphaBufferSize(8);
+#else
+ format->setAlphaBufferSize(0);
+#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// TODO: check if we can use real sRGB space here
format->setColorSpace(KisSurfaceColorSpace::DefaultColorSpace);
#endif
}
}
bool KisOpenGLModeProber::isFormatHDR(const QSurfaceFormat &format)
{
#ifdef HAVE_HDR
bool isBt2020PQ =
format.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace &&
format.redBufferSize() == 10 &&
format.greenBufferSize() == 10 &&
format.blueBufferSize() == 10 &&
format.alphaBufferSize() == 2;
bool isBt709G10 =
format.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace &&
format.redBufferSize() == 16 &&
format.greenBufferSize() == 16 &&
format.blueBufferSize() == 16 &&
format.alphaBufferSize() == 16;
return isBt2020PQ || isBt709G10;
#else
Q_UNUSED(format);
return false;
#endif
}
KisOpenGLModeProber::Result::Result(QOpenGLContext &context) {
if (!context.isValid()) {
return;
}
QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used
m_rendererString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_RENDERER)));
m_driverVersionString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VERSION)));
m_vendorString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VENDOR)));
m_shadingLanguageString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION)));
m_glMajorVersion = context.format().majorVersion();
m_glMinorVersion = context.format().minorVersion();
m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions);
m_isOpenGLES = context.isOpenGLES();
m_format = context.format();
}
diff --git a/libs/ui/opengl/KisScreenInformationAdapter.cpp b/libs/ui/opengl/KisScreenInformationAdapter.cpp
index 236d42bb92..e4c3c075c9 100644
--- a/libs/ui/opengl/KisScreenInformationAdapter.cpp
+++ b/libs/ui/opengl/KisScreenInformationAdapter.cpp
@@ -1,297 +1,299 @@
/*
* Copyright (c) 2019 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "KisScreenInformationAdapter.h"
#include "kis_debug.h"
#include <QOpenGLContext>
#include <QGuiApplication>
#include <QWindow>
#include <config-hdr.h>
#ifdef Q_OS_WIN
#include <qpa/qplatformnativeinterface.h>
#include <d3d11.h>
#include <wrl/client.h>
#include <dxgi1_6.h>
#include "EGL/egl.h"
#include "EGL/eglext.h"
#endif
namespace {
struct EGLException {
EGLException() {}
EGLException(const QString &what) : m_what(what) {}
QString what() const {
return m_what;
}
private:
QString m_what;
};
template <typename FuncType>
void getProcAddressSafe(QOpenGLContext *context, const char *funcName, FuncType &func)
{
func = reinterpret_cast<FuncType>(context->getProcAddress(funcName));
if (!func) {
throw EGLException(QString("failed to fetch function %1").arg(funcName));
}
}
#ifdef Q_OS_WIN
typedef const char *(EGLAPIENTRYP PFNEGLQUERYSTRINGPROC) (EGLDisplay dpy, EGLint name);
#endif
}
struct KisScreenInformationAdapter::Private
{
void initialize(QOpenGLContext *context);
QOpenGLContext *context;
QString errorString;
#ifdef Q_OS_WIN
Microsoft::WRL::ComPtr<IDXGIAdapter1> dxgiAdapter;
#endif
};
KisScreenInformationAdapter::KisScreenInformationAdapter(QOpenGLContext *context)
: m_d(new Private)
{
- m_d->initialize(context);
+ if (context) {
+ m_d->initialize(context);
+ }
}
KisScreenInformationAdapter::~KisScreenInformationAdapter()
{
}
void KisScreenInformationAdapter::Private::initialize(QOpenGLContext *newContext)
{
context = newContext;
errorString.clear();
try {
#if defined Q_OS_WIN && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
if (!context->isOpenGLES()) {
throw EGLException("the context is not OpenGL ES");
}
PFNEGLQUERYSTRINGPROC queryString = nullptr;
getProcAddressSafe(context, "eglQueryString", queryString);
const char* client_extensions = queryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
const QList<QByteArray> extensions = QByteArray(client_extensions).split(' ');
if (!extensions.contains("EGL_ANGLE_platform_angle_d3d") ||
!extensions.contains("EGL_ANGLE_device_creation_d3d11")) {
throw EGLException("the context is not Angle + D3D11");
}
PFNEGLQUERYDISPLAYATTRIBEXTPROC queryDisplayAttribEXT = nullptr;
PFNEGLQUERYDEVICEATTRIBEXTPROC queryDeviceAttribEXT = nullptr;
getProcAddressSafe(context, "eglQueryDisplayAttribEXT", queryDisplayAttribEXT);
getProcAddressSafe(context, "eglQueryDeviceAttribEXT", queryDeviceAttribEXT);
QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
EGLDisplay display = reinterpret_cast<EGLDisplay>(nativeInterface->nativeResourceForContext("egldisplay", context));
if (!display) {
throw EGLException(
QString("couldn't request EGLDisplay handle, display = 0x%1").arg(uintptr_t(display), 0, 16));
}
EGLAttrib value = 0;
EGLBoolean result = false;
result = queryDisplayAttribEXT(display, EGL_DEVICE_EXT, &value);
if (!result || value == EGL_NONE) {
throw EGLException(
QString("couldn't request EGLDeviceEXT handle, result = 0x%1, value = 0x%2")
.arg(result, 0, 16).arg(value, 0, 16));
}
EGLDeviceEXT device = reinterpret_cast<EGLDeviceEXT>(value);
result = queryDeviceAttribEXT(device, EGL_D3D11_DEVICE_ANGLE, &value);
if (!result || value == EGL_NONE) {
throw EGLException(
QString("couldn't request ID3D11Device pointer, result = 0x%1, value = 0x%2")
.arg(result, 0, 16).arg(value, 0, 16));
}
ID3D11Device *deviceD3D = reinterpret_cast<ID3D11Device*>(value);
{
HRESULT result = 0;
Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice;
result = deviceD3D->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
if (FAILED(result)) {
throw EGLException(
QString("couldn't request IDXGIDevice pointer, result = 0x%1").arg(result, 0, 16));
}
Microsoft::WRL::ComPtr<IDXGIAdapter1> dxgiAdapter;
result = dxgiDevice->GetParent(__uuidof(IDXGIAdapter1), (void**)&dxgiAdapter);
if (FAILED(result)) {
throw EGLException(
QString("couldn't request IDXGIAdapter1 pointer, result = 0x%1").arg(result, 0, 16));
}
this->dxgiAdapter = dxgiAdapter;
}
#else
throw EGLException("current platform doesn't support fetching display information");
#endif
} catch (EGLException &e) {
this->context = 0;
this->errorString = e.what();
#ifdef Q_OS_WIN
this->dxgiAdapter.Reset();
#endif
}
}
bool KisScreenInformationAdapter::isValid() const
{
#ifdef Q_OS_WIN
return m_d->context && m_d->dxgiAdapter;
#else
return false;
#endif
}
QString KisScreenInformationAdapter::errorString() const
{
return m_d->errorString;
}
KisScreenInformationAdapter::ScreenInfo KisScreenInformationAdapter::infoForScreen(QScreen *screen) const
{
ScreenInfo info;
#if defined Q_OS_WIN && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
HMONITOR monitor = reinterpret_cast<HMONITOR>(nativeInterface->nativeResourceForScreen("handle", screen));
if (!monitor) {
qWarning("%s: failed to get HMONITOR handle for screen: screen = 0x%X, monitor = 0x%X",
__PRETTY_FUNCTION__, screen, monitor);
}
UINT i = 0;
Microsoft::WRL::ComPtr<IDXGIOutput> currentOutput;
while (m_d->dxgiAdapter->EnumOutputs(i, &currentOutput) != DXGI_ERROR_NOT_FOUND)
{
HRESULT result = 0;
Microsoft::WRL::ComPtr<IDXGIOutput6> output6;
result = currentOutput.As(&output6);
if (output6) {
DXGI_OUTPUT_DESC1 desc;
result = output6->GetDesc1(&desc);
if (desc.Monitor == monitor) {
info.screen = screen;
info.bitsPerColor = desc.BitsPerColor;
info.redPrimary[0] = desc.RedPrimary[0];
info.redPrimary[1] = desc.RedPrimary[1];
info.greenPrimary[0] = desc.GreenPrimary[0];
info.greenPrimary[1] = desc.GreenPrimary[1];
info.bluePrimary[0] = desc.BluePrimary[0];
info.bluePrimary[1] = desc.BluePrimary[1];
info.whitePoint[0] = desc.WhitePoint[0];
info.whitePoint[1] = desc.WhitePoint[1];
info.minLuminance = desc.MinLuminance;
info.maxLuminance = desc.MaxLuminance;
info.maxFullFrameLuminance = desc.MaxFullFrameLuminance;
info.colorSpace = KisSurfaceColorSpace::DefaultColorSpace;
if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) {
info.colorSpace = KisSurfaceColorSpace::sRGBColorSpace;
} else if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) {
#ifdef HAVE_HDR
info.colorSpace = KisSurfaceColorSpace::scRGBColorSpace;
#else
qWarning("WARNING: scRGB display color space is not supported by Qt's build");
#endif
} else if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
#ifdef HAVE_HDR
info.colorSpace = KisSurfaceColorSpace::bt2020PQColorSpace;
#else
qWarning("WARNING: bt2020-pq display color space is not supported by Qt's build");
#endif
} else {
qWarning("WARNING: unknown display color space! 0x%X", desc.ColorSpace);
}
break;
}
}
i++;
}
#endif
Q_UNUSED(screen);
return info;
}
QDebug operator<<(QDebug dbg, const KisScreenInformationAdapter::ScreenInfo &info)
{
QDebugStateSaver saver(dbg);
if (info.isValid()) {
dbg.nospace() << "ScreenInfo("
<< "screen " << info.screen
<< ", bitsPerColor " << info.bitsPerColor
<< ", colorSpace " << info.colorSpace
<< ", redPrimary " << "(" << info.redPrimary[0] << ", " << info.redPrimary[1] << ")"
<< ", greenPrimary " << "(" << info.greenPrimary[0] << ", " << info.greenPrimary[1] << ")"
<< ", bluePrimary " << "(" << info.bluePrimary[0] << ", " << info.bluePrimary[1] << ")"
<< ", whitePoint " << "(" << info.whitePoint[0] << ", " << info.whitePoint[1] << ")"
<< ", minLuminance " << info.minLuminance
<< ", maxLuminance " << info.maxLuminance
<< ", maxFullFrameLuminance " << info.maxFullFrameLuminance
<< ')';
} else {
dbg.nospace() << "ScreenInfo(<invalid>)";
}
return dbg;
}
diff --git a/libs/ui/operations/kis_operation_registry.cpp b/libs/ui/operations/kis_operation_registry.cpp
index c351e840e4..083fce683b 100644
--- a/libs/ui/operations/kis_operation_registry.cpp
+++ b/libs/ui/operations/kis_operation_registry.cpp
@@ -1,53 +1,53 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_operation_registry.h"
#include <QGlobalStatic>
#include "actions/kis_selection_action_factories.h"
-#include "actions/KisPasteActionFactory.h"
+#include "actions/KisPasteActionFactories.h"
Q_GLOBAL_STATIC(KisOperationRegistry, s_instance)
KisOperationRegistry* KisOperationRegistry::instance()
{
return s_instance;
}
KisOperationRegistry::KisOperationRegistry()
{
add(new KisSelectAllActionFactory);
add(new KisDeselectActionFactory);
add(new KisReselectActionFactory);
add(new KisFillActionFactory);
add(new KisClearActionFactory);
add(new KisImageResizeToSelectionActionFactory);
add(new KisCutCopyActionFactory);
add(new KisCopyMergedActionFactory);
add(new KisPasteActionFactory);
add(new KisPasteNewActionFactory);
}
KisOperationRegistry::~KisOperationRegistry()
{
Q_FOREACH (const QString &id, keys()) {
delete get(id);
}
}
diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt
index cfa4b9c64c..a800340260 100644
--- a/libs/ui/tests/CMakeLists.txt
+++ b/libs/ui/tests/CMakeLists.txt
@@ -1,182 +1,183 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata
${CMAKE_SOURCE_DIR}/sdk/tests )
include(ECMAddTests)
macro_add_unittest_definitions()
ecm_add_tests(
kis_image_view_converter_test.cpp
kis_shape_selection_test.cpp
kis_doc2_test.cpp
kis_coordinates_converter_test.cpp
kis_grid_config_test.cpp
kis_stabilized_events_sampler_test.cpp
kis_brush_hud_properties_config_test.cpp
kis_shape_commands_test.cpp
kis_shape_layer_test.cpp
kis_stop_gradient_editor_test.cpp
kis_file_layer_test.cpp
kis_multinode_property_test.cpp
KisFrameSerializerTest.cpp
KisFrameCacheStoreTest.cpp
kis_animation_exporter_test.cpp
kis_prescaled_projection_test.cpp
kis_asl_layer_style_serializer_test.cpp
kis_animation_importer_test.cpp
KisSpinBoxSplineUnitConverterTest.cpp
+ KisDocumentReplaceTest.cpp
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-"
)
ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME KisSelectionDecorationTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeDummiesGraphTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeShapesGraphTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisModelIndexConverterTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp
TEST_NAME KisCategorizedListModelTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeJugglerCompressedTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test(
kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisInputManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test(
kis_node_model_test.cpp modeltest.cpp
TEST_NAME kis_node_model_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
##### Tests that currently fail and should be fixed #####
include(KritaAddBrokenUnitTest)
krita_add_broken_unit_test(
kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp
TEST_NAME kis_shape_controller_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_exiv2_test.cpp
TEST_NAME KisExiv2Test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_clipboard_test.cpp
TEST_NAME KisClipboardTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME FreehandStrokeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME FreehandStrokeBenchmark
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME KisPaintOnTransparencyMaskTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test(
fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME
FillProcessingVisitorTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME FilterStrokeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_selection_manager_test.cpp
TEST_NAME KisSelectionManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
#set_tests_properties(libs-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test(
kis_node_manager_test.cpp
TEST_NAME KisNodeManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisDummiesFacadeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisZoomAndPanTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
#set_tests_properties(libs-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test(
kis_action_manager_test.cpp
TEST_NAME KisActionManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_categories_mapper_test.cpp testing_categories_mapper.cpp
TEST_NAME KisCategoriesMapperTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_animation_frame_cache_test.cpp
TEST_NAME kis_animation_frame_cache_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
krita_add_broken_unit_test(
kis_derived_resources_test.cpp
TEST_NAME kis_derived_resources_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
diff --git a/libs/ui/tests/KisDocumentReplaceTest.cpp b/libs/ui/tests/KisDocumentReplaceTest.cpp
new file mode 100644
index 0000000000..c3ef8e9ae6
--- /dev/null
+++ b/libs/ui/tests/KisDocumentReplaceTest.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "KisDocumentReplaceTest.h"
+
+#include <KisDocument.h>
+#include <kis_image.h>
+#include <kis_types.h>
+#include <KisPart.h>
+#include <kis_layer_utils.h>
+#include <kis_group_layer.h>
+
+#include <QScopedPointer>
+
+void KisDocumentReplaceTest::init()
+{
+ m_doc = KisPart::instance()->createDocument();
+ qDebug() << m_doc->newImage("test", 512, 512, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", 0), KoColor(), KisConfig::RASTER_LAYER, 1, "", 96);
+}
+
+void KisDocumentReplaceTest::finalize()
+{
+ delete m_doc;
+ m_doc = 0;
+}
+
+void KisDocumentReplaceTest::testCopyFromDocument()
+{
+ init();
+ QScopedPointer<KisDocument> clonedDoc(m_doc->lockAndCreateSnapshot());
+ KisDocument *anotherDoc = KisPart::instance()->createDocument();
+ anotherDoc->newImage("test", 512, 512, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", 0), KoColor(), KisConfig::RASTER_LAYER, 2, "", 96);
+ KisImageSP anotherImage(anotherDoc->image());
+ KisNodeSP root(anotherImage->rootLayer());
+ anotherDoc->copyFromDocument(*(clonedDoc.data()));
+ // image pointer should not change
+ QCOMPARE(anotherImage.data(), anotherDoc->image().data());
+ // root node should change
+ QVERIFY(root.data() != anotherDoc->image()->rootLayer().data());
+ // node count should be the same
+ QList<KisNodeSP> oldNodes, newNodes;
+ KisLayerUtils::recursiveApplyNodes(clonedDoc->image()->root(), [&oldNodes](KisNodeSP node) { oldNodes << node; });
+ KisLayerUtils::recursiveApplyNodes(anotherDoc->image()->root(), [&newNodes](KisNodeSP node) { newNodes << node; });
+ QCOMPARE(oldNodes.size(), newNodes.size());
+
+ KisPart::instance()->removeDocument(anotherDoc);
+ finalize();
+}
+
+QTEST_MAIN(KisDocumentReplaceTest)
diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.h b/libs/ui/tests/KisDocumentReplaceTest.h
similarity index 69%
copy from plugins/impex/libkra/tests/kis_kra_loader_test.h
copy to libs/ui/tests/KisDocumentReplaceTest.h
index 88da2d5448..002a7c6ced 100644
--- a/plugins/impex/libkra/tests/kis_kra_loader_test.h
+++ b/libs/ui/tests/KisDocumentReplaceTest.h
@@ -1,37 +1,40 @@
/*
- * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
+ * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_KRA_LOADER_TEST_H
-#define KIS_KRA_LOADER_TEST_H
+#ifndef KIS_DOCUMENT_REPLACE_TEST_H_
+#define KIS_DOCUMENT_REPLACE_TEST_H_
#include <QtTest>
-class KisKraLoaderTest : public QObject
+class KisDocument;
+class KisImage;
+
+class KisDocumentReplaceTest : public QObject
{
Q_OBJECT
-private Q_SLOTS:
- void initTestCase();
+ void init();
+ void finalize();
- void testLoading();
- void testObligeSingleChild();
- void testObligeSingleChildNonTranspPixel();
+private Q_SLOTS:
+ void testCopyFromDocument();
- void testLoadAnimated();
+private:
+ KisDocument *m_doc;
};
-#endif
+#endif // KIS_DOCUMENT_REPLACE_TEST_H_
diff --git a/libs/ui/tests/kis_file_layer_test.cpp b/libs/ui/tests/kis_file_layer_test.cpp
index e3ba09f3aa..a4ada389e4 100644
--- a/libs/ui/tests/kis_file_layer_test.cpp
+++ b/libs/ui/tests/kis_file_layer_test.cpp
@@ -1,158 +1,156 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_file_layer_test.h"
#include <QTest>
#include <KoColor.h>
#include <kis_file_layer.h>
#include <kis_transform_mask.h>
#include <kis_transform_mask_params_interface.h>
#include <testutil.h>
#include "config-limit-long-tests.h"
void waitForMaskUpdates(KisNodeSP root) {
#ifdef LIMIT_LONG_TESTS
KisLayerUtils::forceAllDelayedNodesUpdate(root);
QTest::qWait(100);
#else /* LIMIT_LONG_TESTS */
Q_UNUSED(root);
QTest::qWait(100);
#endif /* LIMIT_LONG_TESTS */
}
void KisFileLayerTest::testFileLayerPlusTransformMaskOffImage()
{
TestUtil::ReferenceImageChecker chk("flayer_tmask_offimage", "file_layer");
QRect refRect(0,0,640,441);
- QRect fillRect(400,400,100,100);
TestUtil::MaskParent p(refRect);
QString refName(TestUtil::fetchDataFileLazy("hakonepa.png"));
KisLayerSP flayer = new KisFileLayer(p.image, "", refName, KisFileLayer::None, "flayer", OPACITY_OPAQUE_U8);
p.image->addNode(flayer, p.image->root(), KisNodeSP());
waitForMaskUpdates(p.image->root());
p.image->waitForDone();
KisTransformMaskSP mask1 = new KisTransformMask();
p.image->addNode(mask1, flayer);
mask1->setName("mask1");
flayer->setDirty(refRect);
p.image->waitForDone();
chk.checkImage(p.image, "00_initial_layer_update");
waitForMaskUpdates(p.image->root());
p.image->waitForDone();
chk.checkImage(p.image, "00X_initial_layer_update");
flayer->setX(580);
flayer->setY(400);
flayer->setDirty(refRect);
p.image->waitForDone();
chk.checkImage(p.image, "01_file_layer_moved");
waitForMaskUpdates(p.image->root());
p.image->waitForDone();
chk.checkImage(p.image, "01X_file_layer_moved");
QTransform transform = QTransform::fromTranslate(-580, -400);
mask1->setTransformParams(KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams(transform)));
/**
* NOTE: here we see our image cropped by 1.5 image size rect!
* That is expected and controlled by
* KisImageConfig::transformMaskOffBoundsReadArea()
* parameter
*/
mask1->setDirty(refRect);
p.image->waitForDone();
chk.checkImage(p.image, "02_mask1_moved_mask_update");
waitForMaskUpdates(p.image->root());
p.image->waitForDone();
chk.checkImage(p.image, "02X_mask1_moved_mask_update");
QVERIFY(chk.testPassed());
}
void KisFileLayerTest::testFileLayerPlusTransformMaskSmallFileBigOffset()
{
TestUtil::ReferenceImageChecker chk("flayer_tmask_huge_offset", "file_layer");
QRect refRect(0,0,2000,1500);
- QRect fillRect(400,400,100,100);
TestUtil::MaskParent p(refRect);
QString refName(TestUtil::fetchDataFileLazy("file_layer_source.png"));
KisLayerSP flayer = new KisFileLayer(p.image, "", refName, KisFileLayer::None, "flayer", OPACITY_OPAQUE_U8);
p.image->addNode(flayer, p.image->root(), KisNodeSP());
waitForMaskUpdates(p.image->root());
p.image->waitForDone();
// check whether the default bounds of the file layer are
// initialized properly
QCOMPARE(flayer->original()->defaultBounds()->bounds(), p.image->bounds());
KisTransformMaskSP mask1 = new KisTransformMask();
p.image->addNode(mask1, flayer);
mask1->setName("mask1");
flayer->setDirty(refRect);
p.image->waitForDone();
chk.checkImage(p.image, "00_initial_layer_update");
waitForMaskUpdates(p.image->root());
p.image->waitForDone();
chk.checkImage(p.image, "00X_initial_layer_update");
QTransform transform;
transform = QTransform::fromTranslate(1200, 300);
mask1->setTransformParams(KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams(transform)));
mask1->setDirty(refRect);
p.image->waitForDone();
chk.checkImage(p.image, "01_mask1_moved_mask_update");
waitForMaskUpdates(p.image->root());
p.image->waitForDone();
chk.checkImage(p.image, "01X_mask1_moved_mask_update");
QVERIFY(chk.testPassed());
}
KISTEST_MAIN(KisFileLayerTest)
diff --git a/libs/ui/tests/kis_node_juggler_compressed_test.cpp b/libs/ui/tests/kis_node_juggler_compressed_test.cpp
index 78c3d12bb6..0fae23ad70 100644
--- a/libs/ui/tests/kis_node_juggler_compressed_test.cpp
+++ b/libs/ui/tests/kis_node_juggler_compressed_test.cpp
@@ -1,190 +1,189 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_juggler_compressed_test.h"
#include <QTest>
#include "kis_node_juggler_compressed.h"
#include <KoColor.h>
#include <KoColorSpace.h>
void KisNodeJugglerCompressedTest::init()
{
p.reset(new TestUtil::MaskParent);
QRect rect1(100, 100, 100, 100);
QRect rect2(150, 150, 150, 150);
- QRect rect3(50, 50, 100, 100);
layer1 = p->layer;
layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace()));
layer2 = new KisPaintLayer(p->image, "paint2", OPACITY_OPAQUE_U8);
layer2->paintDevice()->fill(rect2, KoColor(Qt::blue, layer2->colorSpace()));
layer3 = new KisPaintLayer(p->image, "paint3", OPACITY_OPAQUE_U8);
group4 = new KisGroupLayer(p->image, "group4", OPACITY_OPAQUE_U8);
layer5 = new KisPaintLayer(p->image, "paint5", OPACITY_OPAQUE_U8);
layer6 = new KisPaintLayer(p->image, "paint6", OPACITY_OPAQUE_U8);
p->image->addNode(layer2);
p->image->addNode(layer3);
p->image->addNode(group4);
p->image->addNode(layer5, group4);
p->image->addNode(layer6);
p->image->initialRefreshGraph();
}
void KisNodeJugglerCompressedTest::cleanup()
{
p.reset();
layer1.clear();
layer2.clear();
}
void KisNodeJugglerCompressedTest::testMove(int delayBeforeEnd)
{
TestUtil::ReferenceImageChecker chk("node_juggler", "move_test");
chk.setMaxFailingPixels(0);
KisNodeJugglerCompressed juggler(kundo2_i18n("Move Layer"), p->image, 0, 600);
QVERIFY(chk.checkImage(p->image, "initial"));
juggler.moveNode(layer1, p->image->root(), layer2);
QTest::qWait(100);
QVERIFY(chk.checkImage(p->image, "initial"));
if (delayBeforeEnd) {
QTest::qWait(delayBeforeEnd);
QVERIFY(chk.checkImage(p->image, "moved"));
}
juggler.end();
p->image->waitForDone();
QVERIFY(chk.checkImage(p->image, "moved"));
p->undoStore->undo();
p->image->waitForDone();
QVERIFY(chk.checkImage(p->image, "initial"));
}
void KisNodeJugglerCompressedTest::testApplyUndo()
{
testMove(1000);
}
void KisNodeJugglerCompressedTest::testEndBeforeUpdate()
{
testMove(0);
}
void KisNodeJugglerCompressedTest::testDuplicateImpl(bool externalParent, bool useMove)
{
TestUtil::ReferenceImageChecker chk("node_juggler", "move_test");
chk.setMaxFailingPixels(0);
QStringList initialRef;
initialRef << "paint1";
initialRef << "paint2";
initialRef << "paint3";
initialRef << "group4";
initialRef << "+paint5";
initialRef << "paint6";
QVERIFY(TestUtil::checkHierarchy(p->image->root(), initialRef));
KisNodeList selectedNodes;
selectedNodes << layer2;
selectedNodes << layer3;
selectedNodes << layer5;
KisNodeJugglerCompressed juggler(kundo2_i18n("Duplicate Layers"), p->image, 0, 600);
if (!externalParent) {
juggler.duplicateNode(selectedNodes);
} else {
if (useMove) {
juggler.moveNode(selectedNodes, p->image->root(), layer6);
} else {
juggler.copyNode(selectedNodes, p->image->root(), layer6);
}
}
QTest::qWait(1000);
juggler.end();
p->image->waitForDone();
QStringList ref;
if (!externalParent) {
ref << "paint1";
ref << "paint2";
ref << "paint3";
ref << "group4";
ref << "+paint5";
ref << "+Copy of paint2";
ref << "+Copy of paint3";
ref << "+Copy of paint5";
ref << "paint6";
} else if (!useMove) {
ref << "paint1";
ref << "paint2";
ref << "paint3";
ref << "group4";
ref << "+paint5";
ref << "paint6";
ref << "Copy of paint2";
ref << "Copy of paint3";
ref << "Copy of paint5";
} else {
ref << "paint1";
ref << "group4";
ref << "paint6";
ref << "paint2";
ref << "paint3";
ref << "paint5";
}
QVERIFY(TestUtil::checkHierarchy(p->image->root(), ref));
p->undoStore->undo();
p->image->waitForDone();
QVERIFY(TestUtil::checkHierarchy(p->image->root(), initialRef));
}
void KisNodeJugglerCompressedTest::testDuplicate()
{
testDuplicateImpl(false, false);
}
void KisNodeJugglerCompressedTest::testCopyLayers()
{
testDuplicateImpl(true, false);
}
void KisNodeJugglerCompressedTest::testMoveLayers()
{
testDuplicateImpl(true, true);
}
QTEST_MAIN(KisNodeJugglerCompressedTest)
diff --git a/libs/ui/tests/kis_zoom_and_pan_test.cpp b/libs/ui/tests/kis_zoom_and_pan_test.cpp
index 6be75ad1dd..6690ce7522 100644
--- a/libs/ui/tests/kis_zoom_and_pan_test.cpp
+++ b/libs/ui/tests/kis_zoom_and_pan_test.cpp
@@ -1,766 +1,766 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_zoom_and_pan_test.h"
#include <cmath>
#include <QTest>
#include <kis_filter_configuration.h>
#include "testutil.h"
#include "qimage_based_test.h"
#include <kactioncollection.h>
#include "kis_config.h"
#include "KisMainWindow.h"
#include "KoZoomController.h"
#include "KisDocument.h"
#include "KisPart.h"
#include "KisViewManager.h"
#include "KisView.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_coordinates_converter.h"
#include "kis_filter_strategy.h"
#include "kistest.h"
class ZoomAndPanTester : public TestUtil::QImageBasedTest
{
public:
ZoomAndPanTester()
// we are not going to use our own QImage sets,so
// just exploit the set of the selection manager test
: QImageBasedTest("selection_manager_test")
{
m_undoStore = new KisSurrogateUndoStore();
m_image = createImage(m_undoStore);
m_image->initialRefreshGraph();
QVERIFY(checkLayersInitial(m_image));
m_doc = KisPart::instance()->createDocument();
m_doc->setCurrentImage(m_image);
m_mainWindow = KisPart::instance()->createMainWindow();
m_view = new KisView(m_doc, m_mainWindow->resourceManager(), m_mainWindow->actionCollection(), m_mainWindow);
m_image->refreshGraph();
m_mainWindow->show();
}
~ZoomAndPanTester() {
m_image->waitForDone();
QApplication::processEvents();
delete m_mainWindow;
delete m_doc;
/**
* The event queue may have up to 200k events
* by the time all the tests are finished. Removing
* all of them may last forever, so clear them after
* every single test is finished
*/
QApplication::removePostedEvents(0);
}
QPointer<KisView> view() {
return m_view;
}
KisMainWindow* mainWindow() {
return m_mainWindow;
}
KisImageWSP image() {
return m_image;
}
KisCanvas2* canvas() {
return m_view->canvasBase();
}
QWidget* canvasWidget() {
return m_view->canvasBase()->canvasWidget();
}
KoZoomController* zoomController() {
return m_view->zoomController();
}
KisCanvasController* canvasController() {
return dynamic_cast<KisCanvasController*>(m_view->canvasController());
}
const KisCoordinatesConverter* coordinatesConverter() {
return m_view->canvasBase()->coordinatesConverter();
}
private:
KisSurrogateUndoStore *m_undoStore;
KisImageSP m_image;
KisDocument *m_doc;
QPointer<KisView>m_view;
KisMainWindow *m_mainWindow;
};
template<class P, class T>
inline bool compareWithRounding(const P &pt0, const P &pt1, T tolerance)
{
return qAbs(pt0.x() - pt1.x()) <= tolerance &&
qAbs(pt0.y() - pt1.y()) <= tolerance;
}
bool verifyOffset(ZoomAndPanTester &t, const QPoint &offset) {
if (t.coordinatesConverter()->documentOffset() != offset) {
dbgKrita << "########################";
dbgKrita << "Expected Offset:" << offset;
dbgKrita << "Actual values:";
dbgKrita << "Offset:" << t.coordinatesConverter()->documentOffset();
dbgKrita << "wsize:" << t.canvasWidget()->size();
dbgKrita << "vport:" << t.canvasController()->viewportSize();
dbgKrita << "pref:" << t.canvasController()->preferredCenter();
dbgKrita << "########################";
}
return t.coordinatesConverter()->documentOffset() == offset;
}
bool KisZoomAndPanTest::checkPan(ZoomAndPanTester &t, QPoint shift)
{
QPoint oldOffset = t.coordinatesConverter()->documentOffset();
QPointF oldPrefCenter = t.canvasController()->preferredCenter();
t.canvasController()->pan(shift);
QPoint newOffset = t.coordinatesConverter()->documentOffset();
QPointF newPrefCenter = t.canvasController()->preferredCenter();
QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
QPoint expectedOffset = oldOffset + shift;
QPointF expectedPrefCenter = oldPrefCenter + shift;
// no tolerance accepted for pan
bool offsetAsExpected = newOffset == expectedOffset;
// rounding can happen due to the scroll bars being the main
// source of the offset
bool preferredCenterAsExpected =
compareWithRounding(expectedPrefCenter, newPrefCenter, 1.0);
bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset;
if (!offsetAsExpected ||
!preferredCenterAsExpected ||
!topLeftAsExpected) {
dbgKrita << "***** PAN *****************";
if(!offsetAsExpected) {
dbgKrita << " ### Offset invariant broken";
}
if(!preferredCenterAsExpected) {
dbgKrita << " ### Preferred center invariant broken";
}
if(!topLeftAsExpected) {
dbgKrita << " ### TopLeft invariant broken";
}
dbgKrita << ppVar(expectedOffset);
dbgKrita << ppVar(expectedPrefCenter);
dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
dbgKrita << ppVar(oldPrefCenter) << ppVar(newPrefCenter);
dbgKrita << ppVar(newTopLeft);
dbgKrita << "***************************";
}
return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected;
}
bool KisZoomAndPanTest::checkInvariants(const QPointF &baseFlakePoint,
const QPoint &oldOffset,
const QPointF &oldPreferredCenter,
qreal oldZoom,
const QPoint &newOffset,
const QPointF &newPreferredCenter,
qreal newZoom,
const QPointF &newTopLeft,
const QSize &oldDocumentSize)
{
qreal k = newZoom / oldZoom;
QPointF expectedOffset = oldOffset + (k - 1) * baseFlakePoint;
QPointF expectedPreferredCenter = oldPreferredCenter + (k - 1) * baseFlakePoint;
qreal oldPreferredCenterFractionX = 1.0 * oldPreferredCenter.x() / oldDocumentSize.width();
qreal oldPreferredCenterFractionY = 1.0 * oldPreferredCenter.y() / oldDocumentSize.height();
qreal roundingTolerance =
qMax(qreal(1.0), qMax(oldPreferredCenterFractionX, oldPreferredCenterFractionY) / k);
/**
* In the computation of the offset two roundings happen:
* first for the computation of oldOffset and the second
* for the computation of newOffset. So the maximum tolerance
* should equal 2.
*/
bool offsetAsExpected =
compareWithRounding(expectedOffset, QPointF(newOffset), 2 * roundingTolerance);
/**
* Rounding for the preferred center happens due to the rounding
* of the document size while zooming. The wider the step of the
* zooming, the bigger tolerance should be
*/
bool preferredCenterAsExpected =
compareWithRounding(expectedPreferredCenter, newPreferredCenter,
roundingTolerance);
bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset;
if (!offsetAsExpected ||
!preferredCenterAsExpected ||
!topLeftAsExpected) {
dbgKrita << "***** ZOOM ****************";
if(!offsetAsExpected) {
dbgKrita << " ### Offset invariant broken";
}
if(!preferredCenterAsExpected) {
dbgKrita << " ### Preferred center invariant broken";
}
if(!topLeftAsExpected) {
dbgKrita << " ### TopLeft invariant broken";
}
dbgKrita << ppVar(expectedOffset);
dbgKrita << ppVar(expectedPreferredCenter);
dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter);
dbgKrita << ppVar(oldPreferredCenterFractionX);
dbgKrita << ppVar(oldPreferredCenterFractionY);
dbgKrita << ppVar(oldZoom) << ppVar(newZoom);
dbgKrita << ppVar(baseFlakePoint);
dbgKrita << ppVar(newTopLeft);
dbgKrita << ppVar(roundingTolerance);
dbgKrita << "***************************";
}
return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected;
}
bool KisZoomAndPanTest::checkZoomWithAction(ZoomAndPanTester &t, qreal newZoom, bool limitedZoom)
{
QPoint oldOffset = t.coordinatesConverter()->documentOffset();
QPointF oldPrefCenter = t.canvasController()->preferredCenter();
qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
- QSize oldDocumentSize = t.canvasController()->documentSize();
+ QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom);
QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
return checkInvariants(oldPrefCenter,
oldOffset,
oldPrefCenter,
oldZoom,
t.coordinatesConverter()->documentOffset(),
t.canvasController()->preferredCenter(),
limitedZoom ? oldZoom : newZoom,
newTopLeft,
oldDocumentSize);
}
bool KisZoomAndPanTest::checkZoomWithWheel(ZoomAndPanTester &t, const QPoint &widgetPoint, qreal zoomCoeff, bool limitedZoom)
{
QPoint oldOffset = t.coordinatesConverter()->documentOffset();
QPointF oldPrefCenter = t.canvasController()->preferredCenter();
qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
- QSize oldDocumentSize = t.canvasController()->documentSize();
+ QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
t.canvasController()->zoomRelativeToPoint(widgetPoint, zoomCoeff);
QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();
return checkInvariants(oldOffset + widgetPoint,
oldOffset,
oldPrefCenter,
oldZoom,
t.coordinatesConverter()->documentOffset(),
t.canvasController()->preferredCenter(),
limitedZoom ? oldZoom : zoomCoeff * oldZoom,
newTopLeft,
oldDocumentSize);
}
void KisZoomAndPanTest::testZoom100ChangingWidgetSize()
{
ZoomAndPanTester t;
QCOMPARE(t.image()->size(), QSize(640,441));
QCOMPARE(t.image()->xRes(), 1.0);
QCOMPARE(t.image()->yRes(), 1.0);
t.canvasController()->resize(QSize(1000,1000));
t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
t.canvasController()->setPreferredCenter(QPoint(320,220));
QCOMPARE(t.canvasWidget()->size(), QSize(983,983));
- QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
+ QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
QVERIFY(verifyOffset(t, QPoint(-171,-271)));
t.canvasController()->resize(QSize(700,700));
QCOMPARE(t.canvasWidget()->size(), QSize(683,683));
- QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
+ QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
QVERIFY(verifyOffset(t, QPoint(-171,-271)));
t.canvasController()->setPreferredCenter(QPoint(320,220));
QVERIFY(verifyOffset(t, QPoint(-21,-121)));
t.canvasController()->resize(QSize(400,400));
QCOMPARE(t.canvasWidget()->size(), QSize(383,383));
- QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
+ QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
QVERIFY(verifyOffset(t, QPoint(-21,-121)));
t.canvasController()->setPreferredCenter(QPoint(320,220));
QVERIFY(verifyOffset(t, QPoint(129,29)));
t.canvasController()->pan(QPoint(100,100));
QVERIFY(verifyOffset(t, QPoint(229,129)));
}
void KisZoomAndPanTest::initializeViewport(ZoomAndPanTester &t, bool fullscreenMode, bool rotate, bool mirror)
{
QCOMPARE(t.image()->size(), QSize(640,441));
QCOMPARE(t.image()->xRes(), 1.0);
QCOMPARE(t.image()->yRes(), 1.0);
t.canvasController()->resize(QSize(500,500));
t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
t.canvasController()->setPreferredCenter(QPoint(320,220));
QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
- QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
+ QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
QVERIFY(verifyOffset(t, QPoint(79,-21)));
if (fullscreenMode) {
QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320,220));
QAction *action = t.view()->viewManager()->actionCollection()->action("view_show_canvas_only");
action->setChecked(true);
QVERIFY(verifyOffset(t, QPoint(79,-21)));
QCOMPARE(t.canvasController()->preferredCenter(), QPointF(329,220));
t.canvasController()->resize(QSize(483,483));
QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
- QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
+ QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
QVERIFY(verifyOffset(t, QPoint(79,-21)));
/**
* FIXME: here is a small flaw in KoCanvasControllerWidget
* We cannot set the center point explicitly, because it'll be rounded
* up by recenterPreferred function, so real center point will be
* different. Make the preferredCenter() return real center of the
* image instead of the set value
*/
QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320.5,220));
}
if (rotate) {
t.canvasController()->rotateCanvas(90);
QVERIFY(verifyOffset(t, QPoint(-21,79)));
QVERIFY(compareWithRounding(QPointF(220,320), t.canvasController()->preferredCenter(), 2));
QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
}
if (mirror) {
t.canvasController()->mirrorCanvas(true);
QVERIFY(verifyOffset(t, QPoint(78, -21)));
QVERIFY(compareWithRounding(QPointF(320,220), t.canvasController()->preferredCenter(), 2));
QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
}
}
void KisZoomAndPanTest::testSequentialActionZoomAndPan(bool fullscreenMode, bool rotate, bool mirror)
{
ZoomAndPanTester t;
initializeViewport(t, fullscreenMode, rotate, mirror);
QVERIFY(checkZoomWithAction(t, 0.5));
QVERIFY(checkPan(t, QPoint(100,100)));
QVERIFY(checkZoomWithAction(t, 0.25));
QVERIFY(checkPan(t, QPoint(-100,-100)));
QVERIFY(checkZoomWithAction(t, 0.35));
QVERIFY(checkPan(t, QPoint(100,100)));
QVERIFY(checkZoomWithAction(t, 0.45));
QVERIFY(checkPan(t, QPoint(100,100)));
QVERIFY(checkZoomWithAction(t, 0.85));
QVERIFY(checkPan(t, QPoint(-100,-100)));
QVERIFY(checkZoomWithAction(t, 2.35));
QVERIFY(checkPan(t, QPoint(100,100)));
}
void KisZoomAndPanTest::testSequentialWheelZoomAndPan(bool fullscreenMode, bool rotate, bool mirror)
{
ZoomAndPanTester t;
initializeViewport(t, fullscreenMode, rotate, mirror);
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
QVERIFY(checkPan(t, QPoint(100,100)));
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
QVERIFY(checkPan(t, QPoint(-100,-100)));
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.25));
QVERIFY(checkPan(t, QPoint(100,100)));
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.5));
QVERIFY(checkPan(t, QPoint(100,100)));
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.5));
QVERIFY(checkPan(t, QPoint(-100,-100)));
// check one point which is outside the widget
QVERIFY(checkZoomWithWheel(t, QPoint(-100,100), 2.5));
QVERIFY(checkPan(t, QPoint(-100,-100)));
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5));
QVERIFY(checkPan(t, QPoint(-100,-100)));
}
void KisZoomAndPanTest::testSequentialActionZoomAndPan()
{
testSequentialActionZoomAndPan(false, false, false);
}
void KisZoomAndPanTest::testSequentialActionZoomAndPanFullscreen()
{
testSequentialActionZoomAndPan(true, false, false);
}
void KisZoomAndPanTest::testSequentialActionZoomAndPanRotate()
{
testSequentialActionZoomAndPan(false, true, false);
}
void KisZoomAndPanTest::testSequentialActionZoomAndPanRotateFullscreen()
{
testSequentialActionZoomAndPan(true, true, false);
}
void KisZoomAndPanTest::testSequentialActionZoomAndPanMirror()
{
testSequentialActionZoomAndPan(false, false, true);
}
void KisZoomAndPanTest::testSequentialWheelZoomAndPan()
{
testSequentialWheelZoomAndPan(false, false, false);
}
void KisZoomAndPanTest::testSequentialWheelZoomAndPanFullscreen()
{
testSequentialWheelZoomAndPan(true, false, false);
}
void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotate()
{
testSequentialWheelZoomAndPan(false, true, false);
}
void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotateFullscreen()
{
testSequentialWheelZoomAndPan(true, true, false);
}
void KisZoomAndPanTest::testSequentialWheelZoomAndPanMirror()
{
testSequentialWheelZoomAndPan(false, false, true);
}
void KisZoomAndPanTest::testZoomOnBorderZoomLevels()
{
ZoomAndPanTester t;
initializeViewport(t, false, false, false);
- QPoint widgetPoint(100,100);
+// QPoint widgetPoint(100,100);
warnKrita << "WARNING: testZoomOnBorderZoomLevels() is disabled due to some changes in KoZoomMode::minimum/maximumZoom()";
return;
// test min zoom level
t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::minimumZoom());
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5, true));
QVERIFY(checkZoomWithAction(t, KoZoomMode::minimumZoom() * 0.5, true));
// test max zoom level
t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::maximumZoom());
QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.0, true));
QVERIFY(checkZoomWithAction(t, KoZoomMode::maximumZoom() * 2.0, true));
}
inline QTransform correctionMatrix(qreal angle)
{
return QTransform(0,0,0,sin(M_PI * angle / 180),0,0,0,0,1);
}
bool KisZoomAndPanTest::checkRotation(ZoomAndPanTester &t, qreal angle)
{
// save old values
QPoint oldOffset = t.coordinatesConverter()->documentOffset();
QPointF oldCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
QPointF oldPreferredCenter = t.canvasController()->preferredCenter();
QPointF oldRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
- QSize oldDocumentSize = t.canvasController()->documentSize();
+ QSize oldDocumentSize = t.canvasController()->documentSize().toSize();
qreal baseAngle = t.coordinatesConverter()->rotationAngle();
t.canvasController()->rotateCanvas(angle);
// save result values
QPoint newOffset = t.coordinatesConverter()->documentOffset();
QPointF newCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
QPointF newPreferredCenter = t.canvasController()->preferredCenter();
QPointF newRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
- QSize newDocumentSize = t.canvasController()->documentSize();
+ QSize newDocumentSize = t.canvasController()->documentSize().toSize();
// calculate theoretical preferred center
QTransform rot;
rot.rotate(angle);
QSizeF dSize = t.coordinatesConverter()->imageSizeInFlakePixels();
QPointF dPoint(dSize.width(), dSize.height());
QPointF expectedPreferredCenter =
(oldPreferredCenter - dPoint * correctionMatrix(baseAngle)) * rot +
dPoint * correctionMatrix(baseAngle + angle);
// calculate theoretical offset based on the real preferred center
QPointF wPoint(t.canvasWidget()->size().width(), t.canvasWidget()->size().height());
QPointF expectedOldOffset = oldPreferredCenter - 0.5 * wPoint;
QPointF expectedNewOffset = newPreferredCenter - 0.5 * wPoint;
bool preferredCenterAsExpected =
compareWithRounding(expectedPreferredCenter, newPreferredCenter, 2);
bool oldOffsetAsExpected =
compareWithRounding(expectedOldOffset + oldCenteringCorrection, QPointF(oldOffset), 2);
bool newOffsetAsExpected =
compareWithRounding(expectedNewOffset + newCenteringCorrection, QPointF(newOffset), 3);
qreal zoom = t.zoomController()->zoomAction()->effectiveZoom();
bool realCenterPointAsExpected =
compareWithRounding(oldRealCenterPoint, newRealCenterPoint, 2/zoom);
if (!oldOffsetAsExpected ||
!newOffsetAsExpected ||
!preferredCenterAsExpected ||
!realCenterPointAsExpected) {
dbgKrita << "***** ROTATE **************";
if(!oldOffsetAsExpected) {
dbgKrita << " ### Old offset invariant broken";
}
if(!newOffsetAsExpected) {
dbgKrita << " ### New offset invariant broken";
}
if(!preferredCenterAsExpected) {
dbgKrita << " ### Preferred center invariant broken";
}
if(!realCenterPointAsExpected) {
dbgKrita << " ### *Real* center invariant broken";
}
dbgKrita << ppVar(expectedOldOffset);
dbgKrita << ppVar(expectedNewOffset);
dbgKrita << ppVar(expectedPreferredCenter);
dbgKrita << ppVar(oldOffset) << ppVar(newOffset);
dbgKrita << ppVar(oldCenteringCorrection) << ppVar(newCenteringCorrection);
dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter);
dbgKrita << ppVar(oldRealCenterPoint) << ppVar(newRealCenterPoint);
dbgKrita << ppVar(oldDocumentSize) << ppVar(newDocumentSize);
dbgKrita << ppVar(baseAngle) << "deg";
dbgKrita << ppVar(angle) << "deg";
dbgKrita << "***************************";
}
return preferredCenterAsExpected && oldOffsetAsExpected && newOffsetAsExpected && realCenterPointAsExpected;
}
void KisZoomAndPanTest::testRotation(qreal vastScrolling, qreal zoom)
{
KisConfig cfg(false);
cfg.setVastScrolling(vastScrolling);
ZoomAndPanTester t;
QCOMPARE(t.image()->size(), QSize(640,441));
QCOMPARE(t.image()->xRes(), 1.0);
QCOMPARE(t.image()->yRes(), 1.0);
QPointF preferredCenter = zoom * t.image()->bounds().center();
t.canvasController()->resize(QSize(500,500));
t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, zoom);
t.canvasController()->setPreferredCenter(preferredCenter.toPoint());
QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
- QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
+ QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize());
QPointF realCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
QPointF expectedCenterPoint = QPointF(t.image()->bounds().center());
if(!compareWithRounding(realCenterPoint, expectedCenterPoint, 2/zoom)) {
dbgKrita << "Failed to set initial center point";
dbgKrita << ppVar(expectedCenterPoint) << ppVar(realCenterPoint);
QFAIL("FAIL: Failed to set initial center point");
}
QVERIFY(checkRotation(t, 30));
QVERIFY(checkRotation(t, 20));
QVERIFY(checkRotation(t, 10));
QVERIFY(checkRotation(t, 5));
QVERIFY(checkRotation(t, 5));
QVERIFY(checkRotation(t, 5));
if(vastScrolling < 0.5 && zoom < 1) {
warnKrita << "Disabling a few tests for vast scrolling ="
<< vastScrolling << ". See comment for more";
/**
* We have to disable a couple of tests here for the case when
* vastScrolling value is 0.2. The problem is that the centering
* correction applied to the offset in
* KisCanvasController::rotateCanvas pollutes the preferredCenter
* value, because KoCnvasControllerWidget has no access to this
* correction and cannot calculate the real value of the center of
* the image. To fix this bug the calculation of correction
* (aka "origin") should be moved to the KoCanvasControllerWidget
* itself which would cause quite huge changes (including the change
* of the external interface of it). Namely, we would have to
* *calculate* offset from the value of the scroll bars, but not
* use their values directly:
*
* offset = scrollBarValue - origin
*
* So now we just disable these unittests and allow a couple
* of "jumping" bugs appear in vastScrolling < 0.5 modes, which
* is, actually, not the default case.
*/
} else {
QVERIFY(checkRotation(t, 5));
QVERIFY(checkRotation(t, 5));
QVERIFY(checkRotation(t, 5));
}
}
void KisZoomAndPanTest::testRotation_VastScrolling_1_0()
{
testRotation(0.9, 1.0);
}
void KisZoomAndPanTest::testRotation_VastScrolling_0_5()
{
testRotation(0.9, 0.5);
}
void KisZoomAndPanTest::testRotation_NoVastScrolling_1_0()
{
testRotation(0.2, 1.0);
}
void KisZoomAndPanTest::testRotation_NoVastScrolling_0_5()
{
testRotation(0.2, 0.5);
}
void KisZoomAndPanTest::testImageRescaled_0_5()
{
ZoomAndPanTester t;
QApplication::processEvents();
initializeViewport(t, false, false, false);
QApplication::processEvents();
QVERIFY(checkPan(t, QPoint(200,200)));
QApplication::processEvents();
QPointF oldStillPoint =
t.coordinatesConverter()->imageRectInWidgetPixels().center();
KisFilterStrategy *strategy = new KisBilinearFilterStrategy();
t.image()->scaleImage(QSize(320, 220), t.image()->xRes(), t.image()->yRes(), strategy);
t.image()->waitForDone();
QApplication::processEvents();
delete strategy;
QPointF newStillPoint =
t.coordinatesConverter()->imageRectInWidgetPixels().center();
QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
}
void KisZoomAndPanTest::testImageCropped()
{
ZoomAndPanTester t;
QApplication::processEvents();
initializeViewport(t, false, false, false);
QApplication::processEvents();
QVERIFY(checkPan(t, QPoint(-150,-150)));
QApplication::processEvents();
QPointF oldStillPoint =
t.coordinatesConverter()->imageToWidget(QPointF(150,150));
t.image()->cropImage(QRect(100,100,100,100));
t.image()->waitForDone();
QApplication::processEvents();
QPointF newStillPoint =
t.coordinatesConverter()->imageToWidget(QPointF(50,50));
QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
}
KISTEST_MAIN(KisZoomAndPanTest)
diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp
index 4ccb77a304..5cac907762 100644
--- a/libs/ui/tool/kis_selection_tool_helper.cpp
+++ b/libs/ui/tool/kis_selection_tool_helper.cpp
@@ -1,364 +1,370 @@
/*
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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_selection_tool_helper.h"
#include <kundo2command.h>
#include <kactioncollection.h>
#include <KoShapeController.h>
#include <KoPathShape.h>
#include "kis_pixel_selection.h"
#include "kis_shape_selection.h"
#include "kis_image.h"
#include "canvas/kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_selection_manager.h"
#include "kis_transaction.h"
#include "commands/kis_selection_commands.h"
#include "kis_shape_controller.h"
#include <kis_icon.h>
#include "kis_processing_applicator.h"
#include "commands_new/kis_transaction_based_command.h"
#include "kis_gui_context_command.h"
#include "kis_command_utils.h"
#include "commands/kis_deselect_global_selection_command.h"
#include "kis_algebra_2d.h"
#include "kis_config.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include <QMenu>
KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name)
: m_canvas(canvas)
, m_name(name)
{
m_image = m_canvas->viewManager()->image();
}
KisSelectionToolHelper::~KisSelectionToolHelper()
{
}
struct LazyInitGlobalSelection : public KisTransactionBasedCommand {
LazyInitGlobalSelection(KisView *view) : m_view(view) {}
KisView *m_view;
KUndo2Command* paint() override {
return !m_view->selection() ?
new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0;
}
};
void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action)
{
KisView* view = m_canvas->imageView();
if (selection->selectedExactRect().isEmpty()) {
m_canvas->viewManager()->selectionManager()->deselect();
return;
}
KisProcessingApplicator applicator(view->image(),
0 /* we need no automatic updates */,
KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE,
KisImageSignalVector() << ModifiedSignal,
m_name);
applicator.applyCommand(new LazyInitGlobalSelection(view));
struct ApplyToPixelSelection : public KisTransactionBasedCommand {
ApplyToPixelSelection(KisView *view,
KisPixelSelectionSP selection,
SelectionAction action) : m_view(view),
m_selection(selection),
m_action(action) {}
KisView *m_view;
KisPixelSelectionSP m_selection;
SelectionAction m_action;
KUndo2Command* paint() override {
- KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
- KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
+ KisSelectionSP selection = m_view->selection();
+ KIS_SAFE_ASSERT_RECOVER(selection) { return 0; }
+
+ KisPixelSelectionSP pixelSelection = selection->pixelSelection();
+ KIS_SAFE_ASSERT_RECOVER(pixelSelection) { return 0; }
bool hasSelection = !pixelSelection->isEmpty();
KisSelectionTransaction transaction(pixelSelection);
if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) {
m_action = SELECTION_REPLACE;
}
if (!hasSelection && m_action == SELECTION_SUBTRACT) {
pixelSelection->invert();
}
pixelSelection->applySelection(m_selection, m_action);
QRect dirtyRect = m_view->image()->bounds();
if (hasSelection &&
m_action != SELECTION_REPLACE &&
m_action != SELECTION_INTERSECT &&
m_action != SELECTION_SYMMETRICDIFFERENCE) {
dirtyRect = m_selection->selectedRect();
}
m_view->selection()->updateProjection(dirtyRect);
KUndo2Command *savedCommand = transaction.endAndTake();
pixelSelection->setDirty(dirtyRect);
if (m_view->selection()->selectedExactRect().isEmpty()) {
KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
cmd->addCommand(savedCommand);
cmd->addCommand(new KisDeselectGlobalSelectionCommand(m_view->image()));
savedCommand = cmd;
}
return savedCommand;
}
};
applicator.applyCommand(new ApplyToPixelSelection(view, selection, action));
applicator.end();
}
void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action)
{
QList<KoShape*> shapes;
shapes.append(shape);
addSelectionShapes(shapes, action);
}
void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action)
{
KisView *view = m_canvas->imageView();
if (view->image()->wrapAroundModePermitted()) {
view->showFloatingMessage(
i18n("Shape selection does not fully "
"support wraparound mode. Please "
"use pixel selection instead"),
KisIconUtils::loadIcon("selection-info"));
}
KisProcessingApplicator applicator(view->image(),
0 /* we need no automatic updates */,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
m_name);
applicator.applyCommand(new LazyInitGlobalSelection(view));
struct ClearPixelSelection : public KisTransactionBasedCommand {
ClearPixelSelection(KisView *view) : m_view(view) {}
KisView *m_view;
KUndo2Command* paint() override {
KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
KisSelectionTransaction transaction(pixelSelection);
pixelSelection->clear();
return transaction.endAndTake();
}
};
if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) {
applicator.applyCommand(new ClearPixelSelection(view));
}
struct AddSelectionShape : public KisTransactionBasedCommand {
AddSelectionShape(KisView *view, KoShape* shape, SelectionAction action)
: m_view(view),
m_shape(shape),
m_action(action) {}
KisView *m_view;
KoShape* m_shape;
SelectionAction m_action;
KUndo2Command* paint() override {
KUndo2Command *resultCommand = 0;
KisSelectionSP selection = m_view->selection();
if (selection) {
KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
if (shapeSelection) {
QList<KoShape*> existingShapes = shapeSelection->shapes();
if (existingShapes.size() == 1) {
KoShape *currentShape = existingShapes.first();
QPainterPath path1 = currentShape->absoluteTransformation(0).map(currentShape->outline());
QPainterPath path2 = m_shape->absoluteTransformation(0).map(m_shape->outline());
QPainterPath path = path2;
switch (m_action) {
case SELECTION_DEFAULT:
case SELECTION_REPLACE:
path = path2;
break;
case SELECTION_INTERSECT:
path = path1 & path2;
break;
case SELECTION_ADD:
path = path1 | path2;
break;
case SELECTION_SUBTRACT:
path = path1 - path2;
break;
case SELECTION_SYMMETRICDIFFERENCE:
path = (path1 | path2) - (path1 & path2);
break;
}
KoShape *newShape = KoPathShape::createShapeFromPainterPath(path);
newShape->setUserData(new KisShapeSelectionMarker);
KUndo2Command *parentCommand = new KUndo2Command();
m_view->canvasBase()->shapeController()->removeShape(currentShape, parentCommand);
m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand);
resultCommand = parentCommand;
}
}
}
if (!resultCommand) {
/**
* Mark a shape that it belongs to a shape selection
*/
if(!m_shape->userData()) {
m_shape->setUserData(new KisShapeSelectionMarker);
}
resultCommand = m_view->canvasBase()->shapeController()->addShape(m_shape, 0);
}
return resultCommand;
}
};
Q_FOREACH (KoShape* shape, shapes) {
applicator.applyCommand(
new KisGuiContextCommand(new AddSelectionShape(view, shape, action), view));
}
applicator.end();
}
bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action)
{
return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE);
}
bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action)
{
return rect.isEmpty() && action == SELECTION_ADD;
}
bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action)
{
bool result = false;
if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() &&
(action == SELECTION_INTERSECT || action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) {
// Queueing this action to ensure we avoid a race condition when unlocking the node system
QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect()));
result = true;
}
return result;
}
QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas)
{
QMenu *m_contextMenu = new QMenu();
KActionCollection *actionCollection = canvas->viewManager()->actionCollection();
+ m_contextMenu->addSection(i18n("Selection Actions"));
+ m_contextMenu->addSeparator();
+
m_contextMenu->addAction(actionCollection->action("deselect"));
m_contextMenu->addAction(actionCollection->action("invert"));
m_contextMenu->addAction(actionCollection->action("select_all"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(actionCollection->action("cut_selection_to_new_layer"));
m_contextMenu->addAction(actionCollection->action("copy_selection_to_new_layer"));
m_contextMenu->addSeparator();
KisSelectionSP selection = canvas->viewManager()->selection();
if (selection && canvas->viewManager()->selectionEditable()) {
m_contextMenu->addAction(actionCollection->action("edit_selection"));
if (!selection->hasShapeSelection()) {
m_contextMenu->addAction(actionCollection->action("convert_to_vector_selection"));
} else {
m_contextMenu->addAction(actionCollection->action("convert_to_raster_selection"));
}
QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform"));
transformMenu->addAction(actionCollection->action("KisToolTransform"));
transformMenu->addAction(actionCollection->action("selectionscale"));
transformMenu->addAction(actionCollection->action("growselection"));
transformMenu->addAction(actionCollection->action("shrinkselection"));
transformMenu->addAction(actionCollection->action("borderselection"));
transformMenu->addAction(actionCollection->action("smoothselection"));
transformMenu->addAction(actionCollection->action("featherselection"));
transformMenu->addAction(actionCollection->action("stroke_selection"));
m_contextMenu->addSeparator();
}
m_contextMenu->addAction(actionCollection->action("resizeimagetoselection"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(actionCollection->action("toggle_display_selection"));
m_contextMenu->addAction(actionCollection->action("show-global-selection-mask"));
return m_contextMenu;
}
SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const
{
if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) {
if (activeSelection) {
currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION;
}
}
return currentMode;
}
diff --git a/libs/ui/tool/kis_tool_select_base.h b/libs/ui/tool/kis_tool_select_base.h
index d3dfcc812c..b2f3a0d78b 100644
--- a/libs/ui/tool/kis_tool_select_base.h
+++ b/libs/ui/tool/kis_tool_select_base.h
@@ -1,407 +1,418 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* 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 KISTOOLSELECTBASE_H
#define KISTOOLSELECTBASE_H
#include "KoPointerEvent.h"
#include "kis_tool.h"
#include "kis_canvas2.h"
#include "kis_selection.h"
#include "kis_selection_options.h"
#include "kis_selection_tool_config_widget_helper.h"
#include "KisViewManager.h"
#include "kis_selection_manager.h"
#include "kis_selection_modifier_mapper.h"
#include "strokes/move_stroke_strategy.h"
#include "kis_image.h"
#include "kis_cursor.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include "kis_signal_auto_connection.h"
#include "kis_selection_tool_helper.h"
/**
* This is a basic template to create selection tools from basic path based drawing tools.
* The template overrides the ability to execute alternate actions correctly.
* The default behavior for the modifier keys is as follows:
*
* Shift: add to selection
* Alt: subtract from selection
* Shift+Alt: intersect current selection
* Ctrl: replace selection
*
* The mapping itself is done in KisSelectionModifierMapper.
*
* Certain tools also use modifier keys to alter their behavior, e.g. forcing square proportions with the rectangle tool.
* The template enables the following rules for forwarding keys:
* 1) Any modifier keys held *when the tool is first activated* will determine
* the new selection method. This is recorded in m_selectionActionAlternate. A
* value of m_selectionActionAlternate = SELECTION_DEFAULT means no modifier was
* being pressed when the tool was activated.
*
* 2) If the underlying tool *does not take modifier keys*, pressing modifier
* keys in the middle of a stroke will change the selection method. This is
* recorded in m_selectionAction. A value of SELECTION_DEFAULT means no modifier
* is being pressed. Applies to the lasso tool and polygon tool.
*
* 3) If the underlying tool *takes modifier keys,* they will always be
* forwarded to the underlying tool, and it is not possible to change the
* selection method in the middle of a stroke.
*/
template <class BaseClass>
class KisToolSelectBase : public BaseClass
{
public:
KisToolSelectBase(KoCanvasBase* canvas, const QString toolName)
: BaseClass(canvas)
, m_widgetHelper(toolName)
, m_selectionActionAlternate(SELECTION_DEFAULT)
{
KisSelectionModifierMapper::instance();
}
KisToolSelectBase(KoCanvasBase* canvas, const QCursor cursor, const QString toolName)
: BaseClass(canvas, cursor)
, m_widgetHelper(toolName)
, m_selectionActionAlternate(SELECTION_DEFAULT)
{
KisSelectionModifierMapper::instance();
}
KisToolSelectBase(KoCanvasBase* canvas, QCursor cursor, QString toolName, KoToolBase *delegateTool)
: BaseClass(canvas, cursor, delegateTool)
, m_widgetHelper(toolName)
, m_selectionActionAlternate(SELECTION_DEFAULT)
{
KisSelectionModifierMapper::instance();
}
void updateActionShortcutToolTips() {
KisSelectionOptions *widget = m_widgetHelper.optionWidget();
if (widget) {
widget->updateActionButtonToolTip(
SELECTION_REPLACE,
this->action("selection_tool_mode_replace")->shortcut());
widget->updateActionButtonToolTip(
SELECTION_ADD,
this->action("selection_tool_mode_add")->shortcut());
widget->updateActionButtonToolTip(
SELECTION_SUBTRACT,
this->action("selection_tool_mode_subtract")->shortcut());
widget->updateActionButtonToolTip(
SELECTION_INTERSECT,
this->action("selection_tool_mode_intersect")->shortcut());
}
}
void activate(KoToolBase::ToolActivation activation, const QSet<KoShape*> &shapes)
{
BaseClass::activate(activation, shapes);
m_modeConnections.addUniqueConnection(
this->action("selection_tool_mode_replace"), SIGNAL(triggered()),
&m_widgetHelper, SLOT(slotReplaceModeRequested()));
m_modeConnections.addUniqueConnection(
this->action("selection_tool_mode_add"), SIGNAL(triggered()),
&m_widgetHelper, SLOT(slotAddModeRequested()));
m_modeConnections.addUniqueConnection(
this->action("selection_tool_mode_subtract"), SIGNAL(triggered()),
&m_widgetHelper, SLOT(slotSubtractModeRequested()));
m_modeConnections.addUniqueConnection(
this->action("selection_tool_mode_intersect"), SIGNAL(triggered()),
&m_widgetHelper, SLOT(slotIntersectModeRequested()));
updateActionShortcutToolTips();
+
+ if (isPixelOnly() && m_widgetHelper.optionWidget()) {
+ m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode();
+ }
}
void deactivate()
{
BaseClass::deactivate();
m_modeConnections.clear();
}
QWidget* createOptionWidget()
{
KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas());
Q_ASSERT(canvas);
m_widgetHelper.createOptionWidget(canvas, this->toolId());
this->connect(this, SIGNAL(isActiveChanged(bool)), &m_widgetHelper, SLOT(slotToolActivatedChanged(bool)));
this->connect(&m_widgetHelper, SIGNAL(selectionActionChanged(int)), this, SLOT(resetCursorStyle()));
updateActionShortcutToolTips();
+ if (isPixelOnly() && m_widgetHelper.optionWidget()) {
+ m_widgetHelper.optionWidget()->enablePixelOnlySelectionMode();
+ }
return m_widgetHelper.optionWidget();
}
SelectionMode selectionMode() const
{
return m_widgetHelper.selectionMode();
}
SelectionAction selectionAction() const
{
if (alternateSelectionAction() == SELECTION_DEFAULT) {
return m_widgetHelper.selectionAction();
}
return alternateSelectionAction();
}
bool antiAliasSelection() const
{
return m_widgetHelper.antiAliasSelection();
}
SelectionAction alternateSelectionAction() const
{
return m_selectionActionAlternate;
}
KisSelectionOptions* selectionOptionWidget()
{
return m_widgetHelper.optionWidget();
}
virtual void setAlternateSelectionAction(SelectionAction action)
{
m_selectionActionAlternate = action;
dbgKrita << "Changing to selection action" << m_selectionActionAlternate;
}
void activateAlternateAction(KisTool::AlternateAction action)
{
Q_UNUSED(action);
BaseClass::activatePrimaryAction();
}
void deactivateAlternateAction(KisTool::AlternateAction action)
{
Q_UNUSED(action);
BaseClass::deactivatePrimaryAction();
}
void beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) {
Q_UNUSED(action);
beginPrimaryAction(event);
}
void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) {
Q_UNUSED(action);
continuePrimaryAction(event);
}
void endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) {
Q_UNUSED(action);
endPrimaryAction(event);
}
KisNodeSP locateSelectionMaskUnderCursor(const QPointF &pos, Qt::KeyboardModifiers modifiers) {
if (modifiers != Qt::NoModifier) return 0;
KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas, 0);
KisSelectionSP selection = canvas->viewManager()->selection();
if (selection &&
selection->outlineCacheValid()) {
const qreal handleRadius = qreal(this->handleRadius()) / canvas->coordinatesConverter()->effectiveZoom();
QPainterPath samplePath;
samplePath.addEllipse(pos, handleRadius, handleRadius);
const QPainterPath selectionPath = selection->outlineCache();
if (selectionPath.intersects(samplePath) && !selectionPath.contains(samplePath)) {
KisNodeSP parent = selection->parentNode();
if (parent && parent->isEditable()) {
return parent;
}
}
}
return 0;
}
void keyPressEvent(QKeyEvent *event) {
if (this->mode() != KisTool::PAINT_MODE) {
setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers()));
this->resetCursorStyle();
}
BaseClass::keyPressEvent(event);
}
void keyReleaseEvent(QKeyEvent *event) {
if (this->mode() != KisTool::PAINT_MODE) {
setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers()));
this->resetCursorStyle();
}
BaseClass::keyPressEvent(event);
}
void mouseMoveEvent(KoPointerEvent *event) {
if (!this->hasUserInteractionRunning() &&
(m_moveStrokeId || this->mode() != KisTool::PAINT_MODE)) {
const QPointF pos = this->convertToPixelCoord(event->point);
KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers());
if (selectionMask) {
this->useCursor(KisCursor::moveSelectionCursor());
} else {
setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers()));
this->resetCursorStyle();
}
}
BaseClass::mouseMoveEvent(event);
}
virtual void beginPrimaryAction(KoPointerEvent *event)
{
if (!this->hasUserInteractionRunning()) {
const QPointF pos = this->convertToPixelCoord(event->point);
KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(this->canvas());
KIS_SAFE_ASSERT_RECOVER_RETURN(canvas);
KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers());
if (selectionMask) {
KisStrokeStrategy *strategy = new MoveStrokeStrategy({selectionMask}, this->image().data(), this->image().data());
m_moveStrokeId = this->image()->startStroke(strategy);
m_dragStartPos = pos;
m_didMove = true;
return;
}
}
m_didMove = false;
keysAtStart = event->modifiers();
setAlternateSelectionAction(KisSelectionModifierMapper::map(keysAtStart));
if (alternateSelectionAction() != SELECTION_DEFAULT) {
BaseClass::listenToModifiers(false);
}
BaseClass::beginPrimaryAction(event);
}
virtual void continuePrimaryAction(KoPointerEvent *event)
{
if (m_moveStrokeId) {
const QPointF pos = this->convertToPixelCoord(event->point);
const QPoint offset((pos - m_dragStartPos).toPoint());
this->image()->addJob(m_moveStrokeId, new MoveStrokeStrategy::Data(offset));
return;
}
//If modifier keys have changed, tell the base tool it can start capturing modifiers
if ((keysAtStart != event->modifiers()) && !BaseClass::listeningToModifiers()) {
BaseClass::listenToModifiers(true);
}
//Always defer to the base class if it signals it is capturing modifier keys
if (!BaseClass::listeningToModifiers()) {
setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers()));
}
BaseClass::continuePrimaryAction(event);
}
void endPrimaryAction(KoPointerEvent *event)
{
if (m_moveStrokeId) {
this->image()->endStroke(m_moveStrokeId);
m_moveStrokeId.clear();
return;
}
keysAtStart = Qt::NoModifier; //reset this with each action
BaseClass::endPrimaryAction(event);
}
bool selectionDragInProgress() const {
return m_moveStrokeId;
}
bool selectionDidMove() const {
return m_didMove;
}
QMenu* popupActionsMenu() {
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, 0);
return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas);
}
protected:
using BaseClass::canvas;
KisSelectionToolConfigWidgetHelper m_widgetHelper;
SelectionAction m_selectionActionAlternate;
+ virtual bool isPixelOnly() const {
+ return false;
+ }
+
private:
Qt::KeyboardModifiers keysAtStart;
QPointF m_dragStartPos;
KisStrokeId m_moveStrokeId;
bool m_didMove = false;
KisSignalAutoConnectionsStore m_modeConnections;
};
struct FakeBaseTool : KisTool
{
FakeBaseTool(KoCanvasBase* canvas)
: KisTool(canvas, QCursor())
{
}
FakeBaseTool(KoCanvasBase* canvas, const QString &toolName)
: KisTool(canvas, QCursor())
{
Q_UNUSED(toolName);
}
FakeBaseTool(KoCanvasBase* canvas, const QCursor &cursor)
: KisTool(canvas, cursor)
{
}
bool hasUserInteractionRunning() const {
return false;
}
};
typedef KisToolSelectBase<FakeBaseTool> KisToolSelect;
#endif // KISTOOLSELECTBASE_H
diff --git a/libs/ui/tool/kis_tool_utils.cpp b/libs/ui/tool/kis_tool_utils.cpp
index 3c9c2d00a5..f1b06f191a 100644
--- a/libs/ui/tool/kis_tool_utils.cpp
+++ b/libs/ui/tool/kis_tool_utils.cpp
@@ -1,210 +1,214 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kis_tool_utils.h>
#include <KoMixColorsOp.h>
#include <kis_group_layer.h>
#include <kis_transaction.h>
#include <kis_sequential_iterator.h>
#include <kis_properties_configuration.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
+#include "kis_command_utils.h"
+#include "kis_processing_applicator.h"
namespace KisToolUtils {
bool pickColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos,
KoColor const *const blendColor, int radius, int blend, bool pure)
{
KIS_ASSERT(dev);
// Bugfix hack forcing pure on first sample to avoid wrong
// format blendColor on newly initialized Krita.
static bool firstTime = true;
if (firstTime == true) {
pure = true;
firstTime = false;
}
const KoColorSpace *cs = dev->colorSpace();
KoColor pickedColor(Qt::transparent, cs);
// Sampling radius.
if (!pure && radius > 1) {
QVector<const quint8*> pixels;
const int effectiveRadius = radius - 1;
const QRect pickRect(pos.x() - effectiveRadius, pos.y() - effectiveRadius,
2 * effectiveRadius + 1, 2 * effectiveRadius + 1);
KisSequentialConstIterator it(dev, pickRect);
const int radiusSq = pow2(effectiveRadius);
while (it.nextPixel()) {
const QPoint realPos(it.x(), it.y());
const QPoint pt = realPos - pos;
if (pow2(pt.x()) + pow2(pt.y()) < radiusSq) {
pixels << it.oldRawData();
}
}
const quint8 **cpixels = const_cast<const quint8**>(pixels.constData());
cs->mixColorsOp()->mixColors(cpixels, pixels.size(), pickedColor.data());
} else {
dev->pixel(pos.x(), pos.y(), &pickedColor);
}
// Color blending.
if (!pure && blendColor && blend < 100) {
//Scale from 0..100% to 0..255 range for mixOp weights.
quint8 blendScaled = static_cast<quint8>(blend * 2.55f);
const quint8 *colors[2];
colors[0] = blendColor->data();
colors[1] = pickedColor.data();
qint16 weights[2];
weights[0] = 255 - blendScaled;
weights[1] = blendScaled;
const KoMixColorsOp *mixOp = dev->colorSpace()->mixColorsOp();
mixOp->mixColors(colors, weights, 2, pickedColor.data());
}
pickedColor.convertTo(dev->compositionSourceColorSpace());
bool validColorPicked = pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8;
if (validColorPicked) {
out_color = pickedColor;
}
return validColorPicked;
}
KisNodeSP findNode(KisNodeSP node, const QPoint &point, bool wholeGroup, bool editableOnly)
{
KisNodeSP foundNode = 0;
while (node) {
KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
if (!layer || !layer->isEditable()) {
node = node->prevSibling();
continue;
}
KoColor color(layer->projection()->colorSpace());
layer->projection()->pixel(point.x(), point.y(), &color);
KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(layer.data());
if ((group && group->passThroughMode()) || color.opacityU8() != OPACITY_TRANSPARENT_U8) {
if (layer->inherits("KisGroupLayer") && (!editableOnly || layer->isEditable())) {
// if this is a group and the pixel is transparent, don't even enter it
foundNode = findNode(node->lastChild(), point, wholeGroup, editableOnly);
}
else {
foundNode = !wholeGroup ? node : node->parent();
}
}
if (foundNode) break;
node = node->prevSibling();
}
return foundNode;
}
bool clearImage(KisImageSP image, KisNodeSP node, KisSelectionSP selection)
{
if(node && node->hasEditablePaintDevice()) {
- KisPaintDeviceSP device = node->paintDevice();
-
- image->barrierLock();
- KisTransaction transaction(kundo2_i18n("Clear"), device);
-
- QRect dirtyRect;
- if (selection) {
- dirtyRect = selection->selectedRect();
- device->clearSelection(selection);
- }
- else {
- dirtyRect = device->extent();
- device->clear();
- }
-
- transaction.commit(image->undoAdapter());
- device->setDirty(dirtyRect);
- image->unlock();
+ KUndo2Command *cmd =
+ new KisCommandUtils::LambdaCommand(kundo2_i18n("Clear"),
+ [node, selection] () {
+ KisPaintDeviceSP device = node->paintDevice();
+
+ KisTransaction transaction(kundo2_noi18n("internal-clear-command"), device);
+
+ QRect dirtyRect;
+ if (selection) {
+ dirtyRect = selection->selectedRect();
+ device->clearSelection(selection);
+ } else {
+ dirtyRect = device->extent();
+ device->clear();
+ }
+
+ device->setDirty(dirtyRect);
+ return transaction.endAndTake();
+ });
+ KisProcessingApplicator::runSingleCommandStroke(image, cmd);
return true;
}
return false;
}
const QString ColorPickerConfig::CONFIG_GROUP_NAME = "tool_color_picker";
ColorPickerConfig::ColorPickerConfig()
: toForegroundColor(true)
, updateColor(true)
, addPalette(false)
, normaliseValues(false)
, sampleMerged(true)
, radius(1)
, blend(100)
{
}
inline QString getConfigKey(bool defaultActivation) {
return defaultActivation ?
"ColorPickerDefaultActivation" : "ColorPickerTemporaryActivation";
}
void ColorPickerConfig::save(bool defaultActivation) const
{
KisPropertiesConfiguration props;
props.setProperty("toForegroundColor", toForegroundColor);
props.setProperty("updateColor", updateColor);
props.setProperty("addPalette", addPalette);
props.setProperty("normaliseValues", normaliseValues);
props.setProperty("sampleMerged", sampleMerged);
props.setProperty("radius", radius);
props.setProperty("blend", blend);
KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
config.writeEntry(getConfigKey(defaultActivation), props.toXML());
}
void ColorPickerConfig::load(bool defaultActivation)
{
KisPropertiesConfiguration props;
KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
props.fromXML(config.readEntry(getConfigKey(defaultActivation)));
toForegroundColor = props.getBool("toForegroundColor", true);
updateColor = props.getBool("updateColor", true);
addPalette = props.getBool("addPalette", false);
normaliseValues = props.getBool("normaliseValues", false);
sampleMerged = props.getBool("sampleMerged", !defaultActivation ? false : true);
radius = props.getInt("radius", 1);
blend = props.getInt("blend", 100);
}
}
diff --git a/libs/ui/utils/KisClipboardUtil.cpp b/libs/ui/utils/KisClipboardUtil.cpp
new file mode 100644
index 0000000000..49bc209a08
--- /dev/null
+++ b/libs/ui/utils/KisClipboardUtil.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019 Dmitrii Utkin <loentar@gmail.com>
+ *
+ * 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 "KisClipboardUtil.h"
+
+#include <QApplication>
+#include <QClipboard>
+#include <QMimeData>
+#include <QImage>
+#include <QList>
+#include <QSet>
+#include <QPair>
+
+namespace KisClipboardUtil {
+
+struct ClipboardImageFormat
+{
+ QSet<QString> mimeTypes;
+ QString format;
+};
+
+QImage getImageFromClipboard()
+{
+ static const QList<ClipboardImageFormat> supportedFormats = {
+ {{"image/png"}, "PNG"},
+ {{"image/tiff"}, "TIFF"},
+ {{"image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"}, "BMP"},
+ {{"image/jpeg"}, "JPG"}
+ };
+
+ QClipboard *clipboard = QApplication::clipboard();
+
+ QImage image;
+
+ const QSet<QString> &clipboardMimeTypes = clipboard->mimeData()->formats().toSet();
+
+ Q_FOREACH (const ClipboardImageFormat &item, supportedFormats) {
+ const QSet<QString> &intersection = item.mimeTypes & clipboardMimeTypes;
+ if (intersection.isEmpty()) {
+ continue;
+ }
+
+ const QString &format = *intersection.constBegin();
+ const QByteArray &imageData = clipboard->mimeData()->data(format);
+ if (imageData.isEmpty()) {
+ continue;
+ }
+
+ if (image.loadFromData(imageData, item.format.toLatin1())) {
+ break;
+ }
+ }
+
+ if (image.isNull()) {
+ image = clipboard->image();
+ }
+
+ return image;
+}
+
+}
\ No newline at end of file
diff --git a/libs/vectorimage/VectorImageDebug.cpp b/libs/ui/utils/KisClipboardUtil.h
similarity index 66%
rename from libs/vectorimage/VectorImageDebug.cpp
rename to libs/ui/utils/KisClipboardUtil.h
index bc780b12e7..653b763e28 100644
--- a/libs/vectorimage/VectorImageDebug.cpp
+++ b/libs/ui/utils/KisClipboardUtil.h
@@ -1,26 +1,37 @@
/*
- * Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2019 Dmitrii Utkin <loentar@gmail.com>
*
* 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 "VectorImageDebug.h"
-const QLoggingCategory &VECTOR_IMAGE_LOG() \
-{
- static const QLoggingCategory category("krita.lib.vectorimage", QtInfoMsg);
- return category;
+#ifndef KIS_CLIPBOARD_UTIL_H
+#define KIS_CLIPBOARD_UTIL_H
+
+#include <kritaui_export.h>
+
+class QImage;
+
+namespace KisClipboardUtil {
+
+ /**
+ * load an image from clipboard handling different supported formats
+ * @return image
+ */
+ KRITAUI_EXPORT QImage getImageFromClipboard();
+
}
+#endif //KIS_CLIPBOARD_UTIL_H
diff --git a/libs/ui/utils/KisDitherUtil.cpp b/libs/ui/utils/KisDitherUtil.cpp
new file mode 100644
index 0000000000..0560aa7595
--- /dev/null
+++ b/libs/ui/utils/KisDitherUtil.cpp
@@ -0,0 +1,97 @@
+/*
+ * This file is part of the KDE project
+ *
+ * Copyright (c) 2019 Carl Olsson <carl.olsson@gmail.com>
+ *
+ * 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 "KisDitherUtil.h"
+
+#include <KoPattern.h>
+#include <kis_properties_configuration.h>
+#include <KoResourceServerProvider.h>
+#include <kis_random_generator.h>
+
+KisDitherUtil::KisDitherUtil()
+ : m_thresholdMode(ThresholdMode::Pattern), m_patternValueMode(PatternValueMode::Auto),
+ m_pattern(0), m_noiseSeed(0), m_patternUseAlpha(false), m_spread(1.0)
+{
+}
+
+void KisDitherUtil::setThresholdMode(const ThresholdMode thresholdMode)
+{
+ m_thresholdMode = thresholdMode;
+}
+
+void KisDitherUtil::setPattern(const QString &name, const PatternValueMode valueMode)
+{
+ m_patternValueMode = valueMode;
+ m_pattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(name);
+ if (m_pattern && m_thresholdMode == ThresholdMode::Pattern && m_patternValueMode == PatternValueMode::Auto) {
+ // Automatically pick between lightness-based and alpha-based patterns by whichever has maximum range
+ qreal lightnessMin = 1.0, lightnessMax = 0.0;
+ qreal alphaMin = 1.0, alphaMax = 0.0;
+ const QImage &image = m_pattern->pattern();
+ for (int y = 0; y < image.height(); ++y) {
+ for (int x = 0; x < image.width(); ++x) {
+ const QColor pixel = image.pixelColor(x, y);
+ lightnessMin = std::min(lightnessMin, pixel.lightnessF());
+ lightnessMax = std::max(lightnessMax, pixel.lightnessF());
+ alphaMin = std::min(alphaMin, pixel.alphaF());
+ alphaMax = std::max(alphaMax, pixel.alphaF());
+ }
+ }
+ m_patternUseAlpha = (alphaMax - alphaMin > lightnessMax - lightnessMin);
+ }
+ else {
+ m_patternUseAlpha = (m_patternValueMode == PatternValueMode::Alpha);
+ }
+}
+
+void KisDitherUtil::setNoiseSeed(const quint64 &noiseSeed)
+{
+ m_noiseSeed = noiseSeed;
+}
+
+void KisDitherUtil::setSpread(const qreal &spread)
+{
+ m_spread = spread;
+}
+
+qreal KisDitherUtil::threshold(const QPoint &pos)
+{
+ qreal threshold;
+ if (m_thresholdMode == ThresholdMode::Pattern && m_pattern) {
+ const QImage &image = m_pattern->pattern();
+ const QColor color = image.pixelColor(pos.x() % image.width(), pos.y() % image.height());
+ threshold = (m_patternUseAlpha ? color.alphaF() : color.lightnessF());
+ }
+ else if (m_thresholdMode == ThresholdMode::Noise) {
+ KisRandomGenerator random(m_noiseSeed);
+ threshold = random.doubleRandomAt(pos.x(), pos.y());
+ }
+ else threshold = 0.5;
+
+ return 0.5 - (m_spread / 2.0) + threshold * m_spread;
+}
+
+void KisDitherUtil::setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix)
+{
+ setThresholdMode(ThresholdMode(config.getInt(prefix + "thresholdMode")));
+ setPattern(config.getString(prefix + "pattern"), PatternValueMode(config.getInt(prefix + "patternValueMode")));
+ setNoiseSeed(quint64(config.getInt(prefix + "noiseSeed")));
+ setSpread(config.getDouble(prefix + "spread"));
+}
diff --git a/libs/ui/utils/KisDitherUtil.h b/libs/ui/utils/KisDitherUtil.h
new file mode 100644
index 0000000000..fa8e1e3a27
--- /dev/null
+++ b/libs/ui/utils/KisDitherUtil.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the KDE project
+ *
+ * Copyright (c) 2019 Carl Olsson <carl.olsson@gmail.com>
+ *
+ * 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_DITHER_UTIL_H
+#define KIS_DITHER_UTIL_H
+
+#include <kritaui_export.h>
+
+#include <kis_types.h>
+
+class KoPattern;
+class KisPropertiesConfiguration;
+
+class KRITAUI_EXPORT KisDitherUtil
+{
+public:
+ enum ThresholdMode {
+ Pattern,
+ Noise
+ };
+ enum PatternValueMode {
+ Auto,
+ Lightness,
+ Alpha
+ };
+
+ KisDitherUtil();
+
+ void setThresholdMode(const ThresholdMode thresholdMode);
+ void setPattern(const QString &name, const PatternValueMode valueMode);
+ void setNoiseSeed(const quint64 &noiseSeed);
+ void setSpread(const qreal &spread);
+
+ qreal threshold(const QPoint &pos);
+
+ void setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix = "");
+
+private:
+ ThresholdMode m_thresholdMode;
+ PatternValueMode m_patternValueMode;
+ KoPattern* m_pattern;
+ quint64 m_noiseSeed;
+ bool m_patternUseAlpha;
+ qreal m_spread;
+};
+
+#endif
diff --git a/libs/ui/widgets/KisDitherWidget.cpp b/libs/ui/widgets/KisDitherWidget.cpp
new file mode 100644
index 0000000000..cfcebe5b48
--- /dev/null
+++ b/libs/ui/widgets/KisDitherWidget.cpp
@@ -0,0 +1,90 @@
+/*
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Carl Olsson <carl.olsson@gmail.com>
+ *
+ * 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 "KisDitherWidget.h"
+
+#include <kpluginfactory.h>
+#include <KoUpdater.h>
+#include <KoResourceServerProvider.h>
+#include <KoResourceServer.h>
+#include <KoResourceServerAdapter.h>
+#include <KoResourceItemChooser.h>
+#include <KoColorSet.h>
+#include <KoPattern.h>
+#include <kis_properties_configuration.h>
+#include "KisDitherUtil.h"
+
+KisDitherWidget::KisDitherWidget(QWidget* parent)
+ : QWidget(parent), Ui::KisDitherWidget()
+{
+ setupUi(this);
+
+ QObject::connect(thresholdModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisDitherWidget::sigConfigurationItemChanged);
+
+ patternIconWidget->setFixedSize(32, 32);
+ KoResourceServer<KoPattern>* patternServer = KoResourceServerProvider::instance()->patternServer();
+ QSharedPointer<KoAbstractResourceServerAdapter> patternAdapter(new KoResourceServerAdapter<KoPattern>(patternServer));
+ m_ditherPatternWidget = new KoResourceItemChooser(patternAdapter, this, false);
+ patternIconWidget->setPopupWidget(m_ditherPatternWidget);
+ QObject::connect(m_ditherPatternWidget, &KoResourceItemChooser::resourceSelected, patternIconWidget, &KisIconWidget::setResource);
+ QObject::connect(m_ditherPatternWidget, &KoResourceItemChooser::resourceSelected, this, &KisDitherWidget::sigConfigurationItemChanged);
+
+ QObject::connect(patternValueModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisDitherWidget::sigConfigurationItemChanged);
+
+ noiseSeedLineEdit->setValidator(new QIntValidator(this));
+ QObject::connect(noiseSeedLineEdit, &QLineEdit::textChanged, this, &KisDitherWidget::sigConfigurationItemChanged);
+
+ QObject::connect(noiseSeedRandomizeButton, &QToolButton::clicked, [this](){
+ noiseSeedLineEdit->setText(QString::number(rand()));
+ });
+
+ spreadSpinBox->setPrefix(QString("%1 ").arg(i18n("Spread:")));
+ spreadSpinBox->setRange(0.0, 1.0, 3);
+ spreadSpinBox->setSingleStep(0.125);
+ QObject::connect(spreadSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisDitherWidget::sigConfigurationItemChanged);
+}
+
+void KisDitherWidget::setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix)
+{
+ thresholdModeComboBox->setCurrentIndex(config.getInt(prefix + "thresholdMode"));
+ KoPattern* pattern = KoResourceServerProvider::instance()->patternServer()->resourceByName(config.getString(prefix + "pattern"));
+ if (pattern) m_ditherPatternWidget->setCurrentResource(pattern);
+ patternValueModeComboBox->setCurrentIndex(config.getInt(prefix + "patternValueMode"));
+ noiseSeedLineEdit->setText(QString::number(config.getInt(prefix + "noiseSeed")));
+ spreadSpinBox->setValue(config.getDouble(prefix + "spread"));
+}
+
+void KisDitherWidget::configuration(KisPropertiesConfiguration &config, const QString &prefix) const
+{
+ config.setProperty(prefix + "thresholdMode",thresholdModeComboBox->currentIndex());
+ if (m_ditherPatternWidget->currentResource()) config.setProperty(prefix + "pattern", QVariant(m_ditherPatternWidget->currentResource()->name()));
+ config.setProperty(prefix + "patternValueMode", patternValueModeComboBox->currentIndex());
+ config.setProperty(prefix + "noiseSeed", noiseSeedLineEdit->text().toInt());
+ config.setProperty(prefix + "spread", spreadSpinBox->value());
+}
+
+void KisDitherWidget::factoryConfiguration(KisPropertiesConfiguration &config, const QString &prefix)
+{
+ config.setProperty(prefix + "thresholdMode", KisDitherUtil::ThresholdMode::Pattern);
+ config.setProperty(prefix + "pattern", "DITH 0202 GEN ");
+ config.setProperty(prefix + "patternValueMode", KisDitherUtil::PatternValueMode::Auto);
+ config.setProperty(prefix + "noiseSeed", rand());
+ config.setProperty(prefix + "spread", 1.0);
+}
diff --git a/libs/ui/widgets/KisDitherWidget.h b/libs/ui/widgets/KisDitherWidget.h
new file mode 100644
index 0000000000..e0cb29a1b9
--- /dev/null
+++ b/libs/ui/widgets/KisDitherWidget.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the KDE project
+ *
+ * Copyright (c) 2019 Carl Olsson <carl.olsson@gmail.com>
+ *
+ * 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_DITHER_WIDGET_H
+#define KIS_DITHER_WIDGET_H
+
+#include <kritaui_export.h>
+#include <QWidget>
+#include "ui_KisDitherWidget.h"
+
+class KoResourceItemChooser;
+class KisPropertiesConfiguration;
+
+class KRITAUI_EXPORT KisDitherWidget : public QWidget, public Ui::KisDitherWidget
+{
+ Q_OBJECT
+public:
+ KisDitherWidget(QWidget* parent = 0);
+ void setConfiguration(const KisPropertiesConfiguration &config, const QString &prefix = "");
+ void configuration(KisPropertiesConfiguration &config, const QString &prefix = "") const;
+ static void factoryConfiguration(KisPropertiesConfiguration &config, const QString &prefix = "");
+Q_SIGNALS:
+ void sigConfigurationItemChanged();
+private:
+ KoResourceItemChooser* m_ditherPatternWidget;
+};
+
+#endif
diff --git a/libs/ui/widgets/KisDitherWidget.ui b/libs/ui/widgets/KisDitherWidget.ui
new file mode 100644
index 0000000000..54cd5d2ca8
--- /dev/null
+++ b/libs/ui/widgets/KisDitherWidget.ui
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>KisDitherWidget</class>
+ <widget class="QWidget" name="KisDitherWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>185</width>
+ <height>154</height>
+ </rect>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="1" column="0">
+ <widget class="QLabel" name="thresholdModeLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Threshold Mode</string>
+ </property>
+ <property name="buddy">
+ <cstring>thresholdModeComboBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="thresholdModeComboBox">
+ <item>
+ <property name="text">
+ <string>Pattern</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Noise</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="KisDoubleSliderSpinBox" name="spreadSpinBox"/>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QStackedWidget" name="thresholdModeStackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <property name="prefix" stdset="0">
+ <string>Amount: </string>
+ </property>
+ <widget class="QWidget" name="patternWidget">
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="patternLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Pattern</string>
+ </property>
+ <property name="buddy">
+ <cstring>patternIconWidget</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="KisIconWidget" name="patternIconWidget">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="patternValueModeLabel">
+ <property name="text">
+ <string>Value Mode</string>
+ </property>
+ <property name="buddy">
+ <cstring>patternValueModeComboBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="patternValueModeComboBox">
+ <item>
+ <property name="text">
+ <string>Auto</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Lightness</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Alpha</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="noiseWidget">
+ <layout class="QFormLayout" name="formLayout_5">
+ <item row="0" column="0">
+ <widget class="QLabel" name="noiseSeedLabel">
+ <property name="text">
+ <string>Seed</string>
+ </property>
+ <property name="buddy">
+ <cstring>noiseSeedLineEdit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="noiseSeedLineEdit"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QToolButton" name="noiseSeedRandomizeButton">
+ <property name="text">
+ <string>Randomize</string>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextOnly</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KisIconWidget</class>
+ <extends>QToolButton</extends>
+ <header location="global">kis_iconwidget.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KisDoubleSliderSpinBox</class>
+ <extends>QDoubleSpinBox</extends>
+ <header location="global">kis_slider_spin_box.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>thresholdModeComboBox</tabstop>
+ <tabstop>patternIconWidget</tabstop>
+ <tabstop>patternValueModeComboBox</tabstop>
+ <tabstop>noiseSeedLineEdit</tabstop>
+ <tabstop>noiseSeedRandomizeButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>thresholdModeComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>thresholdModeStackedWidget</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>180</x>
+ <y>11</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>185</x>
+ <y>36</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>thresholdModeStackedWidget</sender>
+ <signal>currentChanged(int)</signal>
+ <receiver>thresholdModeComboBox</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>242</x>
+ <y>96</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>239</x>
+ <y>17</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/libs/ui/widgets/KisGamutMaskToolbar.cpp b/libs/ui/widgets/KisGamutMaskToolbar.cpp
index ef8ca6cc06..6c007584cc 100644
--- a/libs/ui/widgets/KisGamutMaskToolbar.cpp
+++ b/libs/ui/widgets/KisGamutMaskToolbar.cpp
@@ -1,115 +1,116 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 <QWidget>
#include "KisGamutMaskToolbar.h"
#include <kis_icon_utils.h>
#include <kis_canvas_resource_provider.h>
KisGamutMaskToolbar::KisGamutMaskToolbar(QWidget* parent) : QWidget(parent)
, m_selectedMask(nullptr)
{
m_ui = new Ui_wdgGamutMaskToolbar();
m_ui->setupUi(this);
m_iconMaskOff = KisIconUtils::loadIcon("gamut-mask-off");
m_iconMaskOn = KisIconUtils::loadIcon("gamut-mask-on");
m_textNoMask = i18n("Select a mask in \"Gamut Masks\" docker");
m_textMaskDisabled = i18n("Mask is disabled");
m_ui->bnToggleMask->setChecked(false);
m_ui->bnToggleMask->setIcon(m_iconMaskOff);
m_ui->rotationSlider->setRange(0, 360);
m_ui->rotationSlider->setPrefix(i18n("Rotation: "));
m_ui->rotationSlider->setSuffix("°");
m_ui->rotationSlider->setFastSliderStep(30); // TODO: test for usability
m_ui->rotationSlider->hide();
// gamut mask connections
connect(m_ui->bnToggleMask, SIGNAL(toggled(bool)), SLOT(slotGamutMaskToggle(bool)));
connect(m_ui->rotationSlider, SIGNAL(valueChanged(int)), SLOT(slotGamutMaskRotate(int)));
}
void KisGamutMaskToolbar::connectMaskSignals(KisCanvasResourceProvider* resourceProvider)
{
connect(resourceProvider, SIGNAL(sigGamutMaskChanged(KoGamutMask*)),
this, SLOT(slotGamutMaskSet(KoGamutMask*)), Qt::UniqueConnection);
connect(resourceProvider, SIGNAL(sigGamutMaskUnset()),
this, SLOT(slotGamutMaskUnset()), Qt::UniqueConnection);
connect(this, SIGNAL(sigGamutMaskChanged(KoGamutMask*)),
resourceProvider, SLOT(slotGamutMaskActivated(KoGamutMask*)), Qt::UniqueConnection);
}
void KisGamutMaskToolbar::slotGamutMaskSet(KoGamutMask *mask)
{
if (!mask) {
return;
}
m_selectedMask = mask;
if (m_selectedMask) {
slotGamutMaskToggle(true);
} else {
slotGamutMaskToggle(false);
}
}
void KisGamutMaskToolbar::slotGamutMaskUnset()
{
m_ui->rotationSlider->hide();
m_ui->labelMaskName->show();
m_ui->labelMaskName->setText(m_textNoMask);
}
void KisGamutMaskToolbar::slotGamutMaskToggle(bool state)
{
bool b = (!m_selectedMask) ? false : state;
m_ui->bnToggleMask->setChecked(b);
if (b == true) {
m_ui->bnToggleMask->setIcon(m_iconMaskOn);
m_ui->labelMaskName->hide();
m_ui->rotationSlider->show();
m_ui->rotationSlider->blockSignals(true);
m_ui->rotationSlider->setValue(m_selectedMask->rotation());
m_ui->rotationSlider->blockSignals(false);
} else {
m_ui->bnToggleMask->setIcon(m_iconMaskOff);
m_ui->rotationSlider->hide();
m_ui->labelMaskName->show();
m_ui->labelMaskName->setText(m_textMaskDisabled);
}
emit sigGamutMaskToggle(state);
}
void KisGamutMaskToolbar::slotGamutMaskRotate(int angle)
{
if (!m_selectedMask) {
return;
}
m_selectedMask->setRotation(angle);
emit sigGamutMaskChanged(m_selectedMask);
}
diff --git a/libs/ui/widgets/KisGamutMaskToolbar.h b/libs/ui/widgets/KisGamutMaskToolbar.h
index 45386705b2..c61792aa33 100644
--- a/libs/ui/widgets/KisGamutMaskToolbar.h
+++ b/libs/ui/widgets/KisGamutMaskToolbar.h
@@ -1,61 +1,62 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 KISGAMUTMASKTOOLBAR_H
#define KISGAMUTMASKTOOLBAR_H
#include <QWidget>
#include <QIcon>
#include <resources/KoGamutMask.h>
#include "kritaui_export.h"
#include "ui_wdgGamutMaskToolbar.h"
class KisCanvasResourceProvider;
class KRITAUI_EXPORT KisGamutMaskToolbar : public QWidget
{
Q_OBJECT
public:
KisGamutMaskToolbar(QWidget* parent = nullptr);
void connectMaskSignals(KisCanvasResourceProvider* resourceProvider);
Q_SIGNALS:
void sigGamutMaskToggle(bool state);
void sigGamutMaskChanged(KoGamutMask*);
public Q_SLOTS:
void slotGamutMaskSet(KoGamutMask* mask);
void slotGamutMaskUnset();
private Q_SLOTS:
void slotGamutMaskToggle(bool state);
void slotGamutMaskRotate(int angle);
private:
Ui_wdgGamutMaskToolbar* m_ui;
KoGamutMask* m_selectedMask;
QIcon m_iconMaskOff;
QIcon m_iconMaskOn;
QString m_textNoMask;
QString m_textMaskDisabled;
};
#endif // KISGAMUTMASKTOOLBAR_H
diff --git a/libs/ui/widgets/KisLayerStyleAngleSelector.cpp b/libs/ui/widgets/KisLayerStyleAngleSelector.cpp
index e576080b0a..87680a0203 100644
--- a/libs/ui/widgets/KisLayerStyleAngleSelector.cpp
+++ b/libs/ui/widgets/KisLayerStyleAngleSelector.cpp
@@ -1,117 +1,118 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "KisLayerStyleAngleSelector.h"
#include <QWidget>
#include <QDial>
#include <kis_signals_blocker.h>
KisLayerStyleAngleSelector::KisLayerStyleAngleSelector(QWidget *parent)
: QWidget(parent)
, m_enableGlobalLight(false)
{
ui = new Ui_WdgKisLayerStyleAngleSelector();
ui->setupUi(this);
ui->chkUseGlobalLight->hide();
connect(ui->dialAngle, SIGNAL(valueChanged(int)), SLOT(slotDialAngleChanged(int)));
connect(ui->intAngle, SIGNAL(valueChanged(int)), SLOT(slotIntAngleChanged(int)));
}
int KisLayerStyleAngleSelector::value()
{
return ui->intAngle->value();
}
void KisLayerStyleAngleSelector::setValue(int value)
{
KisSignalsBlocker intB(ui->intAngle);
KisSignalsBlocker dialB(ui->dialAngle);
ui->intAngle->setValue(value);
ui->dialAngle->setValue(value + m_dialValueShift);
}
void KisLayerStyleAngleSelector::enableGlobalLight(bool enable)
{
m_enableGlobalLight = enable;
if (enable) {
ui->chkUseGlobalLight->show();
connect(ui->chkUseGlobalLight, SIGNAL(toggled(bool)), SLOT(slotGlobalLightToggled()));
} else {
ui->chkUseGlobalLight->hide();
disconnect(ui->chkUseGlobalLight, SIGNAL(toggled(bool)), this, SLOT(slotGlobalLightToggled()));
}
}
bool KisLayerStyleAngleSelector::useGlobalLight()
{
return m_enableGlobalLight && ui->chkUseGlobalLight->isChecked();
}
void KisLayerStyleAngleSelector::setUseGlobalLight(bool state)
{
ui->chkUseGlobalLight->setChecked(state);
}
void KisLayerStyleAngleSelector::slotDialAngleChanged(int value)
{
KisSignalsBlocker b(ui->intAngle);
int normalizedValue = 0;
if (value >= 270 && value <= 360) {
// Due to the mismatch between the domain of the dial (0°,360°)
// and the spinbox (-179°,180°), the shift in the third quadrant
// of the dial is different
normalizedValue = value - 360 - m_dialValueShift;
} else {
normalizedValue = value - m_dialValueShift;
}
ui->intAngle->setValue(normalizedValue);
emit valueChanged(normalizedValue);
emitChangeSignals();
}
void KisLayerStyleAngleSelector::slotIntAngleChanged(int value)
{
KisSignalsBlocker b(ui->dialAngle);
int angleDialValue = value + m_dialValueShift;
ui->dialAngle->setValue(angleDialValue);
emit valueChanged(value);
emitChangeSignals();
}
void KisLayerStyleAngleSelector::slotGlobalLightToggled()
{
emitChangeSignals();
}
void KisLayerStyleAngleSelector::emitChangeSignals()
{
if (useGlobalLight()) {
emit globalAngleChanged(value());
}
emit configChanged();
}
diff --git a/libs/ui/widgets/KisLayerStyleAngleSelector.h b/libs/ui/widgets/KisLayerStyleAngleSelector.h
index 35eb382641..970078beb0 100644
--- a/libs/ui/widgets/KisLayerStyleAngleSelector.h
+++ b/libs/ui/widgets/KisLayerStyleAngleSelector.h
@@ -1,68 +1,69 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 KISLAYERSTYLEANGLESELECTOR_H
#define KISLAYERSTYLEANGLESELECTOR_H
#include <QObject>
#include <QWidget>
#include <QDial>
#include <ui_wdgKisLayerStyleAngleSelector.h>
class KisLayerStyleAngleSelector : public QWidget
{
Q_OBJECT
public:
KisLayerStyleAngleSelector(QWidget* parent);
int value();
void setValue(int value);
void enableGlobalLight(bool enable);
bool useGlobalLight();
void setUseGlobalLight(bool state);
Q_SIGNALS:
void valueChanged(int);
void configChanged();
void globalAngleChanged(int);
private Q_SLOTS:
void slotDialAngleChanged(int value);
void slotIntAngleChanged(int value);
void slotGlobalLightToggled();
private:
void emitChangeSignals();
Ui_WdgKisLayerStyleAngleSelector* ui;
// BUG: 372169
// Adobe's dial widget differs from QDial by 90 degrees,
// therefore we need to apply this magic constant
// to this widget's QDial for consistency between
// the settings dialogs and on-canvas effects.
// Wrapping is handled by QDial itself.
const static int m_dialValueShift = 90;
bool m_enableGlobalLight;
};
#endif // KISLAYERSTYLEANGLESELECTOR_H
diff --git a/libs/ui/widgets/KoFillConfigWidget.cpp b/libs/ui/widgets/KoFillConfigWidget.cpp
index 75323e75db..11938edf3e 100644
--- a/libs/ui/widgets/KoFillConfigWidget.cpp
+++ b/libs/ui/widgets/KoFillConfigWidget.cpp
@@ -1,898 +1,898 @@
/* This file is part of the KDE project
* Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr)
* Copyright (C) 2012 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
*
* 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 "KoFillConfigWidget.h"
#include <QToolButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QButtonGroup>
#include <QLabel>
#include <QSizePolicy>
#include <QBitmap>
#include <QAction>
#include <QSharedPointer>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <KoColor.h>
#include <KoColorPopupAction.h>
#include "KoResourceServerProvider.h"
#include "KoResourceServerAdapter.h"
#include <KoSelection.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoDocumentResourceManager.h>
#include <KoShape.h>
#include <KoShapeController.h>
#include <KoShapeBackground.h>
#include <KoShapeBackgroundCommand.h>
#include <KoShapeStrokeCommand.h>
#include <KoShapeStroke.h>
#include <KoSelectedShapesProxy.h>
#include <KoColorBackground.h>
#include <KoGradientBackground.h>
#include <KoPatternBackground.h>
#include <KoImageCollection.h>
#include <KoResourcePopupAction.h>
#include "KoZoomHandler.h"
#include "KoColorPopupButton.h"
#include "ui_KoFillConfigWidget.h"
#include <kis_signals_blocker.h>
#include <kis_signal_compressor.h>
#include <kis_acyclic_signal_connector.h>
#include <kis_assert.h>
#include "kis_canvas_resource_provider.h"
#include <KoStopGradient.h>
#include <QInputDialog>
#include <KoShapeFillWrapper.h>
#include "kis_global.h"
#include "kis_debug.h"
static const char* const buttonnone[]={
"16 16 3 1",
"# c #000000",
"e c #ff0000",
"- c #ffffff",
"################",
"#--------------#",
"#-e----------e-#",
"#--e--------e--#",
"#---e------e---#",
"#----e----e----#",
"#-----e--e-----#",
"#------ee------#",
"#------ee------#",
"#-----e--e-----#",
"#----e----e----#",
"#---e------e---#",
"#--e--------e--#",
"#-e----------e-#",
"#--------------#",
"################"};
static const char* const buttonsolid[]={
"16 16 2 1",
"# c #000000",
". c #969696",
"################",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"################"};
// FIXME: Smoother gradient button.
static const char* const buttongradient[]={
"16 16 15 1",
"# c #000000",
"n c #101010",
"m c #202020",
"l c #303030",
"k c #404040",
"j c #505050",
"i c #606060",
"h c #707070",
"g c #808080",
"f c #909090",
"e c #a0a0a0",
"d c #b0b0b0",
"c c #c0c0c0",
"b c #d0d0d0",
"a c #e0e0e0",
"################",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"################"};
static const char* const buttonpattern[]={
"16 16 4 1",
". c #0a0a0a",
"# c #333333",
"a c #a0a0a0",
"b c #ffffffff",
"################",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#bbbbbaaaabbbbb#",
"#bbbbbaaaabbbbb#",
"#bbbbbaaaabbbbb#",
"#bbbbbaaaabbbbb#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"################"};
class Q_DECL_HIDDEN KoFillConfigWidget::Private
{
public:
Private(KoFlake::FillVariant _fillVariant)
: canvas(0),
colorChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE),
gradientChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE),
shapeChangedCompressor(200,KisSignalCompressor::FIRST_ACTIVE),
fillVariant(_fillVariant),
noSelectionTrackingMode(false)
{
}
KoColorPopupAction *colorAction;
KoResourcePopupAction *gradientAction;
KoResourcePopupAction *patternAction;
QButtonGroup *group;
KoCanvasBase *canvas;
KisSignalCompressor colorChangedCompressor;
KisAcyclicSignalConnector shapeChangedAcyclicConnector;
KisAcyclicSignalConnector resourceManagerAcyclicConnector;
KoFillConfigWidget::StyleButton selectedFillIndex {KoFillConfigWidget::None};
QSharedPointer<KoStopGradient> activeGradient;
KisSignalCompressor gradientChangedCompressor;
KisSignalCompressor shapeChangedCompressor;
KoFlake::FillVariant fillVariant;
QList<KoShape*> previousShapeSelected;/// container to see if the selection has actually changed
bool noSelectionTrackingMode;
Ui_KoFillConfigWidget *ui;
std::vector<KisAcyclicSignalConnector::Blocker> deactivationLocks;
};
KoFillConfigWidget::KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, bool trackShapeSelection, QWidget *parent)
: QWidget(parent)
, d(new Private(fillVariant))
{
d->canvas = canvas;
if (trackShapeSelection) {
d->shapeChangedAcyclicConnector.connectBackwardVoid(
d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
&d->shapeChangedCompressor, SLOT(start()));
d->shapeChangedAcyclicConnector.connectBackwardVoid(
d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
&d->shapeChangedCompressor, SLOT(start()));
connect(&d->shapeChangedCompressor, SIGNAL(timeout()), this, SLOT(shapeChanged()));
}
d->resourceManagerAcyclicConnector.connectBackwardResourcePair(
d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(slotCanvasResourceChanged(int,QVariant)));
d->resourceManagerAcyclicConnector.connectForwardVoid(
this, SIGNAL(sigInternalRequestColorToResourceManager()),
this, SLOT(slotProposeCurrentColorToResourceManager()));
// configure GUI
d->ui = new Ui_KoFillConfigWidget();
d->ui->setupUi(this);
d->group = new QButtonGroup(this);
d->group->setExclusive(true);
d->ui->btnNoFill->setIcon(QPixmap((const char **) buttonnone));
d->group->addButton(d->ui->btnNoFill, None);
d->ui->btnSolidFill->setIcon(QPixmap((const char **) buttonsolid));
d->group->addButton(d->ui->btnSolidFill, Solid);
d->ui->btnGradientFill->setIcon(QPixmap((const char **) buttongradient));
d->group->addButton(d->ui->btnGradientFill, Gradient);
d->ui->btnPatternFill->setIcon(QPixmap((const char **) buttonpattern));
d->group->addButton(d->ui->btnPatternFill, Pattern);
d->ui->btnPatternFill->setVisible(false);
d->colorAction = new KoColorPopupAction(d->ui->btnChooseSolidColor);
d->colorAction->setToolTip(i18n("Change the filling color"));
d->colorAction->setCurrentColor(Qt::white);
d->ui->btnChooseSolidColor->setDefaultAction(d->colorAction);
d->ui->btnChooseSolidColor->setPopupMode(QToolButton::InstantPopup);
d->ui->btnSolidColorPick->setIcon(KisIconUtils::loadIcon("krita_tool_color_picker"));
// TODO: for now the color picking button is disabled!
d->ui->btnSolidColorPick->setEnabled(false);
d->ui->btnSolidColorPick->setVisible(false);
connect(d->colorAction, SIGNAL(colorChanged(KoColor)), &d->colorChangedCompressor, SLOT(start()));
connect(&d->colorChangedCompressor, SIGNAL(timeout()), SLOT(colorChanged()));
connect(d->ui->btnChooseSolidColor, SIGNAL(iconSizeChanged()), d->colorAction, SLOT(updateIcon()));
connect(d->group, SIGNAL(buttonClicked(int)), SLOT(styleButtonPressed(int)));
connect(d->group, SIGNAL(buttonClicked(int)), SLOT(slotUpdateFillTitle()));
slotUpdateFillTitle();
styleButtonPressed(d->group->checkedId());
// Gradient selector
d->ui->wdgGradientEditor->setCompactMode(true);
connect(d->ui->wdgGradientEditor, SIGNAL(sigGradientChanged()), &d->gradientChangedCompressor, SLOT(start()));
connect(&d->gradientChangedCompressor, SIGNAL(timeout()), SLOT(activeGradientChanged()));
KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance();
QSharedPointer<KoAbstractResourceServerAdapter> gradientResourceAdapter(
new KoResourceServerAdapter<KoAbstractGradient>(serverProvider->gradientServer()));
d->gradientAction = new KoResourcePopupAction(gradientResourceAdapter,
d->ui->btnChoosePredefinedGradient);
d->gradientAction->setToolTip(i18n("Change filling gradient"));
d->ui->btnChoosePredefinedGradient->setDefaultAction(d->gradientAction);
d->ui->btnChoosePredefinedGradient->setPopupMode(QToolButton::InstantPopup);
connect(d->gradientAction, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground>)),
SLOT(gradientResourceChanged()));
connect(d->ui->btnChoosePredefinedGradient, SIGNAL(iconSizeChanged()), d->gradientAction, SLOT(updateIcon()));
d->ui->btnSaveGradient->setIcon(KisIconUtils::loadIcon("document-save"));
connect(d->ui->btnSaveGradient, SIGNAL(clicked()), SLOT(slotSavePredefinedGradientClicked()));
connect(d->ui->cmbGradientRepeat, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientRepeatChanged()));
connect(d->ui->cmbGradientType, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientTypeChanged()));
deactivate();
#if 0
// Pattern selector
QSharedPointer<KoAbstractResourceServerAdapter>patternResourceAdapter(new KoResourceServerAdapter<KoPattern>(serverProvider->patternServer()));
d->patternAction = new KoResourcePopupAction(patternResourceAdapter, d->colorButton);
d->patternAction->setToolTip(i18n("Change the filling pattern"));
connect(d->patternAction, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground>)), this, SLOT(patternChanged(QSharedPointer<KoShapeBackground>)));
connect(d->colorButton, SIGNAL(iconSizeChanged()), d->patternAction, SLOT(updateIcon()));
#endif
}
KoFillConfigWidget::~KoFillConfigWidget()
{
delete d;
}
void KoFillConfigWidget::activate()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty());
d->deactivationLocks.clear();
if (!d->noSelectionTrackingMode) {
d->shapeChangedCompressor.start();
} else {
loadCurrentFillFromResourceServer();
}
updateWidgetComponentVisbility();
}
void KoFillConfigWidget::deactivate()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty());
d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
}
void KoFillConfigWidget::forceUpdateOnSelectionChanged()
{
d->shapeChangedCompressor.start();
}
void KoFillConfigWidget::setNoSelectionTrackingMode(bool value)
{
d->noSelectionTrackingMode = value;
if (!d->noSelectionTrackingMode) {
d->shapeChangedCompressor.start();
}
}
void KoFillConfigWidget::slotUpdateFillTitle()
{
QString text = d->group->checkedButton() ? d->group->checkedButton()->text() : QString();
text.replace('&', QString());
d->ui->lblFillTitle->setText(text);
}
void KoFillConfigWidget::slotCanvasResourceChanged(int key, const QVariant &value)
{
if ((key == KoCanvasResourceProvider::ForegroundColor && d->fillVariant == KoFlake::Fill) ||
(key == KoCanvasResourceProvider::BackgroundColor &&
d->fillVariant == KoFlake::StrokeFill && !d->noSelectionTrackingMode) ||
(key == KoCanvasResourceProvider::ForegroundColor && d->noSelectionTrackingMode)) {
KoColor color = value.value<KoColor>();
const int checkedId = d->group->checkedId();
if ((checkedId < 0 || checkedId == None || checkedId == Solid) &&
!(checkedId == Solid && d->colorAction->currentKoColor() == color)) {
d->group->button(Solid)->setChecked(true);
d->selectedFillIndex = Solid;
d->colorAction->setCurrentColor(color);
d->colorChangedCompressor.start();
} else if (checkedId == Gradient && key == KoCanvasResourceProvider::ForegroundColor) {
d->ui->wdgGradientEditor->notifyGlobalColorChanged(color);
}
} else if (key == KisCanvasResourceProvider::CurrentGradient) {
KoResource *gradient = value.value<KoAbstractGradient*>();
const int checkedId = d->group->checkedId();
if (gradient && (checkedId < 0 || checkedId == None || checkedId == Gradient)) {
d->group->button(Gradient)->setChecked(true);
d->gradientAction->setCurrentResource(gradient);
}
}
}
QList<KoShape*> KoFillConfigWidget::currentShapes()
{
return d->canvas->selectedShapesProxy()->selection()->selectedEditableShapes();
}
int KoFillConfigWidget::selectedFillIndex() {
return d->selectedFillIndex;
}
void KoFillConfigWidget::styleButtonPressed(int buttonId)
{
QList<KoShape*> shapes = currentShapes();
switch (buttonId) {
case KoFillConfigWidget::None:
noColorSelected();
break;
case KoFillConfigWidget::Solid:
colorChanged();
break;
case KoFillConfigWidget::Gradient:
if (d->activeGradient) {
setNewGradientBackgroundToShape();
updateGradientSaveButtonAvailability();
} else {
gradientResourceChanged();
}
break;
case KoFillConfigWidget::Pattern:
// Only select mode in the widget, don't set actual pattern :/
//d->colorButton->setDefaultAction(d->patternAction);
//patternChanged(d->patternAction->currentBackground());
break;
}
// update tool option fields with first selected object
if (shapes.isEmpty() == false) {
KoShape *firstShape = shapes.first();
updateFillIndexFromShape(firstShape);
updateFillColorFromShape(firstShape);
}
updateWidgetComponentVisbility();
}
KoShapeStrokeSP KoFillConfigWidget::createShapeStroke()
{
KoShapeStrokeSP stroke(new KoShapeStroke());
KIS_ASSERT_RECOVER_RETURN_VALUE(d->fillVariant == KoFlake::StrokeFill, stroke);
switch (d->group->checkedId()) {
case KoFillConfigWidget::None:
stroke->setColor(Qt::transparent);
break;
case KoFillConfigWidget::Solid:
stroke->setColor(d->colorAction->currentColor());
break;
case KoFillConfigWidget::Gradient: {
QScopedPointer<QGradient> g(d->activeGradient->toQGradient());
QBrush newBrush = *g;
stroke->setLineBrush(newBrush);
stroke->setColor(Qt::transparent);
break;
}
case KoFillConfigWidget::Pattern:
break;
}
return stroke;
}
void KoFillConfigWidget::noColorSelected()
{
KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
emit sigFillChanged();
return;
}
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
KUndo2Command *command = wrapper.setColor(QColor());
if (command) {
d->canvas->addCommand(command);
}
if (d->fillVariant == KoFlake::StrokeFill) {
KUndo2Command *lineCommand = wrapper.setLineWidth(0.0);
if (lineCommand) {
d->canvas->addCommand(lineCommand);
}
}
emit sigFillChanged();
}
void KoFillConfigWidget::colorChanged()
{
KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
emit sigInternalRequestColorToResourceManager();
emit sigFillChanged();
return;
}
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor());
if (command) {
d->canvas->addCommand(command);
}
// only returns true if it is a stroke object that has a 0 for line width
if (wrapper.hasZeroLineWidth() ) {
KUndo2Command *lineCommand = wrapper.setLineWidth(1.0);
if (lineCommand) {
d->canvas->addCommand(lineCommand);
}
// * line to test out
QColor solidColor = d->colorAction->currentColor();
solidColor.setAlpha(255);
command = wrapper.setColor(solidColor);
if (command) {
d->canvas->addCommand(command);
}
}
d->colorAction->setCurrentColor(wrapper.color());
emit sigFillChanged();
emit sigInternalRequestColorToResourceManager();
}
void KoFillConfigWidget::slotProposeCurrentColorToResourceManager()
{
const int checkedId = d->group->checkedId();
bool hasColor = false;
KoColor color;
KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor;
if (checkedId == Solid) {
if (d->fillVariant == KoFlake::StrokeFill) {
colorSlot = KoCanvasResourceProvider::BackgroundColor;
}
color = d->colorAction->currentKoColor();
hasColor = true;
} else if (checkedId == Gradient) {
if (boost::optional<KoColor> gradientColor = d->ui->wdgGradientEditor->currentActiveStopColor()) {
color = *gradientColor;
hasColor = true;
}
}
if (hasColor) {
/**
* Don't let opacity leak to our resource manager system
*
* NOTE: theoretically, we could guarantee it on a level of the
* resource manager itself,
*/
color.setOpacity(OPACITY_OPAQUE_U8);
d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(color));
}
}
template <class ResourceServer>
QString findFirstAvailableResourceName(const QString &baseName, ResourceServer *server)
{
if (!server->resourceByName(baseName)) return baseName;
int counter = 1;
QString result;
while ((result = QString("%1%2").arg(baseName).arg(counter)),
server->resourceByName(result)) {
counter++;
}
return result;
}
void KoFillConfigWidget::slotSavePredefinedGradientClicked()
{
KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance();
auto server = serverProvider->gradientServer();
const QString defaultGradientNamePrefix = i18nc("default prefix for the saved gradient", "gradient");
QString name = d->activeGradient->name().isEmpty() ? defaultGradientNamePrefix : d->activeGradient->name();
name = findFirstAvailableResourceName(name, server);
name = QInputDialog::getText(this, i18nc("@title:window", "Save Gradient"), i18n("Enter gradient name:"), QLineEdit::Normal, name);
// TODO: currently we do not allow the user to
// create two resources with the same name!
// Please add some feedback for it!
name = findFirstAvailableResourceName(name, server);
d->activeGradient->setName(name);
const QString saveLocation = server->saveLocation();
d->activeGradient->setFilename(saveLocation + d->activeGradient->name() + d->activeGradient->defaultFileExtension());
KoAbstractGradient *newGradient = d->activeGradient->clone();
server->addResource(newGradient);
d->gradientAction->setCurrentResource(newGradient);
}
void KoFillConfigWidget::activeGradientChanged()
{
setNewGradientBackgroundToShape();
updateGradientSaveButtonAvailability();
emit sigInternalRequestColorToResourceManager();
}
void KoFillConfigWidget::gradientResourceChanged()
{
QSharedPointer<KoGradientBackground> bg =
qSharedPointerDynamicCast<KoGradientBackground>(
d->gradientAction->currentBackground());
uploadNewGradientBackground(bg->gradient());
setNewGradientBackgroundToShape();
updateGradientSaveButtonAvailability();
}
void KoFillConfigWidget::slotGradientTypeChanged()
{
QGradient::Type type =
d->ui->cmbGradientType->currentIndex() == 0 ?
QGradient::LinearGradient : QGradient::RadialGradient;
d->activeGradient->setType(type);
activeGradientChanged();
}
void KoFillConfigWidget::slotGradientRepeatChanged()
{
QGradient::Spread spread =
QGradient::Spread(d->ui->cmbGradientRepeat->currentIndex());
d->activeGradient->setSpread(spread);
activeGradientChanged();
}
void KoFillConfigWidget::uploadNewGradientBackground(const QGradient *gradient)
{
KisSignalsBlocker b1(d->ui->wdgGradientEditor,
d->ui->cmbGradientType,
d->ui->cmbGradientRepeat);
d->ui->wdgGradientEditor->setGradient(0);
d->activeGradient.reset(KoStopGradient::fromQGradient(gradient));
d->ui->wdgGradientEditor->setGradient(d->activeGradient.data());
d->ui->cmbGradientType->setCurrentIndex(d->activeGradient->type() != QGradient::LinearGradient);
d->ui->cmbGradientRepeat->setCurrentIndex(int(d->activeGradient->spread()));
}
void KoFillConfigWidget::setNewGradientBackgroundToShape()
{
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
emit sigFillChanged();
return;
}
KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
QScopedPointer<QGradient> srcQGradient(d->activeGradient->toQGradient());
KUndo2Command *command = wrapper.applyGradientStopsOnly(srcQGradient.data());
if (command) {
d->canvas->addCommand(command);
}
emit sigFillChanged();
}
void KoFillConfigWidget::updateGradientSaveButtonAvailability()
{
bool savingEnabled = false;
QScopedPointer<QGradient> currentGradient(d->activeGradient->toQGradient());
QSharedPointer<KoShapeBackground> bg = d->gradientAction->currentBackground();
if (bg) {
QSharedPointer<KoGradientBackground> resourceBackground =
qSharedPointerDynamicCast<KoGradientBackground>(bg);
savingEnabled = resourceBackground->gradient()->stops() != currentGradient->stops();
savingEnabled |= resourceBackground->gradient()->type() != currentGradient->type();
savingEnabled |= resourceBackground->gradient()->spread() != currentGradient->spread();
}
d->ui->btnSaveGradient->setEnabled(savingEnabled);
}
void KoFillConfigWidget::patternChanged(QSharedPointer<KoShapeBackground> background)
{
Q_UNUSED(background);
#if 0
QSharedPointer<KoPatternBackground> patternBackground = qSharedPointerDynamicCast<KoPatternBackground>(background);
if (! patternBackground) {
return;
}
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
return;
}
KoImageCollection *imageCollection = d->canvas->shapeController()->resourceManager()->imageCollection();
if (imageCollection) {
QSharedPointer<KoPatternBackground> fill(new KoPatternBackground(imageCollection));
fill->setPattern(patternBackground->pattern());
d->canvas->addCommand(new KoShapeBackgroundCommand(selectedShapes, fill));
}
#endif
}
void KoFillConfigWidget::loadCurrentFillFromResourceServer()
{
{
KoColor color = d->canvas->resourceManager()->backgroundColor();
slotCanvasResourceChanged(KoCanvasResourceProvider::BackgroundColor, QVariant::fromValue(color));
}
{
KoColor color = d->canvas->resourceManager()->foregroundColor();
slotCanvasResourceChanged(KoCanvasResourceProvider::ForegroundColor, QVariant::fromValue(color));
}
Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
button->setEnabled(true);
}
emit sigFillChanged();
}
void KoFillConfigWidget::shapeChanged()
{
if (d->noSelectionTrackingMode) return;
QList<KoShape*> shapes = currentShapes();
// check to see if the shape actually changed...or is still the same shape
if (d->previousShapeSelected == shapes) {
return;
} else {
d->previousShapeSelected = shapes;
}
if (shapes.isEmpty() ||
(shapes.size() > 1 && KoShapeFillWrapper(shapes, d->fillVariant).isMixedFill())) {
Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
button->setEnabled(!shapes.isEmpty());
}
} else {
// only one vector object selected
Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
button->setEnabled(true);
}
// update active index of shape
KoShape *shape = shapes.first();
updateFillIndexFromShape(shape);
updateFillColorFromShape(shape); // updates tool options fields
}
// updates the UI
d->group->button(d->selectedFillIndex)->setChecked(true);
updateWidgetComponentVisbility();
slotUpdateFillTitle();
}
void KoFillConfigWidget::updateFillIndexFromShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
KoShapeFillWrapper wrapper(shape, d->fillVariant);
switch (wrapper.type()) {
case KoFlake::None:
d->selectedFillIndex = KoFillConfigWidget::None;
break;
case KoFlake::Solid:
d->selectedFillIndex = KoFillConfigWidget::Solid;
break;
case KoFlake::Gradient:
d->selectedFillIndex = KoFillConfigWidget::Gradient;
break;
case KoFlake::Pattern:
d->selectedFillIndex = KoFillConfigWidget::Pattern;
break;
}
}
void KoFillConfigWidget::updateFillColorFromShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
KoShapeFillWrapper wrapper(shape, d->fillVariant);
switch (wrapper.type()) {
case KoFlake::None:
break;
case KoFlake::Solid: {
QColor color = wrapper.color();
if (color.alpha() > 0) {
d->colorAction->setCurrentColor(wrapper.color());
}
break;
}
case KoFlake::Gradient:
uploadNewGradientBackground(wrapper.gradient());
updateGradientSaveButtonAvailability();
break;
case KoFlake::Pattern:
break;
}
}
void KoFillConfigWidget::updateWidgetComponentVisbility()
{
// The UI is showing/hiding things like this because the 'stacked widget' isn't very flexible
// and makes it difficult to put anything underneath it without a lot empty space
// hide everything first
d->ui->wdgGradientEditor->setVisible(false);
d->ui->btnChoosePredefinedGradient->setVisible(false);
d->ui->btnChooseSolidColor->setVisible(false);
d->ui->typeLabel->setVisible(false);
d->ui->repeatLabel->setVisible(false);
d->ui->cmbGradientRepeat->setVisible(false);
d->ui->cmbGradientType->setVisible(false);
d->ui->btnSolidColorPick->setVisible(false);
d->ui->btnSaveGradient->setVisible(false);
d->ui->gradientTypeLine->setVisible(false);
d->ui->soldStrokeColorLabel->setVisible(false);
d->ui->presetLabel->setVisible(false);
// keep options hidden if no vector shapes are selected
if(currentShapes().isEmpty()) {
return;
}
switch (d->selectedFillIndex) {
case KoFillConfigWidget::None:
break;
case KoFillConfigWidget::Solid:
d->ui->btnChooseSolidColor->setVisible(true);
- d->ui->btnSolidColorPick->setVisible(true);
+ d->ui->btnSolidColorPick->setVisible(false);
d->ui->soldStrokeColorLabel->setVisible(true);
break;
case KoFillConfigWidget::Gradient:
d->ui->wdgGradientEditor->setVisible(true);
d->ui->btnChoosePredefinedGradient->setVisible(true);
d->ui->typeLabel->setVisible(true);
d->ui->repeatLabel->setVisible(true);
d->ui->cmbGradientRepeat->setVisible(true);
d->ui->cmbGradientType->setVisible(true);
d->ui->btnSaveGradient->setVisible(true);
d->ui->gradientTypeLine->setVisible(true);
d->ui->presetLabel->setVisible(true);
break;
case KoFillConfigWidget::Pattern:
break;
}
}
diff --git a/libs/ui/widgets/kis_gradient_chooser.cc b/libs/ui/widgets/kis_gradient_chooser.cc
index 2c6de0353e..c02a8e1abf 100644
--- a/libs/ui/widgets/kis_gradient_chooser.cc
+++ b/libs/ui/widgets/kis_gradient_chooser.cc
@@ -1,205 +1,209 @@
/*
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "widgets/kis_gradient_chooser.h"
#include <QLabel>
#include <QLayout>
#include <QPushButton>
#include <QVBoxLayout>
#include <QMenu>
#include <klocalizedstring.h>
#include <resources/KoAbstractGradient.h>
#include <resources/KoResource.h>
#include <resources/KoSegmentGradient.h>
#include <KoResourceItemView.h>
#include <KisKineticScroller.h>
#include <KoStopGradient.h>
#include <KoColorSpaceRegistry.h>
#include <KoResourceItemChooser.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServerAdapter.h>
#include <kis_icon.h>
#include <kis_config.h>
#include "KisViewManager.h"
#include "kis_global.h"
#include "kis_autogradient.h"
#include "kis_canvas_resource_provider.h"
#include "kis_stopgradient_editor.h"
-KisCustomGradientDialog::KisCustomGradientDialog(KoAbstractGradient* gradient, QWidget * parent, const char *name)
- : KoDialog(parent)
+KisCustomGradientDialog::KisCustomGradientDialog(KoAbstractGradient* gradient, QWidget *parent, const char *name)
+ : KoDialog(parent, Qt::Dialog)
{
- setCaption(i18n("Custom Gradient"));
setButtons(Close);
setDefaultButton(Close);
setObjectName(name);
setModal(false);
KoStopGradient* stopGradient = dynamic_cast<KoStopGradient*>(gradient);
if (stopGradient) {
- m_page = new KisStopGradientEditor(stopGradient, this, "autogradient", i18n("Custom Gradient"));
+ m_page = new KisStopGradientEditor(stopGradient, this, "autogradient", i18n("Custom Stop Gradient"));
}
- KoSegmentGradient* segmentedGradient = dynamic_cast<KoSegmentGradient*>(gradient);
- if (segmentedGradient) {
- m_page = new KisAutogradient(segmentedGradient, this, "autogradient", i18n("Custom Gradient"));
+ else {
+ KoSegmentGradient* segmentedGradient = dynamic_cast<KoSegmentGradient*>(gradient);
+ if (segmentedGradient) {
+ m_page = new KisAutogradientEditor(segmentedGradient, this, "autogradient", i18n("Custom Segmented Gradient"));
+ }
}
+ setCaption(m_page->windowTitle());
setMainWidget(m_page);
}
KisGradientChooser::KisGradientChooser(QWidget *parent, const char *name)
: QFrame(parent)
{
setObjectName(name);
m_lbName = new QLabel();
KoResourceServer<KoAbstractGradient> * rserver = KoResourceServerProvider::instance()->gradientServer();
QSharedPointer<KoAbstractResourceServerAdapter> adapter (new KoResourceServerAdapter<KoAbstractGradient>(rserver));
m_itemChooser = new KoResourceItemChooser(adapter, this);
m_itemChooser->showTaggingBar(true);
m_itemChooser->setFixedSize(250, 250);
m_itemChooser->setColumnCount(1);
connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)),
this, SLOT(update(KoResource*)));
connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)),
this, SIGNAL(resourceSelected(KoResource*)));
QWidget* buttonWidget = new QWidget(this);
QHBoxLayout* buttonLayout = new QHBoxLayout(buttonWidget);
m_addGradient = new QToolButton(this);
m_addGradient->setText(i18n("Add..."));
m_addGradient->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
connect(m_addGradient, SIGNAL(clicked()), this, SLOT(addStopGradient()));
buttonLayout->addWidget(m_addGradient);
QMenu *menuAddGradient = new QMenu(m_addGradient);
QAction* addStopGradient = new QAction(i18n("Stop gradient"), this);
connect(addStopGradient, SIGNAL(triggered(bool)), this, SLOT(addStopGradient()));
menuAddGradient->addAction(addStopGradient);
QAction* addSegmentedGradient = new QAction(i18n("Segmented gradient"), this);
connect(addSegmentedGradient, SIGNAL(triggered(bool)), this, SLOT(addSegmentedGradient()));
menuAddGradient->addAction(addSegmentedGradient);
m_addGradient->setMenu(menuAddGradient);
m_addGradient->setPopupMode(QToolButton::MenuButtonPopup);
m_editGradient = new QPushButton();
m_editGradient->setText(i18n("Edit..."));
m_editGradient->setEnabled(false);
connect(m_editGradient, SIGNAL(clicked()), this, SLOT(editGradient()));
buttonLayout->addWidget(m_editGradient);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setObjectName("main layout");
mainLayout->setMargin(2);
mainLayout->addWidget(m_lbName);
mainLayout->addWidget(m_itemChooser, 10);
mainLayout->addWidget(buttonWidget);
slotUpdateIcons();
setLayout(mainLayout);
}
KisGradientChooser::~KisGradientChooser()
{
}
KoResource *KisGradientChooser::currentResource()
{
return m_itemChooser->currentResource();
}
void KisGradientChooser::setCurrentResource(KoResource *resource)
{
m_itemChooser->setCurrentResource(resource);
}
void KisGradientChooser::setCurrentItem(int row, int column)
{
m_itemChooser->setCurrentItem(row, column);
if (currentResource())
update(currentResource());
}
void KisGradientChooser::slotUpdateIcons()
{
if (m_addGradient && m_editGradient) {
m_addGradient->setIcon(KisIconUtils::loadIcon("list-add"));
m_editGradient->setIcon(KisIconUtils::loadIcon("configure"));
}
}
void KisGradientChooser::update(KoResource * resource)
{
KoAbstractGradient *gradient = static_cast<KoAbstractGradient *>(resource);
m_lbName->setText(gradient ? i18n(gradient->name().toUtf8().data()) : "");
m_editGradient->setEnabled(gradient && gradient->removable());
}
void KisGradientChooser::addStopGradient()
{
KoStopGradient* gradient = new KoStopGradient("");
QList<KoGradientStop> stops;
stops << KoGradientStop(0.0, KoColor(QColor(250, 250, 0), KoColorSpaceRegistry::instance()->rgb8())) << KoGradientStop(1.0, KoColor(QColor(255, 0, 0, 255), KoColorSpaceRegistry::instance()->rgb8()));
gradient->setType(QGradient::LinearGradient);
gradient->setStops(stops);
addGradient(gradient);
}
void KisGradientChooser::addSegmentedGradient()
{
KoSegmentGradient* gradient = new KoSegmentGradient("");
gradient->createSegment(INTERP_LINEAR, COLOR_INTERP_RGB, 0.0, 1.0, 0.5, Qt::black, Qt::white);
gradient->setName(i18n("unnamed"));
addGradient(gradient);
}
void KisGradientChooser::addGradient(KoAbstractGradient* gradient)
{
KoResourceServer<KoAbstractGradient> * rserver = KoResourceServerProvider::instance()->gradientServer();
QString saveLocation = rserver->saveLocation();
- KisCustomGradientDialog dialog(gradient, this, "autogradient");
+ KisCustomGradientDialog dialog(gradient, this, "KisCustomGradientDialog");
dialog.exec();
gradient->setFilename(saveLocation + gradient->name() + gradient->defaultFileExtension());
gradient->setValid(true);
rserver->addResource(gradient);
m_itemChooser->setCurrentResource(gradient);
}
void KisGradientChooser::editGradient()
{
- KisCustomGradientDialog dialog(static_cast<KoAbstractGradient*>(currentResource()), this, "autogradient");
+ KisCustomGradientDialog dialog(static_cast<KoAbstractGradient*>(currentResource()), this, "KisCustomGradientDialog");
dialog.exec();
+
+
}
diff --git a/libs/ui/widgets/kis_gradient_chooser.h b/libs/ui/widgets/kis_gradient_chooser.h
index d86da361b9..abfbdb16fc 100644
--- a/libs/ui/widgets/kis_gradient_chooser.h
+++ b/libs/ui/widgets/kis_gradient_chooser.h
@@ -1,91 +1,91 @@
/*
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
*
* 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_GRADIENT_CHOOSER_H_
#define KIS_GRADIENT_CHOOSER_H_
#include <KoDialog.h>
#include <QFrame>
#include <QToolButton>
#include <kritaui_export.h>
class KoAbstractGradient;
class KoStopGradient;
class KisViewManager;
class QLabel;
class QPushButton;
-class KisAutogradient;
+class KisAutogradientEditor;
class KoResource;
class KoResourceItemChooser;
class KisCustomGradientDialog : public KoDialog
{
Q_OBJECT
public:
- KisCustomGradientDialog(KoAbstractGradient* gradient, QWidget * parent, const char *name);
+ KisCustomGradientDialog(KoAbstractGradient* gradient, QWidget *parent, const char *name);
private:
QWidget * m_page;
};
class KRITAUI_EXPORT KisGradientChooser : public QFrame
{
Q_OBJECT
public:
KisGradientChooser(QWidget *parent = 0, const char *name = 0);
~KisGradientChooser() override;
/// Gets the currently selected resource
/// @returns the selected resource, 0 is no resource is selected
KoResource *currentResource();
void setCurrentResource(KoResource *resource);
void setCurrentItem(int row, int column);
Q_SIGNALS:
/// Emitted when a resource was selected
void resourceSelected(KoResource * resource);
public Q_SLOTS:
void slotUpdateIcons();
private Q_SLOTS:
virtual void update(KoResource * resource);
void addStopGradient();
void addSegmentedGradient();
void editGradient();
private:
void addGradient(KoAbstractGradient* gradient);
private:
QLabel *m_lbName;
KoResourceItemChooser * m_itemChooser;
QToolButton* m_addGradient;
QPushButton* m_editGradient;
};
#endif // KIS_GRADIENT_CHOOSER_H_
diff --git a/libs/ui/widgets/kis_iconwidget.h b/libs/ui/widgets/kis_iconwidget.h
index 3267985a5f..5d9e6e9262 100644
--- a/libs/ui/widgets/kis_iconwidget.h
+++ b/libs/ui/widgets/kis_iconwidget.h
@@ -1,48 +1,49 @@
/*
* Copyright (c) 2000 Matthias Elter <elter@kde.org>
* Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_ICONWIDGET_H_
#define KIS_ICONWIDGET_H_
#include <kis_popup_button.h>
+#include <kritaui_export.h>
class KoResource;
/**
* The icon widget is used in the control box where the current color and brush
* are shown.
*/
-class KisIconWidget : public KisPopupButton
+class KRITAUI_EXPORT KisIconWidget : public KisPopupButton
{
Q_OBJECT
public:
KisIconWidget(QWidget *parent = 0, const char *name = 0);
void setResource(KoResource * resource);
protected:
void paintEvent(QPaintEvent *) override;
private:
KoResource *m_resource;
};
#endif // KIS_ICONWIDGET_H_
diff --git a/libs/ui/widgets/kis_selection_options.cc b/libs/ui/widgets/kis_selection_options.cc
index 3e42dc1ba8..1dbffa4274 100644
--- a/libs/ui/widgets/kis_selection_options.cc
+++ b/libs/ui/widgets/kis_selection_options.cc
@@ -1,187 +1,193 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_selection_options.h"
#include <QWidget>
#include <QRadioButton>
#include <QComboBox>
#include <QVBoxLayout>
#include <QLayout>
#include <QButtonGroup>
#include <kis_icon.h>
#include "kis_types.h"
#include "kis_layer.h"
#include "kis_image.h"
#include "kis_selection.h"
#include "kis_paint_device.h"
#include "canvas/kis_canvas2.h"
#include "KisViewManager.h"
#include <ksharedconfig.h>
#include <kconfiggroup.h>
KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/)
{
m_page = new WdgSelectionOptions(this);
Q_CHECK_PTR(m_page);
QVBoxLayout * l = new QVBoxLayout(this);
l->addWidget(m_page);
l->addSpacerItem(new QSpacerItem(0,0, QSizePolicy::Preferred, QSizePolicy::Expanding));
l->setContentsMargins(0,0,0,0);
m_mode = new QButtonGroup(this);
m_mode->addButton(m_page->pixel, PIXEL_SELECTION);
m_mode->addButton(m_page->shape, SHAPE_PROTECTION);
m_action = new QButtonGroup(this);
m_action->addButton(m_page->add, SELECTION_ADD);
m_action->addButton(m_page->subtract, SELECTION_SUBTRACT);
m_action->addButton(m_page->replace, SELECTION_REPLACE);
m_action->addButton(m_page->intersect, SELECTION_INTERSECT);
m_action->addButton(m_page->symmetricdifference, SELECTION_SYMMETRICDIFFERENCE);
m_page->pixel->setGroupPosition(KoGroupButton::GroupLeft);
m_page->shape->setGroupPosition(KoGroupButton::GroupRight);
m_page->pixel->setIcon(KisIconUtils::loadIcon("select_pixel"));
m_page->shape->setIcon(KisIconUtils::loadIcon("select_shape"));
m_page->add->setGroupPosition(KoGroupButton::GroupCenter);
m_page->subtract->setGroupPosition(KoGroupButton::GroupCenter);
m_page->replace->setGroupPosition(KoGroupButton::GroupLeft);
m_page->intersect->setGroupPosition(KoGroupButton::GroupCenter);
m_page->symmetricdifference->setGroupPosition(KoGroupButton::GroupRight);
m_page->add->setIcon(KisIconUtils::loadIcon("selection_add"));
m_page->subtract->setIcon(KisIconUtils::loadIcon("selection_subtract"));
m_page->replace->setIcon(KisIconUtils::loadIcon("selection_replace"));
m_page->intersect->setIcon(KisIconUtils::loadIcon("selection_intersect"));
m_page->symmetricdifference->setIcon(KisIconUtils::loadIcon("selection_symmetric_difference"));
connect(m_mode, SIGNAL(buttonClicked(int)), this, SIGNAL(modeChanged(int)));
connect(m_action, SIGNAL(buttonClicked(int)), this, SIGNAL(actionChanged(int)));
connect(m_mode, SIGNAL(buttonClicked(int)), this, SLOT(hideActionsForSelectionMode(int)));
connect(m_page->chkAntiAliasing, SIGNAL(toggled(bool)), this, SIGNAL(antiAliasSelectionChanged(bool)));
KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase");
m_page->chkAntiAliasing->setChecked(cfg.readEntry("antiAliasSelection", true));
}
KisSelectionOptions::~KisSelectionOptions()
{
}
int KisSelectionOptions::action()
{
return m_action->checkedId();
}
void KisSelectionOptions::setAction(int action) {
QAbstractButton* button = m_action->button(action);
KIS_SAFE_ASSERT_RECOVER_RETURN(button);
button->setChecked(true);
}
void KisSelectionOptions::setMode(int mode) {
QAbstractButton* button = m_mode->button(mode);
KIS_SAFE_ASSERT_RECOVER_RETURN(button);
button->setChecked(true);
hideActionsForSelectionMode(mode);
}
void KisSelectionOptions::setAntiAliasSelection(bool value)
{
m_page->chkAntiAliasing->setChecked(value);
}
+void KisSelectionOptions::enablePixelOnlySelectionMode()
+{
+ setMode(PIXEL_SELECTION);
+ disableSelectionModeOption();
+}
+
void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequence &shortcut)
{
const QString shortcutString = shortcut.toString(QKeySequence::NativeText);
QString toolTipText;
switch ((SelectionAction)action) {
case SELECTION_DEFAULT:
case SELECTION_REPLACE:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Replace") :
i18nc("@info:tooltip", "Replace (%1)", shortcutString);
m_action->button(SELECTION_REPLACE)->setToolTip(toolTipText);
break;
case SELECTION_ADD:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Add") :
i18nc("@info:tooltip", "Add (%1)", shortcutString);
m_action->button(SELECTION_ADD)->setToolTip(toolTipText);
break;
case SELECTION_SUBTRACT:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Subtract") :
i18nc("@info:tooltip", "Subtract (%1)", shortcutString);
m_action->button(SELECTION_SUBTRACT)->setToolTip(toolTipText);
break;
case SELECTION_INTERSECT:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Intersect") :
i18nc("@info:tooltip", "Intersect (%1)", shortcutString);
m_action->button(SELECTION_INTERSECT)->setToolTip(toolTipText);
break;
case SELECTION_SYMMETRICDIFFERENCE:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Symmetric Difference") :
i18nc("@info:tooltip", "Symmetric Difference (%1)", shortcutString);
m_action->button(SELECTION_SYMMETRICDIFFERENCE)->setToolTip(toolTipText);
break;
}
}
//hide action buttons and antialiasing, if shape selection is active (actions currently don't work on shape selection)
void KisSelectionOptions::hideActionsForSelectionMode(int mode) {
const bool isPixelSelection = (mode == (int)PIXEL_SELECTION);
m_page->chkAntiAliasing->setVisible(isPixelSelection);
}
bool KisSelectionOptions::antiAliasSelection()
{
return m_page->chkAntiAliasing->isChecked();
}
void KisSelectionOptions::disableAntiAliasSelectionOption()
{
m_page->chkAntiAliasing->hide();
disconnect(m_page->pixel, SIGNAL(clicked()), m_page->chkAntiAliasing, SLOT(show()));
}
void KisSelectionOptions::disableSelectionModeOption()
{
m_page->lblMode->hide();
m_page->pixel->hide();
m_page->shape->hide();
}
diff --git a/libs/ui/widgets/kis_selection_options.h b/libs/ui/widgets/kis_selection_options.h
index 32d20c2889..9a8065d60b 100644
--- a/libs/ui/widgets/kis_selection_options.h
+++ b/libs/ui/widgets/kis_selection_options.h
@@ -1,79 +1,80 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_SELECTION_OPTIONS_H__
#define __KIS_SELECTION_OPTIONS_H__
#include <QWidget>
#include "kritaui_export.h"
#include "ui_wdgselectionoptions.h"
class KisCanvas2;
class QButtonGroup;
class QKeySequence;
class WdgSelectionOptions : public QWidget, public Ui::WdgSelectionOptions
{
Q_OBJECT
public:
WdgSelectionOptions(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
/**
*/
class KRITAUI_EXPORT KisSelectionOptions : public QWidget
{
Q_OBJECT
public:
KisSelectionOptions(KisCanvas2 * subject);
~KisSelectionOptions() override;
int action();
bool antiAliasSelection();
void disableAntiAliasSelectionOption();
void disableSelectionModeOption();
void setAction(int);
void setMode(int);
void setAntiAliasSelection(bool value);
+ void enablePixelOnlySelectionMode();
void updateActionButtonToolTip(int action, const QKeySequence &shortcut);
Q_SIGNALS:
void actionChanged(int);
void modeChanged(int);
void antiAliasSelectionChanged(bool);
private Q_SLOTS:
void hideActionsForSelectionMode(int mode);
private:
WdgSelectionOptions * m_page;
QButtonGroup* m_mode;
QButtonGroup* m_action;
};
#endif
diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp
index 40df8bfe68..8db1984887 100644
--- a/libs/ui/widgets/kis_slider_spin_box.cpp
+++ b/libs/ui/widgets/kis_slider_spin_box.cpp
@@ -1,1051 +1,1052 @@
/* This file is part of the KDE project
* Copyright (c) 2010 Justin Noel <justin@ics.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2015 Moritz Molch <kde@moritzmolch.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_slider_spin_box.h"
#include <math.h>
#include <QPainter>
#include <QStyle>
#include <QLineEdit>
#include <QApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QIntValidator>
#include <QTimer>
#include <QtDebug>
#include <QDoubleSpinBox>
#include "kis_cursor.h"
#include "KisPart.h"
#include "input/kis_input_manager.h"
#include "kis_num_parser.h"
class KisAbstractSliderSpinBoxPrivate {
public:
enum Style {
STYLE_NOQUIRK,
STYLE_PLASTIQUE,
STYLE_BREEZE,
STYLE_FUSION,
};
QLineEdit* edit;
QDoubleValidator* validator;
bool upButtonDown;
bool downButtonDown;
int factor;
int fastSliderStep;
qreal slowFactor;
qreal shiftPercent;
bool shiftMode;
QString prefix;
QString suffix;
qreal exponentRatio;
int value;
int maximum;
int minimum;
int singleStep;
QSpinBox* dummySpinBox;
Style style;
bool blockUpdateSignalOnDrag;
bool isDragging;
bool parseInt;
};
KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d)
: QWidget(parent)
, d_ptr(_d)
{
Q_D(KisAbstractSliderSpinBox);
QEvent e(QEvent::StyleChange);
changeEvent(&e);
d->upButtonDown = false;
d->downButtonDown = false;
d->edit = new QLineEdit(this);
d->edit->setFrame(false);
d->edit->setAlignment(Qt::AlignCenter);
d->edit->hide();
d->edit->setContentsMargins(0,0,0,0);
d->edit->installEventFilter(this);
//Make edit transparent
d->edit->setAutoFillBackground(false);
QPalette pal = d->edit->palette();
pal.setColor(QPalette::Base, Qt::transparent);
d->edit->setPalette(pal);
d->edit->setContextMenuPolicy(Qt::PreventContextMenu);
connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus()));
d->validator = new QDoubleValidator(d->edit);
d->value = 0;
d->minimum = 0;
d->maximum = 100;
d->factor = 1.0;
d->singleStep = 1;
d->fastSliderStep = 5;
d->slowFactor = 0.1;
d->shiftMode = false;
d->blockUpdateSignalOnDrag = false;
d->isDragging = false;
d->parseInt = false;
setExponentRatio(1.0);
setCursor(KisCursor::splitHCursor());
//Set sane defaults
setFocusPolicy(Qt::StrongFocus);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
//dummy needed to fix a bug in the polyester theme
d->dummySpinBox = new QSpinBox(this);
d->dummySpinBox->hide();
}
KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox()
{
Q_D(KisAbstractSliderSpinBox);
delete d;
}
void KisAbstractSliderSpinBox::showEdit()
{
Q_D(KisAbstractSliderSpinBox);
if (d->edit->isVisible()) return;
if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) {
d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0));
}
else {
d->edit->setGeometry(progressRect(spinBoxOptions()));
}
d->edit->setText(valueString());
d->edit->selectAll();
d->edit->show();
d->edit->setFocus(Qt::OtherFocusReason);
update();
}
void KisAbstractSliderSpinBox::hideEdit()
{
Q_D(KisAbstractSliderSpinBox);
d->edit->hide();
update();
}
void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
Q_UNUSED(e)
QPainter painter(this);
switch (d->style) {
case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION:
paintFusion(painter);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
paintPlastique(painter);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
paintBreeze(painter);
break;
default:
paint(painter);
break;
}
painter.end();
}
void KisAbstractSliderSpinBox::paint(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
//Create options to draw spin box parts
QStyleOptionSpinBox spinOpts = spinBoxOptions();
spinOpts.rect.adjust(0, 2, 0, -2);
//Draw "SpinBox".Clip off the area of the lineEdit to avoid double
//borders being drawn
painter.save();
painter.setClipping(true);
QRect eraseRect(QPoint(rect().x(), rect().y()),
QPoint(progressRect(spinOpts).right(), rect().bottom()));
painter.setClipRegion(QRegion(rect()).subtracted(eraseRect));
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.setClipping(false);
painter.restore();
QStyleOptionProgressBar progressOpts = progressBarOptions();
progressOpts.rect.adjust(0, 2, 0, -2);
style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0);
//Draw focus if necessary
if (hasFocus() &&
d->edit->hasFocus()) {
QStyleOptionFocusRect focusOpts;
focusOpts.initFrom(this);
focusOpts.rect = progressOpts.rect;
focusOpts.backgroundColor = palette().color(QPalette::Window);
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this);
}
}
void KisAbstractSliderSpinBox::paintFusion(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
spinOpts.frame = true;
spinOpts.rect.adjust(0, -1, 0, 1);
//spinOpts.palette().setBrush(QPalette::Base, palette().highlight());
QStyleOptionProgressBar progressOpts = progressBarOptions();
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.save();
QRect rect = progressOpts.rect.adjusted(1,2,-4,-2);
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) {
leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
} else if (progressIndicatorPos > rect.width()) {
painter.setPen(palette().highlightedText().color());
} else {
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = rect;
rightRect = rightRect.subtracted(leftRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible())) {
painter.setClipRegion(rightRect);
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
painter.setClipping(false);
}
if (!leftRect.isNull()) {
painter.setClipRect(leftRect.adjusted(0, -1, 1, 1));
painter.setPen(palette().highlight().color());
painter.setBrush(palette().highlight());
spinOpts.palette.setBrush(QPalette::Base, palette().highlight());
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
if (!(d->edit && d->edit->isVisible())) {
painter.setPen(palette().highlightedText().color());
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
}
painter.setClipping(false);
}
painter.restore();
}
void KisAbstractSliderSpinBox::paintPlastique(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.save();
QRect rect = progressOpts.rect.adjusted(2,0,-2,0);
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) {
leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
} else if (progressIndicatorPos > rect.width()) {
painter.setPen(palette().highlightedText().color());
} else {
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = rect;
rightRect = rightRect.subtracted(leftRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible())) {
painter.setClipRegion(rightRect);
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
painter.setClipping(false);
}
if (!leftRect.isNull()) {
painter.setPen(palette().highlight().color());
painter.setBrush(palette().highlight());
painter.drawRect(leftRect.adjusted(0,0,0,-1));
if (!(d->edit && d->edit->isVisible())) {
painter.setPen(palette().highlightedText().color());
painter.setClipRect(leftRect.adjusted(0,0,1,0));
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
painter.setClipping(false);
}
}
painter.restore();
}
void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
QString valueText = progressOpts.text;
progressOpts.text = "";
progressOpts.rect.adjust(0, 1, 0, -1);
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this);
style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this);
painter.save();
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
qreal(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width()) {
leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height());
} else if (progressIndicatorPos > progressOpts.rect.width()) {
painter.setPen(palette().highlightedText().color());
} else {
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = progressOpts.rect;
rightRect = rightRect.subtracted(leftRect);
painter.setClipRegion(rightRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible())) {
painter.drawText(progressOpts.rect, valueText, textOption);
}
if (!leftRect.isNull()) {
painter.setPen(palette().highlightedText().color());
painter.setClipRect(leftRect);
style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this);
if (!(d->edit && d->edit->isVisible())) {
painter.drawText(progressOpts.rect, valueText, textOption);
}
}
painter.restore();
}
void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
//Depress buttons or highlight slider
//Also used to emulate mouse grab...
if (e->buttons() & Qt::LeftButton) {
if (upButtonRect(spinOpts).contains(e->pos())) {
d->upButtonDown = true;
} else if (downButtonRect(spinOpts).contains(e->pos())) {
d->downButtonDown = true;
}
} else if (e->buttons() & Qt::RightButton) {
showEdit();
}
update();
}
void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
d->isDragging = false;
//Step up/down for buttons
//Emualting mouse grab too
if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) {
setInternalValue(d->value + d->singleStep);
} else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) {
setInternalValue(d->value - d->singleStep);
} else if (progressRect(spinOpts).contains(e->pos()) &&
!(d->edit->isVisible()) &&
!(d->upButtonDown || d->downButtonDown)) {
//Snap to percentage for progress area
setInternalValue(valueForX(e->pos().x(),e->modifiers()));
} else { // Confirm the last known value, since we might be ignoring move events
setInternalValue(d->value);
}
d->upButtonDown = false;
d->downButtonDown = false;
update();
}
void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
if( e->modifiers() & Qt::ShiftModifier ) {
if( !d->shiftMode ) {
d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) );
d->shiftMode = true;
}
} else {
d->shiftMode = false;
}
//Respect emulated mouse grab.
if (e->buttons() & Qt::LeftButton &&
!(d->downButtonDown || d->upButtonDown)) {
d->isDragging = true;
setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag);
update();
}
}
void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
switch (e->key()) {
case Qt::Key_Up:
case Qt::Key_Right:
setInternalValue(d->value + d->singleStep);
break;
case Qt::Key_Down:
case Qt::Key_Left:
setInternalValue(d->value - d->singleStep);
break;
case Qt::Key_Shift:
d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) );
d->shiftMode = true;
break;
case Qt::Key_Enter: //Line edit isn't "accepting" key strokes...
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_AltGr:
case Qt::Key_Super_L:
case Qt::Key_Super_R:
break;
default:
showEdit();
d->edit->event(e);
break;
}
}
void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e)
{
Q_D(KisAbstractSliderSpinBox);
if ( e->delta() > 0) {
setInternalValue(d->value + d->singleStep);
} else {
setInternalValue(d->value - d->singleStep);
}
update();
e->accept();
}
bool KisAbstractSliderSpinBox::event(QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride){
QKeyEvent* key = static_cast<QKeyEvent*>(event);
if (key->modifiers() == Qt::NoModifier){
switch(key->key()){
case Qt::Key_Up:
case Qt::Key_Right:
case Qt::Key_Down:
case Qt::Key_Left:
event->accept();
return true;
default: break;
}
}
}
return QWidget::event(event);
}
void KisAbstractSliderSpinBox::commitEnteredValue()
{
Q_D(KisAbstractSliderSpinBox);
//QLocale locale;
bool ok = false;
//qreal value = locale.toDouble(d->edit->text(), &ok) * d->factor;
qreal value;
if (d->parseInt) {
value = KisNumericParser::parseIntegerMathExpr(d->edit->text(), &ok) * d->factor;
} else {
value = KisNumericParser::parseSimpleMathExpr(d->edit->text(), &ok) * d->factor;
}
if (ok) {
setInternalValue(value);
}
}
bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
if (recv == static_cast<QObject*>(d->edit) &&
e->type() == QEvent::KeyRelease) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
switch (keyEvent->key()) {
case Qt::Key_Enter:
case Qt::Key_Return: {
commitEnteredValue();
hideEdit();
return true;
}
case Qt::Key_Escape:
d->edit->setText(valueString());
hideEdit();
return true;
default:
break;
}
}
return false;
}
QSize KisAbstractSliderSpinBox::sizeHint() const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QFont ft(font());
if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) {
// Some styles use bold font in progressbars
// unfortunately there is no reliable way to check for that
ft.setBold(true);
}
QFontMetrics fm(ft);
QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size());
hint += QSize(0, 2);
switch (d->style) {
case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION:
hint += QSize(8, 8);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
hint += QSize(8, 0);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
hint += QSize(2, 0);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK:
// almost all "modern" styles have a margin around controls
hint += QSize(6, 6);
break;
default:
break;
}
//Getting the size of the buttons is a pain as the calcs require a rect
//that is "big enough". We run the calc twice to get the "smallest" buttons
//This code was inspired by QAbstractSpinBox
QSize extra(1000, 0);
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
hint += extra;
spinOpts.rect.setSize(hint);
return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint)
.expandedTo(QApplication::globalStrut());
}
QSize KisAbstractSliderSpinBox::minimumSizeHint() const
{
return sizeHint();
}
QSize KisAbstractSliderSpinBox::minimumSize() const
{
return QWidget::minimumSize().expandedTo(minimumSizeHint());
}
QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox opts;
opts.initFrom(this);
opts.frame = false;
opts.buttonSymbols = QAbstractSpinBox::UpDownArrows;
opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
//Disable non-logical buttons
if (d->value == d->minimum) {
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled;
} else if (d->value == d->maximum) {
opts.stepEnabled = QAbstractSpinBox::StepDownEnabled;
} else {
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
}
//Deal with depressed buttons
if (d->upButtonDown) {
opts.activeSubControls = QStyle::SC_SpinBoxUp;
} else if (d->downButtonDown) {
opts.activeSubControls = QStyle::SC_SpinBoxDown;
} else {
opts.activeSubControls = 0;
}
return opts;
}
QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
//Create opts for drawing the progress portion
QStyleOptionProgressBar progressOpts;
progressOpts.initFrom(this);
progressOpts.maximum = d->maximum;
progressOpts.minimum = d->minimum;
qreal minDbl = d->minimum;
qreal dValues = (d->maximum - minDbl);
progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl;
progressOpts.text = d->prefix + valueString() + d->suffix;
progressOpts.textAlignment = Qt::AlignCenter;
progressOpts.textVisible = !(d->edit->isVisible());
//Change opts rect to be only the ComboBox's text area
progressOpts.rect = progressRect(spinOpts);
return progressOpts;
}
QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
const Q_D(KisAbstractSliderSpinBox);
QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxEditField);
switch (d->style) {
case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
ret.adjust(-2, 0, 1, 0);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
ret.adjust(1, 0, 0, 0);
break;
default:
break;
}
return ret;
}
QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxUp);
}
QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxDown);
}
int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QRect correctedProgRect;
if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_FUSION) {
correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0);
}
else if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) {
correctedProgRect = progressRect(spinOpts);
}
else {
//Adjust for magic number in style code (margins)
correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2);
}
//Compute the distance of the progress bar, in pixel
qreal leftDbl = correctedProgRect.left();
qreal xDbl = x - leftDbl;
//Compute the ration of the progress bar used, linearly (ignoring the exponent)
qreal rightDbl = correctedProgRect.right();
qreal minDbl = d->minimum;
qreal maxDbl = d->maximum;
qreal dValues = (maxDbl - minDbl);
qreal percent = (xDbl / (rightDbl - leftDbl));
//If SHIFT is pressed, movement should be slowed.
if( modifiers & Qt::ShiftModifier ) {
percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor;
}
//Final value
- qreal realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl);
+ qreal exp_percent = pow(percent, d->exponentRatio);
+ qreal realvalue = ((dValues * (percent * exp_percent >= 0 ? exp_percent : -exp_percent)) + minDbl);
//If key CTRL is pressed, round to the closest step.
if( modifiers & Qt::ControlModifier ) {
qreal fstep = d->fastSliderStep;
if( modifiers & Qt::ShiftModifier ) {
fstep*=d->slowFactor;
}
realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep;
}
//Return the value
return int(realvalue);
}
void KisAbstractSliderSpinBox::setPrefix(const QString& prefix)
{
Q_D(KisAbstractSliderSpinBox);
d->prefix = prefix;
}
void KisAbstractSliderSpinBox::setSuffix(const QString& suffix)
{
Q_D(KisAbstractSliderSpinBox);
d->suffix = suffix;
}
void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl)
{
Q_D(KisAbstractSliderSpinBox);
Q_ASSERT(dbl > 0);
d->exponentRatio = dbl;
}
void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal)
{
Q_D(KisAbstractSliderSpinBox);
d->blockUpdateSignalOnDrag = blockUpdateSignal;
}
void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
event->accept();
}
void KisAbstractSliderSpinBox::editLostFocus()
{
Q_D(KisAbstractSliderSpinBox);
if (!d->edit->hasFocus()) {
commitEnteredValue();
hideEdit();
}
}
void KisAbstractSliderSpinBox::setInternalValue(int value)
{
setInternalValue(value, false);
}
bool KisAbstractSliderSpinBox::isDragging() const
{
Q_D(const KisAbstractSliderSpinBox);
return d->isDragging;
}
void KisAbstractSliderSpinBox::setPrivateValue(int value)
{
Q_D(KisAbstractSliderSpinBox);
d->value = qBound(d->minimum, value, d->maximum);
}
class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate {
};
KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate)
{
Q_D(KisSliderSpinBox);
d->parseInt = true;
setRange(0,99);
}
KisSliderSpinBox::~KisSliderSpinBox()
{
}
void KisSliderSpinBox::setRange(int minimum, int maximum)
{
Q_D(KisSliderSpinBox);
d->minimum = minimum;
d->maximum = maximum;
d->fastSliderStep = (maximum-minimum+1)/20;
d->validator->setRange(minimum, maximum, 0);
update();
}
int KisSliderSpinBox::minimum() const
{
const Q_D(KisSliderSpinBox);
return d->minimum;
}
void KisSliderSpinBox::setMinimum(int minimum)
{
Q_D(KisSliderSpinBox);
setRange(minimum, d->maximum);
}
int KisSliderSpinBox::maximum() const
{
const Q_D(KisSliderSpinBox);
return d->maximum;
}
void KisSliderSpinBox::setMaximum(int maximum)
{
Q_D(KisSliderSpinBox);
setRange(d->minimum, maximum);
}
int KisSliderSpinBox::fastSliderStep() const
{
const Q_D(KisSliderSpinBox);
return d->fastSliderStep;
}
void KisSliderSpinBox::setFastSliderStep(int step)
{
Q_D(KisSliderSpinBox);
d->fastSliderStep = step;
}
int KisSliderSpinBox::value()
{
Q_D(KisSliderSpinBox);
return d->value;
}
void KisSliderSpinBox::setValue(int value)
{
setInternalValue(value, false);
update();
}
QString KisSliderSpinBox::valueString() const
{
const Q_D(KisSliderSpinBox);
QLocale locale;
return locale.toString((qreal)d->value, 'f', d->validator->decimals());
}
void KisSliderSpinBox::setSingleStep(int value)
{
Q_D(KisSliderSpinBox);
d->singleStep = value;
}
void KisSliderSpinBox::setPageStep(int value)
{
Q_UNUSED(value);
}
void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
Q_D(KisAbstractSliderSpinBox);
d->value = qBound(d->minimum, _value, d->maximum);
if(!blockUpdateSignal) {
emit(valueChanged(value()));
}
}
class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate {
};
KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate)
{
Q_D(KisDoubleSliderSpinBox);
d->parseInt = false;
}
KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox()
{
}
void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals)
{
Q_D(KisDoubleSliderSpinBox);
d->factor = pow(10.0, decimals);
d->minimum = minimum * d->factor;
d->maximum = maximum * d->factor;
//This code auto-compute a new step when pressing control.
//A flag defaulting to "do not change the fast step" should be added, but it implies changing every call
if(maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers
d->fastSliderStep = int(pow(10.0, decimals));
} else if(decimals == 1) {
d->fastSliderStep = (maximum-minimum)*d->factor/10;
} else {
d->fastSliderStep = (maximum-minimum)*d->factor/20;
}
d->validator->setRange(minimum, maximum, decimals);
update();
setValue(value());
}
qreal KisDoubleSliderSpinBox::minimum() const
{
const Q_D(KisAbstractSliderSpinBox);
return d->minimum / d->factor;
}
void KisDoubleSliderSpinBox::setMinimum(qreal minimum)
{
Q_D(KisAbstractSliderSpinBox);
setRange(minimum, d->maximum);
}
qreal KisDoubleSliderSpinBox::maximum() const
{
const Q_D(KisAbstractSliderSpinBox);
return d->maximum / d->factor;
}
void KisDoubleSliderSpinBox::setMaximum(qreal maximum)
{
Q_D(KisAbstractSliderSpinBox);
setRange(d->minimum, maximum);
}
qreal KisDoubleSliderSpinBox::fastSliderStep() const
{
const Q_D(KisAbstractSliderSpinBox);
return d->fastSliderStep;
}
void KisDoubleSliderSpinBox::setFastSliderStep(qreal step)
{
Q_D(KisAbstractSliderSpinBox);
d->fastSliderStep = step;
}
qreal KisDoubleSliderSpinBox::value()
{
Q_D(KisAbstractSliderSpinBox);
return (qreal)d->value / d->factor;
}
void KisDoubleSliderSpinBox::setValue(qreal value)
{
Q_D(KisAbstractSliderSpinBox);
setInternalValue(d->value = qRound(value * d->factor), false);
update();
}
void KisDoubleSliderSpinBox::setSingleStep(qreal value)
{
Q_D(KisAbstractSliderSpinBox);
d->singleStep = value * d->factor;
}
QString KisDoubleSliderSpinBox::valueString() const
{
const Q_D(KisAbstractSliderSpinBox);
QLocale locale;
return locale.toString((qreal)d->value / d->factor, 'f', d->validator->decimals());
}
void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
Q_D(KisAbstractSliderSpinBox);
d->value = qBound(d->minimum, _value, d->maximum);
if(!blockUpdateSignal) {
emit(valueChanged(value()));
}
}
void KisAbstractSliderSpinBox::changeEvent(QEvent *e)
{
Q_D(KisAbstractSliderSpinBox);
QWidget::changeEvent(e);
switch (e->type()) {
case QEvent::StyleChange:
if (style()->objectName() == "fusion") {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_FUSION;
}
else if (style()->objectName() == "plastique") {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE;
}
else if (style()->objectName() == "breeze") {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE;
}
else {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK;
}
break;
default:
break;
}
}
diff --git a/libs/vectorimage/CMakeLists.txt b/libs/vectorimage/CMakeLists.txt
deleted file mode 100644
index d99c5862e1..0000000000
--- a/libs/vectorimage/CMakeLists.txt
+++ /dev/null
@@ -1,49 +0,0 @@
-include_directories(
- ${CMAKE_CURRENT_SOURCE_DIR}/libemf
- ${CMAKE_CURRENT_SOURCE_DIR}/libsvm
- ${CMAKE_CURRENT_SOURCE_DIR}/libwmf
-)
-
-set(vectorimage_LIB_SRCS
- libemf/EmfRecords.cpp
- libemf/EmfObjects.cpp
- libemf/EmfHeader.cpp
- libemf/BitmapHeader.cpp
- libemf/Bitmap.cpp
- libemf/EmfParser.cpp
- libemf/EmfOutput.cpp
- libemf/EmfOutputDebugStrategy.cpp
- libemf/EmfOutputPainterStrategy.cpp
-
- libsvm/SvmStructs.cpp
- libsvm/SvmGraphicsContext.cpp
- libsvm/SvmParser.cpp
- libsvm/SvmPainterBackend.cpp
-
- libwmf/WmfStack.cpp
- libwmf/WmfDeviceContext.cpp
- libwmf/WmfParser.cpp
- libwmf/WmfAbstractBackend.cpp
- libwmf/WmfPainterBackend.cpp
- libwmf/WmfWriter.cpp
-
- VectorImageDebug.cpp
-)
-
-add_library(kritavectorimage SHARED ${vectorimage_LIB_SRCS})
-generate_export_header(kritavectorimage BASE_NAME kritavectorimage)
-
-target_include_directories(kritavectorimage
- PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libemf>
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libwmf>
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libsvm>
-)
-
-
-target_link_libraries(kritavectorimage KF5::I18n Qt5::Gui Qt5::PrintSupport)
-
-set_target_properties(kritavectorimage PROPERTIES
- VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
-)
-install(TARGETS kritavectorimage ${INSTALL_TARGETS_DEFAULT_ARGS} )
diff --git a/libs/vectorimage/Mainpage.dox b/libs/vectorimage/Mainpage.dox
deleted file mode 100644
index 95795028f0..0000000000
--- a/libs/vectorimage/Mainpage.dox
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * \mainpage
- *
- * KritaVectorImage is a library bundling libemf, libsvm and libwmf.
- */
-
-// DOXYGEN_SET_PROJECT_NAME = KritaVectorImage
diff --git a/libs/vectorimage/VectorImageDebug.h b/libs/vectorimage/VectorImageDebug.h
deleted file mode 100644
index 6b19d6931a..0000000000
--- a/libs/vectorimage/VectorImageDebug.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-#ifndef VECTOR_IMAGE_DEBUG_H_
-#define VECTOR_IMAGE_DEBUG_H_
-
-#include <QDebug>
-#include <QLoggingCategory>
-#include <kritavectorimage_export.h>
-
-extern const KRITAVECTORIMAGE_EXPORT QLoggingCategory &VECTOR_IMAGE_LOG();
-
-#define debugVectorImage qCDebug(VECTOR_IMAGE_LOG)
-#define warnVectorImage qCWarning(VECTOR_IMAGE_LOG)
-#define errorVectorImage qCCritical(VECTOR_IMAGE_LOG)
-
-#endif
diff --git a/libs/vectorimage/libemf/Bitmap.cpp b/libs/vectorimage/libemf/Bitmap.cpp
deleted file mode 100644
index 1497961a6a..0000000000
--- a/libs/vectorimage/libemf/Bitmap.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright 2010 Inge Wallin <inge@lysator.liu.se>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "Bitmap.h"
-
-#include <QDataStream>
-#include <QImage>
-
-#include <VectorImageDebug.h>
-
-#include "EmfEnums.h"
-
-
-namespace Libemf
-{
-
-static void soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-
-Bitmap::Bitmap( QDataStream &stream,
- quint32 recordSize, // total size of the EMF record
- quint32 usedBytes, // used bytes of the EMF record before the bitmap part
- quint32 offBmiSrc, // offset to start of bitmapheader
- quint32 cbBmiSrc, // size of bitmap header
- quint32 offBitsSrc,// offset to source bitmap
- quint32 cbBitsSrc) // size of source bitmap
- : m_hasImage(false)
- , m_header(0)
- , m_imageIsValid(false)
-{
- // If necessary read away garbage before the bitmap header.
- if (offBmiSrc > usedBytes) {
- //debugVectorImage << "soaking " << offBmiSrc - usedBytes << "bytes before the header";
- soakBytes(stream, offBmiSrc - usedBytes);
- usedBytes = offBmiSrc;
- }
-
- // Create the header
- m_header = new BitmapHeader(stream, cbBmiSrc);
- usedBytes += cbBmiSrc;
-
- // If necessary read away garbage between the bitmap header and the picture.
- if (offBitsSrc > usedBytes) {
- //debugVectorImage << "soaking " << offBmiSrc - usedBytes << "bytes between the header and the image";
- soakBytes(stream, offBitsSrc - usedBytes);
- usedBytes = offBitsSrc;
- }
-
- // Read the image data
- if (cbBitsSrc > 0) {
- //debugVectorImage << "reading bitmap (" << cbBitsSrc << "bytes)";
- m_imageData.resize( cbBitsSrc );
- stream.readRawData( m_imageData.data(), cbBitsSrc );
- m_hasImage = true;
-
- usedBytes += cbBitsSrc;
- }
-
- // If necessary, read away garbage after the image.
- if (recordSize > usedBytes) {
- //debugVectorImage << "soaking " << recordSize - usedBytes << "bytes after the image";
- soakBytes(stream, recordSize - usedBytes);
- usedBytes = recordSize;
- }
-}
-
-Bitmap::~Bitmap()
-{
- delete m_header;
- //delete m_image;
-}
-
-
-#if 1
-QImage Bitmap::image()
-{
- if (!m_hasImage) {
- return QImage();
- }
-
- if (m_imageIsValid) {
- return m_image;
- }
-
- QImage::Format format = QImage::Format_Invalid;
-
- // Start by determining which QImage format we are going to use.
- if (m_header->bitCount() == BI_BITCOUNT_1) {
- format = QImage::Format_Mono;
- } else if ( m_header->bitCount() == BI_BITCOUNT_4 ) {
- if ( m_header->compression() == BI_RGB ) {
- format = QImage::Format_RGB555;
- } else {
- //debugVectorImage << "Unexpected compression format for BI_BITCOUNT_4:"
- // << m_header->compression();
- //Q_ASSERT( 0 );
- return QImage();
- }
- } else if ( m_header->bitCount() == BI_BITCOUNT_5 ) {
- format = QImage::Format_RGB888;
- } else if ( m_header->bitCount() == BI_BITCOUNT_6 ) {
- if (m_header->compression() == BI_RGB) {
- format = QImage::Format_RGB32;
- //format = QImage::Format_RGB888;
- } else if (m_header->compression() == BI_BITFIELDS) {
- // FIXME: The number of bits is correct, but we need to
- // handle the actual bits per colors specifically.
- //
- // The spec says (MS_WMF section 2.1.1.3):
- // If the Compression field of the BitmapInfoHeader
- // Object is set to BI_BITFIELDS, the Colors field
- // contains three DWORD color masks that specify the
- // red, green, and blue components, respectively, of
- // each pixel. Each DWORD in the bitmap array represents
- // a single pixel.
- format = QImage::Format_RGB32;
- }
- else
- return QImage();
- } else {
- //debugVectorImage << "Unexpected format:" << m_header->bitCount();
- //Q_ASSERT(0);
- return QImage();
- }
-
- // According to MS-WMF 2.2.2.3, the sign of the height decides if
- // this is a compressed bitmap or not.
- if (m_header->height() > 0) {
- // This bitmap is a top-down bitmap without compression.
- m_image = QImage( (const uchar*)m_imageData.constData(),
- m_header->width(), m_header->height(), format );
-
- // This is a workaround around a strange bug. Without this
- // code it shows nothing. Note that if we use Format_ARGB32
- // to begin with, nothing is shown anyway.
- //
- // FIXME: Perhaps it could be tested again with a later
- // version of Qt (I have 4.6.3) /iw
- if (m_header->bitCount() == BI_BITCOUNT_6
- && m_header->compression() == BI_RGB) {
- m_image = m_image.convertToFormat(QImage::Format_ARGB32);
- }
-
- // The WMF images are in the BGR color order.
- if (format == QImage::Format_RGB888)
- m_image = m_image.rgbSwapped();
-
- // We have to mirror this bitmap in the X axis since WMF images are stored bottom-up.
- m_image = m_image.mirrored(false, true);
- } else {
- // This bitmap is a bottom-up bitmap which uses compression.
- switch (m_header->compression()) {
- case BI_RGB:
- m_image = QImage( (const uchar*)m_imageData.constData(),
- m_header->width(), -m_header->height(), format );
- // The WMF images are in the BGR color order.
- m_image = m_image.rgbSwapped();
- break;
-
- // These compressions are not yet supported, so return an empty image.
- case BI_RLE8:
- case BI_RLE4:
- case BI_BITFIELDS:
- case BI_JPEG:
- case BI_PNG:
- case BI_CMYK:
- case BI_CMYKRLE8:
- case BI_CMYKRLE4:
- default:
- m_image = QImage(m_header->width(), m_header->height(), format);
- break;
- }
- }
-
- m_imageIsValid = true;
- return m_image;
-}
-#else
-QImage *Bitmap::image()
-{
- if (!m_hasImage) {
- return 0;
- }
-
- if (m_image) {
- return m_image;
- }
-
- QImage::Format format = QImage::Format_Invalid;
- if ( m_header->bitCount() == BI_BITCOUNT_4 ) {
- if ( m_header->compression() == 0x00 ) {
- format = QImage::Format_RGB555;
- } else {
- //debugVectorImage << "Unexpected compression format for BI_BITCOUNT_4:"
- // << m_header->compression();
- //Q_ASSERT( 0 );
- return 0;
- }
- } else if ( m_header->bitCount() == BI_BITCOUNT_5 ) {
- format = QImage::Format_RGB888;
- } else {
- debugVectorImage << "Unexpected format:" << m_header->bitCount();
- //Q_ASSERT( 0 );
- return 0;
- }
- m_image = new QImage( (const uchar*)m_imageData.constData(),
- m_header->width(), m_header->height(), format );
-
- return m_image;
-}
-#endif
-
-
-} // namespace Libemf
diff --git a/libs/vectorimage/libemf/Bitmap.h b/libs/vectorimage/libemf/Bitmap.h
deleted file mode 100644
index c6b4f40dcc..0000000000
--- a/libs/vectorimage/libemf/Bitmap.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2010 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFBITMAP_H
-#define EMFBITMAP_H
-
-
-#include <Qt> // For qint, etc.
-#include <QByteArray>
-#include <QImage>
-
-#include "BitmapHeader.h"
-
-
-class QDataStream;
-
-/**
- * @file
- *
- * definitions for Device Independent Bitmaps, as used in both WMF and EMF files.
-*/
-
-/// Namespace for Enhanced Metafile (EMF) classes
-
-namespace Libemf
-{
-
-/**
- * @class Bitmap
- *
- * Representation of a bitmap from an EMF file
- */
-class Bitmap
-{
-public:
- /**
- * Constructor
- *
- * The Bitmap structure is built from the specified stream.
- *
- * \param stream the data stream to read from.
- * \param recordSize the size of the EMF record that this bitmap is part of.
- * \param usedBytes number of already used bytes of the EMF record before the bitmap part
- * \param offBmiSrc offset to start of bitmapheader
- * \param cbBmiSrc size of bitmap header
- * \param offBitsSrc offset to source bitmap
- * \param cbBitsSrc size of source bitmap
- */
- Bitmap( QDataStream &stream,
- quint32 recordSize, // total size of the EMF record
- quint32 usedBytes, // used bytes of the EMF record before the bitmap part
- quint32 offBmiSrc, // offset to start of bitmapheader
- quint32 cbBmiSrc, // size of bitmap header
- quint32 offBitsSrc, // offset to source bitmap
- quint32 cbBitsSrc); // size of source bitmap
- ~Bitmap();
-
- /**
- The bitmap header
- */
- BitmapHeader *header() const { return m_header; };
-
- /**
- Return true if there is an image in this record.
- */
- bool hasImage() const { return m_hasImage; };
-
- /**
- The image.
-
- QImage shares its memory already.
- */
- QImage image();
-
-private:
- // No copying for now, because we will get into trouble with the pointers.
- // The remedy is to write a real operator=() and Bitmap(Bitmap&).
- explicit Bitmap(Bitmap&);
- Bitmap &operator=(Bitmap&);
-
-private:
- bool m_hasImage;
- BitmapHeader *m_header;
-
- QByteArray m_imageData;
- QImage m_image;
- bool m_imageIsValid;
-};
-
-
-
-} // namespace Libemf
-
-#endif
diff --git a/libs/vectorimage/libemf/BitmapHeader.cpp b/libs/vectorimage/libemf/BitmapHeader.cpp
deleted file mode 100644
index 5d4b5e682b..0000000000
--- a/libs/vectorimage/libemf/BitmapHeader.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2010 Inge Wallin <inge@lysator.liu.se>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "BitmapHeader.h"
-
-#include <QDataStream>
-//#include <QColor>
-//#include <QImage>
-//#include <QRect> // also provides QSize
-//#include <QString>
-
-#include <VectorImageDebug.h>
-
-
-namespace Libemf
-{
-
-static void soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-
-BitmapHeader::BitmapHeader( QDataStream &stream, int size )
-{
- m_headerType = BitmapInfoHeader; // The default
-
- int read = 40; // Keep track of how many bytes we have read;
-
- // Read the data that is present in a BitmapInfoHeader (size 40)
- stream >> m_headerSize;
- stream >> m_width;
- stream >> m_height;
- stream >> m_planes; // 16 bits
- stream >> m_bitCount; // 16 bits
- stream >> m_compression;
- stream >> m_imageSize;
-
- stream >> m_xPelsPerMeter;
- stream >> m_yPelsPerMeter;
- stream >> m_colorUsed;
- stream >> m_colorImportant;
-
-#if 0
- debugVectorImage << "Width:" << m_width;
- debugVectorImage << "Height:" << m_height;
- debugVectorImage << "planes:" << m_planes;
- debugVectorImage << "BitCount:" << m_bitCount;
- debugVectorImage << "Compression:" << m_compression;
- debugVectorImage << "ImageSize:" << m_imageSize;
- debugVectorImage << "Colors used:" << m_colorUsed;
-#endif
- // BitmapV4Header (size 40+68 = 108)
- if (size >= 108) {
- m_headerType = BitmapV4Header;
- read = 108;
-
- stream >> m_redMask;
- stream >> m_greenMask;
- stream >> m_blueMask;
- stream >> m_alphaMask;
- stream >> m_colorSpaceType;
-
- // FIXME sometime: Implement the real CIEXYZTriple
- for (int i = 0; i < 9; ++i)
- stream >> m_endpoints[i];
-
- stream >> m_gammaRed;
- stream >> m_gammaGreen;
- stream >> m_gammaBlue;
- }
-
- // BitmapV5Header (size 108+16 = 124)
- if (size >= 124) {
- m_headerType = BitmapV5Header;
- read = 124;
-
- stream >> m_intent;
- stream >> m_profileData;
- stream >> m_profileSize;
- stream >> m_reserved;
- }
-
-#if 0
- debugVectorImage << "header type:" << m_headerType;
- debugVectorImage << "header size:" << size;
- debugVectorImage << "read bytes: " << read;
-#endif
- // Read away the overshot from the size parameter;
- if (size > read)
- soakBytes(stream, size - read);
-}
-
-BitmapHeader::~BitmapHeader()
-{
-}
-
-
-
-} // namespace Libemf
diff --git a/libs/vectorimage/libemf/BitmapHeader.h b/libs/vectorimage/libemf/BitmapHeader.h
deleted file mode 100644
index 6087cc303c..0000000000
--- a/libs/vectorimage/libemf/BitmapHeader.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2010 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFBITMAPHEADER_H
-#define EMFBITMAPHEADER_H
-
-
-#include <Qt> // For qint, etc.
-
-class QDataStream;
-
-/**
- * @file
- *
- * definitions for Device Independent Bitmaps, as used in both WMF and EMF files.
-*/
-
-
-
-/// Namespace for Enhanced Metafile (EMF) classes
-
-namespace Libemf
-{
-
-/**
- * @class BitmapHeader
- *
- * Representation of a bitmap header, including the following formats:
- * - BitmapInfoHeader [MS-WMF].pdf 2.2.2.3
- * - BitmapV4Header [MS-WMF].pdf 2.2.2.4
- * - BitmapV5Header [MS-WMF].pdf 2.2.2.5
- */
-class BitmapHeader
-{
-public:
- typedef enum {
- //BitmapCoreHeader, Not yet supported
- BitmapInfoHeader,
- BitmapV4Header,
- BitmapV5Header
- } Type;
-
- /**
- * Constructor
- *
- * The header structure is built from the specified stream.
- *
- * \param stream the data stream to read from.
- * \param size the size of the header. This decides what type it is.
- */
- BitmapHeader( QDataStream &stream, int size );
- ~BitmapHeader();
-
- /**
- The width of the bitmap, in pixels
- */
- qint32 width() const { return m_width; };
-
- /**
- The height of the bitmap, in pixels
- */
- qint32 height() const { return m_height; };
-
- /**
- The number of bits that make up a pixel
-
- This is an enumerated type - see the BitCount enum
- */
- quint16 bitCount() const { return m_bitCount; };
-
- /**
- The type of compression used in the image
-
- This is an enumerated type
- */
- quint32 compression() const { return m_compression; };
-
-//private:
- Type m_headerType; /// Which header type that is represented.
-
- // Data to be found in a BitmapInfoHeader
- quint32 m_headerSize;
- qint32 m_width;
- qint32 m_height;
- quint16 m_planes;
- quint16 m_bitCount;
- quint32 m_compression;
- quint32 m_imageSize;
- qint32 m_xPelsPerMeter;
- qint32 m_yPelsPerMeter;
- quint32 m_colorUsed;
- quint32 m_colorImportant;
-
- // Additional data to be found in a BitmapV4Header
- quint32 m_redMask;
- quint32 m_greenMask;
- quint32 m_blueMask;
- quint32 m_alphaMask;
- quint32 m_colorSpaceType;
- quint32 m_endpoints[9]; // Actually a CIEXYZTriple
- qint32 m_gammaRed;
- qint32 m_gammaGreen;
- qint32 m_gammaBlue;
-
- // Additional data to be found in a BitmapV5Header
- quint32 m_intent;
- quint32 m_profileData;
- quint32 m_profileSize;
- quint32 m_reserved;
-};
-
-
-
-} // namespace Libemf
-
-#endif
diff --git a/libs/vectorimage/libemf/CMakeLists.txt b/libs/vectorimage/libemf/CMakeLists.txt
deleted file mode 100644
index e0f0673c7f..0000000000
--- a/libs/vectorimage/libemf/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-set(libemf_LIB_SRCS
- EmfRecords.cpp
- EmfObjects.cpp
- EmfHeader.cpp
- BitmapHeader.cpp
- EmfParser.cpp
- EmfOutput.cpp
- EmfOutputDebugStrategy.cpp
- EmfOutputPainterStrategy.cpp
-)
diff --git a/libs/vectorimage/libemf/Doxyfile.cmake b/libs/vectorimage/libemf/Doxyfile.cmake
deleted file mode 100644
index 5b57507630..0000000000
--- a/libs/vectorimage/libemf/Doxyfile.cmake
+++ /dev/null
@@ -1,1356 +0,0 @@
-# Doxyfile 1.5.5
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
-#
-# All text after a hash (#) is considered a comment and will be ignored
-# The format is:
-# TAG = value [value, ...]
-# For lists items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all
-# text before the first occurrence of this tag. Doxygen uses libiconv (or the
-# iconv built into libc) for the transcoding. See
-# http://www.gnu.org/software/libiconv for the list of possible encodings.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
-
-PROJECT_NAME = ${PROJECT_NAME}
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
-
-PROJECT_NUMBER =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
-
-OUTPUT_DIRECTORY = apidocs
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
-
-CREATE_SUBDIRS = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
-# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
-# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
-# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
-# and Ukrainian.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
-
-ABBREVIATE_BRIEF =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
-# description.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
-
-FULL_PATH_NAMES = YES
-
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip.
-
-STRIP_FROM_PATH =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful is your file systems
-# doesn't support long names like on DOS, Mac, or CD-ROM.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
-
-JAVADOC_AUTOBRIEF = YES
-
-# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
-# interpret the first line (until the first dot) of a Qt-style
-# comment as the brief description. If set to NO, the comments
-# will behave just like regular Qt-style comments (thus requiring
-# an explicit \brief command for a brief description.)
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the DETAILS_AT_TOP tag is set to YES then Doxygen
-# will output the detailed description near the top, like JavaDoc.
-# If set to NO, the detailed description appears after the member
-# documentation.
-
-DETAILS_AT_TOP = NO
-
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
-
-TAB_SIZE = 8
-
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
-
-ALIASES =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for
-# Java. For instance, namespaces will be presented as packages, qualified
-# scopes will look different, etc.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources only. Doxygen will then generate output that is more tailored for
-# Fortran.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for
-# VHDL.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
-# Doxygen will parse them like normal C++ but will assume all classes use public
-# instead of private inheritance when no explicit protection keyword is present.
-
-SIP_SUPPORT = NO
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
-
-SUBGROUPING = YES
-
-# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
-# is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically
-# be useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-
-TYPEDEF_HIDES_STRUCT = NO
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
-
-EXTRACT_ALL = NO
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
-
-EXTRACT_PRIVATE = NO
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
-
-EXTRACT_STATIC = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base
-# name of the file that contains the anonymous namespace. By default
-# anonymous namespace are hidden.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
-# documentation.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-
-CASE_SENSE_NAMES = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
-# hierarchy of group names into alphabetical order. If set to NO (the default)
-# the group names will appear in their defined order.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
-
-SORT_BY_SCOPE_NAME = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or define consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and defines in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
-# list will mention the files that were used to generate the documentation.
-
-SHOW_USED_FILES = YES
-
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES = NO
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
-
-FILE_VERSION_FILTER =
-
-#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
-
-QUIET = YES
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
-
-WARNINGS = YES
-
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be abled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
-
-WARN_NO_PARAMDOC = YES
-
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
-
-INPUT = ${CMAKE_CURRENT_SOURCE_DIR}
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
-# also the default input encoding. Doxygen uses libiconv (or the iconv built
-# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
-# the list of possible encodings.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
-# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
-
-FILE_PATTERNS = *.h *.doxy
-
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
-
-RECURSIVE = NO
-
-# The EXCLUDE tag can be used to specify files and/or directories that should
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
-# directories that are symbolic links (a Unix filesystem feature) are excluded
-# from the input.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
-
-EXCLUDE_PATTERNS =
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-
-EXCLUDE_SYMBOLS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
-
-EXAMPLE_PATTERNS =
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output. If FILTER_PATTERNS is specified, this tag will be
-# ignored.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
-# is applied to all files.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
-
-FILTER_SOURCE_FILES = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
-
-SOURCE_BROWSER = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES (the default)
-# then for each documented function all documented
-# functions referencing it will be listed.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES (the default)
-# then for each documented function all documented entities
-# called/used by that function will be listed.
-
-REFERENCES_RELATION = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
-# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
-# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code. Otherwise they will link to the documentstion.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
-
-ALPHABETICAL_INDEX = NO
-
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
-
-COLS_IN_ALPHA_INDEX = 5
-
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard header.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
-
-HTML_STYLESHEET =
-
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS = YES
-
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
-# of the generated HTML documentation.
-
-GENERATE_HTMLHELP = NO
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files
-# will be generated that can be used as input for Apple's Xcode 3
-# integrated development environment, introduced with OSX 10.5 (Leopard).
-# To create a documentation set, doxygen will generate a Makefile in the
-# HTML output directory. Running make will produce the docset in that
-# directory and running "make install" will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
-# it at startup.
-
-GENERATE_DOCSET = NO
-
-# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
-# feed. A documentation feed provides an umbrella under which multiple
-# documentation sets from a single provider (such as a company or product suite)
-# can be grouped.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
-# should uniquely identify the documentation set bundle. This should be a
-# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
-# will append .docset to the name.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded. For this to work a browser that supports
-# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
-# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
-# written to the html output directory.
-
-CHM_FILE =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
-
-HHC_LOCATION =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
-
-GENERATE_CHI = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
-
-TOC_EXPAND = NO
-
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
-
-DISABLE_INDEX = NO
-
-# This tag can be used to set the number of enum values (range [1..20])
-# that doxygen will group on one line in the generated HTML documentation.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
-# generated containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
-# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
-# probably better off using the HTML help feature.
-
-GENERATE_TREEVIEW = NO
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
-
-TREEVIEW_WIDTH = 250
-
-#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
-
-GENERATE_LATEX = YES
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, a4wide, letter, legal and
-# executive. If left blank a4wide will be used.
-
-PAPER_TYPE = a4wide
-
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
-
-LATEX_HEADER =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
-
-PDF_HYPERLINKS = YES
-
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
-# higher quality PDF documentation.
-
-USE_PDFLATEX = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
-
-LATEX_BATCHMODE = NO
-
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
-
-LATEX_HIDE_INDICES = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
-# replacements, missing definitions are set to their default value.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
-
-MAN_EXTENSION = .3
-
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
-
-XML_OUTPUT = xml
-
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_DTD =
-
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader. This is useful
-# if you want to understand what is going on. On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
-
-MACRO_EXPANSION = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
-
-EXPAND_ONLY_PREDEF = NO
-
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
-
-INCLUDE_PATH =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all function-like macros that are alone
-# on a line, have an all uppercase name, and do not end with a semicolon. Such
-# function macros are typically used for boiler-plate code, and will confuse
-# the parser if not removed.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths or
-# URLs. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen
-# is run, you must also specify the path to the tagfile here.
-
-TAGFILES = ${CMAKE_CURRENT_SOURCE_DIR}/qt4.tag
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
-
-ALLEXTERNALS = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
-
-EXTERNAL_GROUPS = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
-
-PERL_PATH = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option is superseded by the HAVE_DOT option below. This is only a
-# fallback. It is recommended to install and use dot, since it yields more
-# powerful graphs.
-
-CLASS_DIAGRAMS = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see
-# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH =
-
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
-
-HAVE_DOT = NO
-
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# the CLASS_DIAGRAMS tag to NO.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-
-UML_LOOK = NO
-
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
-
-TEMPLATE_RELATIONS = NO
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
-
-INCLUDE_GRAPH = YES
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH and HAVE_DOT options are set to YES then
-# doxygen will generate a call dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable call graphs
-# for selected functions only using the \callgraph command.
-
-CALL_GRAPH = NO
-
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
-# doxygen will generate a caller dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable caller
-# graphs for selected functions only using the \callergraph command.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are png, jpg, or gif
-# If left blank png will be used.
-
-DOT_IMAGE_FORMAT = png
-
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
-
-DOTFILE_DIRS =
-
-# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
-# nodes that will be shown in the graph. If the number of nodes in a graph
-# becomes larger than this value, doxygen will truncate the graph, which is
-# visualized by representing a node as a red box. Note that doxygen if the
-# number of direct children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
-# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-
-DOT_GRAPH_MAX_NODES = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is enabled by default, which results in a transparent
-# background. Warning: Depending on the platform used, enabling this option
-# may lead to badly anti-aliased labels on the edges of a graph (i.e. they
-# become hard to read).
-
-DOT_TRANSPARENT = YES
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
-
-DOT_MULTI_TARGETS = NO
-
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
-
-DOT_CLEANUP = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to the search engine
-#---------------------------------------------------------------------------
-
-# The SEARCHENGINE tag specifies whether or not a search engine should be
-# used. If set to NO the values of all tags below this one will be ignored.
-
-SEARCHENGINE = NO
diff --git a/libs/vectorimage/libemf/EmfEnums.h b/libs/vectorimage/libemf/EmfEnums.h
deleted file mode 100644
index 7dd16cee1e..0000000000
--- a/libs/vectorimage/libemf/EmfEnums.h
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009-2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFENUMS_H
-#define EMFENUMS_H
-
-#include <QDataStream>
-#include <QRect> // also provides QSize
-#include <QString>
-
-/**
- \file
-
- Enumerations used in various parts of EMF files
-*/
-
-
-// We need most of the WMF enums and flags as well in an EMF file.
-#include <WmfEnums.h>
-
-using namespace Libwmf;
-
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-/**
- Background fill mode
-
- See [MS-EMF] Section 2.1.4
-*/
-#if 0
- enum BackgroundMode {
- TRANSPARENT = 0x01, ///< Equivalent to Qt::TransparentMode
- OPAQUE = 0x02 ///< Equivalent to Qt::OpaqueMode
- };
-#else
- typedef Libwmf::WmfMixMode EmfBackgroundMode; // This is exactly the same.
-#endif
-
- /**
- Parameters for text output.
-
- See [MS-EMF] Section 2.1.11
- */
- enum TextOutOptions {
- //ETO_OPAQUE = 0x000002, // Already defined in WmfEnums.h
- //ETO_CLIPPED = 0x000004,
- //ETO_GLYPH_INDEX = 0x000010,
- //ETO_RTLREADING = 0x000080,
- ETO_NO_RECT = 0x000100,
- ETO_SMALL_CHARS = 0x000200,
- //ETO_NUMERICSLOCAL = 0x000400,
- //ETO_NUMERICSLATIN = 0x000800,
- ETO_IGNORELANGUAGE = 0x001000,
- //ETO_PDY = 0x002000,
- ETO_REVERSE_INDEX_MAP = 0x010000
- };
-
- /**
- Graphics mode, used to interpret shape data such as rectangles
-
- See [MS-EMF] Section 2.1.16
- */
- enum GraphicsMode {
- GM_COMPATIBLE = 0x01,
- GM_ADVANCED = 0x02
- };
-
- /**
- MapModes
-
- See [MS-EMF] Section 2.1.21
- */
- typedef enum {
- MM_TEXT = 0x01,
- MM_LOMETRIC = 0x02,
- MM_HIMETRIC = 0x03,
- MM_LOENGLISH = 0x04,
- MM_HIENGLISH = 0x05,
- MM_TWIPS = 0x06,
- MM_ISOTROPIC = 0x07,
- MM_ANISOTROPIC = 0x08
- } MapMode;
-
- /**
- World Transform modification modes
-
- See [MS-EMF] Section 2.1.24
- */
- enum ModifyWorldTransformMode {
- MWT_IDENTITY = 0x01,
- MWT_LEFTMULTIPLY = 0x02,
- MWT_RIGHTMULTIPLY = 0x03,
- MWT_SET = 0x04
- };
-
- /**
- Pen Styles
-
- See [MS-EMF] Section 2.1.25
- */
- enum PenStyle {
- PS_COSMETIC = 0x00000000,
- PS_ENDCAP_ROUND = 0x00000000,
- PS_JOIN_ROUND = 0x00000000,
- PS_SOLID = 0x00000000,
- PS_DASH = 0x00000001,
- PS_DOT = 0x00000002,
- PS_DASHDOT = 0x00000003,
- PS_DASHDOTDOT = 0x00000004,
- PS_NULL = 0x00000005,
- PS_INSIDEFRAME = 0x00000006,
- PS_USERSTYLE = 0x00000007,
- PS_ALTERNATE = 0x00000008,
- PS_ENDCAP_SQUARE = 0x00000100,
- PS_ENDCAP_FLAT = 0x00000200,
- PS_JOIN_BEVEL = 0x00001000,
- PS_JOIN_MITER = 0x00002000,
- PS_GEOMETRIC = 0x00010000
- };
-
- /**
- Stock Objects
-
- See [MS-EMF] Section 2.1.31
- */
- enum StockObject {
- WHITE_BRUSH = 0x80000000,
- LTGRAY_BRUSH = 0x80000001,
- GRAY_BRUSH = 0x80000002,
- DKGRAY_BRUSH = 0x80000003,
- BLACK_BRUSH = 0x80000004,
- NULL_BRUSH = 0x80000005,
- WHITE_PEN = 0x80000006,
- BLACK_PEN = 0x80000007,
- NULL_PEN = 0x80000008,
- OEM_FIXED_FONT = 0x8000000A,
- ANSI_FIXED_FONT = 0x8000000B,
- ANSI_VAR_FONT = 0x8000000C,
- SYSTEM_FONT = 0x8000000D,
- DEVICE_DEFAULT_FONT = 0x8000000E,
- DEFAULT_PALETTE = 0x8000000F,
- SYSTEM_FIXED_FONT = 0x80000010,
- DEFAULT_GUI_FONT = 0x80000011,
- DC_BRUSH = 0x80000012,
- DC_PEN = 0x80000013
- };
-
- /**
- Fill mode
-
- See [MS-EMF] Section 2.1.27
- */
- enum PolygonFillMode {
- ALTERNATE = 0x01, ///< Equivalent to Qt::OddEvenFill
- WINDING = 0x02 ///< Equivalent to Qt::WindingFill
- };
-
- /**
- Clipping region mode
-
- See [MS-EMF] Section 2.1.29
- */
- enum RegionMode {
- RGN_AND = 0x01, ///< Equivalent to Qt::IntersectClip
- RGN_OR = 0x02, ///< Equivalent to Qt::UniteClip
- RGN_XOR = 0x03,
- RGN_DIFF = 0x04,
- RGN_COPY = 0x05 ///< Equivalent to Qt::ReplaceClip
- };
-
- /**
- Comment type as defined for the EMR_COMMENT record.
-
- See [MS-EMF] section 2.3.3
- */
- enum CommentType {
- EMR_COMMENT_EMFSPOOL = 0x00000000,
- EMR_COMMENT_EMFPLUS = 0x2B464D45, // The string "EMF+"
- EMR_COMMENT_PUBLIC = 0x43494447,
-
- // The following value is not defined in [MS-EMF].pdf, but
- // according to google it means that the file was created by
- // Microsoft Graph. It is present in one test file
- // (Presentation_tips.ppt).
- EMR_COMMENT_MSGR = 0x5247534d // The string MSGR
- };
-}
-
-
-#endif
diff --git a/libs/vectorimage/libemf/EmfHeader.cpp b/libs/vectorimage/libemf/EmfHeader.cpp
deleted file mode 100644
index 1ba26808fb..0000000000
--- a/libs/vectorimage/libemf/EmfHeader.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfHeader.h"
-
-namespace Libemf
-{
-
-/*****************************************************************************/
-const quint32 ENHMETA_SIGNATURE = 0x464D4520;
-
-Header::Header( QDataStream &stream )
-{
- stream >> mType;
- stream >> mSize;
- stream >> mBounds;
- stream >> mFrame;
- stream >> mSignature;
- stream >> mVersion;
- stream >> mBytes;
- stream >> mRecords;
- stream >> mHandles;
- stream >> mReserved;
- stream >> m_nDescription;
- stream >> m_offDescription;
- stream >> m_nPalEntries;
- stream >> mDevice;
- stream >> mMillimeters;
- if ( ( ENHMETA_SIGNATURE == mSignature ) && ( m_nDescription != 0 ) ){
- // we have optional EmfDescription, but don't know how to read that yet.
- }
-
- // FIXME: We could need to read EmfMetafileHeaderExtension1 and
- // ..2 here but we have no example of that.
- soakBytes( stream, mSize - 88 );
-}
-
-Header::~Header()
-{
-}
-
-bool Header::isValid() const
-{
- return ( ( 0x00000001 == mType ) && ( ENHMETA_SIGNATURE == mSignature ) );
-}
-
-QRect Header::bounds() const
-{
- return mBounds;
-}
-
-QRect Header::frame() const
-{
- return mFrame;
-}
-
-QSize Header::device() const
-{
- return mDevice;
-}
-
-QSize Header::millimeters() const
-{
- return mMillimeters;
-}
-
-quint32 Header::recordCount() const
-{
- return mRecords;
-}
-
-void Header::soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-}
diff --git a/libs/vectorimage/libemf/EmfHeader.h b/libs/vectorimage/libemf/EmfHeader.h
deleted file mode 100644
index 9853b2c808..0000000000
--- a/libs/vectorimage/libemf/EmfHeader.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFHEADER_H
-#define EMFHEADER_H
-
-#include <QDataStream>
-#include <QRect> // also provides QSize
-#include <QString>
-
-/**
- \file
-
- Primary definitions for EMF Header record
-*/
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-/**
- Simple representation of an EMF File header
-
- See MS-EMF Section 2.3.4.2 for details
-*/
-class Header
-{
-public:
- /**
- Constructor for header
-
- \param stream the stream to read the header structure from
- */
- explicit Header(QDataStream &stream);
- ~Header();
-
- /**
- Check whether this is a valid EMF Header
- */
- bool isValid() const;
-
- /**
- The number of records in the metafile
- */
- quint32 recordCount() const;
-
- /**
- The bounding box of the file content, in device units
- */
- QRect bounds() const;
-
- /**
- The frame of the file content, in 0.01 mm units
- */
- QRect frame() const;
-
- QSize device() const;
- QSize millimeters() const;
-
-private:
- // Temporary hack to read some bytes.
- void soakBytes( QDataStream &stream, int numBytes );
-
- quint32 mType;
- quint32 mSize;
- QRect mBounds;
- QRect mFrame;
- quint32 mSignature;
- quint32 mVersion;
- quint32 mBytes;
- quint32 mRecords;
- quint16 mHandles;
- quint16 mReserved;
- quint32 m_nDescription;
- quint32 m_offDescription;
- quint32 m_nPalEntries;
- QSize mDevice; // this might need to be converted to something better
- QSize mMillimeters; // this might need to be converted to something better
-};
-
-}
-
-#endif
diff --git a/libs/vectorimage/libemf/EmfObjects.cpp b/libs/vectorimage/libemf/EmfObjects.cpp
deleted file mode 100644
index 7b31de5315..0000000000
--- a/libs/vectorimage/libemf/EmfObjects.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009,2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfObjects.h"
-
-#include <VectorImageDebug.h>
-
-
-namespace Libemf
-{
-
-
-// ================================================================
-// class EmrTextObject
-
-// See MS-EMF section 2.2.5.
-
-EmrTextObject::EmrTextObject( QDataStream &stream, quint32 size, TextType textType )
-{
- stream >> m_referencePoint;
- size -= 8;
- //debugVectorImage << "Text ref. point:" << m_referencePoint;
-
- stream >> m_charCount;
- size -= 4;
- //debugVectorImage << "Number of characters in string:" << m_charCount;
-
- stream >> m_offString;
- size -= 4;
-
- // 36 bytes for the body of the parent structure (EMR_EXTTEXTOUTA or EMR_EXTTEXTOUTW)
- // then parts of the EmrText structure
- quint32 offString = m_offString - 36 - 8 - 4 - 4;
-
- stream >> m_options;
- size -= 4;
- offString -= 4;
-
- stream >> m_rectangle;
- size -= 16;
- offString -= 16;
-
- stream >> m_offDx;
- size -= 4;
- offString -= 4;
- // as for offString. 36 bytes for parent, then the earlier parts of EmrText
- quint32 offDx = m_offDx - 36 - 8 - 4 - 4 - 4 - 16 - 4;
-
- soakBytes( stream, offString ); // skips over UndefinedSpace1.
- size -= offString;
- offDx -= offString;
-
- if ( textType == SixteenBitChars ) {
- m_textString = recordWChars( stream, m_charCount );
- size -= 2 * m_charCount;
- offDx -= 2 * m_charCount;
-
- // If the number of characters is uneven, then we need to soak 2
- // bytes to make it a full word.
- if (m_charCount & 0x01) {
- soakBytes( stream, 2 );
- size -= 2;
- offDx -= 2;
- }
- } else {
- m_textString = recordChars( stream, m_charCount );
- size -= m_charCount;
- offDx -= m_charCount;
-
- // If the number of characters is not a multiple of 4, then we need to soak some
- // bytes to make it a full word.
- int rest = m_charCount % 4;
- if (rest != 0) {
- soakBytes( stream, 4 - rest );
- size -= 4 - rest;
- offDx -= 4 - rest;
- }
- }
-
- // TODO: parse the spacing array
- soakBytes( stream, size );
-}
-
-EmrTextObject::~EmrTextObject()
-{
-}
-
-QPoint EmrTextObject::referencePoint() const
-{
- return m_referencePoint;
-}
-
-QString EmrTextObject::textString() const
-{
- return m_textString;
-}
-
-quint32 EmrTextObject::options() const
-{
- return m_options;
-}
-
-QRect EmrTextObject::rectangle() const
-{
- return m_rectangle;
-}
-
-QString EmrTextObject::recordWChars( QDataStream &stream, int numChars )
-{
- QString text;
- QChar myChar;
- for ( int i = 0; i < numChars; ++i ) {
- stream >> myChar;
- text.append( myChar );
- }
- return text;
-}
-
-QString EmrTextObject::recordChars( QDataStream &stream, int numChars )
-{
- QString text;
- quint8 myChar;
- for ( int i = 0; i < numChars; ++i ) {
- stream >> myChar;
- text.append( QChar( myChar ) );
- }
- return text;
-}
-
-void EmrTextObject::soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-
-}
diff --git a/libs/vectorimage/libemf/EmfObjects.h b/libs/vectorimage/libemf/EmfObjects.h
deleted file mode 100644
index c44bcb7781..0000000000
--- a/libs/vectorimage/libemf/EmfObjects.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009, 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFOBJECTS_H
-#define EMFOBJECTS_H
-
-#include <QDataStream>
-#include <QRect> // also provides QSize
-#include <QString>
-
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-// There are a number of objects defined in the EMF
-// specification. These objects are common to several different EMF
-// records. An example would be the EmfTextObject which is used in
-// among others the ExtTextOutA and ExtTextOutW records.
-//
-// All these objects are defined in sections 2.2.x in [MS-EMF].pdf
-//
-// Note: Not all objects in section 2.2 are implemented yet.
-
-
-// ================================================================
-// class EmrTextObject
-
-
-/**
- * Simple representation of an EmrText object
- *
- * See MS-EMF Section 2.2.5 for details
- */
-class EmrTextObject
-{
-public:
- /**
- The type of text to read
- */
- enum TextType { EightBitChars, SixteenBitChars };
-
- /**
- Constructor for EmrText object
-
- \param stream the stream to read the record structure from
- \param size the number of bytes in this record
- \param textType whether the text is normal (EightBitChars) or wide
- characters (SixteenBitChars)
- */
- EmrTextObject( QDataStream &stream, quint32 size, TextType textType );
- ~EmrTextObject();
-
- /// The reference point for the text output
- QPoint referencePoint() const;
-
- /// Options for rectangle
- quint32 options() const;
-
- // Clipping and/or opaquing rectangle
- QRect rectangle() const;
-
- /// The text to be output
- QString textString() const;
-
-private:
-
- QPoint m_referencePoint; // reference point used to position the string
- quint32 m_charCount; // number of characters (internal use only)
- quint32 m_offString; // offset to output string (internal use only)
- quint32 m_options; // specifies how to use the rectangle
- QRect m_rectangle; // clipping and/or opaquing rectangle
- quint32 m_offDx; // offset to intercharacter spacing array (internal use only)
- QString m_textString; // the text string to output
-
- // Convenience function to handle a 2-byte wide char stream
- QString recordWChars( QDataStream &stream, int numChars );
-
- // Convenience function to handle a 1-byte wide char stream
- QString recordChars( QDataStream &stream, int numChars );
-
- // Routine to throw away a specific number of bytes
- void soakBytes( QDataStream &stream, int numBytes );
-};
-
-}
-
-#endif
diff --git a/libs/vectorimage/libemf/EmfOutput.cpp b/libs/vectorimage/libemf/EmfOutput.cpp
deleted file mode 100644
index 613e4a5da4..0000000000
--- a/libs/vectorimage/libemf/EmfOutput.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfOutput.h"
-
-#include <math.h>
-
-namespace Libemf
-{
-
-
-
-} // xnamespace...
diff --git a/libs/vectorimage/libemf/EmfOutput.h b/libs/vectorimage/libemf/EmfOutput.h
deleted file mode 100644
index 101d5dd1e5..0000000000
--- a/libs/vectorimage/libemf/EmfOutput.h
+++ /dev/null
@@ -1,554 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFOUTPUT_H
-#define EMFOUTPUT_H
-
-#include "kritavectorimage_export.h"
-
-#include <QList>
-#include <QPainter>
-#include <QRect> // also provides QSize, QPoint
-#include <QString>
-#include <QVariant>
-
-#include "EmfEnums.h"
-#include "EmfHeader.h"
-#include "EmfRecords.h"
-
-/**
- \file
-
- Primary definitions for EMF output strategies
-*/
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-/**
- Abstract output strategy for EMF Parser
-*/
-class KRITAVECTORIMAGE_EXPORT AbstractOutput
-{
-public:
- AbstractOutput() {};
- virtual ~AbstractOutput() {};
-
- /**
- Initialisation routine
-
- \param header the EMF Header record
- */
- virtual void init( const Header *header ) = 0;
-
- /**
- Cleanup routine
-
- This function is called when the painting is done. Any
- initializations that are done in init() can be undone here if
- necessary.
-
- \param header the EMF Header record
- */
- virtual void cleanup( const Header *header ) = 0;
-
- /**
- Close-out routine
- */
- virtual void eof() = 0;
-
- /**
- Handler for the EMR_SETPIXELV record type
-
- This fills a specified pixel with a particular color
-
- \param point the point to fill
- \param red the red component of the color
- \param green the green component of the color
- \param blue the blue component of the color
- \param reserved reserved component of the color
- */
- virtual void setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved ) = 0;
-
- /**
- Handler for the EMR_CREATEPEN record type
-
- This creates a pen object (at position ihPen) using the specified parameters.
-
- \param ihPen the internal handle for the pen to be created
- \param penStyle the pen configuration (style, type) - see the PenStyle enumeration
- \param x the width of the pen
- \param y reserved value - ignore this
- \param red the red component of the pen color
- \param green the green component of the pen color
- \param blue the blue component of the pen color
- \param reserved reserved value - ignore this
- */
- virtual void createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y,
- quint8 red, quint8 green, quint8 blue, quint8 reserved ) = 0;
-
- /**
- Handler for the EMR_CREATEBRUSHINDIRECT record type
- */
- virtual void createBrushIndirect( quint32 ihBrush, quint32 BrushStyle, quint8 red,
- quint8 green, quint8 blue, quint8 reserved,
- quint32 BrushHatch ) = 0;
-
- virtual void createMonoBrush( quint32 ihBrush, Bitmap *bitmap ) = 0;
-
- /**
- Handler for the EMR_SETMAPMODE record type.
-
- From [MS-EMF]:\n
- <em>The EMR_SETMAPMODE record specifies the mapping mode of the
- playback device context. The mapping mode specifies the unit of
- measure used to transform page-space units into device-space
- units, and also specifies the orientation of the device's x and
- y axes.</em>
-
- The valid mapping modes are:
- - 0x01 (MM_TEXT): Each logical unit is mapped to one device pixel.
- - 0x02 (MM_LOMETRIC): Each logical unit is mapped to 0.1 millimeter
- - 0x03 (MM_HIMETRIC): Each logical unit is mapped to 0.01 millimeter.
- - 0x04 (MM_LOENGLISH): Each logical unit is mapped to 0.01 inch.
- - 0x05 (MM_HIENGLISH): Each logical unit is mapped to 0.001 inch.
- - 0x06 (MM_TWIPS): Each logical unit is mapped to one twentieth of a printer's point (1/1440 inch).
- - 0x07 (MM_ISOTROPIC): Logical units are mapped to arbitrary units with equally-scaled axes; that is, one unit along the x-axis is equal to one unit along the y-axis.
- - 0x08 (MM_ANISOTROPIC): Logical units are mapped to arbitrary units with arbitrarily scaled axes.
-
- Note that increasing x is to the right, and increasing y is up,
- except for MM_TEXT, where it is down.
-
- You can expect to get several calls to this function
- (e.g. MM_ANISOTROPIC, followed by MM_HIMETRIC). If you maintain
- state based on this call, you probably need to maintain the
- dimensions / direction separate from the isotropic /
- anisotropic state.
-
- \param mapMode the mapping mode value
- */
- virtual void setMapMode( const quint32 mapMode ) = 0;
-
- /**
- Handler for the EMR_SETMETARGN record type
- */
- virtual void setMetaRgn() = 0;
-
- /**
- Handler for the EMR_SETWINDOWORGEX record type
-
- \param origin the origin of the window in logical coordinates
- */
- virtual void setWindowOrgEx( const QPoint &origin ) = 0;
-
- /**
- Handler for the EMR_SETWINDOWEXTEX record type
-
- \param size the size of the window in logical coordinates
- */
- virtual void setWindowExtEx( const QSize &size ) = 0;
-
- /**
- Handler for the EMR_SETVIEWPORTORGEX record type
-
- \param origin the origin of the viewport in logical coordinates
- */
- virtual void setViewportOrgEx( const QPoint &origin ) = 0;
-
- /**
- Handler for the EMR_SETVIEWPORTEXTEX record type
-
- \param size the size of the viewport in logical coordinates
- */
- virtual void setViewportExtEx( const QSize &size ) = 0;
-
- /**
- Handler for the EMR_SETBKMODE record type
-
- \param backgroundMode the background fill mode
- */
- virtual void setBkMode( const quint32 backgroundMode ) = 0;
-
- /**
- Handler for the EMR_SETPOLYFILLMODE record type
-
- \param polyFillMode the fill mode
- */
- virtual void setPolyFillMode( const quint32 polyFillMode ) = 0;
-
- /**
- Handler for the EMR_SETLAYOUT record type
-
- \param layoutMode the layout mode
- */
- virtual void setLayout( const quint32 layoutMode ) = 0;
-
- /**
- Handler for the EMR_MODIFYWORLDTRANSFORM record type
-
- There are a range of modes:
- - 0x01 (MWT_IDENTIFY): Reset current world transform to identity matrix
- - 0x02 (MWT_LEFTMULTIPLY): Left multiply this matrix with current matrix.
- - 0x03 (MWT_RIGHTMULTIPLY): Right multiply current matrix with this matrix.
- - 0x04 (MWT_SET): Set the world transform.
-
- \param mode the mode to use.
- \param M11
- \param M12
- \param M21
- \param M22
- \param Dx
- \param Dy
- */
- virtual void modifyWorldTransform(quint32 mode, float M11, float M12,
- float M21, float M22, float Dx, float Dy ) = 0;
-
- /**
- Handler for the EMR_SETWORLDTRANSFORM record type
-
- \param M11
- \param M12
- \param M21
- \param M22
- \param Dx
- \param Dy
- */
- virtual void setWorldTransform( float M11, float M12, float M21,
- float M22, float Dx, float Dy ) = 0;
-
- /**
- Select a previously created (or stock) object
-
- \param ihObject the reference number for the object to select
- */
- virtual void selectObject( const quint32 ihObject ) = 0;
-
- /**
- Delete a previously created (or stock) object
-
- \param ihObject the reference number for the object to delete
- */
- virtual void deleteObject( const quint32 ihObject ) = 0;
-
- /**
- Handler for the EMR_ARC record type
-
- \param box the bounding box
- \param start the coordinates of the point that defines the first radial end point
- \param end the coordinates of the point that defines the second radial end point
- */
- virtual void arc( const QRect &box, const QPoint &start, const QPoint &end ) = 0;
-
- /**
- Handler for the EMR_CHORD record type
-
- \param box the bounding box
- \param start the coordinates of the point that defines the first radial end point
- \param end the coordinates of the point that defines the second radial end point
- */
- virtual void chord( const QRect &box, const QPoint &start, const QPoint &end ) = 0;
-
- /**
- Handler for the EMR_PIE record type
-
- \param box the bounding box
- \param start the coordinates of the point that defines the first radial end point
- \param end the coordinates of the point that defines the second radial end point
- */
- virtual void pie( const QRect &box, const QPoint &start, const QPoint &end ) = 0;
-
- /**
- Handler for the EMR_ELLIPSE record type
-
- \param box the bounding box for the ellipse
- */
- virtual void ellipse( const QRect &box ) = 0;
-
- /**
- Handler for the EMR_RECTANGLE record type
-
- \param box the bounding box for the rectangle
- */
- virtual void rectangle( const QRect &box ) = 0;
-
- /**
- Handler for the EMR_SETTEXTALIGN record type
-
- The textAlignMode is a bit mask, see [MS-WMF] Section 2.1.2.3 for
- values if the text has a horizontal baseline, [MS-WMF] Section
- 2.1.2.4 if the text has a vertical baseline.
-
- \param textAlignMode the text alignment mode
- */
- virtual void setTextAlign( const quint32 textAlignMode ) = 0;
-
- /**
- Handler for the EMR_SETTEXTCOLOR record type
-
- \param red the red component of the text color
- \param green the blue component of the text color
- \param blue the blue component of the text color
- \param reserved an unused value - ignore this
- */
- virtual void setTextColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved ) = 0;
-
- /**
- Handler for the EMR_SETBKCOLOR record type
-
- \param red the red component of the background color
- \param green the blue component of the background color
- \param blue the blue component of the background color
- \param reserved an unused value - ignore this
- */
- virtual void setBkColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved ) = 0;
-
- /**
- Handler for the EMR_EXTCREATEFONTINDIRECTW record type
-
- \param extCreateFontIndirectW the contents of the
- EMR_EXTCREATEFONTINDIRECTW record
- */
- virtual void extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW ) = 0;
-
- /**
- Handler for text rendering, as described in the
- EMR_EXTTEXTOUTW and EMR_EXTTEXTOUTA record types.
-
- \param bounds the bounds used for e.g. clipping
- \param textObject The object describing the text.
- */
- virtual void extTextOut( const QRect &bounds, const EmrTextObject &textObject ) = 0;
-
- /**
- Handler for the EMR_BEGINPATH record type
- */
- virtual void beginPath() = 0;
-
- /**
- Handler for the EMR_CLOSEFIGURE record type
- */
- virtual void closeFigure() = 0;
-
- /**
- Handler for the EMR_ENDPATH record type
- */
- virtual void endPath() = 0;
-
- /**
- Handler for the EMR_MOVETOEX record type
-
- \param x the X coordinate of the point to move to
- \param y the Y coordinate of the point to move to
- */
- virtual void moveToEx( const qint32 x, const qint32 y ) = 0;
-
- /**
- Handler for the EMR_SAVEDC record type
- */
- virtual void saveDC() = 0;
-
- /**
- Handler for the EMR_RESTOREDC record type
-
- \param savedDC the device context to restore to (always negative)
- */
- virtual void restoreDC( const qint32 savedDC ) = 0;
-
- /**
- Handler for the EMR_LINETO record type
-
- \param finishPoint the point to draw to
- */
- virtual void lineTo( const QPoint &finishPoint ) = 0;
-
- /**
- Handler for the EMR_ARCTO record type
-
- \param box the bounding box
- \param start the coordinates of the point that defines the first radial end point
- \param end the coordinates of the point that defines the second radial end point
- */
- virtual void arcTo( const QRect &box, const QPoint &start, const QPoint &end ) = 0;
-
- /**
- Handler for the EMR_POLYGON16 record type.
-
- This record type specifies how to output a multi-segment filled
- polygon.
-
- \param bounds the bounding rectangle for the line segment
- \param points the sequence of points that describe the polygon
- */
- virtual void polygon16( const QRect &bounds, const QList<QPoint> points ) = 0;
-
- /**
- Handler for the EMR_POLYLINE record type.
-
- This record type specifies how to output a multi-segment line
- (unfilled polyline).
-
- \param bounds the bounding rectangle for the line segments
- \param points the sequence of points that describe the line
-
- \note the line is not meant to be closed (i.e. do not connect
- the last point to the first point) or filled.
- */
- virtual void polyLine( const QRect &bounds, const QList<QPoint> points ) = 0;
-
- /**
- Handler for the EMR_POLYLINE16 record type.
-
- This record type specifies how to output a multi-segment line
- (unfilled polyline).
-
- \param bounds the bounding rectangle for the line segment
- \param points the sequence of points that describe the line
-
- \note the line is not meant to be closed (i.e. do not connect
- the last point to the first point) or filled.
- */
- virtual void polyLine16( const QRect &bounds, const QList<QPoint> points ) = 0;
-
- /**
- Handler for the EMR_POLYPOLYLINE16 record type.
-
- This record type specifies how to output a set of multi-segment line
- (unfilled polylines). Each vector in the list is a separate polyline
-
- \param bounds the bounding rectangle for the line segments
- \param points the sequence of points that describe the line
-
- \note the lines are not meant to be closed (i.e. do not connect
- the last point to the first point) or filled.
- */
- virtual void polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points ) = 0;
-
- /**
- Handler for the EMR_POLYPOLYGON16 record type.
-
- This record type specifies how to output a set of multi-segment polygons.
- Each vector in the list is a separate filled polygon.
-
- \param bounds the bounding rectangle for the polygons
- \param points the sequence of points that describe the polygons
- */
- virtual void polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points ) = 0;
-
- /**
- Handler for the EMR_POLYLINETO16 record type.
-
- This record type specifies how to output a multi-segment set of
- lines (unfilled).
-
- \param bounds the bounding rectangle for the bezier curves
- \param points the sequence of points that describe the curves
-
- \note the line is not meant to be closed (i.e. do not connect
- the last point to the first point) or filled.
- */
- virtual void polyLineTo16( const QRect &bounds, const QList<QPoint> points ) = 0;
-
- /**
- Handler for the EMR_POLYBEZIERO16 record type.
-
- This record type specifies how to output a multi-segment set of
- bezier curves (unfilled).
-
- \param bounds the bounding rectangle for the bezier curves
- \param points the sequence of points that describe the curves
-
- \note the line is not meant to be closed (i.e. do not connect
- the last point to the first point) or filled.
- */
- virtual void polyBezier16( const QRect &bounds, const QList<QPoint> points ) = 0;
-
- /**
- Handler for the EMR_POLYLINETO16 record type.
-
- This record type specifies how to output a multi-segment set of
- bezier curves (unfilled), starting at the current point.
-
- \param bounds the bounding rectangle for the bezier curves
- \param points the sequence of points that describe the curves
-
- \note the line is not meant to be closed (i.e. do not connect
- the last point to the first point) or filled.
- */
- virtual void polyBezierTo16( const QRect &bounds, const QList<QPoint> points ) = 0;
-
- /**
- Handler for the EMR_FILLPATH record type.
-
- \param bounds the bounding rectangle for the region to be filled.
- */
- virtual void fillPath( const QRect &bounds ) = 0;
-
- /**
- Handler for the EMR_STROKEANDFILLPATH record type.
-
- \param bounds the bounding rectangle for the region to be stroked / filled
- */
- virtual void strokeAndFillPath( const QRect &bounds ) = 0;
-
- /**
- Handler for the EMR_STROKEPATH record type.
-
- \param bounds the bounding rectangle for the region to be stroked
- */
- virtual void strokePath( const QRect &bounds ) = 0;
-
- /**
- Handler for the EMR_SETCLIPPATH record type.
-
- See [MS-EMF] Section 2.1.29 for valid ways to set the path.
-
- \param regionMode how to set the clipping path.
- */
- virtual void setClipPath( const quint32 regionMode ) = 0;
-
- /**
- Handler for the EMR_BITBLT record type
-
- \param bitBltRecord contents of the record type
- */
- virtual void bitBlt( BitBltRecord &bitBltRecord ) = 0;
-
- /**
- Handler for the EMR_STRETCHBLTMODE record type
-
- \param stretchMode the stretch mode
- */
- virtual void setStretchBltMode( const quint32 stretchMode ) = 0;
-
- /**
- Handler for the EMR_STRETCHDIBITS record type
-
- \param stretchDiBitsRecord contents of the record type
- */
- virtual void stretchDiBits( StretchDiBitsRecord &stretchDiBitsRecord ) = 0;
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libemf/EmfOutputDebugStrategy.cpp b/libs/vectorimage/libemf/EmfOutputDebugStrategy.cpp
deleted file mode 100644
index 0164250258..0000000000
--- a/libs/vectorimage/libemf/EmfOutputDebugStrategy.cpp
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009-2010 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfOutputDebugStrategy.h"
-
-#include <math.h>
-
-#include <VectorImageDebug.h>
-
-#include "EmfObjects.h"
-
-namespace Libemf
-{
-
-
-
-
-OutputDebugStrategy::OutputDebugStrategy()
-{
-}
-
-OutputDebugStrategy::~OutputDebugStrategy()
-{
-}
-
-void OutputDebugStrategy::init( const Header *header )
-{
- debugVectorImage << "Initialising OutputDebugStrategy";
- debugVectorImage << "image size:" << header->bounds().size();
-}
-
-void OutputDebugStrategy::cleanup( const Header *header )
-{
- debugVectorImage << "Cleanup OutputDebugStrategy";
- debugVectorImage << "image size:" << header->bounds().size();
-}
-
-void OutputDebugStrategy::eof()
-{
- debugVectorImage << "EMR_EOF";
-}
-
-void OutputDebugStrategy::setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved )
-{
- Q_UNUSED( reserved );
- debugVectorImage << "EMR_SETPIXELV:" << point << QColor( red, green, blue );
-}
-
-void OutputDebugStrategy::beginPath()
-{
- debugVectorImage << "EMR_BEGINPATH";
-}
-
-void OutputDebugStrategy::closeFigure()
-{
- debugVectorImage << "EMR_CLOSEFIGURE";
-}
-
-void OutputDebugStrategy::endPath()
-{
- debugVectorImage << "EMR_ENDPATH";
-}
-
-void OutputDebugStrategy::saveDC()
-{
- debugVectorImage << "EMR_SAVEDC";
-}
-
-void OutputDebugStrategy::restoreDC( qint32 savedDC )
-{
- debugVectorImage << "EMR_RESTOREDC" << savedDC;
-}
-
-void OutputDebugStrategy::setMetaRgn()
-{
- debugVectorImage << "EMR_SETMETARGN";
-}
-
-void OutputDebugStrategy::setWindowOrgEx( const QPoint &origin )
-{
- debugVectorImage << "EMR_SETWINDOWORGEX" << origin;
-}
-
-void OutputDebugStrategy::setWindowExtEx( const QSize &size )
-{
- debugVectorImage << "EMR_SETWINDOWEXTEX" << size;
-}
-
-void OutputDebugStrategy::setViewportOrgEx( const QPoint &origin )
-{
- debugVectorImage << "EMR_SETVIEWPORTORGEX" << origin;
-}
-
-void OutputDebugStrategy::setViewportExtEx( const QSize &size )
-{
- debugVectorImage << "EMR_SETVIEWPORTEXTEX" << size;
-}
-
-void OutputDebugStrategy::deleteObject( const quint32 ihObject )
-{
- debugVectorImage << "EMR_DELETEOBJECT:" << ihObject;
-}
-
-void OutputDebugStrategy::arc( const QRect &box, const QPoint &start, const QPoint &end )
-{
- debugVectorImage << "EMR_ARC" << box << start << end;
-}
-
-void OutputDebugStrategy::chord( const QRect &box, const QPoint &start, const QPoint &end )
-{
- debugVectorImage << "EMR_CHORD" << box << start << end;
-}
-
-void OutputDebugStrategy::pie( const QRect &box, const QPoint &start, const QPoint &end )
-{
- debugVectorImage << "EMR_PIE" << box << start << end;
-}
-
-void OutputDebugStrategy::ellipse( const QRect &box )
-{
- debugVectorImage << "EMR_ELLIPSE:" << box;
-}
-
-void OutputDebugStrategy::rectangle( const QRect &box )
-{
- debugVectorImage << "EMR_RECTANGLE:" << box;
-}
-
-void OutputDebugStrategy::modifyWorldTransform( quint32 mode, float M11, float M12,
- float M21, float M22, float Dx, float Dy )
-{
- debugVectorImage << "EMR_MODIFYWORLDTRANSFORM:" << mode << QTransform ( M11, M12, M21, M22, Dx, Dy );
-}
-
-void OutputDebugStrategy::setWorldTransform( float M11, float M12, float M21,
- float M22, float Dx, float Dy )
-{
- debugVectorImage << "EMR_SETWORLDTRANSFORM:" << QTransform ( M11, M12, M21, M22, Dx, Dy );
-}
-
-void OutputDebugStrategy::setMapMode( quint32 mapMode )
-{
- QString modeAsText;
- switch ( mapMode ) {
- case MM_TEXT:
- modeAsText = QString( "map mode - text" );
- break;
- case MM_LOMETRIC:
- modeAsText = QString( "map mode - lometric" );
- break;
- case MM_HIMETRIC:
- modeAsText = QString( "map mode - himetric" );
- break;
- case MM_LOENGLISH:
- modeAsText = QString( "map mode - loenglish" );
- break;
- case MM_HIENGLISH:
- modeAsText = QString( "map mode - hienglish" );
- break;
- case MM_TWIPS:
- modeAsText = QString( "map mode - twips" );
- break;
- case MM_ISOTROPIC:
- modeAsText = QString( "map mode - isotropic" );
- break;
- case MM_ANISOTROPIC:
- modeAsText = QString( "map mode - anisotropic" );
- break;
- default:
- modeAsText = QString( "unexpected map mode: %1").arg( mapMode );
- }
- debugVectorImage << "EMR_SETMAPMODE:" << modeAsText;
-
-}
-
-void OutputDebugStrategy::setBkMode( const quint32 backgroundMode )
-{
- if ( backgroundMode == TRANSPARENT ) {
- debugVectorImage << "EMR_SETBKMODE: Transparent";
- } else if ( backgroundMode == OPAQUE ) {
- debugVectorImage << "EMR_SETBKMODE: Opaque";
- } else {
- debugVectorImage << "EMR_SETBKMODE: Unexpected value -" << backgroundMode;
- Q_ASSERT( 0 );
- }
-}
-
-void OutputDebugStrategy::setPolyFillMode( const quint32 polyFillMode )
-{
- if ( polyFillMode == ALTERNATE ) {
- debugVectorImage << "EMR_SETPOLYFILLMODE: OddEvenFill";
- } else if ( polyFillMode == WINDING ) {
- debugVectorImage << "EMR_SETPOLYFILLMODE: WindingFill";
- } else {
- debugVectorImage << "EMR_SETPOLYFILLMODE: Unexpected value -" << polyFillMode;
- Q_ASSERT( 0 );
- }
-}
-
-void OutputDebugStrategy::setLayout( const quint32 layoutMode )
-{
- debugVectorImage << "EMR_SETLAYOUT:" << layoutMode;
-}
-
-void OutputDebugStrategy::extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW )
-{
- debugVectorImage << "EMR_CREATEFONTINDIRECTW:" << extCreateFontIndirectW.fontFace();
-}
-
-void OutputDebugStrategy::setTextAlign( const quint32 textAlignMode )
-{
- debugVectorImage << "EMR_SETTEXTALIGN:" << textAlignMode;
-}
-
-void OutputDebugStrategy::setTextColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved )
-{
- Q_UNUSED( reserved );
- debugVectorImage << "EMR_SETTEXTCOLOR" << QColor( red, green, blue );
-}
-
-void OutputDebugStrategy::setBkColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved )
-{
- Q_UNUSED( reserved );
- debugVectorImage << "EMR_SETBKCOLOR" << QColor( red, green, blue );
-}
-
-void OutputDebugStrategy::createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y,
- quint8 red, quint8 green, quint8 blue, quint8 reserved )
-{
- Q_UNUSED( y );
- Q_UNUSED( reserved );
-
- debugVectorImage << "EMR_CREATEPEN" << "ihPen:" << ihPen << ", penStyle:" << penStyle
- << "width:" << x << "color:" << QColor( red, green, blue );
-}
-
-void OutputDebugStrategy::createBrushIndirect( quint32 ihBrush, quint32 BrushStyle, quint8 red,
- quint8 green, quint8 blue, quint8 reserved,
- quint32 BrushHatch )
-{
- Q_UNUSED( reserved );
-
- debugVectorImage << "EMR_CREATEBRUSHINDIRECT:" << ihBrush << "style:" << BrushStyle
- << "Colour:" << QColor( red, green, blue ) << ", Hatch:" << BrushHatch;
-}
-
-void OutputDebugStrategy::createMonoBrush( quint32 ihBrush, Bitmap *bitmap )
-{
- debugVectorImage << "EMR_CREATEMONOBRUSH:" << ihBrush << "bitmap:" << bitmap;
-}
-
-void OutputDebugStrategy::selectObject( const quint32 ihObject )
-{
- debugVectorImage << "EMR_SELECTOBJECT" << ihObject;
-}
-
-void OutputDebugStrategy::extTextOut( const QRect &bounds, const EmrTextObject &textObject )
-{
- debugVectorImage << "EMR_EXTTEXTOUTW:" << bounds
- << textObject.referencePoint()
- << textObject.textString();
-}
-
-void OutputDebugStrategy::moveToEx( const qint32 x, const qint32 y )
-{
- debugVectorImage << "EMR_MOVETOEX" << QPoint( x, y );
-}
-
-void OutputDebugStrategy::lineTo( const QPoint &finishPoint )
-{
- debugVectorImage << "EMR_LINETO" << finishPoint;
-}
-
-void OutputDebugStrategy::arcTo( const QRect &box, const QPoint &start, const QPoint &end )
-{
- debugVectorImage << "EMR_ARCTO" << box << start << end;
-}
-
-void OutputDebugStrategy::polygon16( const QRect &bounds, const QList<QPoint> points )
-{
- debugVectorImage << "EMR_POLYGON16" << bounds << points;
-}
-
-void OutputDebugStrategy::polyLine( const QRect &bounds, const QList<QPoint> points )
-{
- debugVectorImage << "EMR_POLYLINE" << bounds << points;
-}
-
-void OutputDebugStrategy::polyLine16( const QRect &bounds, const QList<QPoint> points )
-{
- debugVectorImage << "EMR_POLYLINE16" << bounds << points;
-}
-
-void OutputDebugStrategy::polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points )
-{
- debugVectorImage << "EMR_POLYPOLYLINE16" << bounds << points;
-}
-
-void OutputDebugStrategy::polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points )
-{
- debugVectorImage << "EMR_POLYPOLYGON16" << bounds << points;
-}
-
-void OutputDebugStrategy::polyLineTo16( const QRect &bounds, const QList<QPoint> points )
-{
- debugVectorImage << "EMR_POLYLINETO16" << bounds << points;
-}
-
-void OutputDebugStrategy::polyBezier16( const QRect &bounds, const QList<QPoint> points )
-{
- debugVectorImage << "EMR_POLYBEZIER16" << bounds << points;
-}
-
-void OutputDebugStrategy::polyBezierTo16( const QRect &bounds, const QList<QPoint> points )
-{
- debugVectorImage << "EMR_POLYBEZIERTO16" << bounds << points;
-}
-
-void OutputDebugStrategy::fillPath( const QRect &bounds )
-{
- debugVectorImage << "EMR_FILLPATH" << bounds;
-}
-
-void OutputDebugStrategy::strokeAndFillPath( const QRect &bounds )
-{
- debugVectorImage << "EMR_STROKEANDFILLPATH" << bounds;
-}
-
-void OutputDebugStrategy::strokePath( const QRect &bounds )
-{
- debugVectorImage << "EMR_STROKEPATH" << bounds;
-}
-
-void OutputDebugStrategy::setClipPath( quint32 regionMode )
-{
- debugVectorImage << "EMR_SETCLIPPATH:" << regionMode;
-}
-
-void OutputDebugStrategy::bitBlt( BitBltRecord &bitBltRecord )
-{
- debugVectorImage << "EMR_BITBLT:" << bitBltRecord.destinationRectangle();
-}
-
-void OutputDebugStrategy::setStretchBltMode( const quint32 stretchMode )
-{
- switch ( stretchMode ) {
- case 0x01:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ANDSCANS";
- break;
- case 0x02:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ORSCANS";
- break;
- case 0x03:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_DELETESCANS";
- break;
- case 0x04:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_HALFTONE";
- break;
- default:
- debugVectorImage << "EMR_STRETCHBLTMODE - unknown stretch mode:" << stretchMode;
- }
-}
-
-void OutputDebugStrategy::stretchDiBits( StretchDiBitsRecord &stretchDiBitsRecord )
-{
- debugVectorImage << "EMR_STRETCHDIBITS:" << stretchDiBitsRecord.sourceRectangle()
- << "," << stretchDiBitsRecord.destinationRectangle();
-}
-
-
-} // xnamespace...
diff --git a/libs/vectorimage/libemf/EmfOutputDebugStrategy.h b/libs/vectorimage/libemf/EmfOutputDebugStrategy.h
deleted file mode 100644
index 6b21f6a4ff..0000000000
--- a/libs/vectorimage/libemf/EmfOutputDebugStrategy.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFOUTPUTDEBUGSTRATEGY_H
-#define EMFOUTPUTDEBUGSTRATEGY_H
-
-#include "kritavectorimage_export.h"
-
-#include <QList>
-#include <QPainter>
-#include <QRect> // also provides QSize, QPoint
-#include <QString>
-#include <QVariant>
-
-#include "EmfEnums.h"
-#include "EmfHeader.h"
-#include "EmfRecords.h"
-#include "EmfOutput.h"
-
-/**
- \file
-
- Contains definitions for an EMF debug output strategy
-*/
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-
-/**
- Debug (text dump) output strategy for EMF Parser
-*/
-class KRITAVECTORIMAGE_EXPORT OutputDebugStrategy : public AbstractOutput
-{
-public:
- OutputDebugStrategy();
- ~OutputDebugStrategy() override;
-
- void init( const Header *header ) override;
- void cleanup( const Header *header ) override;
- void eof() override;
-
- void createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y,
- quint8 red, quint8 green, quint8 blue, quint8 reserved ) override;
- void createBrushIndirect( quint32 ihBrush, quint32 BrushStyle, quint8 red,
- quint8 green, quint8 blue, quint8 reserved,
- quint32 BrushHatch ) override;
- void createMonoBrush( quint32 ihBrush, Bitmap *bitmap ) override;
- void selectObject( const quint32 ihObject ) override;
- void deleteObject( const quint32 ihObject ) override;
- void arc( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void chord( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void pie( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void ellipse( const QRect &box ) override;
- void rectangle( const QRect &box ) override;
- void setMapMode( const quint32 mapMode ) override;
- void setMetaRgn() override;
- void setWindowOrgEx( const QPoint &origin ) override;
- void setWindowExtEx( const QSize &size ) override;
- void setViewportOrgEx( const QPoint &origin ) override;
- void setViewportExtEx( const QSize &size ) override;
- void beginPath() override;
- void closeFigure() override;
- void endPath() override;
- void setBkMode( const quint32 backgroundMode ) override;
- void setPolyFillMode( const quint32 polyFillMode ) override;
- void setLayout( const quint32 layoutMode ) override;
- void extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW ) override;
- void setTextAlign( const quint32 textAlignMode ) override;
- void setTextColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved ) override;
- void setBkColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved ) override;
- void setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved ) override;
- void modifyWorldTransform( quint32 mode, float M11, float M12,
- float M21, float M22, float Dx, float Dy ) override;
- void setWorldTransform( float M11, float M12, float M21,
- float M22, float Dx, float Dy ) override;
- void extTextOut( const QRect &bounds, const EmrTextObject &textObject ) override;
- void moveToEx( const qint32 x, const qint32 y ) override;
- void saveDC() override;
- void restoreDC( const qint32 savedDC ) override;
- void lineTo( const QPoint &finishPoint ) override;
- void arcTo( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void polygon16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyLine( const QRect &bounds, const QList<QPoint> points ) override;
- void polyLine16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points ) override;
- void polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points ) override;
- void polyLineTo16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyBezier16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyBezierTo16( const QRect &bounds, const QList<QPoint> points ) override;
- void fillPath( const QRect &bounds ) override;
- void strokeAndFillPath( const QRect &bounds ) override;
- void strokePath( const QRect &bounds ) override;
- void setClipPath( const quint32 regionMode ) override;
- void bitBlt( BitBltRecord &bitBltRecord ) override;
- void setStretchBltMode( const quint32 stretchMode ) override;
- void stretchDiBits( StretchDiBitsRecord &stretchDiBitsRecord ) override;
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp
deleted file mode 100644
index e33b780867..0000000000
--- a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp
+++ /dev/null
@@ -1,1468 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 - 2010 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfOutputPainterStrategy.h"
-
-#include <math.h>
-
-#include <VectorImageDebug.h>
-
-#include "EmfObjects.h"
-
-
-#define DEBUG_EMFPAINT 0
-#define DEBUG_PAINTER_TRANSFORM 0
-
-namespace Libemf
-{
-
-
-static QPainter::CompositionMode rasteropToQtComposition(long rop);
-
-// ================================================================
-// Class OutputPainterStrategy
-
-
-OutputPainterStrategy::OutputPainterStrategy()
- : m_header( 0 )
- , m_path( 0 )
- , m_currentlyBuildingPath( false )
- , m_fillRule(Qt::OddEvenFill)
- , m_mapMode(MM_TEXT)
- , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT
- , m_currentCoords()
-{
- m_painter = 0;
- m_painterSaves = 0;
- m_outputSize = QSize();
- m_keepAspectRatio = true;
-}
-
-OutputPainterStrategy::OutputPainterStrategy(QPainter &painter, QSize &size,
- bool keepAspectRatio)
- : m_header( 0 )
- , m_path( 0 )
- , m_currentlyBuildingPath( false )
- , m_windowExtIsSet(false)
- , m_viewportExtIsSet(false)
- , m_windowViewportIsSet(false)
- , m_fillRule(Qt::OddEvenFill)
- , m_mapMode(MM_TEXT)
- , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT
- , m_currentCoords()
-{
- m_painter = &painter;
- m_painterSaves = 0;
- m_outputSize = size;
- m_keepAspectRatio = keepAspectRatio;
-}
-
-OutputPainterStrategy::~OutputPainterStrategy()
-{
- delete m_header;
- delete m_path;
-}
-
-void OutputPainterStrategy::paintBounds(const Header *header)
-{
- // The rectangle is in device coordinates.
- QRectF rect(header->bounds());
- m_painter->save();
-
- // Draw a simple cross in a rectangle to show the bounds.
- m_painter->setPen(QPen(QColor(172, 196, 206), 0));
- m_painter->drawRect(rect);
- m_painter->drawLine(rect.topLeft(), rect.bottomRight());
- m_painter->drawLine(rect.bottomLeft(), rect.topRight());
-
- m_painter->restore();
-}
-
-void OutputPainterStrategy::init( const Header *header )
-{
- // Save the header since we need the frame and bounds inside the drawing.
- m_header = new Header(*header);
-
- QSize headerBoundsSize = header->bounds().size();
-
-#if DEBUG_EMFPAINT
- debugVectorImage << "----------------------------------------------------------------------";
- debugVectorImage << "Shape size =" << m_outputSize.width() << m_outputSize.height() << " pt";
- debugVectorImage << "----------------------------------------------------------------------";
- debugVectorImage << "Boundary box (dev units) =" << header->bounds().x() << header->bounds().y()
- << header->bounds().width() << header->bounds().height();
- debugVectorImage << "Frame (phys size) =" << header->frame().x() << header->frame().y()
- << header->frame().width() << header->frame().height() << " *0.01 mm";
-
- debugVectorImage << "Device =" << header->device().width() << header->device().height();
- debugVectorImage << "Millimeters =" << header->millimeters().width()
- << header->millimeters().height();
-#endif
-
-#if DEBUG_PAINTER_TRANSFORM
- printPainterTransform("In init, before save:");
-#endif
-
- // This is restored in cleanup().
- m_painter->save();
-
- // Calculate how much the painter should be resized to fill the
- // outputSize with output.
- qreal scaleX = qreal( m_outputSize.width() ) / headerBoundsSize.width();
- qreal scaleY = qreal( m_outputSize.height() ) / headerBoundsSize.height();
- if ( m_keepAspectRatio ) {
- // Use the smaller value so that we don't get an overflow in
- // any direction.
- if ( scaleX > scaleY )
- scaleX = scaleY;
- else
- scaleY = scaleX;
- }
-#if DEBUG_EMFPAINT
- debugVectorImage << "scale = " << scaleX << ", " << scaleY;
-#endif
-
- // Transform the EMF object so that it fits in the shape. The
- // topleft of the EMF will be the top left of the shape.
- m_painter->scale( scaleX, scaleY );
- m_painter->translate(-header->bounds().left(), -header->bounds().top());
-#if DEBUG_PAINTER_TRANSFORM
- printPainterTransform("after fitting into shape");
-#endif
-
- // Save the scale so that we can use it when setting lineWidth.
- m_outputScale = (scaleX + scaleY) / 2;
-
- // Calculate translation if we should center the EMF in the
- // area and keep the aspect ratio.
-#if 0 // Should apparently be upper left. See bug 265868
- if ( m_keepAspectRatio ) {
- m_painter->translate((m_outputSize.width() / scaleX - headerBoundsSize.width()) / 2,
- (m_outputSize.height() / scaleY - headerBoundsSize.height()) / 2);
-#if DEBUG_PAINTER_TRANSFORM
- printPainterTransform("after translation for keeping center in the shape");
-#endif
- }
-#endif
-
- m_outputTransform = m_painter->transform();
- m_worldTransform = QTransform();
-
- // For calculations of window / viewport during the painting
- m_windowOrg = QPoint(0, 0);
- m_viewportOrg = QPoint(0, 0);
- m_windowExtIsSet = false;
- m_viewportExtIsSet = false;
- m_windowViewportIsSet = false;
-
-#if DEBUG_EMFPAINT
- paintBounds(header);
-#endif
-}
-
-void OutputPainterStrategy::cleanup( const Header *header )
-{
- Q_UNUSED( header );
-
-#if DEBUG_EMFPAINT
- if (m_painterSaves > 0)
- debugVectorImage << "WARNING: UNRESTORED DC's:" << m_painterSaves;
-#endif
-
- // Restore all the save()s that were done during the processing.
- for (int i = 0; i < m_painterSaves; ++i)
- m_painter->restore();
- m_painterSaves = 0;
-
- // Restore the painter to what it was before init() was called.
- m_painter->restore();
-}
-
-
-void OutputPainterStrategy::eof()
-{
-}
-
-void OutputPainterStrategy::setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue,
- quint8 reserved )
-{
- Q_UNUSED( reserved );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << point << red << green << blue;
-#endif
-
- m_painter->save();
-
- QPen pen;
- pen.setColor( QColor( red, green, blue ) );
- m_painter->setPen( pen );
- m_painter->drawPoint( point );
-
- m_painter->restore();
-}
-
-
-void OutputPainterStrategy::beginPath()
-{
-#if DEBUG_EMFPAINT
- debugVectorImage;
-#endif
-
- delete( m_path );
- m_path = new QPainterPath;
- m_currentlyBuildingPath = true;
-}
-
-void OutputPainterStrategy::closeFigure()
-{
-#if DEBUG_EMFPAINT
- debugVectorImage;
-#endif
-
- m_path->closeSubpath();
-}
-
-void OutputPainterStrategy::endPath()
-{
-#if DEBUG_EMFPAINT
- debugVectorImage;
-#endif
-
- m_path->setFillRule( m_fillRule );
- m_currentlyBuildingPath = false;
-}
-
-void OutputPainterStrategy::saveDC()
-{
-#if DEBUG_EMFPAINT
- debugVectorImage;
-#endif
-
- // A little trick here: Save the worldTransform in the painter.
- // If we didn't do this, we would have to create a separate stack
- // for these.
- //
- // FIXME: We should collect all the parts of the DC that are not
- // stored in the painter and save them separately.
- QTransform savedTransform = m_painter->worldTransform();
- m_painter->setWorldTransform(m_worldTransform);
-
- m_painter->save();
- ++m_painterSaves;
-
- m_painter->setWorldTransform(savedTransform);
-}
-
-void OutputPainterStrategy::restoreDC( const qint32 savedDC )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << savedDC;
-#endif
-
- // Note that savedDC is always negative
- for (int i = 0; i < -savedDC; ++i) {
- if (m_painterSaves > 0) {
- m_painter->restore();
- --m_painterSaves;
- }
- else {
- debugVectorImage << "restoreDC(): try to restore painter without save" << savedDC - i;
- break;
- }
- }
-
- // We used a trick in saveDC() and stored the worldTransform in
- // the painter. Now restore the full transformation.
- m_worldTransform = m_painter->worldTransform();
- QTransform newMatrix = m_worldTransform * m_outputTransform;
- m_painter->setWorldTransform( newMatrix );
-}
-
-void OutputPainterStrategy::setMetaRgn()
-{
- debugVectorImage << "EMR_SETMETARGN not yet implemented";
-}
-
-
-// ----------------------------------------------------------------
-// World Transform, Window and Viewport
-
-
-// General note about coordinate spaces and transforms:
-//
-// There are several coordinate spaces in use when drawing an EMF file:
-// 1. The object space, in which the objects' coordinates are expressed inside the EMF.
-// In general there are several of these.
-// 2. The page space, which is where they end up being painted in the EMF picture.
-// The union of these form the bounding box of the EMF.
-// 3. (possibly) the output space, where the EMF picture itself is placed
-// and/or scaled, rotated, etc
-//
-// The transform between spaces 1. and 2. is called the World Transform.
-// The world transform can be changed either through calls to change
-// the window or viewport or through calls to setWorldTransform() or
-// modifyWorldTransform().
-//
-// The transform between spaces 2. and 3. is the transform that the QPainter
-// already contains when it is given to us. We need to save this and reapply
-// it after the world transform has changed. We call this transform the Output
-// Transform in lack of a better word. (Some sources call it the Device Transform.)
-//
-
-// An unanswered question:
-//
-// The file mp07_embedded_ppt.pptx in the test files contains the
-// following sequence of records:
-// - SetWorldTransform
-// - ModifyWorldTransform
-// - SetViewportOrg <-- doesn't change anything
-// - SetWindowOrg <-- doesn't change anything
-// - ExtTextOutw
-//
-// I was previously under the impression that whenever a
-// Set{Window,Viewport}{Org,Ext} record was encountered, the world
-// transform was supposed to be recalculated. But in this file, it
-// destroys the world transform. The question is which of the
-// following alternatives is true:
-//
-// 1. The world transform should only be recalculated if the
-// Set{Window,Viewport}{Org,Ext} record actually changes anything.
-//
-// 2. The transformations set by {Set,Modify}WorldTransform records
-// should always be reapplied after a change in window or viewport.
-//
-// 3. Something else
-//
-// I have for now implemented option 1. See the FIXME's in
-// SetWindowOrg et al.
-//
-
-
-// Set Window and Viewport
-void OutputPainterStrategy::recalculateWorldTransform()
-{
- m_worldTransform = QTransform();
-
- // If neither the window nor viewport extension is set, then there
- // is no way to perform the calculation. Just give up.
- if (!m_windowExtIsSet && !m_viewportExtIsSet)
- return;
-
- // Negative window extensions mean flip the picture. Handle this here.
- bool flip = false;
- qreal midpointX = 0.0;
- qreal midpointY = 0.0;
- qreal scaleX = 1.0;
- qreal scaleY = 1.0;
- if (m_windowExt.width() < 0) {
- midpointX = m_windowOrg.x() + m_windowExt.width() / qreal(2.0);
- scaleX = -1.0;
- flip = true;
- }
- if (m_windowExt.height() < 0) {
- midpointY = m_windowOrg.y() + m_windowExt.height() / qreal(2.0);
- scaleY = -1.0;
- flip = true;
- }
- if (flip) {
- //debugVectorImage << "Flipping" << midpointX << midpointY << scaleX << scaleY;
- m_worldTransform.translate(midpointX, midpointY);
- m_worldTransform.scale(scaleX, scaleY);
- m_worldTransform.translate(-midpointX, -midpointY);
- //debugVectorImage << "After flipping for window" << mWorldTransform;
- }
-
- // Update the world transform if both window and viewport are set...
- // FIXME: Check windowExt == 0 in any direction
- if (m_windowExtIsSet && m_viewportExtIsSet) {
- // Both window and viewport are set.
- qreal windowViewportScaleX = qreal(m_viewportExt.width()) / qreal(m_windowExt.width());
- qreal windowViewportScaleY = qreal(m_viewportExt.height()) / qreal(m_windowExt.height());
-
- m_worldTransform.translate(-m_windowOrg.x(), -m_windowOrg.y());
- m_worldTransform.scale(windowViewportScaleX, windowViewportScaleY);
- m_worldTransform.translate(m_viewportOrg.x(), m_viewportOrg.y());
- }
-
- // ...and apply it to the painter
- m_painter->setWorldTransform(m_worldTransform);
- m_windowViewportIsSet = true;
-
- // Apply the output transform.
- QTransform newMatrix = m_worldTransform * m_outputTransform;
- m_painter->setWorldTransform( newMatrix );
-}
-
-
-void OutputPainterStrategy::setWindowOrgEx( const QPoint &origin )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << origin;
-#endif
-
- // FIXME: See unanswered question at the start of this section.
- if (m_windowOrg == origin) {
- //debugVectorImage << "same origin as before";
- return;
- }
-
- m_windowOrg = origin;
-
- recalculateWorldTransform();
-}
-
-void OutputPainterStrategy::setWindowExtEx( const QSize &size )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << size;
-#endif
-
- // FIXME: See unanswered question at the start of this section.
- if (m_windowExt == size) {
- //debugVectorImage << "same extension as before";
- return;
- }
-
- m_windowExt = size;
- m_windowExtIsSet = true;
-
- recalculateWorldTransform();
-}
-
-void OutputPainterStrategy::setViewportOrgEx( const QPoint &origin )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << origin;
-#endif
-
- // FIXME: See unanswered question at the start of this section.
- if (m_viewportOrg == origin) {
- //debugVectorImage << "same origin as before";
- return;
- }
-
- m_viewportOrg = origin;
-
- recalculateWorldTransform();
-}
-
-void OutputPainterStrategy::setViewportExtEx( const QSize &size )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << size;
-#endif
-
- // FIXME: See unanswered question at the start of this section.
- if (m_viewportExt == size) {
- //debugVectorImage << "same extension as before";
- return;
- }
-
- m_viewportExt = size;
- m_viewportExtIsSet = true;
-
- recalculateWorldTransform();
-}
-
-
-
-void OutputPainterStrategy::modifyWorldTransform( quint32 mode, float M11, float M12,
- float M21, float M22, float Dx, float Dy )
-{
-#if DEBUG_EMFPAINT
- if (mode == MWT_IDENTITY)
- debugVectorImage << "Identity matrix";
- else
- debugVectorImage << mode << M11 << M12 << M21 << M22 << Dx << Dy;
-#endif
-
- QTransform matrix( M11, M12, M21, M22, Dx, Dy);
-
- if ( mode == MWT_IDENTITY ) {
- m_worldTransform = QTransform();
- } else if ( mode == MWT_LEFTMULTIPLY ) {
- m_worldTransform = matrix * m_worldTransform;
- } else if ( mode == MWT_RIGHTMULTIPLY ) {
- m_worldTransform = m_worldTransform * matrix;
- } else if ( mode == MWT_SET ) {
- m_worldTransform = matrix;
- } else {
- warnVectorImage << "Unimplemented transform mode" << mode;
- }
-
- // Apply the output transform.
- QTransform newMatrix = m_worldTransform * m_outputTransform;
- m_painter->setWorldTransform( newMatrix );
-}
-
-void OutputPainterStrategy::setWorldTransform( float M11, float M12, float M21,
- float M22, float Dx, float Dy )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << M11 << M12 << M21 << M22 << Dx << Dy;
-#endif
-
- QTransform matrix( M11, M12, M21, M22, Dx, Dy);
-
- m_worldTransform = matrix;
-
- // Apply the output transform.
- QTransform newMatrix = m_worldTransform * m_outputTransform;
- m_painter->setWorldTransform( newMatrix );
-}
-
-
-// ----------------------------------------------------------------
-
-
-void OutputPainterStrategy::createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y,
- quint8 red, quint8 green, quint8 blue, quint8 reserved )
-{
- Q_UNUSED( y );
- Q_UNUSED( reserved );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << ihPen << hex << penStyle << dec << x << y
- << red << green << blue << reserved;
-#endif
-
- QPen pen;
- pen.setColor( QColor( red, green, blue ) );
-
- if ( penStyle & PS_GEOMETRIC ) {
- pen.setCosmetic( false );
- } else {
- pen.setCosmetic( true );
- }
-
- switch ( penStyle & 0xF ) {
- case PS_SOLID:
- pen.setStyle( Qt::SolidLine );
- break;
- case PS_DASH:
- pen.setStyle( Qt::DashLine );
- break;
- case PS_DOT:
- pen.setStyle( Qt::DotLine );
- break;
- case PS_DASHDOT:
- pen.setStyle( Qt::DashDotLine );
- break;
- case PS_DASHDOTDOT:
- pen.setStyle( Qt::DashDotDotLine );
- break;
- case PS_NULL:
- pen.setStyle( Qt::NoPen );
- break;
- case PS_INSIDEFRAME:
- // FIXME: We don't properly support this
- pen.setStyle( Qt::SolidLine );
- break;
- case PS_USERSTYLE:
- debugVectorImage << "UserStyle pen not yet supported, using SolidLine";
- pen.setStyle( Qt::SolidLine );
- break;
- case PS_ALTERNATE:
- debugVectorImage << "Alternate pen not yet supported, using DashLine";
- pen.setStyle( Qt::DashLine );
- break;
- default:
- debugVectorImage << "unexpected pen type, using SolidLine" << (penStyle & 0xF);
- pen.setStyle( Qt::SolidLine );
- }
-
- switch ( penStyle & PS_ENDCAP_FLAT ) {
- case PS_ENDCAP_ROUND:
- pen.setCapStyle( Qt::RoundCap );
- break;
- case PS_ENDCAP_SQUARE:
- pen.setCapStyle( Qt::SquareCap );
- break;
- case PS_ENDCAP_FLAT:
- pen.setCapStyle( Qt::FlatCap );
- break;
- default:
- debugVectorImage << "unexpected cap style, using SquareCap" << (penStyle & PS_ENDCAP_FLAT);
- pen.setCapStyle( Qt::SquareCap );
- }
- pen.setWidthF(x * m_outputScale);
-
- m_objectTable.insert( ihPen, pen );
-}
-
-void OutputPainterStrategy::createBrushIndirect( quint32 ihBrush, quint32 brushStyle,
- quint8 red, quint8 green, quint8 blue,
- quint8 reserved,
- quint32 brushHatch )
-{
- Q_UNUSED( reserved );
- Q_UNUSED( brushHatch );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << ihBrush << hex << brushStyle << dec
- << red << green << blue << reserved << brushHatch;
-#endif
-
- QBrush brush;
-
- switch ( brushStyle ) {
- case BS_SOLID:
- brush.setStyle( Qt::SolidPattern );
- break;
- case BS_NULL:
- brush.setStyle( Qt::NoBrush );
- break;
- case BS_HATCHED:
- brush.setStyle( Qt::CrossPattern );
- break;
- case BS_PATTERN:
- Q_ASSERT( 0 );
- break;
- case BS_INDEXED:
- Q_ASSERT( 0 );
- break;
- case BS_DIBPATTERN:
- Q_ASSERT( 0 );
- break;
- case BS_DIBPATTERNPT:
- Q_ASSERT( 0 );
- break;
- case BS_PATTERN8X8:
- Q_ASSERT( 0 );
- break;
- case BS_DIBPATTERN8X8:
- Q_ASSERT( 0 );
- break;
- case BS_MONOPATTERN:
- Q_ASSERT( 0 );
- break;
- default:
- Q_ASSERT( 0 );
- }
-
- brush.setColor( QColor( red, green, blue ) );
-
- // TODO: Handle the BrushHatch enum.
-
- m_objectTable.insert( ihBrush, brush );
-}
-
-void OutputPainterStrategy::createMonoBrush( quint32 ihBrush, Bitmap *bitmap )
-{
-
- QImage pattern(bitmap->image());
- QBrush brush(pattern);
-
- m_objectTable.insert( ihBrush, brush );
-}
-
-
-void OutputPainterStrategy::extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW )
-{
- QFont font( extCreateFontIndirectW.fontFace() );
-
- font.setWeight( convertFontWeight( extCreateFontIndirectW.weight() ) );
-
- if ( extCreateFontIndirectW.height() < 0 ) {
- font.setPixelSize( -1 * extCreateFontIndirectW.height() );
- } else if ( extCreateFontIndirectW.height() > 0 ) {
- font.setPixelSize( extCreateFontIndirectW.height() );
- } // zero is "use a default size" which is effectively no-op here.
-
- // .snp files don't always provide 0x01 for italics
- if ( extCreateFontIndirectW.italic() != 0x00 ) {
- font.setItalic( true );
- }
-
- if ( extCreateFontIndirectW.underline() != 0x00 ) {
- font.setUnderline( true );
- }
-
- m_objectTable.insert( extCreateFontIndirectW.ihFonts(), font );
-}
-
-void OutputPainterStrategy::selectStockObject( const quint32 ihObject )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << ihObject;
-#endif
-
- switch ( ihObject ) {
- case WHITE_BRUSH:
- m_painter->setBrush( QBrush( Qt::white ) );
- break;
- case LTGRAY_BRUSH:
- m_painter->setBrush( QBrush( Qt::lightGray ) );
- break;
- case GRAY_BRUSH:
- m_painter->setBrush( QBrush( Qt::gray ) );
- break;
- case DKGRAY_BRUSH:
- m_painter->setBrush( QBrush( Qt::darkGray ) );
- break;
- case BLACK_BRUSH:
- m_painter->setBrush( QBrush( Qt::black ) );
- break;
- case NULL_BRUSH:
- m_painter->setBrush( QBrush() );
- break;
- case WHITE_PEN:
- m_painter->setPen( QPen( Qt::white ) );
- break;
- case BLACK_PEN:
- m_painter->setPen( QPen( Qt::black ) );
- break;
- case NULL_PEN:
- m_painter->setPen( QPen( Qt::NoPen ) );
- break;
- case OEM_FIXED_FONT:
- case ANSI_FIXED_FONT:
- case SYSTEM_FIXED_FONT:
- {
- QFont font(QString("Fixed"));
- m_painter->setFont(font);
- break;
- }
- case ANSI_VAR_FONT:
- case DEFAULT_GUI_FONT: // Not sure if this is true, but it should work well
- {
- QFont font(QString("Helvetica")); // Could also be "System"
- m_painter->setFont(font);
- break;
- }
- break;
- case SYSTEM_FONT:
- // TODO: handle this
- break;
- case DEVICE_DEFAULT_FONT:
- // TODO: handle this
- break;
- case DEFAULT_PALETTE:
- break;
- case DC_BRUSH:
- // FIXME
- break;
- case DC_PEN:
- // FIXME
- break;
- default:
- warnVectorImage << "Unexpected stock object:" << ( ihObject & 0x8000000 );
- }
-}
-
-void OutputPainterStrategy::selectObject( const quint32 ihObject )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << hex << ihObject << dec;
-#endif
-
- if ( ihObject & 0x80000000 ) {
- selectStockObject( ihObject );
- } else {
- QVariant obj = m_objectTable.value( ihObject );
-
- switch ( obj.type() ) {
- case QVariant::Pen :
- m_painter->setPen( obj.value<QPen>() );
- break;
- case QVariant::Brush :
- m_painter->setBrush( obj.value<QBrush>() );
- break;
- case QVariant::Font :
- m_painter->setFont( obj.value<QFont>() );
- break;
- default:
- debugVectorImage << "Unexpected type:" << obj.typeName();
- }
- }
-}
-
-void OutputPainterStrategy::deleteObject( const quint32 ihObject )
-{
- m_objectTable.take( ihObject );
-}
-
-void OutputPainterStrategy::arc( const QRect &box, const QPoint &start, const QPoint &end )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << box << start << end;
-#endif
-
- QPoint centrePoint = box.center();
-
- qreal startAngle = angleFromArc( centrePoint, start );
- qreal endAngle = angleFromArc( centrePoint, end );
- qreal spanAngle = angularSpan( startAngle, endAngle );
-
- m_painter->drawArc( box, startAngle*16, spanAngle*16 );
-}
-
-void OutputPainterStrategy::chord( const QRect &box, const QPoint &start, const QPoint &end )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << box << start << end;
-#endif
-
- QPoint centrePoint = box.center();
-
- qreal startAngle = angleFromArc( centrePoint, start );
- qreal endAngle = angleFromArc( centrePoint, end );
- qreal spanAngle = angularSpan( startAngle, endAngle );
-
- m_painter->drawChord( box, startAngle*16, spanAngle*16 );
-}
-
-void OutputPainterStrategy::pie( const QRect &box, const QPoint &start, const QPoint &end )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << box << start << end;
-#endif
-
- QPoint centrePoint = box.center();
-
- qreal startAngle = angleFromArc( centrePoint, start );
- qreal endAngle = angleFromArc( centrePoint, end );
- qreal spanAngle = angularSpan( startAngle, endAngle );
-
- m_painter->drawPie( box, startAngle*16, spanAngle*16 );
-}
-
-void OutputPainterStrategy::ellipse( const QRect &box )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << box;
-#endif
-
- m_painter->drawEllipse( box );
-}
-
-void OutputPainterStrategy::rectangle( const QRect &box )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << box;
-#endif
-
- m_painter->drawRect( box );
-}
-
-void OutputPainterStrategy::setMapMode( const quint32 mapMode )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << "Set map mode:" << mapMode;
-#endif
-
- m_mapMode = (MapMode)mapMode;
-}
-
-void OutputPainterStrategy::setBkMode( const quint32 backgroundMode )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << backgroundMode;
-#endif
-
- if ( backgroundMode == TRANSPARENT ) {
- m_painter->setBackgroundMode( Qt::TransparentMode );
- } else if ( backgroundMode == OPAQUE ) {
- m_painter->setBackgroundMode( Qt::OpaqueMode );
- } else {
- debugVectorImage << "EMR_SETBKMODE: Unexpected value -" << backgroundMode;
- Q_ASSERT( 0 );
- }
-}
-
-void OutputPainterStrategy::setPolyFillMode( const quint32 polyFillMode )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << polyFillMode;
-#endif
-
- if ( polyFillMode == ALTERNATE ) {
- m_fillRule = Qt::OddEvenFill;
- } else if ( polyFillMode == WINDING ) {
- m_fillRule = Qt::WindingFill;
- } else {
- debugVectorImage << "EMR_SETPOLYFILLMODE: Unexpected value -" << polyFillMode;
- Q_ASSERT( 0 );
- }
-}
-
-void OutputPainterStrategy::setLayout( const quint32 layoutMode )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << layoutMode;
-#endif
-
- if ( layoutMode == LAYOUT_LTR ) {
- m_painter->setLayoutDirection( Qt::LeftToRight );
- } else if ( layoutMode == LAYOUT_RTL ) {
- m_painter->setLayoutDirection( Qt::RightToLeft );
- } else {
- debugVectorImage << "EMR_SETLAYOUT: Unexpected value -" << layoutMode;
- Q_ASSERT( 0 );
- }
-}
-
-void OutputPainterStrategy::setTextAlign( const quint32 textAlignMode )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << textAlignMode;
-#endif
-
- m_textAlignMode = textAlignMode;
-}
-
-void OutputPainterStrategy::setTextColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved )
-{
- Q_UNUSED( reserved );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << red << green << blue << reserved;
-#endif
-
- m_textPen.setColor( QColor( red, green, blue ) );
-}
-
-void OutputPainterStrategy::setBkColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved )
-{
- Q_UNUSED( reserved );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << red << green << blue << reserved;
-#endif
-
- m_painter->setBackground( QBrush( QColor( red, green, blue ) ) );
-}
-
-
-#define DEBUG_TEXTOUT 0
-
-void OutputPainterStrategy::extTextOut( const QRect &bounds, const EmrTextObject &textObject )
-{
- const QPoint &referencePoint = textObject.referencePoint();
- const QString &text = textObject.textString();
-
-#if DEBUG_EMFPAINT
- debugVectorImage << "Ref point: " << referencePoint
- << "options: " << hex << textObject.options() << dec
- << "rectangle: " << textObject.rectangle()
- << "text: " << textObject.textString();
-#endif
-
- int x = referencePoint.x();
- int y = referencePoint.y();
-
- // The TA_UPDATECP flag tells us to use the current position
- if (m_textAlignMode & TA_UPDATECP) {
- // (left, top) position = current logical position
-#if DEBUG_EMFPAINT
- debugVectorImage << "TA_UPDATECP: use current logical position";
-#endif
- x = m_currentCoords.x();
- y = m_currentCoords.y();
- }
-
- QFontMetrics fm = m_painter->fontMetrics();
- int textWidth = fm.width(text) + fm.descent(); // fm.width(text) isn't right with Italic text
- int textHeight = fm.height();
-
- // Make (x, y) be the coordinates of the upper left corner of the
- // rectangle surrounding the text.
- //
- // FIXME: Handle RTL text.
-
- // Horizontal align. Default is TA_LEFT.
- if ((m_textAlignMode & TA_HORZMASK) == TA_CENTER)
- x -= (textWidth / 2);
- else if ((m_textAlignMode & TA_HORZMASK) == TA_RIGHT)
- x -= textWidth;
-
- // Vertical align. Default is TA_TOP
- if ((m_textAlignMode & TA_VERTMASK) == TA_BASELINE)
- y -= (textHeight - fm.descent());
- else if ((m_textAlignMode & TA_VERTMASK) == TA_BOTTOM) {
- y -= textHeight;
- }
-
-#if DEBUG_EMFPAINT
- debugVectorImage << "textWidth = " << textWidth << "height = " << textHeight;
-
- debugVectorImage << "font = " << m_painter->font()
- << "pointSize = " << m_painter->font().pointSize()
- << "ascent = " << fm.ascent() << "descent = " << fm.descent()
- << "height = " << fm.height()
- << "leading = " << fm.leading();
- debugVectorImage << "actual point = " << x << y;
-#endif
-
- // Debug code that paints a rectangle around the output area.
-#if DEBUG_TEXTOUT
- m_painter->save();
- m_painter->setWorldTransform(m_outputTransform);
- m_painter->setPen(QPen(Qt::black, 0));
- m_painter->drawRect(bounds);
- m_painter->restore();
-#endif
-
- // Actual painting starts here.
- m_painter->save();
-
- // Find out how much we have to scale the text to make it fit into
- // the output rectangle. Normally this wouldn't be necessary, but
- // when fonts are switched, the replacement fonts are sometimes
- // wider than the original fonts.
- QRect worldRect(m_worldTransform.mapRect(QRect(x, y, textWidth, textHeight)));
- //debugVectorImage << "rects:" << QRect(x, y, textWidth, textHeight) << worldRect;
- qreal scaleX = qreal(1.0);
- qreal scaleY = qreal(1.0);
- if (bounds.width() < worldRect.width())
- scaleX = qreal(bounds.width()) / qreal(worldRect.width());
- if (bounds.height() < worldRect.height())
- scaleY = qreal(bounds.height()) / qreal(worldRect.height());
- //debugVectorImage << "scale:" << scaleX << scaleY;
-
- if (scaleX < qreal(1.0) || scaleY < qreal(1.0)) {
- m_painter->translate(-x, -y);
- m_painter->scale(scaleX, scaleY);
- m_painter->translate(x / scaleX, y / scaleY);
- }
-
- // Use the special pen defined by mTextPen for text.
- QPen savePen = m_painter->pen();
- m_painter->setPen(m_textPen);
- m_painter->drawText(int(x / scaleX), int(y / scaleY), textWidth, textHeight,
- Qt::AlignLeft|Qt::AlignTop, text);
- m_painter->setPen(savePen);
-
- m_painter->restore();
-}
-
-void OutputPainterStrategy::moveToEx( const qint32 x, const qint32 y )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << x << y;
-#endif
-
- if ( m_currentlyBuildingPath )
- m_path->moveTo( QPoint( x, y ) );
- else
- m_currentCoords = QPoint( x, y );
-}
-
-void OutputPainterStrategy::lineTo( const QPoint &finishPoint )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << finishPoint;
-#endif
-
- if ( m_currentlyBuildingPath )
- m_path->lineTo( finishPoint );
- else {
- m_painter->drawLine( m_currentCoords, finishPoint );
- m_currentCoords = finishPoint;
- }
-}
-
-void OutputPainterStrategy::arcTo( const QRect &box, const QPoint &start, const QPoint &end )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << box << start << end;
-#endif
-
- QPoint centrePoint = box.center();
-
- qreal startAngle = angleFromArc( centrePoint, start );
- qreal endAngle = angleFromArc( centrePoint, end );
- qreal spanAngle = angularSpan( startAngle, endAngle );
-
- m_path->arcTo( box, startAngle, spanAngle );
-}
-
-void OutputPainterStrategy::polygon16( const QRect &bounds, const QList<QPoint> points )
-{
- Q_UNUSED( bounds );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- QVector<QPoint> pointVector = points.toVector();
- m_painter->drawPolygon( pointVector.constData(), pointVector.size(), m_fillRule );
-}
-
-void OutputPainterStrategy::polyLine( const QRect &bounds, const QList<QPoint> points )
-{
- Q_UNUSED( bounds );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- QVector<QPoint> pointVector = points.toVector();
- m_painter->drawPolyline( pointVector.constData(), pointVector.size() );
-}
-
-void OutputPainterStrategy::polyLine16( const QRect &bounds, const QList<QPoint> points )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- polyLine( bounds, points );
-}
-
-void OutputPainterStrategy::polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points )
-{
- Q_UNUSED( bounds );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- for ( int i = 0; i < points.size(); ++i ) {
- m_painter->drawPolygon( points[i].constData(), points[i].size(), m_fillRule );
- }
-}
-
-void OutputPainterStrategy::polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points )
-{
- Q_UNUSED( bounds );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- for ( int i = 0; i < points.size(); ++i ) {
- m_painter->drawPolyline( points[i].constData(), points[i].size() );
- }
-}
-
-void OutputPainterStrategy::polyLineTo16( const QRect &bounds, const QList<QPoint> points )
-{
- Q_UNUSED( bounds );
-
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- for ( int i = 0; i < points.count(); ++i ) {
- m_path->lineTo( points[i] );
- }
-}
-
-void OutputPainterStrategy::polyBezier16( const QRect &bounds, const QList<QPoint> points )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- Q_UNUSED( bounds );
- QPainterPath path;
- path.moveTo( points[0] );
- for ( int i = 1; i < points.count(); i+=3 ) {
- path.cubicTo( points[i], points[i+1], points[i+2] );
- }
- m_painter->drawPath( path );
-}
-
-void OutputPainterStrategy::polyBezierTo16( const QRect &bounds, const QList<QPoint> points )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds << points;
-#endif
-
- Q_UNUSED( bounds );
- for ( int i = 0; i < points.count(); i+=3 ) {
- m_path->cubicTo( points[i], points[i+1], points[i+2] );
- }
-}
-
-void OutputPainterStrategy::fillPath( const QRect &bounds )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds;
-#endif
-
- Q_UNUSED( bounds );
- m_painter->fillPath( *m_path, m_painter->brush() );
-}
-
-void OutputPainterStrategy::strokeAndFillPath( const QRect &bounds )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds;
-#endif
-
- Q_UNUSED( bounds );
- m_painter->drawPath( *m_path );
-}
-
-void OutputPainterStrategy::strokePath( const QRect &bounds )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << bounds;
-#endif
-
- Q_UNUSED( bounds );
- m_painter->strokePath( *m_path, m_painter->pen() );
-}
-
-void OutputPainterStrategy::setClipPath( const quint32 regionMode )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << hex << regionMode << dec;
-#endif
-
- switch ( regionMode ) {
- case RGN_AND:
- m_painter->setClipPath( *m_path, Qt::IntersectClip );
- break;
- case RGN_COPY:
- m_painter->setClipPath( *m_path, Qt::ReplaceClip );
- break;
- default:
- warnVectorImage << "Unexpected / unsupported clip region mode:" << regionMode;
- Q_ASSERT( 0 );
- }
-}
-
-void OutputPainterStrategy::bitBlt( BitBltRecord &bitBltRecord )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << bitBltRecord.xDest() << bitBltRecord.yDest()
- << bitBltRecord.cxDest() << bitBltRecord.cyDest()
- << hex << bitBltRecord.rasterOperation() << dec
- << bitBltRecord.bkColorSrc();
-#endif
-
- QRect target( bitBltRecord.xDest(), bitBltRecord.yDest(),
- bitBltRecord.cxDest(), bitBltRecord.cyDest() );
- // 0x00f00021 is the PatCopy raster operation which just fills a rectangle with a brush.
- // This seems to be the most common one.
- //
- // FIXME: Implement the rest of the raster operations.
- if (bitBltRecord.rasterOperation() == 0x00f00021) {
- // Would have been nice if we didn't have to pull out the
- // brush to use it with fillRect()...
- QBrush brush = m_painter->brush();
- m_painter->fillRect(target, brush);
- }
- else if ( bitBltRecord.hasImage() ) {
- m_painter->drawImage( target, bitBltRecord.image() );
- }
-}
-
-void OutputPainterStrategy::setStretchBltMode( const quint32 stretchMode )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << hex << stretchMode << dec;
-#endif
-
- switch ( stretchMode ) {
- case 0x01:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ANDSCANS";
- break;
- case 0x02:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ORSCANS";
- break;
- case 0x03:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_DELETESCANS";
- break;
- case 0x04:
- debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_HALFTONE";
- break;
- default:
- debugVectorImage << "EMR_STRETCHBLTMODE - unknown stretch mode:" << stretchMode;
- }
-}
-
-void OutputPainterStrategy::stretchDiBits( StretchDiBitsRecord &record )
-{
-#if DEBUG_EMFPAINT
- debugVectorImage << "Bounds: " << record.bounds();
- debugVectorImage << "Dest rect: "
- << record.xDest() << record.yDest() << record.cxDest() << record.cyDest();
- debugVectorImage << "Src rect: "
- << record.xSrc() << record.ySrc() << record.cxSrc() << record.cySrc();
- debugVectorImage << "Raster op: " << hex << record.rasterOperation() << dec;
- //<< record.bkColorSrc();
- debugVectorImage << "usageSrc: " << record.usageSrc();
-#endif
-
- QPoint targetPosition( record.xDest(), record.yDest() );
- QSize targetSize( record.cxDest(), record.cyDest() );
-
- QPoint sourcePosition( record.xSrc(), record.ySrc() );
- QSize sourceSize( record.cxSrc(), record.cySrc() );
-
- // special cases, from [MS-EMF] Section 2.3.1.7:
- // "This record specifies a mirror-image copy of the source bitmap to the
- // destination if the signs of the height or width fields differ. That is,
- // if cxSrc and cxDest have different signs, this record specifies a mirror
- // image of the source bitmap along the x-axis. If cySrc and cyDest have
- // different signs, this record specifies a mirror image of the source
- // bitmap along the y-axis."
- QRect target( targetPosition, targetSize );
- QRect source( sourcePosition, sourceSize );
-#if DEBUG_EMFPAINT
- //debugVectorImage << "image size" << record.image()->size();
- debugVectorImage << "Before transformation:";
- debugVectorImage << " target" << target;
- debugVectorImage << " source" << source;
-#endif
- if ( source.width() < 0 && target.width() > 0 ) {
- sourceSize.rwidth() *= -1;
- sourcePosition.rx() -= sourceSize.width();
- source = QRect( sourcePosition, sourceSize );
- }
- if ( source.width() > 0 && target.width() < 0 ) {
- targetSize.rwidth() *= -1;
- targetPosition.rx() -= targetSize.width();
- target = QRect( targetPosition, targetSize );
- }
- if ( source.height() < 0 && target.height() > 0 ) {
- sourceSize.rheight() *= -1;
- sourcePosition.ry() -= sourceSize.height();
- source = QRect( sourcePosition, sourceSize );
- }
- if ( source.height() > 0 && target.height() < 0 ) {
- targetSize.rheight() *= -1;
- targetPosition.ry() -= targetSize.height();
- target = QRect( targetPosition, targetSize );
- }
-#if DEBUG_EMFPAINT
- debugVectorImage << "After transformation:";
- debugVectorImage << " target" << target;
- debugVectorImage << " source" << source;
- QImage image = record.image();
- debugVectorImage << "Image" << image.size();
-
-#endif
-
- QPainter::RenderHints oldRenderHints = m_painter->renderHints();
- QPainter::CompositionMode oldCompMode = m_painter->compositionMode();
- m_painter->setRenderHints(0); // Antialiasing makes composition modes invalid
- m_painter->setCompositionMode(rasteropToQtComposition(record.rasterOperation()));
- m_painter->drawImage(target, record.image(), source);
- m_painter->setCompositionMode(oldCompMode);
- m_painter->setRenderHints(oldRenderHints);
-}
-
-
-// ----------------------------------------------------------------
-// Private functions
-
-
-void OutputPainterStrategy::printPainterTransform(const char *leadText)
-{
- QTransform transform;
-
- recalculateWorldTransform();
-
- debugVectorImage << leadText << "world transform " << m_worldTransform
- << "incl output transform: " << m_painter->transform();
-}
-
-
-qreal OutputPainterStrategy::angleFromArc( const QPoint &centrePoint, const QPoint &radialPoint )
-{
- double dX = radialPoint.x() - centrePoint.x();
- double dY = centrePoint.y() - radialPoint.y();
- // Qt angles are in degrees. atan2 returns radians
- return ( atan2( dY, dX ) * 180 / M_PI );
-}
-
-qreal OutputPainterStrategy::angularSpan( const qreal startAngle, const qreal endAngle )
-{
- qreal spanAngle = endAngle - startAngle;
-
- if ( spanAngle <= 0 ) {
- spanAngle += 360;
- }
-
- return spanAngle;
-}
-
-int OutputPainterStrategy::convertFontWeight( quint32 emfWeight )
-{
- // FIXME: See how it's done in the wmf library and check if this is suitable here.
-
- if ( emfWeight == 0 ) {
- return QFont::Normal;
- } else if ( emfWeight <= 200 ) {
- return QFont::Light;
- } else if ( emfWeight <= 450 ) {
- return QFont::Normal;
- } else if ( emfWeight <= 650 ) {
- return QFont::DemiBold;
- } else if ( emfWeight <= 850 ) {
- return QFont::Bold;
- } else {
- return QFont::Black;
- }
-}
-
-static QPainter::CompositionMode rasteropToQtComposition(long rop)
-{
- // Code copied from filters/libkowmf/qwmf.cc
- // FIXME: Should be cleaned up
-
- /* TODO: Ternary raster operations
- 0x00C000CA dest = (source AND pattern)
- 0x00F00021 dest = pattern
- 0x00FB0A09 dest = DPSnoo
- 0x005A0049 dest = pattern XOR dest */
- static const struct OpTab {
- long winRasterOp;
- QPainter::CompositionMode qtRasterOp;
- } opTab[] = {
- // ### untested (conversion from Qt::RasterOp)
- { 0x00CC0020, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00EE0086, QPainter::RasterOp_SourceOrDestination }, // OrROP
- { 0x008800C6, QPainter::RasterOp_SourceAndDestination }, // AndROP
- { 0x00660046, QPainter::RasterOp_SourceXorDestination }, // XorROP
- // ----------------------------------------------------------------
- // FIXME: Checked above this, below is still todo
- // ----------------------------------------------------------------
- { 0x00440328, QPainter::CompositionMode_DestinationOut }, // AndNotROP
- { 0x00330008, QPainter::CompositionMode_DestinationOut }, // NotCopyROP
- { 0x001100A6, QPainter::CompositionMode_SourceOut }, // NandROP
- { 0x00C000CA, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00BB0226, QPainter::CompositionMode_Destination }, // NotOrROP
- { 0x00F00021, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00FB0A09, QPainter::CompositionMode_Source }, // CopyROP
- { 0x005A0049, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00550009, QPainter::CompositionMode_DestinationOut }, // NotROP
- { 0x00000042, QPainter::CompositionMode_Clear }, // ClearROP
- { 0x00FF0062, QPainter::CompositionMode_Source } // SetROP
- };
-
- int i;
- for (i = 0 ; i < 15 ; i++)
- if (opTab[i].winRasterOp == rop)
- break;
-
- if (i < 15)
- return opTab[i].qtRasterOp;
- else
- return QPainter::CompositionMode_Source;
-}
-
-} // xnamespace...
diff --git a/libs/vectorimage/libemf/EmfOutputPainterStrategy.h b/libs/vectorimage/libemf/EmfOutputPainterStrategy.h
deleted file mode 100644
index bb2a1f9618..0000000000
--- a/libs/vectorimage/libemf/EmfOutputPainterStrategy.h
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 - 2010 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFOUTPUTPAINTERSTRATEGY_H
-#define EMFOUTPUTPAINTERSTRATEGY_H
-
-#include "kritavectorimage_export.h"
-
-#include <QList>
-#include <QPainter>
-#include <QRect> // also provides QSize, QPoint
-#include <QString>
-#include <QVariant>
-
-#include "EmfEnums.h"
-#include "EmfHeader.h"
-#include "EmfRecords.h"
-#include "EmfOutput.h"
-
-
-/**
- \file
-
- Primary definitions for EMF output strategies
-*/
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-class EmrTextObject;
-
-/**
- QPainter based output strategy for EMF Parser.
-
- This class allows rendering of an EMF file to a QPixmap or any other QPaintDevice.
-*/
-class KRITAVECTORIMAGE_EXPORT OutputPainterStrategy : public AbstractOutput
-{
-public:
- /**
- Constructor.
-
- This will probably need to take an enum to say what sort of output
- we want.
- */
- OutputPainterStrategy();
- OutputPainterStrategy( QPainter &painter, QSize &size,
- bool keepAspectRatio = false );
- ~OutputPainterStrategy() override;
-
- void init( const Header *header ) override;
- void cleanup( const Header *header ) override;
- void eof() override;
-
- /**
- The image that has been rendered to.
- */
- QImage *image();
-
- void createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y,
- quint8 red, quint8 green, quint8 blue, quint8 reserved ) override;
- void createBrushIndirect( quint32 ihBrush, quint32 BrushStyle, quint8 red,
- quint8 green, quint8 blue, quint8 reserved,
- quint32 BrushHatch ) override;
- void createMonoBrush( quint32 ihBrush, Bitmap *bitmap ) override;
- void selectObject( const quint32 ihObject ) override;
- void deleteObject( const quint32 ihObject ) override;
- void arc( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void chord( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void pie( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void ellipse( const QRect &box ) override;
- void rectangle( const QRect &box ) override;
- void setMapMode( const quint32 mapMode ) override;
- void setMetaRgn() override;
- void setWindowOrgEx( const QPoint &origin ) override;
- void setWindowExtEx( const QSize &size ) override;
- void setViewportOrgEx( const QPoint &origin ) override;
- void setViewportExtEx( const QSize &size ) override;
- void beginPath() override;
- void closeFigure() override;
- void endPath() override;
- void setBkMode( const quint32 backgroundMode ) override;
- void setPolyFillMode( const quint32 polyFillMode ) override;
- void setLayout( const quint32 layoutMode ) override;
- void extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW ) override;
- void setTextAlign( const quint32 textAlignMode ) override;
- void setTextColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved ) override;
- void setBkColor( const quint8 red, const quint8 green, const quint8 blue,
- const quint8 reserved ) override;
- void setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved ) override;
- void modifyWorldTransform( quint32 mode, float M11, float M12,
- float M21, float M22, float Dx, float Dy ) override;
- void setWorldTransform( float M11, float M12, float M21,
- float M22, float Dx, float Dy ) override;
- void extTextOut( const QRect &bounds, const EmrTextObject &textObject ) override;
- void moveToEx( const qint32 x, const qint32 y ) override;
- void saveDC() override;
- void restoreDC( const qint32 savedDC ) override;
- void lineTo( const QPoint &finishPoint ) override;
- void arcTo( const QRect &box, const QPoint &start, const QPoint &end ) override;
- void polygon16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyLine16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points ) override;
- void polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points ) override;
- void polyLine( const QRect &bounds, const QList<QPoint> points ) override;
- void polyLineTo16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyBezier16( const QRect &bounds, const QList<QPoint> points ) override;
- void polyBezierTo16( const QRect &bounds, const QList<QPoint> points ) override;
- void fillPath( const QRect &bounds ) override;
- void strokeAndFillPath( const QRect &bounds ) override;
- void strokePath( const QRect &bounds ) override;
- void setClipPath( const quint32 regionMode ) override;
- void bitBlt( BitBltRecord &bitBltRecord ) override;
- void setStretchBltMode( const quint32 stretchMode ) override;
- void stretchDiBits( StretchDiBitsRecord &stretchDiBitsRecord ) override;
-
-private:
- void printPainterTransform(const char *leadText);
-
- /// For debugging purposes: Draw the boundary box.
- void paintBounds(const Header *header);
-
- /// Recalculate the world transform and then apply it to the painter
- /// This must be called at the end of every function that changes the transform.
- void recalculateWorldTransform();
-
- /**
- Select a stock object.
-
- See [MS-EMF] Section 2.1.31.
-
- \param ihObject the stock object value
- */
- void selectStockObject( const quint32 ihObject );
-
-
- /**
- Helper routine to convert the EMF angle (centrepoint + radial endpoint) into
- the Qt format (in degress - may need to multiply by 16 for some purposes)
- */
- qreal angleFromArc( const QPoint &centrePoint, const QPoint &radialPoint );
-
- /**
- Calculate the angular difference (span) between two angles
-
- This should always be positive.
- */
- qreal angularSpan( const qreal startAngle, const qreal endAngle );
-
- /**
- Convert the EMF font weight scale (0..1000) to Qt equivalent.
-
- This is a bit rough - the EMF spec only says 400 is normal, and
- 700 is bold.
- */
- int convertFontWeight( quint32 emfWeight );
-
-
- Header *m_header; // Save to be able to retain scaling.
-
- int m_painterSaves; // The number of times that save() was called.
- QSize m_outputSize;
- bool m_keepAspectRatio;
-
- QMap<quint32, QVariant> m_objectTable;
-
- QPainterPath *m_path;
- bool m_currentlyBuildingPath;
-
- QPainter *m_painter;
- QTransform m_worldTransform; // The transform inside the EMF.
- QTransform m_outputTransform; // The transform that the painter already had
- qreal m_outputScale;
-
- // Everything that has to do with window and viewport calculation
- QPoint m_windowOrg;
- QSize m_windowExt;
- QPoint m_viewportOrg;
- QSize m_viewportExt;
- bool m_windowExtIsSet;
- bool m_viewportExtIsSet;
- bool m_windowViewportIsSet;
-
-#if 0
- // This matrix is needed because the window / viewport calculation
- // is not the last one in the chain. After that one comes the
- // transform that the painter already has when the painting
- // starts, and that one has to be saved and reapplied again after
- // the window / viewport calculation is redone.
- QTransform m_outputTransform;
-#endif
-
- // ----------------------------------------------------------------
- // The playback device context
-
- // The Playback Device Context (PDC) contains the following:
- // - bitmap
- // - brush (part of the painter)
- // - palette
- // - font (part of the painter)
- // - pen (part of the painter)
- // - region
- // - drawing mode
- // - mapping mode
- // FIXME: what more? textalign? textpen?
-
- /**
- The current text pen
- */
- QPen m_textPen;
-
- /**
- The current fill rule
- */
- enum Qt::FillRule m_fillRule;
-
- /**
- The current map mode
- */
- MapMode m_mapMode;
- /**
- The current text alignment mode
- */
- quint32 m_textAlignMode;
-
- /**
- The current coordinates
- */
- QPoint m_currentCoords;
-};
-
-}
-
-#endif
diff --git a/libs/vectorimage/libemf/EmfParser.cpp b/libs/vectorimage/libemf/EmfParser.cpp
deleted file mode 100644
index 1a96c232da..0000000000
--- a/libs/vectorimage/libemf/EmfParser.cpp
+++ /dev/null
@@ -1,1090 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 - 2010 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-// Own
-#include "EmfParser.h"
-
-// Qt
-#include <QColor>
-#include <QFile>
-#include <QBuffer>
-
-// KDE
-#include <VectorImageDebug.h>
-
-// LibEmf
-#include "EmfRecords.h"
-#include "EmfObjects.h"
-
-
-// 0 - No debug
-// 1 - Print a lot of debug info
-// 2 - Just print all the records instead of parsing them
-#define DEBUG_EMFPARSER 0
-
-
-namespace Libemf
-{
-
-
-// ================================================================
-
-
-Parser::Parser()
- : mOutput( 0 )
-{
-}
-
-Parser::~Parser()
-{
-}
-
-
-bool Parser::load( const QString &fileName )
-{
- QFile *file = new QFile( fileName );
-
- if ( ! file->exists() ) {
- warnVectorImage << "Request to load file (%s) that does not exist"
- << qPrintable(file->fileName());
- delete file;
- return false;
- }
-
- if ( ! file->open( QIODevice::ReadOnly ) ) {
- warnVectorImage << "Request to load file (" << file->fileName()
- << ") that cannot be opened";
- delete file;
- return false;
- }
-
- // Use version 11, which makes floats always be 32 bits without
- // the need to call setFloatingPointPrecision().
- QDataStream stream( file );
- stream.setVersion(QDataStream::Qt_4_6);
- stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
-
- bool result = loadFromStream( stream );
-
- delete file;
-
- return result;
-}
-
-bool Parser::load(const QByteArray &contents)
-{
- // Create a QBuffer to read from...
- QBuffer emfBuffer((QByteArray *)&contents, 0);
- emfBuffer.open(QIODevice::ReadOnly);
-
- // ...but what we really want is a stream.
- QDataStream emfStream;
- emfStream.setDevice(&emfBuffer);
- emfStream.setByteOrder(QDataStream::LittleEndian);
-
- return loadFromStream(emfStream);
-}
-
-bool Parser::loadFromStream( QDataStream &stream )
-{
- stream.setByteOrder( QDataStream::LittleEndian );
-
- Header *header = new Header( stream );
- if ( ! header->isValid() ) {
- warnVectorImage << "Failed to parse header, perhaps not an EMF file";
- delete header;
- return false;
- }
-
- mOutput->init( header );
-
-#if DEBUG_EMFPARSER
- debugVectorImage << "========================================================== Starting EMF";
-#endif
-
- int numRecords = header->recordCount();
- for ( int i = 1; i < numRecords; ++i ) {
- // debugVectorImage << "Record" << i << "of" << numRecords;
- if ( ! readRecord( stream ) ) {
- break;
- }
- }
-
- mOutput->cleanup( header );
-
- delete header;
-
- return true;
-}
-
-void Parser::setOutput( AbstractOutput *output )
-{
- mOutput = output;
-}
-
-void Parser::soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-void Parser::outputBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-/**
- The supported EMF Record Types
-
- Refer to [MS-EMF] Section 2.1.1 for more information.
-
- NOTE: Not all records are part of this enum, only the implemented ones.
-*/
-enum RecordType {
- EMR_POLYLINE = 0x00000004,
- EMR_POLYPOLYGON = 0x00000008,
- EMR_SETWINDOWEXTEX = 0x00000009,
- EMR_SETWINDOWORGEX = 0x0000000A,
- EMR_SETVIEWPORTEXTEX = 0x0000000B,
- EMR_SETVIEWPORTORGEX = 0x0000000C,
- EMR_SETBRUSHORGEX = 0x0000000D,
- EMR_EOF = 0x0000000E,
- EMR_SETPIXELV = 0x0000000F,
- EMR_SETMAPMODE = 0x00000011,
- EMR_SETBKMODE = 0x00000012,
- EMR_SETPOLYFILLMODE = 0x00000013,
- EMR_SETROP2 = 0x00000014,
- EMR_SETSTRETCHBLTMODE = 0x00000015,
- EMR_SETTEXTALIGN = 0x00000016,
- EMR_SETTEXTCOLOR = 0x00000018,
- EMR_SETBKCOLOR = 0x00000019,
- EMR_MOVETOEX = 0x0000001B,
- EMR_SETMETARGN = 0x0000001C,
- EMR_EXCLUDECLIPRECT = 0x0000001D,
- EMR_INTERSECTCLIPRECT = 0x0000001E,
- EMR_SAVEDC = 0x00000021,
- EMR_RESTOREDC = 0x00000022,
- EMR_SETWORLDTRANSFORM = 0x00000023,
- EMR_MODIFYWORLDTRANSFORM = 0x00000024,
- EMR_SELECTOBJECT = 0x00000025,
- EMR_CREATEPEN = 0x00000026,
- EMR_CREATEBRUSHINDIRECT = 0x00000027,
- EMR_DELETEOBJECT = 0x00000028,
- EMR_ELLIPSE = 0x0000002A,
- EMR_RECTANGLE = 0x0000002B,
- EMR_ARC = 0x0000002D,
- EMR_CHORD = 0x0000002E,
- EMR_PIE = 0x0000002F,
- EMR_SELECTPALLETTE = 0x00000030,
- EMR_LINETO = 0x00000036,
- EMR_ARCTO = 0x00000037,
- EMR_SETMITERLIMIT = 0x0000003A,
- EMR_BEGINPATH = 0x0000003B,
- EMR_ENDPATH = 0x0000003C,
- EMR_CLOSEFIGURE = 0x0000003D,
- EMR_FILLPATH = 0x0000003E,
- EMR_STROKEANDFILLPATH = 0x0000003F,
- EMR_STROKEPATH = 0x00000040,
- EMR_SETCLIPPATH = 0x00000043,
- EMR_COMMENT = 0x00000046,
- EMR_EXTSELECTCLIPRGN = 0x0000004B,
- EMR_BITBLT = 0x0000004C,
- EMR_STRETCHDIBITS = 0x00000051,
- EMR_EXTCREATEFONTINDIRECTW = 0x00000052,
- EMR_EXTTEXTOUTA = 0x00000053,
- EMR_EXTTEXTOUTW = 0x00000054,
- EMR_POLYBEZIER16 = 0x00000055,
- EMR_POLYGON16 = 0x00000056,
- EMR_POLYLINE16 = 0x00000057,
- EMR_POLYBEZIERTO16 = 0x00000058,
- EMR_POLYLINETO16 = 0x00000059,
- EMR_POLYPOLYLINE16 = 0x0000005A,
- EMR_POLYPOLYGON16 = 0x0000005B,
- EMR_CREATEMONOBRUSH = 0x0000005D,
- EMR_EXTCREATEPEN = 0x0000005F,
- EMR_SETICMMODE = 0x00000062,
- EMR_SETLAYOUT = 0x00000073,
-
- EMR_LASTRECORD = 0x0000007A // Only a placeholder
-};
-
-static const struct {
- int recordType;
- QString name;
-} EmfRecords[] = {
- { 0x00000000, "no type" },
- { 0x00000001, "EMR_HEADER" },
- { 0x00000002, "EMR_POLYBEZIER" },
- { 0x00000003, "EMR_POLYGON" },
- { 0x00000004, "EMR_POLYLINE" },
- { 0x00000005, "EMR_POLYBEZIERTO" },
- { 0x00000006, "EMR_POLYLINETO" },
- { 0x00000007, "EMR_POLYPOLYLINE" },
- { 0x00000008, "EMR_POLYPOLYGON" },
- { 0x00000009, "EMR_SETWINDOWEXTEX" },
- { 0x0000000A, "EMR_SETWINDOWORGEX" },
- { 0x0000000B, "EMR_SETVIEWPORTEXTEX" },
- { 0x0000000C, "EMR_SETVIEWPORTORGEX" },
- { 0x0000000D, "EMR_SETBRUSHORGEX" },
- { 0x0000000E, "EMR_EOF" },
- { 0x0000000F, "EMR_SETPIXELV" },
- { 0x00000010, "EMR_SETMAPPERFLAGS" },
- { 0x00000011, "EMR_SETMAPMODE" },
- { 0x00000012, "EMR_SETBKMODE" },
- { 0x00000013, "EMR_SETPOLYFILLMODE" },
- { 0x00000014, "EMR_SETROP2" },
- { 0x00000015, "EMR_SETSTRETCHBLTMODE" },
- { 0x00000016, "EMR_SETTEXTALIGN" },
- { 0x00000017, "EMR_SETCOLORADJUSTMENT" },
- { 0x00000018, "EMR_SETTEXTCOLOR" },
- { 0x00000019, "EMR_SETBKCOLOR" },
- { 0x0000001A, "EMR_OFFSETCLIPRGN" },
- { 0x0000001B, "EMR_MOVETOEX" },
- { 0x0000001C, "EMR_SETMETARGN" },
- { 0x0000001D, "EMR_EXCLUDECLIPRECT" },
- { 0x0000001E, "EMR_INTERSECTCLIPRECT" },
- { 0x0000001F, "EMR_SCALEVIEWPORTEXTEX" },
- { 0x00000020, "EMR_SCALEWINDOWEXTEX" },
- { 0x00000021, "EMR_SAVEDC" },
- { 0x00000022, "EMR_RESTOREDC" },
- { 0x00000023, "EMR_SETWORLDTRANSFORM" },
- { 0x00000024, "EMR_MODIFYWORLDTRANSFORM" },
- { 0x00000025, "EMR_SELECTOBJECT" },
- { 0x00000026, "EMR_CREATEPEN" },
- { 0x00000027, "EMR_CREATEBRUSHINDIRECT" },
- { 0x00000028, "EMR_DELETEOBJECT" },
- { 0x00000029, "EMR_ANGLEARC" },
- { 0x0000002A, "EMR_ELLIPSE" },
- { 0x0000002B, "EMR_RECTANGLE" },
- { 0x0000002C, "EMR_ROUNDRECT" },
- { 0x0000002D, "EMR_ARC" },
- { 0x0000002E, "EMR_CHORD" },
- { 0x0000002F, "EMR_PIE" },
- { 0x00000030, "EMR_SELECTPALETTE" },
- { 0x00000031, "EMR_CREATEPALETTE" },
- { 0x00000032, "EMR_SETPALETTEENTRIES" },
- { 0x00000033, "EMR_RESIZEPALETTE" },
- { 0x00000034, "EMR_REALIZEPALETTE" },
- { 0x00000035, "EMR_EXTFLOODFILL" },
- { 0x00000036, "EMR_LINETO" },
- { 0x00000037, "EMR_ARCTO" },
- { 0x00000038, "EMR_POLYDRAW" },
- { 0x00000039, "EMR_SETARCDIRECTION" },
- { 0x0000003A, "EMR_SETMITERLIMIT" },
- { 0x0000003B, "EMR_BEGINPATH" },
- { 0x0000003C, "EMR_ENDPATH" },
- { 0x0000003D, "EMR_CLOSEFIGURE" },
- { 0x0000003E, "EMR_FILLPATH" },
- { 0x0000003F, "EMR_STROKEANDFILLPATH" },
- { 0x00000040, "EMR_STROKEPATH" },
- { 0x00000041, "EMR_FLATTENPATH" },
- { 0x00000042, "EMR_WIDENPATH" },
- { 0x00000043, "EMR_SELECTCLIPPATH" },
- { 0x00000044, "EMR_ABORTPATH" },
- { 0x00000045, "no type" },
- { 0x00000046, "EMR_COMMENT" },
- { 0x00000047, "EMR_FILLRGN" },
- { 0x00000048, "EMR_FRAMERGN" },
- { 0x00000049, "EMR_INVERTRGN" },
- { 0x0000004A, "EMR_PAINTRGN" },
- { 0x0000004B, "EMR_EXTSELECTCLIPRGN" },
- { 0x0000004C, "EMR_BITBLT" },
- { 0x0000004D, "EMR_STRETCHBLT" },
- { 0x0000004E, "EMR_MASKBLT" },
- { 0x0000004F, "EMR_PLGBLT" },
- { 0x00000050, "EMR_SETDIBITSTODEVICE" },
- { 0x00000051, "EMR_STRETCHDIBITS" },
- { 0x00000052, "EMR_EXTCREATEFONTINDIRECTW" },
- { 0x00000053, "EMR_EXTTEXTOUTA" },
- { 0x00000054, "EMR_EXTTEXTOUTW" },
- { 0x00000055, "EMR_POLYBEZIER16" },
- { 0x00000056, "EMR_POLYGON16" },
- { 0x00000057, "EMR_POLYLINE16" },
- { 0x00000058, "EMR_POLYBEZIERTO16" },
- { 0x00000059, "EMR_POLYLINETO16" },
- { 0x0000005A, "EMR_POLYPOLYLINE16" },
- { 0x0000005B, "EMR_POLYPOLYGON16" },
- { 0x0000005C, "EMR_POLYDRAW16" },
- { 0x0000005D, "EMR_CREATEMONOBRUSH" },
- { 0x0000005E, "EMR_CREATEDIBPATTERNBRUSHPT" },
- { 0x0000005F, "EMR_EXTCREATEPEN" },
- { 0x00000060, "EMR_POLYTEXTOUTA" },
- { 0x00000061, "EMR_POLYTEXTOUTW" },
- { 0x00000062, "EMR_SETICMMODE" },
- { 0x00000063, "EMR_CREATECOLORSPACE" },
- { 0x00000064, "EMR_SETCOLORSPACE" },
- { 0x00000065, "EMR_DELETECOLORSPACE" },
- { 0x00000066, "EMR_GLSRECORD" },
- { 0x00000067, "EMR_GLSBOUNDEDRECORD" },
- { 0x00000068, "EMR_PIXELFORMAT" },
- { 0x00000069, "EMR_DRAWESCAPE" },
- { 0x0000006A, "EMR_EXTESCAPE" },
- { 0x0000006B, "no type" },
- { 0x0000006C, "EMR_SMALLTEXTOUT" },
- { 0x0000006D, "EMR_FORCEUFIMAPPING" },
- { 0x0000006E, "EMR_NAMEDESCAPE" },
- { 0x0000006F, "EMR_COLORCORRECTPALETTE" },
- { 0x00000070, "EMR_SETICMPROFILEA" },
- { 0x00000071, "EMR_SETICMPROFILEW" },
- { 0x00000072, "EMR_ALPHABLEND" },
- { 0x00000073, "EMR_SETLAYOUT" },
- { 0x00000074, "EMR_TRANSPARENTBLT" },
- { 0x00000075, "no type" },
- { 0x00000076, "EMR_GRADIENTFILL" },
- { 0x00000077, "EMR_SETLINKEDUFIS" },
- { 0x00000078, "EMR_SETTEXTJUSTIFICATION" },
- { 0x00000079, "EMR_COLORMATCHTOTARGETW" },
- { 0x0000007A, "EMR_CREATECOLORSPACEW" }
-};
-
-bool Parser::readRecord( QDataStream &stream )
-{
- if ( ! mOutput ) {
- warnVectorImage << "Output device not set";
- return false;
- }
- quint32 type;
- quint32 size;
-
- stream >> type;
- stream >> size;
-
- {
- QString name;
- if (0 < type && type <= EMR_LASTRECORD)
- name = EmfRecords[type].name;
- else
- name = "(out of bounds)";
-#if DEBUG_EMFPARSER
- debugVectorImage << "Record length" << size << "type " << hex << type << "(" << dec << type << ")" << name;
-#endif
- }
-
-#if DEBUG_EMFPARSER == 2
- soakBytes(stream, size - 8);
-#else
- switch ( type ) {
- case EMR_POLYLINE:
- {
- QRect bounds;
- stream >> bounds;
- quint32 count;
- stream >> count;
- QList<QPoint> aPoints;
- for (quint32 i = 0; i < count; ++i) {
- QPoint point;
- stream >> point;
- aPoints.append( point );
- }
- mOutput->polyLine( bounds, aPoints );
- }
- break;
- case EMR_SETWINDOWEXTEX:
- {
- QSize size;
- //stream >> size;
- qint32 width, height;
- stream >> width >> height;
- //debugVectorImage << "SETWINDOWEXTEX" << width << height;
- size = QSize(width, height);
- mOutput->setWindowExtEx( size );
- }
- break;
- case EMR_SETWINDOWORGEX:
- {
- QPoint origin;
- stream >> origin;
- mOutput->setWindowOrgEx( origin );
- }
- break;
- case EMR_SETVIEWPORTEXTEX:
- {
- QSize size;
- stream >> size;
- mOutput->setViewportExtEx( size );
- }
- break;
- case EMR_SETVIEWPORTORGEX:
- {
- QPoint origin;
- stream >> origin;
- mOutput->setViewportOrgEx( origin );
- }
- break;
- case EMR_SETBRUSHORGEX:
- {
- QPoint origin;
- stream >> origin;
-#if DEBUG_EMFPARSER
- debugVectorImage << "EMR_SETBRUSHORGEX" << origin;
-#endif
- }
- break;
- case EMR_EOF:
- {
- mOutput->eof();
- soakBytes( stream, size-8 ); // because we already took 8.
- return false;
- }
- break;
- case EMR_SETPIXELV:
- {
- QPoint point;
- quint8 red, green, blue, reserved;
- stream >> point;
- stream >> red >> green >> blue >> reserved;
- mOutput->setPixelV( point, red, green, blue, reserved );
- }
- break;
- case EMR_SETMAPMODE:
- {
- quint32 mapMode;
- stream >> mapMode;
- mOutput->setMapMode( mapMode );
- }
- break;
- case EMR_SETBKMODE:
- {
- quint32 backgroundMode;
- stream >> backgroundMode;
- mOutput->setBkMode( backgroundMode );
- }
- break;
- case EMR_SETPOLYFILLMODE:
- {
- quint32 PolygonFillMode;
- stream >> PolygonFillMode;
- mOutput->setPolyFillMode( PolygonFillMode );
- }
- break;
- case EMR_SETROP2:
- {
- quint32 ROP2Mode;
- stream >> ROP2Mode;
- //debugVectorImage << "EMR_SETROP2" << ROP2Mode;
- }
- break;
- case EMR_SETSTRETCHBLTMODE:
- {
- quint32 stretchMode;
- stream >> stretchMode;
- mOutput->setStretchBltMode( stretchMode );
-
- }
- break;
- case EMR_SETTEXTALIGN:
- {
- quint32 textAlignMode;
- stream >> textAlignMode;
- mOutput->setTextAlign( textAlignMode );
- }
- break;
- case EMR_SETTEXTCOLOR:
- {
- quint8 red, green, blue, reserved;
- stream >> red >> green >> blue >> reserved;
- mOutput->setTextColor( red, green, blue, reserved );
- }
- break;
- case EMR_SETBKCOLOR:
- {
- quint8 red, green, blue, reserved;
- stream >> red >> green >> blue >> reserved;
- mOutput->setBkColor( red, green, blue, reserved );
- }
- break;
- case EMR_MOVETOEX:
- {
- qint32 x, y;
- stream >> x >> y;
- mOutput->moveToEx( x, y );
- //debugVectorImage << "xx EMR_MOVETOEX" << x << y;
- }
- break;
- case EMR_SETMETARGN:
- {
- // Takes no arguments
- mOutput->setMetaRgn();
- }
- break;
- case EMR_EXCLUDECLIPRECT:
- {
- QRect clip;
- stream >> clip;
- //debugVectorImage << "EMR_EXCLUDECLIPRECT" << clip;
- }
- break;
- case EMR_INTERSECTCLIPRECT:
- {
- QRect clip;
- stream >> clip;
- //debugVectorImage << "EMR_INTERSECTCLIPRECT" << clip;
- }
- break;
- case EMR_SAVEDC:
- {
- mOutput->saveDC();
- }
- break;
- case EMR_RESTOREDC:
- {
- qint32 savedDC;
- stream >> savedDC;
- mOutput->restoreDC( savedDC );
- }
- break;
- case EMR_SETWORLDTRANSFORM:
- {
- stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
- float M11, M12, M21, M22, Dx, Dy;
- stream >> M11;
- stream >> M12;
- stream >> M21;
- stream >> M22;
- stream >> Dx;
- stream >> Dy;
- //debugVectorImage << "Set world transform" << M11 << M12 << M21 << M22 << Dx << Dy;
- mOutput->setWorldTransform( M11, M12, M21, M22, Dx, Dy );
- }
- break;
- case EMR_MODIFYWORLDTRANSFORM:
- {
- stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
- float M11, M12, M21, M22, Dx, Dy;
- stream >> M11;
- stream >> M12;
- stream >> M21;
- stream >> M22;
- stream >> Dx;
- stream >> Dy;
- //debugVectorImage << "stream position after the matrix: " << stream.device()->pos();
- quint32 ModifyWorldTransformMode;
- stream >> ModifyWorldTransformMode;
- mOutput->modifyWorldTransform( ModifyWorldTransformMode, M11, M12,
- M21, M22, Dx, Dy );
- }
- break;
- case EMR_SELECTOBJECT:
- quint32 ihObject;
- stream >> ihObject;
- mOutput->selectObject( ihObject );
- break;
- case EMR_CREATEPEN:
- {
- quint32 ihPen;
- stream >> ihPen;
-
- quint32 penStyle;
- stream >> penStyle;
-
- quint32 x, y;
- stream >> x;
- stream >> y; // unused
-
- quint8 red, green, blue, reserved;
- stream >> red >> green >> blue;
- stream >> reserved; // unused;
-
- mOutput->createPen( ihPen, penStyle, x, y, red, green, blue, reserved );
-
- break;
- }
- case EMR_CREATEBRUSHINDIRECT:
- {
- quint32 ihBrush;
- stream >> ihBrush;
-
- quint32 BrushStyle;
- stream >> BrushStyle;
-
- quint8 red, green, blue, reserved;
- stream >> red >> green >> blue;
- stream >> reserved; // unused;
-
- quint32 BrushHatch;
- stream >> BrushHatch;
-
- mOutput->createBrushIndirect( ihBrush, BrushStyle, red, green, blue, reserved, BrushHatch );
-
- break;
- }
- case EMR_DELETEOBJECT:
- {
- quint32 ihObject;
- stream >> ihObject;
- mOutput->deleteObject( ihObject );
- }
- break;
- case EMR_ELLIPSE:
- {
- QRect box;
- stream >> box;
- mOutput->ellipse( box );
- }
- break;
- case EMR_RECTANGLE:
- {
- QRect box;
- stream >> box;
- mOutput->rectangle( box );
- //debugVectorImage << "xx EMR_RECTANGLE" << box;
- }
- break;
- case EMR_ARC:
- {
- QRect box;
- QPoint start, end;
- stream >> box;
- stream >> start >> end;
- mOutput->arc( box, start, end );
- }
- break;
- case EMR_CHORD:
- {
- QRect box;
- QPoint start, end;
- stream >> box;
- stream >> start >> end;
- mOutput->chord( box, start, end );
- }
- break;
- case EMR_PIE:
- {
- QRect box;
- QPoint start, end;
- stream >> box;
- stream >> start >> end;
- mOutput->pie( box, start, end );
- }
- break;
- case EMR_SELECTPALLETTE:
- {
- quint32 ihPal;
- stream >> ihPal;
-#if DEBUG_EMFPARSER
- debugVectorImage << "EMR_SELECTPALLETTE" << ihPal;
-#endif
- }
- break;
- case EMR_SETMITERLIMIT:
- {
- stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
- float miterLimit;
- stream >> miterLimit;
-#if DEBUG_EMFPARSER
- debugVectorImage << "EMR_SETMITERLIMIT" << miterLimit;
-#endif
- }
- break;
- case EMR_BEGINPATH:
- mOutput->beginPath();
- break;
- case EMR_ENDPATH:
- mOutput->endPath();
- break;
- case EMR_CLOSEFIGURE:
- mOutput->closeFigure();
- break;
- case EMR_FILLPATH:
- {
- QRect bounds;
- // NOTE: The spec says that there should always be a
- // bound, i.e. the size should be 24. But the file
- // http://www.eventlink.org.uk/uploads/DOCS2/53-Cartledge_Energy_Efficient_IT_Sheffield_Sep10.ppt
- // has an EMF with a FILLPATH record without this
- // bound. So let's allow without it too.
- if (size >= 24) {
- stream >> bounds;
- }
- else if (size > 8)
- soakBytes(stream, size - 8);
-
- mOutput->fillPath( bounds );
- //debugVectorImage << "xx EMR_FILLPATH" << bounds;
- }
- break;
- case EMR_STROKEANDFILLPATH:
- {
- QRect bounds;
- stream >> bounds;
- mOutput->strokeAndFillPath( bounds );
- //debugVectorImage << "xx EMR_STROKEANDFILLPATHPATH" << bounds;
- }
- break;
- case EMR_STROKEPATH:
- {
- QRect bounds;
- stream >> bounds;
- mOutput->strokePath( bounds );
- //debugVectorImage << "xx EMR_STROKEPATH" << bounds;
- }
- break;
- case EMR_SETCLIPPATH:
- {
- quint32 regionMode;
- stream >> regionMode;
- mOutput->setClipPath( regionMode );
- }
- break;
- case EMR_LINETO:
- {
- quint32 x, y;
- stream >> x >> y;
- QPoint finishPoint( x, y );
- mOutput->lineTo( finishPoint );
- //debugVectorImage << "xx EMR_LINETO" << x << y;
- }
- break;
- case EMR_ARCTO:
- {
- QRect box;
- stream >> box;
- QPoint start;
- stream >> start;
- QPoint end;
- stream >> end;
- mOutput->arcTo( box, start, end );
- }
- break;
- case EMR_COMMENT:
- {
- quint32 dataSize;
- quint32 commentIdentifier;
-
- stream >> dataSize;
- stream >> commentIdentifier;
-
- switch (commentIdentifier) {
- case EMR_COMMENT_EMFSPOOL:
- debugVectorImage << "EMR_COMMENT_EMFSPOOL";
- soakBytes( stream, size-16 ); // because we already took 16.
- break;
- case EMR_COMMENT_EMFPLUS:
- debugVectorImage << "EMR_COMMENT_EMFPLUS";
- soakBytes( stream, size-16 ); // because we already took 16.
- break;
- case EMR_COMMENT_PUBLIC:
- quint32 commentType;
- stream >> commentType;
- debugVectorImage << "EMR_COMMENT_PUBLIC type" << commentType;
- soakBytes( stream, size-20 ); // because we already took 20.
- break;
- case EMR_COMMENT_MSGR:
- debugVectorImage << "EMR_COMMENT_MSGR";
- soakBytes( stream, size-16 ); // because we already took 16.
- break;
- default:
- debugVectorImage << "EMR_COMMENT unknown type" << hex << commentIdentifier << dec
- << "datasize =" << dataSize;
- soakBytes( stream, size-16 ); // because we already took 16.
- }
- }
- break;
- case EMR_EXTSELECTCLIPRGN:
- debugVectorImage << "EMR_EXTSELECTCLIPRGN";
- soakBytes( stream, size-8 ); // because we already took 8.
- break;
- case EMR_BITBLT:
- {
- //debugVectorImage << "Found BitBlt record";
- BitBltRecord bitBltRecord( stream, size );
- mOutput->bitBlt( bitBltRecord );
- }
- break;
- case EMR_STRETCHDIBITS:
- {
- StretchDiBitsRecord stretchDiBitsRecord( stream, size );
- if (stretchDiBitsRecord.hasImage()) {
- mOutput->stretchDiBits( stretchDiBitsRecord );
- }
- }
- break;
- case EMR_EXTCREATEFONTINDIRECTW:
- {
- ExtCreateFontIndirectWRecord extCreateFontIndirectWRecord( stream, size );
- mOutput->extCreateFontIndirectW( extCreateFontIndirectWRecord );
- }
- break;
- case EMR_EXTTEXTOUTA:
- case EMR_EXTTEXTOUTW:
- {
- QRect bounds;
- quint32 iGraphicsMode;
- // FIXME: These should really be floats, but that crashes
- // for a test file where eyScale contains NaN.
- // Unfortunately this file contains secret data and
- // can't be committed into the test suite. Let's just
- // work around the problem until we support scaling anyway.
- quint32 exScale;
- quint32 eyScale;
-
- //debugVectorImage << "size:" << size;
- size -= 8;
-
- stream >> bounds;
- //debugVectorImage << "bounds:" << bounds;
- size -= 16;
-
- stream >> iGraphicsMode;
- //debugVectorImage << "graphics mode:" << iGraphicsMode;
- size -= 4;
-
- stream >> exScale;
- stream >> eyScale;
-#if DEBUG_EMFPARSER
- if (iGraphicsMode == GM_COMPATIBLE) {
- debugVectorImage << "exScale:" << exScale;
- debugVectorImage << "eyScale:" << eyScale;
- }
-#endif
- size -= 8;
-
- // The only difference between ExtTextOutA and ...W is
- // that A uses 8 bit chars and W uses 16 bit chars.
- EmrTextObject emrText(stream, size,
- (type == EMR_EXTTEXTOUTA) ? EmrTextObject::EightBitChars
- : EmrTextObject::SixteenBitChars);
- mOutput->extTextOut( bounds, emrText );
- }
- break;
- case EMR_SETLAYOUT:
- {
- quint32 layoutMode;
- stream >> layoutMode;
- mOutput->setLayout( layoutMode );
- }
- break;
- case EMR_POLYBEZIER16:
- {
- QRect bounds;
- stream >> bounds;
- quint32 count;
- stream >> count;
- QList<QPoint> aPoints;
- for (quint32 i = 0; i < count; ++i) {
- qint16 x, y;
- stream >> x;
- stream >> y;
- aPoints.append( QPoint( x, y ) );
- }
- mOutput->polyBezier16( bounds, aPoints );
- }
- break;
- case EMR_POLYGON16:
- {
- QRect bounds;
- stream >> bounds;
- quint32 count;
- stream >> count;
- QList<QPoint> aPoints;
- for (quint32 i = 0; i < count; ++i) {
- qint16 x, y;
- stream >> x;
- stream >> y;
- aPoints.append( QPoint( x, y ) );
- }
- mOutput->polygon16( bounds, aPoints );
- }
- break;
- case EMR_POLYLINE16:
- {
- QRect bounds;
- stream >> bounds;
- quint32 count;
- stream >> count;
- QList<QPoint> aPoints;
- for (quint32 i = 0; i < count; ++i) {
- qint16 x, y;
- stream >> x;
- stream >> y;
- aPoints.append( QPoint( x, y ) );
- }
- mOutput->polyLine16( bounds, aPoints );
- }
- break;
- case EMR_POLYBEZIERTO16:
- {
- QRect bounds;
- stream >> bounds;
- quint32 count;
- stream >> count;
- QList<QPoint> aPoints;
- for (quint32 i = 0; i < count; ++i) {
- qint16 x, y;
- stream >> x;
- stream >> y;
- aPoints.append( QPoint( x, y ) );
- }
- mOutput->polyBezierTo16( bounds, aPoints );
- }
- break;
- case EMR_POLYLINETO16:
- {
- QRect bounds;
- stream >> bounds;
- quint32 count;
- stream >> count;
- QList<QPoint> aPoints;
- for (quint32 i = 0; i < count; ++i) {
- qint16 x, y;
- stream >> x;
- stream >> y;
- aPoints.append( QPoint( x, y ) );
- }
- mOutput->polyLineTo16( bounds, aPoints );
- }
- break;
- case EMR_POLYPOLYLINE16:
- {
- QRect bounds;
- stream >> bounds;
- quint32 numberOfPolylines;
- stream >> numberOfPolylines;
- quint32 count; // number of points in all polylines
- stream >> count;
- QList< QVector< QPoint > > aPoints;
- for ( quint32 i = 0; i < numberOfPolylines; ++i ) {
- quint32 polylinePointCount;
- stream >> polylinePointCount;
- QVector<QPoint> polylinePoints( polylinePointCount );
- aPoints.append( polylinePoints );
- }
- for ( quint32 i = 0; i < numberOfPolylines; ++i ) {
- for ( int j = 0; j < aPoints[i].size(); ++j ) {
- qint16 x, y;
- stream >> x >> y;
- aPoints[i].replace( j, QPoint( x, y ) );
- }
- }
- mOutput->polyPolyLine16( bounds, aPoints );
- }
- break;
- case EMR_POLYPOLYGON16:
- {
- QRect bounds;
- stream >> bounds;
- quint32 numberOfPolygons;
- stream >> numberOfPolygons;
- quint32 count; // number of points in all polygons
- stream >> count;
- QList< QVector< QPoint > > aPoints;
- for ( quint32 i = 0; i < numberOfPolygons; ++i ) {
- quint32 polygonPointCount;
- stream >> polygonPointCount;
- QVector<QPoint> polygonPoints( polygonPointCount );
- aPoints.append( polygonPoints );
- }
- for ( quint32 i = 0; i < numberOfPolygons; ++i ) {
- for ( int j = 0; j < aPoints[i].size(); ++j ) {
- qint16 x, y;
- stream >> x >> y;
- aPoints[i].replace( j, QPoint( x, y ) );
- }
- }
- mOutput->polyPolygon16( bounds, aPoints );
- }
- break;
- case EMR_CREATEMONOBRUSH:
- // MS-EMF 2.3.7.5: EMR_CREATEMONOBRUSH Record
- {
-#if DEBUG_EMFPARSER
- debugVectorImage << "EMR_CREATEMONOBRUSH ============================";
-#endif
-
- quint32 ihBrush; // Index in the EMF Object Table
- stream >> ihBrush;
- quint32 usage; // DIBColors enumeration
- stream >> usage;
- quint32 offBmi; // Offset of the DIB header
- stream >> offBmi;
- quint32 cbBmi; // Size of the DIB header
- stream >> cbBmi;
- quint32 offBits; // Offset of the bitmap
- stream >> offBits;
- quint32 cbBits; // Size of the bitmap
- stream >> cbBits;
-
-#if DEBUG_EMFPARSER
- debugVectorImage << "index:" << ihBrush;
- debugVectorImage << "DIBColors enum:" << usage;
- debugVectorImage << "header offset:" << offBmi;
- debugVectorImage << "header size: " << cbBmi;
- debugVectorImage << "bitmap offset:" << offBits;
- debugVectorImage << "bitmap size: " << cbBits;
-#endif
-
- // FIXME: Handle the usage DIBColors info.
- Bitmap bitmap(stream, size, 8 + 6 * 4, // header + 6 ints
- offBmi, cbBmi, offBits, cbBits);
-
- mOutput->createMonoBrush(ihBrush, &bitmap);
-
- }
- break;
- case EMR_EXTCREATEPEN:
- {
- quint32 ihPen;
- stream >> ihPen;
-
- quint32 offBmi, cbBmi, offBits, cbBits;
- stream >> offBmi >> cbBmi >> offBits >> cbBits;
-
- quint32 penStyle;
- stream >> penStyle;
-
- quint32 width;
- stream >> width;
-
- quint32 brushStyle;
- stream >> brushStyle;
-
- quint8 red, green, blue, reserved;
- stream >> red >> green >> blue;
- stream >> reserved; // unused;
-
- // TODO: There is more stuff to parse here
-
- // TODO: this needs to go to an extCreatePen() output method
- mOutput->createPen( ihPen, penStyle, width, 0, red, green, blue, reserved );
- soakBytes( stream, size-44 );
- }
- break;
- case EMR_SETICMMODE:
- {
- quint32 icmMode;
- stream >> icmMode;
- //debugVectorImage << "EMR_SETICMMODE:" << icmMode;
- }
- break;
- default:
-#if DEBUG_EMFPARSER
- debugVectorImage << "unknown record type:" << type;
-#endif
- soakBytes(stream, size - 8); // because we already took 8.
- }
-#endif
-
- return true;
-}
-
-}
diff --git a/libs/vectorimage/libemf/EmfParser.h b/libs/vectorimage/libemf/EmfParser.h
deleted file mode 100644
index 0bd7072cc7..0000000000
--- a/libs/vectorimage/libemf/EmfParser.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFPARSER_H
-#define EMFPARSER_H
-
-#include "kritavectorimage_export.h"
-
-#include "EmfOutput.h"
-
-#include <QString>
-#include <QRect> // also provides QSize
-
-/**
- \file
-
- Primary definitions for EMF parser
-*/
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-/**
- %Parser for an EMF format file
- */
-class KRITAVECTORIMAGE_EXPORT Parser
-{
-public:
- Parser();
- ~Parser();
-
- /**
- * Load an EMF file
- *
- * \param fileName the name of the file to load
- *
- * \return true on successful load, or false on failure
- */
- bool load( const QString &fileName );
- /**
- * Load an EMF file
- *
- * \param contents a QByteArray containing the contents of the EMF.
- *
- * \return true on successful load, or false on failure
- */
- bool load(const QByteArray &contents);
-
-
- /**
- * Load an EMF file from a stream
- *
- * \param stream the stream to read from
- *
- * \return true on successful load, or false on failure
- */
- bool loadFromStream( QDataStream &stream );
-
- /**
- Set the output strategy for the parserr
-
- \param output pointer to a strategy implementation
- */
- void setOutput( AbstractOutput *output );
-
-private:
- // read a single EMF record
- bool readRecord( QDataStream &stream );
-
- // temporary function to soak up unneeded bytes
- void soakBytes( QDataStream &stream, int numBytes );
-
- // temporary function to dump output bytes
- void outputBytes( QDataStream &stream, int numBytes );
-
- // Pointer to the output strategy
- AbstractOutput *mOutput;
-};
-
-}
-
-#endif
diff --git a/libs/vectorimage/libemf/EmfRecords.cpp b/libs/vectorimage/libemf/EmfRecords.cpp
deleted file mode 100644
index 8ac59b08b8..0000000000
--- a/libs/vectorimage/libemf/EmfRecords.cpp
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfRecords.h"
-
-#include "EmfEnums.h"
-#include "EmfObjects.h"
-#include "Bitmap.h"
-
-#include <VectorImageDebug.h>
-
-namespace Libemf
-{
-
-
-/*****************************************************************************/
-
-
-BitBltRecord::BitBltRecord( QDataStream &stream, quint32 recordSize )
- : m_bitmap(0)
-{
- //debugVectorImage << "stream position at the start: " << stream.device()->pos();
- //debugVectorImage << "record size: " << recordSize;
-
- stream >> m_bounds;
-
- stream >> m_xDest; // x, y of upper left corner of the destination.
- stream >> m_yDest;
- stream >> m_cxDest; // width, height of the rectangle in logical coords.
- stream >> m_cyDest;
- //debugVectorImage << "Destination" << m_xDest << m_yDest << m_cxDest << m_cyDest;
-
- stream >> m_BitBltRasterOperation;
- //debugVectorImage << "bitblt raster operation:" << hex << m_BitBltRasterOperation << dec;
-
- stream >> m_xSrc; // x, y of the source
- stream >> m_ySrc;
- //debugVectorImage << "Source" << m_xSrc << m_ySrc;
-
- //debugVectorImage << "position before the matrix: " << stream.device()->pos();
- stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
- float M11, M12, M21, M22, Dx, Dy;
- stream >> M11; // Transformation matrix
- stream >> M12;
- stream >> M21;
- stream >> M22;
- stream >> Dx;
- stream >> Dy;
- m_XFormSrc = QTransform( M11, M12, M21, M22, Dx, Dy );
- //debugVectorImage << "Matrix" << m_XFormSrc;
- //debugVectorImage << "position after the matrix: " << stream.device()->pos();
-
- stream >> m_red >> m_green >> m_blue >> m_reserved;
- //debugVectorImage << "Background color" << m_red << m_green << m_blue << m_reserved;
- //debugVectorImage << "position after background color: " << stream.device()->pos();
-
- stream >> m_UsageSrc;
- //debugVectorImage << "Color table interpretation" << m_UsageSrc;
-
- stream >> m_offBmiSrc; // Offset to start of bitmap header from start of record
- stream >> m_cbBmiSrc; // Size of source bitmap header
- stream >> m_offBitsSrc; // Offset to source bitmap from start of record
- stream >> m_cbBitsSrc; // Size of source bitmap
-#if 0
- debugVectorImage << "header offset:" << m_offBmiSrc;
- debugVectorImage << "header size: " << m_cbBmiSrc;
- debugVectorImage << "bitmap offset:" << m_offBitsSrc;
- debugVectorImage << "bitmap size: " << m_cbBitsSrc;
-#endif
-
- //debugVectorImage << "stream position before the image: " << stream.device()->pos();
- if (m_cbBmiSrc > 0) {
- m_bitmap = new Bitmap( stream, recordSize, 8 + 23 * 4, // header + 23 ints
- m_offBmiSrc, m_cbBmiSrc,
- m_offBitsSrc, m_cbBitsSrc );
- }
-
- //debugVectorImage << "stream position at the end: " << stream.device()->pos();
-}
-
-BitBltRecord::~BitBltRecord()
-{
- delete m_bitmap;
-}
-
-bool BitBltRecord::hasImage() const
-{
- return m_bitmap && m_bitmap->hasImage();
- //return ( ( m_cbBmiSrc != 0 ) && ( m_cbBitsSrc != 0 ) );
-}
-
-QImage BitBltRecord::image()
-{
- return m_bitmap->image();
-#if 0
- if ( ! hasImage() ) {
- return 0;
- }
-
- if ( m_image != 0 ) {
- return m_image;
- }
-
- QImage::Format format = QImage::Format_Invalid;
- if ( m_BmiSrc->bitCount() == BI_BITCOUNT_4 ) {
- if ( m_BmiSrc->compression() == 0x00 ) {
- format = QImage::Format_RGB555;
- } else {
- debugVectorImage << "Unexpected compression format for BI_BITCOUNT_4:"
- << m_BmiSrc->compression();
- Q_ASSERT( 0 );
- }
- } else if ( m_BmiSrc->bitCount() == BI_BITCOUNT_5 ) {
- format = QImage::Format_RGB888;
- } else {
- debugVectorImage << "Unexpected format:" << m_BmiSrc->bitCount();
- Q_ASSERT( 0 );
- }
- m_image = new QImage( (const uchar*)m_imageData.constData(),
- m_BmiSrc->width(), m_BmiSrc->height(), format );
-
- return m_image;
-#endif
-}
-
-/*****************************************************************************/
-StretchDiBitsRecord::StretchDiBitsRecord( QDataStream &stream, quint32 recordSize )
- : m_bitmap(0)
-{
- //debugVectorImage << "stream position at the start: " << stream.device()->pos();
- //debugVectorImage << "recordSize =" << recordSize;
-
- stream >> m_Bounds;
- stream >> m_xDest;
- stream >> m_yDest;
- stream >> m_xSrc;
- stream >> m_ySrc;
- stream >> m_cxSrc;
- stream >> m_cySrc;
-
- stream >> m_offBmiSrc;
- stream >> m_cbBmiSrc;
- stream >> m_offBitsSrc;
- stream >> m_cbBitsSrc;
-
- stream >> m_UsageSrc; // How to interpret color table values.
- stream >> m_BitBltRasterOperation;
- stream >> m_cxDest;
- stream >> m_cyDest;
-#if 0
- debugVectorImage << "bounds:" << m_Bounds;
- debugVectorImage << "destination:" << QPoint(m_xDest, m_yDest) << QSize(m_cxDest, m_cyDest);
- debugVectorImage << "source:" << QPoint(m_xSrc, m_ySrc) << QSize(m_cxSrc, m_cySrc);
- debugVectorImage << "header offset:" << m_offBmiSrc;
- debugVectorImage << "header size: " << m_cbBmiSrc;
- debugVectorImage << "bitmap offset:" << m_offBitsSrc;
- debugVectorImage << "bitmap size: " << m_cbBitsSrc;
-
- debugVectorImage << "m_BitBltRasterOperation =" << hex << m_BitBltRasterOperation << dec;
-#endif
-
- //debugVectorImage << "stream position before the image: " << stream.device()->pos();
- if (m_cbBmiSrc > 0) {
- m_bitmap = new Bitmap( stream, recordSize, 8 + 18 * 4, // header + 18 ints
- m_offBmiSrc, m_cbBmiSrc,
- m_offBitsSrc, m_cbBitsSrc );
- }
-
- //debugVectorImage << "stream position at the end: " << stream.device()->pos();
-#if 0
- // Read away those bytes that precede the header. These are undefined
- // according to the spec. 80 is the size of the record above.
- qint32 dummy;
- int padding = 0;
- while (m_offBmiSrc - padding > 80) {
- stream >> dummy;
- padding += 4;
- }
- m_BmiSrc = new BitmapHeader( stream, m_cbBmiSrc );
-
- // 40 is the size of the header record.
- while (m_offBitsSrc - padding > 80 + 40) {
- stream >> dummy;
- padding += 4;
- }
- m_imageData.resize( m_cbBitsSrc );
- stream.readRawData( m_imageData.data(), m_cbBitsSrc );
-#endif
-}
-
-StretchDiBitsRecord::~StretchDiBitsRecord()
-{
- delete m_bitmap;
-}
-
-QRect StretchDiBitsRecord::bounds() const
-{
- return m_Bounds;
-}
-
-bool StretchDiBitsRecord::hasImage() const
-{
- return m_bitmap && m_bitmap->hasImage();
-}
-
-QImage StretchDiBitsRecord::image()
-{
- return m_bitmap->image();
-#if 0
- if ( m_image != 0 ) {
- return m_image;
- }
-
- QImage::Format format = QImage::Format_Invalid;
-
- // Start by determining which QImage format we are going to use.
- if (m_BmiSrc->bitCount() == BI_BITCOUNT_1) {
- format = QImage::Format_Mono;
- } else if ( m_BmiSrc->bitCount() == BI_BITCOUNT_4 ) {
- if ( m_BmiSrc->compression() == BI_RGB ) {
- format = QImage::Format_RGB555;
- } else {
- debugVectorImage << "Unexpected compression format for BI_BITCOUNT_4:"
- << m_BmiSrc->compression();
- Q_ASSERT( 0 );
- }
- } else if ( m_BmiSrc->bitCount() == BI_BITCOUNT_5 ) {
- format = QImage::Format_RGB888;
- } else {
- debugVectorImage << "Unexpected format:" << m_BmiSrc->bitCount();
- //Q_ASSERT(0);
- }
-
- // According to MS-WMF 2.2.2.3, the sign of the height decides if
- // this is a compressed bitmap or not.
- if (m_BmiSrc->height() > 0) {
- // This bitmap is a top-down bitmap without compression.
- m_image = new QImage( (const uchar*)m_imageData.constData(),
- m_BmiSrc->width(), m_BmiSrc->height(), format );
-
- // The WMF images are in the BGR color order.
- if (format == QImage::Format_RGB888)
- *m_image = m_image->rgbSwapped();
-
- // We have to mirror this bitmap in the X axis since WMF images are stored bottom-up.
- *m_image = m_image->mirrored(false, true);
- } else {
- // This bitmap is a bottom-up bitmap which uses compression.
- switch (m_BmiSrc->compression()) {
- case BI_RGB:
- m_image = new QImage( (const uchar*)m_imageData.constData(),
- m_BmiSrc->width(), -m_BmiSrc->height(), format );
- // The WMF images are in the BGR color order.
- *m_image = m_image->rgbSwapped();
- break;
-
- // These compressions are not yet supported, so return an empty image.
- case BI_RLE8:
- case BI_RLE4:
- case BI_BITFIELDS:
- case BI_JPEG:
- case BI_PNG:
- case BI_CMYK:
- case BI_CMYKRLE8:
- case BI_CMYKRLE4:
- default:
- m_image = new QImage(m_BmiSrc->width(), m_BmiSrc->height(), format);
- break;
- }
- }
-
- return m_image;
-#endif
-}
-
-/*****************************************************************************/
-ExtCreateFontIndirectWRecord::ExtCreateFontIndirectWRecord( QDataStream &stream, quint32 size )
-{
- stream >> m_ihFonts;
- size -= 12;
-
- // TODO: Check size, we might need to do a LogFontExDv parse
- stream >> m_height;
- stream >> m_width;
- size -= 8;
-
- stream >> m_escapement;
- size -= 4;
-
- stream >> m_orientation;
- size -= 4;
-
- stream >> m_weight;
- size -= 4;
-
- stream >> m_italic;
- stream >> m_underline;
- stream >> m_strikeout;
- stream >> m_charSet;
- size -= 4;
-
- stream >> m_outPrecision;
- stream >> m_clipPrecision;
- stream >> m_quality;
- stream >> m_pitchAndFamily;
- size -= 4;
-
- QChar myChar[64];
- for ( int i = 0; i < 32; ++i ) {
- stream >> myChar[i];
- }
- size -= 64;
-
- for ( int i = 0; i < 32; ++i ) {
- if ( ! myChar[i].isNull() ) {
- m_facename.append( myChar[i] );
- }
- }
-
-#if 0
- for ( int i = 0; i < 64; ++i ) {
- stream >> myChar[i];
- }
- size -= 128;
-
- for ( int i = 0; i < 64; ++i ) {
- if ( ! myChar[i].isNull() ) {
- m_fullName.append( myChar[i] );
- }
- }
- debugVectorImage << "fullName:" << m_fullName;
-
- for ( int i = 0; i < 32; ++i ) {
- stream >> myChar[i];
- }
- size -= 64;
- for ( int i = 0; i < 32; ++i ) {
- if ( ! myChar[i].isNull() ) {
- m_style.append( myChar[i] );
- }
- }
- debugVectorImage << "style:" << m_style;
-
- for ( int i = 0; i < 32; ++i ) {
- stream >> myChar[i];
- }
- size -= 64;
- for ( int i = 0; i < 32; ++i ) {
- if ( ! myChar[i].isNull() ) {
- m_script.append( myChar[i] );
- }
- }
- debugVectorImage << "script:" << m_script;
-#endif
- soakBytes( stream, size ); // rest of the record.
-}
-
-ExtCreateFontIndirectWRecord::~ExtCreateFontIndirectWRecord()
-{
-}
-
-void ExtCreateFontIndirectWRecord::soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-}
diff --git a/libs/vectorimage/libemf/EmfRecords.h b/libs/vectorimage/libemf/EmfRecords.h
deleted file mode 100644
index e2f0bef9a0..0000000000
--- a/libs/vectorimage/libemf/EmfRecords.h
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFRECORDS_H
-#define EMFRECORDS_H
-
-#include <QDataStream>
-#include <QColor>
-#include <QImage>
-#include <QRect> // also provides QSize
-#include <QString>
-
-#include "Bitmap.h"
-/**
- \file
-
- Primary definitions for EMF Records
-*/
-
-/**
- Namespace for Enhanced Metafile (EMF) classes
-*/
-namespace Libemf
-{
-
-class EmrTextObject;
-
-
-/*****************************************************************************/
-
-/**
- Simple representation of an EMR_BITBLT record
-
- See MS-EMF Section 2.3.1.2 for details
-*/
-class BitBltRecord
-{
-public:
- /**
- Constructor for record type
-
- \param stream the stream to read the record structure from
- \param recordSize the size of one record
- */
- BitBltRecord( QDataStream &stream, quint32 recordSize );
- ~BitBltRecord();
-
- /**
- The X origin of the destination rectangle
- */
- qint32 xDest() const { return m_xDest; };
-
- /**
- The Y origin of the destination rectangle
- */
- qint32 yDest() const { return m_yDest; };
-
- /**
- The width of the destination rectangle
- */
- qint32 cxDest() const { return m_cxDest; };
-
- /**
- The height of the destination rectangle
- */
- qint32 cyDest() const { return m_cyDest; };
-
- quint32 rasterOperation() const { return m_BitBltRasterOperation; }
-
- QColor bkColorSrc() const { return QColor(m_red, m_green, m_blue, m_reserved); }
-
- /**
- The destination rectangle
- */
- QRect destinationRectangle() const { return QRect( xDest(), yDest(), cxDest(), cyDest() ); };
-
- /**
- The image to display
- */
- QImage image();
-
- /**
- Whether there is a valid image in this BitBlt record
- */
- bool hasImage() const;
-
-private:
- // No copying for now, because we will get into trouble with the pointers.
- // The remedy is to write a real operator=() and BitBltRecord(BitBltRecord&).
- explicit BitBltRecord(BitBltRecord&);
- BitBltRecord &operator=(BitBltRecord&);
-
-private:
- QRect m_bounds;
- qint32 m_xDest;
- qint32 m_yDest;
- qint32 m_cxDest;
- qint32 m_cyDest;
- quint32 m_BitBltRasterOperation;
- qint32 m_xSrc;
- qint32 m_ySrc;
- QTransform m_XFormSrc;
-
- // Background color - elements below
- quint8 m_red;
- quint8 m_green;
- quint8 m_blue;
- quint8 m_reserved;
-
- // Color table interpretation
- quint32 m_UsageSrc;
-
- // The source bitmap meta data
- quint32 m_offBmiSrc;
- quint32 m_cbBmiSrc;
- quint32 m_offBitsSrc;
- quint32 m_cbBitsSrc;
-
- Bitmap *m_bitmap; // The source bitmap
-
- //QByteArray m_imageData;
- //QImage *m_image;
-};
-
-/*****************************************************************************/
-
-/**
- Simple representation of an EMR_STRETCHDIBITS record
-
- See MS-EMF Section 2.3.1.7 for details
-*/
-class StretchDiBitsRecord
-{
-public:
- /**
- Constructor for record type
-
- \param stream the stream to read the record structure from
- \param recordSize the size of one record
- */
- StretchDiBitsRecord( QDataStream &stream, quint32 recordSize );
- ~StretchDiBitsRecord();
-
- /**
- The bounds of the affected area, in device units
- */
- QRect bounds() const;
-
- /**
- The X origin of the destination rectangle
- */
- qint32 xDest() const { return m_xDest; };
-
- /**
- The Y origin of the destination rectangle
- */
- qint32 yDest() const { return m_yDest; };
-
- /**
- The width of the destination rectangle
- */
- qint32 cxDest() const { return m_cxDest; };
-
- /**
- The height of the destination rectangle
- */
- qint32 cyDest() const { return m_cyDest; };
-
- /**
- The destination rectangle
- */
- QRect destinationRectangle() const { return QRect( xDest(), yDest(), cxDest(), cyDest() ); };
-
- /**
- The X origin of the source rectangle
- */
- qint32 xSrc() const { return m_xSrc; };
-
- /**
- The Y origin of the source rectangle
- */
- qint32 ySrc() const { return m_ySrc; };
-
- /**
- The width of the source rectangle
- */
- qint32 cxSrc() const { return m_cxSrc; };
-
- /**
- The height of the source rectangle
- */
- qint32 cySrc() const { return m_cySrc; };
-
- /**
- The source rectangle
- */
- QRect sourceRectangle() const { return QRect( xSrc(), ySrc(), cxSrc(), cySrc() ); };
-
- /**
- The raster operation
- */
- qint32 rasterOperation() const { return m_BitBltRasterOperation; };
-
- quint32 usageSrc() const { return m_UsageSrc; };
-
- /**
- The image to display
- */
- QImage image();
- /**
- Whether there is a valid image in this StretchDiBitsRecord record
- */
- bool hasImage() const;
-
-private:
- // No copying for now, because we will get into trouble with the pointers.
- // The remedy is to write a real operator=() and StretchDiBitsRecord(StretchDiBitsRecord&).
- explicit StretchDiBitsRecord(StretchDiBitsRecord&);
- StretchDiBitsRecord &operator=(StretchDiBitsRecord&);
-
-private:
- QRect m_Bounds;
- qint32 m_xDest;
- qint32 m_yDest;
- qint32 m_xSrc;
- qint32 m_ySrc;
- qint32 m_cxSrc;
- qint32 m_cySrc;
- quint32 m_offBmiSrc;
- quint32 m_cbBmiSrc;
- quint32 m_offBitsSrc;
- quint32 m_cbBitsSrc;
- quint32 m_UsageSrc;
- quint32 m_BitBltRasterOperation;
- qint32 m_cxDest;
- qint32 m_cyDest;
-
- Bitmap *m_bitmap; // The source bitmap
-};
-
-/*****************************************************************************/
-
-/**
- Simple representation of an EMR_EXTCREATEFONTINDIRECTW record
-
- See MS-EMF Section 2.3.7.8 for details
-*/
-class ExtCreateFontIndirectWRecord
-{
-public:
- /**
- Constructor for record type
-
- \param stream the stream to read the record structure from
- \param size the number of bytes in this record
- */
- ExtCreateFontIndirectWRecord( QDataStream &stream, quint32 size );
- ~ExtCreateFontIndirectWRecord();
-
-
- /**
- The font handle index
- */
- quint32 ihFonts() const { return m_ihFonts; };
-
- /**
- The height of the font
- */
- qint32 height() const { return m_height; };
-
- /**
- Whether this is a italic font
- */
- quint8 italic() const { return m_italic; };
-
- /**
- Whether this is a underlined font
- */
- quint8 underline() const { return m_underline; };
-
- /**
- The weight of this font
- */
- quint32 weight() const { return m_weight; };
-
- /**
- The name of the font face
- */
- QString fontFace() const { return m_facename; };
-
-private:
- quint32 m_ihFonts;
-
- qint32 m_height;
- qint32 m_width;
- qint32 m_escapement;
- qint32 m_orientation;
- qint32 m_weight;
- quint8 m_italic;
- quint8 m_underline;
- quint8 m_strikeout;
- quint8 m_charSet;
- quint8 m_outPrecision;
- quint8 m_clipPrecision;
- quint8 m_quality;
- quint8 m_pitchAndFamily;
- QString m_facename;
- QString m_fullName;
- QString m_style;
- QString m_script;
-
- // Routine to throw away a specific number of bytes
- void soakBytes( QDataStream &stream, int numBytes );
-};
-
-}
-
-#endif
diff --git a/libs/vectorimage/libemf/TODO b/libs/vectorimage/libemf/TODO
deleted file mode 100644
index cde117e811..0000000000
--- a/libs/vectorimage/libemf/TODO
+++ /dev/null
@@ -1,33 +0,0 @@
-Complete doxygen \mainpage
-
-Check that our constructor / destructor / copy constructor arrangement for AbstractOutput follows best practice.
-
-Move all the debug stuff out into DebugOutput class
-
-Complete handling of EMR_EOF
-
-Figure out why the pyemf tests have the wrong number of records.
-
-Create a TextOutput class
-
-Complete PainterOutput class (using QPainter)
-
-Create a RecorderOutput class
-
-Use RecorderOutput with an EMF writer to ensure we can "round trip" EMF files.
-
-Get examples of EMF files that use Extension 1 and 2, add to the unit tests.
-
-Fix temporary parser hacks:
- - EXTCREATEPEN
-
-Make emf_demo work properly.
- - Allow selection of zoom level
-
-Implement Painter operations:
- - extTextOutW not yet done
- - setMapMode
- - remaining transform types
- - Handle remainder issues in CreateBrushIndirect
- - Handle SYSTEM_FONT stock object
- - Handle DEVICE_DEFAULT_FONT stock object
diff --git a/libs/vectorimage/libemf/demo/CMakeLists.txt b/libs/vectorimage/libemf/demo/CMakeLists.txt
deleted file mode 100644
index 8ca2b555aa..0000000000
--- a/libs/vectorimage/libemf/demo/CMakeLists.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-include_directories(
- ${ENHMETAFILE_SOURCE_DIR}/demo/
- ${CMAKE_CURRENT_BINARY_DIR}
-
-)
-
-link_directories(${ENHMETAFILE_BINARY_DIR})
-
-set( emf_demo_bin_SRCS
- emf_demo.cpp
- EmfViewer.cpp
-
- ../EmfRecords.cpp
- ../EmfHeader.cpp
- ../EmfParser.cpp
- ../EmfOutput.cpp
- ../EmfOutputDebugStrategy.cpp
- ../EmfOutputPainterStrategy.cpp
-)
-
-qt4_automoc( ${emf_demo_bin_SRCS} )
-add_executable( emf_demo ${emf_demo_bin_SRCS} )
-
-#TARGET_LINK_LIBRARIES( emf_demo libemf Qt5::Gui Qt5::Test )
-target_link_libraries( emf_demo Qt5::Gui Qt5::Test )
-
-
diff --git a/libs/vectorimage/libemf/demo/EmfViewer.cpp b/libs/vectorimage/libemf/demo/EmfViewer.cpp
deleted file mode 100644
index 5024b19f57..0000000000
--- a/libs/vectorimage/libemf/demo/EmfViewer.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2008 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfViewer.h"
-
-#include <QApplication>
-#include <QFileDialog>
-#include <QMenuBar>
-
-#include "../EmfOutputPainterStrategy.h"
-
-
-EmfViewer::EmfViewer( QSize &size )
- : QMainWindow()
-{
- m_size = size;
-
- setWindowTitle( "EMF Demo Viewer" );
-
- QMenu *fileMenu = menuBar()->addMenu( "&File" );
-
- // The "Open" action
- m_fileOpenAction->setShortcut( Qt::CTRL + Qt::Key_O );
- m_fileOpenAction = fileMenu->addAction( "&Open", this,
- SLOT(slotOpenFile()) );
-
- fileMenu->addSeparator();
-
- // The "Quit" action
- m_fileQuitAction->setShortcut( Qt::CTRL + Qt::Key_Q );
- m_fileQuitAction = fileMenu->addAction( "&Quit", qApp,
- SLOT(closeAllWindows()) );
-
- // Set a suitably large size.
- resize( m_size + QSize( 50, 50 ) );
-
- // ...and finably create the label that will show everything.
- m_label = new QLabel(this);
- setCentralWidget( m_label );
-}
-
-EmfViewer::~EmfViewer()
-{
-}
-
-
-void EmfViewer::loadFile( const QString &fileName )
-{
- Parser parser;
-
- // The image that the EMF parser should paint on.
- QImage image( m_size, QImage::Format_ARGB32_Premultiplied );
- QPainter painter( &image );
-
- OutputPainterStrategy output( painter, m_size );
- parser.setOutput( &output );
- parser.load( QString( fileName ) );
-
- QPixmap pixmap = QPixmap::fromImage( image );
- m_label->setPixmap( pixmap.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
-
- m_label->show();
-
-
-}
-
-// ----------------------------------------------------------------
-// Slots
-
-
-void EmfViewer::slotOpenFile()
-{
- QString fileName = QFileDialog::getOpenFileName(this, "Open EMF document", QDir::homePath(), "EMF Documents (*.emf)" );
- if (fileName.isEmpty()) {
- return;
- }
-
- loadFile( fileName );
-}
-
-
-#include <EmfViewer.moc>
-
-
diff --git a/libs/vectorimage/libemf/demo/EmfViewer.h b/libs/vectorimage/libemf/demo/EmfViewer.h
deleted file mode 100644
index e46b20734f..0000000000
--- a/libs/vectorimage/libemf/demo/EmfViewer.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef EMFVIEWER_H
-#define EMFVIEWER_H
-
-#include "../EmfParser.h"
-
-#include <QLabel>
-#include <QMainWindow>
-
-using namespace Libemf;
-
-class EmfViewer : public QMainWindow
-{
- Q_OBJECT
-
-public:
- explicit EmfViewer(QSize &size);
- ~EmfViewer();
-
- void loadFile( const QString &fileName );
-
-private Q_SLOTS:
- void slotOpenFile();
-
-private:
-
- // Actions
- QAction *m_fileOpenAction;
- QAction *m_fileQuitAction;
-
- // The central widget
- QLabel *m_label;
-
- QSize m_size;
-};
-
-#endif
diff --git a/libs/vectorimage/libemf/demo/emf_demo.cpp b/libs/vectorimage/libemf/demo/emf_demo.cpp
deleted file mode 100644
index 5edbbc99f9..0000000000
--- a/libs/vectorimage/libemf/demo/emf_demo.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "EmfViewer.h"
-
-#include <QApplication>
-
-using namespace Libemf;
-
-int main( int argc, char **argv )
-{
- QApplication app( argc, argv );
-
- QSize size( 1280, 800 );
- EmfViewer viewer( size );
- viewer.show();
-
- if ( argc > 1 ) {
- QString filename( argv[1] );
- viewer.loadFile( filename );
- }
-
- app.exec();
-}
diff --git a/libs/vectorimage/libemf/mainpage.doxy b/libs/vectorimage/libemf/mainpage.doxy
deleted file mode 100644
index 2bb60aba53..0000000000
--- a/libs/vectorimage/libemf/mainpage.doxy
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- \mainpage
-
- EmfParser is a parser and pluggable backend for the Microsoft
- Enhanced Metafile format (EMF), which essentially provides vector
- graphics operations (heavily based on the GDI programming
- interface) embedded in a file.
-*/
diff --git a/libs/vectorimage/libemf/tests/CMakeLists.txt b/libs/vectorimage/libemf/tests/CMakeLists.txt
deleted file mode 100644
index e635d2119f..0000000000
--- a/libs/vectorimage/libemf/tests/CMakeLists.txt
+++ /dev/null
@@ -1,63 +0,0 @@
-include_directories(
- ${ENHMETAFILE_SOURCE_DIR}/tests/
- ${CMAKE_CURRENT_BINARY_DIR}
-
-)
-
-link_directories(${ENHMETAFILE_BINARY_DIR})
-
-set( snp_tests_bin_SRCS snp_tests.cpp )
-qt4_automoc( ${snp_tests_bin_SRCS} )
-add_executable( snp_tests ${snp_tests_bin_SRCS} )
-target_link_libraries( snp_tests Qt5::Gui Qt5::Test EnhMetaFile )
-add_test( SnpTests ${CMAKE_CURRENT_BINARY_DIR}/snp_tests )
-configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snp-1.emf ${CMAKE_CURRENT_BINARY_DIR}/snp-1.emf COPYONLY )
-configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snp-2.emf ${CMAKE_CURRENT_BINARY_DIR}/snp-2.emf COPYONLY )
-configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snp-3.emf ${CMAKE_CURRENT_BINARY_DIR}/snp-3.emf COPYONLY )
-
-set( no_such_bin_SRCS no_such.cpp )
-qt4_automoc( ${no_such_bin_SRCS} )
-add_executable( no_such_file ${no_such_bin_SRCS} )
-target_link_libraries( no_such_file Qt5::Gui Qt5::Test EnhMetaFile )
-add_test( NoSuchFile ${CMAKE_CURRENT_BINARY_DIR}/no_such_file )
-
-set( bad_header_bin_SRCS bad_header.cpp )
-qt4_automoc( ${bad_header_bin_SRCS} )
-add_executable( bad_header ${bad_header_bin_SRCS} )
-target_link_libraries( bad_header Qt5::Gui Qt5::Test EnhMetaFile )
-add_test( BadHeader ${CMAKE_CURRENT_BINARY_DIR}/bad_header )
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cof.wmf ${CMAKE_CURRENT_BINARY_DIR}/cof.wmf COPYONLY)
-
-set( pyemf_tests_bin_SRCS pyemf_tests.cpp )
-qt4_automoc( ${pyemf_tests_bin_SRCS} )
-add_executable( pyemf_tests ${pyemf_tests_bin_SRCS} )
-target_link_libraries( pyemf_tests Qt5::Gui Qt5::Test EnhMetaFile )
-add_test(PyEmfTests ${CMAKE_CURRENT_BINARY_DIR}/pyemf_tests)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-1.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-1.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-arc-chord-pie.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-arc-chord-pie.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-deleteobject.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-deleteobject.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-drawing1.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-drawing1.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-fontbackground.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-fontbackground.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-optimize16bit.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-optimize16bit.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-paths1.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-paths1.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-poly1.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-poly1.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-poly2.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-poly2.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-selectclippath1.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-selectclippath1.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-setpixel.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-setpixel.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-viewport-window-origin.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-viewport-window-origin.emf COPYONLY)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyemf-worldtransform1.emf ${CMAKE_CURRENT_BINARY_DIR}/pyemf-worldtransform1.emf COPYONLY)
-
-set( visio_tests_bin_SRCS visio_tests.cpp )
-qt4_automoc( ${visio_tests_bin_SRCS} )
-add_executable( visio_tests ${visio_tests_bin_SRCS} )
-target_link_libraries( visio_tests Qt5::Gui Qt5::Test EnhMetaFile )
-add_test( VisioTests ${CMAKE_CURRENT_BINARY_DIR}/visio_tests )
-configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/visio-1.emf ${CMAKE_CURRENT_BINARY_DIR}/visio-1.emf COPYONLY )
-configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/visio-kde41.emf ${CMAKE_CURRENT_BINARY_DIR}/visio-kde41.emf COPYONLY )
-
-set( render_bin_SRCS render.cpp )
-qt4_automoc( ${render_bin_SRCS} )
-add_executable( render ${render_bin_SRCS} )
-target_link_libraries( render Qt5::Gui EnhMetaFile )
-add_test( QPainter-Render ${CMAKE_CURRENT_BINARY_DIR}/render )
-
diff --git a/libs/vectorimage/libemf/tests/bad_header.cpp b/libs/vectorimage/libemf/tests/bad_header.cpp
deleted file mode 100644
index f202db4fb6..0000000000
--- a/libs/vectorimage/libemf/tests/bad_header.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "bad_header.h"
-#include <EnhMetaFile.h>
-
-using namespace EnhancedMetafile;
-
-void BadHeader::checkWmfHeader()
-{
- QTest::ignoreMessage( QtWarningMsg, "Failed to parse header, perhaps not an EMF file" );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( ! parser.load( QString( "cof.wmf" ) ) );
-}
-
-QTEST_MAIN( BadHeader )
-#include <bad_header.moc>
diff --git a/libs/vectorimage/libemf/tests/bad_header.h b/libs/vectorimage/libemf/tests/bad_header.h
deleted file mode 100644
index 2c883ddedb..0000000000
--- a/libs/vectorimage/libemf/tests/bad_header.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef BAD_HEADER_H
-#define BAD_HEADER_H
-
-#include <QtTest>
-
-class BadHeader: public QObject
-{
- Q_OBJECT
-private Q_SLOTS:
- void checkWmfHeader();
-};
-
-#endif
diff --git a/libs/vectorimage/libemf/tests/cof.wmf b/libs/vectorimage/libemf/tests/cof.wmf
deleted file mode 100644
index c522e0751e..0000000000
Binary files a/libs/vectorimage/libemf/tests/cof.wmf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/no_such.cpp b/libs/vectorimage/libemf/tests/no_such.cpp
deleted file mode 100644
index a63e8f7f71..0000000000
--- a/libs/vectorimage/libemf/tests/no_such.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "no_such.h"
-#include <EnhMetaFile.h>
-
-using namespace EnhancedMetafile;
-
-void NoSuch::checkNonExisting()
-{
- QTest::ignoreMessage( QtWarningMsg, "Request to load file (nosuchfilename.emf) that does not exist" );
- Parser parser;
- QVERIFY( ! parser.load( QString( "nosuchfilename.emf" ) ) );
-}
-
-QTEST_MAIN( NoSuch )
-#include <no_such.moc>
diff --git a/libs/vectorimage/libemf/tests/no_such.h b/libs/vectorimage/libemf/tests/no_such.h
deleted file mode 100644
index 0f62b8768d..0000000000
--- a/libs/vectorimage/libemf/tests/no_such.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef NO_SUCH_H
-#define NO_SUCH_H
-
-#include <QtTest>
-
-class NoSuch: public QObject
-{
- Q_OBJECT
-private Q_SLOTS:
- void checkNonExisting();
-};
-
-#endif
diff --git a/libs/vectorimage/libemf/tests/pyemf-1.emf b/libs/vectorimage/libemf/tests/pyemf-1.emf
deleted file mode 100644
index cff4d5a75d..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-arc-chord-pie.emf b/libs/vectorimage/libemf/tests/pyemf-arc-chord-pie.emf
deleted file mode 100644
index 164ade189b..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-arc-chord-pie.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-deleteobject.emf b/libs/vectorimage/libemf/tests/pyemf-deleteobject.emf
deleted file mode 100644
index b8908c6564..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-deleteobject.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-drawing1.emf b/libs/vectorimage/libemf/tests/pyemf-drawing1.emf
deleted file mode 100644
index 5b4e48d034..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-drawing1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-fontbackground.emf b/libs/vectorimage/libemf/tests/pyemf-fontbackground.emf
deleted file mode 100644
index 44780a6c03..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-fontbackground.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-optimize16bit.emf b/libs/vectorimage/libemf/tests/pyemf-optimize16bit.emf
deleted file mode 100644
index 51eaac603d..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-optimize16bit.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-paths1.emf b/libs/vectorimage/libemf/tests/pyemf-paths1.emf
deleted file mode 100644
index 3bdedfa537..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-paths1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-poly1.emf b/libs/vectorimage/libemf/tests/pyemf-poly1.emf
deleted file mode 100644
index b155bee034..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-poly1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-poly2.emf b/libs/vectorimage/libemf/tests/pyemf-poly2.emf
deleted file mode 100644
index eae143ea64..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-poly2.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-selectclippath1.emf b/libs/vectorimage/libemf/tests/pyemf-selectclippath1.emf
deleted file mode 100644
index 7a89462658..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-selectclippath1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-setpixel.emf b/libs/vectorimage/libemf/tests/pyemf-setpixel.emf
deleted file mode 100644
index e4276ce192..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-setpixel.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-viewport-window-origin.emf b/libs/vectorimage/libemf/tests/pyemf-viewport-window-origin.emf
deleted file mode 100644
index c9bcd52e17..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-viewport-window-origin.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf-worldtransform1.emf b/libs/vectorimage/libemf/tests/pyemf-worldtransform1.emf
deleted file mode 100644
index 2e14735377..0000000000
Binary files a/libs/vectorimage/libemf/tests/pyemf-worldtransform1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/pyemf_tests.cpp b/libs/vectorimage/libemf/tests/pyemf_tests.cpp
deleted file mode 100644
index 40a49677d5..0000000000
--- a/libs/vectorimage/libemf/tests/pyemf_tests.cpp
+++ /dev/null
@@ -1,913 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "pyemf_tests.h"
-#include <EnhMetaFile.h>
-
-using namespace EnhancedMetafile;
-
-void PyEmfTests::test1()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 1 , penStyle: 0 width: 1 color: QColor(ARGB 1, 1, 0, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 2401x1801) (QPoint(0,1800) , QPoint(2400,0) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 2401x1801) (QPoint(0,0) , QPoint(2400,1800) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-1.emf") ) );
-}
-
-void PyEmfTests::testArcChordPie()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEBRUSHINDIRECT: 1 style: 0 Colour: QColor(ARGB 1, 0.498039, 0, 1) , Hatch: 4 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 2 , penStyle: 1 width: 1 color: QColor(ARGB 1, 0, 0.501961, 0.501961) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARC QRect(100,100 301x301) QPoint(100,100) QPoint(100,400) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CHORD QRect(400,400 301x301) QPoint(400,400) QPoint(400,700) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_PIE QRect(700,700 301x301) QPoint(700,700) QPoint(700,1000) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ELLIPSE: QRect(1000,700 301x301) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-arc-chord-pie.emf") ) );
-}
-
-void PyEmfTests::testDeleteObject()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(801, 601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 1 , penStyle: 0 width: 0 color: QColor(ARGB 1, 0, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 2 , penStyle: 0 width: 1 color: QColor(ARGB 1, 0.0627451, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 3 , penStyle: 0 width: 2 color: QColor(ARGB 1, 0.12549, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 4 , penStyle: 0 width: 3 color: QColor(ARGB 1, 0.188235, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 5 , penStyle: 0 width: 4 color: QColor(ARGB 1, 0.25098, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 6 , penStyle: 0 width: 5 color: QColor(ARGB 1, 0.313725, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 7 , penStyle: 0 width: 6 color: QColor(ARGB 1, 0.376471, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 8 , penStyle: 0 width: 7 color: QColor(ARGB 1, 0.439216, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 9 , penStyle: 0 width: 8 color: QColor(ARGB 1, 0.501961, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 10 , penStyle: 0 width: 9 color: QColor(ARGB 1, 0.564706, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 11 , penStyle: 0 width: 10 color: QColor(ARGB 1, 0.627451, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 12 , penStyle: 0 width: 11 color: QColor(ARGB 1, 0.690196, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 13 , penStyle: 0 width: 12 color: QColor(ARGB 1, 0.752941, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 14 , penStyle: 0 width: 13 color: QColor(ARGB 1, 0.815686, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 15 , penStyle: 0 width: 14 color: QColor(ARGB 1, 0.878431, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 16 , penStyle: 0 width: 15 color: QColor(ARGB 1, 0.941176, 0.12549, 0.25098) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 401x1) (QPoint(0,0) , QPoint(400,0) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,32 401x1) (QPoint(0,32) , QPoint(400,32) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,64 401x1) (QPoint(0,64) , QPoint(400,64) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 4 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,96 401x1) (QPoint(0,96) , QPoint(400,96) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 5 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,128 401x1) (QPoint(0,128) , QPoint(400,128) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 6 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,160 401x1) (QPoint(0,160) , QPoint(400,160) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 7 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,192 401x1) (QPoint(0,192) , QPoint(400,192) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 8 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,224 401x1) (QPoint(0,224) , QPoint(400,224) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 9 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,256 401x1) (QPoint(0,256) , QPoint(400,256) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 10 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,288 401x1) (QPoint(0,288) , QPoint(400,288) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 11 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,320 401x1) (QPoint(0,320) , QPoint(400,320) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 12 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,352 401x1) (QPoint(0,352) , QPoint(400,352) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 13 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,384 401x1) (QPoint(0,384) , QPoint(400,384) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 14 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,416 401x1) (QPoint(0,416) , QPoint(400,416) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 15 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,448 401x1) (QPoint(0,448) , QPoint(400,448) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 16 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,480 401x1) (QPoint(0,480) , QPoint(400,480) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_DELETEOBJECT: 5 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_DELETEOBJECT: 11 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_DELETEOBJECT: 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_DELETEOBJECT: 9 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_DELETEOBJECT: 16 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 16 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.941176, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 11 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.878431, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 9 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.815686, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 5 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.752941, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 3 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.690196, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 17 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.627451, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 18 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.564706, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 19 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.501961, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 20 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.439216, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 21 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.376471, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 22 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.313725, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 23 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.25098, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 24 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.188235, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 25 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.12549, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 26 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0.0627451, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 27 , penStyle: 1 width: 8 color: QColor(ARGB 1, 0.00392157, 0, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,0 401x1) (QPoint(400,0) , QPoint(800,0) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,32 401x1) (QPoint(400,32) , QPoint(800,32) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,64 401x1) (QPoint(400,64) , QPoint(800,64) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 4 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,96 401x1) (QPoint(400,96) , QPoint(800,96) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 5 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,128 401x1) (QPoint(400,128) , QPoint(800,128) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 6 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,160 401x1) (QPoint(400,160) , QPoint(800,160) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 7 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,192 401x1) (QPoint(400,192) , QPoint(800,192) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 8 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,224 401x1) (QPoint(400,224) , QPoint(800,224) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 9 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,256 401x1) (QPoint(400,256) , QPoint(800,256) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 10 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,288 401x1) (QPoint(400,288) , QPoint(800,288) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 11 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,320 401x1) (QPoint(400,320) , QPoint(800,320) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 12 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,352 401x1) (QPoint(400,352) , QPoint(800,352) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 13 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,384 401x1) (QPoint(400,384) , QPoint(800,384) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 14 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,416 401x1) (QPoint(400,416) , QPoint(800,416) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 15 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,448 401x1) (QPoint(400,448) , QPoint(800,448) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 16 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(400,480 401x1) (QPoint(400,480) , QPoint(800,480) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-deleteobject.emf") ) );
-}
-
-void PyEmfTests::testSetPixel()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(801, 601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(150,75) QColor(ARGB 1, 0.501961, 0.588235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(151,75) QColor(ARGB 1, 0.501961, 0.592157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(152,76) QColor(ARGB 1, 0.501961, 0.596078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(153,76) QColor(ARGB 1, 0.501961, 0.6, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(154,77) QColor(ARGB 1, 0.501961, 0.603922, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(155,77) QColor(ARGB 1, 0.501961, 0.607843, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(156,78) QColor(ARGB 1, 0.501961, 0.611765, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(157,78) QColor(ARGB 1, 0.501961, 0.615686, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(158,79) QColor(ARGB 1, 0.501961, 0.619608, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(159,79) QColor(ARGB 1, 0.501961, 0.623529, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(160,80) QColor(ARGB 1, 0.501961, 0.627451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(161,80) QColor(ARGB 1, 0.501961, 0.631373, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(162,81) QColor(ARGB 1, 0.501961, 0.635294, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(163,81) QColor(ARGB 1, 0.501961, 0.639216, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(164,82) QColor(ARGB 1, 0.501961, 0.643137, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(165,82) QColor(ARGB 1, 0.501961, 0.647059, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(166,83) QColor(ARGB 1, 0.501961, 0.65098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(167,83) QColor(ARGB 1, 0.501961, 0.654902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(168,84) QColor(ARGB 1, 0.501961, 0.658824, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(169,84) QColor(ARGB 1, 0.501961, 0.662745, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(170,85) QColor(ARGB 1, 0.501961, 0.666667, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(171,85) QColor(ARGB 1, 0.501961, 0.670588, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(172,86) QColor(ARGB 1, 0.501961, 0.67451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(173,86) QColor(ARGB 1, 0.501961, 0.678431, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(174,87) QColor(ARGB 1, 0.501961, 0.682353, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(175,87) QColor(ARGB 1, 0.501961, 0.686275, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(176,88) QColor(ARGB 1, 0.501961, 0.690196, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(177,88) QColor(ARGB 1, 0.501961, 0.694118, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(178,89) QColor(ARGB 1, 0.501961, 0.698039, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(179,89) QColor(ARGB 1, 0.501961, 0.701961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(180,90) QColor(ARGB 1, 0.501961, 0.705882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(181,90) QColor(ARGB 1, 0.501961, 0.709804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(182,91) QColor(ARGB 1, 0.501961, 0.713725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(183,91) QColor(ARGB 1, 0.501961, 0.717647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(184,92) QColor(ARGB 1, 0.501961, 0.721569, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(185,92) QColor(ARGB 1, 0.501961, 0.72549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(186,93) QColor(ARGB 1, 0.501961, 0.729412, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(187,93) QColor(ARGB 1, 0.501961, 0.733333, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(188,94) QColor(ARGB 1, 0.501961, 0.737255, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(189,94) QColor(ARGB 1, 0.501961, 0.741176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(190,95) QColor(ARGB 1, 0.501961, 0.745098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(191,95) QColor(ARGB 1, 0.501961, 0.74902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(192,96) QColor(ARGB 1, 0.501961, 0.752941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(193,96) QColor(ARGB 1, 0.501961, 0.756863, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(194,97) QColor(ARGB 1, 0.501961, 0.760784, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(195,97) QColor(ARGB 1, 0.501961, 0.764706, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(196,98) QColor(ARGB 1, 0.501961, 0.768627, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(197,98) QColor(ARGB 1, 0.501961, 0.772549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(198,99) QColor(ARGB 1, 0.501961, 0.776471, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(199,99) QColor(ARGB 1, 0.501961, 0.780392, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(200,100) QColor(ARGB 1, 0.501961, 0.784314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(201,100) QColor(ARGB 1, 0.501961, 0.788235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(202,101) QColor(ARGB 1, 0.501961, 0.792157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(203,101) QColor(ARGB 1, 0.501961, 0.796078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(204,102) QColor(ARGB 1, 0.501961, 0.8, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(205,102) QColor(ARGB 1, 0.501961, 0.803922, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(206,103) QColor(ARGB 1, 0.501961, 0.807843, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(207,103) QColor(ARGB 1, 0.501961, 0.811765, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(208,104) QColor(ARGB 1, 0.501961, 0.815686, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(209,104) QColor(ARGB 1, 0.501961, 0.819608, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(210,105) QColor(ARGB 1, 0.501961, 0.823529, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(211,105) QColor(ARGB 1, 0.501961, 0.827451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(212,106) QColor(ARGB 1, 0.501961, 0.831373, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(213,106) QColor(ARGB 1, 0.501961, 0.835294, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(214,107) QColor(ARGB 1, 0.501961, 0.839216, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(215,107) QColor(ARGB 1, 0.501961, 0.843137, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(216,108) QColor(ARGB 1, 0.501961, 0.847059, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(217,108) QColor(ARGB 1, 0.501961, 0.85098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(218,109) QColor(ARGB 1, 0.501961, 0.854902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(219,109) QColor(ARGB 1, 0.501961, 0.858824, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(220,110) QColor(ARGB 1, 0.501961, 0.862745, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(221,110) QColor(ARGB 1, 0.501961, 0.866667, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(222,111) QColor(ARGB 1, 0.501961, 0.870588, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(223,111) QColor(ARGB 1, 0.501961, 0.87451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(224,112) QColor(ARGB 1, 0.501961, 0.878431, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(225,112) QColor(ARGB 1, 0.501961, 0.882353, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(226,113) QColor(ARGB 1, 0.501961, 0.886275, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(227,113) QColor(ARGB 1, 0.501961, 0.890196, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(228,114) QColor(ARGB 1, 0.501961, 0.894118, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(229,114) QColor(ARGB 1, 0.501961, 0.898039, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(230,115) QColor(ARGB 1, 0.501961, 0.901961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(231,115) QColor(ARGB 1, 0.501961, 0.905882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(232,116) QColor(ARGB 1, 0.501961, 0.909804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(233,116) QColor(ARGB 1, 0.501961, 0.913725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(234,117) QColor(ARGB 1, 0.501961, 0.917647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(235,117) QColor(ARGB 1, 0.501961, 0.921569, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(236,118) QColor(ARGB 1, 0.501961, 0.92549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(237,118) QColor(ARGB 1, 0.501961, 0.929412, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(238,119) QColor(ARGB 1, 0.501961, 0.933333, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(239,119) QColor(ARGB 1, 0.501961, 0.937255, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(240,120) QColor(ARGB 1, 0.501961, 0.941176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(241,120) QColor(ARGB 1, 0.501961, 0.945098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(242,121) QColor(ARGB 1, 0.501961, 0.94902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(243,121) QColor(ARGB 1, 0.501961, 0.952941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(244,122) QColor(ARGB 1, 0.501961, 0.956863, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(245,122) QColor(ARGB 1, 0.501961, 0.960784, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(246,123) QColor(ARGB 1, 0.501961, 0.964706, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(247,123) QColor(ARGB 1, 0.501961, 0.968627, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(248,124) QColor(ARGB 1, 0.501961, 0.972549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(249,124) QColor(ARGB 1, 0.501961, 0.976471, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(250,125) QColor(ARGB 1, 0.501961, 0.980392, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(251,125) QColor(ARGB 1, 0.501961, 0.984314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(252,126) QColor(ARGB 1, 0.501961, 0.988235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(253,126) QColor(ARGB 1, 0.501961, 0.992157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(254,127) QColor(ARGB 1, 0.501961, 0.996078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(255,127) QColor(ARGB 1, 0.501961, 1, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(256,128) QColor(ARGB 1, 0.501961, 0, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(257,128) QColor(ARGB 1, 0.501961, 0.00392157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(258,129) QColor(ARGB 1, 0.501961, 0.00784314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(259,129) QColor(ARGB 1, 0.501961, 0.0117647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(260,130) QColor(ARGB 1, 0.501961, 0.0156863, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(261,130) QColor(ARGB 1, 0.501961, 0.0196078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(262,131) QColor(ARGB 1, 0.501961, 0.0235294, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(263,131) QColor(ARGB 1, 0.501961, 0.027451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(264,132) QColor(ARGB 1, 0.501961, 0.0313725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(265,132) QColor(ARGB 1, 0.501961, 0.0352941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(266,133) QColor(ARGB 1, 0.501961, 0.0392157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(267,133) QColor(ARGB 1, 0.501961, 0.0431373, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(268,134) QColor(ARGB 1, 0.501961, 0.0470588, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(269,134) QColor(ARGB 1, 0.501961, 0.0509804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(270,135) QColor(ARGB 1, 0.501961, 0.054902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(271,135) QColor(ARGB 1, 0.501961, 0.0588235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(272,136) QColor(ARGB 1, 0.501961, 0.0627451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(273,136) QColor(ARGB 1, 0.501961, 0.0666667, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(274,137) QColor(ARGB 1, 0.501961, 0.0705882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(275,137) QColor(ARGB 1, 0.501961, 0.0745098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(276,138) QColor(ARGB 1, 0.501961, 0.0784314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(277,138) QColor(ARGB 1, 0.501961, 0.0823529, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(278,139) QColor(ARGB 1, 0.501961, 0.0862745, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(279,139) QColor(ARGB 1, 0.501961, 0.0901961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(280,140) QColor(ARGB 1, 0.501961, 0.0941176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(281,140) QColor(ARGB 1, 0.501961, 0.0980392, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(282,141) QColor(ARGB 1, 0.501961, 0.101961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(283,141) QColor(ARGB 1, 0.501961, 0.105882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(284,142) QColor(ARGB 1, 0.501961, 0.109804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(285,142) QColor(ARGB 1, 0.501961, 0.113725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(286,143) QColor(ARGB 1, 0.501961, 0.117647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(287,143) QColor(ARGB 1, 0.501961, 0.121569, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(288,144) QColor(ARGB 1, 0.501961, 0.12549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(289,144) QColor(ARGB 1, 0.501961, 0.129412, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(290,145) QColor(ARGB 1, 0.501961, 0.133333, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(291,145) QColor(ARGB 1, 0.501961, 0.137255, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(292,146) QColor(ARGB 1, 0.501961, 0.141176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(293,146) QColor(ARGB 1, 0.501961, 0.145098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(294,147) QColor(ARGB 1, 0.501961, 0.14902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(295,147) QColor(ARGB 1, 0.501961, 0.152941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(296,148) QColor(ARGB 1, 0.501961, 0.156863, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(297,148) QColor(ARGB 1, 0.501961, 0.160784, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(298,149) QColor(ARGB 1, 0.501961, 0.164706, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(299,149) QColor(ARGB 1, 0.501961, 0.168627, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(300,150) QColor(ARGB 1, 0.501961, 0.172549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(301,150) QColor(ARGB 1, 0.501961, 0.176471, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(302,151) QColor(ARGB 1, 0.501961, 0.180392, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(303,151) QColor(ARGB 1, 0.501961, 0.184314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(304,152) QColor(ARGB 1, 0.501961, 0.188235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(305,152) QColor(ARGB 1, 0.501961, 0.192157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(306,153) QColor(ARGB 1, 0.501961, 0.196078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(307,153) QColor(ARGB 1, 0.501961, 0.2, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(308,154) QColor(ARGB 1, 0.501961, 0.203922, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(309,154) QColor(ARGB 1, 0.501961, 0.207843, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(310,155) QColor(ARGB 1, 0.501961, 0.211765, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(311,155) QColor(ARGB 1, 0.501961, 0.215686, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(312,156) QColor(ARGB 1, 0.501961, 0.219608, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(313,156) QColor(ARGB 1, 0.501961, 0.223529, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(314,157) QColor(ARGB 1, 0.501961, 0.227451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(315,157) QColor(ARGB 1, 0.501961, 0.231373, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(316,158) QColor(ARGB 1, 0.501961, 0.235294, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(317,158) QColor(ARGB 1, 0.501961, 0.239216, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(318,159) QColor(ARGB 1, 0.501961, 0.243137, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(319,159) QColor(ARGB 1, 0.501961, 0.247059, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(320,160) QColor(ARGB 1, 0.501961, 0.25098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(321,160) QColor(ARGB 1, 0.501961, 0.254902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(322,161) QColor(ARGB 1, 0.501961, 0.258824, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(323,161) QColor(ARGB 1, 0.501961, 0.262745, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(324,162) QColor(ARGB 1, 0.501961, 0.266667, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(325,162) QColor(ARGB 1, 0.501961, 0.270588, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(326,163) QColor(ARGB 1, 0.501961, 0.27451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(327,163) QColor(ARGB 1, 0.501961, 0.278431, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(328,164) QColor(ARGB 1, 0.501961, 0.282353, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(329,164) QColor(ARGB 1, 0.501961, 0.286275, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(330,165) QColor(ARGB 1, 0.501961, 0.290196, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(331,165) QColor(ARGB 1, 0.501961, 0.294118, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(332,166) QColor(ARGB 1, 0.501961, 0.298039, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(333,166) QColor(ARGB 1, 0.501961, 0.301961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(334,167) QColor(ARGB 1, 0.501961, 0.305882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(335,167) QColor(ARGB 1, 0.501961, 0.309804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(336,168) QColor(ARGB 1, 0.501961, 0.313725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(337,168) QColor(ARGB 1, 0.501961, 0.317647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(338,169) QColor(ARGB 1, 0.501961, 0.321569, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(339,169) QColor(ARGB 1, 0.501961, 0.32549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(340,170) QColor(ARGB 1, 0.501961, 0.329412, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(341,170) QColor(ARGB 1, 0.501961, 0.333333, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(342,171) QColor(ARGB 1, 0.501961, 0.337255, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(343,171) QColor(ARGB 1, 0.501961, 0.341176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(344,172) QColor(ARGB 1, 0.501961, 0.345098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(345,172) QColor(ARGB 1, 0.501961, 0.34902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(346,173) QColor(ARGB 1, 0.501961, 0.352941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(347,173) QColor(ARGB 1, 0.501961, 0.356863, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(348,174) QColor(ARGB 1, 0.501961, 0.360784, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(349,174) QColor(ARGB 1, 0.501961, 0.364706, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(350,175) QColor(ARGB 1, 0.501961, 0.368627, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(351,175) QColor(ARGB 1, 0.501961, 0.372549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(352,176) QColor(ARGB 1, 0.501961, 0.376471, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(353,176) QColor(ARGB 1, 0.501961, 0.380392, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(354,177) QColor(ARGB 1, 0.501961, 0.384314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(355,177) QColor(ARGB 1, 0.501961, 0.388235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(356,178) QColor(ARGB 1, 0.501961, 0.392157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(357,178) QColor(ARGB 1, 0.501961, 0.396078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(358,179) QColor(ARGB 1, 0.501961, 0.4, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(359,179) QColor(ARGB 1, 0.501961, 0.403922, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(360,180) QColor(ARGB 1, 0.501961, 0.407843, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(361,180) QColor(ARGB 1, 0.501961, 0.411765, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(362,181) QColor(ARGB 1, 0.501961, 0.415686, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(363,181) QColor(ARGB 1, 0.501961, 0.419608, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(364,182) QColor(ARGB 1, 0.501961, 0.423529, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(365,182) QColor(ARGB 1, 0.501961, 0.427451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(366,183) QColor(ARGB 1, 0.501961, 0.431373, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(367,183) QColor(ARGB 1, 0.501961, 0.435294, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(368,184) QColor(ARGB 1, 0.501961, 0.439216, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(369,184) QColor(ARGB 1, 0.501961, 0.443137, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(370,185) QColor(ARGB 1, 0.501961, 0.447059, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(371,185) QColor(ARGB 1, 0.501961, 0.45098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(372,186) QColor(ARGB 1, 0.501961, 0.454902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(373,186) QColor(ARGB 1, 0.501961, 0.458824, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(374,187) QColor(ARGB 1, 0.501961, 0.462745, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(375,187) QColor(ARGB 1, 0.501961, 0.466667, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(376,188) QColor(ARGB 1, 0.501961, 0.470588, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(377,188) QColor(ARGB 1, 0.501961, 0.47451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(378,189) QColor(ARGB 1, 0.501961, 0.478431, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(379,189) QColor(ARGB 1, 0.501961, 0.482353, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(380,190) QColor(ARGB 1, 0.501961, 0.486275, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(381,190) QColor(ARGB 1, 0.501961, 0.490196, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(382,191) QColor(ARGB 1, 0.501961, 0.494118, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(383,191) QColor(ARGB 1, 0.501961, 0.498039, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(384,192) QColor(ARGB 1, 0.501961, 0.501961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(385,192) QColor(ARGB 1, 0.501961, 0.505882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(386,193) QColor(ARGB 1, 0.501961, 0.509804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(387,193) QColor(ARGB 1, 0.501961, 0.513725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(388,194) QColor(ARGB 1, 0.501961, 0.517647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(389,194) QColor(ARGB 1, 0.501961, 0.521569, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(390,195) QColor(ARGB 1, 0.501961, 0.52549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(391,195) QColor(ARGB 1, 0.501961, 0.529412, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(392,196) QColor(ARGB 1, 0.501961, 0.533333, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(393,196) QColor(ARGB 1, 0.501961, 0.537255, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(394,197) QColor(ARGB 1, 0.501961, 0.541176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(395,197) QColor(ARGB 1, 0.501961, 0.545098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(396,198) QColor(ARGB 1, 0.501961, 0.54902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(397,198) QColor(ARGB 1, 0.501961, 0.552941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(398,199) QColor(ARGB 1, 0.501961, 0.556863, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(399,199) QColor(ARGB 1, 0.501961, 0.560784, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(400,200) QColor(ARGB 1, 0.501961, 0.564706, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(401,200) QColor(ARGB 1, 0.501961, 0.568627, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(402,201) QColor(ARGB 1, 0.501961, 0.572549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(403,201) QColor(ARGB 1, 0.501961, 0.576471, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(404,202) QColor(ARGB 1, 0.501961, 0.580392, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(405,202) QColor(ARGB 1, 0.501961, 0.584314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(406,203) QColor(ARGB 1, 0.501961, 0.588235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(407,203) QColor(ARGB 1, 0.501961, 0.592157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(408,204) QColor(ARGB 1, 0.501961, 0.596078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(409,204) QColor(ARGB 1, 0.501961, 0.6, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(410,205) QColor(ARGB 1, 0.501961, 0.603922, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(411,205) QColor(ARGB 1, 0.501961, 0.607843, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(412,206) QColor(ARGB 1, 0.501961, 0.611765, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(413,206) QColor(ARGB 1, 0.501961, 0.615686, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(414,207) QColor(ARGB 1, 0.501961, 0.619608, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(415,207) QColor(ARGB 1, 0.501961, 0.623529, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(416,208) QColor(ARGB 1, 0.501961, 0.627451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(417,208) QColor(ARGB 1, 0.501961, 0.631373, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(418,209) QColor(ARGB 1, 0.501961, 0.635294, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(419,209) QColor(ARGB 1, 0.501961, 0.639216, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(420,210) QColor(ARGB 1, 0.501961, 0.643137, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(421,210) QColor(ARGB 1, 0.501961, 0.647059, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(422,211) QColor(ARGB 1, 0.501961, 0.65098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(423,211) QColor(ARGB 1, 0.501961, 0.654902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(424,212) QColor(ARGB 1, 0.501961, 0.658824, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(425,212) QColor(ARGB 1, 0.501961, 0.662745, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(426,213) QColor(ARGB 1, 0.501961, 0.666667, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(427,213) QColor(ARGB 1, 0.501961, 0.670588, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(428,214) QColor(ARGB 1, 0.501961, 0.67451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(429,214) QColor(ARGB 1, 0.501961, 0.678431, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(430,215) QColor(ARGB 1, 0.501961, 0.682353, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(431,215) QColor(ARGB 1, 0.501961, 0.686275, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(432,216) QColor(ARGB 1, 0.501961, 0.690196, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(433,216) QColor(ARGB 1, 0.501961, 0.694118, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(434,217) QColor(ARGB 1, 0.501961, 0.698039, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(435,217) QColor(ARGB 1, 0.501961, 0.701961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(436,218) QColor(ARGB 1, 0.501961, 0.705882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(437,218) QColor(ARGB 1, 0.501961, 0.709804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(438,219) QColor(ARGB 1, 0.501961, 0.713725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(439,219) QColor(ARGB 1, 0.501961, 0.717647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(440,220) QColor(ARGB 1, 0.501961, 0.721569, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(441,220) QColor(ARGB 1, 0.501961, 0.72549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(442,221) QColor(ARGB 1, 0.501961, 0.729412, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(443,221) QColor(ARGB 1, 0.501961, 0.733333, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(444,222) QColor(ARGB 1, 0.501961, 0.737255, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(445,222) QColor(ARGB 1, 0.501961, 0.741176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(446,223) QColor(ARGB 1, 0.501961, 0.745098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(447,223) QColor(ARGB 1, 0.501961, 0.74902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(448,224) QColor(ARGB 1, 0.501961, 0.752941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(449,224) QColor(ARGB 1, 0.501961, 0.756863, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(450,225) QColor(ARGB 1, 0.501961, 0.760784, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(451,225) QColor(ARGB 1, 0.501961, 0.764706, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(452,226) QColor(ARGB 1, 0.501961, 0.768627, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(453,226) QColor(ARGB 1, 0.501961, 0.772549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(454,227) QColor(ARGB 1, 0.501961, 0.776471, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(455,227) QColor(ARGB 1, 0.501961, 0.780392, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(456,228) QColor(ARGB 1, 0.501961, 0.784314, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(457,228) QColor(ARGB 1, 0.501961, 0.788235, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(458,229) QColor(ARGB 1, 0.501961, 0.792157, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(459,229) QColor(ARGB 1, 0.501961, 0.796078, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(460,230) QColor(ARGB 1, 0.501961, 0.8, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(461,230) QColor(ARGB 1, 0.501961, 0.803922, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(462,231) QColor(ARGB 1, 0.501961, 0.807843, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(463,231) QColor(ARGB 1, 0.501961, 0.811765, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(464,232) QColor(ARGB 1, 0.501961, 0.815686, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(465,232) QColor(ARGB 1, 0.501961, 0.819608, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(466,233) QColor(ARGB 1, 0.501961, 0.823529, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(467,233) QColor(ARGB 1, 0.501961, 0.827451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(468,234) QColor(ARGB 1, 0.501961, 0.831373, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(469,234) QColor(ARGB 1, 0.501961, 0.835294, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(470,235) QColor(ARGB 1, 0.501961, 0.839216, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(471,235) QColor(ARGB 1, 0.501961, 0.843137, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(472,236) QColor(ARGB 1, 0.501961, 0.847059, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(473,236) QColor(ARGB 1, 0.501961, 0.85098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(474,237) QColor(ARGB 1, 0.501961, 0.854902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(475,237) QColor(ARGB 1, 0.501961, 0.858824, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(476,238) QColor(ARGB 1, 0.501961, 0.862745, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(477,238) QColor(ARGB 1, 0.501961, 0.866667, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(478,239) QColor(ARGB 1, 0.501961, 0.870588, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(479,239) QColor(ARGB 1, 0.501961, 0.87451, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(480,240) QColor(ARGB 1, 0.501961, 0.878431, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(481,240) QColor(ARGB 1, 0.501961, 0.882353, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(482,241) QColor(ARGB 1, 0.501961, 0.886275, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(483,241) QColor(ARGB 1, 0.501961, 0.890196, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(484,242) QColor(ARGB 1, 0.501961, 0.894118, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(485,242) QColor(ARGB 1, 0.501961, 0.898039, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(486,243) QColor(ARGB 1, 0.501961, 0.901961, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(487,243) QColor(ARGB 1, 0.501961, 0.905882, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(488,244) QColor(ARGB 1, 0.501961, 0.909804, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(489,244) QColor(ARGB 1, 0.501961, 0.913725, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(490,245) QColor(ARGB 1, 0.501961, 0.917647, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(491,245) QColor(ARGB 1, 0.501961, 0.921569, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(492,246) QColor(ARGB 1, 0.501961, 0.92549, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(493,246) QColor(ARGB 1, 0.501961, 0.929412, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(494,247) QColor(ARGB 1, 0.501961, 0.933333, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(495,247) QColor(ARGB 1, 0.501961, 0.937255, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(496,248) QColor(ARGB 1, 0.501961, 0.941176, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(497,248) QColor(ARGB 1, 0.501961, 0.945098, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(498,249) QColor(ARGB 1, 0.501961, 0.94902, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPIXELV: QPoint(499,249) QColor(ARGB 1, 0.501961, 0.952941, 0.564706) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-setpixel.emf") ) );
-}
-
-void PyEmfTests::testDrawing1()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-drawing1.emf") ) );
-}
-
-void PyEmfTests::testFontBackground()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(1201, 901) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 1 , penStyle: 0 width: 1 color: QColor(ARGB 1, 0.00392157, 0.00784314, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETBKCOLOR QColor(ARGB 1, 0, 1, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETBKMODE: Opaque " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTALIGN: 8 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTCOLOR QColor(ARGB 1, 0, 0, 1) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,100) \"height=48\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,100) \"height=-48\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTCOLOR QColor(ARGB 1, 0.501961, 0, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,152 1111x1) (QPoint(90,152) , QPoint(1200,152) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,176 1111x1) (QPoint(90,176) , QPoint(1200,176) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,200 1111x1) (QPoint(90,200) , QPoint(1200,200) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Arial\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,200) \"Arial\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Arial\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 4 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,200) \"Arial\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,232 1111x1) (QPoint(90,232) , QPoint(1200,232) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,256 1111x1) (QPoint(90,256) , QPoint(1200,256) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,280 1111x1) (QPoint(90,280) , QPoint(1200,280) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Times New Roman\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 5 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,280) \"Times New Roman\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Times New Roman\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 6 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,280) \"Times New Roman\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,312 1111x1) (QPoint(90,312) , QPoint(1200,312) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,336 1111x1) (QPoint(90,336) , QPoint(1200,336) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,360 1111x1) (QPoint(90,360) , QPoint(1200,360) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Andale Mono\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 7 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,360) \"Andale Mono\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Andale Mono\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 8 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,360) \"Andale Mono\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,392 1111x1) (QPoint(90,392) , QPoint(1200,392) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,416 1111x1) (QPoint(90,416) , QPoint(1200,416) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,440 1111x1) (QPoint(90,440) , QPoint(1200,440) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Trebuchet MS\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 9 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,440) \"Trebuchet MS\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Trebuchet MS\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 10 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,440) \"Trebuchet MS\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,472 1111x1) (QPoint(90,472) , QPoint(1200,472) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,496 1111x1) (QPoint(90,496) , QPoint(1200,496) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,520 1111x1) (QPoint(90,520) , QPoint(1200,520) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Georgia\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 11 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,520) \"Georgia\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Georgia\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 12 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,520) \"Georgia\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,552 1111x1) (QPoint(90,552) , QPoint(1200,552) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,576 1111x1) (QPoint(90,576) , QPoint(1200,576) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,600 1111x1) (QPoint(90,600) , QPoint(1200,600) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Verdana\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 13 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,600) \"Verdana\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Verdana\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 14 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,600) \"Verdana\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,632 1111x1) (QPoint(90,632) , QPoint(1200,632) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,656 1111x1) (QPoint(90,656) , QPoint(1200,656) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,680 1111x1) (QPoint(90,680) , QPoint(1200,680) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Courier New\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 15 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,680) \"Courier New\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Courier New\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 16 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,680) \"Courier New\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,712 1111x1) (QPoint(90,712) , QPoint(1200,712) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,736 1111x1) (QPoint(90,736) , QPoint(1200,736) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(90,760 1111x1) (QPoint(90,760) , QPoint(1200,760) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 17 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,760) \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 18 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(700,760) \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(0,840) \"All text should be on a green background\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-fontbackground.emf") ) );
-}
-
-void PyEmfTests::testOptimize16Bit()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 1 , penStyle: 0 width: 1 color: QColor(ARGB 1, 0.00392157, 0.00784314, 1) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 2 , penStyle: 0 width: 1 color: QColor(ARGB 1, 0.00392157, 1, 0.0117647) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 2401x901) (QPoint(0,0) , QPoint(2400,900) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE QRect(0,0 40001x901) (QPoint(0,0) , QPoint(40000,900) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,900 2401x901) (QPoint(2400,900) , QPoint(0,1800) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE QRect(0,900 40001x901) (QPoint(40000,900) , QPoint(0,1800) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-optimize16bit.emf") ) );
-}
-
-void PyEmfTests::testPaths1()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEBRUSHINDIRECT: 1 style: 0 Colour: QColor(ARGB 1, 0.498039, 0.498039, 1) , Hatch: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 2 , penStyle: 3 width: 1 color: QColor(ARGB 1, 0.941176, 0, 0.501961) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETBKMODE: Transparent " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTALIGN: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTCOLOR QColor(ARGB 1, 0, 0, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(0,0) \"FillPath\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(0,50) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(100,350) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(100,350 301x301) QPoint(100,350) QPoint(400,350) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(400,150 151x201) (QPoint(400,350) , QPoint(450,250) , QPoint(500,350) , QPoint(550,150) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(200,100 201x101) (QPoint(400,100) , QPoint(300,200) , QPoint(200,150) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_FILLPATH QRect(0,50 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(800,0) \"StrokePath\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(800,50) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(900,350) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(900,350 301x301) QPoint(900,350) QPoint(1200,350) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(1200,150 151x201) (QPoint(1200,350) , QPoint(1250,250) , QPoint(1300,350) , QPoint(1350,150) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(1000,100 201x101) (QPoint(1200,100) , QPoint(1100,200) , QPoint(1000,150) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEPATH QRect(800,50 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(0,800) \"StrokePath,FillPath\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(0,850) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(100,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(100,1150 301x301) QPoint(100,1150) QPoint(400,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(400,950 151x201) (QPoint(400,1150) , QPoint(450,1050) , QPoint(500,1150) , QPoint(550,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(200,900 201x101) (QPoint(400,900) , QPoint(300,1000) , QPoint(200,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEPATH QRect(0,850 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_FILLPATH QRect(0,850 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(800,800) \"FillPath,StrokePath\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(800,850) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(900,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(900,1150 301x301) QPoint(900,1150) QPoint(1200,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(1200,950 151x201) (QPoint(1200,1150) , QPoint(1250,1050) , QPoint(1300,1150) , QPoint(1350,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(1000,900 201x101) (QPoint(1200,900) , QPoint(1100,1000) , QPoint(1000,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_FILLPATH QRect(800,850 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEPATH QRect(800,850 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(1600,800) \"StrokeAndFillPath\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(1600,850) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(1700,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(1700,1150 301x301) QPoint(1700,1150) QPoint(2000,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(2000,950 151x201) (QPoint(2000,1150) , QPoint(2050,1050) , QPoint(2100,1150) , QPoint(2150,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(1800,900 201x101) (QPoint(2000,900) , QPoint(1900,1000) , QPoint(1800,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEANDFILLPATH QRect(1600,850 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-paths1.emf") ) );
-}
-
-void PyEmfTests::testPoly1()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 1 , penStyle: 0 width: 10 color: QColor(ARGB 1, 0.00392157, 0.627451, 1) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEBRUSHINDIRECT: 2 style: 0 Colour: QColor(ARGB 1, 0.313725, 0.313725, 0.313725) , Hatch: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETBKMODE: Transparent " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTALIGN: 8 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTCOLOR QColor(ARGB 1, 0, 0, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Arial\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(500,50) \"Test of polypolygon and polypolyline\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,300) \"several filled-in squares. OpenOffice doesn't seem to complete the polygons.\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,800) \"it's ... just a bunch of wavy lines.\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-poly1.emf") ) );
-}
-
-void PyEmfTests::testPoly2()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 1 , penStyle: 0 width: 10 color: QColor(ARGB 1, 0.00392157, 0.627451, 1) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEBRUSHINDIRECT: 2 style: 0 Colour: QColor(ARGB 1, 0.313725, 0.313725, 0.313725) , Hatch: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETBKMODE: Transparent " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTALIGN: 8 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTCOLOR QColor(ARGB 1, 0, 0, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Arial\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(500,50) \"Test of polygon fill mode\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,300) \"ALTERNATE\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPOLYFILLMODE: OddEvenFill " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYGON16 QRect(100,300 851x451) (QPoint(100,400) , QPoint(500,700) , QPoint(600,400) , QPoint(900,700) , QPoint(900,400) , QPoint(100,700) , QPoint(100,300) , QPoint(950,300) , QPoint(950,750) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(100,900) \"WINDING\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETPOLYFILLMODE: WindingFill " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYGON16 QRect(100,900 851x451) (QPoint(100,1000) , QPoint(500,1300) , QPoint(600,1000) , QPoint(900,1300) , QPoint(900,1000) , QPoint(100,1300) , QPoint(100,900) , QPoint(950,900) , QPoint(950,1350) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-poly2.emf") ) );
-}
-
-void PyEmfTests::testSetClipPath()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(901, 601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-selectclippath1.emf") ) );
-}
-
-void PyEmfTests::testViewportWindowOrigin()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(2401, 1801) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEBRUSHINDIRECT: 1 style: 0 Colour: QColor(ARGB 1, 0.498039, 0.498039, 1) , Hatch: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 2 , penStyle: 3 width: 1 color: QColor(ARGB 1, 0.941176, 0, 0.501961) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETBKMODE: Transparent " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTALIGN: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTCOLOR QColor(ARGB 1, 0, 0, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETVIEWPORTORGEX QPoint(400,400) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(0,0) \"Viewport: 400,400, loc: 0,0\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(0,50) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(100,350) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(100,350 301x301) QPoint(100,350) QPoint(400,350) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(400,150 151x201) (QPoint(400,350) , QPoint(450,250) , QPoint(500,350) , QPoint(550,150) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(200,100 201x101) (QPoint(400,100) , QPoint(300,200) , QPoint(200,150) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEANDFILLPATH QRect(0,50 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETVIEWPORTORGEX QPoint(0,0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETWINDOWORGEX QPoint(800,800) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(800,800) \"Window: 800,800, loc: 800,800\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(800,850) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(900,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(900,1150 301x301) QPoint(900,1150) QPoint(1200,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(1200,950 151x201) (QPoint(1200,1150) , QPoint(1250,1050) , QPoint(1300,1150) , QPoint(1350,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(1000,900 201x101) (QPoint(1200,900) , QPoint(1100,1000) , QPoint(1000,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEANDFILLPATH QRect(800,850 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 2401x1801) (QPoint(0,0) , QPoint(2400,1800) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 2401x1801) (QPoint(0,1800) , QPoint(2400,0) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETVIEWPORTORGEX QPoint(200,200) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETWINDOWORGEX QPoint(800,800) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(1800,800) \"Window: 800,800, Viewport: 200,200, loc: 1800,800\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(1800,850) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(1900,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(1900,1150 301x301) QPoint(1900,1150) QPoint(2200,1150) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(2200,950 151x201) (QPoint(2200,1150) , QPoint(2250,1050) , QPoint(2300,1150) , QPoint(2350,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(2000,900 201x101) (QPoint(2200,900) , QPoint(2100,1000) , QPoint(2000,950) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEANDFILLPATH QRect(1800,850 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-viewport-window-origin.emf") ) );
-}
-
-void PyEmfTests::testWorldTransform1()
-{
- QTest::ignoreMessage( QtDebugMsg, "Initialising DebugOutput " );
- QTest::ignoreMessage( QtDebugMsg, "image size: QSize(1801, 1201) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEBRUSHINDIRECT: 1 style: 0 Colour: QColor(ARGB 1, 0.498039, 0.498039, 1) , Hatch: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 1 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 2 , penStyle: 3 width: 1 color: QColor(ARGB 1, 0.941176, 0, 0.501961) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETBKMODE: Transparent " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTALIGN: 0 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETTEXTCOLOR QColor(ARGB 1, 0, 0, 0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEFONTINDIRECTW: \"Helvetica\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 3 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CREATEPEN ihPen: 4 , penStyle: 2 width: 1 color: QColor(ARGB 1, 0.00784314, 0.0117647, 0.0156863) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 4 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 51x51) (QPoint(50,0) , QPoint(50,50) , QPoint(0,50) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETWORLDTRANSFORM: QTransform(11=1 12=0 21=0 22=1 dx=50 dy=50) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(0,0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(100,300) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(100,300 301x301) QPoint(100,300) QPoint(400,300) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(400,100 151x201) (QPoint(400,300) , QPoint(450,200) , QPoint(500,300) , QPoint(550,100) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(200,50 201x101) (QPoint(400,50) , QPoint(300,150) , QPoint(200,100) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEANDFILLPATH QRect(0,0 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(0,0) \"translate (50,50) loc: 0,0\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MODIFYWORLDTRANSFORM: 1 QTransform(11=1 12=0 21=0 22=1 dx=0 dy=0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 4 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 501x801) (QPoint(500,0) , QPoint(500,800) , QPoint(0,800) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETWORLDTRANSFORM: QTransform(11=0.707107 12=-0.707107 21=0.707107 22=0.707107 dx=500 dy=800) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(0,0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(100,300) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(100,300 301x301) QPoint(100,300) QPoint(400,300) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(400,100 151x201) (QPoint(400,300) , QPoint(450,200) , QPoint(500,300) , QPoint(550,100) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(200,50 201x101) (QPoint(400,50) , QPoint(300,150) , QPoint(200,100) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEANDFILLPATH QRect(0,0 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(0,0) \"rotate 45 deg, translate (500,800) loc: 0,0\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SETWORLDTRANSFORM: QTransform(11=1 12=0 21=0 22=1 dx=0 dy=0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 4 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINE16 QRect(0,0 1001x1001) (QPoint(1000,0) , QPoint(1000,1000) , QPoint(0,1000) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_SELECTOBJECT 2 " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MODIFYWORLDTRANSFORM: 3 QTransform(11=0.173648 12=-0.984808 21=0.984808 22=0.173648 dx=1000 dy=1000) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_BEGINPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_MOVETOEX QPoint(0,0) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_LINETO QPoint(100,300) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ARCTO QRect(100,300 301x301) QPoint(100,300) QPoint(400,300) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYLINETO16 QRect(400,100 151x201) (QPoint(400,300) , QPoint(450,200) , QPoint(500,300) , QPoint(550,100) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_POLYBEZIERTO16 QRect(200,50 201x101) (QPoint(400,50) , QPoint(300,150) , QPoint(200,100) ) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_CLOSEFIGURE " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_ENDPATH " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_STROKEANDFILLPATH QRect(0,0 551x601) " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EXTTEXTOUTA: QPoint(0,0) \"rotate 80 deg, translate (1000,1000) loc: 0,0\" " );
- QTest::ignoreMessage( QtDebugMsg, "EMR_EOF " );
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("pyemf-worldtransform1.emf") ) );
-}
-
-QTEST_MAIN( PyEmfTests )
-#include <pyemf_tests.moc>
diff --git a/libs/vectorimage/libemf/tests/pyemf_tests.h b/libs/vectorimage/libemf/tests/pyemf_tests.h
deleted file mode 100644
index e431ee4f9c..0000000000
--- a/libs/vectorimage/libemf/tests/pyemf_tests.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef PYEMF_TESTS_H
-#define PYEMF_TESTS_H
-
-#include <QtTest>
-
-class PyEmfTests: public QObject
-{
- Q_OBJECT
-private Q_SLOTS:
- void test1();
- void testArcChordPie();
- void testDeleteObject();
- void testDrawing1();
- void testFontBackground();
- void testOptimize16Bit();
- void testPaths1();
- void testPoly1();
- void testPoly2();
- void testSetClipPath();
- void testSetPixel();
- void testViewportWindowOrigin();
- void testWorldTransform1();
-};
-
-#endif
diff --git a/libs/vectorimage/libemf/tests/render.cpp b/libs/vectorimage/libemf/tests/render.cpp
deleted file mode 100644
index 1d143dee31..0000000000
--- a/libs/vectorimage/libemf/tests/render.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-
-#include <EnhMetaFile.h>
-#include <QApplication>
-
-using namespace EnhancedMetafile;
-
-bool testOneFile( const QString &filename )
-{
- Parser parser;
- PainterOutput output;
- parser.setOutput( &output );
- if( parser.load( filename ) == false ) {
- debugVectorImage() << "failed to load" << filename;
- return false;
- } else {
- debugVectorImage() << "successfully loaded" << filename;
- return true;
- }
-}
-
-int main( int argc, char **argv )
-{
- QApplication app( argc, argv );
-
- QStringList filesToTest;
- filesToTest << "pyemf-1.emf" << "pyemf-arc-chord-pie.emf" << "pyemf-deleteobject.emf";
- filesToTest << "pyemf-drawing1.emf" << "pyemf-fontbackground.emf" << "pyemf-optimize16bit.emf";
- filesToTest << "pyemf-paths1.emf" << "pyemf-poly1.emf" << "pyemf-poly2.emf" << "pyemf-setpixel.emf";
- filesToTest << "snp-1.emf" << "snp-2.emf" << "snp-3.emf";
- filesToTest << "visio-1.emf";
-
-
- Q_FOREACH ( const QString &fileToTest, filesToTest ) {
- if ( testOneFile( fileToTest ) == false ) {
- return -1;
- }
- }
- return 0;
-}
-
diff --git a/libs/vectorimage/libemf/tests/snp-1.emf b/libs/vectorimage/libemf/tests/snp-1.emf
deleted file mode 100644
index e2a3500258..0000000000
Binary files a/libs/vectorimage/libemf/tests/snp-1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/snp-2.emf b/libs/vectorimage/libemf/tests/snp-2.emf
deleted file mode 100644
index 6a5ea6ecc5..0000000000
Binary files a/libs/vectorimage/libemf/tests/snp-2.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/snp-3.emf b/libs/vectorimage/libemf/tests/snp-3.emf
deleted file mode 100644
index 94a3e96c23..0000000000
Binary files a/libs/vectorimage/libemf/tests/snp-3.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/snp_tests.cpp b/libs/vectorimage/libemf/tests/snp_tests.cpp
deleted file mode 100644
index 262b877467..0000000000
--- a/libs/vectorimage/libemf/tests/snp_tests.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "snp_tests.h"
-#include <EnhMetaFile.h>
-
-using namespace EnhancedMetafile;
-
-void SnpTests::test1()
-{
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("snp-1.emf") ) );
-}
-
-void SnpTests::test2()
-{
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("snp-2.emf") ) );
-}
-
-void SnpTests::test3()
-{
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("snp-3.emf") ) );
-}
-
-QTEST_MAIN( SnpTests )
-#include <snp_tests.moc>
diff --git a/libs/vectorimage/libemf/tests/snp_tests.h b/libs/vectorimage/libemf/tests/snp_tests.h
deleted file mode 100644
index 2f7a7e69be..0000000000
--- a/libs/vectorimage/libemf/tests/snp_tests.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef SNP_TESTS_H
-#define SNP_TESTS_H
-
-#include <QtTest>
-
-class SnpTests: public QObject
-{
- Q_OBJECT
-private Q_SLOTS:
- void test1();
- void test2();
- void test3();
-};
-
-#endif
diff --git a/libs/vectorimage/libemf/tests/visio-1.emf b/libs/vectorimage/libemf/tests/visio-1.emf
deleted file mode 100644
index 5a8bb5b9c3..0000000000
Binary files a/libs/vectorimage/libemf/tests/visio-1.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/visio-kde41.emf b/libs/vectorimage/libemf/tests/visio-kde41.emf
deleted file mode 100644
index 4861f25cd1..0000000000
Binary files a/libs/vectorimage/libemf/tests/visio-kde41.emf and /dev/null differ
diff --git a/libs/vectorimage/libemf/tests/visio_tests.cpp b/libs/vectorimage/libemf/tests/visio_tests.cpp
deleted file mode 100644
index 7ace7f57d5..0000000000
--- a/libs/vectorimage/libemf/tests/visio_tests.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "visio_tests.h"
-#include <EnhMetaFile.h>
-
-using namespace EnhancedMetafile;
-
-void VisioTests::test1()
-{
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("visio-1.emf") ) );
-}
-
-void VisioTests::test_kde41()
-{
- Parser parser;
- DebugOutput output;
- parser.setOutput( &output );
- QVERIFY( parser.load( QString("visio-kde41.emf") ) );
-}
-
-QTEST_MAIN( VisioTests )
-
-#include <visio_tests.moc>
diff --git a/libs/vectorimage/libemf/tests/visio_tests.h b/libs/vectorimage/libemf/tests/visio_tests.h
deleted file mode 100644
index 75608352f0..0000000000
--- a/libs/vectorimage/libemf/tests/visio_tests.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- Copyright 2008 Brad Hards <bradh@frogmouth.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef VISIO_TESTS_H
-#define VISIO_TESTS_H
-
-#include <QtTest>
-
-class VisioTests: public QObject
-{
- Q_OBJECT
-private Q_SLOTS:
- void test1();
- void test_kde41();
-};
-
-#endif
diff --git a/libs/vectorimage/libsvm/CMakeLists.txt b/libs/vectorimage/libsvm/CMakeLists.txt
deleted file mode 100644
index eff0bfb5f1..0000000000
--- a/libs/vectorimage/libsvm/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-#add_subdirectory(demo)
-
-
-########### library target ###############
-
-set(libsvm_LIB_SRCS
- #SvmStructs.cpp
-)
-
-#add_library(libsvm SHARED ${libsvm_LIB_SRCS})
-
-#target_link_libraries(libsvm Qt5::Gui )
-
-#set_target_properties(libsvm PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} )
-
diff --git a/libs/vectorimage/libsvm/SPEC b/libs/vectorimage/libsvm/SPEC
deleted file mode 100644
index e7e2ac3bbd..0000000000
--- a/libs/vectorimage/libsvm/SPEC
+++ /dev/null
@@ -1,743 +0,0 @@
- StarView Metafile
-
- Specification
-
- Inge Wallin
- Pierre Ducroquet
-
-
-1. Introduction
-===============
-
-This file describes the format of a StarView Metafile, SVM, the vector
-file format that is used by Star Office, OpenOffice.org, and
-LibreOffice to create object replacement images.
-
-As far as we know, there is no formal specification of this format. This
-documentation has therefore been created by reading the source code of
-LibreOffice.
-
-The structure of this document is modeled after the documentations of Windows
-MetaFile ([MS-WMF].pdf) and the Extended MetaFile ([MS-EMF].pdf) from
-Microsoft.
-
-
-1.1 Glossary
-------------
-FIXME
-
-
-1.2 Structure Overview
-----------------------
-
-An SVM file always starts with a signature of 6 bytes that identifies
-the file as an SVM. This signature is always the string "VCLMTF"
-(excluding the quotes).
-
-Second, there is a header object describing the rest of the file.
-
-Third, the header is followed by a number of actions as specified in
-the header. Each action or object, including the header, starts with
-a VersionCompat object that specifies the version and size of the
-action or object in question. The size in the VersionCompat object is
-*excluding* the VersionCompat itself.
-
-
-2. Structures
-=============
-
-This section specifies the structures used in a StarView
-Metafile. This includes:
-
- * enumerations of SVM constants: enums and flags
-
- * definitions of SVM objects, i.e. primitive data types and
- composite structs and objects
-
- * specifications of SVM actions.
-
-
-2.1 SVM Constants
------------------
-
-Constants come in two different forms: enumerations and flags.
-
-
-2.1.1 SVM Enumerations
-- - - - - - - - - - - -
-
-The following enumerations should be used in programs that handle SVM
-files.
-
-
-2.1.1.1 ActionType Enumeration
-
-The ActionType Enumeration defines the type of actions that can be
-used in an SVM file.
-
-enum ActionType {
- META_NULL_ACTION = 0,
- META_PIXEL_ACTION = 100,
- META_POINT_ACTION = 101,
- META_LINE_ACTION = 102,
- META_RECT_ACTION = 103,
- META_ROUNDRECT_ACTION = 104,
- META_ELLIPSE_ACTION = 105
- META_ARC_ACTION = 106,
- META_PIE_ACTION = 107,
- META_CHORD_ACTION = 108,
- META_POLYLINE_ACTION = 109,
- META_POLYGON_ACTION = 110,
- META_POLYPOLYGON_ACTION = 111,
- META_TEXT_ACTION = 112,
- META_TEXTARRAY_ACTION = 113,
- META_STRETCHTEXT_ACTION = 114,
- META_TEXTRECT_ACTION = 115,
- META_BMP_ACTION = 116,
- META_BMPSCALE_ACTION = 117,
- META_BMPSCALEPART_ACTION = 118,
- META_BMPEX_ACTION = 119,
- META_BMPEXSCALE_ACTION = 120,
- META_BMPEXSCALEPART_ACTION = 121,
- META_MASK_ACTION = 122,
- META_MASKSCALE_ACTION = 123,
- META_MASKSCALEPART_ACTION = 124,
- META_GRADIENT_ACTION = 125,
- META_HATCH_ACTION = 126,
- META_WALLPAPER_ACTION = 127,
- META_CLIPREGION_ACTION = 128,
- META_ISECTRECTCLIPREGION_ACTION = 129,
- META_ISECTREGIONCLIPREGION_ACTION = 130,
- META_MOVECLIPREGION_ACTION = 131,
- META_LINECOLOR_ACTION = 132,
- META_FILLCOLOR_ACTION = 133,
- META_TEXTCOLOR_ACTION = 134,
- META_TEXTFILLCOLOR_ACTION = 135,
- META_TEXTALIGN_ACTION = 136,
- META_MAPMODE_ACTION = 137,
- META_FONT_ACTION = 138,
- META_PUSH_ACTION = 139,
- META_POP_ACTION = 140,
- META_RASTEROP_ACTION = 141,
- META_TRANSPARENT_ACTION = 142,
- META_EPS_ACTION = 143,
- META_REFPOINT_ACTION = 144,
- META_TEXTLINECOLOR_ACTION = 145,
- META_TEXTLINE_ACTION = 146,
- META_FLOATTRANSPARENT_ACTION = 147,
- META_GRADIENTEX_ACTION = 148,
- META_LAYOUTMODE_ACTION = 149,
- META_TEXTLANGUAGE_ACTION = 150,
- META_OVERLINECOLOR_ACTION = 151,
- META_COMMENT_ACTION = 512
-};
-
-
-2.1.1.2 Mtf Enumeration
-
-The Mtf Enumeration defines ... FIXME
-
-enum MtfType {
- MTF_CONVERSION_NONE = 0,
- MTF_CONVERSION_1BIT_THRESHOLD = 1,
- MTF_CONVERSION_8BIT_GREYS = 2
-};
-
-
-2.1.1.3 LineStyle Enumeration
-
-enum LineStyle
-{
- LINE_NONE = 0,
- LINE_SOLID = 1,
- LINE_DASH = 2
-};
-
-
-2.1.1.4 LayoutMode Enumeration
-
-enum LayoutMode
-{
- TEXT_LAYOUT_DEFAULT = 0x0000,
- TEXT_LAYOUT_BIDI_LTR = 0x0000,
- TEXT_LAYOUT_BIDI_RTL = 0x0001,
- TEXT_LAYOUT_BIDI_STRONG = 0x0002,
- TEXT_LAYOUT_TEXTORIGIN_LEFT = 0x0004,
- TEXT_LAYOUT_TEXTORIGIN_RIGHT = 0x0008,
- TEXT_LAYOUT_COMPLEX_DISABLED = 0x0100,
- TEXT_LAYOUT_ENABLE_LIGATURES = 0x0200,
- TEXT_LAYOUT_SUBSTITUTE_DIGITS = 0x0400
-};
-
-
-2.1.1.5 ... Enumeration
-FIXME
-
-
-2.1.2 SVM Flags
-- - - - - - - -
-
-A variable can only containing one value from an SVM enumeration at
-the same time. In contrast, a variable containing SVN flags can
-combine as many values from the respective definition of flags as the
-enumeration of the flag itself.
-
-
-2.1.2.1 Mirror Flags
-
-MTF_MIRROR_NONE 0x00000000
-MTF_MIRROR_HORZ 0x00000001
-MTF_MIRROR_VERT 0x00000002
-
-FIXME: Check the details here.
-MTF_MIRROR_NONE The picture is not mirrored in any direction.
-
-MTF_MIRROR_HORZ The picture should be drawn mirrored horizontally,
- i.e. mirrored in the center line of the picture
- parallel to the Y axis.
-
-MTF_MIRROR_VERT The picture should be drawn mirrored vertically,
- i.e. mirrored in the center line of the picture
- parallel to the X axis.
-
-In all of the cases above, the space occupied by the picture is the
-same.
-
-
-2.2 SVM Objects
----------------
-
-This section specifies the objects used in SVM files.
-
-
-2.2.1 Primitive Data Types
-- - - - - - - - - - - - - -
-
-The following primitive data types are used in the objects or actions.
-
-uint8 an unsigned 8 bit integer, taking up 1 byte in the file.
-int8 a signed 8 bit integer, taking up 1 byte in the file.
-uint16 an unsigned 16 bit integer, taking up 2 bytes in the file.
-int16 a signed 16 bit integer, taking up 2 bytes in the file.
-uint32 an unsigned 32 bit integer, taking up 4 bytes in the file.
-int32 a signed 32 bit integer, taking up 4 bytes in the file.
-bool an unsigned 8 bit value, taking up 1 byte in the file
-
-Floating point values are not used in SVM.
-
-
-2.2.1 Basic Objects
-- - - - - - - - - -
-
-These object types are mostly used as primitives in the larger and
-more complex object types and actions.
-
-
-2.2.1.1 VersionCompat Object
-
-The VersionCompat object is always the first object of any bigger
-object and any action. This object describes which version the object
-or action has.
-
-struct VersionCompat {
- uint16 version;
- uint32 length;
-}
-
-version defines the version of the object. Depending on the
- version, the object will have a different number of
- components, thus changing the parser behaviour.
-
-length defines the length of the object, excluding the
- VersionCompat object.
-
-
-2.2.1.2 Point
-
-struct Point {
- uint32 x;
- uint32 y;
-}
-
-
-2.2.1.3 Polygon
-
-struct Polygon {
- uint16 nPoints;
- Point points[nPoints];
-}
-
-
-2.2.1.4 PolyPolygon
-
-struct PolyPolygon {
- uint16 nPolygons;
- Polygon polygons[nPolygons];
-}
-
-
-2.2.1.5 String
-
-struct String {
- uint16 size;
- char data[size];
-}
-
-A string object is built in a Pascal style, not a C style. There is
-no trailing \0 at the end of the string.
-
-
-2.2.1.6 Fraction
-
-struct Fraction {
- uint32 numerator;
- unit32 denominator;
-}
-
-The value of a Fraction is numerator/denominator.
-
-
-2.2.2 Header Object
-- - - - - - - - - -
-
-The header object is a special object in an SVM. It is always the
-first object in the file. There is only one header object.
-
-VersionCompat versionCompat
-uint32 compressionMode
-MapMode mapMode
-uint32 width
-uint32 height
-uint32 actionCount
-
-versionCompat Version and size of the header record, excluding the
- VersionCompat object.
-
-compressionMode ... FIXME
-
-mapMode Default MapMode for the file. FIXME: Check if true
-
-width Width of the picture in logical coordinates.
-
-height Height of the picture in logical coordinates.
-
-actionCount Number of Actions in the file.
-
-
-2.2.3 Graphics Objects
-- - - - - - - - - - - -
-
-The SVM Graphics Objects specify parameters for graphics output. A
-particular graphics object becomes part of the drawing context when it
-is selected by an appropriate object record, and it is reused in
-subsequent graphics operations until a different object is selected.
-
-
-2.2.3.1 MapMode
-
-This object describes some properties of the current drawing.
-
-VersionCompat version
-uint16 unit
-Point origin
-Fraction scaleX
-Fraction scaleY
-bool isSimple
-
-....FIXME: how do they alter the rendering context ?
-.... Can the scale, the origin change ?
-.... Find the values for the unit...
-
-
-2.2.3.2 Font
-
-This object describes the properties of a font face to be used when rendering text.
-
-VersionCompat version
-String family
-String style
-uint32 fontWidth
-uint32 fontHeight
-uint16 charset
-uint16 family
-uint16 pitch
-uint16 weight
-uint16 underline
-uint16 strikeout
-uint16 italic
-uint16 language
-uint16 width
-uint16 orientation
-bool wordline
-bool outline
-bool shadow
-int8 kerning
- If version is greater than 1:
-int8 relief
-uint16 language
-bool vertical
-uint16 emphasis
- If version is greater than 2:
-uint16 overline
-
-version Version and size of the font record, excluding the
- VersionCompat object.
-
-FIXME: Describe the following parts in detail
-family
-style
-fontWidth
-fontHeight
-charset
-family
-pitch
-weight
-underline
-strikeout
-italic
-language
-width
-orientation
-wordline
-outline
-shadow
-kerning
- If version is greater than 1:
-relief
-language
-vertical
-emphasis
- If version is greater than 2:
-overline
-
-
-
-2.2.4 Structure Objects
-- - - - - - - - - - - -
-
-The SVM Structure Objects specify data structures that are embedded in
-SVM objects and actions. Structure objects, unlike graphics objects,
-are not explicitly created or deleted; they are components of more
-complex structures.
-
-
-2.2.4.1 Color
-
-FIXME
-
-
-2.2.4.2 PenInformations
-
-This object describes the pen properties for drawing lines. Its
-contents depends on its version.
-
- For all values of version:
-VersionCompat version
-uint16 lineStyle
-uint32 lineWidth
- If version is greater than 1:
-uint16 dashCount
-uint32 dashLength
-uint16 dotCount
-uint32 dotLength
-uint32 distance
- If version is greater than 2:
-uint16 lineJoin
-
-lineStyle describes the line style of the pen. It can take any
- value of the LineStyle enumeration.
-
-lineWidth width of the pen in <unit> (FIXME)
-
-....FIXME: how to interpret the next fields values ?
-
-
-2.3 SVM Actions
----------------
-
-The action type is specified using a uint16 at the beginning of each
-action following the file header. Every action except the special
-META_NULL_ACTION begins with a VersionCompat object.
-
-
-2.3.1 Control Action Types
-- - - - - - - - - - - - - -
-
-2.3.1.1 META_NULL_ACTION (0)
-
-This action should never occur, ever.
-It contains a uint16 that is never used.
-
-
-2.3.1.2 META_COMMENT_ACTION (512)
-
-A comment action has no effect on the rendering.
-
-string comment
-uint32 value
-string comment2
-
-
-comment The comment text
-
-value ... FIXME
-
-comment2 ... FIXME
-
-
-2.3.2 State Action Types
-- - - - - - - - - - - - -
-
-These actions change the state of the drawing context.
-
-
-2.3.2.1 META_LINECOLOR_ACTION (132)
-
-A line color sets the pen color in the drawing context.
-
-uint32 colorData
-bool doSet
-
-colorData The color of the pen, in RGB format
- (... QColor::fromRgb format)
-
-doSet Should the color be applied (true), or should the pen
- be hidden (false)
-
-
-2.3.2.2 META_FILLCOLOR_ACTION (133)
-
-A fill color sets the brush color in the current context.
-
-It contains the following parts
-uint32 colorData
-bool isSet
-
-colorData The color of the brush, in RGB format
- (... QColor::fromRgb format)
-
-isSet Specifies whether the color should be applied
- (true), or if the brush should be hidden (false)
-
-
-2.3.2.3 META_MAPMODE_ACTION (137)
-
-This action loads a new MapMode object into the drawing context.
-
-MapMode mapMode
-
-
-2.3.2.4 META_FONT_ACTION (138)
-
-This action sets the current font in the graphics context.
-
-Font font
-
-font The new font that will be used when writing text.
-
-
-
-2.3.2.5 META_LAYOUTMODE_ACTION (149)
-
-This action sets the current layout mode in the graphics context.
-
-uint32 layoutMode
-
-layoutMode This defines the text layout mode. It should contain
- one of the values of the LayoutMode enumeration.
-
-
-2.3.2.6 META_TEXTLANGUAGE_ACTION (150)
-
-This action sets the current text language in the graphics context.
-
-uint16 languageType
-
-languageType It is a decimal code that specifies the language.
- It is described in i18npool/inc/i18npool/lang.h
- FIXME: Add these language codes to the enums in 2.1.1
-
-
-2.3.3 Drawing Action Types
-- - - - - - - - - - - - - -
-
-These actions all cause some form of output to the picture. The
-operations described by these actions are influenced by the current
-drawing context.
-
-
-2.3.3.1 META_POLYLINE_ACTION (109)
-
-A polyline action draws a polygon without filling it.
-
-It contains the following parts
-Polygon polygon
- If the version of the action is greater than 1:
-PenInformations penInfos
- If the version of the action is greater than 2:
-bool polygonFlags
- If polygonFlags is true:
-VersionCompat flagsVersionCompat
-Polygon polygon (replaces the previous polygon)
-bool polygonFlagsEnabled
- If polygonFlagsEnabled
-....... FIXME!
-
-polygon The polygon to draw
-
-penInfos The pen style to apply
-
-polygonFlags Indicate the presence of extra polygon information
-....... FIXME: next part is complicated...
-
-
-2.3.3.2 META_POLYGON_ACTION (110)
-
-A polygon action draws a polygon and filling it.
-
-// FIXME: Check if the following is true. Right now it is just copied
-// from META_POLYLINE_ACTION
-Polygon polygon
- If the version of the action is greater than 1:
-PenInformations penInfos
- If the version of the action is greater than 2:
-bool polygonFlags
- If polygonFlags is true:
-VersionCompat flagsVersionCompat
-Polygon polygon (replaces the previous polygon)
-bool polygonFlagsEnabled
- If polygonFlagsEnabled
-....... FIXME!
-
-polygon The polygon to draw
-
-penInfos The pen style to apply
-
-polygonFlags Indicate the presence of extra polygon information
-....... FIXME: next part is complicated...
-
-
-2.3.3.3 META_POLYPOLYGON_ACTION (111)
-
-A polypolygon action draws a group of polygons.
-
-It contains the following parts
-PolyPolygon polygons
- If the version of the action is greater than 1:
-uint16 numberOfComplexPolygons
-For each complexPolygon :
-uint16 complexPolygonIndex
-Polygon complexPolygon
-
-The complexPolygonIndex is used to replace a polygon from the polygons list.
-
-
-2.3.3.4 META_TEXT_ACTION
-
-...
-
-
-2.3.3.5 META_TEXTARRAY_ACTION
-
-The textarray action draws a complex text.
-
-It contains the following parts
-
-Point startPoint
-String string
-uint16 startIndex
-uint16 len
-uint32 dxArrayLen
-int32 dxArray[]
- If version is greater than 1:
-uint16 len2
-unicodechar chars[len2]
-
-
-startPoint The point where the string is drawn
-
-string the text string
-
-startIndex The index of the first character of the string that
- should be drawn.
-
-len The number of characters that should be drawn,
- starting with the character at 'startIndex'.
-
-dxArrayLen The number of character offsets in dxArray
-
-dxArray An array of character offsets for the characters in
- the string. The number of offsets is stored in
- dxArrayLen. FIXME: What unit is this in?
-
-
-2.3.4 Unsorted Action Types
-- - - - - - - - - - - - - -
-
-These action types have so far not being sorted into any other type.
-
- META_PIXEL_ACTION
- META_POINT_ACTION
- META_LINE_ACTION
- META_RECT_ACTION
- META_ROUNDRECT_ACTION
- META_ELLIPSE_ACTION
- META_ARC_ACTION
- META_PIE_ACTION
- META_CHORD_ACTION
- META_POLYGON_ACTION
- META_STRETCHTEXT_ACTION
- META_TEXTRECT_ACTION
- META_BMP_ACTION
- META_BMPSCALE_ACTION
- META_BMPSCALEPART_ACTION
- META_BMPEX_ACTION
- META_BMPEXSCALE_ACTION
- META_BMPEXSCALEPART_ACTION
- META_MASK_ACTION
- META_MASKSCALE_ACTION
- META_MASKSCALEPART_ACTION
- META_GRADIENT_ACTION
- META_HATCH_ACTION
- META_WALLPAPER_ACTION
- META_CLIPREGION_ACTION
- META_ISECTRECTCLIPREGION_ACTION
- META_ISECTREGIONCLIPREGION_ACTION
- META_MOVECLIPREGION_ACTION
- META_TEXTCOLOR_ACTION
- META_TEXTFILLCOLOR_ACTION
- META_TEXTALIGN_ACTION
- META_FONT_ACTION
- META_PUSH_ACTION
- META_POP_ACTION
- META_RASTEROP_ACTION
- META_TRANSPARENT_ACTION
- META_EPS_ACTION
- META_REFPOINT_ACTION
- META_TEXTLINECOLOR_ACTION
- META_TEXTLINE_ACTION
- META_FLOATTRANSPARENT_ACTION
- META_GRADIENTEX_ACTION
- META_LAYOUTMODE_ACTION
- META_OVERLINECOLOR_ACTION
-
-
-Appendix A: Sources
-
-This documentation has been created by reading the source code of
-LibreOffice. The following files were used:
-
-tools/inc/tools/fontenum.hxx Enums for fonts
- FontFamily, FontPitch, TextAlign,
- FontWeight, FontWidth, FontItalic,
- FontUnderLine, FontStrikeout,
- FontEmphasisMark (#defines instead of enum),
- FontType, FontEmbeddedBitmap, FontAntiAlias
-
-vcl/inc/vcl/gdimtf.hxx The main GDI file object
-vcl/source/gdi/gdimtf.cxx
-vcl/inc/vcl/metaact.hxx Metafile actions
-vcl/source/gdi/metaact.cxx
-vcl/inc/vcl/outdev.hxx Enums, defines and flags for outdevice:
- Push, DrawText, Drawimage, Grid, ComplexTextLayout,
- DrawModes, AntiAlias, AddFontSubstitute
diff --git a/libs/vectorimage/libsvm/SvmAbstractBackend.h b/libs/vectorimage/libsvm/SvmAbstractBackend.h
deleted file mode 100644
index d053b0c1de..0000000000
--- a/libs/vectorimage/libsvm/SvmAbstractBackend.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef SVMABSTRACTBACKEND_H
-#define SVMABSTRACTBACKEND_H
-
-#include "kritavectorimage_export.h"
-
-#include "SvmEnums.h"
-#include "SvmStructs.h"
-#include "SvmGraphicsContext.h"
-
-
-class QPoint;
-class QRect;
-class QPolygon;
-class QString;
-
-
-/**
- \file
-
- Primary definitions for SVM output backend
-*/
-
-/**
- Namespace for StarView Metafile (SVM) classes
-*/
-namespace Libsvm
-{
-
-/**
- Abstract output strategy for SVM Parser
-*/
-class KRITAVECTORIMAGE_EXPORT SvmAbstractBackend
-{
-public:
- SvmAbstractBackend() {};
- virtual ~SvmAbstractBackend() {};
-
- /**
- Initialisation routine
-
- \param header the SVM Header record
- */
- virtual void init(const SvmHeader &header) = 0;
-
- /**
- Cleanup routine
-
- This function is called when the parsing is done. Any
- initializations that are done in init() can be undone here if
- necessary.
- */
- virtual void cleanup() = 0;
-
- /**
- Close-out routine
- */
- virtual void eof() = 0;
-
- virtual void rect(SvmGraphicsContext &context, const QRect &rect) = 0;
-
- /**
- Handler META_POLYLINE_ACTION
-
- This action type specifies how to output a multi-segment line
- (unfilled polyline).
-
- \param context the graphics context to be used when drawing the polyline
- \param polyline the sequence of points that describe the line
-
- \note the line is not meant to be closed nor filled, i.e. do
- not connect the last point to the first point.
- */
- virtual void polyLine(SvmGraphicsContext &context, const QPolygon &polyline) = 0;
-
- virtual void polygon(SvmGraphicsContext &context, const QPolygon &polygon) = 0;
-
- virtual void polyPolygon(SvmGraphicsContext &context, const QList<QPolygon> &polyPolygon) = 0;
-
- virtual void textArray(SvmGraphicsContext &context,
- const QPoint &point, const QString &string,
- quint16 startIndex, quint16 len,
- quint32 dxArrayLen, qint32 *dxArray) = 0;
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libsvm/SvmEnums.h b/libs/vectorimage/libsvm/SvmEnums.h
deleted file mode 100644
index dd142c67ea..0000000000
--- a/libs/vectorimage/libsvm/SvmEnums.h
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef SVMENUMS_H
-#define SVMENUMS_H
-
-
-/**
- \file
-
- Enumerations used in various parts of SVM files
-*/
-
-/**
- Namespace for StarView Metafile (SVM) classes
-*/
-namespace Libsvm
-{
-
- /**
- Action types
-
- See the SPEC Section 2.1.1.1
- */
- enum ActionType {
- META_NULL_ACTION = 0,
- META_PIXEL_ACTION = 100,
- META_POINT_ACTION = 101,
- META_LINE_ACTION = 102,
- META_RECT_ACTION = 103,
- META_ROUNDRECT_ACTION = 104,
- META_ELLIPSE_ACTION = 105,
- META_ARC_ACTION = 106,
- META_PIE_ACTION = 107,
- META_CHORD_ACTION = 108,
- META_POLYLINE_ACTION = 109,
- META_POLYGON_ACTION = 110,
- META_POLYPOLYGON_ACTION = 111,
- META_TEXT_ACTION = 112,
- META_TEXTARRAY_ACTION = 113,
- META_STRETCHTEXT_ACTION = 114,
- META_TEXTRECT_ACTION = 115,
- META_BMP_ACTION = 116,
- META_BMPSCALE_ACTION = 117,
- META_BMPSCALEPART_ACTION = 118,
- META_BMPEX_ACTION = 119,
- META_BMPEXSCALE_ACTION = 120,
- META_BMPEXSCALEPART_ACTION = 121,
- META_MASK_ACTION = 122,
- META_MASKSCALE_ACTION = 123,
- META_MASKSCALEPART_ACTION = 124,
- META_GRADIENT_ACTION = 125,
- META_HATCH_ACTION = 126,
- META_WALLPAPER_ACTION = 127,
- META_CLIPREGION_ACTION = 128,
- META_ISECTRECTCLIPREGION_ACTION = 129,
- META_ISECTREGIONCLIPREGION_ACTION = 130,
- META_MOVECLIPREGION_ACTION = 131,
- META_LINECOLOR_ACTION = 132,
- META_FILLCOLOR_ACTION = 133,
- META_TEXTCOLOR_ACTION = 134,
- META_TEXTFILLCOLOR_ACTION = 135,
- META_TEXTALIGN_ACTION = 136,
- META_MAPMODE_ACTION = 137,
- META_FONT_ACTION = 138,
- META_PUSH_ACTION = 139,
- META_POP_ACTION = 140,
- META_RASTEROP_ACTION = 141,
- META_TRANSPARENT_ACTION = 142,
- META_EPS_ACTION = 143,
- META_REFPOINT_ACTION = 144,
- META_TEXTLINECOLOR_ACTION = 145,
- META_TEXTLINE_ACTION = 146,
- META_FLOATTRANSPARENT_ACTION = 147,
- META_GRADIENTEX_ACTION = 148,
- META_LAYOUTMODE_ACTION = 149,
- META_TEXTLANGUAGE_ACTION = 150,
- META_OVERLINECOLOR_ACTION = 151,
- META_RENDERGRAPHIC_ACTION = 152,
- META_COMMENT_ACTION = 512
- };
-
-#define META_LAST_ACTION META_RENDERGRAPHIC_ACTION
-
- /**
- Text align
-
-
- FIXME: Define this in the spec
- */
-
- enum TextAlign {
- ALIGN_TOP,
- ALIGN_BASELINE,
- ALIGN_BOTTOM
- };
-
- /**
- Mtf (FIXME)
-
- See the SPEC Section 2.1.1.2
- */
- enum MtfType {
- MTF_CONVERSION_NONE = 0,
- MTF_CONVERSION_1BIT_THRESHOLD = 1,
- MTF_CONVERSION_8BIT_GREYS = 2
- };
-
-
- /**
- Layout Mode
-
- See the SPEC, Section 2.2.2.4
- */
- enum LayoutMode {
- TEXT_LAYOUT_DEFAULT = 0x0000,
- TEXT_LAYOUT_BIDI_LTR = 0x0000,
- TEXT_LAYOUT_BIDI_RTL = 0x0001,
- TEXT_LAYOUT_BIDI_STRONG = 0x0002,
- TEXT_LAYOUT_TEXTORIGIN_LEFT = 0x0004,
- TEXT_LAYOUT_TEXTORIGIN_RIGHT = 0x0008,
- TEXT_LAYOUT_COMPLEX_DISABLED = 0x0100,
- TEXT_LAYOUT_ENABLE_LIGATURES = 0x0200,
- TEXT_LAYOUT_SUBSTITUTE_DIGITS = 0x0400
- };
-
-
- // ----------------------------------------------------------------
- // Flags
- // ----------------------------------------------------------------
-
-
- /**
- Mirror flags
-
- See the SPEC Section 2.1.2.1
- */
- enum MtfMirrorType {
- MTF_MIRROR_NONE = 0x00000000,
- MTF_MIRROR_HORZ = 0x00000001,
- MTF_MIRROR_VERT = 0x00000002
- };
-
-}
-
-
-#endif
diff --git a/libs/vectorimage/libsvm/SvmGraphicsContext.cpp b/libs/vectorimage/libsvm/SvmGraphicsContext.cpp
deleted file mode 100644
index 2674e50887..0000000000
--- a/libs/vectorimage/libsvm/SvmGraphicsContext.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "SvmGraphicsContext.h"
-
-#include <QtGlobal>
-
-
-namespace Libsvm
-{
-
-SvmGraphicsContext::SvmGraphicsContext()
- : lineColor(Qt::black)
- , lineColorSet(true)
- , fillColor(Qt::white)
- , fillColorSet(false)
- , textColor(Qt::black)
- , textFillColor(Qt::black)
- , textFillColorSet(false)
- , textAlign(ALIGN_TOP) // FIXME: Correct?
- , mapMode()
- , font("Helvetica", 300) // 300 is of course a completely arbitrary value
- , overlineColor(Qt::black)
- , overlineColorSet(false)
- //... more here
- , changedItems(0xffffffff) // Everything changed the first time.
-{
-}
-
-
-}
diff --git a/libs/vectorimage/libsvm/SvmGraphicsContext.h b/libs/vectorimage/libsvm/SvmGraphicsContext.h
deleted file mode 100644
index 1ff4fb6183..0000000000
--- a/libs/vectorimage/libsvm/SvmGraphicsContext.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef SVMGRAPHICSCONTEXT_H
-#define SVMGRAPHICSCONTEXT_H
-
-
-#include <QColor>
-#include <QBrush>
-#include <QFont>
-
-#include "SvmEnums.h"
-#include "SvmStructs.h"
-
-
-/**
- * @file
- *
- * Graphics Context that is used in the backends of the SVM parser.
- */
-
-/**
- Namespace for StarView Metafile (SVM) classes
-*/
-namespace Libsvm
-{
-
-
-enum GraphicsContextMembers {
- GCLineColor = 0x0001,
- GCFillColor = 0x0002,
- GCTextColor = 0x0004,
- GCTextFillColor = 0x0008,
- GCTextAlign = 0x0010,
- GCMapMode = 0x0020,
- GCFont = 0x0040,
- GCOverlineColor = 0x0080
- // ...more here
-};
-
-
-struct SvmGraphicsContext {
- SvmGraphicsContext();
-
- QColor lineColor;
- bool lineColorSet; // true: use lineColor, false: set penStyle to Qt::NoPen.
- QColor fillColor;
- bool fillColorSet; // true: use fillColor, false: set brushStyle to Qt::NoBrush.
- QColor textColor;
- QColor textFillColor;
- bool textFillColorSet;
- TextAlign textAlign;
- MapMode mapMode;
- quint32 layoutMode;
- QFont font;
- QColor overlineColor;
- bool overlineColorSet;
- // ... much more here;
-
- quint32 changedItems; // bitmap
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libsvm/SvmPainterBackend.cpp b/libs/vectorimage/libsvm/SvmPainterBackend.cpp
deleted file mode 100644
index 4bc83ae388..0000000000
--- a/libs/vectorimage/libsvm/SvmPainterBackend.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-
-// Own
-#include "SvmPainterBackend.h"
-
-// Qt
-#include <QPoint>
-#include <QRect>
-#include <QPolygon>
-#include <QString>
-#include <QPainter>
-
-// KDE
-#include <VectorImageDebug.h>
-
-// Libsvm
-#include "SvmEnums.h"
-#include "SvmStructs.h"
-#include "SvmGraphicsContext.h"
-
-
-#define DEBUG_SVMPAINT 0
-
-
-/**
- Namespace for StarView Metafile (SVM) classes
-*/
-namespace Libsvm
-{
-
-SvmPainterBackend::SvmPainterBackend(QPainter *painter, const QSize &outputSize)
- : m_painter(painter)
- , m_outputSize(outputSize)
-{
-}
-
-SvmPainterBackend::~SvmPainterBackend()
-{
-}
-
-
-void SvmPainterBackend::init(const SvmHeader &header)
-{
- // This is restored in cleanup().
- m_painter->save();
-
- qreal scaleX = qreal( m_outputSize.width() ) / header.width;
- qreal scaleY = qreal( m_outputSize.height() ) / header.height;
-
-#if DEBUG_SVMPAINT
- debugVectorImage << "scale before:" << scaleX << ", " << scaleY;
-#endif
-
- // Keep aspect ratio. Use the smaller value so that we don't get
- // an overflow in any direction.
-#if 0 // Set this to 1 to keep aspect ratio.
- if ( scaleX > scaleY )
- scaleX = scaleY;
- else
- scaleY = scaleX;
-#endif
-#if DEBUG_SVMPAINT
- debugVectorImage << "shape size:" << m_outputSize;
- debugVectorImage << "scale after:" << scaleX << ", " << scaleY;
-#endif
-
- // Transform the SVM object so that it fits in the shape as much
- // as possible. The topleft will be the top left of the shape.
- m_painter->scale( scaleX, scaleY );
- //m_painter->translate(-header->bounds().left(), -header->bounds().top());
-
- m_outputTransform = m_painter->transform();
- //m_worldTransform = QTransform();
-
- m_painter->setRenderHint(QPainter::Antialiasing);
- m_painter->setRenderHint(QPainter::TextAntialiasing);
-}
-
-void SvmPainterBackend::cleanup()
-{
- // Restore the painter to what it was before init() was called.
- m_painter->restore();
-}
-
-void SvmPainterBackend::eof()
-{
-}
-
-
-// ----------------------------------------------------------------
-// Graphics output
-
-
-void SvmPainterBackend::rect( SvmGraphicsContext &context, const QRect &rect )
-{
- updateFromGraphicscontext(context);
- m_painter->drawRect(rect);
-}
-
-void SvmPainterBackend::polyLine( SvmGraphicsContext &context, const QPolygon &polyline )
-{
- updateFromGraphicscontext(context);
- m_painter->drawPolyline(polyline);
-}
-
-void SvmPainterBackend::polygon( SvmGraphicsContext &context, const QPolygon &polygon )
-{
- updateFromGraphicscontext(context);
- m_painter->drawPolygon(polygon);
-}
-
-void SvmPainterBackend::polyPolygon(SvmGraphicsContext &context,
- const QList<QPolygon> &polyPolygon)
-{
- updateFromGraphicscontext(context);
-
- QPainterPath path;
-
- path.setFillRule(Qt::OddEvenFill);
- //path.setFillRule(Qt::WindingFill);
- foreach (const QPolygon &polygon, polyPolygon) {
- path.addPolygon(polygon);
- }
- m_painter->drawPath(path);
-}
-
-void SvmPainterBackend::textArray(SvmGraphicsContext &context,
- const QPoint &point, const QString &string,
- quint16 startIndex, quint16 len,
- quint32 dxArrayLen, qint32 *dxArray)
-{
- updateFromGraphicscontext(context);
-
- m_painter->save();
- m_painter->setPen(context.textColor);
- // FIXME: Handle text background color. How do we get the area? A testfile would be nice.
- m_painter->drawText(point, string.mid(startIndex, len));
-
- // FIXME: DxArray not handled yet.
- Q_UNUSED(dxArrayLen);
- Q_UNUSED(dxArray);
-
- m_painter->restore();
-}
-
-
-// ----------------------------------------------------------------
-// Private functions
-
-void SvmPainterBackend::updateFromGraphicscontext(SvmGraphicsContext &context)
-{
- if (context.changedItems & GCLineColor) {
- QPen pen = m_painter->pen();
- if (context.lineColorSet) {
- pen.setColor(context.lineColor);
- pen.setStyle(Qt::SolidLine);
- }
- else
- pen.setStyle(Qt::NoPen);
- m_painter->setPen(pen);
-#if DEBUG_SVMPAINT
- debugVectorImage << "*** Setting line color to" << context.lineColor;
-#endif
- }
- if (context.changedItems & GCFillColor) {
- QBrush brush(m_painter->brush());
- if (context.fillColorSet) {
- brush.setColor(context.fillColor);
- brush.setStyle(Qt::SolidPattern);
- }
- else
- brush.setStyle(Qt::NoBrush);
- m_painter->setBrush(brush);
-#if DEBUG_SVMPAINT
- if (context.fillColorSet)
- debugVectorImage << "*** Setting fill color to" << context.fillColor;
- else
- debugVectorImage << "*** Unsetting fill color";
-#endif
- }
- // GCTextColor: We don't need to do anything here since text color
- // is set when the text is drawn.
- // GCTextFillColor: We don't need to do anything here since text
- // fill color is set when the text is drawn.
- // GCTextAlign: We don't need to do anything here since text
- // alignment is only used when the text is drawn.
- if (context.changedItems & GCMapMode) {
- // Reset the transform and then apply the new mapmode to it.
- m_painter->setTransform(m_outputTransform);
- m_painter->translate(context.mapMode.origin);
- // FIXME: Do scaling here too. But we need a testfile for that.
- }
- if (context.changedItems & GCFont) {
- m_painter->setFont(context.font);
-#if DEBUG_SVMPAINT
- debugVectorImage << "*** Setting font to" << context.font;
-#endif
- }
- if (context.changedItems & GCOverlineColor) {
- // FIXME
- }
-
- // Reset all changes until next time.
- context.changedItems = 0;
-}
-
-
-
-}
diff --git a/libs/vectorimage/libsvm/SvmPainterBackend.h b/libs/vectorimage/libsvm/SvmPainterBackend.h
deleted file mode 100644
index 2d2e285113..0000000000
--- a/libs/vectorimage/libsvm/SvmPainterBackend.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- Copyright 2009 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef SVMPAINTERBACKEND_H
-#define SVMPAINTERBACKEND_H
-
-#include "SvmAbstractBackend.h"
-#include "kritavectorimage_export.h"
-
-#include <QSize>
-#include <QTransform>
-
-#include "SvmEnums.h"
-#include "SvmStructs.h"
-#include "SvmGraphicsContext.h"
-
-class QRect;
-class QPolygon;
-class QPainter;
-
-
-/**
- \file
-
- Primary definitions for SVM output backend
-*/
-
-/**
- Namespace for StarView Metafile (SVM) classes
-*/
-namespace Libsvm
-{
-
-/**
- Painter output strategy for SVM Parser
-*/
-class KRITAVECTORIMAGE_EXPORT SvmPainterBackend : public SvmAbstractBackend
-{
-public:
- SvmPainterBackend(QPainter *painter, const QSize &outputSize);
- ~SvmPainterBackend() override;
-
- /**
- Initialisation routine
-
- \param header the SVM Header record
- */
- void init(const SvmHeader &header) override;
-
- /**
- Cleanup routine
-
- This function is called when the painting is done. Any
- initializations that are done in init() can be undone here if
- necessary.
- */
- void cleanup() override;
-
- /**
- Close-out routine
- */
- void eof() override;
-
- void rect( SvmGraphicsContext &context, const QRect &rect ) override;
-
- /**
- Handler META_POLYLINE_ACTION
-
- This action type specifies how to output a multi-segment line
- (unfilled polyline).
-
- \param context the graphics context to be used when drawing the polyline
- \param polyline the sequence of points that describe the line
-
- \note the line is not meant to be closed (i.e. do not connect
- the last point to the first point) or filled.
- */
- void polyLine(SvmGraphicsContext &context, const QPolygon &polyline) override;
-
- void polygon(SvmGraphicsContext &context, const QPolygon &polygon) override;
-
- void polyPolygon(SvmGraphicsContext &context, const QList<QPolygon> &polyPolygon) override;
-
- void textArray(SvmGraphicsContext &context,
- const QPoint &point, const QString &string,
- quint16 startIndex, quint16 len,
- quint32 dxArrayLen, qint32 *dxArray) override;
-
- private:
- void updateFromGraphicscontext(SvmGraphicsContext &context);
-
- private:
- QPainter *m_painter;
- QSize m_outputSize;
-
- QTransform m_outputTransform;
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libsvm/SvmParser.cpp b/libs/vectorimage/libsvm/SvmParser.cpp
deleted file mode 100644
index 0bc47922f4..0000000000
--- a/libs/vectorimage/libsvm/SvmParser.cpp
+++ /dev/null
@@ -1,680 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
- Copyright 2011 Pierre Ducroquet <pinaraf@pinaraf.info>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-
-// Own
-#include "SvmParser.h"
-
-// Qt
-#include <QByteArray>
-#include <QBuffer>
-#include <QDataStream>
-#include <QString>
-#include <QPolygon>
-
-// KDE
-#include <VectorImageDebug.h>
-
-// Libsvm
-#include "SvmEnums.h"
-#include "SvmStructs.h"
-
-// 0 - No debug
-// 1 - Print a lot of debug info
-// 2 - Just print all the records instead of parsing them
-#define DEBUG_SVMPARSER 0
-
-namespace Libsvm
-{
-
-
-static void soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-
-SvmParser::SvmParser()
- : mContext()
- , mBackend(0)
-{
-}
-
-
-static const struct ActionNames {
- int actionNumber;
- QString actionName;
-} actionNames[] = {
- { META_NULL_ACTION, "META_NULL_ACTION" },
- { META_PIXEL_ACTION, "META_PIXEL_ACTION" },
- { META_POINT_ACTION, "META_POINT_ACTION" },
- { META_LINE_ACTION, "META_LINE_ACTION" },
- { META_RECT_ACTION, "META_RECT_ACTION" },
- { META_ROUNDRECT_ACTION, "META_ROUNDRECT_ACTION" },
- { META_ELLIPSE_ACTION, "META_ELLIPSE_ACTION" },
- { META_ARC_ACTION, "META_ARC_ACTION" },
- { META_PIE_ACTION, "META_PIE_ACTION" },
- { META_CHORD_ACTION, "META_CHORD_ACTION" },
- { META_POLYLINE_ACTION, "META_POLYLINE_ACTION" },
- { META_POLYGON_ACTION, "META_POLYGON_ACTION" },
- { META_POLYPOLYGON_ACTION, "META_POLYPOLYGON_ACTION" },
- { META_TEXT_ACTION, "META_TEXT_ACTION" },
- { META_TEXTARRAY_ACTION, "META_TEXTARRAY_ACTION" },
- { META_STRETCHTEXT_ACTION, "META_STRETCHTEXT_ACTION" },
- { META_TEXTRECT_ACTION, "META_TEXTRECT_ACTION" },
- { META_BMP_ACTION, "META_BMP_ACTION" },
- { META_BMPSCALE_ACTION, "META_BMPSCALE_ACTION" },
- { META_BMPSCALEPART_ACTION, "META_BMPSCALEPART_ACTION" },
- { META_BMPEX_ACTION, "META_BMPEX_ACTION" },
- { META_BMPEXSCALE_ACTION, "META_BMPEXSCALE_ACTION" },
- { META_BMPEXSCALEPART_ACTION, "META_BMPEXSCALEPART_ACTION" },
- { META_MASK_ACTION, "META_MASK_ACTION" },
- { META_MASKSCALE_ACTION, "META_MASKSCALE_ACTION" },
- { META_MASKSCALEPART_ACTION, "META_MASKSCALEPART_ACTION" },
- { META_GRADIENT_ACTION, "META_GRADIENT_ACTION" },
- { META_HATCH_ACTION, "META_HATCH_ACTION" },
- { META_WALLPAPER_ACTION, "META_WALLPAPER_ACTION" },
- { META_CLIPREGION_ACTION, "META_CLIPREGION_ACTION" },
- { META_ISECTRECTCLIPREGION_ACTION, "META_ISECTRECTCLIPREGION_ACTION" },
- { META_ISECTREGIONCLIPREGION_ACTION, "META_ISECTREGIONCLIPREGION_ACTION" },
- { META_MOVECLIPREGION_ACTION, "META_MOVECLIPREGION_ACTION" },
- { META_LINECOLOR_ACTION, "META_LINECOLOR_ACTION" },
- { META_FILLCOLOR_ACTION, "META_FILLCOLOR_ACTION" },
- { META_TEXTCOLOR_ACTION, "META_TEXTCOLOR_ACTION" },
- { META_TEXTFILLCOLOR_ACTION, "META_TEXTFILLCOLOR_ACTION" },
- { META_TEXTALIGN_ACTION, "META_TEXTALIGN_ACTION" },
- { META_MAPMODE_ACTION, "META_MAPMODE_ACTION" },
- { META_FONT_ACTION, "META_FONT_ACTION" },
- { META_PUSH_ACTION, "META_PUSH_ACTION" },
- { META_POP_ACTION, "META_POP_ACTION" },
- { META_RASTEROP_ACTION, "META_RASTEROP_ACTION" },
- { META_TRANSPARENT_ACTION, "META_TRANSPARENT_ACTION" },
- { META_EPS_ACTION, "META_EPS_ACTION" },
- { META_REFPOINT_ACTION, "META_REFPOINT_ACTION" },
- { META_TEXTLINECOLOR_ACTION, "META_TEXTLINECOLOR_ACTION" },
- { META_TEXTLINE_ACTION, "META_TEXTLINE_ACTION" },
- { META_FLOATTRANSPARENT_ACTION, "META_FLOATTRANSPARENT_ACTION" },
- { META_GRADIENTEX_ACTION, "META_GRADIENTEX_ACTION" },
- { META_LAYOUTMODE_ACTION, "META_LAYOUTMODE_ACTION" },
- { META_TEXTLANGUAGE_ACTION, "META_TEXTLANGUAGE_ACTION" },
- { META_OVERLINECOLOR_ACTION, "META_OVERLINECOLOR_ACTION" },
- { META_RENDERGRAPHIC_ACTION, "META_RENDERGRAPHIC_ACTION" },
- { META_COMMENT_ACTION, "META_COMMENT_ACTION" }
-};
-
-
-void SvmParser::setBackend(SvmAbstractBackend *backend)
-{
- mBackend = backend;
-}
-
-
-bool SvmParser::parse(const QByteArray &data)
-{
- // Check the signature "VCLMTF"
- if (!data.startsWith("VCLMTF"))
- return false;
-
- QBuffer buffer((QByteArray *) &data);
- buffer.open(QIODevice::ReadOnly);
-
- QDataStream mainStream(&buffer);
- mainStream.setByteOrder(QDataStream::LittleEndian);
-
- // Start reading from the stream: read past the signature and get the header.
- soakBytes(mainStream, 6);
- SvmHeader header(mainStream);
-#if DEBUG_SVMPARSER
- debugVectorImage << "================ SVM HEADER ================";
- debugVectorImage << "version, length:" << header.versionCompat.version << header.versionCompat.length;
- debugVectorImage << "compressionMode:" << header.compressionMode;
- debugVectorImage << "mapMode:" << "Origin" << header.mapMode.origin
- << "scaleX"
- << header.mapMode.scaleX.numerator << header.mapMode.scaleX.denominator
- << (qreal(header.mapMode.scaleX.numerator) / header.mapMode.scaleX.denominator)
- << "scaleY"
- << header.mapMode.scaleY.numerator << header.mapMode.scaleY.denominator
- << (qreal(header.mapMode.scaleY.numerator) / header.mapMode.scaleY.denominator);
- debugVectorImage << "size:" << header.width << header.height;
- debugVectorImage << "actionCount:" << header.actionCount;
- debugVectorImage << "================ SVM HEADER ================";
-#endif
-
- mBackend->init(header);
-
-#if DEBUG_SVMPARSER
- {
- QPolygon polygon;
- polygon << QPoint(0, 0);
- polygon << QPoint(header.width, header.height);
- mBackend->polyLine(mContext, polygon);
- }
-#endif
-
- // Parse all actions and call the appropriate backend callback for
- // the graphics drawing actions. The context actions will
- // manipulate the graphics context, which is maintained here.
- for (uint action = 0; action < header.actionCount; ++action) {
- quint16 actionType;
- quint16 version;
- quint32 totalSize;
-
- // Here starts the Action itself. The first two bytes is the action type.
- mainStream >> actionType;
-
- // The VersionCompat object
- mainStream >> version;
- mainStream >> totalSize;
-
- char *rawData = new char[totalSize];
- mainStream.readRawData(rawData, totalSize);
- QByteArray dataArray(rawData, totalSize);
- QDataStream stream(&dataArray, QIODevice::ReadOnly);
- stream.setByteOrder(QDataStream::LittleEndian);
-
- // Debug
-#if DEBUG_SVMPARSER
- {
- QString name;
- if (actionType == 0)
- name = actionNames[0].actionName;
- else if (100 <= actionType && actionType <= META_LAST_ACTION)
- name = actionNames[actionType - 99].actionName;
- else if (actionType == 512)
- name = "META_COMMENT_ACTION";
- else
- name = "(out of bounds)";
-
- debugVectorImage << name << "(" << actionType << ")" << "version" << version
- << "totalSize" << totalSize;
- }
-#endif
-
- // Parse all actions.
- switch (actionType) {
- case META_NULL_ACTION:
- break;
- case META_PIXEL_ACTION:
- break;
- case META_POINT_ACTION:
- break;
- case META_LINE_ACTION:
- break;
- case META_RECT_ACTION:
- {
- QRect rect;
-
- parseRect(stream, rect);
- debugVectorImage << "Rect:" << rect;
- mBackend->rect(mContext, rect);
- }
- break;
- case META_ROUNDRECT_ACTION:
- break;
- case META_ELLIPSE_ACTION:
- break;
- case META_ARC_ACTION:
- break;
- case META_PIE_ACTION:
- break;
- case META_CHORD_ACTION:
- break;
- case META_POLYLINE_ACTION:
- {
- QPolygon polygon;
-
- parsePolygon(stream, polygon);
- debugVectorImage << "Polyline:" << polygon;
- mBackend->polyLine(mContext, polygon);
-
- // FIXME: Version 2: Lineinfo, Version 3: polyflags
- if (version > 1)
- soakBytes(stream, totalSize - 2 - 4 * 2 * polygon.size());
- }
- break;
- case META_POLYGON_ACTION:
- {
- QPolygon polygon;
-
- parsePolygon(stream, polygon);
- debugVectorImage << "Polygon:" << polygon;
- mBackend->polygon(mContext, polygon);
-
- // FIXME: Version 2: Lineinfo, Version 3: polyflags
- if (version > 1)
- soakBytes(stream, totalSize - 2 - 4 * 2 * polygon.size());
- }
- break;
- case META_POLYPOLYGON_ACTION:
- {
- quint16 polygonCount;
- stream >> polygonCount;
- //debugVectorImage << "Number of polygons:" << polygonCount;
-
- QList<QPolygon> polygons;
- for (quint16 i = 0 ; i < polygonCount ; i++) {
- QPolygon polygon;
- parsePolygon(stream, polygon);
- polygons << polygon;
- //debugVectorImage << "Polygon:" << polygon;
- }
-
- if (version > 1) {
- quint16 complexPolygonCount;
- stream >> complexPolygonCount;
- //debugVectorImage << "Number of complex polygons:" << complexPolygonCount;
-
- // Parse the so called "complex polygons". For
- // each one, there is an index and a polygon. The
- // index tells which of the original polygons to
- // replace.
- for (quint16 i = 0; i < complexPolygonCount; i++) {
- quint16 complexPolygonIndex;
- stream >> complexPolygonIndex;
-
- QPolygon polygon;
- parsePolygon(stream, polygon);
- //debugVectorImage << "polygon index:" << complexPolygonIndex << polygon;
-
- // FIXME: The so called complex polygons have something to do
- // with modifying the polygons, but I have not yet been
- // able to understand how. So until I do, we'll disable
- // this.
- //polygons[complexPolygonIndex] = polygon;
- }
- }
-
- mBackend->polyPolygon(mContext, polygons);
- }
- break;
- case META_TEXT_ACTION:
- break;
- case META_TEXTARRAY_ACTION:
- {
- QPoint startPoint;
- QString string;
- quint16 startIndex;
- quint16 len;
- quint32 dxArrayLen;
- qint32 *dxArray = 0;
-
- stream >> startPoint;
- parseString(stream, string);
- stream >> startIndex;
- stream >> len;
- stream >> dxArrayLen;
- if (dxArrayLen > 0) {
- quint32 maxDxArrayLen = totalSize - stream.device()->pos();
- if (dxArrayLen > maxDxArrayLen) {
- debugVectorImage << "Defined dxArrayLen= " << dxArrayLen << "exceeds available size" << maxDxArrayLen;
- dxArrayLen = maxDxArrayLen;
- }
-
- dxArray = new qint32[dxArrayLen];
-
- for (uint i = 0; i < dxArrayLen; ++i)
- stream >> dxArray[i];
- }
-
- if (version > 1) {
- quint16 len2;
-
- stream >> len2;
- // FIXME: More here
- }
-
-#if 0
- debugVectorImage << "Text: " << startPoint << string
- << startIndex << len;
- if (dxArrayLen > 0) {
- debugVectorImage << "dxArrayLen:" << dxArrayLen;
- for (uint i = 0; i < dxArrayLen; ++i)
- debugVectorImage << dxArray[i];
- }
- else
- debugVectorImage << "dxArrayLen = 0";
-#endif
- mBackend->textArray(mContext, startPoint, string, startIndex, len,
- dxArrayLen, dxArray);
-
- if (dxArrayLen)
- delete[] dxArray;
- }
- break;
- case META_STRETCHTEXT_ACTION:
- break;
- case META_TEXTRECT_ACTION:
- break;
- case META_BMP_ACTION:
- break;
- case META_BMPSCALE_ACTION:
- break;
- case META_BMPSCALEPART_ACTION:
- break;
- case META_BMPEX_ACTION:
- break;
- case META_BMPEXSCALE_ACTION:
- break;
- case META_BMPEXSCALEPART_ACTION:
- break;
- case META_MASK_ACTION:
- break;
- case META_MASKSCALE_ACTION:
- break;
- case META_MASKSCALEPART_ACTION:
- break;
- case META_GRADIENT_ACTION:
- break;
- case META_HATCH_ACTION:
- break;
- case META_WALLPAPER_ACTION:
- break;
- case META_CLIPREGION_ACTION:
- break;
- case META_ISECTRECTCLIPREGION_ACTION:
- break;
- case META_ISECTREGIONCLIPREGION_ACTION:
- break;
- case META_MOVECLIPREGION_ACTION:
- break;
- case META_LINECOLOR_ACTION:
- {
- quint32 colorData;
-
- stream >> colorData;
- stream >> mContext.lineColorSet;
-
- mContext.lineColor = QColor::fromRgb(colorData);
- debugVectorImage << "Color:" << mContext.lineColor
- << '(' << mContext.lineColorSet << ')';
- mContext.changedItems |= GCLineColor;
- }
- break;
- case META_FILLCOLOR_ACTION:
- {
- quint32 colorData;
-
- stream >> colorData;
- stream >> mContext.fillColorSet;
- //mContext.fillColorSet = false;
-
- debugVectorImage << "Fill color :" << hex << colorData << dec
- << '(' << mContext.fillColorSet << ')';
-
- mContext.fillColor = QColor::fromRgb(colorData);
- mContext.changedItems |= GCFillColor;
- }
- break;
- case META_TEXTCOLOR_ACTION:
- {
- quint32 colorData;
- stream >> colorData;
-
- mContext.textColor = QColor::fromRgb(colorData);
- debugVectorImage << "Color:" << mContext.textColor;
- mContext.changedItems |= GCTextColor;
- }
- break;
- case META_TEXTFILLCOLOR_ACTION:
- {
- quint32 colorData;
-
- stream >> colorData;
- stream >> mContext.textFillColorSet;
-
- debugVectorImage << "Text fill color :" << hex << colorData << dec
- << '(' << mContext.textFillColorSet << ')';
-
- mContext.textFillColor = QColor::fromRgb(colorData);
- debugVectorImage << "Color:" << mContext.textFillColor
- << '(' << mContext.textFillColorSet << ')';
- mContext.changedItems |= GCTextFillColor;
- }
- break;
- case META_TEXTALIGN_ACTION:
- {
- quint16 textAlign;
- stream >> textAlign;
-
- mContext.textAlign = (TextAlign)textAlign;
- debugVectorImage << "TextAlign:" << mContext.textAlign;
- mContext.changedItems |= GCTextAlign;
- }
- break;
- case META_MAPMODE_ACTION:
- {
- stream >> mContext.mapMode;
- debugVectorImage << "mapMode:" << "Origin" << mContext.mapMode.origin
- << "scaleX"
- << mContext.mapMode.scaleX.numerator << mContext.mapMode.scaleX.denominator
- << (qreal(mContext.mapMode.scaleX.numerator) / mContext.mapMode.scaleX.denominator)
- << "scaleY"
- << mContext.mapMode.scaleY.numerator << mContext.mapMode.scaleY.denominator
- << (qreal(mContext.mapMode.scaleY.numerator) / mContext.mapMode.scaleY.denominator);
- mContext.changedItems |= GCMapMode;
- }
- break;
- case META_FONT_ACTION:
- {
- parseFont(stream, mContext.font);
- debugVectorImage << "Font:" << mContext.font;
- mContext.changedItems |= GCFont;
- }
- break;
- case META_PUSH_ACTION:
- {
- debugVectorImage << "Push action : " << totalSize;
- quint16 pushValue;
- stream >> pushValue;
- debugVectorImage << "Push value : " << pushValue;
- }
- break;
- case META_POP_ACTION:
- {
- debugVectorImage << "Pop action : " << totalSize;
- /*quint16 pushValue;
- stream >> pushValue;
- debugVectorImage << "Push value : " << pushValue;*/
- }
- break;
- case META_RASTEROP_ACTION:
- break;
- case META_TRANSPARENT_ACTION:
- break;
- case META_EPS_ACTION:
- break;
- case META_REFPOINT_ACTION:
- break;
- case META_TEXTLINECOLOR_ACTION:
- break;
- case META_TEXTLINE_ACTION:
- break;
- case META_FLOATTRANSPARENT_ACTION:
- break;
- case META_GRADIENTEX_ACTION:
- break;
- case META_LAYOUTMODE_ACTION:
- {
- stream >> mContext.layoutMode;
- debugVectorImage << "New layout mode:" << hex << mContext.layoutMode << dec << "hex";
- }
- break;
- case META_TEXTLANGUAGE_ACTION:
- break;
- case META_OVERLINECOLOR_ACTION:
- {
- quint32 colorData;
-
- stream >> colorData;
- stream >> mContext.overlineColorSet;
-
- debugVectorImage << "Overline color :" << colorData
- << '(' << mContext.overlineColorSet << ')';
-
- mContext.overlineColor = QColor::fromRgb(colorData);
- mContext.changedItems |= GCOverlineColor;
- }
- break;
- case META_RENDERGRAPHIC_ACTION:
- //dumpAction(stream, version, totalSize);
- break;
- case META_COMMENT_ACTION:
- break;
-
- default:
-#if DEBUG_SVMPARSER
- debugVectorImage << "unknown action type:" << actionType;
-#endif
- break;
- }
-
- delete [] rawData;
-
- // Security measure
- if (mainStream.atEnd())
- break;
- }
-
- mBackend->cleanup();
-
- return true;
-}
-
-
-// ----------------------------------------------------------------
-// Private methods
-
-
-void SvmParser::parseRect(QDataStream &stream, QRect &rect)
-{
- qint32 left;
- qint32 top;
- qint32 right;
- qint32 bottom;
-
- stream >> left;
- stream >> top;
- stream >> right;
- stream >> bottom;
-
- rect.setLeft(left);
- rect.setTop(top);
- rect.setRight(right);
- rect.setBottom(bottom);
-}
-
-void SvmParser::parsePolygon(QDataStream &stream, QPolygon &polygon)
-{
- quint16 numPoints;
- QPoint point;
-
- stream >> numPoints;
- for (uint i = 0; i < numPoints; ++i) {
- stream >> point;
- polygon << point;
- }
-}
-
-void SvmParser::parseString(QDataStream &stream, QString &string)
-{
- quint16 length;
-
- stream >> length;
- for (uint i = 0; i < length; ++i) {
- quint8 ch;
- stream >> ch;
- string += char(ch);
- }
-}
-
-void SvmParser::parseFont(QDataStream &stream, QFont &font)
-{
- quint16 version;
- quint32 totalSize;
-
- // the VersionCompat struct
- stream >> version;
- stream >> totalSize;
-
- // Name and style
- QString family;
- QString style;
- parseString(stream, family);
- parseString(stream, style);
- font.setFamily(family);
-
- // Font size
- quint32 width;
- quint32 height;
- stream >> width;
- stream >> height;
- // Multiply by 0.7 since it seems that, just like WMF, the height
- // in the font struct is actually the height of the character cell.
- font.setPointSize(height * 7 / 10);
-
- qint8 temp8;
- bool tempbool;
- quint16 tempu16;
- stream >> tempu16; // charset
- stream >> tempu16; // family
- stream >> tempu16; // pitch
- stream >> tempu16; // weight
- stream >> tempu16; // underline
- font.setUnderline(tempu16);
- stream >> tempu16; // strikeout
- stream >> tempu16; // italic
- font.setItalic(tempu16);
- stream >> tempu16; // language
- stream >> tempu16; // width
- stream >> tempu16; // orientation
-
- stream >> tempbool; // wordline
- stream >> tempbool; // outline
- stream >> tempbool; // shadow
- stream >> temp8; // kerning
-
- if (version > 1) {
- stream >> temp8; // relief
- stream >> tempu16; // language
- stream >> tempbool; // vertical
- stream >> tempu16; // emphasis
- }
-
- if (version > 2) {
- stream >> tempu16; // overline
- }
-
- // FIXME: Read away the rest of font here to allow for higher versions than 3.
-}
-
-
-void SvmParser::dumpAction(QDataStream &stream, quint16 version, quint32 totalSize)
-{
- debugVectorImage << "Version: " << version;
- for (uint i = 0; i < totalSize; ++i) {
- quint8 temp;
- stream >> temp;
- debugVectorImage << hex << i << temp << dec;
- }
-}
-
-} // namespace Libsvm
diff --git a/libs/vectorimage/libsvm/SvmParser.h b/libs/vectorimage/libsvm/SvmParser.h
deleted file mode 100644
index 33b10bae06..0000000000
--- a/libs/vectorimage/libsvm/SvmParser.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef SVMPARSER_H
-#define SVMPARSER_H
-
-
-#include "SvmGraphicsContext.h"
-#include "SvmAbstractBackend.h"
-#include "kritavectorimage_export.h"
-
-class QByteArray;
-class QDataStream;
-
-
-namespace Libsvm
-{
-
-
-class KRITAVECTORIMAGE_EXPORT SvmParser
-{
- public:
- SvmParser();
-
- void setBackend(SvmAbstractBackend *backend);
-
- bool parse(const QByteArray &data);
-
- private:
- void parseRect(QDataStream &stream, QRect &rect);
- void parsePolygon(QDataStream &stream, QPolygon &polygon);
- void parseString(QDataStream &stream, QString &string);
- void parseFont(QDataStream &stream, QFont &font);
-
- void dumpAction(QDataStream &stream, quint16 version, quint32 totalSize);
-
- private:
- SvmGraphicsContext mContext;
- SvmAbstractBackend *mBackend;
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libsvm/SvmStructs.cpp b/libs/vectorimage/libsvm/SvmStructs.cpp
deleted file mode 100644
index 6b4100c2d3..0000000000
--- a/libs/vectorimage/libsvm/SvmStructs.cpp
+++ /dev/null
@@ -1,136 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "SvmStructs.h"
-
-#include <QDataStream>
-
-
-static void soakBytes( QDataStream &stream, int numBytes )
-{
- quint8 scratch;
- for ( int i = 0; i < numBytes; ++i ) {
- stream >> scratch;
- }
-}
-
-
-namespace Libsvm
-{
-
-VersionCompat::VersionCompat()
- : version(0)
- , length(0)
-{
-}
-
-VersionCompat::VersionCompat(QDataStream &stream)
-{
- stream >> version;
- stream >> length;
-}
-
-QDataStream &operator>>(QDataStream &stream, VersionCompat &compat)
-{
- stream >> compat.version;
- stream >> compat.length;
-
- return stream;
-}
-
-
-Fraction::Fraction()
- : numerator(1)
- , denominator(1)
-{
-}
-
-Fraction::Fraction(QDataStream &stream)
-{
- stream >> numerator;
- stream >> denominator;
-}
-
-QDataStream &operator>>(QDataStream &stream, Fraction &fract)
-{
- stream >> fract.numerator;
- stream >> fract.denominator;
-
- return stream;
-}
-
-
-MapMode::MapMode()
- : version()
- , unit(0)
- , origin(0, 0)
- , scaleX()
- , scaleY()
- , isSimple(true)
-{
-}
-
-MapMode::MapMode(QDataStream &stream)
-{
- stream >> *this;
-}
-
-QDataStream &operator>>(QDataStream &stream, MapMode &mm)
-{
- stream >> mm.version;
- stream >> mm.unit;
- stream >> mm.origin;
- stream >> mm.scaleX;
- stream >> mm.scaleY;
- stream >> mm.isSimple; // FIXME: how many bytes?
-
- return stream;
-}
-
-SvmHeader::SvmHeader()
- : versionCompat()
- , compressionMode()
- , mapMode()
- , width(0)
- , height(0)
- , actionCount(0)
-{
-}
-
-SvmHeader::SvmHeader(QDataStream &stream)
-{
- stream >> *this;
-}
-
-QDataStream &operator>>(QDataStream &stream, SvmHeader &header)
-{
- stream >> header.versionCompat;
- stream >> header.compressionMode;
- stream >> header.mapMode;
- stream >> header.width;
- stream >> header.height;
- stream >> header.actionCount;
-
- if (header.versionCompat.version > 1)
- soakBytes(stream, 1);
-
- return stream;
-}
-
-
-}
diff --git a/libs/vectorimage/libsvm/SvmStructs.h b/libs/vectorimage/libsvm/SvmStructs.h
deleted file mode 100644
index c7349a56b9..0000000000
--- a/libs/vectorimage/libsvm/SvmStructs.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef SVMSTRUCT_H
-#define SVMSTRUCT_H
-
-
-#include <QtGlobal>
-#include <QPoint>
-
-class QDataStream;
-
-/**
- * @file
- *
- * Structs used in various parts of SVM files.
- */
-
-/**
- Namespace for StarView Metafile (SVM) classes
-*/
-namespace Libsvm
-{
-
-/**
- * Contains version and length of an action.
- */
-struct VersionCompat {
- VersionCompat();
- VersionCompat(QDataStream &stream);
-
- quint16 version;
- quint32 length;
-};
-
-QDataStream &operator>>(QDataStream &stream, VersionCompat &compat);
-
-
-
-struct Fraction {
- Fraction();
- Fraction(QDataStream &stream);
-
- quint32 numerator;
- quint32 denominator;
-};
-
-QDataStream &operator>>(QDataStream &stream, Fraction &fract);
-
-
-struct MapMode {
- MapMode();
- MapMode(QDataStream &stream);
-
- VersionCompat version;
- quint16 unit;
- QPoint origin;
- Fraction scaleX;
- Fraction scaleY;
- bool isSimple;
-};
-
-QDataStream &operator>>(QDataStream &stream, MapMode &mm);
-
-
-/**
- * The header of an SVM file.
- */
-struct SvmHeader {
- SvmHeader();
- SvmHeader(QDataStream &stream);
-
- VersionCompat versionCompat;
- quint32 compressionMode;
- MapMode mapMode;
- quint32 width;
- quint32 height;
- quint32 actionCount;
-};
-
-QDataStream &operator>>(QDataStream &stream, SvmHeader &header);
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libsvm/tests/circles.svm b/libs/vectorimage/libsvm/tests/circles.svm
deleted file mode 100644
index b676265a57..0000000000
Binary files a/libs/vectorimage/libsvm/tests/circles.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/color_lines.svm b/libs/vectorimage/libsvm/tests/color_lines.svm
deleted file mode 100644
index 599f49da3e..0000000000
Binary files a/libs/vectorimage/libsvm/tests/color_lines.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/crashi.svm b/libs/vectorimage/libsvm/tests/crashi.svm
deleted file mode 100644
index 51e18b6660..0000000000
Binary files a/libs/vectorimage/libsvm/tests/crashi.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/cross.svm b/libs/vectorimage/libsvm/tests/cross.svm
deleted file mode 100644
index 815852d282..0000000000
Binary files a/libs/vectorimage/libsvm/tests/cross.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/gradient.svm b/libs/vectorimage/libsvm/tests/gradient.svm
deleted file mode 100644
index b0b5fe1a0c..0000000000
Binary files a/libs/vectorimage/libsvm/tests/gradient.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/gradient2.svm b/libs/vectorimage/libsvm/tests/gradient2.svm
deleted file mode 100644
index 9703cb4166..0000000000
Binary files a/libs/vectorimage/libsvm/tests/gradient2.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/green_circle.svm b/libs/vectorimage/libsvm/tests/green_circle.svm
deleted file mode 100644
index 46c8bf7b83..0000000000
Binary files a/libs/vectorimage/libsvm/tests/green_circle.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/hello_world.svm b/libs/vectorimage/libsvm/tests/hello_world.svm
deleted file mode 100644
index a84ed6571f..0000000000
Binary files a/libs/vectorimage/libsvm/tests/hello_world.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/love_calligra.svm b/libs/vectorimage/libsvm/tests/love_calligra.svm
deleted file mode 100644
index 2129612c6e..0000000000
Binary files a/libs/vectorimage/libsvm/tests/love_calligra.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/many_items.svm b/libs/vectorimage/libsvm/tests/many_items.svm
deleted file mode 100644
index 93346c54dc..0000000000
Binary files a/libs/vectorimage/libsvm/tests/many_items.svm and /dev/null differ
diff --git a/libs/vectorimage/libsvm/tests/rectangles_opacity.svm b/libs/vectorimage/libsvm/tests/rectangles_opacity.svm
deleted file mode 100644
index 276cc1ad65..0000000000
Binary files a/libs/vectorimage/libsvm/tests/rectangles_opacity.svm and /dev/null differ
diff --git a/libs/vectorimage/libwmf/CMakeLists.txt b/libs/vectorimage/libwmf/CMakeLists.txt
deleted file mode 100644
index 8d16a2c055..0000000000
--- a/libs/vectorimage/libwmf/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-set(libwmf_LIB_SRCS
- WmfStack.cpp
- WmfDeviceContext.cpp
- WmfParser.cpp
- WmfAbstractBackend.cpp
- WmfPainterBackend.cpp
-
- WmfWriter.cpp
- )
-
-add_library(libwmf SHARED ${libwmf_LIB_SRCS})
-
-target_link_libraries(libwmf Qt5::Gui)
-
-set_target_properties(libwmf PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} )
-install(TARGETS libwmf ${INSTALL_TARGETS_DEFAULT_ARGS})
-
diff --git a/libs/vectorimage/libwmf/DESIGN b/libs/vectorimage/libwmf/DESIGN
deleted file mode 100644
index bde760ebf1..0000000000
--- a/libs/vectorimage/libwmf/DESIGN
+++ /dev/null
@@ -1,58 +0,0 @@
-This directory contains several parsers for the Windows MetaFile
-format (WMF). This is only for historical reasons.
-
-1. The first parser is a simple viewer that uses a QPainter to paint
- the output. This parser consists of wmfstruct.h, qwmf.{h,cc}, and
- metafuncs.h.
-
-2. The second is a more generic advanced parser for WMF that provides
- callbacks for a generic output backend API. This parser consists
- of kwmf.{h,cc} only and seems not to be used anywhere.
-
-These two parsers are included in the kwmf library created by the
-CMakeLists.txt file.
-
-3. The third parser is also a generic one that uses callbacks. This
- API has several implementations, including one using a QPainter
- like the first one, and one that only outputs debuginfo. This
- parser consists of all the Wmf* files.
-
-This parser is included in the libwmf library, also created by
-CMakeLists.txt. It is also the most advanced one, and the one that is
-used in the filters and in the vector shape.
-
-
-Files
------
-
-qwmf.{h,cc} "QWinMetaFile is a WMF viewer based on Qt" according
- to the class doc
-wmfstruct.h Structs that are used in the records.
-metafuncs.h Big table of MetaFuncRec, with {name, number, handler} for
- each record type. Only included in qwmf.cc, and it's
- not really sure why it's in a .h file at all.
-
-kwmf.{h,cc] "a generic parser for WMFs. The output is a series of callbacks."
- Not used anywhere.
-
-
----------------- The actively maintained parser ----------------
-
-WmfEnums.h Most of the enums defined in the [MS-WMF].pdf documentation
-WmfStruct.h A number of structs used in the WMF file.
-WmfStack.{h,cpp}
- "WMF file allows manipulation on a stack of object.
- It's possible to create, delete or select an object."
-WmfParser.{h,cpp}
- Contains a parser for WMF. This parser will call
- functions in the backend to perform actions.
-WmfAbstractBackend.{h,cpp}
- Abstract base class of the callback interface.
- Also contains some implemented methods.
-WmfPainterBackend.{h,cpp}
- Implementation of the WmfAbstractBackend interface using a QPainter.
-WmfWriter.{h,cpp}
-
- Implementation of a writer for WMF's using the same
- interface as the callback interface to the parser.
- This makes it possible to create a new WMF file.
diff --git a/libs/vectorimage/libwmf/WmfAbstractBackend.cpp b/libs/vectorimage/libwmf/WmfAbstractBackend.cpp
deleted file mode 100644
index ca6a77671e..0000000000
--- a/libs/vectorimage/libwmf/WmfAbstractBackend.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/* This file is part of the KDE libraries
- *
- * Copyright (c) 2003 Thierry Lorthiois (lorthioist@wanadoo.fr)
- * 2009-2011 Inge Wallin <inge@lysator.liu.se>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License version 2 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-#include "WmfAbstractBackend.h"
-#include "WmfParser.h"
-
-#include <VectorImageDebug.h>
-
-#include <QFile>
-#include <QString>
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-WmfAbstractBackend::WmfAbstractBackend()
-{
- m_parser = new WmfParser();
-}
-
-WmfAbstractBackend::~WmfAbstractBackend()
-{
- delete m_parser;
-}
-
-
-bool WmfAbstractBackend::load(const QString& filename)
-{
- QFile file(filename);
-
- if (!file.open(QIODevice::ReadOnly)) {
- debugVectorImage << "Cannot open file" << QFile::encodeName(filename);
- return false;
- }
-
- bool ret = m_parser->load(file.readAll());
- file.close();
-
- return ret;
-}
-
-
-bool WmfAbstractBackend::load(const QByteArray& array)
-{
- return m_parser->load(array);
-}
-
-
-bool WmfAbstractBackend::play()
-{
- return m_parser->play(this);
-}
-
-
-bool WmfAbstractBackend::isValid(void) const
-{
- return m_parser->mValid;
-}
-
-
-bool WmfAbstractBackend::isStandard(void) const
-{
- return m_parser->mStandard;
-}
-
-
-bool WmfAbstractBackend::isPlaceable(void) const
-{
- return m_parser->mPlaceable;
-}
-
-
-bool WmfAbstractBackend::isEnhanced(void) const
-{
- return m_parser->mEnhanced;
-}
-
-
-QRect WmfAbstractBackend::boundingRect(void) const
-{
- return QRect(QPoint(m_parser->mBBoxLeft, m_parser->mBBoxTop),
- QSize(m_parser->mBBoxRight - m_parser->mBBoxLeft,
- m_parser->mBBoxBottom - m_parser->mBBoxTop));
-}
-
-
-int WmfAbstractBackend::defaultDpi(void) const
-{
- if (m_parser->mPlaceable) {
- return m_parser->mDpi;
- } else {
- return 0;
- }
-}
-
-
-void WmfAbstractBackend::setDebug(int nbrFunc)
-{
- m_parser->mNbrFunc = nbrFunc;
-}
-
-
-}
diff --git a/libs/vectorimage/libwmf/WmfAbstractBackend.h b/libs/vectorimage/libwmf/WmfAbstractBackend.h
deleted file mode 100644
index 60d551faa8..0000000000
--- a/libs/vectorimage/libwmf/WmfAbstractBackend.h
+++ /dev/null
@@ -1,152 +0,0 @@
-/* This file is part of the KDE libraries
-
- Copyright (c) 2003 thierry lorthiois (lorthioist@wanadoo.fr)
- Copyright (c) 2011 Inge Wallin (inge@lysator.liu.se)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License version 2 as published by the Free Software Foundation.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-#ifndef _WMFABSTRACTBACKEND_H_
-#define _WMFABSTRACTBACKEND_H_
-
-#include "kritavectorimage_export.h"
-
-#include <QRect>
-#include <QRegion>
-#include <QPainter>
-
-class QString;
-class QColor;
-class QImage;
-class QMatrix;
-
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-class WmfParser;
-class WmfDeviceContext;
-
-
-/**
- * WmfAbstractBackend allows the redirection of the actions stored in a WMF file.
- * Most of the virtuals functions are compatible with QPainter format.
- *
- * How to use :
- * inherit this class and define abstract functions
- * then create an object and call @ref load() and @ref play()
- *
- */
-
-class KRITAVECTORIMAGE_EXPORT WmfAbstractBackend
-{
-public:
- WmfAbstractBackend();
- virtual ~WmfAbstractBackend();
-
- /**
- * Load WMF file. Returns true on success.
- */
- virtual bool load(const QString& fileName);
- virtual bool load(const QByteArray& array);
-
- /**
- * play the WMF file => call virtuals functions
- */
- virtual bool play();
-
- /**
- * Returns true if the metafile is standard / placeable / enhanced / valid
- */
- bool isStandard(void) const;
- bool isPlaceable(void) const;
- bool isEnhanced(void) const;
- bool isValid(void) const;
-
- /**
- * Returns the bounding rectangle
- * Standard Meta File : return the bounding box from setWindowOrg and setWindowExt (slow)
- * Placeable Meta File : return the bounding box from header
- * always in logical coordinate
- */
- virtual QRect boundingRect(void) const;
-
- /**
- * Returns the default DotPerInch for placeable meta file,
- * return 0 for Standard meta file
- */
- int defaultDpi(void) const;
-
- /**
- * Activate debug mode.
- * nbFunc : number of functions to draw
- * nbFunc!=0 switch to debug mode with trace
- */
- void setDebug(int nbFunc);
-
- // -------------------------------------------------------------------------
- // A virtual QPainter: inherit those virtuals functions
- // for a good documentation : check QPainter documentation
- virtual bool begin(const QRect &boundingBox) = 0;
- virtual bool end() = 0;
- virtual void save() = 0;
- virtual void restore() = 0;
-
- // Drawing attributes/modes
- virtual void setCompositionMode(QPainter::CompositionMode) = 0;
-
- // Change logical Coordinate
- // some wmf files call those functions several times in the middle of a drawing
- // others doesn't call setWindow* at all
- virtual void setWindowOrg(int left, int top) = 0;
- virtual void setWindowExt(int width, int height) = 0;
- virtual void setViewportOrg(int left, int top) = 0;
- virtual void setViewportExt(int width, int height) = 0;
-
- // Graphics drawing functions
- virtual void setPixel(WmfDeviceContext &context, int x, int y, QColor color) = 0;
- virtual void lineTo(WmfDeviceContext &context, int x, int y) = 0;
- virtual void drawRect(WmfDeviceContext &context, int x, int y, int w, int h) = 0;
- virtual void drawRoundRect(WmfDeviceContext &context, int x, int y, int w, int h, int = 25, int = 25) = 0;
- virtual void drawEllipse(WmfDeviceContext &context, int x, int y, int w, int h) = 0;
- virtual void drawArc(WmfDeviceContext &context, int x, int y, int w, int h, int a, int alen) = 0;
- virtual void drawPie(WmfDeviceContext &context, int x, int y, int w, int h, int a, int alen) = 0;
- virtual void drawChord(WmfDeviceContext &context, int x, int y, int w, int h, int a, int alen) = 0;
- virtual void drawPolyline(WmfDeviceContext &context, const QPolygon &pa) = 0;
- virtual void drawPolygon(WmfDeviceContext &context, const QPolygon &pa) = 0;
- // drawPolyPolygon draw the XOR of a list of polygons
- virtual void drawPolyPolygon(WmfDeviceContext &context, QList<QPolygon>& listPa) = 0;
- virtual void drawImage(WmfDeviceContext &context, int x, int y, const QImage &, int sx = 0, int sy = 0, int sw = -1, int sh = -1) = 0;
- virtual void patBlt(WmfDeviceContext &context, int x, int y, int width, int height,
- quint32 rasterOperation) = 0;
-
- // Text drawing functions
- virtual void drawText(WmfDeviceContext &context, int x, int y, const QString &s) = 0;
-
- // matrix transformation : only used for bitmap manipulation
- virtual void setMatrix(WmfDeviceContext &context, const QMatrix &, bool combine = false) = 0;
-
-protected:
- WmfParser *m_parser;
-};
-
-}
-
-#endif
-
diff --git a/libs/vectorimage/libwmf/WmfDeviceContext.cpp b/libs/vectorimage/libwmf/WmfDeviceContext.cpp
deleted file mode 100644
index 5d35cfe9c3..0000000000
--- a/libs/vectorimage/libwmf/WmfDeviceContext.cpp
+++ /dev/null
@@ -1,188 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-
-#include "WmfDeviceContext.h"
-#include "WmfEnums.h"
-
-#include <QtGlobal>
-
-#include <VectorImageDebug.h>
-
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-WmfDeviceContext::WmfDeviceContext()
-{
- reset();
-}
-
-
-void WmfDeviceContext::reset()
-{
- // Graphics Objects
- brush = QBrush(Qt::NoBrush);
- image = QImage();
- font = QFont();
- escapement = 0;
- orientation = 0;
- height = 0;
- //Palette
- pen = QPen(Qt::black);
- clipRegion = QRegion();
-
- // Structure Objects
- backgroundColor = QColor(Qt::white);
- currentPosition = QPoint(0, 0);
- foregroundTextColor = QColor(Qt::black);
- //Output Surface** (what is this good for? Mixing colors?)
- viewportExt = QSize();
- viewportOrg = QPoint();
- windowExt = QSize();
- windowOrg = QPoint();
-
- // Graphic Properties
- bgMixMode = 0;// FIXME: Check the real default
- //Break extra space
- //Font mapping mode
- rop = 0;// FIXME: Check the real default
- layoutMode = 0;// FIXME: Check the real default
- //Mapping mode
- polyFillMode = Libwmf::ALTERNATE;
- //Stretchblt mode
- textAlign = 0;// FIXME: Check the real default
- //Text extra space
-
- changedItems = 0xffffffff; // Everything changed the first time.
-
- // Derivative values.
- m_windowExtIsSet = false;
- m_viewportExtIsSet = false;
- m_worldTransform.reset();
-}
-
-
-// General note about coordinate spaces and transforms:
-//
-// There are several coordinate spaces in use when drawing an WMF file:
-// 1. The object space, in which the objects' coordinates are expressed inside the WMF.
-// In general there are several of these.
-// 2. The page space, which is where they end up being painted in the WMF picture.
-// The union of these form the bounding box of the WMF.
-// 3. (possibly) the output space, where the WMF picture itself is placed
-// and/or scaled, rotated, etc
-//
-// The transform between spaces 1. and 2. is called the World Transform.
-// The world transform can be changed either through calls to change
-// the window or viewport or through calls to setWorldTransform() or
-// modifyWorldTransform().
-//
-// The transform between spaces 2. and 3. is the transform that the QPainter
-// already contains when it is given to us. We need to save this and reapply
-// it after the world transform has changed. We call this transform the Output
-// Transform in lack of a better word. (Some sources call it the Device Transform.)
-//
-
-
-// FIXME:
-// To change those functions it's better to have
-// a large set of WMF files. WMF special case includes :
-// - without call to setWindowOrg and setWindowExt
-// - change the origin or the scale in the middle of the drawing
-// - negative width or height
-// and relative/absolute coordinate
-
-
-void WmfDeviceContext::recalculateWorldTransform()
-{
- m_worldTransform = QTransform();
-
- if (!m_windowExtIsSet && !m_viewportExtIsSet)
- return;
-
- // FIXME: Check windowExt == 0 in any direction
- qreal windowViewportScaleX;
- qreal windowViewportScaleY;
- if (m_windowExtIsSet && m_viewportExtIsSet) {
- // Both window and viewport are set.
- windowViewportScaleX = qreal(viewportExt.width()) / qreal(windowExt.width());
- windowViewportScaleY = qreal(viewportExt.height()) / qreal(windowExt.height());
-#if 0
- debugVectorImage << "Scale for Window -> Viewport"
- << windowViewportScaleX << windowViewportScaleY;
-#endif
- }
- else {
- // At most one of window and viewport ext is set: Use same width for window and viewport
- windowViewportScaleX = qreal(1.0);
- windowViewportScaleY = qreal(1.0);
-#if 0
- debugVectorImage << "Only one of Window or Viewport set: scale window -> viewport = 1";
-#endif
- }
-
- // Negative window extensions mean flip the picture. Handle this here.
- bool flip = false;
- qreal midpointX = 0.0;
- qreal midpointY = 0.0;
- qreal scaleX = 1.0;
- qreal scaleY = 1.0;
- if (windowExt.width() < 0) {
- midpointX = (windowOrg.x() + windowExt.width()) / qreal(2.0);
- scaleX = -1.0;
- flip = true;
- }
- if (windowExt.height() < 0) {
- midpointY = (windowOrg.y() + windowExt.height()) / qreal(2.0);
- scaleY = -1.0;
- flip = true;
- }
- if (flip) {
- debugVectorImage << "Flipping round midpoint" << midpointX << midpointY << scaleX << scaleY;
- m_worldTransform.translate(midpointX, midpointY);
- m_worldTransform.scale(scaleX, scaleY);
- m_worldTransform.translate(-midpointX, -midpointY);
- //debugVectorImage << "After flipping for window" << m_worldTransform;
- }
-
- // Calculate the world transform.
- m_worldTransform.translate(-windowOrg.x(), -windowOrg.y());
- m_worldTransform.scale(windowViewportScaleX, windowViewportScaleY);
- if (m_viewportExtIsSet) {
- m_worldTransform.translate(viewportOrg.x(), viewportOrg.y());
- }
- else {
- // If viewport is not set, but window is *and* the window
- // width/height is negative, then we must compensate for this.
- // If the width/height is positive, we already did it with the
- // first translate before the scale() above.
- if (windowExt.width() < 0)
- m_worldTransform.translate(windowOrg.x() + windowExt.width(), qreal(0.0));
- if (windowExt.height() < 0)
- m_worldTransform.translate(qreal(0.0), windowOrg.y() + windowExt.height());
- }
- //debugVectorImage << "After window viewport calculation" << m_worldTransform;
-}
-
-
-}
diff --git a/libs/vectorimage/libwmf/WmfDeviceContext.h b/libs/vectorimage/libwmf/WmfDeviceContext.h
deleted file mode 100644
index 4c902505ff..0000000000
--- a/libs/vectorimage/libwmf/WmfDeviceContext.h
+++ /dev/null
@@ -1,145 +0,0 @@
-/* This file is part of the Calligra project
-
- Copyright 2011 Inge Wallin <inge@lysator.liu.se>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-
-#ifndef _WMFDEVICECONTEXT_H_
-#define _WMFDEVICECONTEXT_H_
-
-
-#include <QColor>
-#include <QRect>
-#include <QPen>
-#include <QFont>
-#include <QPolygon>
-#include <QRegion>
-#include <QTransform>
-
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-/**
- * WmfDeviceContext contains the WMF Playback Device Context.
- *
- * See [MS-WMF].pdf section 3.1.5: Playback Device Context for details.
- */
-
-
-enum DeviceContextMembers {
- // Graphic objects
- DCBrush = 0x00000001,
- DCFont = 0x00000002,
- DCPalette = 0x00000004,
- DCPen = 0x00000008,
- DCClipRegion = 0x00000010,
-
- // Structure objects
- DCBgTextColor = 0x00000020,
- DCCurrentPos = 0x00000040,
- DCFgTextColor = 0x00000080,
- // Output surface not supported
- DCViewportExt = 0x00000100,
- DCViewportorg = 0x00000200,
- DCWindowExt = 0x00000400,
- DCWindoworg = 0x00000800,
-
- // Graphic properties
- DCBgMixMode = 0x00001000,
- DCBrExtraSpace = 0x00002000,
- DCFontMapMode = 0x00004000,
- DCFgMixMode = 0x00008000,
- DCLayoutMode = 0x00010000,
- DCMapMode = 0x00020000,
- DCPolyFillMode = 0x00040000,
- DCStretchBltMode = 0x00080000,
- DCTextAlignMode = 0x00100000,
- DCTextExtraSpace = 0x00200000
-};
-
-/**
- WMF Playback Device Context
-
- See [MS-WMF].pdf section 3.1.5
-*/
-class WmfDeviceContext
-{
-public:
- WmfDeviceContext();
- void reset();
-
- // Graphic Objects
- QBrush brush; // Brush
- QImage image; // - extra image
- QFont font; // Font
- int escapement; // - rotation of the text in 1/10th of a degree
- int orientation; // - rotation of characters in 1/10th of a degree
- int height; // - original font height; can be negative
- //Palette // Palette not supported yet
- QPen pen; // Pen
- QRegion clipRegion; // Region
-
- // Structure Objects
- QColor backgroundColor; // Background text color
- QPoint currentPosition; // Drawing position (Current point)
- QColor foregroundTextColor; // Foreground text color
- //Output Surface** (what is this good for? Mixing colors?)
- QSize viewportExt; // Viewport extent
- QPoint viewportOrg; // Viewport origin
- QSize windowExt; // Window extent
- QPoint windowOrg; // Window origin
-
- // Graphic Properties
- quint16 bgMixMode; // Background mix mode
- //Break extra space NYI
- //Font mapping mode NYI
- quint16 rop; // Foreground mix mode
- quint16 layoutMode; // Layout mode
- //Mapping mode NYI
- quint16 polyFillMode; // Polygon fill mode
- //Stretchblt mode NYI
- quint16 textAlign; // Text alignment mode
- //Text extra space NYI
-
- // ----------------------------------------------------------------
- // Helper data
-
- // This is not part of the actual device context, but indicates
- // changed items. It is used by the backends to update their
- // internal state.
- quint32 changedItems; // bitmap of DeviceContextMembers
-
-
- // Cached values
-
- // window and viewport calculation
- bool m_windowExtIsSet;
- bool m_viewportExtIsSet;
- QTransform m_worldTransform;
-
-private:
- void recalculateWorldTransform();
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libwmf/WmfEnums.h b/libs/vectorimage/libwmf/WmfEnums.h
deleted file mode 100644
index 3927ac0991..0000000000
--- a/libs/vectorimage/libwmf/WmfEnums.h
+++ /dev/null
@@ -1,900 +0,0 @@
-/* This file is part of the KDE project
- *
- * Copyright (C) 2010-11 Inge Wallin <inge@lysator.liu.se>
- *
- * 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 WMFENUMS_H
-#define WMFENUMS_H
-
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-// ----------------------------------------------------------------
-// Enums
-
-/**
- WMF 2.1.1.1 RecordType Enumeration
-
- The RecordType Enumeration defines the types of records that can be used in WMF metafiles.
-*/
-
-enum RecordType {
- META_EOF = 0x0000,
- META_SETBKCOLOR = 0x0201,
- META_SETBKMODE = 0x0102,
- META_SETMAPMODE = 0x0103,
- META_SETROP2 = 0x0104,
- META_SETRELABS = 0x0105,
- META_SETPOLYFILLMODE = 0x0106,
- META_SETSTRETCHBLTMODE = 0x0107,
- META_SETTEXTCHAREXTRA = 0x0108,
- META_SETTEXTCOLOR = 0x0209,
- META_SETTEXTJUSTIFICATION = 0x020A,
- META_SETWINDOWORG = 0x020B,
- META_SETWINDOWEXT = 0x020C,
- META_SETVIEWPORTORG = 0x020D,
- META_SETVIEWPORTEXT = 0x020E,
- META_OFFSETWINDOWORG = 0x020F,
- META_SCALEWINDOWEXT = 0x0410,
- META_OFFSETVIEWPORTORG = 0x0211,
- META_SCALEVIEWPORTEXT = 0x0412,
- META_LINETO = 0x0213,
- META_MOVETO = 0x0214,
- META_EXCLUDECLIPRECT = 0x0415,
- META_INTERSECTCLIPRECT = 0x0416,
- META_ARC = 0x0817,
- META_ELLIPSE = 0x0418,
- META_FLOODFILL = 0x0419,
- META_PIE = 0x081A,
- META_RECTANGLE = 0x041B,
- META_ROUNDRECT = 0x061C,
- META_PATBLT = 0x061D,
- META_SAVEDC = 0x001E,
- META_SETPIXEL = 0x041F,
- META_OFFSETCLIPRGN = 0x0220,
- META_TEXTOUT = 0x0521,
- META_BITBLT = 0x0922,
- META_STRETCHBLT = 0x0B23,
- META_POLYGON = 0x0324,
- META_POLYLINE = 0x0325,
- META_ESCAPE = 0x0626,
- META_RESTOREDC = 0x0127,
- META_FILLREGION = 0x0228,
- META_FRAMEREGION = 0x0429,
- META_INVERTREGION = 0x012A,
- META_PAINTREGION = 0x012B,
- META_SELECTCLIPREGION = 0x012C,
- META_SELECTOBJECT = 0x012D,
- META_SETTEXTALIGN = 0x012E,
- // No 2F
- META_CHORD = 0x0830,
- META_SETMAPPERFLAGS = 0x0231,
- META_EXTTEXTOUT = 0x0a32,
- META_SETDIBTODEV = 0x0d33,
- META_SELECTPALETTE = 0x0234,
- META_REALIZEPALETTE = 0x0035,
- META_ANIMATEPALETTE = 0x0436,
- META_SETPALENTRIES = 0x0037,
- META_POLYPOLYGON = 0x0538,
- META_RESIZEPALETTE = 0x0139,
- META_DIBBITBLT = 0x0940,
- META_DIBSTRETCHBLT = 0x0b41,
- META_DIBCREATEPATTERNBRUSH = 0x0142,
- META_STRETCHDIB = 0x0f43,
- // No 44 - 47
- META_EXTFLOODFILL = 0x0548,
- META_SETLAYOUT = 0x0149,
- // Some strange things in the 4a-5f range, that is not documented,
- // but the old code has a few unimplemented functions here.
-
- // BIG GAP HERE
- META_DELETEOBJECT = 0x01f0,
- // No f1-f6
- META_CREATEPALETTE = 0x00f7,
- META_CREATEBRUSH = 0x00f8, // Can't find this in [MS-WMF].pdf, but is unimplemented here.
- META_CREATEPATTERNBRUSH = 0x01F9,
- META_CREATEPENINDIRECT = 0x02FA,
- META_CREATEFONTINDIRECT = 0x02FB,
- META_CREATEBRUSHINDIRECT = 0x02FC,
- META_CREATEREGION = 0x06FF
-};
-
-// MS-WMF 2.1.1.2 Binary RasterOperation Enumeration
-
-enum WmfBinaryRasterOperation {
- R2_BLACK = 0x0001,
- R2_NOTMERGEPEN = 0x0002,
- R2_MASKNOTPEN = 0x0003,
- R2_NOTCOPYPEN = 0x0004,
- R2_MASKPENNOT = 0x0005,
- R2_NOT = 0x0006,
- R2_XORPEN = 0x0007,
- R2_NOTMASKPEN = 0x0008,
- R2_MASKPEN = 0x0009,
- R2_NOTXORPEN = 0x000A,
- R2_NOP = 0x000B,
- R2_MERGENOTPEN = 0x000C,
- R2_COPYPEN = 0x000D,
- R2_MERGEPENNOT = 0x000E,
- R2_MERGEPEN = 0x000F,
- R2_WHITE = 0x0010
-};
-
-
-/**
- WMF 2.1.1.3 BitCount Enumeration
-
- The BitCount Enumeration specifies the number of bits that define
- each pixel and the maximum number of colors in a device-independent
- bitmap (DIB).
-*/
-enum WmfBitCount {
- BI_BITCOUNT_0 = 0x0000,
- BI_BITCOUNT_1 = 0x0001,
- BI_BITCOUNT_2 = 0x0004,
- BI_BITCOUNT_3 = 0x0008,
- BI_BITCOUNT_4 = 0x0010,
- BI_BITCOUNT_5 = 0x0018,
- BI_BITCOUNT_6 = 0x0020
-};
-
-
-/**
- MS-WMF 2.1.1.4 BrushStyle Enumeration
-
- The BrushStyle Enumeration specifies the different possible brush
- types that can be used in graphics operations. For more
- information, see the specification of the Brush Object (section 2.2.1.1).
-*/
-
-enum WmfBrushStyle {
- BS_SOLID = 0x0000,
- BS_NULL = 0x0001,
- BS_HATCHED = 0x0002,
- BS_PATTERN = 0x0003,
- BS_INDEXED = 0x0004,
- BS_DIBPATTERN = 0x0005,
- BS_DIBPATTERNPT = 0x0006,
- BS_PATTERN8X8 = 0x0007,
- BS_DIBPATTERN8X8 = 0x0008,
- BS_MONOPATTERN = 0x0009
-};
-
-
-/**
- MS-WMF 2.1.1.5 CharacterSet Enumeration
-
- The CharacterSet Enumeration defines the possible sets of character
- glyphs that are defined in fonts for graphics output.
-*/
-
-enum WmfCharacterSet {
- ANSI_CHARSET = 0x00000000,
- DEFAULT_CHARSET = 0x00000001,
- SYMBOL_CHARSET = 0x00000002,
- MAC_CHARSET = 0x0000004D,
- SHIFTJIS_CHARSET = 0x00000080,
- HANGUL_CHARSET = 0x00000081,
- JOHAB_CHARSET = 0x00000082,
- GB2312_CHARSET = 0x00000086,
- CHINESEBIG5_CHARSET = 0x00000088,
- GREEK_CHARSET = 0x000000A1,
- TURKISH_CHARSET = 0x000000A2,
- VIETNAMESE_CHARSET = 0x000000A3,
- HEBREW_CHARSET = 0x000000B1,
- ARABIC_CHARSET = 0x000000B2,
- BALTIC_CHARSET = 0x000000BA,
- RUSSIAN_CHARSET = 0x000000CC,
- THAI_CHARSET = 0x000000DE,
- EASTEUROPE_CHARSET = 0x000000EE,
- OEM_CHARSET = 0x000000FF
-};
-
-
-/**
- MS-WMF 2.1.1.6 ColorUsage Enumeration
-
- The ColorUsage Enumeration specifies whether a color table exists
- in a device -independent bitmap (DIB) and how to interpret its values.
-*/
-
-enum WmfColorUsage {
- DIB_RGB_COLORS = 0x0000,
- DIB_PAL_COLORS = 0x0001,
- DIB_PAL_INDICES = 0x0002
-};
-
-
-/**
- MS-WMF 2.1.1.7 Compression Enumeration
-
- The Compression Enumeration specifies the type of compression for a
- bitmap image.
-*/
-
-enum WmfCompression {
- BI_RGB = 0x0000,
- BI_RLE8 = 0x0001,
- BI_RLE4 = 0x0002,
- BI_BITFIELDS = 0x0003,
- BI_JPEG = 0x0004,
- BI_PNG = 0x0005,
- BI_CMYK = 0x000B,
- BI_CMYKRLE8 = 0x000C,
- BI_CMYKRLE4 = 0x000D
-};
-
-
-/**
- MS-WMF 2.1.1.8 FamilyFont Enumeration
-
- The FamilyFont enumeration specifies the font family. Font families
- describe the look of a font in a general way. They are intended for
- specifying fonts when the exact typeface desired is not available.
-*/
-
-typedef enum {
- FF_DONTCARE = 0x00,
- FF_ROMAN = 0x01,
- FF_SWISS = 0x02,
- FF_MODERN = 0x03,
- FF_SCRIPT = 0x04,
- FF_DECORATIVE = 0x05
-} WmfFamilyFont;
-
-
-/**
- MS-WMF 2.1.1.9 FloodFill Enumeration
-
- The FloodFill Enumeration specifies the type of fill operation to
- be performed.
-*/
-
-enum WmfFloodFill {
- FLOODFILLBORDER = 0x0000,
- FLOODFILLSURFACE = 0x0001
-};
-
-
-/**
- MS-WMF 2.1.1.10 FontQuality Enumeration
-
- The FontQuality Enumeration specifies how closely the attributes of
- the logical font should match those of the physical font when
- rendering text.
-*/
-
-enum WmfFontQuality {
- DEFAULT_QUALITY = 0x00,
- DRAFT_QUALITY = 0x01,
- PROOF_QUALITY = 0x02,
- NONANTIALIASED_QUALITY = 0x03,
- ANTIALIASED_QUALITY = 0x04,
- CLEARTYPE_QUALITY = 0x05
-};
-
-
-/*
- MS-WMF 2.1.1.11 GamutMappingIntent Enumeration
-
- The GamutMappingIntent Enumeration specifies the relationship
- between logical and physical colors.
-*/
-
-enum WmfGamutMappingIntent {
- LCS_GM_ABS_COLORIMETRIC = 0x00000008,
- LCS_GM_BUSINESS = 0x00000001,
- LCS_GM_GRAPHICS = 0x00000002,
- LCS_GM_IMAGES = 0x00000004
-};
-
-
-/**
- MS-WMF 2.1.1.12 HatchStyle Enumeration
-
- The HatchStyle Enumeration specifies the hatch pattern.
-*/
-
-enum WmfHatchStyle {
- HS_HORIZONTAL = 0x0000,
- HS_VERTICAL = 0x0001,
- HS_FDIAGONAL = 0x0002,
- HS_BDIAGONAL = 0x0003,
- HS_CROSS = 0x0004,
- HS_DIAGCROSS = 0x0005
-};
-
-
-/**
- MS-WMF 2.1.1.13 Layout Enumeration
-
- The Layout Enumeration defines options for controlling the
- direction in which text and graphics are drawn.
-*/
-
-enum WmfLayout {
- LAYOUT_LTR = 0x0000,
- LAYOUT_RTL = 0x0001,
- LAYOUT_BTT = 0x0002,
- LAYOUT_VBH = 0x0004,
- LAYOUT_BITMAPORIENTATIONPRESERVED = 0x0008
-};
-
-
-/**
- MS-WMF 2.1.1.14 LogicalColorSpace Enumeration
-
- The LogicalColorSpace Enumeration specifies the type of color space.
-*/
-
-enum WmfLogicalColorSpace {
- LCS_CALIBRATED_RGB = 0x00000000,
- LCS_sRGB = 0x73524742,
- LCS_WINDOWS_COLOR_SPACE = 0x57696E20
-};
-
-
-/**
- MS-WMF 2.1.1.15 LogicalColorSpaceV5 Enumeration
-
- The LogicalColorSpaceV5 Enumeration is used to specify where to
- find color profile information for a DeviceIndependentBitmap (DIB)
- Object (section 2.2.2.9) that has a header of type BitmapV5Header
- Object (section 2.2.2.5).
-*/
-
-enum WmfLogicalColorSpaceV5 {
- LCS_PROFILE_LINKED = 0x4C494E4B,
- LCS_PROFILE_EMBEDDED = 0x4D424544
-};
-
-
-/**
- MS-WMF 2.1.1.16 MapMode Enumeration
-
- The MapMode Enumeration defines how logical units are mapped to
- physical units; that is, assuming that the origins in both the
- logical and physical coordinate systems are at the same point on
- the drawing surface, what is the physical coordinate (x',y') that
- corresponds to logical coordinate (x,y).
-*/
-
-enum WmfMapMode {
- MM_TEXT = 0x0001,
- MM_LOMETRIC = 0x0002,
- MM_HIMETRIC = 0x0003,
- MM_LOENGLISH = 0x0004,
- MM_HIENGLISH = 0x0005,
- MM_TWIPS = 0x0006,
- MM_ISOTROPIC = 0x0007,
- MM_ANISOTROPIC = 0x0008
-};
-
-
-/**
- MS-WMF 2.1.1.18 Metafile Type Enumeration
-
- The MetafileType Enumeration specifies where the metafile is stored.
-*/
-
-enum WmfMetafileType {
- MEMORYMETAFILE = 0x0001,
- DISKMETAFILE = 0x0002
-};
-
-
-/**
- MS-WMF 2.1.1.19 MetafileVersion Enumeration
-
- The MetafileVersion Enumeration defines values that specify support
- for device-independent bitmaps (DIBs) in metafiles.
-*/
-
-enum WmfMetafileVersion {
- METAVERSION100 = 0x0100,
- METAVERSION300 = 0x0300
-};
-
-
-/**
- MS-WMF 2.1.1.20 MixMode Enumeration
-
- The MixMode Enumeration specifies the background mix mode for text,
- hatched brushes, and other nonsolid pen styles.
-*/
-
-enum WmfMixMode {
- TRANSPARENT = 0x0001,
- OPAQUE = 0x0002
-};
-
-
-/**
- MS-WMF 2.1.1.21 OutPrecision Enumeration
-
- The OutPrecision enumeration defines values for output precision,
- which is the requirement for the font mapper to match specific font
- parameters, including height, width, character orientation,
- escapement, pitch, and font type.
-*/
-
-enum WmfOutPrecision {
- OUT_DEFAULT_PRECIS = 0x00000000,
- OUT_STRING_PRECIS = 0x00000001,
- OUT_STROKE_PRECIS = 0x00000003,
- OUT_TT_PRECIS = 0x00000004,
- OUT_DEVICE_PRECIS = 0x00000005,
- OUT_RASTER_PRECIS = 0x00000006,
- OUT_TT_ONLY_PRECIS = 0x00000007,
- OUT_OUTLINE_PRECIS = 0x00000008,
- OUT_SCREEN_OUTLINE_PRECIS = 0x00000009,
- OUT_PS_ONLY_PRECIS = 0x0000000A
-};
-
-
-/**
- MS-WMF 2.1.1.22 PaletteEntryFlag Enumeration
-
- The PaletteEntryFlag Enumeration specifies how the palette entry should be used.
-*/
-
-enum WmfPaletteEntryFlag {
- PC_RESERVED = 0x01,
- PC_EXPLICIT = 0x02,
- PC_NOCOLLAPSE = 0x04
-};
-
-
-/**
- MS-WMF 2.1.1.23 Pe nStyle Enumeration
-
- The 16-bit PenStyle Enumeration is used to specify different types
- of pens that can be used in graphics operations.
-
- Various styles can be combined by using a logical OR statement, one
- from each subsection of Style, EndCap, Join, and Type (Cosmetic).
-*/
-
-enum WmfPenStyle {
- PS_COSMETIC = 0x0000,
- PS_ENDCAP_ROUND = 0x0000,
- PS_JOIN_ROUND = 0x0000,
- PS_SOLID = 0x0000,
- PS_DASH = 0x0001,
- PS_DOT = 0x0002,
- PS_DASHDOT = 0x0003,
- PS_DASHDOTDOT = 0x0004,
- PS_NULL = 0x0005,
- PS_INSIDEFRAME = 0x0006,
- PS_USERSTYLE = 0x0007,
- PS_ALTERNATE = 0x0008,
- PS_ENDCAP_SQUARE = 0x0100,
- PS_ENDCAP_FLAT = 0x0200,
- PS_JOIN_BEVEL = 0x1000,
- PS_JOIN_MITER = 0x2000
-};
-
-
-/**
- MS-WMF 2.1.1.24 PitchFont Enumeration
-
- The PitchFont enumeration defines values that are used for
- specifying characteristics of a font. The values are used to
- indicate whether the characters in a font have a fixed or variable
- width, or pitch.
-*/
-
-enum WmfPitchFont {
- DEFAULT_PITCH = 0,
- FIXED_PITCH = 1,
- VARIABLE_PITCH = 2
-};
-
-
-/*
- MS-WMF 2.1.1.25 PolyFillMode Enumeration
-
- The PolyFillMode Enumeration specifies the method used for filling
- a polygon.
-*/
-
-enum WmfPolyFillMode {
- ALTERNATE = 0x0001,
- WINDING = 0x0002
-};
-
-
-/**
- MS-WMF 2.1.1.29 StretchMode Enumeration
-
- The StretchMode Enumeration specifies the bitmap stretching mode,
- which defines how the system combines rows or columns of a bitmap
- with existing pixels.
-*/
-
-enum WmfStretchMode {
- BLACKONWHITE = 0x0001,
- WHITEONBLACK = 0x0002,
- COLORONCOLOR = 0x0003,
- HALFTONE = 0x0004
-};
-
-
-/**
- MS-WMF 2.1.1.30 Ternary RasterOperation Enumeration
-
- The TernaryRasterOperation Enumeration specifies ternary raster
- operation codes, which define how to combine the bits in a source
- bitmap with the bits in a destination bitmap.
-*/
-
-enum WmfTernaryRasterOperation {
- BLACKNESS = 0x00,
- DPSOON = 0x01,
- DPSONA = 0x02,
- PSON = 0x03,
- SDPONA = 0x04,
- DPON = 0x05,
- PDSXNON = 0x06,
- PDSAON = 0x07,
- SDPNAA = 0x08,
- PDSXON = 0x09,
- DPNA = 0x0A,
- PSDNAON = 0x0B,
- SPNA = 0x0C,
- PDSNAON = 0x0D,
- PDSONON = 0x0E,
- PN = 0x0F,
- PDSONA = 0x10,
- NOTSRCERASE = 0x11,
- SDPXNON = 0x12,
- SDPAON = 0x13,
- DPSXNON = 0x14,
- DPSAON = 0x15,
- PSDPSANAXX = 0x16,
- SSPXDSXAXN = 0x17,
- SPXPDXA = 0x18,
- SDPSANAXN = 0x19,
- PDSPAOX = 0x1A,
- SDPSXAXN = 0x1B,
- PSDPAOX = 0x1C,
- DSPDXAXN = 0x1D,
- PDSOX = 0x1E,
- PDSOAN = 0x1F,
- DPSNAA = 0x20,
- SDPXON = 0x21,
- DSNA = 0x22,
- SPDNAON = 0x23,
- SPXDSXA = 0x24,
- PDSPANAXN = 0x25,
- SDPSAOX = 0x26,
- SDPSXNOX = 0x27,
- DPSXA = 0x28,
- PSDPSAOXXN = 0x29,
- DPSANA = 0x2A,
- SSPXPDXAXN = 0x2B,
- SPDSOAX = 0x2C,
- PSDNOX = 0x2D,
- PSDPXOX = 0x2E,
- PSDNOAN = 0x2F,
- PSNA = 0x30,
- SDPNAON = 0x31,
- SDPSOOX = 0x32,
- NOTSRCCOPY = 0x33,
- SPDSAOX = 0x34,
- SPDSXNOX = 0x35,
- SDPOX = 0x36,
- SDPOAN = 0x37,
- PSDPOAX = 0x38,
- SPDNOX = 0x39,
- SPDSXOX = 0x3A,
- SPDNOAN = 0x3B,
- PSX = 0x3C,
- SPDSONOX = 0x3D,
- SPDSNAOX = 0x3E,
- PSAN = 0x3F,
- PSDNAA = 0x40,
- DPSXON = 0x41,
- SDXPDXA = 0x42,
- SPDSANAXN = 0x43,
- SRCERASE = 0x44,
- DPSNAON = 0x45,
- DSPDAOX = 0x46,
- PSDPXAXN = 0x47,
- SDPXA = 0x48,
- PDSPDAOXXN = 0x49,
- DPSDOAX = 0x4A,
- PDSNOX = 0x4B,
- SDPANA = 0x4C,
- SSPXDSXOXN = 0x4D,
- PDSPXOX = 0x4E,
- PDSNOAN = 0x4F,
- PDNA = 0x50,
- DSPNAON = 0x51,
- DPSDAOX = 0x52,
- SPDSXAXN = 0x53,
- DPSONON = 0x54,
- DSTINVERT = 0x55,
- DPSOX = 0x56,
- DPSOAN = 0x57,
- PDSPOAX = 0x58,
- DPSNOX = 0x59,
- PATINVERT = 0x5A,
- DPSDONOX = 0x5B,
- DPSDXOX = 0x5C,
- DPSNOAN = 0x5D,
- DPSDNAOX = 0x5E,
- DPAN = 0x5F,
- PDSXA = 0x60,
- DSPDSAOXXN = 0x61,
- DSPDOAX = 0x62,
- SDPNOX = 0x63,
- SDPSOAX = 0x64,
- DSPNOX = 0x65,
- SRCINVERT = 0x66,
- SDPSONOX = 0x67,
- DSPDSONOXXN = 0x68,
- PDSXXN = 0x69,
- DPSAX = 0x6A,
- PSDPSOAXXN = 0x6B,
- SDPAX = 0x6C,
- PDSPDOAXXN = 0x6D,
- SDPSNOAX = 0x6E,
- PDXNAN = 0x6F,
- PDSANA = 0x70,
- SSDXPDXAXN = 0x71,
- SDPSXOX = 0x72,
- SDPNOAN = 0x73,
- DSPDXOX = 0x74,
- DSPNOAN = 0x75,
- SDPSNAOX = 0x76,
- DSAN = 0x77,
- PDSAX = 0x78,
- DSPDSOAXXN = 0x79,
- DPSDNOAX = 0x7A,
- SDPXNAN = 0x7B,
- SPDSNOAX = 0x7C,
- DPSXNAN = 0x7D,
- SPXDSXO = 0x7E,
- DPSAAN = 0x7F,
- DPSAA = 0x80,
- SPXDSXON = 0x81,
- DPSXNA = 0x82,
- SPDSNOAXN = 0x83,
- SDPXNA = 0x84,
- PDSPNOAXN = 0x85,
- DSPDSOAXX = 0x86,
- PDSAXN = 0x87,
- SRCAND = 0x88,
- SDPSNAOXN = 0x89,
- DSPNOA = 0x8A,
- DSPDXOXN = 0x8B,
- SDPNOA = 0x8C,
- SDPSXOXN = 0x8D,
- SSDXPDXAX = 0x8E,
- PDSANAN = 0x8F,
- PDSXNA = 0x90,
- SDPSNOAXN = 0x91,
- DPSDPOAXX = 0x92,
- SPDAXN = 0x93,
- PSDPSOAXX = 0x94,
- DPSAXN = 0x95,
- DPSXX = 0x96,
- PSDPSONOXX = 0x97,
- SDPSONOXN = 0x98,
- DSXN = 0x99,
- DPSNAX = 0x9A,
- SDPSOAXN = 0x9B,
- SPDNAX = 0x9C,
- DSPDOAXN = 0x9D,
- DSPDSAOXX = 0x9E,
- PDSXAN = 0x9F,
- DPA = 0xA0,
- PDSPNAOXN = 0xA1,
- DPSNOA = 0xA2,
- DPSDXOXN = 0xA3,
- PDSPONOXN = 0xA4,
- PDXN = 0xA5,
- DSPNAX = 0xA6,
- PDSPOAXN = 0xA7,
- DPSOA = 0xA8,
- DPSOXN = 0xA9,
- D = 0xAA,
- DPSONO = 0xAB,
- SPDSXAX = 0xAC,
- DPSDAOXN = 0xAD,
- DSPNAO = 0xAE,
- DPNO = 0xAF,
- PDSNOA = 0xB0,
- PDSPXOXN = 0xB1,
- SSPXDSXOX = 0xB2,
- SDPANAN = 0xB3,
- PSDNAX = 0xB4,
- DPSDOAXN = 0xB5,
- DPSDPAOXX = 0xB6,
- SDPXAN = 0xB7,
- PSDPXAX = 0xB8,
- DSPDAOXN = 0xB9,
- DPSNAO = 0xBA,
- MERGEPAINT = 0xBB,
- SPDSANAX = 0xBC,
- SDXPDXAN = 0xBD,
- DPSXO = 0xBE,
- DPSANO = 0xBF,
- MERGECOPY = 0xC0,
- SPDSNAOXN = 0xC1,
- SPDSONOXN = 0xC2,
- PSXN = 0xC3,
- SPDNOA = 0xC4,
- SPDSXOXN = 0xC5,
- SDPNAX = 0xC6,
- PSDPOAXN = 0xC7,
- SDPOA = 0xC8,
- SPDOXN = 0xC9,
- DPSDXAX = 0xCA,
- SPDSAOXN = 0xCB,
- SRCCOPY = 0xCC,
- SDPONO = 0xCD,
- SDPNAO = 0xCE,
- SPNO = 0xCF,
- PSDNOA = 0xD0,
- PSDPXOXN = 0xD1,
-
- PDSNAX = 0xD2,
- SPDSOAXN = 0xD3,
- SSPXPDXAX = 0xD4,
- DPSANAN = 0xD5,
- PSDPSAOXX = 0xD6,
- DPSXAN = 0xD7,
- PDSPXAX = 0xD8,
- SDPSAOXN = 0xD9,
- DPSDANAX = 0xDA,
- SPXDSXAN = 0xDB,
- SPDNAO = 0xDC,
- SDNO = 0xDD,
- SDPXO = 0xDE,
- SDPANO = 0xDF,
- PDSOA = 0xE0,
- PDSOXN = 0xE1,
- DSPDXAX = 0xE2,
- PSDPAOXN = 0xE3,
- SDPSXAX = 0xE4,
- PDSPAOXN = 0xE5,
- SDPSANAX = 0xE6,
- SPXPDXAN = 0xE7,
- SSPXDSXAX = 0xE8,
- DSPDSANAXXN = 0xE9,
- DPSAO = 0xEA,
- DPSXNO = 0xEB,
- SDPAO = 0xEC,
- SDPXNO = 0xED,
- SRCPAINT = 0xEE,
- SDPNOO = 0xEF,
- PATCOPY = 0xF0,
- PDSONO = 0xF1,
- PDSNAO = 0xF2,
- PSNO = 0xF3,
- PSDNAO = 0xF4,
- PDNO = 0xF5,
- PDSXO = 0xF6,
- PDSANO = 0xF7,
- PDSAO = 0xF8,
- PDSXNO = 0xF9,
- DPO = 0xFA,
- PATPAINT = 0xFB,
- PSO = 0xFC,
- PSDNOO = 0xFD,
- DPSOO = 0xFE,
- WHITENESS = 0xFF
-};
-
-
-// ----------------------------------------------------------------
-// Flags
-
-
-/**
- MS-WMF 2.1.2.1 ClipPrecision Flags
-
- ClipPrecision Flags specify clipping precision, which defines how
- to clip characters that are partially outside a clipping
- region. These flags can be combined to specify multiple options.
-*/
-
-#define CLIP_DEFAULT_PRECIS 0x00000000
-#define CLIP_CHARACTER_PRECIS 0x00000001
-#define CLIP_STROKE_PRECIS 0x00000002
-#define CLIP_LH_ANGLES 0x00000010
-#define CLIP_TT_ALWAYS 0x00000020
-#define CLIP_DFA_DISABLE 0x00000040
-#define CLIP_EMBEDDED 0x00000080
-
-
-/**
- MS-WMF 2.1.2.2 ExtTextOutOptions Flags
-
- ExtTextOutOptions Flags specify various characteristics of the
- output of text. These flags can be combined to specify multiple options.
-*/
-
-#define ETO_OPAQUE 0x0002
-#define ETO_CLIPPED 0x0004
-#define ETO_GLYPH_INDEX 0x0010
-#define ETO_RTLREADING 0x0080
-#define ETO_NUMERICSLOCAL 0x0400
-#define ETO_NUMERICSLATIN 0x0800
-#define ETO_PDY 0x2000
-
-
-/**
- MS-WMF 2.1.2.3 TextAlignmentMode Flags
-
- TextAlignmentMode Flags specify the relationship between a
- reference point and a bounding rectangle, for text alignment. These
- flags can be combined to specify multiple options, with the
- restriction that only one flag can be chosen that alters the
- drawing position in the playback device context.
-
- Horizontal text alignment is performed when the font has a
- horizontal default baseline.
-*/
-
-#define TA_NOUPDATECP 0x0000 /// Do not update Current Point (default)
-#define TA_LEFT 0x0000 /// The reference point is on the left edge of the bounding rectangle
-#define TA_TOP 0x0000 /// The reference point is on the top edge of the bounding rectangle
-#define TA_UPDATECP 0x0001 /// Use Current Point. The Current Point must be updated
-#define TA_RIGHT 0x0002 /// The reference point is on the right edge of the bounding rectangle
-#define TA_CENTER 0x0006 /// The reference point is at the center of the bounding rectangle
-#define TA_BOTTOM 0x0008 /// The reference point is on the bottom edge of the bounding rectangle
-#define TA_BASELINE 0x0018 /// The reference point is on the baseline
-#define TA_RTLREADING 0x0100 /// The text is laid out in Right-to-Left direction.
-
-// Some useful masks, not part of the specification:
-#define TA_HORZMASK 0x0006
-#define TA_VERTMASK 0x0018
-
-
-
-// MS-WMF 2.1.2.4 VerticalTextAlignmentMode Flags
-//
-// VerticalTextAlignmentMode Flags specify the relationship between a
-// reference point and a bounding rectangle, for text alignment. These
-// flags can be combined to specify multiple options, with the
-// restriction that only one flag can be chosen that alters the
-// drawing position in the playback device context.
-//
-// Vertical text alignment is performed when the font has a vertical
-// default baseline, such as Kanji.
-
-#define VTA_TOP 0x0000
-#define VTA_RIGHT 0x0000
-#define VTA_BOTTOM 0x0002
-#define VTA_CENTER 0x0006
-#define VTA_LEFT 0x0008
-#define VTA_BASELINE 0x0018
-
-
-}
-
-#endif // KOWMFENUMS_H
diff --git a/libs/vectorimage/libwmf/WmfPainterBackend.cpp b/libs/vectorimage/libwmf/WmfPainterBackend.cpp
deleted file mode 100644
index 6e95058aa4..0000000000
--- a/libs/vectorimage/libwmf/WmfPainterBackend.cpp
+++ /dev/null
@@ -1,836 +0,0 @@
-/* This file is part of the KDE libraries
- *
- * Copyright (c) 2003 thierry lorthiois (lorthioist@wanadoo.fr)
- * 2009-2011 Inge Wallin <inge@lysator.liu.se>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License version 2 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-#include "WmfPainterBackend.h"
-
-#include <QPolygon>
-#include <QPrinter>
-#include <QFontMetrics>
-
-#include <VectorImageDebug.h>
-
-#include "WmfEnums.h"
-#include "WmfParser.h"
-
-
-#define DEBUG_WMFPAINT 0
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-WmfPainterBackend::WmfPainterBackend(QPainter *painter, const QSizeF &outputSize)
- : WmfAbstractBackend()
- , mPainter(painter)
- , mOutputSize(outputSize)
- , mSaveCount(0)
-{
- mTarget = painter->device();
- mIsInternalPainter = false;
- mWorldTransform = QTransform();
-}
-
-WmfPainterBackend::~WmfPainterBackend()
-{
-}
-
-#if 0
-bool WmfPainterBackend::play(QPaintDevice& target)
-{
- if (!mPainter)
- mPainter = new QPainter(&target);
- mIsInternalPainter = true;
-
- if (mPainter->isActive()) return false;
- mTarget = &target;
-
- // Play the wmf file
- mSaveCount = 0;
- bool ret = WmfAbstractBackend::play();
-
- // Make sure that the painter is in the same state as before WmfParser::play()
- for (; mSaveCount > 0; mSaveCount--)
- restore();
- return ret;
-}
-#endif
-
-bool WmfPainterBackend::play()
-{
- // Play the wmf file
- return m_parser->play(this);
-}
-
-
-//-----------------------------------------------------------------------------
-// Virtual Painter
-
-
-bool WmfPainterBackend::begin(const QRect &boundingBox)
-{
- // If the painter is our own, we have to call begin() on it.
- // If it's external, we assume that it's already done for us.
- if (mIsInternalPainter) {
- if (!mPainter->begin(mTarget))
- return false;
- }
-
- // For calculations of window / viewport during the painting
- mWindowOrg = QPoint(0, 0);
- mViewportOrg = QPoint(0, 0);
- mWindowExtIsSet = false;
- mViewportExtIsSet = false;
- mOutputTransform = mPainter->transform();
- mWorldTransform = QTransform();
-
- //mPainter->setBrush(QBrush(Qt::NoBrush));
-
-#if DEBUG_WMFPAINT
- debugVectorImage << "Using QPainter: " << mPainter->pen() << mPainter->brush()
- << "Background: " << mPainter->background() << " " << mPainter->backgroundMode();
-#endif
-
- qreal scaleX = mOutputSize.width() / boundingBox.width();
- qreal scaleY = mOutputSize.height() / boundingBox.height();
-
- // Transform the WMF object so that it fits in the shape as much
- // as possible. The topleft will be the top left of the shape.
- mPainter->scale( scaleX, scaleY );
-
- mOutputTransform = mPainter->transform();
-
- mPainter->setRenderHint(QPainter::Antialiasing);
- mPainter->setRenderHint(QPainter::TextAntialiasing);
-
- mSaveCount = 0;
-
- return true;
-}
-
-
-bool WmfPainterBackend::end()
-{
- // Make sure that the painter is in the same state as before calling play above().
- for (; mSaveCount > 0; mSaveCount--)
- restore();
-
- bool ret = true;
- if (mIsInternalPainter)
- ret = mPainter->end();
-
- return ret;
-}
-
-
-void WmfPainterBackend::save()
-{
- // A little trick here: Save the worldTransform in the painter.
- // If we didn't do this, we would have to create a separate stack
- // for these.
- //
- // FIXME: We should collect all the parts of the DC that are not
- // stored in the painter and save them separately.
- QTransform savedTransform = mPainter->worldTransform();
- mPainter->setWorldTransform(mWorldTransform);
-
- mPainter->save();
- ++mSaveCount;
-
- mPainter->setWorldTransform(savedTransform);
-}
-
-
-void WmfPainterBackend::restore()
-{
- if (mSaveCount > 0) {
- mPainter->restore();
- mSaveCount--;
- }
- else {
- debugVectorImage << "restore(): try to restore painter without save";
- }
-
- // We used a trick in save() and stored the worldTransform in
- // the painter. Now restore the full transformation.
- mWorldTransform = mPainter->worldTransform();
- recalculateWorldTransform();
-}
-
-
-void WmfPainterBackend::setCompositionMode(QPainter::CompositionMode mode)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << mode << "(ignored)";
-#endif
-
- // FIXME: This doesn't work. I don't understand why, but when I
- // enable this all backgrounds become black. /iw
- Q_UNUSED(mode);
- //mPainter->setCompositionMode(mode);
-}
-
-
-// ---------------------------------------------------------------------
-// World Transform, Window and Viewport
-
-
-// General note about coordinate spaces and transforms:
-//
-// There are several coordinate spaces in use when drawing an WMF file:
-// 1. The object space, in which the objects' coordinates are expressed inside the WMF.
-// In general there are several of these.
-// 2. The page space, which is where they end up being painted in the WMF picture.
-// The union of these form the bounding box of the WMF.
-// 3. (possibly) the output space, where the WMF picture itself is placed
-// and/or scaled, rotated, etc
-//
-// The transform between spaces 1. and 2. is called the World Transform.
-// The world transform can be changed either through calls to change
-// the window or viewport or through calls to setWorldTransform() or
-// modifyWorldTransform().
-//
-// The transform between spaces 2. and 3. is the transform that the QPainter
-// already contains when it is given to us. We need to save this and reapply
-// it after the world transform has changed. We call this transform the Output
-// Transform in lack of a better word. (Some sources call it the Device Transform.)
-//
-
-
-// FIXME:
-// To change those functions it's better to have
-// a large set of WMF files. WMF special case includes :
-// - without call to setWindowOrg and setWindowExt
-// - change the origin or the scale in the middle of the drawing
-// - negative width or height
-// and relative/absolute coordinate
-
-
-void WmfPainterBackend::recalculateWorldTransform()
-{
- mWorldTransform = QTransform();
-
- if (!mWindowExtIsSet && !mViewportExtIsSet)
- return;
-
- // FIXME: Check windowExt == 0 in any direction
- qreal windowViewportScaleX;
- qreal windowViewportScaleY;
- if (mWindowExtIsSet && mViewportExtIsSet) {
- // Both window and viewport are set.
- windowViewportScaleX = qreal(mViewportExt.width()) / qreal(mWindowExt.width());
- windowViewportScaleY = qreal(mViewportExt.height()) / qreal(mWindowExt.height());
-#if DEBUG_WMFPAINT
- debugVectorImage << "Scale for Window -> Viewport"
- << windowViewportScaleX << windowViewportScaleY;
-#endif
- }
- else {
- // At most one of window and viewport ext is set: Use same width for window and viewport
- windowViewportScaleX = qreal(1.0);
- windowViewportScaleY = qreal(1.0);
-#if DEBUG_WMFPAINT
- debugVectorImage << "Only one of Window or Viewport set: scale window -> viewport = 1";
-#endif
- }
-
- // Negative window extensions mean flip the picture. Handle this here.
- bool flip = false;
- qreal midpointX = 0.0;
- qreal midpointY = 0.0;
- qreal scaleX = 1.0;
- qreal scaleY = 1.0;
- if (mWindowExt.width() < 0) {
- midpointX = (mWindowOrg.x() + mWindowExt.width()) / qreal(2.0);
- scaleX = -1.0;
- flip = true;
- }
- if (mWindowExt.height() < 0) {
- midpointY = (mWindowOrg.y() + mWindowExt.height()) / qreal(2.0);
- scaleY = -1.0;
- flip = true;
- }
- if (flip) {
- //debugVectorImage << "Flipping round midpoint" << midpointX << midpointY << scaleX << scaleY;
- mWorldTransform.translate(midpointX, midpointY);
- mWorldTransform.scale(scaleX, scaleY);
- mWorldTransform.translate(-midpointX, -midpointY);
- //debugVectorImage << "After flipping for window" << mWorldTransform;
- }
-
- // Calculate the world transform.
- mWorldTransform.translate(-mWindowOrg.x(), -mWindowOrg.y());
- mWorldTransform.scale(windowViewportScaleX, windowViewportScaleY);
- if (mViewportExtIsSet) {
- mWorldTransform.translate(mViewportOrg.x(), mViewportOrg.y());
- }
- else {
- // If viewport is not set, but window is *and* the window
- // width/height is negative, then we must compensate for this.
- // If the width/height is positive, we already did it with the
- // first translate before the scale() above.
- if (mWindowExt.width() < 0)
- mWorldTransform.translate(mWindowOrg.x() + mWindowExt.width(), qreal(0.0));
- if (mWindowExt.height() < 0)
- mWorldTransform.translate(qreal(0.0), mWindowOrg.y() + mWindowExt.height());
- }
- //debugVectorImage << "After window viewport calculation" << mWorldTransform;
-
- // FIXME: also handle negative viewport extensions? If so, do it here.
-
- // Apply the world transform to the painter.
- mPainter->setWorldTransform(mWorldTransform);
-
- // Apply the output transform.
- QTransform currentMatrix = mPainter->worldTransform();
- QTransform newMatrix = currentMatrix * mOutputTransform;
- //debugVectorImage << "Output transform" << mOutputTransform;
- //debugVectorImage << "Total transform" << newMatrix;
- mPainter->setWorldTransform( newMatrix );
-}
-
-
-
-
-void WmfPainterBackend::setWindowOrg(int left, int top)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << left << " " << top;
-#endif
-
- // Only set WindowOrg if it changes. See the Unanswered Question in libemf.
- if (mWindowOrg == QPoint(left, top))
- return;
-
- mWindowOrg = QPoint(left, top);
-
- recalculateWorldTransform();
-
-#if 0
- // Debug code. Draw a rectangle with some decoration to show see
- // if all the transformations work.
- mPainter->save();
-
- // Paint a black rectangle around the current window.
- mPainter->setPen(Qt::green);
- QRect windowRect = QRect(mWindowOrg, mWindowExt);
- mPainter->drawRect(windowRect);
-
-#if 0
- // Paint a black line from the Window origin to (0, 0)
- mPainter->drawLine(mWindowOrg, QPoint(0, 0));
-
- mPainter->setPen(Qt::red);
- mPainter->drawRect(boundingRect());
-
- mPainter->drawLine(boundingRect().topLeft(), QPoint(0, 0));
-#endif
- mPainter->restore();
-
- debugVectorImage << "Window rect: " << windowRect;
- debugVectorImage << "Bounding rect: " << boundingRect();
-#endif
-}
-
-
-void WmfPainterBackend::setWindowExt(int width, int height)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << width << " " << height;
-#endif
-
- // Only set WindowExt if it changes. See the Unanswered Question in libemf.
- if (mWindowExt == QSize(width, height))
- return;
-
- mWindowExt = QSize(width, height);
- mWindowExtIsSet = true;
-
- recalculateWorldTransform();
-
-#if 0
- // Debug code. Draw a rectangle with some decoration to show see
- // if all the transformations work.
- mPainter->save();
-
- // Paint a red rectangle around the current window.
- mPainter->setPen(Qt::red);
- QRect windowRect = QRect(mWindowOrg, mWindowExt);
- mPainter->drawRect(windowRect);
-
- // Paint a line from the Window origin to (0, 0)
- mPainter->drawLine(mWindowOrg, QPoint(0, 0));
-
- mPainter->setPen(Qt::black);
- mPainter->drawRect(boundingRect());
-
- mPainter->drawLine(boundingRect().topLeft(), QPoint(0, 0));
- mPainter->restore();
-
- debugVectorImage << "Window rect: " << windowRect;
- debugVectorImage << "Bounding rect: " << boundingRect();
-#endif
-}
-
-void WmfPainterBackend::setViewportOrg( int left, int top )
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << left << top;
-#endif
-
- // Only set ViewportOrg if it changes. See the Unanswered Question in libemf.
- if (mViewportOrg == QPoint(left, top))
- return;
-
- mViewportOrg = QPoint(left, top);
-
- recalculateWorldTransform();
-}
-
-void WmfPainterBackend::setViewportExt( int width, int height )
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << width << height;
-#endif
-
- // Only set ViewportOrg if it changes. See the Unanswered Question in libemf.
- if (mViewportExt == QSize(width, height))
- return;
-
- mViewportExt = QSize(width, height);
- mViewportExtIsSet = true;
-
- recalculateWorldTransform();
-}
-
-
-void WmfPainterBackend::setMatrix(WmfDeviceContext &context, const QMatrix &wm, bool combine)
-{
- Q_UNUSED(context);
-#if DEBUG_WMFPAINT
- debugVectorImage << wm << " " << combine;
-#endif
- mPainter->setMatrix(wm, combine);
-
- recalculateWorldTransform();
-}
-
-
-// ----------------------------------------------------------------
-// Drawing
-
-
-void WmfPainterBackend::setPixel(WmfDeviceContext &context, int x, int y, QColor color)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << y << color;
-#endif
-
- updateFromDeviceContext(context);
-
- QPen oldPen = mPainter->pen();
- QPen pen = oldPen;
- pen.setColor(color);
- mPainter->setPen(pen);
- mPainter->drawLine(x, y, x, y);
- mPainter->setPen(oldPen);
-}
-
-
-void WmfPainterBackend::lineTo(WmfDeviceContext &context, int x, int y)
-{
- updateFromDeviceContext(context);
-
-#if DEBUG_WMFPAINT
- debugVectorImage << x << ", " << y << " using " << mPainter->pen();
-#endif
-
- QPoint newPoint(x, y);
- mPainter->drawLine(context.currentPosition, newPoint);
- context.currentPosition = newPoint;
-}
-
-
-void WmfPainterBackend::drawRect(WmfDeviceContext &context, int x, int y, int w, int h)
-{
- updateFromDeviceContext(context);
-
-#if DEBUG_WMFPAINT
- debugVectorImage << x << ", " << y << ", " << w << ", " << h;
- debugVectorImage << "Using QPainter: " << mPainter->pen() << mPainter->brush();
-#endif
-
- mPainter->drawRect(x, y, w, h);
-}
-
-
-void WmfPainterBackend::drawRoundRect(WmfDeviceContext &context, int x, int y, int w, int h,
- int roudw, int roudh)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << ", " << y << ", " << w << ", " << h;
-#endif
-
- updateFromDeviceContext(context);
- mPainter->drawRoundRect(x, y, w, h, roudw, roudh);
-}
-
-
-void WmfPainterBackend::drawEllipse(WmfDeviceContext &context, int x, int y, int w, int h)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << ", " << y << ", " << w << ", " << h;
-#endif
- updateFromDeviceContext(context);
- mPainter->drawEllipse(x, y, w, h);
-}
-
-
-void WmfPainterBackend::drawArc(WmfDeviceContext &context, int x, int y, int w, int h,
- int a, int alen)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << ", " << y << ", " << w << ", " << h;
-#endif
- updateFromDeviceContext(context);
- mPainter->drawArc(x, y, w, h, a, alen);
-}
-
-
-void WmfPainterBackend::drawPie(WmfDeviceContext &context, int x, int y, int w, int h,
- int a, int alen)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << ", " << y << ", " << w << ", " << h;
-#endif
- updateFromDeviceContext(context);
- mPainter->drawPie(x, y, w, h, a, alen);
-}
-
-
-void WmfPainterBackend::drawChord(WmfDeviceContext &context, int x, int y, int w, int h,
- int a, int alen)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << ", " << y << ", " << w << ", " << h
- << ", " << a << ", " << alen;
-#endif
- updateFromDeviceContext(context);
- mPainter->drawChord(x, y, w, h, a, alen);
-}
-
-
-void WmfPainterBackend::drawPolyline(WmfDeviceContext &context, const QPolygon &pa)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << pa;
-#endif
- updateFromDeviceContext(context);
- mPainter->drawPolyline(pa);
-}
-
-
-void WmfPainterBackend::drawPolygon(WmfDeviceContext &context, const QPolygon &pa)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << pa;
- debugVectorImage << "Using QPainter: " << mPainter->pen() << mPainter->brush();
-#endif
-
- updateFromDeviceContext(context);
- if (context.polyFillMode)
- mPainter->drawPolygon(pa, Qt::WindingFill);
- else
- mPainter->drawPolygon(pa, Qt::OddEvenFill);
-}
-
-
-void WmfPainterBackend::drawPolyPolygon(WmfDeviceContext &context, QList<QPolygon>& listPa)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage;
-#endif
-
- updateFromDeviceContext(context);
-
- mPainter->save();
- QBrush brush = mPainter->brush();
-
- // define clipping region
- QRegion region;
- Q_FOREACH (const QPolygon & pa, listPa) {
- region = region.xored(pa);
- }
- mPainter->setClipRegion(region);
-
- // fill polygons
- if (brush != Qt::NoBrush) {
- //debugVectorImage << "Filling polygon with " << brush;
- mPainter->fillRect(region.boundingRect(), brush);
- }
-
- // draw polygon's border
- mPainter->setClipping(false);
- if (mPainter->pen().style() != Qt::NoPen) {
- mPainter->setBrush(Qt::NoBrush);
- Q_FOREACH (const QPolygon & pa, listPa) {
-#if DEBUG_WMFPAINT
- debugVectorImage << pa;
-#endif
- if (context.polyFillMode == WINDING)
- mPainter->drawPolygon(pa, Qt::WindingFill);
- else
- mPainter->drawPolygon(pa, Qt::OddEvenFill);
- }
- }
-
- // restore previous state
- mPainter->restore();
-}
-
-
-void WmfPainterBackend::drawImage(WmfDeviceContext &context, int x, int y, const QImage &img,
- int sx, int sy, int sw, int sh)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << " " << y << " " << sx << " " << sy << " " << sw << " " << sh;
-#endif
- updateFromDeviceContext(context);
- mPainter->drawImage(x, y, img, sx, sy, sw, sh);
-}
-
-
-void WmfPainterBackend::patBlt(WmfDeviceContext &context, int x, int y, int width, int height,
- quint32 rasterOperation)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << y << width << height << hex << rasterOperation << dec;
-#endif
-
- updateFromDeviceContext(context);
-
- // 0x00f00021 is the PatCopy raster operation which just fills a rectangle with a brush.
- // This seems to be the most common one.
- //
- // FIXME: Implement the rest of the raster operations.
- if (rasterOperation == 0x00f00021) {
- // Would have been nice if we didn't have to pull out the
- // brush to use it with fillRect()...
- QBrush brush = mPainter->brush();
- mPainter->fillRect(x, y, width, height, brush);
- }
-}
-
-
-void WmfPainterBackend::drawText(WmfDeviceContext &context, int x, int y, const QString& text)
-{
-#if DEBUG_WMFPAINT
- debugVectorImage << x << y << hex << dec << text;
-#endif
-
- updateFromDeviceContext(context);
-
- // The TA_UPDATECP flag tells us to use the current position
- if (context.textAlign & TA_UPDATECP) {
- // (left, top) position = current logical position
- x = context.currentPosition.x();
- y = context.currentPosition.y();
-#if DEBUG_WMFPAINT
- debugVectorImage << "Using current position:" << x << y;
-#endif
- }
-
- QFontMetrics fm(mPainter->font(), mTarget);
- int width = fm.width(text) + fm.descent(); // fm.width(text) isn't right with Italic text
- int height = fm.height();
-
- // Horizontal align. These flags are supposed to be mutually exclusive.
- if ((context.textAlign & TA_CENTER) == TA_CENTER)
- x -= (width / 2);
- else if ((context.textAlign & TA_RIGHT) == TA_RIGHT)
- x -= width;
-
- // Vertical align.
- if ((context.textAlign & TA_BASELINE) == TA_BASELINE)
- y -= fm.ascent(); // (height - fm.descent()) is used in qwmf. This should be the same.
- else if ((context.textAlign & TA_BOTTOM) == TA_BOTTOM) {
- y -= height;
- }
-
-#if DEBUG_WMFPAINT
- debugVectorImage << "font = " << mPainter->font() << " pointSize = " << mPainter->font().pointSize()
- << "ascent = " << fm.ascent() << " height = " << fm.height()
- << "leading = " << fm.leading();
-#endif
-
- // Use the special pen defined by the foregroundTextColor in the device context for text.
- mPainter->save();
- mPainter->setPen(context.foregroundTextColor);
-
- // If the actual height is < 0, we should use device units. This
- // means that if the text is currently upside-down due to some
- // transformations, we should un-upside-down it before painting.
- //debugVectorImage << "fontheight:" << context.height << "height:" << height << "y" << y;
- if (context.height < 0 && mPainter->worldTransform().m22() < 0) {
- mPainter->translate(0, -(y - height / 2));
- mPainter->scale(qreal(1.0), qreal(-1.0));
- mPainter->translate(0, +(y - height / 2));
-
- // This is necessary to get drawText(x, y, ...) right below.
- y = -3 * y;
- }
-
- mPainter->translate(x, y);
- if (context.escapement != 0) {
- mPainter->rotate(qreal(context.escapement) / qreal(-10.0));
- }
- mPainter->drawText(0, 0, width, height, Qt::AlignLeft|Qt::AlignTop, text);
-
- mPainter->restore();
-}
-
-
-// ----------------------------------------------------------------
-// Private functions
-
-
-// If anything has changed in the device context that is relevant to
-// the QPainter, then update the painter with the corresponding data.
-//
-void WmfPainterBackend::updateFromDeviceContext(WmfDeviceContext &context)
-{
- // Graphic objects
- if (context.changedItems & DCBrush) {
- mPainter->setBrush(context.brush);
-#if DEBUG_WMFPAINT
- debugVectorImage << "*** Setting fill brush to" << context.brush;
-#endif
- }
- // FIXME: context.image
- if (context.changedItems & DCFont) {
- mPainter->setFont(context.font);
-#if DEBUG_WMFPAINT
- debugVectorImage << "*** Setting font to" << context.font;
-#endif
- }
- if (context.changedItems & DCPalette) {
- // NYI
-#if DEBUG_WMFPAINT
- debugVectorImage << "*** Setting palette (NYI)";
-#endif
- }
- if (context.changedItems & DCPen) {
- QPen p = context.pen;
- int width = p.width();
-
- if (dynamic_cast<QPrinter *>(mTarget)) {
- width = 0;
- }
- else if (width == 1)
- // I'm unsure of this, but it seems that WMF uses line
- // width == 1 as cosmetic pen. Or it could just be that
- // any line width < 1 should be drawn as width == 1. The
- // WMF spec doesn't mention the term "cosmetic pen"
- // anywhere so we don't get any clue there.
- //
- // For an example where this is shown clearly, see
- // wmf_tests.doc, in the colored rectangles and the polypolygon.
- width = 0;
-#if 0
- else {
- // WMF spec: width of pen in logical coordinate
- // => width of pen proportional with device context width
- QRect rec = mPainter->window();
- // QPainter documentation says this is equivalent of xFormDev, but it doesn't compile. Bug reported.
-
- QRect devRec = rec * mPainter->matrix();
- if (rec.width() != 0)
- width = (width * devRec.width()) / rec.width() ;
- else
- width = 0;
- }
-#endif
-
- p.setWidth(width);
- mPainter->setPen(p);
-#if DEBUG_WMFPAINT
- debugVectorImage << "*** Setting pen to" << p;
-#endif
- }
- if (context.changedItems & DCClipRegion) {
- // Not used until SETCLIPREGION is used
-#if DEBUG_WMFPAINT
- //debugVectorImage << "*** region changed to" << context.region;
-#endif
- }
-
- // Structure objects
- if (context.changedItems & DCBgTextColor) {
- mPainter->setBackground(QBrush(context.backgroundColor));
-#if DEBUG_WMFPAINT
- debugVectorImage << "*** Setting background text color to" << context.backgroundColor;
-#endif
- }
- //----------------------------------------------------------------
- // Output surface not supported
- //DCViewportExt
- //DCViewportorg
- //DCWindowExt
- //DCWindoworg
-
- //----------------------------------------------------------------
- // Graphic Properties
-
- if (context.changedItems & DCBgMixMode) {
- // FIXME: Check the default value for this.
- mPainter->setBackgroundMode(context.bgMixMode == TRANSPARENT ? Qt::TransparentMode
- : Qt::OpaqueMode);
-#if DEBUG_WMFPAINT
- debugVectorImage << "*** Setting background mode to" << context.bgMixMode;
-#endif
- }
- //Break extra space NYI
- //Font mapping mode NYI
- if (context.changedItems & DCFgMixMode) {
- // FIXME: Check the default value for this.
- QPainter::CompositionMode compMode = QPainter::CompositionMode_Source;
- if (context.rop < 17)
- compMode = koWmfOpTab16[context.rop];
- mPainter->setCompositionMode(compMode);
-
-#if DEBUG_WMFPAINT
- debugVectorImage << "*** Setting composition mode to" << context.rop;
-#endif
- }
- //layoutMode not necessary to handle here
- //Mapping mode NYI
- //PolyFillMode not necessary to handle here
- //Stretchblt mode NYI
- //textAlign not necessary to handle here
- //Text extra space NYI
-
- // Reset all changes until next time.
- context.changedItems = 0;
-}
-
-
-}
diff --git a/libs/vectorimage/libwmf/WmfPainterBackend.h b/libs/vectorimage/libwmf/WmfPainterBackend.h
deleted file mode 100644
index 8f23769a38..0000000000
--- a/libs/vectorimage/libwmf/WmfPainterBackend.h
+++ /dev/null
@@ -1,156 +0,0 @@
-/* This file is part of the KDE libraries
- *
- * Copyright (c) 2003 thierry lorthiois (lorthioist@wanadoo.fr)
- * 2009-2011 Inge Wallin <inge@lysator.liu.se>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License version 2 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-#ifndef _WMFPAINTERBACKEND_H_
-#define _WMFPAINTERBACKEND_H_
-
-#include "kritavectorimage_export.h"
-#include "WmfAbstractBackend.h"
-
-#include <QPainter>
-#include <QTransform>
-
-class QPolygon;
-
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-class WmfDeviceContext;
-
-/**
- * WmfPainterBackend inherits the abstract class WmfAbstractbackend
- * and redirects WMF actions onto a QPaintDevice.
- * Uses relative or absolute coordinate.
- *
- * how to use:
- * <pre>
- * QPixmap pix(100, 100);
- * QPainter painter(pix);
- * WmfPainterBackend wmf(painter, pix.size());
- * if (wmf.load("/home/test.wmf" )) {
- * wmf.play(pix);
- * }
- * paint.drawPixmap(0, 0, pix);
- * </pre>
- *
- */
-
-class KRITAVECTORIMAGE_EXPORT WmfPainterBackend : public WmfAbstractBackend
-{
-public:
- WmfPainterBackend(QPainter *painter, const QSizeF &outputSize);
- ~WmfPainterBackend() override;
-
- using WmfAbstractBackend::play;
-
- /**
- * Play a WMF file on a QPaintDevice. Return true on success.
- */
- //bool play(QPaintDevice& target);
- //bool play(QPainter &painter);
- bool play() override;
-
-
-private:
- // -------------------------------------------------------------------------
- // A virtual QPainter
- bool begin(const QRect &boundingBox) override;
- bool end() override;
- void save() override;
- void restore() override;
-
- /// Recalculate the world transform and then apply it to the painter
- /// This must be called at the end of every function that changes the transform.
- void recalculateWorldTransform();
-
- // Drawing attributes/modes
- void setCompositionMode(QPainter::CompositionMode mode) override;
-
- /**
- * Change logical Coordinate
- * some wmf files call those functions several times in the middle of a drawing
- * others wmf files doesn't call setWindow* at all
- * negative width and height are possible
- */
- void setWindowOrg(int left, int top) override;
- void setWindowExt(int width, int height) override;
- void setViewportOrg(int left, int top) override;
- void setViewportExt(int width, int height) override;
-
- // Graphics drawing functions
- void setPixel(WmfDeviceContext &context, int x, int y, QColor color) override;
- void lineTo(WmfDeviceContext &context, int x, int y) override;
- void drawRect(WmfDeviceContext &context, int x, int y, int w, int h) override;
- void drawRoundRect(WmfDeviceContext &context, int x, int y, int w, int h, int = 25, int = 25) override;
- void drawEllipse(WmfDeviceContext &context, int x, int y, int w, int h) override;
- void drawArc(WmfDeviceContext &context, int x, int y, int w, int h, int a, int alen) override;
- void drawPie(WmfDeviceContext &context, int x, int y, int w, int h, int a, int alen) override;
- void drawChord(WmfDeviceContext &context, int x, int y, int w, int h, int a, int alen) override;
- void drawPolyline(WmfDeviceContext &context, const QPolygon& pa) override;
- void drawPolygon(WmfDeviceContext &context, const QPolygon& pa) override;
- /**
- * drawPolyPolygon draw the XOR of a list of polygons
- * listPa : list of polygons
- */
- void drawPolyPolygon(WmfDeviceContext &context, QList<QPolygon>& listPa) override;
- void drawImage(WmfDeviceContext &context, int x, int y, const QImage &,
- int sx = 0, int sy = 0, int sw = -1, int sh = -1) override;
- void patBlt(WmfDeviceContext &context, int x, int y, int width, int height,
- quint32 rasterOperation) override;
-
- // Text drawing functions
- // rotation = the degrees of rotation in counterclockwise
- // not yet implemented in KWinMetaFile
- void drawText(WmfDeviceContext &context, int x, int y, const QString &s) override;
-
- // matrix transformation : only used in some bitmap manipulation
- void setMatrix(WmfDeviceContext &context, const QMatrix &, bool combine = false) override;
-
- private:
- void updateFromDeviceContext(WmfDeviceContext &context);
-
-protected:
- bool mIsInternalPainter; // True if the painter wasn't externally provided.
- QPainter *mPainter;
- QSizeF mOutputSize;
- QPaintDevice *mTarget;
- bool mRelativeCoord;
- //QPoint mLastPos;
-
- // Everything that has to do with window and viewport calculation
- QPoint mWindowOrg;
- QSize mWindowExt;
- QPoint mViewportOrg;
- QSize mViewportExt;
- bool mWindowExtIsSet;
- bool mViewportExtIsSet;
- QTransform mOutputTransform;
- QTransform mWorldTransform;
-
- int mSaveCount; //number of times Save() was called without Restore()
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libwmf/WmfParser.cpp b/libs/vectorimage/libwmf/WmfParser.cpp
deleted file mode 100644
index 90ad834698..0000000000
--- a/libs/vectorimage/libwmf/WmfParser.cpp
+++ /dev/null
@@ -1,1696 +0,0 @@
-/* This file is part of the KDE libraries
- *
- * Copyright (c) 1998 Stefan Taferner
- * 2001/2003 thierry lorthiois (lorthioist@wanadoo.fr)
- * 2007-2008 Jan Hambrecht <jaham@gmx.net>
- * 2009-2011 Inge Wallin <inge@lysator.liu.se>
- * With the help of WMF documentation by Caolan Mc Namara
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License version 2 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-#include "WmfParser.h"
-#include "WmfAbstractBackend.h"
-
-#include <VectorImageDebug.h>
-
-#include <QImage>
-#include <QMatrix>
-#include <QDataStream>
-#include <QByteArray>
-#include <QBuffer>
-#include <QPolygon>
-
-#include <math.h>
-
-
-#define DEBUG_BBOX 0
-#define DEBUG_RECORDS 0
-
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-#if defined(DEBUG_RECORDS)
-// Used for debugging of records
-static const struct KoWmfFunc {
- const char *name;
-} koWmfFunc[] = {
- // index metafunc
- { "end" }, // 0 0x00
- { "setBkColor" }, // 1 0x01
- { "setBkMode" }, // 2 0x02
- { "setMapMode" }, // 3 0x03
- { "setRop" }, // 4 0x04
- { "setRelAbs" }, // 5 0x05
- { "setPolyFillMode" }, // 6 0x06
- { "setStretchBltMode" }, // 7 0x07
- { "setTextCharExtra" }, // 8 0x08
- { "setTextColor" }, // 9 0x09
- { "setTextJustification" }, // 10 0x0a
- { "setWindowOrg" }, // 11 0x0b
- { "setWindowExt" }, // 12 0x0c
- { "setViewportOrg" }, // 13 0x0d
- { "setViewportExt" }, // 14 0x0e
- { "offsetWindowOrg" }, // 15 0x0f
- { "scaleWindowExt" }, // 16 0x10
- { "offsetViewportOrg" }, // 17 0x11
- { "scaleViewportExt" }, // 18 0x12
- { "lineTo" }, // 19 0x13
- { "moveTo" }, // 20 0x14
- { "excludeClipRect" }, // 21 0x15
- { "intersectClipRect" }, // 22 0x16
- { "arc" }, // 23 0x17
- { "ellipse" }, // 24 0x18
- { "floodfill" }, // 25 0x19 floodfill
- { "pie" }, // 26 0x1a
- { "rectangle" }, // 27 0x1b
- { "roundRect" }, // 28 0x1c
- { "patBlt" }, // 29 0x1d
- { "saveDC" }, // 30 0x1e
- { "setPixel" }, // 31 0x1f
- { "offsetClipRegion" }, // 32 0x20
- { "textOut" }, // 33 0x21
- { "bitBlt" }, // 34 0x22
- { "stretchBlt" }, // 35 0x23
- { "polygon" }, // 36 0x24
- { "polyline" }, // 37 0x25
- { "escape" }, // 38 0x26
- { "restoreDC" }, // 39 0x27
- { "fillRegion" }, // 40 0x28
- { "frameRegion" }, // 41 0x29
- { "invertRegion" }, // 42 0x2a
- { "paintRegion" }, // 43 0x2b
- { "selectClipRegion" }, // 44 0x2c
- { "selectObject" }, // 45 0x2d
- { "setTextAlign" }, // 46 0x2e
- { "noSuchRecord" }, // 47 0x2f
- { "chord" }, // 48 0x30
- { "setMapperFlags" }, // 49 0x31
- { "extTextOut" }, // 50 0x32
- { "setDibToDev" }, // 51 0x33
- { "selectPalette" }, // 52 0x34
- { "realizePalette" }, // 53 0x35
- { "animatePalette" }, // 54 0x36
- { "setPalEntries" }, // 55 0x37
- { "polyPolygon" }, // 56 0x38
- { "resizePalette" }, // 57 0x39
- { "noSuchRecord" }, // 58 0x3a
- { "noSuchRecord" }, // 59 0x3b
- { "noSuchRecord" }, // 60 0x3c
- { "noSuchRecord" }, // 61 0x3d
- { "noSuchRecord" }, // 62 0x3e
- { "unimplemented" }, // 63 0x3f
- { "dibBitBlt" }, // 64 0x40
- { "dibStretchBlt" }, // 65 0x41
- { "dibCreatePatternBrush" }, // 66 0x42
- { "stretchDib" }, // 67 0x43
- { "noSuchRecord" }, // 68 0x44
- { "noSuchRecord" }, // 69 0x45
- { "noSuchRecord" }, // 70 0x46
- { "noSuchRecord" }, // 71 0x47
- { "extFloodFill" }, // 72 0x48
- { "setLayout" }, // 73 0x49
- { "unimplemented" }, // 74 0x4a
- { "unimplemented" }, // 75 0x4b
- { "resetDC" }, // 76 0x4c
- { "startDoc" }, // 77 0x4d
- { "unimplemented" }, // 78 0x4e
- { "startPage" }, // 79 0x4f
- { "endPage" }, // 80 0x50
- { "unimplemented" }, // 81 0x51
- { "unimplemented" }, // 82 0x52
- { "unimplemented" }, // 83 0x53
- { "unimplemented" }, // 84 0x54
- { "unimplemented" }, // 85 0x55
- { "unimplemented" }, // 86 0x56
- { "unimplemented" }, // 87 0x57
- { "unimplemented" }, // 88 0x58
- { "unimplemented" }, // 89 0x59
- { "unimplemented" }, // 90 0x5a
- { "unimplemented" }, // 91 0x5b
- { "unimplemented" }, // 92 0x5c
- { "unimplemented" }, // 93 0x5d
- { "endDoc" }, // 94 0x5e
- { "unimplemented" }, // 95 0x5f
- { "deleteObject" }, // 96 0xf0
- { "noSuchRecord" }, // 97 0xf1
- { "noSuchRecord" }, // 98 0xf2
- { "noSuchRecord" }, // 99 0xf3
- { "noSuchRecord" }, // 100 0xf4
- { "noSuchRecord" }, // 101 0xf5
- { "noSuchRecord" }, // 102 0xf6
- { "createPalette" }, // 103 0xf7
- { "createBrush" }, // 104 0xf8
- { "createPatternBrush" }, // 105 0xf9
- { "createPenIndirect" }, // 106 0xfa
- { "createFontIndirect" }, // 107 0xfb
- { "createBrushIndirect" }, //108 0xfc
- { "createBitmapIndirect" }, //109 0xfd
- { "createBitmap" }, // 110 0xfe
- { "createRegion" } // 111 0xff
-};
-#endif
-
-WmfParser::WmfParser()
-{
- mNbrFunc = 0;
- mValid = false;
- mStandard = false;
- mPlaceable = false;
- mEnhanced = false;
- mBuffer = 0;
- mObjHandleTab = 0;
-}
-
-
-WmfParser::~WmfParser()
-{
- if (mObjHandleTab != 0) {
- for (int i = 0 ; i < mNbrObject ; i++) {
- if (mObjHandleTab[i] != 0)
- delete mObjHandleTab[i];
- }
- delete[] mObjHandleTab;
- }
- if (mBuffer != 0) {
- mBuffer->close();
- delete mBuffer;
- }
-}
-
-
-bool WmfParser::load(const QByteArray& array)
-{
- // delete previous buffer
- if (mBuffer != 0) {
- mBuffer->close();
- delete mBuffer;
- mBuffer = 0;
- }
-
- if (array.size() == 0)
- return false;
-
- // load into buffer
- mBuffer = new QBuffer();
- mBuffer->setData(array);
- mBuffer->open(QIODevice::ReadOnly);
-
- // read and check the header
- WmfMetaHeader header;
- WmfEnhMetaHeader eheader;
- WmfPlaceableHeader pheader; // Contains a bounding box
- unsigned short checksum;
- int filePos;
-
- QDataStream stream(mBuffer);
- stream.setByteOrder(QDataStream::LittleEndian);
-
- mStackOverflow = false;
- mLayout = LAYOUT_LTR;
- mTextColor = Qt::black;
- mMapMode = MM_ANISOTROPIC;
-
- mValid = false;
- mStandard = false;
- mPlaceable = false;
- mEnhanced = false;
-
- // Initialize the bounding box.
- //mBBoxTop = 0; // The default origin is (0, 0).
- //mBBoxLeft = 0;
- mBBoxTop = 32767;
- mBBoxLeft = 32767;
- mBBoxRight = -32768;
- mBBoxBottom = -32768;
- mMaxWidth = 0;
- mMaxHeight = 0;
-
-#if DEBUG_RECORDS
- debugVectorImage << "--------------------------- Starting parsing WMF ---------------------------";
-#endif
- stream >> pheader.key;
- if (pheader.key == (quint32)APMHEADER_KEY) {
- //----- Read placeable metafile header
- mPlaceable = true;
-#if DEBUG_RECORDS
- debugVectorImage << "Placeable header! Yessss!";
-#endif
-
- stream >> pheader.handle;
- stream >> pheader.left;
- stream >> pheader.top;
- stream >> pheader.right;
- stream >> pheader.bottom;
- stream >> pheader.inch;
- stream >> pheader.reserved;
- stream >> pheader.checksum;
- checksum = calcCheckSum(&pheader);
- if (pheader.checksum != checksum) {
- warnVectorImage << "Checksum for placeable metafile header is incorrect ( actual checksum" << pheader.checksum << ", expected checksum" << checksum << ")";
- return false;
- }
- stream >> header.fileType;
- stream >> header.headerSize;
- stream >> header.version;
- stream >> header.fileSize;
- stream >> header.numOfObjects;
- stream >> header.maxRecordSize;
- stream >> header.numOfParameters;
-
- mNbrObject = header.numOfObjects;
-
- // The bounding box of the WMF
- mBBoxLeft = pheader.left;
- mBBoxTop = pheader.top;
- mBBoxRight = pheader.right;
- mBBoxBottom = pheader.bottom;
-#if DEBUG_RECORDS
- debugVectorImage << "bounding box in header: " << mBBoxLeft << mBBoxTop << mBBoxRight << mBBoxBottom
- << "width, height: " << mBBoxRight - mBBoxLeft << mBBoxBottom - mBBoxTop;
-#endif
- mMaxWidth = abs(pheader.right - pheader.left);
- mMaxHeight = abs(pheader.bottom - pheader.top);
-
- mDpi = pheader.inch;
- } else {
- mBuffer->reset();
- //----- Read as enhanced metafile header
- filePos = mBuffer->pos();
- stream >> eheader.recordType;
- stream >> eheader.recordSize;
- stream >> eheader.boundsLeft;
- stream >> eheader.boundsTop;
- stream >> eheader.boundsRight;
- stream >> eheader.boundsBottom;
- stream >> eheader.frameLeft;
- stream >> eheader.frameTop;
- stream >> eheader.frameRight;
- stream >> eheader.frameBottom;
-
- stream >> eheader.signature;
- if (eheader.signature == ENHMETA_SIGNATURE) {
- mEnhanced = true;
- stream >> eheader.version;
- stream >> eheader.size;
- stream >> eheader.numOfRecords;
- stream >> eheader.numHandles;
- stream >> eheader.reserved;
- stream >> eheader.sizeOfDescription;
- stream >> eheader.offsetOfDescription;
- stream >> eheader.numPaletteEntries;
- stream >> eheader.widthDevicePixels;
- stream >> eheader.heightDevicePixels;
- stream >> eheader.widthDeviceMM;
- stream >> eheader.heightDeviceMM;
- } else {
- //----- Read as standard metafile header
- mStandard = true;
- mBuffer->seek(filePos);
- stream >> header.fileType;
- stream >> header.headerSize;
- stream >> header.version;
- stream >> header.fileSize;
- stream >> header.numOfObjects;
- stream >> header.maxRecordSize;
- stream >> header.numOfParameters;
- mNbrObject = header.numOfObjects;
- }
- }
- mOffsetFirstRecord = mBuffer->pos();
-
- //----- Test header validity
- if (((header.headerSize == 9) && (header.numOfParameters == 0)) || (mPlaceable)) {
- // valid wmf file
- mValid = true;
- } else {
- debugVectorImage << "WmfParser : incorrect file format !";
- }
-
- // check bounding rectangle for standard meta file
- if (mStandard && mValid) {
- // Note that this call can change mValid.
- createBoundingBox(stream);
-
-#if DEBUG_RECORDS
- debugVectorImage << "bounding box created by going through all records: "
- << mBBoxLeft << mBBoxTop << mBBoxRight << mBBoxBottom
- << "width, height: " << mBBoxRight - mBBoxLeft << mBBoxBottom - mBBoxTop;
-#endif
- }
-
- return mValid;
-}
-
-
-bool WmfParser::play(WmfAbstractBackend* backend)
-{
- if (!(mValid)) {
- debugVectorImage << "WmfParser::play : invalid WMF file";
- return false;
- }
-
- if (mNbrFunc) {
-#if DEBUG_RECORDS
- if ((mStandard)) {
- debugVectorImage << "Standard :" << mBBoxLeft << "" << mBBoxTop << "" << mBBoxRight - mBBoxLeft << "" << mBBoxBottom - mBBoxTop;
- } else {
- debugVectorImage << "DPI :" << mDpi;
- debugVectorImage << "size (inch):" << (mBBoxRight - mBBoxLeft) / mDpi
- << "" << (mBBoxBottom - mBBoxTop) / mDpi;
- debugVectorImage << "size (mm):" << (mBBoxRight - mBBoxLeft) * 25.4 / mDpi
- << "" << (mBBoxBottom - mBBoxTop) * 25.4 / mDpi;
- }
- debugVectorImage << mValid << "" << mStandard << "" << mPlaceable;
-#endif
- }
-
- // Stack of handles
- mObjHandleTab = new KoWmfHandle* [ mNbrObject ];
- for (int i = 0; i < mNbrObject ; i++) {
- mObjHandleTab[ i ] = 0;
- }
-
- mDeviceContext.reset();
-
- quint16 recordType;
- quint32 size;
- int bufferOffset;
-
- // Create a stream from which the records will be read.
- QDataStream stream(mBuffer);
- stream.setByteOrder(QDataStream::LittleEndian);
-
- // Set the output backend.
- m_backend = backend;
-
- // Set some initial values.
- mDeviceContext.windowOrg = QPoint(0, 0);
- mDeviceContext.windowExt = QSize(1, 1);
-
- QRect bbox(QPoint(mBBoxLeft,mBBoxTop),
- QSize(mBBoxRight - mBBoxLeft, mBBoxBottom - mBBoxTop));
- if (m_backend->begin(bbox)) {
- // Play WMF functions.
- mBuffer->seek(mOffsetFirstRecord);
- recordType = 1;
-
- while ((recordType) && (!mStackOverflow)) {
- int j = 1;
-
- bufferOffset = mBuffer->pos();
- stream >> size;
- stream >> recordType;
-
- // mapping between n function and index of table 'metaFuncTab'
- // lower 8 digits of the function => entry in the table
- quint16 index = recordType & 0xff;
- if (index > 0x5F) {
- index -= 0x90;
- }
-
-#if defined(DEBUG_RECORDS)
- debugVectorImage << "Record = " << koWmfFunc[ index ].name
- << " (" << hex << recordType
- << ", index" << dec << index << ")";
-#endif
-
- if (mNbrFunc) {
- // debug mode
- if ((j + 12) > mNbrFunc) {
- // output last 12 functions
- int offBuff = mBuffer->pos();
- quint16 param;
-
- debugVectorImage << j << " :" << index << " :";
- for (quint16 i = 0 ; i < (size - 3) ; i++) {
- stream >> param;
- debugVectorImage << param << "";
- }
- debugVectorImage;
- mBuffer->seek(offBuff);
- }
- if (j >= mNbrFunc) {
- break;
- }
- j++;
- }
-
- // Execute the function and parse the record.
- switch (recordType & 0xff) {
- case (META_EOF & 0xff):
- // Don't need to do anything here.
- break;
- case (META_SETBKCOLOR & 0xff):
- {
- quint32 color;
-
- stream >> color;
- mDeviceContext.backgroundColor = qtColor(color);
- mDeviceContext.changedItems |= DCBgTextColor;
- }
- break;
- case (META_SETBKMODE & 0xff):
- {
- quint16 bkMode;
-
- stream >> bkMode;
- //debugVectorImage << "New bkMode: " << bkMode;
-
- mDeviceContext.bgMixMode = bkMode;
- mDeviceContext.changedItems |= DCBgMixMode;
- }
- break;
- case (META_SETMAPMODE & 0xff):
- {
- stream >> mMapMode;
- //debugVectorImage << "New mapmode: " << mMapMode;
-
- //mDeviceContext.FontMapMode = mMapMode;Not defined yet
- mDeviceContext.changedItems |= DCFontMapMode;
- }
- break;
- case (META_SETROP2 & 0xff):
- {
- quint16 rop;
-
- stream >> rop;
- m_backend->setCompositionMode(winToQtComposition(rop));
-
- mDeviceContext.rop = rop;
- mDeviceContext.changedItems |= DCFgMixMode;
- } break;
- case (META_SETRELABS & 0xff):
- break;
- case (META_SETPOLYFILLMODE & 0xff):
- {
- stream >> mDeviceContext.polyFillMode;
- mDeviceContext.changedItems |= DCPolyFillMode;
- }
- break;
- case (META_SETSTRETCHBLTMODE & 0xff):
- case (META_SETTEXTCHAREXTRA & 0xff):
- break;
- case (META_SETTEXTCOLOR & 0xff):
- {
- quint32 color;
-
- stream >> color;
- mDeviceContext.foregroundTextColor = qtColor(color);
- mDeviceContext.changedItems |= DCFgTextColor;
- }
- break;
- case (META_SETTEXTJUSTIFICATION & 0xff):
- break;
- case (META_SETWINDOWORG & 0xff):
- {
- qint16 top, left;
-
- stream >> top >> left;
-
- m_backend->setWindowOrg(left, top);
- mDeviceContext.windowOrg = QPoint(left, top);
-#if DEBUG_RECORDS
- debugVectorImage <<"Org: (" << left <<"," << top <<")";
-#endif
- }
- break;
- case (META_SETWINDOWEXT & 0xff):
- {
- qint16 width, height;
-
- // negative value allowed for width and height
- stream >> height >> width;
-#if DEBUG_RECORDS
- debugVectorImage <<"Ext: (" << width <<"," << height <<")";
-#endif
-
- m_backend->setWindowExt(width, height);
- mDeviceContext.windowExt = QSize(width, height);
- }
- break;
- case (META_SETVIEWPORTORG & 0xff):
- {
- qint16 top, left;
-
- stream >> top >> left;
- m_backend->setViewportOrg(left, top);
- mDeviceContext.viewportOrg = QPoint(left, top);
-
-#if DEBUG_RECORDS
- debugVectorImage <<"Org: (" << left <<"," << top <<")";
-#endif
- }
- break;
- case (META_SETVIEWPORTEXT & 0xff):
- {
- qint16 width, height;
-
- // Negative value allowed for width and height
- stream >> height >> width;
-#if DEBUG_RECORDS
- debugVectorImage <<"Ext: (" << width <<"," << height <<")";
-#endif
-
- m_backend->setViewportExt(width, height);
- mDeviceContext.viewportExt = QSize(width, height);
- }
- break;
- case (META_OFFSETWINDOWORG & 0xff):
- {
- qint16 offTop, offLeft;
-
- stream >> offTop >> offLeft;
- m_backend->setWindowOrg(mDeviceContext.windowOrg.x() + offLeft,
- mDeviceContext.windowOrg.y() + offTop);
-
- mDeviceContext.windowOrg += QPoint(offLeft, offTop);
- }
- break;
- case (META_SCALEWINDOWEXT & 0xff):
- {
- // Use 32 bits in the calculations to not lose precision.
- qint32 width, height;
- qint16 heightDenum, heightNum, widthDenum, widthNum;
-
- stream >> heightDenum >> heightNum >> widthDenum >> widthNum;
-
- if ((widthDenum != 0) && (heightDenum != 0)) {
- width = (qint32(mDeviceContext.windowExt.width()) * widthNum) / widthDenum;
- height = (qint32(mDeviceContext.windowExt.height()) * heightNum) / heightDenum;
- m_backend->setWindowExt(width, height);
- mDeviceContext.windowExt = QSize(width, height);
- }
- //debugVectorImage <<"WmfParser::ScaleWindowExt :" << widthDenum <<"" << heightDenum;
- }
- break;
- case (META_OFFSETVIEWPORTORG & 0xff):
- {
- qint16 offTop, offLeft;
-
- stream >> offTop >> offLeft;
- m_backend->setViewportOrg(mDeviceContext.windowOrg.x() + offLeft,
- mDeviceContext.windowOrg.y() + offTop);
- mDeviceContext.viewportOrg += QPoint(offLeft, offTop);
- }
- break;
- case (META_SCALEVIEWPORTEXT & 0xff):
- {
- // Use 32 bits in the calculations to not lose precision.
- qint32 width, height;
- qint16 heightDenum, heightNum, widthDenum, widthNum;
-
- stream >> heightDenum >> heightNum >> widthDenum >> widthNum;
-
- if ((widthDenum != 0) && (heightDenum != 0)) {
- width = (qint32(mDeviceContext.windowExt.width()) * widthNum) / widthDenum;
- height = (qint32(mDeviceContext.windowExt.height()) * heightNum) / heightDenum;
- m_backend->setViewportExt(width, height);
- mDeviceContext.viewportExt = QSize(width, height);
- }
- //debugVectorImage <<"WmfParser::ScaleWindowExt :" << widthDenum <<"" << heightDenum;
- }
- break;
-
- // ----------------------------------------------------------------
- // Drawing records
-
- case (META_LINETO & 0xff):
- {
- qint16 top, left;
-
- stream >> top >> left;
- m_backend->lineTo(mDeviceContext, left, top);
- }
- break;
- case (META_MOVETO & 0xff):
- {
- qint16 top, left;
-
- stream >> top >> left;
- mDeviceContext.currentPosition = QPoint(left, top);
- }
- break;
- case (META_EXCLUDECLIPRECT & 0xff):
- {
- qint16 top, left, right, bottom;
-
- stream >> bottom >> right >> top >> left;
-
- QRegion region = mDeviceContext.clipRegion;
- QRegion newRegion(left, top, right - left, bottom - top);
- if (region.isEmpty()) {
- // FIXME: I doubt that if the region is previously empty,
- // it should be set to the new region. /iw
- region = newRegion;
- } else {
- region = region.subtracted(newRegion);
- }
-
- mDeviceContext.clipRegion = region;
- mDeviceContext.changedItems |= DCClipRegion;
- }
- break;
- case (META_INTERSECTCLIPRECT & 0xff):
- {
- qint16 top, left, right, bottom;
-
- stream >> bottom >> right >> top >> left;
-
- QRegion region = mDeviceContext.clipRegion;
- QRegion newRegion(left, top, right - left, bottom - top);
- if (region.isEmpty()) {
- // FIXME: I doubt that if the region is previously empty,
- // it should be set to the new region. /iw
- region = newRegion;
- } else {
- region = region.intersected(newRegion);
- }
-
- mDeviceContext.clipRegion = region;
- mDeviceContext.changedItems |= DCClipRegion;
- }
- break;
- case (META_ARC & 0xff):
- {
- int xCenter, yCenter, angleStart, aLength;
- qint16 topEnd, leftEnd, topStart, leftStart;
- qint16 top, left, right, bottom;
-
- stream >> topEnd >> leftEnd >> topStart >> leftStart;
- stream >> bottom >> right >> top >> left;
-
- xCenter = left + ((right - left) / 2);
- yCenter = top + ((bottom - top) / 2);
- xyToAngle(leftStart - xCenter, yCenter - topStart,
- leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);
-
- m_backend->drawArc(mDeviceContext, left, top, right - left, bottom - top,
- angleStart, aLength);
- }
- break;
- case (META_ELLIPSE & 0xff):
- {
- qint16 top, left, right, bottom;
-
- stream >> bottom >> right >> top >> left;
- m_backend->drawEllipse(mDeviceContext, left, top, right - left, bottom - top);
- }
- break;
- case (META_FLOODFILL & 0xff):
- break;
- case (META_PIE & 0xff):
- {
- int xCenter, yCenter, angleStart, aLength;
- qint16 topEnd, leftEnd, topStart, leftStart;
- qint16 top, left, right, bottom;
-
- stream >> topEnd >> leftEnd >> topStart >> leftStart;
- stream >> bottom >> right >> top >> left;
-
- xCenter = left + ((right - left) / 2);
- yCenter = top + ((bottom - top) / 2);
- xyToAngle(leftStart - xCenter, yCenter - topStart, leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);
-
- m_backend->drawPie(mDeviceContext, left, top, right - left, bottom - top,
- angleStart, aLength);
- }
- break;
- case (META_RECTANGLE & 0xff):
- {
- qint16 top, left, right, bottom;
-
- stream >> bottom >> right >> top >> left;
- //debugVectorImage << left << top << right << bottom;
- m_backend->drawRect(mDeviceContext, left, top, right - left, bottom - top);
- }
- break;
- case (META_ROUNDRECT & 0xff):
- {
- int xRnd = 0, yRnd = 0;
- quint16 widthCorner, heightCorner;
- qint16 top, left, right, bottom;
-
- stream >> heightCorner >> widthCorner;
- stream >> bottom >> right >> top >> left;
-
- // convert (widthCorner, heightCorner) in percentage
- if ((right - left) != 0)
- xRnd = (widthCorner * 100) / (right - left);
- if ((bottom - top) != 0)
- yRnd = (heightCorner * 100) / (bottom - top);
-
- m_backend->drawRoundRect(mDeviceContext, left, top, right - left, bottom - top,
- xRnd, yRnd);
- }
- break;
- case (META_PATBLT & 0xff):
- {
- quint32 rasterOperation;
- quint16 height, width;
- qint16 y, x;
-
- stream >> rasterOperation;
- stream >> height >> width;
- stream >> y >> x;
-
- //debugVectorImage << "patBlt record" << hex << rasterOperation << dec
- // << x << y << width << height;
-
- m_backend->patBlt(mDeviceContext, x, y, width, height, rasterOperation);
- }
- break;
- case (META_SAVEDC & 0xff):
- m_backend->save();
- break;
- case (META_SETPIXEL & 0xff):
- {
- qint16 left, top;
- quint32 color;
-
- stream >> color >> top >> left;
- m_backend->setPixel(mDeviceContext, left, top, qtColor(color));
- }
- break;
- case (META_OFFSETCLIPRGN & 0xff):
- break;
- case (META_TEXTOUT & 0xff):
- {
- quint16 textLength;
- qint16 x, y;
-
- stream >> textLength;
-
- QByteArray text;
- text.resize(textLength);
- stream.readRawData(text.data(), textLength);
-
- // The string is always of even length, so if the actual data is
- // of uneven length, read an extra byte.
- if (textLength & 0x01) {
- quint8 dummy;
- stream >> dummy;
- }
-
- stream >> y;
- stream >> x;
-
- m_backend->drawText(mDeviceContext, x, y, text);
- }
- break;
- case (META_BITBLT & 0xff):
- case (META_STRETCHBLT & 0xff):
- break;
- case (META_POLYGON & 0xff):
- {
- quint16 num;
-
- stream >> num;
-
- QPolygon pa(num);
-
- pointArray(stream, pa);
- m_backend->drawPolygon(mDeviceContext, pa);
- }
- break;
- case (META_POLYLINE & 0xff):
- {
- quint16 num;
-
- stream >> num;
- QPolygon pa(num);
-
- pointArray(stream, pa);
- m_backend->drawPolyline(mDeviceContext, pa);
- }
- break;
- case (META_ESCAPE & 0xff):
- break;
- case (META_RESTOREDC & 0xff):
- {
- qint16 num;
-
- stream >> num;
- for (int i = 0; i > num ; i--)
- m_backend->restore();
- }
- break;
- case (META_FILLREGION & 0xff):
- case (META_FRAMEREGION & 0xff):
- case (META_INVERTREGION & 0xff):
- case (META_PAINTREGION & 0xff):
- case (META_SELECTCLIPREGION & 0xff):
- break;
- case (META_SELECTOBJECT & 0xff):
- {
- quint16 idx;
-
- stream >> idx;
- if ((idx < mNbrObject) && (mObjHandleTab[ idx ] != 0))
- mObjHandleTab[ idx ]->apply(&mDeviceContext);
- else
- debugVectorImage << "WmfParser::selectObject : selection of an empty object";
- }
- break;
- case (META_SETTEXTALIGN & 0xff):
- stream >> mDeviceContext.textAlign;
- mDeviceContext.changedItems |= DCTextAlignMode;
- break;
- case (META_CHORD & 0xff):
- {
- int xCenter, yCenter, angleStart, aLength;
- qint16 topEnd, leftEnd, topStart, leftStart;
- qint16 top, left, right, bottom;
-
- stream >> topEnd >> leftEnd >> topStart >> leftStart;
- stream >> bottom >> right >> top >> left;
-
- xCenter = left + ((right - left) / 2);
- yCenter = top + ((bottom - top) / 2);
- xyToAngle(leftStart - xCenter, yCenter - topStart, leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);
-
- m_backend->drawChord(mDeviceContext, left, top, right - left, bottom - top,
- angleStart, aLength);
- }
- break;
- case (META_SETMAPPERFLAGS & 0xff):
- break;
- case (META_EXTTEXTOUT & 0xff):
- {
- qint16 y, x;
- qint16 stringLength;
- quint16 fwOpts;
- qint16 top, left, right, bottom; // optional cliprect
-
- stream >> y;
- stream >> x;
- stream >> stringLength;
- stream >> fwOpts;
-
- // ETO_CLIPPED flag adds 4 parameters
- if (fwOpts & (ETO_CLIPPED | ETO_OPAQUE)) {
- // read the optional clip rect
- stream >> bottom >> right >> top >> left;
- }
-
- // Read the string. Note that it's padded to 16 bits.
- QByteArray text;
- text.resize(stringLength);
- stream.readRawData(text.data(), stringLength);
-
- if (stringLength & 0x01) {
- quint8 padding;
- stream >> padding;
- }
-
-#if DEBUG_RECORDS
- debugVectorImage << "text at" << x << y << "length" << stringLength
- << ':' << text;
- //debugVectorImage << "flags:" << hex << fwOpts << dec;
- debugVectorImage << "flags:" << fwOpts;
- debugVectorImage << "record length:" << size;
-#endif
- m_backend->drawText(mDeviceContext, x, y, text);
- }
- break;
- case (META_SETDIBTODEV & 0xff):
- case (META_SELECTPALETTE & 0xff):
- case (META_REALIZEPALETTE & 0xff):
- case (META_ANIMATEPALETTE & 0xff):
- case (META_SETPALENTRIES & 0xff):
- break;
- case (META_POLYPOLYGON & 0xff):
- {
- quint16 numberPoly;
- quint16 sizePoly;
- QList<QPolygon> listPa;
-
- stream >> numberPoly;
-
- for (int i = 0 ; i < numberPoly ; i++) {
- stream >> sizePoly;
- listPa.append(QPolygon(sizePoly));
- }
-
- // list of point array
- for (int i = 0; i < numberPoly; i++) {
- pointArray(stream, listPa[i]);
- }
-
- // draw polygon's
- m_backend->drawPolyPolygon(mDeviceContext, listPa);
- listPa.clear();
- }
- break;
- case (META_RESIZEPALETTE & 0xff):
- break;
- case (META_DIBBITBLT & 0xff):
- {
- quint32 raster;
- qint16 topSrc, leftSrc, widthSrc, heightSrc;
- qint16 topDst, leftDst;
-
- stream >> raster;
- stream >> topSrc >> leftSrc >> heightSrc >> widthSrc;
- stream >> topDst >> leftDst;
-
- if (size > 11) { // DIB image
- QImage bmpSrc;
-
- if (dibToBmp(bmpSrc, stream, (size - 11) * 2)) {
- m_backend->setCompositionMode(winToQtComposition(raster));
-
- m_backend->save();
- if (widthSrc < 0) {
- // negative width => horizontal flip
- QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
- m_backend->setMatrix(mDeviceContext, m, true);
- }
- if (heightSrc < 0) {
- // negative height => vertical flip
- QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
- m_backend->setMatrix(mDeviceContext, m, true);
- }
- m_backend->drawImage(mDeviceContext, leftDst, topDst,
- bmpSrc, leftSrc, topSrc, widthSrc, heightSrc);
- m_backend->restore();
- }
- } else {
- debugVectorImage << "WmfParser::dibBitBlt without image not implemented";
- }
- }
- break;
- case (META_DIBSTRETCHBLT & 0xff):
- {
- quint32 raster;
- qint16 topSrc, leftSrc, widthSrc, heightSrc;
- qint16 topDst, leftDst, widthDst, heightDst;
- QImage bmpSrc;
-
- stream >> raster;
- stream >> heightSrc >> widthSrc >> topSrc >> leftSrc;
- stream >> heightDst >> widthDst >> topDst >> leftDst;
-
- if (dibToBmp(bmpSrc, stream, (size - 13) * 2)) {
- m_backend->setCompositionMode(winToQtComposition(raster));
-
- m_backend->save();
- if (widthDst < 0) {
- // negative width => horizontal flip
- QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
- m_backend->setMatrix(mDeviceContext, m, true);
- }
- if (heightDst < 0) {
- // negative height => vertical flip
- QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
- m_backend->setMatrix(mDeviceContext, m, true);
- }
- bmpSrc = bmpSrc.copy(leftSrc, topSrc, widthSrc, heightSrc);
- // TODO: scale the bitmap : QImage::scale(widthDst, heightDst)
- // is actually too slow
-
- m_backend->drawImage(mDeviceContext, leftDst, topDst, bmpSrc);
- m_backend->restore();
- }
- }
- break;
- case (META_DIBCREATEPATTERNBRUSH & 0xff):
- {
- KoWmfPatternBrushHandle* handle = new KoWmfPatternBrushHandle;
-
- if (addHandle(handle)) {
- quint32 arg;
- QImage bmpSrc;
-
- stream >> arg;
- if (dibToBmp(bmpSrc, stream, (size - 5) * 2)) {
- handle->image = bmpSrc;
- handle->brush.setTextureImage(handle->image);
- } else {
- debugVectorImage << "WmfParser::dibCreatePatternBrush : incorrect DIB image";
- }
- }
- }
- break;
- case (META_STRETCHDIB & 0xff):
- {
- quint32 raster;
- qint16 arg, topSrc, leftSrc, widthSrc, heightSrc;
- qint16 topDst, leftDst, widthDst, heightDst;
- QImage bmpSrc;
-
- stream >> raster >> arg;
- stream >> heightSrc >> widthSrc >> topSrc >> leftSrc;
- stream >> heightDst >> widthDst >> topDst >> leftDst;
-
- if (dibToBmp(bmpSrc, stream, (size - 14) * 2)) {
- m_backend->setCompositionMode(winToQtComposition(raster));
-
- m_backend->save();
- if (widthDst < 0) {
- // negative width => horizontal flip
- QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
- m_backend->setMatrix(mDeviceContext, m, true);
- }
- if (heightDst < 0) {
- // negative height => vertical flip
- QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
- m_backend->setMatrix(mDeviceContext, m, true);
- }
- bmpSrc = bmpSrc.copy(leftSrc, topSrc, widthSrc, heightSrc);
- // TODO: scale the bitmap ( QImage::scale(param[ 8 ], param[ 7 ]) is actually too slow )
-
- m_backend->drawImage(mDeviceContext, leftDst, topDst, bmpSrc);
- m_backend->restore();
- }
- }
- break;
- case (META_EXTFLOODFILL & 0xff):
- break;
- case (META_SETLAYOUT & 0xff):
- {
- quint16 layout;
- quint16 reserved;
-
- // negative value allowed for width and height
- stream >> layout >> reserved;
-#if DEBUG_RECORDS
- debugVectorImage << "layout=" << layout;
-#endif
- mLayout = (WmfLayout)layout;
-
- mDeviceContext.layoutMode = mLayout;
- mDeviceContext.changedItems |= DCLayoutMode;
-
- }
- break;
- case (META_DELETEOBJECT & 0xff):
- {
- quint16 idx;
-
- stream >> idx;
- deleteHandle(idx);
- }
- break;
- case (META_CREATEPALETTE & 0xff):
- // Unimplemented
- createEmptyObject();
- break;
- case (META_CREATEBRUSH & 0xff):
- case (META_CREATEPATTERNBRUSH & 0xff):
- break;
- case (META_CREATEPENINDIRECT & 0xff):
- {
- // TODO: userStyle and alternateStyle
- quint32 color;
- quint16 style, width, arg;
-
- KoWmfPenHandle* handle = new KoWmfPenHandle;
-
- if (addHandle(handle)) {
- stream >> style >> width >> arg >> color;
-
- // set the style defaults
- handle->pen.setStyle(Qt::SolidLine);
- handle->pen.setCapStyle(Qt::RoundCap);
- handle->pen.setJoinStyle(Qt::RoundJoin);
-
- const int PenStyleMask = 0x0000000F;
- const int PenCapMask = 0x00000F00;
- const int PenJoinMask = 0x0000F000;
-
- quint16 penStyle = style & PenStyleMask;
- if (penStyle < 7)
- handle->pen.setStyle(koWmfStylePen[ penStyle ]);
- else
- debugVectorImage << "WmfParser::createPenIndirect: invalid pen" << style;
-
- quint16 capStyle = (style & PenCapMask) >> 8;
- if (capStyle < 3)
- handle->pen.setCapStyle(koWmfCapStylePen[ capStyle ]);
- else
- debugVectorImage << "WmfParser::createPenIndirect: invalid pen cap style" << style;
-
- quint16 joinStyle = (style & PenJoinMask) >> 12;
- if (joinStyle < 3)
- handle->pen.setJoinStyle(koWmfJoinStylePen[ joinStyle ]);
- else
- debugVectorImage << "WmfParser::createPenIndirect: invalid pen join style" << style;
-
- handle->pen.setColor(qtColor(color));
- handle->pen.setWidth(width);
- debugVectorImage << "Creating pen" << handle->pen;
- }
- }
- break;
- case (META_CREATEFONTINDIRECT & 0xff):
- {
- qint16 height; // Height of the character cell
- qint16 width; // Average width (not used)
- qint16 escapement; // The rotation of the text in 1/10th degrees
- qint16 orientation; // The rotation of each character
- quint16 weight, property, fixedPitch, arg;
-
- KoWmfFontHandle* handle = new KoWmfFontHandle;
-
- if (addHandle(handle)) {
- stream >> height >> width;
- stream >> escapement >> orientation;
- stream >> weight >> property >> arg >> arg;
- stream >> fixedPitch;
-
- //debugVectorImage << height << width << weight << property;
- // text rotation (in 1/10 degree)
- handle->font.setFixedPitch(((fixedPitch & 0x01) == 0));
- handle->escapement = escapement;
- handle->orientation = orientation;
-
- // A negative height means to use device units.
- //debugVectorImage << "Font height:" << height;
- handle->height = height;
-
- // FIXME: For some reason this value needs to be multiplied by
- // a factor. 0.6 seems to give a good result, but why??
- // ANSWER(?): The doc says the height is the height of the character cell.
- // But normally the font height is only the height above the
- // baseline, isn't it?
- handle->font.setPointSize(qAbs(height) * 6 / 10);
- if (weight == 0)
- weight = QFont::Normal;
- else {
- // Linear transform between MS weights to Qt weights
- // MS: 400=normal, 700=bold
- // Qt: 50=normal, 75=bold
- // This makes the line cross x=0 at y=50/3. (x=MS weight, y=Qt weight)
- //
- // FIXME: Is this a linear relationship?
- weight = (50 + 3 * ((weight * (75-50))/(700-400))) / 3;
- }
- handle->font.setWeight(weight);
- handle->font.setItalic((property & 0x01));
- handle->font.setUnderline((property & 0x100));
- // TODO: Strikethrough
-
- // font name
- int maxChar = (size - 12) * 2;
- char* nameFont = new char[maxChar];
- stream.readRawData(nameFont, maxChar);
- handle->font.setFamily(nameFont);
- delete[] nameFont;
- }
- }
- break;
- case (META_CREATEBRUSHINDIRECT & 0xff):
- {
- Qt::BrushStyle style;
- quint16 sty, arg2;
- quint32 color;
- KoWmfBrushHandle* handle = new KoWmfBrushHandle;
-
- if (addHandle(handle)) {
- stream >> sty >> color >> arg2;
-
- if (sty == 2) {
- if (arg2 < 6)
- style = koWmfHatchedStyleBrush[ arg2 ];
- else {
- debugVectorImage << "WmfParser::createBrushIndirect: invalid hatched brush" << arg2;
- style = Qt::SolidPattern;
- }
- } else {
- if (sty < 9)
- style = koWmfStyleBrush[ sty ];
- else {
- debugVectorImage << "WmfParser::createBrushIndirect: invalid brush" << sty;
- style = Qt::SolidPattern;
- }
- }
- handle->brush.setStyle(style);
- handle->brush.setColor(qtColor(color));
- }
- }
- break;
-#if 0
- UNSPECIFIED in the Spec:
- { &WmfParser::createBitmapIndirect, "createBitmapIndirect" }, //109 0xfd
- { &WmfParser::createBitmap, "createBitmap" }, // 110 0xfe
-#endif
- case (META_CREATEREGION & 0xff):
- // FIXME: Unimplemented
- createEmptyObject();
- break;
- default:
- // function outside WMF specification
- errorVectorImage << "BROKEN WMF file: Record number" << hex << recordType << dec
- << " index " << index;
- mValid = false;
- break;
- }
-
- mBuffer->seek(bufferOffset + (size << 1));
- }
-
- // Let the backend clean up it's internal state.
- m_backend->end();
- }
-
- for (int i = 0 ; i < mNbrObject ; i++) {
- if (mObjHandleTab[ i ] != 0)
- delete mObjHandleTab[ i ];
- }
- delete[] mObjHandleTab;
- mObjHandleTab = 0;
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-
-
-void WmfParser::createBoundingBox(QDataStream &stream)
-{
- // Check bounding rectangle for standard meta file.
- // This calculation is done in device coordinates.
- if (!mStandard || !mValid)
- return;
-
- bool windowExtIsSet = false;
- bool viewportExtIsSet = false;
-
- quint16 recordType = 1;
- quint32 size;
-
- int filePos;
-
- // Search for records setWindowOrg and setWindowExt to
- // determine what the total bounding box of this WMF is.
- // This initialization assumes that setWindowOrg comes before setWindowExt.
- qint16 windowOrgX = 0;
- qint16 windowOrgY = 0;
- qint16 windowWidth = 0;
- qint16 windowHeight = 0;
- qint16 viewportOrgX = 0;
- qint16 viewportOrgY = 0;
- qint16 viewportWidth = 0;
- qint16 viewportHeight = 0;
- bool bboxRecalculated = false;
- while (recordType) {
-
- filePos = mBuffer->pos();
- stream >> size >> recordType;
-
- if (size == 0) {
- debugVectorImage << "WmfParser: incorrect file!";
- mValid = 0;
- return;
- }
-
- bool doRecalculateBBox = false;
- qint16 orgX = 0;
- qint16 orgY = 0;
- qint16 extX = 0;
- qint16 extY = 0;
- switch (recordType &= 0xFF) {
- case 11: // setWindowOrg
- {
- stream >> windowOrgY >> windowOrgX;
-#if DEBUG_BBOX
- debugVectorImage << "setWindowOrg" << windowOrgX << windowOrgY;
-#endif
- if (!windowExtIsSet)
- break;
-
- // The bounding box doesn't change just because we get
- // a new window. Remember we are working in device
- // (viewport) coordinates when deciding the bounding
- // box.
- if (viewportExtIsSet)
- break;
-
- // If there is no viewport, then use the window ext as
- // size, and (0, 0) as origin.
- //
- // FIXME: Handle the case where the window is defined
- // first and then the viewport, without any
- // drawing in between. If that happens, I
- // don't think that the window definition
- // should influence the bounding box.
- orgX = 0;
- orgY = 0;
- extX = windowWidth;
- extY = windowHeight;
- }
- break;
-
- case 12: // setWindowExt
- {
- stream >> windowHeight >> windowWidth;
- windowExtIsSet = true;
- bboxRecalculated = false;
-
-#if DEBUG_BBOX
- debugVectorImage << "setWindowExt" << windowWidth << windowHeight
- << "(viewportOrg = " << viewportOrgX << viewportOrgY << ")";
-#endif
-
- // If the viewport is set, then a changed window
- // changes nothing in the bounding box.
- if (viewportExtIsSet)
- break;
-
- bboxRecalculated = false;
-
- // Collect the maximum width and height.
- if (abs(windowWidth - windowOrgX) > mMaxWidth)
- mMaxWidth = abs(windowWidth - windowOrgX);
- if (abs(windowHeight - windowOrgY) > mMaxHeight)
- mMaxHeight = abs(windowHeight - windowOrgY);
-
- orgX = 0;
- orgY = 0;
- extX = windowWidth;
- extY = windowHeight;
- }
- break;
-
- case 13: //setViewportOrg
- {
- stream >> viewportOrgY >> viewportOrgX;
- bboxRecalculated = false;
-
-#if DEBUG_BBOX
- debugVectorImage << "setViewportOrg" << viewportOrgX << viewportOrgY;
-#endif
- orgX = viewportOrgX;
- orgY = viewportOrgY;
- if (viewportExtIsSet) {
- extX = viewportWidth;
- extY = viewportHeight;
- }
- else {
- // If the viewportExt is not set, then either a
- // subsequent setViewportExt will set it, or the
- // windowExt will be used instead.
- extX = windowWidth;
- extY = windowHeight;
- }
- break;
-
- // FIXME: Handle the case where the org changes but
- // there is no subsequent Ext change (should be
- // rather uncommon).
- }
- break;
-
- case 14: //setViewportExt
- {
- stream >> viewportHeight >> viewportWidth;
- viewportExtIsSet = true;
- bboxRecalculated = false;
-
-#if DEBUG_BBOX
- debugVectorImage << "setViewportExt" << viewportWidth << viewportHeight;
-#endif
- orgX = viewportOrgX;
- orgY = viewportOrgY;
- extX = viewportWidth;
- extY = viewportHeight;
- }
- break;
-
- // FIXME: Also support:
- // ScaleWindowExt, ScaleViewportExt,
- // OffsetWindowOrg, OffsetViewportOrg
-
- // The following are drawing commands. It is only when
- // there is an actual drawing command that we should check
- // the bounding box. It seems that some WMF files have
- // lots of changes of the window or viewports but without
- // any drawing commands in between. These changes should
- // not affect the bounding box.
- case 19: // lineTo
- //case 20: // moveTo
- case 23: // arc
- case 24: // ellipse
- case 26: // pie
- case 27: // rectangle
- case 28: // roundRect
- case 29: // patBlt
- case 31: // setPixel
- case 33: // textOut
- case 34: // bitBlt
- case 36: // polygon
- case 37: // polyline
- //case 38: // escape FIXME: is this a drawing command?
- case 40: // fillRegion
- case 41:
- case 42:
- case 43:
- case 44:
- case 48: // chord
- case 50: // extTextOut
- case 56: // polyPolygon
- case 64: // dibBitBlt
- case 65: // dibStretchBlt
- case 67: // stretchDib
- case 72: // extFloodFill
-#if DEBUG_BBOX
- debugVectorImage << "drawing record: " << (recordType & 0xff);
-#endif
- doRecalculateBBox = true;
- break;
-
- default:
- ;
- }
-
- // Recalculate the BBox if it was indicated above that it should be.
- if (doRecalculateBBox && !bboxRecalculated) {
-#if DEBUG_BBOX
- debugVectorImage << "Recalculating BBox";
-#endif
- // If we have a viewport, always use that one.
- if (viewportExtIsSet) {
- orgX = viewportOrgX;
- orgY = viewportOrgY;
- extX = viewportWidth;
- extY = viewportHeight;
- }
- else {
- // If there is no defined viewport, then use the
- // window as the fallback viewport. But only the size,
- // the origin is always (0, 0).
- orgX = 0;
- orgY = 0;
- extX = qAbs(windowWidth);
- extY = qAbs(windowHeight);
- }
-
- // If ext < 0, switch the org and org+ext
- if (extX < 0) {
- orgX += extX;
- extX = -extX;
- }
- if (extY < 0) {
- orgY += extY;
- extY = -extY;
- }
-
- // At this point, the ext is always >= 0, i.e. org <= org+ext
-#if DEBUG_BBOX
- debugVectorImage << orgX << orgY << extX << extY;
-#endif
- if (orgX < mBBoxLeft) mBBoxLeft = orgX;
- if (orgY < mBBoxTop) mBBoxTop = orgY;
- if (orgX + extX > mBBoxRight) mBBoxRight = orgX + extX;
- if (orgY + extY > mBBoxBottom) mBBoxBottom = orgY + extY;
-
- bboxRecalculated = true;
- }
-
-#if DEBUG_BBOX
- if (isOrgOrExt) {
- debugVectorImage << " mBBoxTop = " << mBBoxTop;
- debugVectorImage << "mBBoxLeft = " << mBBoxLeft << " mBBoxRight = " << mBBoxRight;
- debugVectorImage << " MBBoxBotton = " << mBBoxBottom;
- debugVectorImage << "Max width,height = " << mMaxWidth << mMaxHeight;
- }
-#endif
-
- mBuffer->seek(filePos + (size << 1));
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Object handle
-
-
-void WmfParser::createEmptyObject()
-{
- // allocation of an empty object (to keep object counting in sync)
- KoWmfPenHandle* handle = new KoWmfPenHandle;
-
- addHandle(handle);
-}
-
-
-//-----------------------------------------------------------------------------
-// Misc functions
-
-
-quint16 WmfParser::calcCheckSum(WmfPlaceableHeader* apmfh)
-{
- quint16* lpWord;
- quint16 wResult, i;
-
- // Start with the first word
- wResult = *(lpWord = (quint16*)(apmfh));
- // XOR in each of the other 9 words
- for (i = 1; i <= 9; i++) {
- wResult ^= lpWord[ i ];
- }
- return wResult;
-}
-
-
-//-----------------------------------------------------------------------------
-// Utilities and conversion Wmf -> Qt
-
-bool WmfParser::addHandle(KoWmfHandle* handle)
-{
- int idx;
-
- for (idx = 0; idx < mNbrObject ; idx++) {
- if (mObjHandleTab[ idx ] == 0) break;
- }
-
- if (idx < mNbrObject) {
- mObjHandleTab[ idx ] = handle;
- return true;
- } else {
- delete handle;
- mStackOverflow = true;
- debugVectorImage << "WmfParser::addHandle : stack overflow = broken file !";
- return false;
- }
-}
-
-
-void WmfParser::deleteHandle(int idx)
-{
- if ((idx < mNbrObject) && (mObjHandleTab[idx] != 0)) {
- delete mObjHandleTab[ idx ];
- mObjHandleTab[ idx ] = 0;
- } else {
- debugVectorImage << "WmfParser::deletehandle() : bad index number";
- }
-}
-
-
-void WmfParser::pointArray(QDataStream& stream, QPolygon& pa)
-{
- qint16 left, top;
- int i, max;
-
- for (i = 0, max = pa.size() ; i < max ; i++) {
- stream >> left >> top;
- pa.setPoint(i, left, top);
- }
-}
-
-
-void WmfParser::xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int& angleStart, int& angleLength)
-{
- double aStart, aLength;
-
- aStart = atan2((double)yStart, (double)xStart);
- aLength = atan2((double)yEnd, (double)xEnd) - aStart;
-
- angleStart = (int)((aStart * 2880) / 3.14166);
- angleLength = (int)((aLength * 2880) / 3.14166);
- if (angleLength < 0) angleLength = 5760 + angleLength;
-}
-
-
-QPainter::CompositionMode WmfParser::winToQtComposition(quint16 param) const
-{
- if (param < 17)
- return koWmfOpTab16[ param ];
- else
- return QPainter::CompositionMode_Source;
-}
-
-
-QPainter::CompositionMode WmfParser::winToQtComposition(quint32 param) const
-{
- /* TODO: Ternary raster operations
- 0x00C000CA dest = (source AND pattern)
- 0x00F00021 dest = pattern
- 0x00FB0A09 dest = DPSnoo
- 0x005A0049 dest = pattern XOR dest */
- int i;
-
- for (i = 0 ; i < 15 ; i++) {
- if (koWmfOpTab32[ i ].winRasterOp == param) break;
- }
-
- if (i < 15)
- return koWmfOpTab32[ i ].qtRasterOp;
- else
- return QPainter::CompositionMode_SourceOver;
-}
-
-
-bool WmfParser::dibToBmp(QImage& bmp, QDataStream& stream, quint32 size)
-{
- typedef struct _BMPFILEHEADER {
- quint16 bmType;
- quint32 bmSize;
- quint16 bmReserved1;
- quint16 bmReserved2;
- quint32 bmOffBits;
- } BMPFILEHEADER;
-
- int sizeBmp = size + 14;
-
- QByteArray pattern; // BMP header and DIB data
- pattern.resize(sizeBmp);
- pattern.fill(0);
- stream.readRawData(pattern.data() + 14, size);
-
- // add BMP header
- // First cast to void* to silence alignment warnings
- BMPFILEHEADER* bmpHeader;
- bmpHeader = (BMPFILEHEADER*)(void *)(pattern.data());
- bmpHeader->bmType = 0x4D42;
- bmpHeader->bmSize = sizeBmp;
-
-// if ( !bmp.loadFromData( (const uchar*)bmpHeader, pattern.size(), "BMP" ) ) {
- if (!bmp.loadFromData(pattern, "BMP")) {
- debugVectorImage << "WmfParser::dibToBmp: invalid bitmap";
- return false;
- } else {
- return true;
- }
-}
-
-
-}
diff --git a/libs/vectorimage/libwmf/WmfParser.h b/libs/vectorimage/libwmf/WmfParser.h
deleted file mode 100644
index 9428579b0a..0000000000
--- a/libs/vectorimage/libwmf/WmfParser.h
+++ /dev/null
@@ -1,156 +0,0 @@
-/* This file is part of the KDE libraries
- *
- * Copyright (c) 1998 Stefan Taferner
- * 2001/2003 thierry lorthiois (lorthioist@wanadoo.fr)
- * 2009-2011 Inge Wallin <inge@lysator.liu.se>
- * With the help of WMF documentation by Caolan Mc Namara
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License version 2 as published by the Free Software Foundation.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef _WMFPARSER_H_
-#define _WMFPARSER_H_
-
-#include <QColor>
-#include <QRect>
-
-#include "WmfEnums.h"
-#include "WmfStructs.h"
-#include "WmfDeviceContext.h"
-#include "WmfStack.h"
-
-class WmfAbstractBackend;
-class QBuffer;
-class QPolygon;
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-/**
- * WmfParser allows to read WMF files
- *
- */
-
-class WmfParser
-{
-public:
- WmfParser();
- virtual ~WmfParser();
-
- /**
- * Load WMF file. Returns true on success.
- */
- bool load(const QByteArray& array);
-
- /**
- * Plays a metafile using @p backend as backend and returns true on success.
- * To draw on a device you have to inherit the class WmfAbstractBackend.
- */
- bool play(WmfAbstractBackend* backend);
-
- /****************** Object handle *******************/
- /// create an empty object in the object list
- void createEmptyObject();
-
- /****************** misc *******************/
-
- /** Calculate header checksum */
- static quint16 calcCheckSum(WmfPlaceableHeader*);
-
-private:
- //-----------------------------------------------------------------------------
- // Utilities and conversion Wmf -> Qt
-
- // Create a boundingbox from all set{Window,Viewport}{Org,Ext} records.
- void createBoundingBox(QDataStream &st);
-
- /** Handle win-object-handles */
- bool addHandle(KoWmfHandle*);
- void deleteHandle(int);
-
- /** Convert QINT16 points into QPointArray */
- void pointArray(QDataStream& stream, QPolygon& pa);
-
- /** Conversion between windows color and QColor */
- QColor qtColor(quint32 color) const {
- return QColor(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
- }
-
- /** Convert (x1,y1) and (x2, y2) positions in angle and angleLength */
- void xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int& angle, int& aLength);
-
- /** Convert windows rasterOp in QT rasterOp */
- QPainter::CompositionMode winToQtComposition(quint16 param) const;
- QPainter::CompositionMode winToQtComposition(quint32 param) const;
-
- /** Converts DIB to BMP */
- bool dibToBmp(QImage& bmp, QDataStream& stream, quint32 size);
-
-
-public:
- // state of the WMF
- bool mValid;
- bool mStandard;
- bool mPlaceable;
- bool mEnhanced;
-
- // Bounding rectangle. In a placeable file this is in the header,
- // otherwise its comprised of calls to setWindowOrg and setWindowExt.
- //
- // We can't use a QRect here because width and/or height may be negative.
- qint16 mBBoxTop;
- qint16 mBBoxLeft;
- qint16 mBBoxRight;
- qint16 mBBoxBottom;
- qint16 mMaxWidth;
- qint16 mMaxHeight;
-
- // standard file : this is the value in setWindowOrg and setWindowExt
- // number of points per inch for the default size
- int mDpi;
-
- /// number of functions to draw (==0 for all)
- int mNbrFunc;
-
-private:
- // the output strategy
- WmfAbstractBackend *m_backend;
-
- // Current state of the drawing
- WmfDeviceContext mDeviceContext;
-
- WmfLayout mLayout;
- QColor mTextColor;
- quint16 mMapMode;
-
- // Memory allocation for WMF file
- QBuffer* mBuffer;
- int mOffsetFirstRecord;
-
- // stack of object handle
- KoWmfHandle** mObjHandleTab;
- int mNbrObject; // number of object on the stack
- bool mStackOverflow;
-};
-
-
-}
-
-#endif
-
diff --git a/libs/vectorimage/libwmf/WmfStack.cpp b/libs/vectorimage/libwmf/WmfStack.cpp
deleted file mode 100644
index 1ebb33380d..0000000000
--- a/libs/vectorimage/libwmf/WmfStack.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/* This file is part of the KDE libraries
- Copyright (c) 1998 Stefan Taferner
- 2001/2003 thierry lorthiois (lorthioist@wanadoo.fr)
- 2011 Inge Wallin (inge@lysator.liu.se)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License version 2 as published by the Free Software Foundation.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-// Own
-#include "WmfStack.h"
-
-// KDE
-#include <VectorImageDebug.h>
-
-// Local
-#include "WmfAbstractBackend.h"
-#include "WmfDeviceContext.h"
-
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-void KoWmfBrushHandle::apply(WmfDeviceContext *dc)
-{
- dc->brush = brush;
- dc->changedItems |= DCBrush;
-}
-
-void KoWmfPenHandle::apply(WmfDeviceContext *dc)
-{
- debugVectorImage << "Setting pen" << pen;
- dc->pen = pen;
- dc->changedItems |= DCPen;
-}
-
-void KoWmfPatternBrushHandle::apply(WmfDeviceContext *dc)
-{
- dc->brush = brush;
- dc->changedItems |= DCBrush;
-}
-
-void KoWmfFontHandle::apply(WmfDeviceContext *dc)
-{
- dc->font = font;
- dc->escapement = escapement;
- dc->orientation = orientation;
- dc->height = height;
- dc->changedItems |= DCFont; // Includes the font itself, the rotation and the height;
-}
-
-}
diff --git a/libs/vectorimage/libwmf/WmfStack.h b/libs/vectorimage/libwmf/WmfStack.h
deleted file mode 100644
index 01f4be5796..0000000000
--- a/libs/vectorimage/libwmf/WmfStack.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* This file is part of the KDE libraries
- Copyright (c) 1998 Stefan Taferner
- 2001/2003 thierry lorthiois (lorthioist@wanadoo.fr)
- 2011 Inge Wallin (inge@lysator.liu.se)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License version 2 as published by the Free Software Foundation.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-#ifndef _WMFSTACK_H_
-#define _WMFSTACK_H_
-
-#include <QPen>
-#include <QColor>
-#include <QFont>
-#include <QBrush>
-#include <QImage>
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-class WmfAbstractBackend;
-class WmfDeviceContext;
-
-/**
- * WMF file allows manipulation on a stack of object.
- * It's possible to create, delete or select an object.
- */
-class KoWmfHandle
-{
-public:
- virtual ~KoWmfHandle() {}
- virtual void apply(WmfDeviceContext *) = 0;
-};
-
-class KoWmfBrushHandle: public KoWmfHandle
-{
-public:
- void apply(WmfDeviceContext *) override;
- QBrush brush;
-};
-
-class KoWmfPenHandle: public KoWmfHandle
-{
-public:
- void apply(WmfDeviceContext *) override;
- QPen pen;
-};
-
-class KoWmfPatternBrushHandle: public KoWmfHandle
-{
-public:
- void apply(WmfDeviceContext *) override;
- QBrush brush;
- QImage image;
-};
-
-class KoWmfFontHandle: public KoWmfHandle
-{
-public:
- void apply(WmfDeviceContext *) override;
- QFont font;
- int escapement;
- int orientation;
- int height; // Can be negative. In 'font' above, we store the absolute value.
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libwmf/WmfStructs.h b/libs/vectorimage/libwmf/WmfStructs.h
deleted file mode 100644
index 95a821427a..0000000000
--- a/libs/vectorimage/libwmf/WmfStructs.h
+++ /dev/null
@@ -1,195 +0,0 @@
-/* This file is part of the Calligra project
- * Copyright (C) 2002/2003 thierry lorthiois
- *
- * 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 _WMFSTRUCT_H_
-#define _WMFSTRUCT_H_
-
-#include <QtGlobal>
-#include <qnamespace.h>
-#include <QPainter>
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-#define APMHEADER_KEY 0x9AC6CDD7
-#define ENHMETA_SIGNATURE 0x464D4520
-
-struct WmfMetaHeader {
- quint16 fileType; // Type of metafile (0=memory, 1=disk)
- quint16 headerSize; // always 9
- quint16 version;
- quint32 fileSize; // Total size of the metafile in WORDs
- quint16 numOfObjects; // Maximum Number of objects in the stack
- quint32 maxRecordSize; // The size of largest record in WORDs
- quint16 numOfParameters; // not used (always 0)
-};
-
-
-struct WmfPlaceableHeader {
- quint32 key; // Magic number (always 9AC6CDD7h)
- quint16 handle; // Metafile HANDLE number (always 0)
- qint16 left; // Left coordinate in metafile units
- qint16 top;
- qint16 right;
- qint16 bottom;
- quint16 inch; // Number of metafile units per inch
- quint32 reserved;
- quint16 checksum; // Checksum value for previous 10 WORDs
-};
-
-
-struct WmfEnhMetaHeader {
- quint32 recordType; // Record type (is always 00000001h)
- quint32 recordSize; // Record size in bytes. This may be greater
- // than the sizeof( ENHMETAHEADER ).
- qint32 boundsLeft; // Inclusive-inclusive bounds in device units
- qint32 boundsTop;
- qint32 boundsRight;
- qint32 boundsBottom;
- qint32 frameLeft; // Inclusive-inclusive Picture Frame
- qint32 frameTop;
- qint32 frameRight;
- qint32 frameBottom;
- quint32 signature; // Signature. Must be ENHMETA_SIGNATURE.
- quint32 version; // Version number
- quint32 size; // Size of the metafile in bytes
- quint32 numOfRecords; // Number of records in the metafile
- quint16 numHandles; // Number of handles in the handle table
- // Handle index zero is reserved.
- quint16 reserved; // always 0
- quint32 sizeOfDescription; // Number of chars in the unicode description string
- // This is 0 if there is no description string
- quint32 offsetOfDescription; // Offset to the metafile description record.
- // This is 0 if there is no description string
- quint32 numPaletteEntries; // Number of color palette entries
- qint32 widthDevicePixels; // Size of the reference device in pixels
- qint32 heightDevicePixels;
- qint32 widthDeviceMM; // Size of the reference device in millimeters
- qint32 heightDeviceMM;
-};
-
-
-struct WmfMetaRecord {
- quint32 size; // Total size of the record in WORDs
- quint16 function; // Record function number
- quint16 param[ 1 ]; // quint16 array of parameters
-};
-
-
-struct WmfEnhMetaRecord {
- quint32 function; // Record function number
- quint32 size; // Record size in bytes
- quint32 param[ 1 ]; // quint32 array of parameters
-};
-
-// Static data
-static const struct OpTab {
- quint32 winRasterOp;
- QPainter::CompositionMode qtRasterOp;
-} koWmfOpTab32[] = {
- // ### untested (conversion from Qt::RasterOp)
- { 0x00CC0020, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00EE0086, QPainter::CompositionMode_SourceOver }, // OrROP
- { 0x008800C6, QPainter::CompositionMode_SourceIn }, // AndROP
- { 0x00660046, QPainter::CompositionMode_Xor }, // XorROP
- { 0x00440328, QPainter::CompositionMode_DestinationOut }, // AndNotROP
- { 0x00330008, QPainter::CompositionMode_DestinationOut }, // NotCopyROP
- { 0x001100A6, QPainter::CompositionMode_SourceOut }, // NandROP
- { 0x00C000CA, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00BB0226, QPainter::CompositionMode_Destination }, // NotOrROP
- { 0x00F00021, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00FB0A09, QPainter::CompositionMode_Source }, // CopyROP
- { 0x005A0049, QPainter::CompositionMode_Source }, // CopyROP
- { 0x00550009, QPainter::CompositionMode_DestinationOut }, // NotROP
- { 0x00000042, QPainter::CompositionMode_Clear }, // ClearROP
- { 0x00FF0062, QPainter::CompositionMode_Source } // SetROP
-};
-
-static const QPainter::CompositionMode koWmfOpTab16[] = {
- // ### untested (conversion from Qt::RasterOp)
- QPainter::CompositionMode_Source, // CopyROP
- QPainter::CompositionMode_Clear, // ClearROP
- QPainter::CompositionMode_SourceOut, // NandROP
- QPainter::CompositionMode_SourceOut, // NotAndROP
- QPainter::CompositionMode_DestinationOut, // NotCopyROP
- QPainter::CompositionMode_DestinationOut, // AndNotROP
- QPainter::CompositionMode_DestinationOut, // NotROP
- QPainter::CompositionMode_Xor, // XorROP
- QPainter::CompositionMode_Source, // NorROP
- QPainter::CompositionMode_SourceIn, // AndROP
- QPainter::CompositionMode_SourceIn, //NotXorROP
- QPainter::CompositionMode_Destination, // NopROP
- QPainter::CompositionMode_Destination, // NotOrROP
- QPainter::CompositionMode_Source, // CopyROP
- QPainter::CompositionMode_Source, // OrNotROP
- QPainter::CompositionMode_SourceOver, // OrROP
- QPainter::CompositionMode_Source // SetROP
-};
-
-static const Qt::BrushStyle koWmfHatchedStyleBrush[] = {
- Qt::HorPattern,
- Qt::VerPattern,
- Qt::FDiagPattern,
- Qt::BDiagPattern,
- Qt::CrossPattern,
- Qt::DiagCrossPattern
-};
-
-static const Qt::BrushStyle koWmfStyleBrush[] = {
- Qt::SolidPattern,
- Qt::NoBrush,
- Qt::FDiagPattern, /* hatched */
- Qt::Dense4Pattern, /* should be custom bitmap pattern */
- Qt::HorPattern, /* should be BS_INDEXED (?) */
- Qt::VerPattern, /* should be device-independent bitmap */
- Qt::Dense6Pattern, /* should be device-independent packed-bitmap */
- Qt::Dense2Pattern, /* should be BS_PATTERN8x8 */
- Qt::Dense3Pattern /* should be device-independent BS_DIBPATTERN8x8 */
-};
-
-static const Qt::PenStyle koWmfStylePen[] = {
- Qt::SolidLine,
- Qt::DashLine,
- Qt::DotLine,
- Qt::DashDotLine,
- Qt::DashDotDotLine,
- Qt::NoPen,
- Qt::SolidLine
-};
-
-static const Qt::PenCapStyle koWmfCapStylePen[] = {
- Qt::RoundCap,
- Qt::SquareCap,
- Qt::FlatCap
-};
-
-static const Qt::PenJoinStyle koWmfJoinStylePen[] = {
- Qt::RoundJoin,
- Qt::BevelJoin,
- Qt::MiterJoin
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libwmf/WmfWriter.cpp b/libs/vectorimage/libwmf/WmfWriter.cpp
deleted file mode 100644
index 313ab590bf..0000000000
--- a/libs/vectorimage/libwmf/WmfWriter.cpp
+++ /dev/null
@@ -1,505 +0,0 @@
-/* This file is part of the KDE libraries
- *
- * Copyright (c) 2003 thierry lorthiois (lorthioist@wanadoo.fr)
- * Copyright (c) 2011 Inge Wallin (inge@lysator.liu.se)
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License version 2 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-#include "WmfWriter.h"
-
-#include "WmfStructs.h"
-#include "WmfParser.h"
-
-#include <VectorImageDebug.h>
-
-#include <QFile>
-#include <QDataStream>
-#include <QPolygon>
-#include <QPen>
-#include <QBrush>
-#include <QColor>
-#include <QFont>
-#include <QRegion>
-
-#include <math.h>
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-
-/**
- * Private data
- */
-class WmfWriterPrivate
-{
-public:
- QRect mBBox; // bounding rectangle
- int mDpi; // number of point per inch for the default size
- int mMaxRecordSize;
-
- // memory allocation for WMF file
- QFile mFileOut;
- QDataStream mSt;
-};
-
-
-
-WmfWriter::WmfWriter(const QString& fileName)
- : d(new WmfWriterPrivate)
-{
- d->mDpi = 1024;
- d->mMaxRecordSize = 0;
- d->mFileOut.setFileName(fileName);
-}
-
-WmfWriter::~WmfWriter()
-{
- delete d;
-}
-
-
-void WmfWriter::setDefaultDpi(int dpi)
-{
- d->mDpi = dpi;
-}
-
-
-//-----------------------------------------------------------------------------
-// Virtual Painter => create the WMF
-
-bool WmfWriter::begin()
-{
-
- if (!d->mFileOut.open(QIODevice::WriteOnly)) {
- debugVectorImage << "Cannot open file" << QFile::encodeName(d->mFileOut.fileName());
- return false;
- }
- d->mSt.setDevice(&d->mFileOut);
- d->mSt.setByteOrder(QDataStream::LittleEndian);
-
- // reserved placeable and standard header
- for (int i = 0 ; i < 10 ; i++) {
- d->mSt << (quint32)0;
- }
-
- // initialize the stack of objects
- // Pen
- d->mSt << (quint32)8 << (quint16)0x02FA;
- d->mSt << (quint16)5 << (quint16)0 << (quint16)0 << (quint32)0;
- // Brush
- d->mSt << (quint32)7 << (quint16)0x02FC;
- d->mSt << (quint16)1 << (quint32)0 << (quint16)0;
- for (int i = 0 ; i < 4 ; i++) {
- d->mSt << (quint32)8 << (quint16)0x02FA << (quint16)0 << (quint32)0 << (quint32)0;
- }
- d->mMaxRecordSize = 8;
-
- return true;
-}
-
-
-bool WmfWriter::end()
-{
- WmfPlaceableHeader pheader = { 0x9AC6CDD7, 0, 0, 0, 0, 0, 0, 0, 0 };
- quint16 checksum;
-
- // End of the wmf file
- d->mSt << (quint32)3 << (quint16)0;
-
- // adjust header
- pheader.left = d->mBBox.left();
- pheader.top = d->mBBox.top();
- pheader.right = d->mBBox.right();
- pheader.bottom = d->mBBox.bottom();
- pheader.inch = d->mDpi;
- checksum = WmfParser::calcCheckSum(&pheader);
-
- // write headers
- d->mFileOut.reset();
- d->mSt << (quint32)0x9AC6CDD7 << (quint16)0;
- d->mSt << (qint16)d->mBBox.left() << (qint16)d->mBBox.top() << (qint16)d->mBBox.right() << (qint16)d->mBBox.bottom();
- d->mSt << (quint16)d->mDpi << (quint32)0 << checksum;
- d->mSt << (quint16)1 << (quint16)9 << (quint16)0x300 << (quint32)(d->mFileOut.size() / 2);
- d->mSt << (quint16)6 << (quint32)d->mMaxRecordSize << (quint16)0;
-
- d->mFileOut.close();
-
- return true;
-}
-
-
-void WmfWriter::save()
-{
- d->mSt << (quint32)3 << (quint16)0x001E;
-}
-
-
-void WmfWriter::restore()
-{
- d->mSt << (quint32)4 << (quint16)0x0127 << (quint16)1;
-}
-
-
-void WmfWriter::setPen(const QPen &pen)
-{
- int style;
- int max = sizeof(koWmfStylePen) / sizeof(Qt::SolidLine);
-
- // we can't delete an object currently selected
- // select another object
- d->mSt << (quint32)4 << (quint16)0x012D << (quint16)0;
- // delete object
- d->mSt << (quint32)4 << (quint16)0x01f0 << (quint16)2;
-
- for (style = 0 ; style < max ; style++) {
- if (koWmfStylePen[ style ] == pen.style()) break;
- }
- if (style == max) {
- // SolidLine
- style = 0;
- }
- d->mSt << (quint32)8 << (quint16)0x02FA;
- d->mSt << (quint16)style << (quint16)pen.width() << (quint16)0 << (quint32)winColor(pen.color());
-
- // select object
- d->mSt << (quint32)4 << (quint16)0x012D << (quint16)2;
-}
-
-
-void WmfWriter::setBrush(const QBrush &brush)
-{
- int style;
- int max = sizeof(koWmfStyleBrush) / sizeof(Qt::NoBrush);
-
- // we can't delete an object currently selected
- // select another object
- d->mSt << (quint32)4 << (quint16)0x012D << (quint16)1;
- // delete object
- d->mSt << (quint32)4 << (quint16)0x01f0 << (quint16)3;
-
- for (style = 0 ; style < max ; style++) {
- if (koWmfStyleBrush[ style ] == brush.style()) break;
- }
- if (style == max) {
- // SolidPattern
- style = 0;
- }
- d->mSt << (quint32)7 << (quint16)0x02FC;
- d->mSt << (quint16)style << (quint32)winColor(brush.color()) << (quint16)0;
-
- // select object
- d->mSt << (quint32)4 << (quint16)0x012D << (quint16)3;
-}
-
-
-void WmfWriter::setFont(const QFont &)
-{
-}
-
-
-void WmfWriter::setBackgroundColor(const QColor &c)
-{
- d->mSt << (quint32)5 << (quint16)0x0201 << (quint32)winColor(c);
-}
-
-
-void WmfWriter::setBackgroundMode(Qt::BGMode mode)
-{
- d->mSt << (quint32)4 << (quint16)0x0102;
- if (mode == Qt::TransparentMode)
- d->mSt << (quint16)1;
- else
- d->mSt << (quint16)0;
-}
-
-
-void WmfWriter::setCompositionMode(QPainter::CompositionMode op)
-{
- d->mSt << (quint32)5 << (quint16)0x0104 << (quint32)qtRasterToWin32(op);
-}
-
-
-void WmfWriter::setWindow(int left, int top, int width, int height)
-{
- d->mBBox.setRect(left, top, width, height);
-
- // windowOrg
- d->mSt << (quint32)5 << (quint16)0x020B << (quint16)top << (quint16)left;
-
- // windowExt
- d->mSt << (quint32)5 << (quint16)0x020C << (quint16)height << (quint16)width;
-}
-
-
-void WmfWriter::setClipRegion(const QRegion &)
-{
-
-}
-
-
-void WmfWriter::clipping(bool enable)
-{
- if (!enable) {
- // clipping region == bounding rectangle
- setClipRegion(d->mBBox);
- }
-}
-
-
-void WmfWriter::moveTo(int left, int top)
-{
- d->mSt << (quint32)5 << (quint16)0x0214 << (quint16)top << (quint16)left;
-}
-
-
-void WmfWriter::lineTo(int left, int top)
-{
- d->mSt << (quint32)5 << (quint16)0x0213 << (quint16)top << (quint16)left;
-}
-
-
-void WmfWriter::drawRect(int left, int top, int width, int height)
-{
- QRect rec(left, top, width, height);
-
- d->mSt << (quint32)7 << (quint16)0x041B;
- d->mSt << (quint16)rec.bottom() << (quint16)rec.right() << (quint16)rec.top() << (quint16)rec.left();
-}
-
-
-void WmfWriter::drawRoundRect(int left, int top, int width, int height , int roudw, int roudh)
-{
- int widthCorner, heightCorner;
- QRect rec(left, top, width, height);
-
- // convert percentage (roundw, roudh) in (widthCorner, heightCorner)
- widthCorner = (roudw * width) / 100;
- heightCorner = (roudh * height) / 100;
-
- d->mSt << (quint32)9 << (quint16)0x061C << (quint16)heightCorner << (quint16)widthCorner;
- d->mSt << (quint16)rec.bottom() << (quint16)rec.right() << (quint16)rec.top() << (quint16)rec.left();
-
- d->mMaxRecordSize = qMax(d->mMaxRecordSize, 9);
-}
-
-
-void WmfWriter::drawEllipse(int left, int top, int width, int height)
-{
- QRect rec(left, top, width, height);
-
- d->mSt << (quint32)7 << (quint16)0x0418;
- d->mSt << (quint16)rec.bottom() << (quint16)rec.right() << (quint16)rec.top() << (quint16)rec.left();
-}
-
-
-void WmfWriter::drawArc(int left, int top, int width, int height , int a, int alen)
-{
- int xCenter, yCenter;
- int offXStart, offYStart, offXEnd, offYEnd;
-
- angleToxy(offXStart, offYStart, offXEnd, offYEnd, a, alen);
- xCenter = left + (width / 2);
- yCenter = top + (height / 2);
-
- d->mSt << (quint32)11 << (quint16)0x0817;
- d->mSt << (quint16)(yCenter + offYEnd) << (quint16)(xCenter + offXEnd);
- d->mSt << (quint16)(yCenter + offYStart) << (quint16)(xCenter + offXStart);
- d->mSt << (quint16)(top + height) << (quint16)(left + width);
- d->mSt << (quint16)top << (quint16)left;
-
- d->mMaxRecordSize = qMax(d->mMaxRecordSize, 11);
-}
-
-
-void WmfWriter::drawPie(int left, int top, int width, int height , int a, int alen)
-{
- int xCenter, yCenter;
- int offXStart, offYStart, offXEnd, offYEnd;
-
- angleToxy(offXStart, offYStart, offXEnd, offYEnd, a, alen);
- xCenter = left + (width / 2);
- yCenter = top + (height / 2);
-
- d->mSt << (quint32)11 << (quint16)0x081A;
- d->mSt << (quint16)(yCenter + offYEnd) << (quint16)(xCenter + offXEnd);
- d->mSt << (quint16)(yCenter + offYStart) << (quint16)(xCenter + offXStart);
- d->mSt << (quint16)(top + height) << (quint16)(left + width);
- d->mSt << (quint16)top << (quint16)left;
-
- d->mMaxRecordSize = qMax(d->mMaxRecordSize, 11);
-}
-
-
-void WmfWriter::drawChord(int left, int top, int width, int height , int a, int alen)
-{
- int xCenter, yCenter;
- int offXStart, offYStart, offXEnd, offYEnd;
-
- angleToxy(offXStart, offYStart, offXEnd, offYEnd, a, alen);
- xCenter = left + (width / 2);
- yCenter = top + (height / 2);
-
- d->mSt << (quint32)11 << (quint16)0x0830;
- d->mSt << (quint16)(yCenter + offYEnd) << (quint16)(xCenter + offXEnd);
- d->mSt << (quint16)(yCenter + offYStart) << (quint16)(xCenter + offXStart);
- d->mSt << (quint16)(top + height) << (quint16)(left + width);
- d->mSt << (quint16)top << (quint16)left;
-
- d->mMaxRecordSize = qMax(d->mMaxRecordSize, 11);
-}
-
-
-void WmfWriter::drawPolyline(const QPolygon &pa)
-{
- int size = 4 + (pa.size() * 2);
-
- d->mSt << (quint32)size << (quint16)0x0325 << (quint16)pa.size();
- pointArray(pa);
-
- d->mMaxRecordSize = qMax(d->mMaxRecordSize, size);
-}
-
-
-void WmfWriter::drawPolygon(const QPolygon &pa, bool)
-{
- int size = 4 + (pa.size() * 2);
-
- d->mSt << (quint32)size << (quint16)0x0324 << (quint16)pa.size();
- pointArray(pa);
-
- d->mMaxRecordSize = qMax(d->mMaxRecordSize, size);
-}
-
-
-void WmfWriter::drawPolyPolygon(QList<QPolygon>& listPa, bool)
-{
-
- int sizeArrayPoly = 0;
-
- Q_FOREACH (const QPolygon & pa, listPa) {
- sizeArrayPoly += (pa.size() * 2);
- }
- int size = 4 + listPa.count() + sizeArrayPoly;
- d->mSt << (quint32)size << (quint16)0x0538 << (quint16)listPa.count();
-
- // number of point for each Polygon
- Q_FOREACH (const QPolygon & pa, listPa) {
- d->mSt << (quint16)pa.size();
- }
-
- // list of points
- Q_FOREACH (const QPolygon & pa, listPa) {
- pointArray(pa);
- }
-
- d->mMaxRecordSize = qMax(d->mMaxRecordSize, size);
-}
-
-
-void WmfWriter::drawImage(int , int , const QImage &, int , int , int , int)
-{
- /*
- QImage img;
-
- img = image;
- img.setFormat( "BMP" );
-
- QIODevice io = img.ioDevice();
- io.at( 14 ); // skip the BMP header
- d->mSt << io.readAll();
- */
-}
-
-
-void WmfWriter::drawText(int , int , int , int , int , const QString& , double)
-{
-// d->mSt << (quint32)3 << (quint16)0x0A32;
-}
-
-//-----------------------------------------------------------------------------
-// Utilities and conversion Qt --> Wmf
-
-void WmfWriter::pointArray(const QPolygon &pa)
-{
- int left, top, i, max;
-
- for (i = 0, max = pa.size() ; i < max ; i++) {
- pa.point(i, &left, &top);
- d->mSt << (qint16)left << (qint16)top;
- }
-}
-
-
-quint32 WmfWriter::winColor(const QColor &color)
-{
- quint32 c;
-
- c = (color.red() & 0xFF);
- c += ((color.green() & 0xFF) << 8);
- c += ((color.blue() & 0xFF) << 16);
-
- return c;
-}
-
-
-void WmfWriter::angleToxy(int &xStart, int &yStart, int &xEnd, int &yEnd, int a, int alen)
-{
- double angleStart, angleLength;
-
- angleStart = ((double)a * 3.14166) / 2880;
- angleLength = ((double)alen * 3.14166) / 2880;
-
- xStart = (int)(cos(angleStart) * 50);
- yStart = -(int)(sin(angleStart) * 50);
- xEnd = (int)(cos(angleLength) * 50);
- yEnd = -(int)(sin(angleLength) * 50);
-}
-
-
-quint16 WmfWriter::qtRasterToWin16(QPainter::CompositionMode op) const
-{
- int i;
-
- for (i = 0 ; i < 17 ; i++) {
- if (koWmfOpTab16[ i ] == op) break;
- }
-
- if (i < 17)
- return (quint16)i;
- else
- return (quint16)0;
-}
-
-
-quint32 WmfWriter::qtRasterToWin32(QPainter::CompositionMode op) const
-{
- int i;
-
- for (i = 0 ; i < 15 ; i++) {
- if (koWmfOpTab32[ i ].qtRasterOp == op) break;
- }
-
- if (i < 15)
- return koWmfOpTab32[ i ].winRasterOp;
- else
- return koWmfOpTab32[ 0 ].winRasterOp;
-}
-
-
-}
diff --git a/libs/vectorimage/libwmf/WmfWriter.h b/libs/vectorimage/libwmf/WmfWriter.h
deleted file mode 100644
index fbaa448628..0000000000
--- a/libs/vectorimage/libwmf/WmfWriter.h
+++ /dev/null
@@ -1,151 +0,0 @@
-/* This file is part of the KDE libraries
-
- Copyright (c) 2003 thierry lorthiois (lorthioist@wanadoo.fr)
- Copyright (c) 2011 Inge Wallin (inge@lysator.liu.se)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License version 2 as published by the Free Software Foundation.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-#ifndef _WMFWRITER_H_
-#define _WMFWRITER_H_
-
-#include "kritavectorimage_export.h"
-
-#include <QPainter>
-
-class QPen;
-class QBrush;
-class QColor;
-class QFont;
-class QRegion;
-class QString;
-class QImage;
-
-/**
- Namespace for Windows Metafile (WMF) classes
-*/
-namespace Libwmf
-{
-
-class WmfWriterPrivate;
-
-/**
- * WmfWriter allows to create a windows placeable meta file (WMF).
- * Most of the functions are compatible with QPainter format.
- *
- * sample of utilization:
- * <pre>
- * WmfWriter wmf("/home/test.wmf");
- * wmf.begin();
- * wmf.setWindow(0, 0, 200, 200);
- * wmf.drawRect(10, 20, 50, 120);
- * wmf.end();
- * </pre>
- */
-class KRITAVECTORIMAGE_EXPORT WmfWriter
-{
-public:
- explicit WmfWriter(const QString& fileName);
- virtual ~WmfWriter();
-
-
- // -------------------------------------------------------------------------
- // virtual Painter
- // for a good documentation : check QPainter documentation
- /**
- * Open the file. Returns true on success.
- */
- bool begin();
- /**
- * Close the file. Returns true on success.
- */
- bool end();
- void save();
- void restore();
-
- /**
- * Placeable WMF's use logical coordinates and have a default DPI.
- * This function set the dot per inch ratio.
- * If not specified the dpi is 1024.
- */
- void setDefaultDpi(int dpi);
-
- // Drawing tools
- void setFont(const QFont& f);
- // the width of the pen is in logical coordinate
- void setPen(const QPen& p);
- void setBrush(const QBrush& b);
-
- // Drawing attributes/modes
- void setBackgroundColor(const QColor& r);
- void setBackgroundMode(Qt::BGMode);
- void setCompositionMode(QPainter::CompositionMode);
-
- // Change logical Coordinate
- void setWindow(int left, int top , int width, int height);
-
- // Clipping
- // the 'CoordinateMode' parameter is omitted : always CoordPainter in wmf
- // not yet implemented
- void setClipRegion(const QRegion& r);
- void clipping(bool enable);
-
- // Graphics drawing functions
- void moveTo(int left, int top);
- void lineTo(int left, int top);
- void drawRect(int left, int top, int width, int height);
- void drawRoundRect(int left, int top, int width, int height, int = 25, int = 25);
- void drawEllipse(int left, int top, int width, int height);
- void drawArc(int left, int top, int width, int height, int a, int alen);
- void drawPie(int left, int top, int width, int height, int a, int alen);
- void drawChord(int left, int top, int width, int height, int a, int alen);
- void drawPolyline(const QPolygon& pa);
- void drawPolygon(const QPolygon& pa, bool winding = false);
- // drawPolyPolygon draw the XOR of a list of polygons
- // listPa : list of polygons
- void drawPolyPolygon(QList<QPolygon>& listPa, bool winding = false);
- void drawImage(int left, int top, const QImage &, int sx = 0, int sy = 0, int sw = -1, int sh = -1);
-
- // Text drawing functions
- // rotation = the degrees of rotation in counterclockwise
- // not yet implemented
- void drawText(int x, int y, int w, int h, int flags, const QString &s, double rotation);
-
-private:
- //-----------------------------------------------------------------------------
- // Utilities and conversion Qt --> Wmf
-
- /** Convert QPointArray into qint16 position (result in mSt) */
- void pointArray(const QPolygon& pa);
-
- /** Conversion between windows color and QColor */
- quint32 winColor(const QColor &color);
-
- /** Convert angle a and alen in coordinate (xStart,yStart) and (xEnd, yEnd) */
- void angleToxy(int& xStart, int& yStart, int& xEnd, int& yEnd, int a, int alen);
-
- /** Convert windows rasterOp in QT rasterOp */
- quint16 qtRasterToWin16(QPainter::CompositionMode op) const;
- quint32 qtRasterToWin32(QPainter::CompositionMode op) const;
-
-
-private:
- WmfWriterPrivate * const d;
-
-};
-
-
-}
-
-#endif
diff --git a/libs/vectorimage/libwmf/kwmf.h b/libs/vectorimage/libwmf/kwmf.h
deleted file mode 100644
index 5a8392a6ab..0000000000
--- a/libs/vectorimage/libwmf/kwmf.h
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- Copyright (C) 2000, S.R.Haque <shaheedhaque@hotmail.com>.
- This file is part of the KDE project
-
- 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
- aS32 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.
-
-DESCRIPTION
-
- This is a generic parser for Windows MetaFiles (WMFs). The output is
- a series of callbacks (a.k.a. virtual functions) which the caller can
- override as required.
-
- This is based on code originally written by Stefan Taferner
- (taferner@kde.org).
-*/
-
-#ifndef KWMF_H
-#define KWMF_H
-
-#include <kritavectorimage_export.h>
-
-#include <QLinkedList>
-#include <QList>
-#include <QPoint>
-#include <QSize>
-
-class QDataStream;
-class QPolygon;
-
-class KRITAVECTORIMAGE_EXPORT KWmf
-{
-public:
-
- // Construction.
-
- explicit KWmf(
- unsigned dpi);
- virtual ~KWmf();
-
- // Called to parse the given file.
-
- bool parse(
- const QString &file);
- bool parse(
- QDataStream &stream,
- unsigned size);
-
- class KRITAVECTORIMAGE_EXPORT DrawContext
- {
- public:
- DrawContext();
- bool m_winding;
- unsigned m_brushColor;
- unsigned m_brushStyle;
- unsigned m_penColor;
- unsigned m_penStyle;
- unsigned m_penWidth;
- };
-
- // Should be protected...
-
- void brushSet(
- unsigned color,
- unsigned style);
- void penSet(
- unsigned color,
- unsigned style,
- unsigned width);
-
-protected:
- // Override to get results of parsing.
-
- virtual void gotEllipse(
- const DrawContext &dc,
- QString type,
- QPoint topLeft,
- QSize halfAxes,
- unsigned startAngle,
- unsigned stopAngle) = 0;
- virtual void gotPolygon(
- const DrawContext &dc,
- const QPolygon &points) = 0;
- virtual void gotPolyline(
- const DrawContext &dc,
- const QPolygon &points) = 0;
- virtual void gotRectangle(
- const DrawContext &dc,
- const QPolygon &points) = 0;
-
-private:
- // Debug support.
-
- static const int s_area;
-
- // Use unambiguous names for Microsoft types.
-
- typedef short S16;
- typedef int S32;
- typedef unsigned int U32;
-
- int m_dpi;
- int m_windowOrgX;
- int m_windowOrgY;
- int m_windowFlipX;
- int m_windowFlipY;
- DrawContext m_dc;
- QLinkedList<DrawContext> m_savedDcs;
- QPoint m_lineFrom;
-
- // Windows handle management.
-
- class WinObjHandle
- {
- public:
- virtual ~WinObjHandle() {}
- virtual void apply(KWmf &p) = 0;
- };
-
- class WinObjBrushHandle: public WinObjHandle
- {
- public:
- virtual void apply(KWmf &p);
- unsigned m_color;
- unsigned m_style;
- };
-
- class WinObjPenHandle: public WinObjHandle
- {
- public:
- virtual void apply(KWmf &p);
- unsigned m_color;
- unsigned m_style;
- unsigned m_width;
- };
-
- WinObjPenHandle *handleCreatePen(void);
- WinObjBrushHandle *handleCreateBrush(void);
- QList<WinObjHandle *>m_objectHandles;
-
- unsigned getColor(S32 color);
- QPoint normalisePoint(
- QDataStream &operands);
- QSize normaliseSize(
- QDataStream &operands);
- void genericArc(
- const QString &type,
- QDataStream &operands);
-
- // Opcode handling and painter methods.
-
- void walk(
- U32 words,
- QDataStream &stream);
- void skip(
- U32 words,
- QDataStream &operands);
- void invokeHandler(
- S16 opcode,
- U32 words,
- QDataStream &operands);
- /*
- // draw multiple polygons
- void opPolypolygon(U32 words, QDataStream &operands);
- */
- void opArc(U32 words, QDataStream &operands);
- // create a logical brush
- void opBrushCreateIndirect(U32 words, QDataStream &operands);
- void opEllipse(U32 words, QDataStream &operands);
- // draw line to coord
- void opLineTo(U32 words, QDataStream &operands);
- // move pen to coord
- void opMoveTo(U32 words, QDataStream &operands);
- // do nothing
- void opNoop(U32 words, QDataStream &operands);
- // Free object handle
- void opObjectDelete(U32 words, QDataStream &operands);
- // Activate object handle
- void opObjectSelect(U32 words, QDataStream &operands);
- // create a logical pen
- void opPenCreateIndirect(U32 words, QDataStream &operands);
- void opPie(U32 words, QDataStream &operands);
- // draw polygon
- void opPolygon(U32 words, QDataStream &operands);
- // set polygon fill mode
- void opPolygonSetFillMode(U32 words, QDataStream &operands);
- // draw series of lines
- void opPolyline(U32 words, QDataStream &operands);
- void opRectangle(U32 words, QDataStream &operands);
- // restore drawing context
- void opRestoreDc(U32 words, QDataStream &operands);
- // save drawing context
- void opSaveDc(U32 words, QDataStream &operands);
- // set window origin
- void opWindowSetOrg(U32 words, QDataStream &operands);
- // set window extents
- void opWindowSetExt(U32 words, QDataStream &operands);
- /*
- // set background pen color
- void opsetBkColor(U32 words, QDataStream &operands);
- // set background pen mode
- void opsetBkMode(U32 words, QDataStream &operands);
- // Set raster operation mode
- void opsetRop(U32 words, QDataStream &operands);
- // Escape (enhanced command set)
- void opescape(U32 words, QDataStream &operands);
- */
-};
-
-#endif
diff --git a/libs/vectorimage/libwmf/metafuncs.h b/libs/vectorimage/libwmf/metafuncs.h
deleted file mode 100644
index 1711f4009b..0000000000
--- a/libs/vectorimage/libwmf/metafuncs.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/* This file is part of the Calligra project
- * Copyright (c) 2003 Stefan Taferner <taferner@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License version 2 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#ifndef metafunc_h
-#define metafunc_h
-
-class QWinMetaFile;
-
-static const struct MetaFuncRec {
- const char* name;
- unsigned short func;
- void (QWinMetaFile::*method)(long, short*);
-} metaFuncTab[] = {
- { "SETBKCOLOR", 0x0201, &QWinMetaFile::setBkColor },
- { "SETBKMODE", 0x0102, &QWinMetaFile::setBkMode },
- { "SETMAPMODE", 0x0103, &QWinMetaFile::noop },
- { "SETROP2", 0x0104, &QWinMetaFile::setRop },
- { "SETRELABS", 0x0105, &QWinMetaFile::noop },
- { "SETPOLYFILLMODE", 0x0106, &QWinMetaFile::setPolyFillMode },
- { "SETSTRETCHBLTMODE", 0x0107, &QWinMetaFile::noop },
- { "SETTEXTCHAREXTRA", 0x0108, &QWinMetaFile::noop },
- { "SETTEXTCOLOR", 0x0209, &QWinMetaFile::setTextColor },
- { "SETTEXTJUSTIFICATION", 0x020A, &QWinMetaFile::noop },
- { "SETWINDOWORG", 0x020B, &QWinMetaFile::setWindowOrg },
- { "SETWINDOWEXT", 0x020C, &QWinMetaFile::setWindowExt },
- { "SETVIEWPORTORG", 0x020D, &QWinMetaFile::noop },
- { "SETVIEWPORTEXT", 0x020E, &QWinMetaFile::noop },
- { "OFFSETWINDOWORG", 0x020F, &QWinMetaFile::noop },
- { "SCALEWINDOWEXT", 0x0410, &QWinMetaFile::noop },
- { "OFFSETVIEWPORTORG", 0x0211, &QWinMetaFile::noop },
- { "SCALEVIEWPORTEXT", 0x0412, &QWinMetaFile::noop },
- { "LINETO", 0x0213, &QWinMetaFile::lineTo },
- { "MOVETO", 0x0214, &QWinMetaFile::moveTo },
- { "EXCLUDECLIPRECT", 0x0415, &QWinMetaFile::excludeClipRect },
- { "INTERSECTCLIPRECT", 0x0416, &QWinMetaFile::intersectClipRect },
- { "ARC", 0x0817, &QWinMetaFile::arc },
- { "ELLIPSE", 0x0418, &QWinMetaFile::ellipse },
- { "FLOODFILL", 0x0419, &QWinMetaFile::noop },
- { "PIE", 0x081A, &QWinMetaFile::pie },
- { "RECTANGLE", 0x041B, &QWinMetaFile::rectangle },
- { "ROUNDRECT", 0x061C, &QWinMetaFile::roundRect },
- { "PATBLT", 0x061D, &QWinMetaFile::noop },
- { "SAVEDC", 0x001E, &QWinMetaFile::saveDC },
- { "SETPIXEL", 0x041F, &QWinMetaFile::setPixel },
- { "OFFSETCLIPRGN", 0x0220, &QWinMetaFile::noop },
- { "TEXTOUT", 0x0521, &QWinMetaFile::textOut },
- { "BITBLT", 0x0922, &QWinMetaFile::noop },
- { "STRETCHBLT", 0x0B23, &QWinMetaFile::noop },
- { "POLYGON", 0x0324, &QWinMetaFile::polygon },
- { "POLYLINE", 0x0325, &QWinMetaFile::polyline },
- { "ESCAPE", 0x0626, &QWinMetaFile::noop },
- { "RESTOREDC", 0x0127, &QWinMetaFile::restoreDC },
- { "FILLREGION", 0x0228, &QWinMetaFile::noop },
- { "FRAMEREGION", 0x0429, &QWinMetaFile::noop },
- { "INVERTREGION", 0x012A, &QWinMetaFile::noop },
- { "PAINTREGION", 0x012B, &QWinMetaFile::noop },
- { "SELECTCLIPREGION", 0x012C, &QWinMetaFile::noop },
- { "SELECTOBJECT", 0x012D, &QWinMetaFile::selectObject },
- { "SETTEXTALIGN", 0x012E, &QWinMetaFile::setTextAlign },
- { "CHORD", 0x0830, &QWinMetaFile::chord },
- { "SETMAPPERFLAGS", 0x0231, &QWinMetaFile::noop },
- { "EXTTEXTOUT", 0x0a32, &QWinMetaFile::extTextOut },
- { "SETDIBTODEV", 0x0d33, &QWinMetaFile::noop },
- { "SELECTPALETTE", 0x0234, &QWinMetaFile::noop },
- { "REALIZEPALETTE", 0x0035, &QWinMetaFile::noop },
- { "ANIMATEPALETTE", 0x0436, &QWinMetaFile::noop },
- { "SETPALENTRIES", 0x0037, &QWinMetaFile::noop },
- { "POLYPOLYGON", 0x0538, &QWinMetaFile::polyPolygon },
- { "RESIZEPALETTE", 0x0139, &QWinMetaFile::noop },
- { "DIBBITBLT", 0x0940, &QWinMetaFile::dibBitBlt },
- { "DIBSTRETCHBLT", 0x0b41, &QWinMetaFile::dibStretchBlt },
- { "DIBCREATEPATTERNBRUSH", 0x0142, &QWinMetaFile::dibCreatePatternBrush },
- { "STRETCHDIB", 0x0f43, &QWinMetaFile::stretchDib },
- { "EXTFLOODFILL", 0x0548, &QWinMetaFile::noop },
- { "DELETEOBJECT", 0x01f0, &QWinMetaFile::deleteObject },
- { "CREATEPALETTE", 0x00f7, &QWinMetaFile::createEmptyObject },
- { "CREATEPATTERNBRUSH", 0x01F9, &QWinMetaFile::createEmptyObject },
- { "CREATEPENINDIRECT", 0x02FA, &QWinMetaFile::createPenIndirect },
- { "CREATEFONTINDIRECT", 0x02FB, &QWinMetaFile::createFontIndirect },
- { "CREATEBRUSHINDIRECT", 0x02FC, &QWinMetaFile::createBrushIndirect },
- { "CREATEREGION", 0x06FF, &QWinMetaFile::createEmptyObject },
- { "END", 0, &QWinMetaFile::end },
- // always the latest in the table : in case of unknown function
- { NULL, 0, &QWinMetaFile::noop },
-};
-
-
-#endif /*metafunc_h*/
diff --git a/libs/vectorimage/libwmf/qwmf.h b/libs/vectorimage/libwmf/qwmf.h
deleted file mode 100644
index db739d194b..0000000000
--- a/libs/vectorimage/libwmf/qwmf.h
+++ /dev/null
@@ -1,236 +0,0 @@
-/* Windows Meta File Loader
- *
- * Copyright ( C ) 1998 Stefan Taferner
- * Modified 2002 thierry lorthiois
- *
- * 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
- * MERCHANTABLILITY 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 qwmf_h
-#define qwmf_h
-
-#include <QString>
-#include <QPainter>
-#include <QMatrix>
-#include <QPen>
-#include <QColor>
-#include <QImage>
-#include <QRect>
-
-class QBuffer;
-class QString;
-class WmfCmd;
-class WinObjHandle;
-struct WmfPlaceableHeader;
-
-/**
- * QWinMetaFile is a WMF viewer based on Qt toolkit
- * How to use QWinMetaFile :
- * @code
- * QWinMetaFile wmf;
- * QPicture pic; // or QImage pic;
- * if ( wmf.load( filename )
- * wmf.paint( &pic );
- * @endcode
- */
-
-
-class QWinMetaFile
-{
-public:
- QWinMetaFile();
- virtual ~QWinMetaFile();
-
- /**
- * Load WMF file.
- * @return true on success.
- */
- virtual bool load(const QString &fileName);
- virtual bool load(QBuffer &buffer);
-
- /**
- * Paint metafile to given paint-device using absolute or relative coordinate.
- * - absolute coord. Reset the world transformation Matrix
- * - relative coord. Use the existing world transformation Matrix
- *
- * @return true on success.
- */
- virtual bool paint(QPaintDevice* target, bool absolute = false);
-
- /**
- * @return true if the metafile is placeable.
- */
- bool isPlaceable(void) const {
- return mIsPlaceable;
- }
-
- /**
- * @return true if the metafile is enhanced.
- */
- bool isEnhanced(void) const {
- return mIsEnhanced;
- }
-
- /**
- * @return bounding rectangle
- */
- QRect bbox(void) const {
- return mBBox;
- }
-
-public: // should be protected but cannot
- /* Metafile painter methods */
-
- /** set window origin */
- void setWindowOrg(long num, short* parms);
- /** set window extents */
- void setWindowExt(long num, short* parms);
-
- /****************** Drawing *******************/
- /** draw line to coord */
- void lineTo(long num, short* parms);
- /** move pen to coord */
- void moveTo(long num, short* parms);
- /** draw ellipse */
- void ellipse(long num, short* parms);
- /** draw polygon */
- void polygon(long num, short* parms);
- /** draw a list of polygons */
- void polyPolygon(long num, short* parms);
- /** draw series of lines */
- void polyline(long num, short* parms);
- /** draw a rectangle */
- void rectangle(long num, short* parms);
- /** draw round rectangle */
- void roundRect(long num, short* parms);
- /** draw arc */
- void arc(long num, short* parms);
- /** draw chord */
- void chord(long num, short* parms);
- /** draw pie */
- void pie(long num, short* parms);
- /** set polygon fill mode */
- void setPolyFillMode(long num, short* parms);
- /** set background pen color */
- void setBkColor(long num, short* parms);
- /** set background pen mode */
- void setBkMode(long num, short* parms);
- /** set a pixel */
- void setPixel(long num, short* parms);
- /** Set raster operation mode */
- void setRop(long num, short* parms);
- /** save device context */
- void saveDC(long num, short* parms);
- /** restore device context */
- void restoreDC(long num, short* parms);
- /** clipping region is the intersection of this region and the original region */
- void intersectClipRect(long num, short* parms);
- /** delete a clipping rectangle of the original region */
- void excludeClipRect(long num, short* parms);
-
- /****************** Text *******************/
- /** set text color */
- void setTextColor(long num, short* parms);
- /** set text alignment */
- void setTextAlign(long num, short* parms);
- /** draw text */
- void textOut(long num, short* parms);
- void extTextOut(long num, short* parms);
-
- /****************** Bitmap *******************/
- /** copies a DIB into a dest location */
- void dibBitBlt(long num, short* parms);
- /** stretches a DIB into a dest location */
- void dibStretchBlt(long num, short* parms);
- void stretchDib(long num, short* parms);
- /** create a pattern brush */
- void dibCreatePatternBrush(long num, short* parms);
-
- /****************** Object handle *******************/
- /** Activate object handle */
- void selectObject(long num, short* parms);
- /** Free object handle */
- void deleteObject(long num, short* parms);
- /** create an empty object in the object list */
- void createEmptyObject(long num, short* parms);
- /** create a logical brush */
- void createBrushIndirect(long num, short* parms);
- /** create a logical pen */
- void createPenIndirect(long num, short* parms);
- /** create a logical font */
- void createFontIndirect(long num, short* parms);
-
- /****************** misc *******************/
- /** nothing to do */
- void noop(long , short*);
- /** end of meta file */
- void end(long /*num*/, short* /*parms*/);
- /** Resolution of the image in dots per inch */
- int dpi(void) const {
- return mDpi;
- }
-
-protected:
- /** Calculate header checksum */
- unsigned short calcCheckSum(WmfPlaceableHeader*);
-
- /** Find function in metafunc table by metafile-function.
- Returns index or -1 if not found. */
- virtual int findFunc(unsigned short aFunc) const;
-
- /** Fills given parms into mPoints. */
- QPolygon* pointArray(short num, short* parms);
-
- /** Returns color given by the two parameters */
- QColor color(short* parm);
-
- /** Converts two parameters to long */
- unsigned int toDWord(short* parm);
-
- /** Convert (x1,y1) and (x2, y2) positions in angle and angleLength */
- void xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int& angle, int& aLength);
-
- /** Handle win-object-handles */
- void addHandle(WinObjHandle*);
- void deleteHandle(int);
-
- /** Convert windows rasterOp in QT rasterOp */
- QPainter::CompositionMode winToQtComposition(short parm) const;
- QPainter::CompositionMode winToQtComposition(long parm) const;
-
- /** Converts DIB to BMP */
- bool dibToBmp(QImage& bmp, const char* dib, long size);
-
-protected:
- QPainter mPainter;
- bool mIsPlaceable, mIsEnhanced, mValid;
-
- // coordinate system
- bool mAbsoluteCoord;
- QMatrix mInternalWorldMatrix; // memorisation of WMF matrix transformation
- QRect mHeaderBoundingBox;
- QRect mBBox;
-
- // information shared between Metafile Functions
- QColor mTextColor;
- int mTextAlign, mRotation;
- bool mWinding;
-
- WmfCmd* mFirstCmd;
- WinObjHandle** mObjHandleTab;
- QPolygon mPoints;
- int mDpi;
- QPoint mLastPos;
-};
-
-#endif /*qwmf_h*/
diff --git a/libs/vectorimage/libwmf/wmfstruct.h b/libs/vectorimage/libwmf/wmfstruct.h
deleted file mode 100644
index 1b2c198297..0000000000
--- a/libs/vectorimage/libwmf/wmfstruct.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/* This file is part of the Calligra project
- * Copyright (c) 2003 Stefan Taferner <taferner@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License version 2 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#ifndef wmfstruct_h
-#define wmfstruct_h
-
-typedef short WORD;
-typedef int DWORD;
-typedef qint32 LONG;
-typedef void* _HANDLE;
-
-typedef struct _RECT {
- WORD left;
- WORD top;
- WORD right;
- WORD bottom;
-} RECT;
-
-typedef struct _RECTL {
- LONG left;
- LONG top;
- LONG right;
- LONG bottom;
-} RECTL;
-
-typedef struct _SIZE {
- WORD width;
- WORD height;
-} SIZE;
-
-typedef struct _SIZEL {
- LONG width;
- LONG height;
-} SIZEL;
-
-
-struct WmfEnhMetaHeader {
- DWORD iType; // Record type EMR_HEADER
- DWORD nSize; // Record size in bytes. This may be greater
- // than the sizeof( ENHMETAHEADER ).
- RECTL rclBounds; // Inclusive-inclusive bounds in device units
- RECTL rclFrame; // Inclusive-inclusive Picture Frame of metafile
- // in .01 mm units
- DWORD dSignature; // Signature. Must be ENHMETA_SIGNATURE.
- DWORD nVersion; // Version number
- DWORD nBytes; // Size of the metafile in bytes
- DWORD nRecords; // Number of records in the metafile
- WORD nHandles; // Number of handles in the handle table
- // Handle index zero is reserved.
- WORD sReserved; // Reserved. Must be zero.
- DWORD nDescription; // Number of chars in the unicode description string
- // This is 0 if there is no description string
- DWORD offDescription; // Offset to the metafile description record.
- // This is 0 if there is no description string
- DWORD nPalEntries; // Number of entries in the metafile palette.
- SIZEL szlDevice; // Size of the reference device in pels
- SIZEL szlMillimeters; // Size of the reference device in millimeters
-};
-#define ENHMETA_SIGNATURE 0x464D4520
-
-
-struct WmfMetaHeader {
- WORD mtType;
- WORD mtHeaderSize;
- WORD mtVersion;
- DWORD mtSize;
- WORD mtNoObjects;
- DWORD mtMaxRecord;
- WORD mtNoParameters;
-};
-
-
-struct WmfPlaceableHeader {
- DWORD key;
- WORD hmf;
- RECT bbox;
- WORD inch;
- DWORD reserved;
- WORD checksum;
-};
-#define APMHEADER_KEY 0x9AC6CDD7
-
-
-struct WmfMetaRecord {
- DWORD rdSize; // Record size ( in words ) of the function
- WORD rdFunction; // Record function number
- WORD rdParm[ 1 ]; // WORD array of parameters
-};
-
-
-struct WmfEnhMetaRecord {
- DWORD iType; // Record type EMR_xxx
- DWORD nSize; // Record size in bytes
- DWORD dParm[ 1 ]; // DWORD array of parameters
-};
-
-
-#endif /*wmfstruct_h*/
diff --git a/libs/widgets/CMakeLists.txt b/libs/widgets/CMakeLists.txt
index 2bb25e6b5c..c709f4fcab 100644
--- a/libs/widgets/CMakeLists.txt
+++ b/libs/widgets/CMakeLists.txt
@@ -1,135 +1,134 @@
add_subdirectory( tests )
include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(kritawidgets_LIB_SRCS
- KoGradientEditWidget.cpp
KoVBox.cpp
KoDialog.cpp
KoZoomWidget.cpp
KoTagToolButton.cpp
KoTagChooserWidget.cpp
KoTagFilterWidget.cpp
KoResourceTaggingManager.cpp
KoResourceItemChooserContextMenu.cpp
KoAspectButton.cpp
KoPagePreviewWidget.cpp
KoSliderCombo.cpp
KoColorPopupButton.cpp
KoConfigAuthorPage.cpp
KoUnitDoubleSpinBox.cpp
KoZoomAction.cpp
KoZoomController.cpp
KoZoomInput.cpp
KoZoomHandler.cpp
KoZoomMode.cpp
KoDpi.cpp
KoColorPatch.cpp
KoColorPopupAction.cpp
KoColorSetWidget.cpp
KoColorSlider.cpp
KoTriangleColorSelector.cpp
KoResourcePopupAction.cpp
KoIconToolTip.cpp
KoResourceItemChooser.cpp
KoResourceItemChooserSync.cpp
KoResourceModel.cpp
KoResourceItemDelegate.cpp
KoResourceItemView.cpp
KoResourceTagStore.cpp
KoRuler.cpp
KoItemToolTip.cpp
KoCheckerBoardPainter.cpp
KoResourceServerAdapter.cpp
KoResourceServerProvider.cpp
KoLineStyleSelector.cpp
KoLineStyleItemDelegate.cpp
KoLineStyleModel.cpp
KoResourceFiltering.cpp
KoTitledTabWidget.cpp
KoToolBoxButton.cpp
KoToolBox.cpp
KoToolBoxDocker.cpp
KoToolBoxFactory.cpp
KoToolDocker.cpp
KoPageLayoutWidget.cpp
KoPageLayoutDialog.cpp
KoShadowConfigWidget.cpp
KoMarkerSelector.cpp
KoMarkerModel.cpp
KoMarkerItemDelegate.cpp
KoDocumentInfoDlg.cpp
KoTableView.cpp
WidgetsDebug.cpp
kis_file_name_requester.cpp
KisColorSelectorInterface.cpp
KoAnchorSelectionWidget.cpp
KisGradientSlider.cpp
KisGradientSliderWidget.cpp
kis_color_input.cpp
# classes used by internal color selector
kis_spinbox_color_selector.cpp
KisVisualColorSelector.cpp
KisVisualColorSelectorShape.cpp
KisVisualEllipticalSelectorShape.cpp
KisVisualRectangleSelectorShape.cpp
KisVisualTriangleSelectorShape.cpp
KisScreenColorPickerBase.cpp
KisDlgInternalColorSelector.cpp
KisPaletteModel.cpp
KisPaletteDelegate.cpp
kis_palette_view.cpp
KisPaletteListWidget.cpp
KisPaletteComboBox.cpp
kis_popup_button.cc
kis_color_button.cpp
)
ki18n_wrap_ui( kritawidgets_LIB_SRCS
KoConfigAuthorPage.ui
koDocumentInfoAboutWidget.ui
koDocumentInfoAuthorWidget.ui
wdg_file_name_requester.ui
KoPageLayoutWidget.ui
KoShadowConfigWidget.ui
WdgDlgInternalColorSelector.ui
WdgPaletteListWidget.ui
)
add_library(kritawidgets SHARED ${kritawidgets_LIB_SRCS})
generate_export_header(kritawidgets BASE_NAME kritawidgets)
target_link_libraries(kritawidgets
kritaodf
kritaglobal
kritaflake
kritapigment
kritawidgetutils
Qt5::PrintSupport
KF5::CoreAddons
KF5::ConfigGui
KF5::GuiAddons
KF5::WidgetsAddons
KF5::ConfigCore
KF5::Completion
)
if(X11_FOUND)
target_link_libraries(kritawidgets Qt5::X11Extras ${X11_LIBRARIES})
endif()
set_target_properties(kritawidgets PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritawidgets ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp
index 2c66f15026..3b8c7885ca 100644
--- a/libs/widgets/KisDlgInternalColorSelector.cpp
+++ b/libs/widgets/KisDlgInternalColorSelector.cpp
@@ -1,345 +1,350 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>, (C) 2016
*
* 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 <QList>
#include <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QPointer>
#include <QCompleter>
#include <functional>
#include <KConfigGroup>
#include "KoColorSpaceRegistry.h"
#include <KoColorSet.h>
#include <KisPaletteModel.h>
#include <KisPaletteListWidget.h>
#include <kis_palette_view.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServer.h>
#include "kis_signal_compressor.h"
#include "KoColorDisplayRendererInterface.h"
#include "kis_spinbox_color_selector.h"
#include "KisDlgInternalColorSelector.h"
#include "ui_WdgDlgInternalColorSelector.h"
#include "kis_config_notifier.h"
#include "kis_color_input.h"
#include "kis_icon_utils.h"
#include "KisSqueezedComboBox.h"
std::function<KisScreenColorPickerBase *(QWidget *)> KisDlgInternalColorSelector::s_screenColorPickerFactory = 0;
struct KisDlgInternalColorSelector::Private
{
bool allowUpdates = true;
KoColor currentColor;
KoColor previousColor;
KoColor sRGB = KoColor(KoColorSpaceRegistry::instance()->rgb8());
const KoColorSpace *currentColorSpace;
bool lockUsedCS = false;
bool chooseAlpha = false;
KisSignalCompressor *compressColorChanges;
const KoColorDisplayRendererInterface *displayRenderer;
KisHexColorInput *hexColorInput = 0;
KisPaletteModel *paletteModel = 0;
KisPaletteListWidget *paletteChooser = 0;
KisScreenColorPickerBase *screenColorPicker = 0;
};
KisDlgInternalColorSelector::KisDlgInternalColorSelector(QWidget *parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer)
: QDialog(parent)
, m_d(new Private)
{
setModal(config.modal);
setFocusPolicy(Qt::ClickFocus);
m_ui = new Ui_WdgDlgInternalColorSelector();
m_ui->setupUi(this);
setWindowTitle(caption);
m_d->currentColor = color;
m_d->currentColorSpace = m_d->currentColor.colorSpace();
m_d->displayRenderer = displayRenderer;
m_ui->spinboxselector->slotSetColor(color);
connect(m_ui->spinboxselector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor)));
m_ui->visualSelector->slotSetColor(color);
m_ui->visualSelector->setDisplayRenderer(displayRenderer);
m_ui->visualSelector->setConfig(false, config.modal);
if (config.visualColorSelector) {
connect(m_ui->visualSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_ui->visualSelector, SLOT(configurationChanged()));
} else {
m_ui->visualSelector->hide();
}
m_d->paletteChooser = new KisPaletteListWidget(this);
m_d->paletteModel = new KisPaletteModel(this);
m_ui->bnPaletteChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library"));
m_ui->paletteBox->setPaletteModel(m_d->paletteModel);
m_ui->paletteBox->setDisplayRenderer(displayRenderer);
m_ui->cmbNameList->setCompanionView(m_ui->paletteBox);
connect(m_d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), this, SLOT(slotChangePalette(KoColorSet*)));
connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor)));
// For some bizare reason, the modal dialog doesn't like having the colorset set, so let's not.
if (config.paletteBox) {
//TODO: Add disable signal as well. Might be not necessary...?
KConfigGroup cfg(KSharedConfig::openConfig()->group(""));
QString paletteName = cfg.readEntry("internal_selector_active_color_set", QString());
KoResourceServer<KoColorSet>* rServer = KoResourceServerProvider::instance()->paletteServer();
KoColorSet *savedPal = rServer->resourceByName(paletteName);
if (savedPal) {
this->slotChangePalette(savedPal);
} else {
if (rServer->resources().count()) {
savedPal = rServer->resources().first();
if (savedPal) {
this->slotChangePalette(savedPal);
}
}
}
connect(m_ui->paletteBox, SIGNAL(sigColorSelected(KoColor)), this,
SLOT(slotColorUpdated(KoColor)));
m_ui->bnPaletteChooser->setPopupWidget(m_d->paletteChooser);
} else {
m_ui->paletteBox->setEnabled(false);
m_ui->cmbNameList->setEnabled(false);
m_ui->bnPaletteChooser->setEnabled(false);
}
if (config.prevNextButtons) {
m_ui->currentColor->setColor(m_d->currentColor);
m_ui->currentColor->setDisplayRenderer(displayRenderer);
m_ui->previousColor->setColor(m_d->currentColor);
m_ui->previousColor->setDisplayRenderer(displayRenderer);
connect(m_ui->previousColor, SIGNAL(triggered(KoColorPatch*)), SLOT(slotSetColorFromPatch(KoColorPatch*)));
} else {
m_ui->currentColor->hide();
m_ui->previousColor->hide();
}
if (config.hexInput) {
m_d->sRGB.fromKoColor(m_d->currentColor);
m_d->hexColorInput = new KisHexColorInput(this, &m_d->sRGB);
m_d->hexColorInput->update();
connect(m_d->hexColorInput, SIGNAL(updated()), SLOT(slotSetColorFromHex()));
m_ui->rightPane->addWidget(m_d->hexColorInput);
m_d->hexColorInput->setToolTip(i18n("This is a hexcode input, for webcolors. It can only get colors in the sRGB space."));
}
// KisScreenColorPicker is in the kritaui module, so dependency inversion is used to access it.
m_ui->screenColorPickerWidget->setLayout(new QHBoxLayout(m_ui->screenColorPickerWidget));
if (s_screenColorPickerFactory) {
m_d->screenColorPicker = s_screenColorPickerFactory(m_ui->screenColorPickerWidget);
m_ui->screenColorPickerWidget->layout()->addWidget(m_d->screenColorPicker);
if (config.screenColorPicker) {
connect(m_d->screenColorPicker, SIGNAL(sigNewColorPicked(KoColor)),this, SLOT(slotColorUpdated(KoColor)));
} else {
m_d->screenColorPicker->hide();
}
}
connect(this, SIGNAL(signalForegroundColorChosen(KoColor)), this, SLOT(slotLockSelector()));
m_d->compressColorChanges = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this);
connect(m_d->compressColorChanges, SIGNAL(timeout()), this, SLOT(endUpdateWithNewColor()));
connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()), Qt::UniqueConnection);
connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()), Qt::UniqueConnection);
connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp()));
}
KisDlgInternalColorSelector::~KisDlgInternalColorSelector()
{
delete m_ui;
}
void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor)
{
//if the update did not come from this selector...
if (m_d->allowUpdates || QObject::sender() == this->parent()) {
+ // Enforce palette colors
+ KConfigGroup group(KSharedConfig::openConfig(), "");
+ if (group.readEntry("colorsettings/forcepalettecolors", false)) {
+ newColor = m_ui->paletteBox->closestColor(newColor);
+ }
+
if (m_d->lockUsedCS){
newColor.convertTo(m_d->currentColorSpace);
m_d->currentColor = newColor;
} else {
m_d->currentColor = newColor;
}
updateAllElements(QObject::sender());
}
}
void KisDlgInternalColorSelector::slotSetColorFromPatch(KoColorPatch *patch)
{
slotColorUpdated(patch->color());
}
void KisDlgInternalColorSelector::colorSpaceChanged(const KoColorSpace *cs)
{
if (cs == m_d->currentColorSpace) {
return;
}
m_d->currentColorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile());
m_ui->spinboxselector->slotSetColorSpace(m_d->currentColorSpace);
m_ui->visualSelector->slotsetColorSpace(m_d->currentColorSpace);
}
void KisDlgInternalColorSelector::lockUsedColorSpace(const KoColorSpace *cs)
{
colorSpaceChanged(cs);
m_d->lockUsedCS = true;
}
void KisDlgInternalColorSelector::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
m_d->displayRenderer = displayRenderer;
m_ui->visualSelector->setDisplayRenderer(displayRenderer);
m_ui->currentColor->setDisplayRenderer(displayRenderer);
m_ui->previousColor->setDisplayRenderer(displayRenderer);
m_ui->paletteBox->setDisplayRenderer(displayRenderer);
} else {
m_d->displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
KoColor KisDlgInternalColorSelector::getModalColorDialog(const KoColor color, QWidget* parent, QString caption)
{
Config config = Config();
KisDlgInternalColorSelector dialog(parent, color, config, caption);
dialog.setPreviousColor(color);
dialog.exec();
return dialog.getCurrentColor();
}
KoColor KisDlgInternalColorSelector::getCurrentColor()
{
return m_d->currentColor;
}
void KisDlgInternalColorSelector::chooseAlpha(bool chooseAlpha)
{
m_d->chooseAlpha = chooseAlpha;
}
void KisDlgInternalColorSelector::slotConfigurationChanged()
{
//m_d->canvas->displayColorConverter()->
//slotColorSpaceChanged(m_d->canvas->image()->colorSpace());
}
void KisDlgInternalColorSelector::slotLockSelector()
{
m_d->allowUpdates = false;
}
void KisDlgInternalColorSelector::setPreviousColor(KoColor c)
{
m_d->previousColor = c;
}
void KisDlgInternalColorSelector::reject()
{
slotColorUpdated(m_d->previousColor);
QDialog::reject();
}
void KisDlgInternalColorSelector::updateAllElements(QObject *source)
{
//update everything!!!
if (source != m_ui->spinboxselector) {
m_ui->spinboxselector->slotSetColor(m_d->currentColor);
}
if (source != m_ui->visualSelector) {
m_ui->visualSelector->slotSetColor(m_d->currentColor);
}
if (source != m_d->hexColorInput) {
m_d->sRGB.fromKoColor(m_d->currentColor);
m_d->hexColorInput->update();
}
- KConfigGroup group(KSharedConfig::openConfig(), "");
- if (source != m_ui->paletteBox && group.readEntry("colorsettings/forcepalettecolors", false)) {
+ if (source != m_ui->paletteBox) {
m_ui->paletteBox->selectClosestColor(m_d->currentColor);
}
m_ui->previousColor->setColor(m_d->previousColor);
m_ui->currentColor->setColor(m_d->currentColor);
if (source != this->parent()) {
emit(signalForegroundColorChosen(m_d->currentColor));
m_d->compressColorChanges->start();
}
if (m_d->screenColorPicker) {
m_d->screenColorPicker->updateIcons();
}
}
void KisDlgInternalColorSelector::endUpdateWithNewColor()
{
m_d->allowUpdates = true;
}
void KisDlgInternalColorSelector::focusInEvent(QFocusEvent *)
{
//setPreviousColor();
}
void KisDlgInternalColorSelector::slotFinishUp()
{
setPreviousColor(m_d->currentColor);
KConfigGroup cfg(KSharedConfig::openConfig()->group(""));
if (m_d->paletteModel) {
if (m_d->paletteModel->colorSet()) {
cfg.writeEntry("internal_selector_active_color_set", m_d->paletteModel->colorSet()->name());
}
}
}
void KisDlgInternalColorSelector::slotSetColorFromHex()
{
slotColorUpdated(m_d->sRGB);
}
void KisDlgInternalColorSelector::slotChangePalette(KoColorSet *set)
{
if (!set) {
return;
}
m_d->paletteModel->setPalette(set);
}
void KisDlgInternalColorSelector::showEvent(QShowEvent *event)
{
updateAllElements(0);
QDialog::showEvent(event);
}
diff --git a/libs/widgets/KisDlgInternalColorSelector.h b/libs/widgets/KisDlgInternalColorSelector.h
index 558095bff5..b0b63b4882 100644
--- a/libs/widgets/KisDlgInternalColorSelector.h
+++ b/libs/widgets/KisDlgInternalColorSelector.h
@@ -1,195 +1,195 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>, (C) 2016
*
* 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 KISDLGINTERNALCOLORSELECTOR_H
#define KISDLGINTERNALCOLORSELECTOR_H
#include "kritawidgets_export.h"
#include "KoColor.h"
#include "KoColorSpace.h"
#include "KoColorDisplayRendererInterface.h"
#include "KoColorSet.h"
#include <QScopedPointer>
#include "KisScreenColorPickerBase.h"
#include "ui_WdgDlgInternalColorSelector.h"
/**
* @brief The KisInternalColorSelector class
*
* A non-modal color selector dialog that is not a plugin and can thus be used for filters.
*/
class KRITAWIDGETS_EXPORT KisDlgInternalColorSelector : public QDialog
{
Q_OBJECT
static std::function<KisScreenColorPickerBase *(QWidget *)> s_screenColorPickerFactory;
public:
static void setScreenColorPickerFactory(std::function<KisScreenColorPickerBase *(QWidget *)> f) {
s_screenColorPickerFactory = f;
}
struct Config
{
Config() :
modal(true),
visualColorSelector(true),
paletteBox(true),
screenColorPicker(true),
prevNextButtons(true),
hexInput(true),
useAlpha(false){}
bool modal;
bool visualColorSelector;
bool paletteBox;
bool screenColorPicker;
bool prevNextButtons;
bool hexInput;
bool useAlpha;
};
KisDlgInternalColorSelector(QWidget* parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance());
~KisDlgInternalColorSelector() override;
/**
* @brief slotColorSpaceChanged
* Color space has changed, use this dialog to change the colorspace.
*/
void colorSpaceChanged(const KoColorSpace *cs);
/**
* @brief lockUsedColorSpace
* Lock the used colorspace of this selector.
* @param cs
*/
void lockUsedColorSpace(const KoColorSpace *cs);
/**
* @brief setDisplayRenderer
* Set the display renderer. This is necessary for HDR color manage support.
* @param displayRenderer
*/
void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer);
/**
* @brief getModalColorDialog
* Execute this dialog modally. The function returns
* the KoColor you want.
* @param color - The current color. Make sure this is in the color space you want your
* end color to be in.
* @param parent parent widget.
* @param caption the dialog caption.
*/
- static KoColor getModalColorDialog(const KoColor color, QWidget* parent = Q_NULLPTR, QString caption = QString());
+ static KoColor getModalColorDialog(const KoColor color, QWidget* parent = 0, QString caption = QString());
/**
* @brief getCurrentColor
* @return gives currently active color;
*/
KoColor getCurrentColor();
void chooseAlpha(bool chooseAlpha);
Q_SIGNALS:
/**
* @brief signalForegroundColorChosen
* The most important signal. This will sent out when a color has been picked from the selector.
* There will be a small delay to make sure that the selector causes too many updates.
*
* Do not connect this to slotColorUpdated.
* @param color The new color chosen
*/
void signalForegroundColorChosen(KoColor color);
public Q_SLOTS:
/**
* @brief slotColorUpdated
* Very important slot. Is connected to krita's resources to make sure it has
* the currently active color. It's very important that this function is able to understand
* when the signal came from itself.
* @param newColor This is the new color.
*/
void slotColorUpdated(KoColor newColor);
/**
* @brief slotSetColorFromPatch
* update current color from kocolorpatch.
* @param patch
*/
void slotSetColorFromPatch(KoColorPatch* patch);
/**
* @brief setPreviousColor
* set the previous color.
*/
void setPreviousColor(KoColor c);
void reject() override;
private Q_SLOTS:
/**
* @brief slotLockSelector
* This slot will prevent the color from being updated.
*/
void slotLockSelector();
/**
* @brief slotConfigurationChanged
* Wrapper slot for changes to the colorspace.
*/
void slotConfigurationChanged();
void endUpdateWithNewColor();
/**
* @brief slotFinishUp
* This is called when the selector is closed, for saving the current palette.
*/
void slotFinishUp();
/**
* @brief slotSetColorFromHex
* Update from the hex color input.
*/
void slotSetColorFromHex();
void slotChangePalette(KoColorSet *set);
protected:
void showEvent(QShowEvent *event) override;
private:
void focusInEvent(QFocusEvent *) override;
/**
* @brief updateAllElements
* Updates each widget with the new element, and if it's responsible for the update sents
* a signal out that there's a new color.
*/
void updateAllElements(QObject *source);
private:
Ui_WdgDlgInternalColorSelector *m_ui;
struct Private; //The private struct
const QScopedPointer<Private> m_d; //the private pointer
};
#endif // KISDLGINTERNALCOLORSELECTOR_H
diff --git a/libs/widgets/KisPaletteComboBox.cpp b/libs/widgets/KisPaletteComboBox.cpp
index 56f51ad8f2..e0cf48d691 100644
--- a/libs/widgets/KisPaletteComboBox.cpp
+++ b/libs/widgets/KisPaletteComboBox.cpp
@@ -1,162 +1,160 @@
/*
* Copyright (c) 2018 Michael Zhou <simeirxh@gmail.com>
*
* 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.
*/
// Qt
#include <QPainter>
#include <QPen>
#include <QVector>
#include <QCompleter>
// STL
#include <algorithm>
#include "kis_palette_view.h"
#include "KisPaletteComboBox.h"
KisPaletteComboBox::KisPaletteComboBox(QWidget *parent)
: KisSqueezedComboBox(parent)
- , m_model(Q_NULLPTR)
+ , m_model(0)
{
setEditable(true);
setInsertPolicy(NoInsert);
completer()->setCompletionMode(QCompleter::PopupCompletion);
completer()->setCaseSensitivity(Qt::CaseInsensitive);
completer()->setFilterMode(Qt::MatchContains);
connect(this, SIGNAL(currentIndexChanged(int)), SLOT(slotIndexUpdated(int)));
}
KisPaletteComboBox::~KisPaletteComboBox()
{ }
void KisPaletteComboBox::setPaletteModel(const KisPaletteModel *paletteModel)
{
if (!m_model.isNull()) {
m_model->disconnect(this);
}
m_model = paletteModel;
if (m_model.isNull()) { return; }
slotPaletteChanged();
connect(m_model, SIGNAL(sigPaletteChanged()),
SLOT(slotPaletteChanged()));
connect(m_model, SIGNAL(sigPaletteModified()),
SLOT(slotPaletteChanged()));
}
void KisPaletteComboBox::setCompanionView(KisPaletteView *view)
{
if (!m_view.isNull()) {
m_view->disconnect(this);
disconnect(m_view.data());
}
m_view = view;
setPaletteModel(view->paletteModel());
connect(view, SIGNAL(sigIndexSelected(QModelIndex)),
SLOT(slotSwatchSelected(QModelIndex)));
- connect(this, SIGNAL(sigColorSelected(KoColor)),
- view, SLOT(slotSelectColor(KoColor)));
}
void KisPaletteComboBox::slotPaletteChanged()
{
clear();
m_groupMapMap.clear();
m_idxSwatchMap.clear();
if (QPointer<KoColorSet>(m_model->colorSet()).isNull()) { return; }
for (const QString &groupName : m_model->colorSet()->getGroupNames()) {
QVector<SwatchInfoType> infoList;
PosIdxMapType posIdxMap;
const KisSwatchGroup *group = m_model->colorSet()->getGroup(groupName);
for (const SwatchInfoType &info : group->infoList()) {
infoList.append(info);
}
std::sort(infoList.begin(), infoList.end(), swatchInfoLess);
for (const SwatchInfoType &info : infoList) {
const KisSwatch &swatch = info.swatch;
QString name = swatch.name();
if (!swatch.id().isEmpty()){
name = swatch.id() + " - " + swatch.name();
}
addSqueezedItem(QIcon(createColorSquare(swatch)), name);
posIdxMap[SwatchPosType(info.column, info.row)] = count() - 1;
m_idxSwatchMap.push_back(swatch);
}
m_groupMapMap[group->name()] = posIdxMap;
}
if (m_view.isNull()) {
setCurrentIndex(0);
}
QModelIndex idx = m_view->currentIndex();
if (!idx.isValid()) { return; }
if (qvariant_cast<bool>(idx.data(KisPaletteModel::IsGroupNameRole))) { return; }
if (!qvariant_cast<bool>(idx.data(KisPaletteModel::CheckSlotRole))) { return; }
blockSignals(true); // this is a passive selection; this shouldn't make others change
slotSwatchSelected(idx);
blockSignals(false);
}
bool KisPaletteComboBox::swatchInfoLess(const SwatchInfoType &first, const SwatchInfoType &second)
{
return first.swatch.name() < second.swatch.name();
}
QPixmap KisPaletteComboBox::createColorSquare(const KisSwatch &swatch) const
{
QPixmap colorSquare(32, 32);
if (swatch.spotColor()) {
QImage img = QImage(32, 32, QImage::Format_ARGB32);
QPainter circlePainter;
img.fill(Qt::transparent);
circlePainter.begin(&img);
QBrush brush = QBrush(Qt::SolidPattern);
brush.setColor(swatch.color().toQColor());
circlePainter.setBrush(brush);
QPen pen = circlePainter.pen();
pen.setColor(Qt::transparent);
pen.setWidth(0);
circlePainter.setPen(pen);
circlePainter.drawEllipse(0, 0, 32, 32);
circlePainter.end();
colorSquare = QPixmap::fromImage(img);
} else {
colorSquare.fill(swatch.color().toQColor());
}
return colorSquare;
}
void KisPaletteComboBox::slotSwatchSelected(const QModelIndex &index)
{
if (!qvariant_cast<bool>(index.data(KisPaletteModel::CheckSlotRole))) {
return;
}
if (qvariant_cast<bool>(index.data(KisPaletteModel::IsGroupNameRole))) {
return;
}
QString gName = qvariant_cast<QString>(index.data(KisPaletteModel::GroupNameRole));
int rowInGroup = qvariant_cast<int>(index.data(KisPaletteModel::RowInGroupRole));
setCurrentIndex(m_groupMapMap[gName][SwatchPosType(index.column(), rowInGroup)]);
}
void KisPaletteComboBox::slotIndexUpdated(int idx)
{
if (idx >= 0 && idx < m_idxSwatchMap.size()) {
emit sigColorSelected(m_idxSwatchMap[idx].color());
}
}
diff --git a/libs/widgets/KisPaletteComboBox.h b/libs/widgets/KisPaletteComboBox.h
index f6d0d54d61..91b80a4435 100644
--- a/libs/widgets/KisPaletteComboBox.h
+++ b/libs/widgets/KisPaletteComboBox.h
@@ -1,75 +1,75 @@
/*
* Copyright (c) 2018 Michael Zhou <simeirxh@gmail.com>
*
* 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 KISPALETTECOMBOBOX_H
#define KISPALETTECOMBOBOX_H
#include "kritawidgets_export.h"
#include <QComboBox>
#include <QPointer>
#include <QScopedPointer>
#include <QPixmap>
#include <QPair>
#include <QHash>
#include <KisSqueezedComboBox.h>
#include <KisPaletteModel.h>
class KisPaletteView;
/**
* @brief The KisPaletteComboBox class
* A combobox used with KisPaletteView
*
*/
class KRITAWIDGETS_EXPORT KisPaletteComboBox : public KisSqueezedComboBox
{
Q_OBJECT
private /* typedef */:
typedef KisSwatchGroup::SwatchInfo SwatchInfoType;
typedef QPair<int, int> SwatchPosType; // first is column #, second is row #
typedef QHash<SwatchPosType, int> PosIdxMapType;
public:
- explicit KisPaletteComboBox(QWidget *parent = Q_NULLPTR);
+ explicit KisPaletteComboBox(QWidget *parent = 0);
~KisPaletteComboBox();
Q_SIGNALS:
void sigColorSelected(const KoColor &);
public /* methods */:
void setCompanionView(KisPaletteView *);
private Q_SLOTS:
void setPaletteModel(const KisPaletteModel *);
void slotPaletteChanged();
void slotSwatchSelected(const QModelIndex &index);
void slotIndexUpdated(int);
private /* methods */:
QPixmap createColorSquare(const KisSwatch &swatch) const;
static bool swatchInfoLess(const SwatchInfoType &, const SwatchInfoType &);
private /* member variables */:
QPointer<const KisPaletteModel> m_model;
QPointer<KisPaletteView> m_view;
QHash<QString, PosIdxMapType> m_groupMapMap;
QVector<KisSwatch> m_idxSwatchMap;
};
#endif // KISPALETTECOMBOBOX_H
diff --git a/libs/widgets/KisPaletteModel.cpp b/libs/widgets/KisPaletteModel.cpp
index 0174774932..f1c4e71f8b 100644
--- a/libs/widgets/KisPaletteModel.cpp
+++ b/libs/widgets/KisPaletteModel.cpp
@@ -1,508 +1,508 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2018 Michael Zhou <simeirxh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisPaletteModel.h"
#include <QBrush>
#include <QDomDocument>
#include <QDomElement>
#include <QMimeData>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorModelStandardIds.h>
#include <resources/KoColorSet.h>
#include <KoColorDisplayRendererInterface.h>
KisPaletteModel::KisPaletteModel(QObject* parent)
: QAbstractTableModel(parent)
- , m_colorSet(Q_NULLPTR)
+ , m_colorSet(0)
, m_displayRenderer(KoDumbColorDisplayRenderer::instance())
{
connect(this, SIGNAL(sigPaletteModified()), SLOT(slotPaletteModified()));
}
KisPaletteModel::~KisPaletteModel()
{
}
QVariant KisPaletteModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) { return QVariant(); }
bool groupNameRow = m_rowGroupNameMap.contains(index.row());
if (role == IsGroupNameRole) {
return groupNameRow;
}
if (groupNameRow) {
return dataForGroupNameRow(index, role);
} else {
return dataForSwatch(index, role);
}
}
int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const
{
if (!m_colorSet)
return 0;
return m_colorSet->rowCount() // count of color rows
+ m_rowGroupNameMap.size() // rows for names
- 1; // global doesn't have a name
}
int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const
{
if (m_colorSet && m_colorSet->columnCount() > 0) {
return m_colorSet->columnCount();
}
if (!m_colorSet) {
return 0;
}
return 16;
}
Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const
{
if (index.isValid()) {
return Qt::ItemIsSelectable |
Qt::ItemIsEnabled |
Qt::ItemIsUserCheckable |
Qt::ItemIsDragEnabled |
Qt::ItemIsDropEnabled;
}
return Qt::ItemIsDropEnabled;
}
QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(parent);
Q_ASSERT(m_colorSet);
int groupNameRow = groupNameRowForRow(row);
KisSwatchGroup *group = m_colorSet->getGroup(m_rowGroupNameMap[groupNameRow]);
Q_ASSERT(group);
return createIndex(row, column, group);
}
void KisPaletteModel::resetGroupNameRows()
{
m_rowGroupNameMap.clear();
int row = -1;
for (const QString &groupName : m_colorSet->getGroupNames()) {
m_rowGroupNameMap[row] = groupName;
row += m_colorSet->getGroup(groupName)->rowCount();
row += 1; // row for group name
}
}
void KisPaletteModel::setPalette(KoColorSet* palette)
{
beginResetModel();
m_colorSet = palette;
if (palette) {
resetGroupNameRows();
}
endResetModel();
emit sigPaletteChanged();
}
KoColorSet* KisPaletteModel::colorSet() const
{
return m_colorSet;
}
int KisPaletteModel::rowNumberInGroup(int rowInModel) const
{
if (m_rowGroupNameMap.contains(rowInModel)) {
return -1;
}
QList<int> rowNumberList = m_rowGroupNameMap.keys();
for (auto it = rowNumberList.rbegin(); it != rowNumberList.rend(); it++) {
if (*it < rowInModel) {
return rowInModel - *it - 1;
}
}
return rowInModel;
}
int KisPaletteModel::groupNameRowForName(const QString &groupName)
{
for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) {
if (it.value() == groupName) {
return it.key();
}
}
return -1;
}
bool KisPaletteModel::addEntry(const KisSwatch &entry, const QString &groupName)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1);
m_colorSet->add(entry, groupName);
endInsertRows();
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
emit sigPaletteModified();
return true;
}
bool KisPaletteModel::removeEntry(const QModelIndex &index, bool keepColors)
{
if (!qvariant_cast<bool>(data(index, IsGroupNameRole))) {
static_cast<KisSwatchGroup*>(index.internalPointer())->removeEntry(index.column(),
rowNumberInGroup(index.row()));
emit dataChanged(index, index);
} else {
int groupNameRow = groupNameRowForRow(index.row());
QString groupName = m_rowGroupNameMap[groupNameRow];
removeGroup(groupName, keepColors);
}
emit sigPaletteModified();
return true;
}
void KisPaletteModel::removeGroup(const QString &groupName, bool keepColors)
{
int removeStart = groupNameRowForName(groupName);
int removedRowCount = m_colorSet->getGroup(groupName)->rowCount();
int insertStart = m_colorSet->getGlobalGroup()->rowCount();
beginRemoveRows(QModelIndex(),
removeStart,
removeStart + removedRowCount);
m_colorSet->removeGroup(groupName, keepColors);
resetGroupNameRows();
endRemoveRows();
beginInsertRows(QModelIndex(),
insertStart, m_colorSet->getGlobalGroup()->rowCount());
endInsertRows();
emit sigPaletteModified();
}
bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(column);
if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) {
return false;
}
if (action == Qt::IgnoreAction) {
return false;
}
QModelIndex finalIndex = parent;
if (!finalIndex.isValid()) { return false; }
if (data->hasFormat("krita/x-colorsetgroup")) {
// dragging group not supported for now
QByteArray encodedData = data->data("krita/x-colorsetgroup");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
QString groupNameDroppedOn = qvariant_cast<QString>(finalIndex.data(GroupNameRole));
if (groupNameDroppedOn == KoColorSet::GLOBAL_GROUP_NAME) {
return false;
}
QString groupNameDragged;
stream >> groupNameDragged;
KisSwatchGroup *groupDragged = m_colorSet->getGroup(groupNameDragged);
int start = groupNameRowForName(groupNameDragged);
int end = start + groupDragged->rowCount();
if (!beginMoveRows(QModelIndex(), start, end, QModelIndex(), groupNameRowForName(groupNameDroppedOn))) {
return false;
}
m_colorSet->moveGroup(groupNameDragged, groupNameDroppedOn);
resetGroupNameRows();
endMoveRows();
emit sigPaletteModified();
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
return true;
}
if (qvariant_cast<bool>(finalIndex.data(KisPaletteModel::IsGroupNameRole))) {
return true;
}
QByteArray encodedData = data->data("krita/x-colorsetentry");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
KisSwatch entry;
QString name, id;
bool spotColor;
QString oldGroupName;
int oriRow;
int oriColumn;
QString colorXml;
stream >> name >> id >> spotColor
>> oriRow >> oriColumn
>> oldGroupName
>> colorXml;
entry.setName(name);
entry.setId(id);
entry.setSpotColor(spotColor);
QDomDocument doc;
doc.setContent(colorXml);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement();
if (!c.isNull()) {
QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id());
entry.setColor(KoColor::fromXML(c, colorDepthId));
}
if (action == Qt::MoveAction){
KisSwatchGroup *g = m_colorSet->getGroup(oldGroupName);
if (g) {
if (qvariant_cast<bool>(finalIndex.data(KisPaletteModel::CheckSlotRole))) {
g->setEntry(getEntry(finalIndex), oriColumn, oriRow);
} else {
g->removeEntry(oriColumn, oriRow);
}
}
setEntry(entry, finalIndex);
emit sigPaletteModified();
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
}
return true;
}
QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QModelIndex index = indexes.last();
if (index.isValid() && qvariant_cast<bool>(index.data(CheckSlotRole))) {
QString mimeTypeName = "krita/x-colorsetentry";
if (qvariant_cast<bool>(index.data(IsGroupNameRole))==false) {
KisSwatch entry = getEntry(index);
QDomDocument doc;
QDomElement root = doc.createElement("Color");
root.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id());
doc.appendChild(root);
entry.color().toXML(doc, root);
stream << entry.name() << entry.id() << entry.spotColor()
<< rowNumberInGroup(index.row()) << index.column()
<< qvariant_cast<QString>(index.data(GroupNameRole))
<< doc.toString();
} else {
mimeTypeName = "krita/x-colorsetgroup";
QString groupName = qvariant_cast<QString>(index.data(GroupNameRole));
stream << groupName;
}
mimeData->setData(mimeTypeName, encodedData);
}
return mimeData;
}
QStringList KisPaletteModel::mimeTypes() const
{
return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup";
}
Qt::DropActions KisPaletteModel::supportedDropActions() const
{
return Qt::MoveAction;
}
void KisPaletteModel::setEntry(const KisSwatch &entry,
const QModelIndex &index)
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(index.internalPointer());
Q_ASSERT(group);
group->setEntry(entry, index.column(), rowNumberInGroup(index.row()));
emit sigPaletteModified();
emit dataChanged(index, index);
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
bool KisPaletteModel::renameGroup(const QString &groupName, const QString &newName)
{
beginResetModel();
bool success = m_colorSet->changeGroupName(groupName, newName);
for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) {
if (it.value() == groupName) {
m_rowGroupNameMap[it.key()] = newName;
break;
}
}
endResetModel();
emit sigPaletteModified();
return success;
}
void KisPaletteModel::addGroup(const KisSwatchGroup &group)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount() + group.rowCount());
m_colorSet->addGroup(group.name());
*m_colorSet->getGroup(group.name()) = group;
endInsertColumns();
emit sigPaletteModified();
}
void KisPaletteModel::setRowNumber(const QString &groupName, int rowCount)
{
beginResetModel();
KisSwatchGroup *g = m_colorSet->getGroup(groupName);
if (g) {
g->setRowCount(rowCount);
}
endResetModel();
}
void KisPaletteModel::clear()
{
beginResetModel();
m_colorSet->clear();
endResetModel();
}
QVariant KisPaletteModel::dataForGroupNameRow(const QModelIndex &idx, int role) const
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(idx.internalPointer());
Q_ASSERT(group);
QString groupName = group->name();
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return groupName;
}
case GroupNameRole: {
return groupName;
}
case CheckSlotRole: {
return true;
}
case RowInGroupRole: {
return -1;
}
default: {
return QVariant();
}
}
}
QVariant KisPaletteModel::dataForSwatch(const QModelIndex &idx, int role) const
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(idx.internalPointer());
Q_ASSERT(group);
int rowInGroup = rowNumberInGroup(idx.row());
bool entryPresent = group->checkEntry(idx.column(), rowInGroup);
KisSwatch entry;
if (entryPresent) {
entry = group->getEntry(idx.column(), rowInGroup);
}
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return entryPresent ? entry.name() : i18n("Empty slot");
}
case Qt::BackgroundRole: {
QColor color(0, 0, 0, 0);
if (entryPresent) {
color = m_displayRenderer->toQColor(entry.color());
}
return QBrush(color);
}
case GroupNameRole: {
return group->name();
}
case CheckSlotRole: {
return entryPresent;
}
case RowInGroupRole: {
return rowInGroup;
}
default: {
return QVariant();
}
}
}
void KisPaletteModel::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
if (m_displayRenderer) {
disconnect(m_displayRenderer, 0, this, 0);
}
m_displayRenderer = displayRenderer;
connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()),
SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection);
} else {
m_displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
void KisPaletteModel::slotDisplayConfigurationChanged()
{
beginResetModel();
endResetModel();
}
void KisPaletteModel::slotPaletteModified() {
m_colorSet->setPaletteType(KoColorSet::KPL);
}
QModelIndex KisPaletteModel::indexForClosest(const KoColor &compare)
{
KisSwatchGroup::SwatchInfo info = colorSet()->getClosestColorInfo(compare);
return createIndex(indexRowForInfo(info), info.column, colorSet()->getGroup(info.group));
}
int KisPaletteModel::indexRowForInfo(const KisSwatchGroup::SwatchInfo &info)
{
for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) {
if (it.value() == info.group) {
return it.key() + info.row + 1;
}
}
return info.row;
}
KisSwatch KisPaletteModel::getEntry(const QModelIndex &index) const
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(index.internalPointer());
if (!group || !group->checkEntry(index.column(), rowNumberInGroup(index.row()))) {
return KisSwatch();
}
return group->getEntry(index.column(), rowNumberInGroup(index.row()));
}
int KisPaletteModel::groupNameRowForRow(int rowInModel) const
{
return rowInModel - rowNumberInGroup(rowInModel) - 1;
}
diff --git a/libs/widgets/KisPaletteModel.h b/libs/widgets/KisPaletteModel.h
index 5f5c113dc9..46119a050d 100644
--- a/libs/widgets/KisPaletteModel.h
+++ b/libs/widgets/KisPaletteModel.h
@@ -1,180 +1,180 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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_PALETTEMODEL_H
#define KIS_PALETTEMODEL_H
#include <QPointer>
#include <QModelIndex>
#include <QMap>
#include <KoColorDisplayRendererInterface.h>
#include "kritawidgets_export.h"
#include <KoColorSet.h>
#include <QScopedPointer>
class KoColorSet;
class KisPaletteView;
/**
* @brief The KisPaletteModel class
* This, together with KisPaletteView and KisPaletteDelegate forms a mvc way to access kocolorsets.
* A display renderer is given to this model to convert KoColor to QColor when
* colors are requested
*/
class KRITAWIDGETS_EXPORT KisPaletteModel : public QAbstractTableModel
{
Q_OBJECT
public:
- explicit KisPaletteModel(QObject* parent = Q_NULLPTR);
+ explicit KisPaletteModel(QObject* parent = 0);
~KisPaletteModel() override;
enum AdditionalRoles {
IsGroupNameRole = Qt::UserRole + 1,
CheckSlotRole,
GroupNameRole,
RowInGroupRole
};
public /* overridden methods */: // QAbstractTableModel
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
/**
* @brief index
* @param row
* @param column
* @param parent
* @return the index of for the data at row, column
* if the data is a color entry, the internal pointer points to the group
* the entry belongs to, and the row and column are row number and column
* number inside the group.
* if the data is a group, the row number and group number is Q_INFINIFY,
* and the internal pointer also points to the group
*/
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
/**
* @brief dropMimeData
* This is an overridden function that handles dropped mimedata.
* right now only colorsetentries and colorsetgroups are handled.
* @return
*/
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) override;
/**
* @brief mimeData
* gives the mimedata for a kocolorsetentry or a kocolorsetgroup.
* @param indexes
* @return the mimedata for the given indices
*/
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList mimeTypes() const override;
Qt::DropActions supportedDropActions() const override;
/**
* @brief setData
* setData is not used as KoColor is not a QVariant
* use setEntry, addEntry and removeEntry instead
*/
// TODO Used QVariant::setValue and QVariant.value<KoColor> to implement this
// bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Q_SIGNALS:
/**
* @brief sigPaletteModified
* emitted when palette associated with the model is modified
*/
void sigPaletteModified();
/**
* @brief sigPaletteChanged
* emitted when the palette associated with the model is made another one
*/
void sigPaletteChanged();
public /* methods */:
/**
* @brief addEntry
* proper function to handle adding entries.
* @return whether successful.
*/
bool addEntry(const KisSwatch &entry,
const QString &groupName = KoColorSet::GLOBAL_GROUP_NAME);
void setEntry(const KisSwatch &entry, const QModelIndex &index);
/**
* @brief removeEntry
* proper function to remove the colorsetentry at the given index.
* The consolidates both removeentry and removegroup.
* @param index the given index
* @param keepColors This bool determines whether, when deleting a group,
* the colors should be added to the default group. This is usually desirable,
* so hence the default is true.
* @return if successful
*/
bool removeEntry(const QModelIndex &index, bool keepColors=true);
void removeGroup(const QString &groupName, bool keepColors);
bool renameGroup(const QString &groupName, const QString &newName);
void addGroup(const KisSwatchGroup &group);
void setRowNumber(const QString &groupName, int rowCount);
void clear();
KisSwatch getEntry(const QModelIndex &index) const;
void setPalette(KoColorSet* colorSet);
KoColorSet* colorSet() const;
QModelIndex indexForClosest(const KoColor &compare);
int indexRowForInfo(const KisSwatchGroup::SwatchInfo &info);
public Q_SLOTS:
private Q_SLOTS:
void slotDisplayConfigurationChanged();
void slotPaletteModified();
private /* methods */:
QVariant dataForGroupNameRow(const QModelIndex &idx, int role) const;
QVariant dataForSwatch(const QModelIndex &idx, int role) const;
int rowNumberInGroup(int rowInModel) const;
int groupNameRowForRow(int rowInModel) const;
int groupNameRowForName(const QString &groupName);
void resetGroupNameRows();
/**
* Installs a display renderer object for a palette that will
* convert the KoColor to the displayable QColor. Default is the
* dumb renderer.
*/
void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer);
private /* member variables */:
QPointer<KoColorSet> m_colorSet;
QPointer<const KoColorDisplayRendererInterface> m_displayRenderer;
QMap<int, QString> m_rowGroupNameMap;
friend class KisPaletteView;
};
#endif
diff --git a/libs/widgets/KoGradientEditWidget.cpp b/libs/widgets/KoGradientEditWidget.cpp
deleted file mode 100644
index 31fcaba26e..0000000000
--- a/libs/widgets/KoGradientEditWidget.cpp
+++ /dev/null
@@ -1,409 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2001-2002 Beno�t Vautrin <benoit.vautrin@free.fr>
- Copyright (C) 2002-2003 Rob Buis <buis@kde.org>
- Copyright (C) 2006-2008,2011 Jan Hambrecht <jaham@gmx.net>
-
- 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 "KoGradientEditWidget.h"
-
-#include <resources/KoAbstractGradient.h>
-#include <resources/KoStopGradient.h>
-#include <KoResourceServer.h>
-#include <KoResourceServerProvider.h>
-#include <KoSliderCombo.h>
-#include <KoColorPopupAction.h>
-
-#include <klocalizedstring.h>
-
-#include <QDoubleSpinBox>
-#include <QComboBox>
-#include <QFileInfo>
-#include <QPointF>
-#include <QLabel>
-#include <QPushButton>
-#include <QToolButton>
-#include <QGridLayout>
-#include <QRadialGradient>
-#include <QLinearGradient>
-#include <QConicalGradient>
-
-#include <math.h>
-
-void transferGradientPosition(const QGradient * srcGradient, QGradient * dstGradient)
-{
- // first check if gradients have the same type
- if (srcGradient->type() == dstGradient->type()) {
- switch (srcGradient->type()) {
- case QGradient::LinearGradient: {
- const QLinearGradient * src = static_cast<const QLinearGradient*>(srcGradient);
- QLinearGradient * dst = static_cast<QLinearGradient*>(dstGradient);
- dst->setStart(src->start());
- dst->setFinalStop(src->finalStop());
- break;
- }
- case QGradient::RadialGradient: {
- const QRadialGradient * src = static_cast<const QRadialGradient*>(srcGradient);
- QRadialGradient * dst = static_cast<QRadialGradient*>(dstGradient);
- dst->setCenter(src->center());
- dst->setRadius(src->radius());
- dst->setFocalPoint(src->focalPoint());
- break;
- }
- case QGradient::ConicalGradient: {
- const QConicalGradient * src = static_cast<const QConicalGradient*>(srcGradient);
- QConicalGradient * dst = static_cast<QConicalGradient*>(dstGradient);
- dst->setCenter(src->center());
- dst->setAngle(src->angle());
- break;
- }
- default:
- return;
- }
- return;
- }
-
- // try to preserve gradient positions as best as possible
- QPointF start, stop;
- switch (srcGradient->type()) {
- case QGradient::LinearGradient: {
- const QLinearGradient * g = static_cast<const QLinearGradient*>(srcGradient);
- start = g->start();
- stop = g->finalStop();
- break;
- }
- case QGradient::RadialGradient: {
- const QRadialGradient * g = static_cast<const QRadialGradient*>(srcGradient);
- start = g->center();
- stop = QPointF(g->radius(), 0.0);
- break;
- }
- case QGradient::ConicalGradient: {
- const QConicalGradient * g = static_cast<const QConicalGradient*>(srcGradient);
- start = g->center();
- qreal radAngle = g->angle() * M_PI / 180.0;
- stop = QPointF(50.0 * cos(radAngle), 50.*sin(radAngle));
- break;
- }
- default:
- start = QPointF(0.0, 0.0);
- stop = QPointF(50.0, 50.0);
- }
-
- switch (dstGradient->type()) {
- case QGradient::LinearGradient: {
- QLinearGradient * g = static_cast<QLinearGradient*>(dstGradient);
- g->setStart(start);
- g->setFinalStop(stop);
- break;
- }
- case QGradient::RadialGradient: {
- QRadialGradient * g = static_cast<QRadialGradient*>(dstGradient);
- QPointF diff = stop - start;
- qreal radius = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
- g->setCenter(start);
- g->setFocalPoint(start);
- g->setRadius(radius);
- break;
- }
- case QGradient::ConicalGradient: {
- QConicalGradient * g = static_cast<QConicalGradient*>(dstGradient);
- QPointF diff = stop - start;
- qreal angle = atan2(diff.y(), diff.x());
- if (angle < 0.0)
- angle += 2 * M_PI;
- g->setCenter(start);
- g->setAngle(angle*180 / M_PI);
- break;
- }
- default:
- return;
- }
-}
-
-KoGradientEditWidget::KoGradientEditWidget(QWidget* parent)
- : QWidget(parent)
- , m_gradOpacity(1.0), m_stopIndex(-1), m_checkerPainter(4)
- , m_type(QGradient::LinearGradient), m_spread(QGradient::PadSpread)
-{
- setObjectName("KoGradientEditWidget");
- // create a default gradient
- m_stops.append(QGradientStop(0.0, Qt::white));
- m_stops.append(QGradientStop(1.0, Qt::green));
-
- setupUI();
- setupConnections();
- updateUI();
-}
-
-KoGradientEditWidget::~KoGradientEditWidget()
-{
-}
-
-void KoGradientEditWidget::setupUI()
-{
- QGridLayout* editLayout = new QGridLayout(this);
-
- int row = 0;
- editLayout->addWidget(new QLabel(i18n("Target:"), this), row, 0);
- m_gradientTarget = new QComboBox(this);
- m_gradientTarget->insertItem(0, i18n("Line"));
- m_gradientTarget->insertItem(1, i18n("Fill"));
- m_gradientTarget->setCurrentIndex(FillGradient);
- editLayout->addWidget(m_gradientTarget, row, 1, 1, 2);
-
- editLayout->addWidget(new QLabel(i18n("Type:"), this), ++row, 0);
- m_gradientType = new QComboBox(this);
- m_gradientType->insertItem(0, i18nc("Linear gradient type", "Linear"));
- m_gradientType->insertItem(1, i18nc("Radial gradient type", "Radial"));
- m_gradientType->insertItem(2, i18nc("Conical gradient type", "Conical"));
- editLayout->addWidget(m_gradientType, row, 1, 1, 2);
-
- editLayout->addWidget(new QLabel(i18n("Repeat:"), this), ++row, 0);
- m_gradientRepeat = new QComboBox(this);
- m_gradientRepeat->insertItem(0, i18nc("No gradient spread", "None"));
- m_gradientRepeat->insertItem(1, i18n("Reflect"));
- m_gradientRepeat->insertItem(2, i18n("Repeat"));
- editLayout->addWidget(m_gradientRepeat, row, 1, 1, 2);
-
- editLayout->addWidget(new QLabel(i18n("Overall opacity:"), this), ++row, 0);
- m_opacity = new KoSliderCombo(this);
- m_opacity->setDecimals(0);
- editLayout->addWidget(m_opacity, row, 1, 1, 2);
-
- editLayout->addWidget(new QLabel(i18n("Color stop:"), this), ++row, 0);
- m_stopColor = new QToolButton(this);
- editLayout->addWidget(m_stopColor, row, 1);
- m_stopPosition = new QDoubleSpinBox(this);
- m_stopPosition->setRange(0.0, 1.0);
- m_stopPosition->setSingleStep(0.01);
- editLayout->addWidget(m_stopPosition, row, 2);
- m_actionStopColor = new KoColorPopupAction(this);
- m_actionStopColor ->setToolTip(i18n("Stop color."));
- m_stopColor->setDefaultAction(m_actionStopColor);
-
- m_addToPredefs = new QPushButton(i18n("&Add to Predefined Gradients"), this);
- editLayout->addWidget(m_addToPredefs, ++row, 0, 1, 3);
-
- editLayout->setSpacing(3);
- editLayout->setMargin(6);
- editLayout->setRowMinimumHeight(0, 12);
- editLayout->setRowStretch(++row, 1);
-}
-
-void KoGradientEditWidget::setupConnections()
-{
- connect(m_gradientType, SIGNAL(activated(int)), this, SLOT(combosChange(int)));
- connect(m_gradientRepeat, SIGNAL(activated(int)), this, SLOT(combosChange(int)));
- connect(m_gradientTarget, SIGNAL(activated(int)), this, SLOT(combosChange(int)));
- connect(m_addToPredefs, SIGNAL(clicked()), this, SLOT(addGradientToPredefs()));
- connect(m_opacity, SIGNAL(valueChanged(qreal,bool)), this, SLOT(opacityChanged(qreal,bool)));
- connect(m_actionStopColor, SIGNAL(colorChanged(KoColor)), this, SLOT(stopChanged()));
- connect(m_stopPosition, SIGNAL(valueChanged(double)), this, SLOT(stopChanged()));
-}
-
-void KoGradientEditWidget::blockChildSignals(bool block)
-{
- m_gradientType->blockSignals(block);
- m_gradientRepeat->blockSignals(block);
- m_addToPredefs->blockSignals(block);
- m_opacity->blockSignals(block);
- m_stopColor->blockSignals(block);
- m_stopPosition->blockSignals(block);
-}
-
-void KoGradientEditWidget::updateUI()
-{
- blockChildSignals(true);
-
- m_gradientType->setCurrentIndex(m_type);
- m_gradientRepeat->setCurrentIndex(m_spread);
-
- uint stopCount = m_stops.count();
- qreal opacity = m_stops[0].second.alphaF();
- bool equalOpacity = true;
- for (uint i = 1; i < stopCount; ++i) {
- if (opacity != m_stops[i].second.alphaF()) {
- equalOpacity = false;
- break;
- }
- }
-
- m_opacity->setEnabled(equalOpacity);
- if (equalOpacity) {
- m_opacity->setValue(opacity * 100);
- }
-
-
-
- // now update the stop color and opacity
- const bool colorStopSelected = m_stopIndex >= 0 && m_stopIndex < m_stops.count();
- if (colorStopSelected) {
- QColor c = m_stops[m_stopIndex].second;
- m_stopPosition->setValue(m_stops[m_stopIndex].first);
- m_actionStopColor->setCurrentColor(c);
- }
- m_stopColor->setEnabled(colorStopSelected);
- m_stopPosition->setEnabled(colorStopSelected);
-
- blockChildSignals(false);
-}
-
-qreal KoGradientEditWidget::opacity() const
-{
- return m_opacity->value() / 100.0;
-}
-
-void KoGradientEditWidget::setOpacity(qreal opacity)
-{
- if (opacity < 0.0 || opacity > 1.0)
- return;
-
- m_gradOpacity = opacity;
- m_opacity->setValue(int(opacity*100.0));
-}
-
-void KoGradientEditWidget::setStopIndex(int index)
-{
- m_stopIndex = index;
- updateUI();
-}
-
-void KoGradientEditWidget::setGradient(const QGradient & gradient)
-{
- m_stops = gradient.stops();
- m_type = gradient.type();
- m_spread = gradient.spread();
-
- updateUI();
-}
-
-KoGradientEditWidget::GradientTarget KoGradientEditWidget::target()
-{
- return (GradientTarget)m_gradientTarget->currentIndex();
-}
-
-void KoGradientEditWidget::setTarget(GradientTarget target)
-{
- m_gradientTarget->setCurrentIndex(target);
-}
-
-QGradient::Spread KoGradientEditWidget::spread() const
-{
- return m_spread;
-}
-
-void KoGradientEditWidget::setSpread(QGradient::Spread spread)
-{
- m_spread = spread;
- updateUI();
-}
-
-QGradient::Type KoGradientEditWidget::type() const
-{
- return m_type;
-}
-
-void KoGradientEditWidget::setType(QGradient::Type type)
-{
- m_type = type;
- updateUI();
-}
-
-QGradientStops KoGradientEditWidget::stops() const
-{
- return m_stops;
-}
-
-void KoGradientEditWidget::setStops(const QGradientStops &stops)
-{
- m_stops = stops;
- updateUI();
-}
-
-void KoGradientEditWidget::combosChange(int)
-{
- m_type = static_cast<QGradient::Type>(m_gradientType->currentIndex());
- m_spread = static_cast<QGradient::Spread>(m_gradientRepeat->currentIndex());
-
- emit changed();
-}
-
-void KoGradientEditWidget::opacityChanged(qreal value, bool final)
-{
- Q_UNUSED(final);
-
- m_gradOpacity = value / 100.0;
-
- uint stopCount = m_stops.count();
- for (uint i = 0; i < stopCount; ++i)
- m_stops[i].second.setAlphaF(m_gradOpacity);
-
- emit changed();
-}
-
-void KoGradientEditWidget::addGradientToPredefs()
-{
- KoResourceServer<KoAbstractGradient>* server = KoResourceServerProvider::instance()->gradientServer();
-
- QString savePath = server->saveLocation();
-
- int i = 1;
- QFileInfo fileInfo;
-
- do {
- fileInfo.setFile(savePath + QString("%1.svg").arg(i++, 4, 10, QChar('0')));
- } while (fileInfo.exists());
-
- QGradient * gradient = 0;
- switch (m_type) {
- case QGradient::LinearGradient:
- gradient = new QLinearGradient();
- break;
- case QGradient::RadialGradient:
- gradient = new QRadialGradient();
- break;
- case QGradient::ConicalGradient:
- gradient = new QConicalGradient();
- break;
- default:
- // should not happen
- return;
- }
- gradient->setSpread(m_spread);
- gradient->setStops(m_stops);
- KoStopGradient * g = KoStopGradient::fromQGradient(gradient);
- delete gradient;
- if (! g)
- return;
- g->setFilename(fileInfo.filePath());
- g->setValid(true);
-
- if (! server->addResource(g))
- delete g;
-}
-
-void KoGradientEditWidget::stopChanged()
-{
- if (m_stopIndex >= 0 && m_stopIndex < m_stops.count()) {
- m_stops[m_stopIndex].first = m_stopPosition->value();
- m_stops[m_stopIndex].second = m_actionStopColor->currentColor();
- emit changed();
- }
-}
-
-
diff --git a/libs/widgets/KoGradientEditWidget.h b/libs/widgets/KoGradientEditWidget.h
deleted file mode 100644
index 4b856c6aa9..0000000000
--- a/libs/widgets/KoGradientEditWidget.h
+++ /dev/null
@@ -1,133 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2001-2002 Beno�t Vautrin <benoit.vautrin@free.fr>
- Copyright (C) 2002 Rob Buis <buis@kde.org>
- Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
-
- 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 KO_GRADIENT_EDIT_WIDGET_H
-#define KO_GRADIENT_EDIT_WIDGET_H
-
-#include <kritawidgets_export.h>
-#include <KoCheckerBoardPainter.h>
-#include <QWidget>
-
-class KoSliderCombo;
-class QComboBox;
-class QDoubleSpinBox;
-class KoColorPopupAction;
-class QToolButton;
-class QPushButton;
-
-/**
- * A tab widget for managing gradients.
- *
- * It has one tab to edit a selected gradients type, spread method and color stops.
- * Another tab contains a list with predefined gradients to choose from.
- */
-class KRITAWIDGETS_EXPORT KoGradientEditWidget : public QWidget
-{
- Q_OBJECT
-
-public:
- enum GradientTarget {
- StrokeGradient,
- FillGradient
- };
-
- /**
- * Creates a new gradient tab widget with the given parent.
- * @param parent the widgets parent
- */
- explicit KoGradientEditWidget(QWidget* parent = 0L);
-
- /// Destroys the widget
- ~KoGradientEditWidget() override;
-
- /**
- * Sets a new gradient to edit.
- * @param gradient the gradient to edit
- */
- void setGradient(const QGradient & gradient);
-
- /// Returns the gradient target (fill/stroke)
- GradientTarget target();
-
- /// Sets a new gradient target
- void setTarget(GradientTarget target);
-
- /// Returns the gradient opacity
- qreal opacity() const;
-
- /// Sets the gradients opacity to @p opacity
- void setOpacity(qreal opacity);
-
- /// Sets the index of the stop to edit
- void setStopIndex(int index);
-
- /// Returns the gradient spread
- QGradient::Spread spread() const;
-
- /// Sets the gradient spread
- void setSpread(QGradient::Spread spread);
-
- /// Returns the gradient type
- QGradient::Type type() const;
-
- /// Sets the gradient type
- void setType(QGradient::Type type);
-
- /// Returns the gradient stops
- QGradientStops stops() const;
-
- /// Sets the gradient stops
- void setStops(const QGradientStops &stops);
-
-Q_SIGNALS:
- /// Is emitted a soon as the gradient changes
- void changed();
-
-protected Q_SLOTS:
- void combosChange(int);
- void addGradientToPredefs();
- void opacityChanged(qreal value, bool final);
- void stopChanged();
-protected:
- void setupUI();
- void updateUI();
- void updatePredefGradients();
- void setupConnections();
- void blockChildSignals(bool block);
-
-private:
- QComboBox *m_gradientTarget;
- QComboBox *m_gradientRepeat;
- QComboBox *m_gradientType;
- QPushButton *m_addToPredefs;
- KoSliderCombo *m_opacity;
- QDoubleSpinBox *m_stopPosition;
- QToolButton *m_stopColor;
- qreal m_gradOpacity; ///< the gradient opacity
- int m_stopIndex; ///< the index of the selected gradient stop
- KoCheckerBoardPainter m_checkerPainter;
- QGradient::Type m_type;
- QGradient::Spread m_spread;
- QGradientStops m_stops;
- KoColorPopupAction *m_actionStopColor;
-};
-
-#endif // KARBONGRADIENTEDITWIDGET_H
diff --git a/libs/widgets/KoResourceItemChooser.cpp b/libs/widgets/KoResourceItemChooser.cpp
index eec4ed6c10..ec7220f2b2 100644
--- a/libs/widgets/KoResourceItemChooser.cpp
+++ b/libs/widgets/KoResourceItemChooser.cpp
@@ -1,579 +1,579 @@
/* This file is part of the KDE project
Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
Copyright (c) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
Copyright (c) 2013 Sascha Suelzer <s.suelzer@gmail.com>
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 "KoResourceItemChooser.h"
#include <math.h>
#include <QGridLayout>
#include <QButtonGroup>
#include <QHeaderView>
#include <QAbstractProxyModel>
#include <QLabel>
#include <QScrollArea>
#include <QImage>
#include <QPixmap>
#include <QPainter>
#include <QSplitter>
#include <QToolButton>
#include <QWheelEvent>
#include <QLineEdit>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <KoFileDialog.h>
#include <KisMimeDatabase.h>
#include "KoResourceServerAdapter.h"
#include "KoResourceItemView.h"
#include "KoResourceItemDelegate.h"
#include "KoResourceModel.h"
#include <resources/KoResource.h>
#include "KoResourceTaggingManager.h"
#include "KoTagFilterWidget.h"
#include "KoTagChooserWidget.h"
#include "KoResourceItemChooserSync.h"
#include "kis_assert.h"
#include <KisKineticScroller.h>
class Q_DECL_HIDDEN KoResourceItemChooser::Private
{
public:
Private()
: model(0)
, view(0)
, buttonGroup(0)
, viewModeButton(0)
, usePreview(false)
, previewScroller(0)
, previewLabel(0)
, splitter(0)
, tiledPreview(false)
, grayscalePreview(false)
, synced(false)
, updatesBlocked(false)
, savedResourceWhileReset(0)
{}
KoResourceModel *model;
KoResourceTaggingManager *tagManager;
KoResourceItemView *view;
QButtonGroup *buttonGroup;
QToolButton *viewModeButton;
bool usePreview;
QScrollArea *previewScroller;
QLabel *previewLabel;
QSplitter *splitter;
QGridLayout *buttonLayout;
bool tiledPreview;
bool grayscalePreview;
bool synced;
bool updatesBlocked;
KoResource *savedResourceWhileReset;
QList<QAbstractButton*> customButtons;
};
KoResourceItemChooser::KoResourceItemChooser(QSharedPointer<KoAbstractResourceServerAdapter> resourceAdapter, QWidget *parent, bool usePreview)
: QWidget(parent)
, d(new Private())
{
Q_ASSERT(resourceAdapter);
d->splitter = new QSplitter(this);
d->model = new KoResourceModel(resourceAdapter, this);
connect(d->model, SIGNAL(beforeResourcesLayoutReset(KoResource*)), SLOT(slotBeforeResourcesLayoutReset(KoResource*)));
connect(d->model, SIGNAL(afterResourcesLayoutReset()), SLOT(slotAfterResourcesLayoutReset()));
d->view = new KoResourceItemView(this);
d->view->setObjectName("ResourceItemview");
d->view->setModel(d->model);
d->view->setItemDelegate(new KoResourceItemDelegate(this));
d->view->setSelectionMode(QAbstractItemView::SingleSelection);
d->view->viewport()->installEventFilter(this);
connect(d->view, SIGNAL(currentResourceChanged(QModelIndex)), this, SLOT(activated(QModelIndex)));
connect(d->view, SIGNAL(currentResourceClicked(QModelIndex)), this, SLOT(clicked(QModelIndex)));
connect(d->view, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint)));
connect(d->view, SIGNAL(sigSizeChanged()), this, SLOT(updateView()));
d->splitter->addWidget(d->view);
d->splitter->setStretchFactor(0, 2);
d->usePreview = usePreview;
if (d->usePreview) {
d->previewScroller = new QScrollArea(this);
d->previewScroller->setWidgetResizable(true);
d->previewScroller->setBackgroundRole(QPalette::Dark);
d->previewScroller->setVisible(true);
d->previewScroller->setAlignment(Qt::AlignCenter);
d->previewLabel = new QLabel(this);
d->previewScroller->setWidget(d->previewLabel);
d->splitter->addWidget(d->previewScroller);
if (d->splitter->count() == 2) {
d->splitter->setSizes(QList<int>() << 280 << 160);
}
QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(d->previewScroller);
if (scroller) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
}
d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
connect(d->splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterMoved()));
d->buttonGroup = new QButtonGroup(this);
d->buttonGroup->setExclusive(false);
QGridLayout *layout = new QGridLayout(this);
d->buttonLayout = new QGridLayout();
importButton = new QPushButton(this);
importButton->setToolTip(i18nc("@info:tooltip", "Import resource"));
importButton->setEnabled(true);
d->buttonGroup->addButton(importButton, Button_Import);
d->buttonLayout->addWidget(importButton, 0, 0);
deleteButton = new QPushButton(this);
deleteButton->setToolTip(i18nc("@info:tooltip", "Delete resource"));
deleteButton->setEnabled(false);
d->buttonGroup->addButton(deleteButton, Button_Remove);
d->buttonLayout->addWidget(deleteButton, 0, 1);
connect(d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotButtonClicked(int)));
d->buttonLayout->setColumnStretch(0, 1);
d->buttonLayout->setColumnStretch(1, 1);
d->buttonLayout->setColumnStretch(2, 2);
d->buttonLayout->setSpacing(0);
d->buttonLayout->setMargin(0);
d->viewModeButton = new QToolButton(this);
d->viewModeButton->setPopupMode(QToolButton::InstantPopup);
d->viewModeButton->setVisible(false);
d->tagManager = new KoResourceTaggingManager(d->model, this);
connect(d->tagManager, SIGNAL(updateView()), this, SLOT(updateView()));
layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0);
layout->addWidget(d->viewModeButton, 0, 1);
layout->addWidget(d->splitter, 1, 0, 1, 2);
layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 2);
layout->addLayout(d->buttonLayout, 3, 0, 1, 2);
layout->setMargin(0);
layout->setSpacing(0);
updateView();
updateButtonState();
showTaggingBar(false);
activated(d->model->index(0, 0));
}
KoResourceItemChooser::~KoResourceItemChooser()
{
disconnect();
delete d;
}
void KoResourceItemChooser::slotButtonClicked(int button)
{
if (button == Button_Import) {
QString extensions = d->model->extensions();
QStringList mimeTypes;
Q_FOREACH(const QString &suffix, extensions.split(":")) {
mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix);
}
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
QString filename = dialog.filename();
d->model->importResourceFile(filename);
} else if (button == Button_Remove) {
QModelIndex index = d->view->currentIndex();
int row = index.row();
int column = index.column();
if (index.isValid()) {
KoResource *resource = resourceFromModelIndex(index);
if (resource) {
d->model->removeResource(resource);
}
}
if (column == 0) {
int rowMin = --row;
row = qBound(0, rowMin, row);
}
int columnMin = --column;
column = qBound(0, columnMin, column);
setCurrentItem(row, column);
activated(d->model->index(row, column));
}
updateButtonState();
}
void KoResourceItemChooser::showButtons(bool show)
{
foreach (QAbstractButton * button, d->buttonGroup->buttons()) {
show ? button->show() : button->hide();
}
Q_FOREACH (QAbstractButton *button, d->customButtons) {
show ? button->show() : button->hide();
}
}
void KoResourceItemChooser::addCustomButton(QAbstractButton *button, int cell)
{
d->buttonLayout->addWidget(button, 0, cell);
d->buttonLayout->setColumnStretch(2, 1);
d->buttonLayout->setColumnStretch(3, 1);
}
void KoResourceItemChooser::showTaggingBar(bool show)
{
d->tagManager->showTaggingBar(show);
}
void KoResourceItemChooser::setRowCount(int rowCount)
{
int resourceCount = d->model->resourcesCount();
d->model->setColumnCount(static_cast<qreal>(resourceCount) / rowCount);
//Force an update to get the right row height (in theory)
QRect geometry = d->view->geometry();
d->view->setViewMode(KoResourceItemView::FIXED_ROWS);
d->view->setGeometry(geometry.adjusted(0, 0, 0, 1));
d->view->setGeometry(geometry);
}
void KoResourceItemChooser::setColumnCount(int columnCount)
{
d->model->setColumnCount(columnCount);
}
void KoResourceItemChooser::setRowHeight(int rowHeight)
{
d->view->verticalHeader()->setDefaultSectionSize(rowHeight);
}
void KoResourceItemChooser::setColumnWidth(int columnWidth)
{
d->view->horizontalHeader()->setDefaultSectionSize(columnWidth);
}
void KoResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate)
{
d->view->setItemDelegate(delegate);
}
KoResource *KoResourceItemChooser::currentResource() const
{
QModelIndex index = d->view->currentIndex();
if (index.isValid()) {
return resourceFromModelIndex(index);
}
return 0;
}
void KoResourceItemChooser::setCurrentResource(KoResource *resource)
{
// don't update if the change came from the same chooser
if (d->updatesBlocked) {
return;
}
QModelIndex index = d->model->indexFromResource(resource);
d->view->setCurrentIndex(index);
updatePreview(index.isValid() ? resource : 0);
}
void KoResourceItemChooser::slotBeforeResourcesLayoutReset(KoResource *activateAfterReset)
{
d->savedResourceWhileReset = activateAfterReset ? activateAfterReset : currentResource();
}
void KoResourceItemChooser::slotAfterResourcesLayoutReset()
{
if (d->savedResourceWhileReset) {
this->blockSignals(true);
setCurrentResource(d->savedResourceWhileReset);
this->blockSignals(false);
}
}
void KoResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation)
{
d->splitter->setOrientation(orientation);
}
void KoResourceItemChooser::setPreviewTiled(bool tiled)
{
d->tiledPreview = tiled;
}
void KoResourceItemChooser::setGrayscalePreview(bool grayscale)
{
d->grayscalePreview = grayscale;
}
void KoResourceItemChooser::setCurrentItem(int row, int column)
{
QModelIndex index = d->model->index(row, column);
if (!index.isValid())
return;
d->view->setCurrentIndex(index);
if (index.isValid()) {
updatePreview(resourceFromModelIndex(index));
}
}
void KoResourceItemChooser::setProxyModel(QAbstractProxyModel *proxyModel)
{
proxyModel->setSourceModel(d->model);
d->view->setModel(proxyModel);
}
void KoResourceItemChooser::activated(const QModelIndex &index)
{
if (!index.isValid()) return;
KoResource *resource = 0;
if (index.isValid()) {
resource = resourceFromModelIndex(index);
}
KIS_SAFE_ASSERT_RECOVER (resource) {
resource = currentResource();
}
if (resource) {
d->updatesBlocked = true;
emit resourceSelected(resource);
d->updatesBlocked = false;
updatePreview(resource);
updateButtonState();
}
}
void KoResourceItemChooser::clicked(const QModelIndex &index)
{
Q_UNUSED(index);
KoResource *resource = currentResource();
if (resource) {
emit resourceClicked(resource);
}
}
void KoResourceItemChooser::updateButtonState()
{
QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove);
if (! removeButton)
return;
KoResource *resource = currentResource();
if (resource) {
removeButton->setEnabled(!resource->permanent());
return;
}
removeButton->setEnabled(false);
}
void KoResourceItemChooser::updatePreview(KoResource *resource)
{
if (!d->usePreview) return;
if (!resource) {
d->previewLabel->setPixmap(QPixmap());
return;
}
QImage image = resource->image();
if (image.format() != QImage::Format_RGB32 &&
image.format() != QImage::Format_ARGB32 &&
image.format() != QImage::Format_ARGB32_Premultiplied) {
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
if (d->tiledPreview) {
int width = d->previewScroller->width() * 4;
int height = d->previewScroller->height() * 4;
QImage img(width, height, image.format());
QPainter gc(&img);
gc.fillRect(img.rect(), Qt::white);
gc.setPen(Qt::NoPen);
gc.setBrush(QBrush(image));
gc.drawRect(img.rect());
image = img;
}
// Only convert to grayscale if it is rgb. Otherwise, it's gray already.
if (d->grayscalePreview && !image.isGrayscale()) {
QRgb *pixel = reinterpret_cast<QRgb *>(image.bits());
for (int row = 0; row < image.height(); ++row) {
for (int col = 0; col < image.width(); ++col) {
const QRgb currentPixel = pixel[row * image.width() + col];
const int red = qRed(currentPixel);
const int green = qGreen(currentPixel);
const int blue = qBlue(currentPixel);
const int grayValue = (red * 11 + green * 16 + blue * 5) / 32;
pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue);
}
}
}
d->previewLabel->setPixmap(QPixmap::fromImage(image));
}
KoResource *KoResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
const QAbstractProxyModel *proxyModel = dynamic_cast<const QAbstractProxyModel *>(index.model());
if (proxyModel) {
//Get original model index, because proxy models destroy the internalPointer
QModelIndex originalIndex = proxyModel->mapToSource(index);
return static_cast<KoResource *>(originalIndex.internalPointer());
}
return static_cast<KoResource *>(index.internalPointer());
}
QSize KoResourceItemChooser::viewSize() const
{
return d->view->size();
}
KoResourceItemView *KoResourceItemChooser::itemView() const
{
return d->view;
}
void KoResourceItemChooser::contextMenuRequested(const QPoint &pos)
{
d->tagManager->contextMenuRequested(currentResource(), pos);
}
void KoResourceItemChooser::setViewModeButtonVisible(bool visible)
{
d->viewModeButton->setVisible(visible);
}
QToolButton *KoResourceItemChooser::viewModeButton() const
{
return d->viewModeButton;
}
void KoResourceItemChooser::setSynced(bool sync)
{
if (d->synced == sync)
return;
d->synced = sync;
KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
if (sync) {
connect(chooserSync, SIGNAL(baseLengthChanged(int)), SLOT(baseLengthChanged(int)));
baseLengthChanged(chooserSync->baseLength());
} else {
chooserSync->disconnect(this);
}
}
void KoResourceItemChooser::baseLengthChanged(int length)
{
if (d->synced) {
int resourceCount = d->model->resourcesCount();
int width = d->view->width();
int maxColumns = width / length;
int cols = width / (2 * length) + 1;
while (cols <= maxColumns) {
int size = width / cols;
int rows = ceil(resourceCount / (double)cols);
- if (rows * size < (d->view->height() - 5)) {
+ if (rows * size < (d->view->height())) {
break;
}
cols++;
}
setColumnCount(cols);
}
d->view->updateView();
}
bool KoResourceItemChooser::eventFilter(QObject *object, QEvent *event)
{
if (d->synced && event->type() == QEvent::Wheel) {
KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
QWheelEvent *qwheel = static_cast<QWheelEvent *>(event);
if (qwheel->modifiers() & Qt::ControlModifier) {
int degrees = qwheel->delta() / 8;
int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10;
chooserSync->setBaseLength(newBaseLength);
return true;
}
}
return QObject::eventFilter(object, event);
}
void KoResourceItemChooser::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateView();
}
void KoResourceItemChooser::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
updateView();
}
void KoResourceItemChooser::updateView()
{
if (d->synced) {
KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
baseLengthChanged(chooserSync->baseLength());
}
/// helps to set icons here in case the theme is changed
d->viewModeButton->setIcon(koIcon("view-choose"));
importButton->setIcon(koIcon("document-open"));
deleteButton->setIcon(koIcon("trash-empty"));
}
diff --git a/libs/widgets/KoResourceItemDelegate.cpp b/libs/widgets/KoResourceItemDelegate.cpp
index 1bb43a466a..b1826481c2 100644
--- a/libs/widgets/KoResourceItemDelegate.cpp
+++ b/libs/widgets/KoResourceItemDelegate.cpp
@@ -1,87 +1,94 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
*
* 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 "KoResourceItemDelegate.h"
#include <resources/KoAbstractGradient.h>
+#include <resources/KoColorSet.h>
#include <QPainter>
KoResourceItemDelegate::KoResourceItemDelegate( QObject * parent )
: QAbstractItemDelegate( parent ), m_checkerPainter( 4 )
{
}
void KoResourceItemDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
if( ! index.isValid() )
return;
KoResource * resource = static_cast<KoResource*>( index.internalPointer() );
if (!resource)
return;
painter->save();
if (option.state & QStyle::State_Selected)
painter->fillRect( option.rect, option.palette.highlight() );
QRect innerRect = option.rect.adjusted( 2, 1, -2, -1 );
KoAbstractGradient * gradient = dynamic_cast<KoAbstractGradient*>( resource );
+ KoColorSet * palette = dynamic_cast<KoColorSet*>( resource );
if (gradient) {
QGradient * g = gradient->toQGradient();
QLinearGradient paintGradient;
paintGradient.setStops( g->stops() );
paintGradient.setStart( innerRect.topLeft() );
paintGradient.setFinalStop( innerRect.topRight() );
m_checkerPainter.paint( *painter, innerRect );
painter->fillRect( innerRect, QBrush( paintGradient ) );
delete g;
}
+ else if (palette) {
+ QImage thumbnail = index.data( Qt::DecorationRole ).value<QImage>();
+ painter->setRenderHint(QPainter::SmoothPixmapTransform, thumbnail.width() > innerRect.width() || thumbnail.height() > innerRect.height());
+ painter->drawImage(innerRect, thumbnail);
+ }
else {
QImage thumbnail = index.data( Qt::DecorationRole ).value<QImage>();
QSize imageSize = thumbnail.size();
if(imageSize.height() > innerRect.height() || imageSize.width() > innerRect.width()) {
qreal scaleW = static_cast<qreal>( innerRect.width() ) / static_cast<qreal>( imageSize.width() );
qreal scaleH = static_cast<qreal>( innerRect.height() ) / static_cast<qreal>( imageSize.height() );
qreal scale = qMin( scaleW, scaleH );
int thumbW = static_cast<int>( imageSize.width() * scale );
int thumbH = static_cast<int>( imageSize.height() * scale );
thumbnail = thumbnail.scaled( thumbW, thumbH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
}
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
if (thumbnail.hasAlphaChannel()) {
painter->fillRect(innerRect, Qt::white); // no checkers, they are confusing with patterns.
}
painter->fillRect( innerRect, QBrush(thumbnail) );
}
painter->restore();
}
QSize KoResourceItemDelegate::sizeHint( const QStyleOptionViewItem & optionItem, const QModelIndex & ) const
{
return optionItem.decorationSize;
}
diff --git a/libs/widgets/KoTableView.cpp b/libs/widgets/KoTableView.cpp
index 3fb10b8fc1..a03fd0f1ba 100644
--- a/libs/widgets/KoTableView.cpp
+++ b/libs/widgets/KoTableView.cpp
@@ -1,93 +1,95 @@
/*
* Copyright (C) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "KoTableView.h"
#include <QEvent>
#include <QHeaderView>
+#include <QtMath>
KoTableView::KoTableView(QWidget *parent)
: QTableView(parent)
{
setSelectionMode(QAbstractItemView::SingleSelection);
verticalHeader()->hide();
horizontalHeader()->hide();
verticalHeader()->setDefaultSectionSize(20);
setContextMenuPolicy(Qt::DefaultContextMenu);
setViewMode(FIXED_COLUMNS);
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
if (scroller) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChange(QScroller::State)));
}
}
void KoTableView::resizeEvent(QResizeEvent *event)
{
QTableView::resizeEvent(event);
updateView();
emit sigSizeChanged();
}
void KoTableView::setViewMode(KoTableView::ViewMode mode)
{
m_viewMode = mode;
switch (m_viewMode) {
case FIXED_COLUMNS:
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Horizontal scrollbar is never needed
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
break;
case FIXED_ROWS:
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Vertical scrollbar is never needed
break;
default:
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
}
}
void KoTableView::updateView()
{
int columnCount = model()->columnCount(QModelIndex());
int rowCount = model()->rowCount(QModelIndex());
int rowHeight, columnWidth;
if (m_viewMode == FIXED_COLUMNS) {
- columnWidth = viewport()->size().width() / columnCount;
+ columnWidth = qFloor(viewport()->size().width() / static_cast<double>(columnCount));
for (int i = 0; i < columnCount; ++i) {
setColumnWidth(i, columnWidth);
}
+ // keep aspect ratio always square.
if (columnCount > 1) {
for (int i = 0; i < rowCount; ++i) {
setRowHeight(i, columnWidth);
}
}
} else if (m_viewMode == FIXED_ROWS) {
if (rowCount == 0) return; // Don't divide by zero
- rowHeight = viewport()->size().height() / rowCount;
+ rowHeight = qFloor(viewport()->size().height() / static_cast<double>(rowCount));
for (int i = 0; i < rowCount; ++i) {
setRowHeight(i, rowHeight);
}
}
}
diff --git a/libs/widgets/KoToolDocker.cpp b/libs/widgets/KoToolDocker.cpp
index ee1499d64e..2720be5704 100644
--- a/libs/widgets/KoToolDocker.cpp
+++ b/libs/widgets/KoToolDocker.cpp
@@ -1,232 +1,233 @@
/* This file is part of the KDE project
*
* Copyright (c) 2010-2011 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2005-2006 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2006 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License 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 "KoToolDocker.h"
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <QIcon>
#include <QApplication>
#include <QPointer>
#include <QGridLayout>
#include <QScrollArea>
#include <QScrollBar>
#include <QScroller>
#include <QLabel>
#include <QSet>
#include <QAction>
#include <QStyleOptionFrame>
#include <QToolButton>
#include <KisKineticScroller.h>
#include <WidgetsDebug.h>
#include <kis_debug.h>
class Q_DECL_HIDDEN KoToolDocker::Private
{
public:
Private(KoToolDocker *dock)
: q(dock)
{
}
QList<QPointer<QWidget> > currentWidgetList;
QSet<QWidget *> currentAuxWidgets;
QScrollArea *scrollArea;
QWidget *hiderWidget; // non current widgets are hidden by being children of this
QWidget *housekeeperWidget;
QGridLayout *housekeeperLayout;
KoToolDocker *q;
Qt::DockWidgetArea dockingArea;
void resetWidgets()
{
currentWidgetList.clear();
qDeleteAll(currentAuxWidgets);
currentAuxWidgets.clear();
}
void recreateLayout(const QList<QPointer<QWidget> > &optionWidgetList)
{
Q_FOREACH (QPointer<QWidget> widget, currentWidgetList) {
if (!widget.isNull() && widget && hiderWidget) {
widget->setParent(hiderWidget);
}
}
qDeleteAll(currentAuxWidgets);
currentAuxWidgets.clear();
currentWidgetList = optionWidgetList;
// need to unstretch row that have previously been stretched
housekeeperLayout->setRowStretch(housekeeperLayout->rowCount()-1, 0);
int cnt = 0;
QFrame *s;
QLabel *l;
switch(dockingArea) {
case Qt::TopDockWidgetArea:
case Qt::BottomDockWidgetArea:
housekeeperLayout->setHorizontalSpacing(2);
housekeeperLayout->setVerticalSpacing(0);
Q_FOREACH (QPointer<QWidget> widget, currentWidgetList) {
if (widget.isNull() || widget->objectName().isEmpty()) {
continue;
}
if (!widget->windowTitle().isEmpty()) {
housekeeperLayout->addWidget(l = new QLabel(widget->windowTitle()), 0, 2*cnt);
currentAuxWidgets.insert(l);
}
housekeeperLayout->addWidget(widget, 1, 2*cnt);
widget->show();
if (widget != currentWidgetList.last()) {
housekeeperLayout->addWidget(s = new QFrame(), 0, 2*cnt+1, 2, 1);
s->setFrameShape(QFrame::VLine);
currentAuxWidgets.insert(s);
}
cnt++;
}
break;
+ case Qt::NoDockWidgetArea:
case Qt::LeftDockWidgetArea:
case Qt::RightDockWidgetArea: {
housekeeperLayout->setHorizontalSpacing(0);
housekeeperLayout->setVerticalSpacing(2);
int specialCount = 0;
Q_FOREACH (QPointer<QWidget> widget, currentWidgetList) {
if (widget.isNull() || widget->objectName().isEmpty()) {
continue;
}
if (!widget->windowTitle().isEmpty()) {
housekeeperLayout->addWidget(l = new QLabel(widget->windowTitle()), cnt++, 0);
currentAuxWidgets.insert(l);
}
housekeeperLayout->addWidget(widget, cnt++, 0);
QLayout *subLayout = widget->layout();
if (subLayout) {
for (int i = 0; i < subLayout->count(); ++i) {
QWidget *spacerWidget = subLayout->itemAt(i)->widget();
if (spacerWidget && spacerWidget->objectName().contains("SpecialSpacer")) {
specialCount++;
}
}
}
widget->show();
if (widget != currentWidgetList.last()) {
housekeeperLayout->addWidget(s = new QFrame(), cnt++, 0);
s->setFrameShape(QFrame::HLine);
currentAuxWidgets.insert(s);
}
}
if (specialCount == currentWidgetList.count() || qApp->applicationName().contains("krita")) {
housekeeperLayout->setRowStretch(cnt, 10000);
}
break;
}
default:
break;
}
housekeeperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
housekeeperLayout->invalidate();
}
void locationChanged(Qt::DockWidgetArea area)
{
dockingArea = area;
recreateLayout(currentWidgetList);
}
};
KoToolDocker::KoToolDocker(QWidget *parent)
: QDockWidget(i18n("Tool Options"), parent),
d(new Private(this))
{
setFeatures(DockWidgetMovable|DockWidgetFloatable);
connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(locationChanged(Qt::DockWidgetArea)));
d->housekeeperWidget = new QWidget();
d->housekeeperLayout = new QGridLayout();
d->housekeeperLayout->setContentsMargins(4,4,4,0);
d->housekeeperWidget->setLayout(d->housekeeperLayout);
d->housekeeperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
d->hiderWidget = new QWidget(d->housekeeperWidget);
d->hiderWidget->setVisible(false);
d->scrollArea = new QScrollArea();
d->scrollArea->setWidget(d->housekeeperWidget);
d->scrollArea->setFrameShape(QFrame::NoFrame);
d->scrollArea->setWidgetResizable(true);
d->scrollArea->setFocusPolicy(Qt::NoFocus);
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(d->scrollArea);
if( scroller ) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChange(QScroller::State)));
}
setWidget(d->scrollArea);
}
KoToolDocker::~KoToolDocker()
{
delete d;
}
bool KoToolDocker::hasOptionWidget()
{
return !d->currentWidgetList.isEmpty();
}
void KoToolDocker::setOptionWidgets(const QList<QPointer<QWidget> > &optionWidgetList)
{
d->recreateLayout(optionWidgetList);
}
void KoToolDocker::slotScrollerStateChange(QScroller::State state)
{
KisKineticScroller::updateCursor(d->scrollArea, state);
}
void KoToolDocker::resetWidgets()
{
d->resetWidgets();
}
void KoToolDocker::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
}
void KoToolDocker::unsetCanvas()
{
setEnabled(false);
}
//have to include this because of Q_PRIVATE_SLOT
#include <moc_KoToolDocker.cpp>
diff --git a/libs/widgets/KoZoomController.cpp b/libs/widgets/KoZoomController.cpp
index 7f7f99c631..dd25f05922 100644
--- a/libs/widgets/KoZoomController.cpp
+++ b/libs/widgets/KoZoomController.cpp
@@ -1,215 +1,223 @@
/* This file is part of the KDE project
* Copyright (C) 2007 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2011 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 <KoZoomController.h>
#include <KoZoomController_p.h>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <WidgetsDebug.h>
#include <KoZoomHandler.h>
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
+#include <QtMath>
+
void KoZoomController::Private::init(KoCanvasController *co,
KoZoomHandler *zh,
KActionCollection *actionCollection)
{
canvasController = co;
fitMargin = co->margin();
zoomHandler = zh;
connect(action, SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
parent, SLOT(setZoom(KoZoomMode::Mode,qreal)));
connect(action, SIGNAL(aspectModeChanged(bool)),
parent, SIGNAL(aspectModeChanged(bool)));
connect(action, SIGNAL(zoomedToSelection()),
parent, SIGNAL(zoomedToSelection()));
connect(action, SIGNAL(zoomedToAll()),
parent, SIGNAL(zoomedToAll()));
actionCollection->addAction("view_zoom", action);
connect(canvasController->proxyObject, SIGNAL(sizeChanged(QSize)), parent, SLOT(setAvailableSize()) );
connect(canvasController->proxyObject, SIGNAL(zoomRelative(qreal,QPointF)), parent, SLOT(requestZoomRelative(qreal,QPointF)) );
}
KoZoomController::KoZoomController(KoCanvasController *co, KoZoomHandler *zh, KActionCollection *actionCollection, QObject *parent)
: QObject(parent),
d(new Private(this))
{
d->init(co, zh, actionCollection);
}
KoZoomController::~KoZoomController()
{
delete d;
}
KoZoomAction *KoZoomController::zoomAction() const
{
return d->action;
}
void KoZoomController::setZoomMode(KoZoomMode::Mode mode)
{
setZoom(mode, 1.0);
}
KoZoomMode::Mode KoZoomController::zoomMode() const
{
return d->zoomHandler->zoomMode();
}
void KoZoomController::setPageSize(const QSizeF &pageSize)
{
if(d->pageSize == pageSize) return;
d->pageSize = pageSize;
if(d->zoomHandler->zoomMode() == KoZoomMode::ZOOM_WIDTH)
setZoom(KoZoomMode::ZOOM_WIDTH, 0);
if(d->zoomHandler->zoomMode() == KoZoomMode::ZOOM_PAGE)
setZoom(KoZoomMode::ZOOM_PAGE, 0);
}
QSizeF KoZoomController::pageSize() const
{
return d->pageSize;
}
void KoZoomController::setDocumentSize(const QSizeF &documentSize, bool recalculateCenter)
{
d->documentSize = documentSize;
d->canvasController->updateDocumentSize(documentToViewport(d->documentSize), recalculateCenter);
// Finally ask the canvasController to recenter
d->canvasController->recenterPreferred();
}
QSizeF KoZoomController::documentSize() const
{
return d->documentSize;
}
void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom)
{
setZoom(mode, zoom, d->canvasController->preferredCenter());
}
void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, const QPointF &stillPoint)
{
setZoom(mode, zoom, d->zoomHandler->resolutionX(), d->zoomHandler->resolutionY(), stillPoint);
}
void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY)
{
setZoom(mode, zoom, resolutionX, resolutionY, d->canvasController->preferredCenter());
}
void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const QPointF &stillPoint)
{
if (d->zoomHandler->zoomMode() == mode &&
qFuzzyCompare(d->zoomHandler->zoom(), zoom) &&
qFuzzyCompare(d->zoomHandler->resolutionX(), resolutionX) &&
qFuzzyCompare(d->zoomHandler->resolutionY(), resolutionY)) {
return; // no change
}
qreal oldEffectiveZoom = d->action->effectiveZoom();
- QSize oldPageViewportSize = documentToViewport(d->pageSize);
+ QSizeF oldPageViewportSize = documentToViewport(d->pageSize);
if(!qFuzzyCompare(d->zoomHandler->resolutionX(), resolutionX) ||
!qFuzzyCompare(d->zoomHandler->resolutionY(), resolutionY)) {
d->zoomHandler->setResolution(resolutionX, resolutionY);
}
if(mode == KoZoomMode::ZOOM_CONSTANT) {
if(zoom == 0.0) return;
d->action->setZoom(zoom);
}
else if(mode == KoZoomMode::ZOOM_WIDTH) {
zoom = (d->canvasController->viewportSize().width() - 2 * d->fitMargin)
/ (oldPageViewportSize.width() / d->zoomHandler->zoom());
d->action->setSelectedZoomMode(mode);
d->action->setEffectiveZoom(zoom);
}
else if(mode == KoZoomMode::ZOOM_PAGE) {
zoom = (d->canvasController->viewportSize().width() - 2 * d->fitMargin)
/ (oldPageViewportSize.width() / d->zoomHandler->zoom());
zoom = qMin(zoom, (d->canvasController->viewportSize().height() - 2 * d->fitMargin)
/ (oldPageViewportSize.height() / d->zoomHandler->zoom()));
d->action->setSelectedZoomMode(mode);
d->action->setEffectiveZoom(zoom);
}
d->zoomHandler->setZoomMode(mode);
d->zoomHandler->setZoom(d->action->effectiveZoom());
#ifdef DEBUG
if(! d->documentSize.isValid())
warnWidgets << "Setting zoom while there is no document size set, this will fail";
else if (d->pageSize.width() > d->documentSize.width() || d->pageSize.height() > d->documentSize.height())
warnWidgets << "ZoomController; Your page size is larger than your document size (" <<
d->pageSize << " > " << d->documentSize << ")\n";
#endif
- QSize documentViewportSize = documentToViewport(d->documentSize);
+ QSizeF documentViewportSize = documentToViewport(d->documentSize);
// Tell the canvasController that the zoom has changed
// Actually canvasController doesn't know about zoom, but the document in pixels
// has changed as a result of the zoom change
// To allow listeners of offset changes to react on the real new offset and not on the
// intermediate offsets, we block the signals here, and emit by ourselves later.
d->canvasController->proxyObject->blockSignals(true);
d->canvasController->updateDocumentSize(documentViewportSize, true);
d->canvasController->proxyObject->blockSignals(false);
// Finally ask the canvasController to recenter
QPointF documentCenter;
if (mode == KoZoomMode::ZOOM_WIDTH || mode == KoZoomMode::ZOOM_PAGE) {
documentCenter = QRectF(QPointF(), documentViewportSize).center();
}
else {
qreal zoomCoeff = d->action->effectiveZoom() / oldEffectiveZoom;
QPointF oldCenter = d->canvasController->preferredCenter();
documentCenter = stillPoint * zoomCoeff - (stillPoint - 1.0 / zoomCoeff * oldCenter);
}
d->canvasController->setPreferredCenter(documentCenter);
emit zoomChanged(mode, d->action->effectiveZoom());
}
-QSize KoZoomController::documentToViewport(const QSizeF &size)
+QSizeF KoZoomController::documentToViewport(const QSizeF &size)
{
return d->zoomHandler->documentToView(size).toSize();
}
+QSize KoZoomController::documentToViewportCeil(const QSizeF &size)
+{
+ QSizeF viewport = documentToViewport(size);
+ return QSize(qCeil(viewport.width()), qCeil(viewport.height()));
+}
+
void KoZoomController::setAspectMode(bool status)
{
if (d->action) {
d->action->setAspectMode(status);
}
}
//have to include this because of Q_PRIVATE_SLOT
#include <moc_KoZoomController.cpp>
diff --git a/libs/widgets/KoZoomController.h b/libs/widgets/KoZoomController.h
index f1b7b652e8..b6abc0ab4c 100644
--- a/libs/widgets/KoZoomController.h
+++ b/libs/widgets/KoZoomController.h
@@ -1,203 +1,204 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007,2012 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* 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 KOZOOMCONTROLLER_H
#define KOZOOMCONTROLLER_H
#include "KoZoomAction.h"
#include "kritawidgets_export.h"
#include <KoZoomMode.h>
#include <QObject>
#include <QSizeF>
class KoCanvasController;
class KoZoomAction;
class KoZoomHandler;
class KActionCollection;
class QSize;
/**
* This controller class handles zoom levels for any canvas.
*
* For each KoCanvasController you should have one instance of this
* class to go with it. This class then creates a KoZoomAction and
* basically handles all zooming for you.
*
* All you need to do is connect to the setDocumentSize() slot and
* keep the controller up-to-date if your on-screen document ever
* changes (note that this is in document units, so this is a zoom
* independent size).
*
* If you choose to have zoom modes of 'page' and 'width' you are
* required to set the page size using the setPageSize() method.
*
* Additionally you can connect to the zoomChanged() signal if you
* want to store the latest zoom level and mode, for example to
* restore the last used one at next restart.
*
* The specialAspectMode toggle is only a UI element. It does nothing
* except emit the aspectModeChanged signal.
*
*/
class KRITAWIDGETS_EXPORT KoZoomController : public QObject {
Q_OBJECT
public:
/**
* Constructor. Create one per canvasController. The zoomAction is created in the constructor and will
* be available to the passed actionCollection for usage by XMLGui.
* @param controller the canvasController
* @param zoomHandler the zoom handler (viewconverter with setter methods)
* @param actionCollection the action collection where the KoZoomAction is added to
* @param parent the parent QObject
*/
KoZoomController(KoCanvasController *controller,
KoZoomHandler *zoomHandler,
KActionCollection *actionCollection,
QObject *parent = 0);
/// destructor
~KoZoomController() override;
/// returns the zoomAction that is maintained by this controller
KoZoomAction *zoomAction() const;
/**
* Alter the current zoom mode which updates the Gui.
* @param mode the new mode that will be used to auto-calculate a new zoom-level if needed.
*/
void setZoomMode(KoZoomMode::Mode mode);
/**
* @return the current zoom mode.
*/
KoZoomMode::Mode zoomMode() const;
/**
* Set the resolution, zoom, the zoom mode for this zoom Controller.
* Typically for use just after construction to restore the
* persistent data.
*
* @param mode new zoom mode for the canvas
* @param zoom (for ZOOM_CONSTANT zoom mode only) new zoom value for
* the canvas
* @param resolutionX new X resolution for the document
* @param resolutionY new Y resolution for the document
* @param stillPoint (for ZOOM_CONSTANT zoom mode only) the point
* which will not change its position in widget
* during the zooming. It is measured in view
* coordinate system *before* zoom.
*/
void setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const QPointF &stillPoint);
/**
* Convenience function that changes resolution with
* keeping the centering unchanged
*/
void setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY);
/**
* Convenience function that does not touch the resolution of the
* document
*/
void setZoom(KoZoomMode::Mode mode, qreal zoom, const QPointF &stillPoint);
/**
* Convenience function with @p center always set to the current
* center point of the canvas
*/
void setZoom(KoZoomMode::Mode mode, qreal zoom);
/**
* Set Aspect Mode button status and begin a chain of signals
*/
void setAspectMode(bool status);
public Q_SLOTS:
/**
* Set the size of the current page in document coordinates which allows zoom modes that use the pageSize
* to update.
* @param pageSize the new page size in points
*/
void setPageSize(const QSizeF &pageSize);
/**
* Returns the size of the current page in document coordinates
* @returns the page size in points
*/
QSizeF pageSize() const;
/**
* Set the size of the whole document currently being shown on the canvas.
* The document size will be used together with the current zoom level to calculate the size of the
* canvas in the canvasController.
* @param documentSize the new document size in points
* @param recalculateCenter tells canvas controller not to touch
* preferredCenterFraction
*/
void setDocumentSize(const QSizeF &documentSize, bool recalculateCenter = false);
/**
* Returns the size of the whole document currently being shown on the canvas.
* @returns the document size in points
*/
QSizeF documentSize() const;
Q_SIGNALS:
/**
* This signal is emitted whenever either the zoommode or the zoom level is changed by the user.
* the application can use the emitted data for persistency purposes.
*/
void zoomChanged (KoZoomMode::Mode mode, qreal zoom);
/**
* emitted when the special aspect mode toggle changes.
* @see KoZoomAction::aspectModeChanged()
*/
void aspectModeChanged (bool aspectModeActivated);
/**
* Signal is triggered when the user clicks the zoom to selection button.
* Nothing else happens except that this signal is emitted.
*/
void zoomedToSelection();
/**
* Signal is triggered when the user clicks the zoom to all button.
* Nothing else happens except that this signal is emitted.
*/
void zoomedToAll();
protected:
- virtual QSize documentToViewport(const QSizeF &size);
+ virtual QSizeF documentToViewport(const QSizeF &size);
+ QSize documentToViewportCeil(const QSizeF &size);
private:
Q_PRIVATE_SLOT(d, void setAvailableSize())
Q_PRIVATE_SLOT(d, void requestZoomRelative(const qreal, const QPointF&))
Q_PRIVATE_SLOT(d, void setZoom(KoZoomMode::Mode, qreal))
Q_DISABLE_COPY( KoZoomController )
class Private;
Private * const d;
};
#endif
diff --git a/libs/widgets/kis_color_input.cpp b/libs/widgets/kis_color_input.cpp
index 0ff533e202..4bbd8e02f3 100644
--- a/libs/widgets/kis_color_input.cpp
+++ b/libs/widgets/kis_color_input.cpp
@@ -1,416 +1,417 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Moritz Molch <kde@moritzmolch.de>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_input.h"
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
#endif
#include <cmath>
#include <kis_debug.h>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <klocalizedstring.h>
#include <KoChannelInfo.h>
#include <KoColor.h>
#include <KoColorSlider.h>
#include <KoColorSpace.h>
#include "kis_double_parse_spin_box.h"
#include "kis_int_parse_spin_box.h"
KisColorInput::KisColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
QWidget(parent), m_channelInfo(channelInfo), m_color(color), m_displayRenderer(displayRenderer),
m_usePercentage(usePercentage)
{
}
void KisColorInput::init()
{
QHBoxLayout* m_layout = new QHBoxLayout(this);
m_layout->setContentsMargins(0,0,0,0);
m_layout->setSpacing(1);
QLabel* m_label = new QLabel(i18n("%1:", m_channelInfo->name()), this);
m_layout->addWidget(m_label);
m_colorSlider = new KoColorSlider(Qt::Horizontal, this, m_displayRenderer);
m_colorSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_layout->addWidget(m_colorSlider);
QWidget* m_input = createInput();
m_colorSlider->setFixedHeight(m_input->sizeHint().height());
m_layout->addWidget(m_input);
}
KisIntegerColorInput::KisIntegerColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage)
{
init();
}
void KisIntegerColorInput::setValue(int v)
{
quint8* data = m_color->data() + m_channelInfo->pos();
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
*(reinterpret_cast<quint8*>(data)) = v;
break;
case KoChannelInfo::UINT16:
*(reinterpret_cast<quint16*>(data)) = v;
break;
case KoChannelInfo::UINT32:
*(reinterpret_cast<quint32*>(data)) = v;
break;
default:
Q_ASSERT(false);
}
emit(updated());
}
void KisIntegerColorInput::update()
{
KoColor min = *m_color;
KoColor max = *m_color;
quint8* data = m_color->data() + m_channelInfo->pos();
quint8* dataMin = min.data() + m_channelInfo->pos();
quint8* dataMax = max.data() + m_channelInfo->pos();
m_intNumInput->blockSignals(true);
m_colorSlider->blockSignals(true);
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
m_intNumInput->setValue(round(*(reinterpret_cast<quint8*>(data))*1.0 / 255.0 * 100.0));
} else {
m_intNumInput->setMaximum(0xFF);
m_intNumInput->setValue(*(reinterpret_cast<quint8*>(data)));
}
m_colorSlider->setValue(*(reinterpret_cast<quint8*>(data)));
*(reinterpret_cast<quint8*>(dataMin)) = 0x0;
*(reinterpret_cast<quint8*>(dataMax)) = 0xFF;
break;
case KoChannelInfo::UINT16:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
m_intNumInput->setValue(round(*(reinterpret_cast<quint16*>(data))*1.0 / 65535.0 * 100.0));
} else {
m_intNumInput->setMaximum(0xFFFF);
m_intNumInput->setValue(*(reinterpret_cast<quint16*>(data)));
}
m_colorSlider->setValue(*(reinterpret_cast<quint16*>(data)));
*(reinterpret_cast<quint16*>(dataMin)) = 0x0;
*(reinterpret_cast<quint16*>(dataMax)) = 0xFFFF;
break;
case KoChannelInfo::UINT32:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
m_intNumInput->setValue(round(*(reinterpret_cast<quint32*>(data))*1.0 / 4294967295.0 * 100.0));
} else {
m_intNumInput->setMaximum(0xFFFF);
m_intNumInput->setValue(*(reinterpret_cast<quint32*>(data)));
}
m_colorSlider->setValue(*(reinterpret_cast<quint32*>(data)));
*(reinterpret_cast<quint32*>(dataMin)) = 0x0;
*(reinterpret_cast<quint32*>(dataMax)) = 0xFFFFFFFF;
break;
default:
Q_ASSERT(false);
}
m_colorSlider->setColors(min, max);
m_intNumInput->blockSignals(false);
m_colorSlider->blockSignals(false);
}
QWidget* KisIntegerColorInput::createInput()
{
m_intNumInput = new KisIntParseSpinBox(this);
m_intNumInput->setMinimum(0);
m_colorSlider->setMinimum(0);
if (m_usePercentage) {
m_intNumInput->setSuffix("%");
} else {
m_intNumInput->setSuffix("");
}
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
} else {
m_intNumInput->setMaximum(0xFF);
}
m_colorSlider->setMaximum(0xFF);
break;
case KoChannelInfo::UINT16:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
} else {
m_intNumInput->setMaximum(0xFFFF);
}
m_colorSlider->setMaximum(0xFFFF);
break;
case KoChannelInfo::UINT32:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
} else {
m_intNumInput->setMaximum(0xFFFFFFFF);
}
m_colorSlider->setMaximum(0xFFFFFFFF);
break;
default:
Q_ASSERT(false);
}
connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(onColorSliderChanged(int)));
connect(m_intNumInput, SIGNAL(valueChanged(int)), this, SLOT(onNumInputChanged(int)));
return m_intNumInput;
}
void KisIntegerColorInput::setPercentageWise(bool val)
{
m_usePercentage = val;
if (m_usePercentage) {
m_intNumInput->setSuffix("%");
} else {
m_intNumInput->setSuffix("");
}
}
void KisIntegerColorInput::onColorSliderChanged(int val)
{
m_intNumInput->blockSignals(true);
if (m_usePercentage) {
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
m_intNumInput->setValue(round((val*1.0) / 255.0 * 100.0));
break;
case KoChannelInfo::UINT16:
m_intNumInput->setValue(round((val*1.0) / 65535.0 * 100.0));
break;
case KoChannelInfo::UINT32:
m_intNumInput->setValue(round((val*1.0) / 4294967295.0 * 100.0));
break;
default:
Q_ASSERT(false);
}
} else {
m_intNumInput->setValue(val);
}
m_intNumInput->blockSignals(false);
setValue(val);
}
void KisIntegerColorInput::onNumInputChanged(int val)
{
m_colorSlider->blockSignals(true);
if (m_usePercentage) {
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
m_colorSlider->setValue((val*1.0)/100.0 * 255.0);
m_colorSlider->blockSignals(false);
setValue((val*1.0)/100.0 * 255.0);
break;
case KoChannelInfo::UINT16:
m_colorSlider->setValue((val*1.0)/100.0 * 65535.0);
m_colorSlider->blockSignals(false);
setValue((val*1.0)/100.0 * 65535.0);
break;
case KoChannelInfo::UINT32:
m_colorSlider->setValue((val*1.0)/100.0 * 4294967295.0);
m_colorSlider->blockSignals(false);
setValue((val*1.0)/100.0 * 4294967295.0);
break;
default:
Q_ASSERT(false);
}
} else {
m_colorSlider->setValue(val);
m_colorSlider->blockSignals(false);
setValue(val);
}
}
KisFloatColorInput::KisFloatColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage)
{
init();
}
void KisFloatColorInput::setValue(double v)
{
quint8* data = m_color->data() + m_channelInfo->pos();
switch (m_channelInfo->channelValueType()) {
#ifdef HAVE_OPENEXR
case KoChannelInfo::FLOAT16:
*(reinterpret_cast<half*>(data)) = v;
break;
#endif
case KoChannelInfo::FLOAT32:
*(reinterpret_cast<float*>(data)) = v;
break;
default:
Q_ASSERT(false);
}
emit(updated());
}
QWidget* KisFloatColorInput::createInput()
{
m_dblNumInput = new KisDoubleParseSpinBox(this);
m_dblNumInput->setMinimum(0);
m_dblNumInput->setMaximum(1.0);
connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int)));
connect(m_dblNumInput, SIGNAL(valueChanged(double)), this, SLOT(setValue(double)));
m_dblNumInput->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
m_dblNumInput->setMinimumWidth(60);
m_dblNumInput->setMaximumWidth(60);
return m_dblNumInput;
}
void KisFloatColorInput::sliderChanged(int i)
{
const qreal floatRange = m_maxValue - m_minValue;
m_dblNumInput->setValue(m_minValue + (i / 255.0) * floatRange);
}
void KisFloatColorInput::update()
{
KoColor min = *m_color;
KoColor max = *m_color;
quint8* data = m_color->data() + m_channelInfo->pos();
quint8* dataMin = min.data() + m_channelInfo->pos();
quint8* dataMax = max.data() + m_channelInfo->pos();
qreal value = 1.0;
m_minValue = m_displayRenderer->minVisibleFloatValue(m_channelInfo);
m_maxValue = m_displayRenderer->maxVisibleFloatValue(m_channelInfo);
switch (m_channelInfo->channelValueType()) {
#ifdef HAVE_OPENEXR
case KoChannelInfo::FLOAT16:
value = *(reinterpret_cast<half*>(data));
m_minValue = qMin(value, m_minValue);
m_maxValue = qMax(value, m_maxValue);
*(reinterpret_cast<half*>(dataMin)) = m_minValue;
*(reinterpret_cast<half*>(dataMax)) = m_maxValue;
break;
#endif
case KoChannelInfo::FLOAT32:
value = *(reinterpret_cast<float*>(data));
m_minValue = qMin(value, m_minValue);
m_maxValue = qMax(value, m_maxValue);
*(reinterpret_cast<float*>(dataMin)) = m_minValue;
*(reinterpret_cast<float*>(dataMax)) = m_maxValue;
break;
default:
Q_ASSERT(false);
}
m_dblNumInput->setMinimum(m_minValue);
m_dblNumInput->setMaximum(m_maxValue);
// ensure at least 3 significant digits are always shown
int newPrecision = 2 + qMax(qreal(0.0), std::ceil(-std::log10(m_maxValue)));
if (newPrecision != m_dblNumInput->decimals()) {
m_dblNumInput->setDecimals(newPrecision);
m_dblNumInput->updateGeometry();
}
m_colorSlider->setColors(min, max);
const qreal floatRange = m_maxValue - m_minValue;
m_dblNumInput->setValue(value);
m_colorSlider->setValue((value - m_minValue) / floatRange * 255);
}
KisHexColorInput::KisHexColorInput(QWidget* parent, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
KisColorInput(parent, 0, color, displayRenderer, usePercentage)
{
QHBoxLayout* m_layout = new QHBoxLayout(this);
m_layout->setContentsMargins(0,0,0,0);
m_layout->setSpacing(1);
QLabel* m_label = new QLabel(i18n("Color name:"), this);
m_label->setMinimumWidth(50);
m_layout->addWidget(m_label);
QWidget* m_input = createInput();
m_input->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
m_layout->addWidget(m_input);
}
void KisHexColorInput::setValue()
{
QString valueString = m_hexInput->text();
valueString.remove(QChar('#'));
QList<KoChannelInfo*> channels = m_color->colorSpace()->channels();
channels = KoChannelInfo::displayOrderSorted(channels);
Q_FOREACH (KoChannelInfo* channel, channels) {
if (channel->channelType() == KoChannelInfo::COLOR) {
Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8);
quint8* data = m_color->data() + channel->pos();
int value = valueString.left(2).toInt(0, 16);
*(reinterpret_cast<quint8*>(data)) = value;
valueString.remove(0, 2);
}
}
emit(updated());
}
void KisHexColorInput::update()
{
QString hexString("#");
QList<KoChannelInfo*> channels = m_color->colorSpace()->channels();
channels = KoChannelInfo::displayOrderSorted(channels);
Q_FOREACH (KoChannelInfo* channel, channels) {
if (channel->channelType() == KoChannelInfo::COLOR) {
Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8);
quint8* data = m_color->data() + channel->pos();
hexString.append(QString("%1").arg(*(reinterpret_cast<quint8*>(data)), 2, 16, QChar('0')));
}
}
m_hexInput->setText(hexString);
}
QWidget* KisHexColorInput::createInput()
{
m_hexInput = new QLineEdit(this);
m_hexInput->setAlignment(Qt::AlignRight);
int digits = 2*m_color->colorSpace()->colorChannelCount();
QString pattern = QString("#?[a-fA-F0-9]{%1,%2}").arg(digits).arg(digits);
m_hexInput->setValidator(new QRegExpValidator(QRegExp(pattern), this));
connect(m_hexInput, SIGNAL(editingFinished()), this, SLOT(setValue()));
return m_hexInput;
}
diff --git a/libs/widgets/kis_color_input.h b/libs/widgets/kis_color_input.h
index 51d910be68..0a3ffcb276 100644
--- a/libs/widgets/kis_color_input.h
+++ b/libs/widgets/kis_color_input.h
@@ -1,111 +1,112 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_COLOR_INPUT_H_
#define _KIS_COLOR_INPUT_H_
#include <QWidget>
class KoChannelInfo;
class KoColor;
class QWidget;
class QSpinBox;
class QDoubleSpinBox;
class KisIntParseSpinBox;
class KisDoubleParseSpinBox;
class KoColorSlider;
class QLineEdit;
#include <KoColorDisplayRendererInterface.h>
#include "kritawidgets_export.h"
class KRITAWIDGETS_EXPORT KisColorInput : public QWidget
{
Q_OBJECT
public:
KisColorInput(QWidget* parent, const KoChannelInfo*, KoColor* color, KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), bool usePercentage = false);
inline bool usePercentage() const {
return m_usePercentage;
}
virtual inline void setPercentageWise(bool val) {
m_usePercentage = val;
}
protected:
void init();
virtual QWidget* createInput() = 0;
Q_SIGNALS:
void updated();
protected:
const KoChannelInfo* m_channelInfo;
KoColor* m_color;
KoColorSlider* m_colorSlider;
KoColorDisplayRendererInterface *m_displayRenderer;
bool m_usePercentage;
};
class KRITAWIDGETS_EXPORT KisIntegerColorInput : public KisColorInput
{
Q_OBJECT
public:
KisIntegerColorInput(QWidget* parent, const KoChannelInfo*, KoColor* color, KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), bool usePercentage = false);
protected:
QWidget* createInput() override;
void setPercentageWise(bool val) override;
public Q_SLOTS:
void setValue(int);
void update();
private Q_SLOTS:
void onColorSliderChanged(int);
void onNumInputChanged(int);
private:
KisIntParseSpinBox* m_intNumInput;
};
class KRITAWIDGETS_EXPORT KisFloatColorInput : public KisColorInput
{
Q_OBJECT
public:
KisFloatColorInput(QWidget* parent, const KoChannelInfo*, KoColor* color, KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), bool usePercentage = false);
protected:
QWidget* createInput() override;
public Q_SLOTS:
void setValue(double);
void sliderChanged(int);
void update();
private:
KisDoubleParseSpinBox* m_dblNumInput;
qreal m_minValue;
qreal m_maxValue;
};
class KRITAWIDGETS_EXPORT KisHexColorInput : public KisColorInput
{
Q_OBJECT
public:
KisHexColorInput(QWidget* parent, KoColor* color, KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), bool usePercentage = false);
protected:
QWidget* createInput() override;
public Q_SLOTS:
void setValue();
void update();
private:
QLineEdit* m_hexInput;
};
#endif
diff --git a/libs/widgets/kis_palette_view.cpp b/libs/widgets/kis_palette_view.cpp
index 587eaf839e..09cbb7498d 100644
--- a/libs/widgets/kis_palette_view.cpp
+++ b/libs/widgets/kis_palette_view.cpp
@@ -1,310 +1,300 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_palette_view.h"
#include <QWheelEvent>
#include <QHeaderView>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QCheckBox>
#include <QComboBox>
#include <QMenu>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KLocalizedString>
#include <kis_icon_utils.h>
#include <KisKineticScroller.h>
#include <KoDialog.h>
#include <KoColorDisplayRendererInterface.h>
#include "KisPaletteDelegate.h"
#include "KisPaletteModel.h"
#include "kis_color_button.h"
#include <KisSwatch.h>
int KisPaletteView::MININUM_ROW_HEIGHT = 10;
struct KisPaletteView::Private
{
QPointer<KisPaletteModel> model;
bool allowPaletteModification {false}; // if modification is allowed from this widget
};
KisPaletteView::KisPaletteView(QWidget *parent)
: QTableView(parent)
, m_d(new Private)
{
setItemDelegate(new KisPaletteDelegate(this));
setShowGrid(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(false);
setAcceptDrops(false);
/*
* without this, a cycle might be created:
* the view stretches to right border, and this make it need a scroll bar;
* after the bar is added, the view shrinks to the bar, and this makes it
* no longer need the bar any more, and the bar is removed again
*/
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
// set the size of swatches
horizontalHeader()->setVisible(false);
verticalHeader()->setVisible(false);
horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
horizontalHeader()->setMinimumSectionSize(MININUM_ROW_HEIGHT);
verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
verticalHeader()->setMinimumSectionSize(MININUM_ROW_HEIGHT);
connect(horizontalHeader(), SIGNAL(sectionResized(int,int,int)),
SLOT(slotHorizontalHeaderResized(int,int,int)));
setAutoFillBackground(true);
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
if (scroller) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)),
this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
+
+ connect(this, SIGNAL(clicked(QModelIndex)), SLOT(slotCurrentSelectionChanged(QModelIndex)));
}
KisPaletteView::~KisPaletteView()
{
}
void KisPaletteView::setCrossedKeyword(const QString &value)
{
KisPaletteDelegate *delegate =
dynamic_cast<KisPaletteDelegate*>(itemDelegate());
KIS_ASSERT_RECOVER_RETURN(delegate);
delegate->setCrossedKeyword(value);
}
bool KisPaletteView::addEntryWithDialog(KoColor color)
{
QScopedPointer<KoDialog> window(new KoDialog(this));
window->setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry"));
QFormLayout *editableItems = new QFormLayout(window.data());
window->mainWidget()->setLayout(editableItems);
QComboBox *cmbGroups = new QComboBox(window.data());
QString defaultGroupName = i18nc("Name for default group", "Default");
cmbGroups->addItem(defaultGroupName);
cmbGroups->addItems(m_d->model->colorSet()->getGroupNames());
QLineEdit *lnIDName = new QLineEdit(window.data());
QLineEdit *lnName = new QLineEdit(window.data());
KisColorButton *bnColor = new KisColorButton(window.data());
QCheckBox *chkSpot = new QCheckBox(window.data());
chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color."));
editableItems->addRow(i18n("Group"), cmbGroups);
editableItems->addRow(i18n("ID"), lnIDName);
editableItems->addRow(i18n("Name"), lnName);
editableItems->addRow(i18n("Color"), bnColor);
editableItems->addRow(i18nc("Spot color", "Spot"), chkSpot);
cmbGroups->setCurrentIndex(0);
lnName->setText(i18nc("Part of a default name for a color","Color")+" " + QString::number(m_d->model->colorSet()->colorCount()+1));
lnIDName->setText(QString::number(m_d->model->colorSet()->colorCount() + 1));
bnColor->setColor(color);
chkSpot->setChecked(false);
if (window->exec() == KoDialog::Accepted) {
QString groupName = cmbGroups->currentText();
if (groupName == defaultGroupName) {
groupName = QString();
}
KisSwatch newEntry;
newEntry.setColor(bnColor->color());
newEntry.setName(lnName->text());
newEntry.setId(lnIDName->text());
newEntry.setSpotColor(chkSpot->isChecked());
m_d->model->addEntry(newEntry, groupName);
return true;
}
return false;
}
bool KisPaletteView::addGroupWithDialog()
{
KoDialog *window = new KoDialog();
window->setWindowTitle(i18nc("@title:window","Add a new group"));
QFormLayout *editableItems = new QFormLayout();
window->mainWidget()->setLayout(editableItems);
QLineEdit *lnName = new QLineEdit();
editableItems->addRow(i18nc("Name for a group", "Name"), lnName);
lnName->setText(i18nc("Part of default name for a new group", "Color Group")+""+QString::number(m_d->model->colorSet()->getGroupNames().size()+1));
if (window->exec() == KoDialog::Accepted) {
KisSwatchGroup group;
group.setName(lnName->text());
m_d->model->addGroup(group);
m_d->model->colorSet()->save();
return true;
}
return false;
}
bool KisPaletteView::removeEntryWithDialog(QModelIndex index)
{
bool keepColors = false;
if (qvariant_cast<bool>(index.data(KisPaletteModel::IsGroupNameRole))) {
QScopedPointer<KoDialog> window(new KoDialog(this));
window->setWindowTitle(i18nc("@title:window","Removing Group"));
QFormLayout *editableItems = new QFormLayout(window.data());
QCheckBox *chkKeep = new QCheckBox(window.data());
window->mainWidget()->setLayout(editableItems);
editableItems->addRow(i18nc("Shows up when deleting a swatch group", "Keep the Colors"), chkKeep);
if (window->exec() != KoDialog::Accepted) { return false; }
keepColors = chkKeep->isChecked();
}
m_d->model->removeEntry(index, keepColors);
if (m_d->model->colorSet()->isGlobal()) {
m_d->model->colorSet()->save();
}
return true;
}
void KisPaletteView::selectClosestColor(const KoColor &color)
{
KoColorSet* color_set = m_d->model->colorSet();
if (!color_set) {
return;
}
//also don't select if the color is the same as the current selection
if (m_d->model->getEntry(currentIndex()).color() == color) {
return;
}
selectionModel()->clearSelection();
QModelIndex index = m_d->model->indexForClosest(color);
- selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
-}
-void KisPaletteView::slotFGColorChanged(const KoColor &color)
-{
- KConfigGroup group(KSharedConfig::openConfig(), "");
- if (group.readEntry("colorsettings/forcepalettecolors", false)) {
- selectClosestColor(color);
- }
+ selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
}
-void KisPaletteView::slotFGColorResourceChanged(const KoColor& color)
+const KoColor KisPaletteView::closestColor(const KoColor &color) const
{
- // This slot is called, because fg color was changed in the resource manager.
- // To enable re-picking the swatch color again, we reset currentIndex
- // of the selectionModel. We are not clearing the selection itself,
- // so the user can see the swatch selected previously.
- // See bug 402072
- selectionModel()->clearCurrentIndex();
- slotFGColorChanged(color);
+ QModelIndex index = m_d->model->indexForClosest(color);
+ KisSwatch swatch = m_d->model->getEntry(index);
+ return swatch.color();
}
-void KisPaletteView::slotSelectColor(const KoColor &color)
+void KisPaletteView::slotFGColorChanged(const KoColor &color)
{
selectClosestColor(color);
}
void KisPaletteView::setPaletteModel(KisPaletteModel *model)
{
if (m_d->model) {
- disconnect(m_d->model, Q_NULLPTR, this, Q_NULLPTR);
+ disconnect(m_d->model, 0, this, 0);
}
m_d->model = model;
setModel(model);
slotAdditionalGuiUpdate();
connect(model, SIGNAL(sigPaletteModified()), SLOT(slotAdditionalGuiUpdate()));
connect(model, SIGNAL(sigPaletteChanged()), SLOT(slotAdditionalGuiUpdate()));
- connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(slotCurrentSelectionChanged(QModelIndex)));
}
KisPaletteModel* KisPaletteView::paletteModel() const
{
return m_d->model;
}
void KisPaletteView::setAllowModification(bool allow)
{
m_d->allowPaletteModification = allow;
setDragEnabled(allow);
setAcceptDrops(allow);
}
void KisPaletteView::slotHorizontalHeaderResized(int, int, int newSize)
{
resizeRows(newSize);
slotAdditionalGuiUpdate();
}
void KisPaletteView::resizeRows(int newSize)
{
verticalHeader()->setDefaultSectionSize(newSize);
verticalHeader()->resizeSections(QHeaderView::Fixed);
}
void KisPaletteView::removeSelectedEntry()
{
if (selectedIndexes().size() <= 0) {
return;
}
m_d->model->removeEntry(currentIndex());
}
void KisPaletteView::slotAdditionalGuiUpdate()
{
clearSpans();
resizeRows(verticalHeader()->defaultSectionSize());
for (int groupNameRowNumber : m_d->model->m_rowGroupNameMap.keys()) {
if (groupNameRowNumber == -1) { continue; }
setSpan(groupNameRowNumber, 0, 1, m_d->model->columnCount());
setRowHeight(groupNameRowNumber, fontMetrics().lineSpacing() + 6);
verticalHeader()->resizeSection(groupNameRowNumber, fontMetrics().lineSpacing() + 6);
}
}
void KisPaletteView::slotCurrentSelectionChanged(const QModelIndex &newCurrent)
{
if (!newCurrent.isValid()) { return; }
const bool isGroupName = newCurrent.data(KisPaletteModel::IsGroupNameRole).toBool();
const bool isCheckSlot = newCurrent.data(KisPaletteModel::CheckSlotRole).toBool();
const KisSwatch newEntry = m_d->model->getEntry(newCurrent);
emit sigIndexSelected(newCurrent);
if (isGroupName) {
return;
}
if (isCheckSlot) {
emit sigColorSelected(newEntry.color());
}
}
void KisPaletteView::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
Q_ASSERT(m_d->model);
m_d->model->setDisplayRenderer(displayRenderer);
}
diff --git a/libs/widgets/kis_palette_view.h b/libs/widgets/kis_palette_view.h
index 6b18ea436d..65bae3620f 100644
--- a/libs/widgets/kis_palette_view.h
+++ b/libs/widgets/kis_palette_view.h
@@ -1,134 +1,130 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PALETTE_VIEW_H
#define __KIS_PALETTE_VIEW_H
#include <QScopedPointer>
#include <QTableView>
#include <QColorDialog>
#include <QPushButton>
#include <QPixmap>
#include <QIcon>
#include <KoColorSet.h>
#include "kritawidgets_export.h"
#include <KisKineticScroller.h>
class KisPaletteModel;
class QWheelEvent;
class KoColorDisplayRendererInterface;
class KRITAWIDGETS_EXPORT KisPaletteView : public QTableView
{
Q_OBJECT
private:
static int MININUM_ROW_HEIGHT;
public:
- explicit KisPaletteView(QWidget *parent = Q_NULLPTR);
+ explicit KisPaletteView(QWidget *parent = 0);
~KisPaletteView() override;
void setPaletteModel(KisPaletteModel *model);
KisPaletteModel* paletteModel() const;
public:
/**
* @brief setAllowModification
* Set whether doubleclick calls up a modification window. This is to prevent users from editing
* the palette when the palette is intended to be a list of items.
*/
void setAllowModification(bool allow);
void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer);
/**
* @brief setCrossedKeyword
* this apparently allows you to set keywords that can cross out colors.
* This is implemented to mark the lazybrush "transparent" color.
* @param value
*/
void setCrossedKeyword(const QString &value);
void removeSelectedEntry();
/**
* @brief selectClosestColor
* select a color that's closest to parameter color
* @param color
*/
void selectClosestColor(const KoColor &color);
+ /**
+ * @brief closestColor
+ * determines closest swatch in the active palette and returns it's color as KoColor
+ * @param color
+ * @return KoColor
+ */
+ const KoColor closestColor(const KoColor& color) const;
+
/**
* add an entry with a dialog window.
* @warning deprecated.
* kept for compatibility with PaletteView in libkis
*/
bool addEntryWithDialog(KoColor color);
/**
* remove entry with a dialog window.(Necessary for groups.
* @warning deprecated.
* kept for compatibility with PaletteView in libkis
*/
bool removeEntryWithDialog(QModelIndex index);
/**
* add entry with a dialog window.
* @warning deprecated.
* kept for compatibility with PaletteView in libkis
*/
bool addGroupWithDialog();
Q_SIGNALS:
void sigIndexSelected(const QModelIndex &index);
void sigColorSelected(const KoColor &);
public Q_SLOTS:
/**
* This tries to select the closest color in the palette.
* This doesn't update the foreground color, just the visual selection.
*/
void slotFGColorChanged(const KoColor &);
- /**
- * @brief slot that reacts to color changes in resource manager
- * @param color
- */
- void slotFGColorResourceChanged(const KoColor& color);
-
- /**
- * Slot that selects the right index for provided color.
- * Called from KisPaletteComboBox when user selects color in the dropdown.
- */
- void slotSelectColor(const KoColor& color);
-
void slotScrollerStateChanged(QScroller::State state){KisKineticScroller::updateCursor(this, state);}
private Q_SLOTS:
void slotHorizontalHeaderResized(int, int, int newSize);
void slotAdditionalGuiUpdate();
void slotCurrentSelectionChanged(const QModelIndex &newCurrent);
private:
void resizeRows(int newSize);
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_PALETTE_VIEW_H */
diff --git a/libs/ui/kis_highlighted_button.h b/libs/widgetutils/KisHighlightedToolButton.h
similarity index 74%
rename from libs/ui/kis_highlighted_button.h
rename to libs/widgetutils/KisHighlightedToolButton.h
index c6ea0dc5ce..aaef42817e 100644
--- a/libs/ui/kis_highlighted_button.h
+++ b/libs/widgetutils/KisHighlightedToolButton.h
@@ -1,56 +1,58 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_HIGHLIGHTED_BUTTON_H
-#define __KIS_HIGHLIGHTED_BUTTON_H
+#ifndef __KIS_HIGHLIGHTED_TOOL_BUTTON_H
+#define __KIS_HIGHLIGHTED_TOOL_BUTTON_H
-template <class BaseButton>
-class HighlightedButtonBase : public BaseButton
+#include <QToolButton>
+
+#include "kritawidgetutils_export.h"
+
+class KRITAWIDGETUTILS_EXPORT KisHighlightedToolButton : public QToolButton
{
public:
- HighlightedButtonBase(QWidget *parent = 0) : BaseButton(parent) {}
+ KisHighlightedToolButton(QWidget *parent = 0)
+ : QToolButton(parent)
+ {
+ }
+
protected:
void checkStateSet() override {
- BaseButton::checkStateSet();
+ QToolButton::checkStateSet();
updatePalette();
}
void nextCheckState() override {
- BaseButton::nextCheckState();
+ QToolButton::nextCheckState();
updatePalette();
}
private:
void updatePalette() {
QWidget *parent = this->parentWidget();
if (parent) {
QPalette p = parent->palette();
QColor color = p.color(this->isChecked() ? QPalette::Highlight : QPalette::Button);
p.setColor(QPalette::Button, color);
this->setPalette(p);
}
}
};
-class QPushButton;
-class QToolButton;
-typedef HighlightedButtonBase<QPushButton> KisHighlightedButton;
-typedef HighlightedButtonBase<QToolButton> KisHighlightedToolButton;
-
#endif /* __KIS_HIGHLIGHTED_BUTTON_H */
diff --git a/libs/widgetutils/KoGroupButton.cpp b/libs/widgetutils/KoGroupButton.cpp
index 9950af22b2..d1ce3e58c6 100644
--- a/libs/widgetutils/KoGroupButton.cpp
+++ b/libs/widgetutils/KoGroupButton.cpp
@@ -1,164 +1,160 @@
/* This file is part of the KDE libraries
Copyright (C) 2007 Aurélien Gâteau <agateau@kde.org>
Copyright (C) 2012 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
Copyright (C) 2012 Jarosław Staniek <staniek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
-
#include "KoGroupButton.h"
-// Qt
#include <QAction>
#include <QStyleOptionToolButton>
#include <QStylePainter>
-#include <QToolButton>
-// KF5
#include <KLocalizedString>
class Q_DECL_HIDDEN KoGroupButton::Private
{
public:
Private(KoGroupButton *qq, const GroupPosition position) : groupPosition(position)
{
// Make the policy closer to QPushButton's default but horizontal shouldn't be Fixed,
// otherwise spacing gets broken
qq->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
}
GroupPosition groupPosition;
};
KoGroupButton::KoGroupButton(GroupPosition position, QWidget* parent)
- : QToolButton(parent), d(new Private(this, position))
+ : KisHighlightedToolButton(parent), d(new Private(this, position))
{
}
KoGroupButton::KoGroupButton(QWidget* parent)
- : QToolButton(parent), d(new Private(this, NoGroup))
+ : KisHighlightedToolButton(parent), d(new Private(this, NoGroup))
{
}
KoGroupButton::~KoGroupButton()
{
delete d;
}
void KoGroupButton::setGroupPosition(KoGroupButton::GroupPosition groupPosition)
{
d->groupPosition = groupPosition;
}
KoGroupButton::GroupPosition KoGroupButton::groupPosition() const
{
return d->groupPosition;
}
void KoGroupButton::paintEvent(QPaintEvent* event)
{
if (groupPosition() == NoGroup) {
QToolButton::paintEvent(event);
return;
}
QStylePainter painter(this);
QStyleOptionToolButton opt;
initStyleOption(&opt);
QStyleOptionToolButton panelOpt = opt;
// Panel
QRect& panelRect = panelOpt.rect;
switch (groupPosition()) {
case GroupLeft:
panelRect.setWidth(panelRect.width() * 2);
break;
case GroupCenter:
panelRect.setLeft(panelRect.left() - panelRect.width());
panelRect.setWidth(panelRect.width() * 3);
break;
case GroupRight:
panelRect.setLeft(panelRect.left() - panelRect.width());
break;
case NoGroup:
Q_ASSERT(0);
}
if (autoRaise()) {
if (!isChecked() && !isDown() && !(panelOpt.state & QStyle::State_MouseOver)) {
// Use 'pushed' appearance for all buttons, but those that are not really pushed
// are drawn with less contrast and are toned down.
panelOpt.state |= (QStyle::State_On | QStyle::State_Sunken);
QPalette panelPal(panelOpt.palette);
QColor c;
c = panelPal.color(QPalette::Button);
c.setAlpha(50);
panelPal.setColor(QPalette::Button, c);
c = panelPal.color(QPalette::Window);
c.setAlpha(50);
panelPal.setColor(QPalette::Window, c);
panelOpt.palette = panelPal;
painter.setOpacity(0.5);
}
} else {
if (!isChecked() && !isDown() && !(panelOpt.state & QStyle::State_MouseOver)) {
} else {
- // only highlight the selected item
+ // only highlight the selected item
panelOpt.state |= (QStyle::State_On | QStyle::State_Sunken);
QPalette panelPal(panelOpt.palette);
QColor c;
c = panelPal.color(QPalette::Button);
c.setAlpha(50);
panelPal.setColor(QPalette::Button, c);
c = panelPal.color(QPalette::Window);
c.setAlpha(50);
panelPal.setColor(QPalette::Window, c);
panelOpt.palette = panelPal;
painter.setOpacity(0.5);
}
}
painter.drawPrimitive(QStyle::PE_PanelButtonTool, panelOpt);
painter.setOpacity(1.0);
// Separator
//! @todo make specific fixes for styles such as Plastique, Cleanlooks if there's practical no alternative
const int y1 = opt.rect.top() + 1;
const int y2 = opt.rect.bottom() - 1;
painter.setOpacity(0.4);
if (d->groupPosition != GroupRight) {
const int x = opt.rect.right();
painter.setPen(QPen(opt.palette.color(QPalette::Dark), 0));
painter.drawLine(x, y1, x, y2);
}
painter.setOpacity(1.0);
// Text
painter.drawControl(QStyle::CE_ToolButtonLabel, opt);
// Filtering message on tooltip text for CJK to remove accelerators.
// Quoting ktoolbar.cpp:
// """
// CJK languages use more verbose accelerator marker: they add a Latin
// letter in parenthesis, and put accelerator on that. Hence, the default
// removal of ampersand only may not be enough there, instead the whole
// parenthesis construct should be removed. Provide these filtering i18n
// messages so that translators can use Transcript for custom removal.
// """
if (!actions().isEmpty()) {
QAction* action = actions().first();
setToolTip(i18nc("@info:tooltip of custom triple button", "%1", action->toolTip()));
}
}
diff --git a/libs/widgetutils/KoGroupButton.h b/libs/widgetutils/KoGroupButton.h
index 25142ef8fc..7519c4b471 100644
--- a/libs/widgetutils/KoGroupButton.h
+++ b/libs/widgetutils/KoGroupButton.h
@@ -1,72 +1,71 @@
/* This file is part of the KDE libraries
Copyright (C) 2007 Aurélien Gâteau <agateau@kde.org>
Copyright (C) 2012 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
Copyright (C) 2012 Jarosław Staniek <staniek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KOGROUPBUTTON_H
#define KOGROUPBUTTON_H
#include "kritawidgetutils_export.h"
-// Qt
-#include <QToolButton>
+#include <KisHighlightedToolButton.h>
/**
* A thin tool button which can be visually grouped with other buttons.
*
* The group can thus look like one solid bar: ( button1 | button2 | button3 )
*
* For groupping layout can be used. For exclusive checkable behaviour assign QButtonGroup on the buttons.
*/
-class KRITAWIDGETUTILS_EXPORT KoGroupButton : public QToolButton
+class KRITAWIDGETUTILS_EXPORT KoGroupButton : public KisHighlightedToolButton
{
Q_OBJECT
Q_ENUMS( GroupPosition )
Q_PROPERTY( GroupPosition groupPosition READ groupPosition WRITE setGroupPosition )
public:
/**
* Position of the button within the button group what affects the appearance.
*/
enum GroupPosition {
NoGroup, //!< No particular position, gives the button unchanged appearance
GroupLeft, //!< The button is at the left of the group, so it would have rounded the left part
GroupRight, //!< The button is at the right of the group, so it would have rounded the right part
GroupCenter //!< The button is on the center of the group, so it would have separators on both sides
};
explicit KoGroupButton(GroupPosition position, QWidget* parent = 0);
/**
* Creates button with no NoGroup position.
*/
explicit KoGroupButton(QWidget* parent = 0);
~KoGroupButton() override;
void setGroupPosition(KoGroupButton::GroupPosition groupPosition);
KoGroupButton::GroupPosition groupPosition() const;
protected:
void paintEvent(QPaintEvent* event) override;
private:
class Private;
Private *const d;
};
#endif /* KOGROUPBUTTON_H */
diff --git a/libs/widgetutils/tests/dialogsavertestwidget.ui b/libs/widgetutils/tests/dialogsavertestwidget.ui
index 66e70b337d..ba0f54f926 100644
--- a/libs/widgetutils/tests/dialogsavertestwidget.ui
+++ b/libs/widgetutils/tests/dialogsavertestwidget.ui
@@ -1,103 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DialogSaverTestWidget</class>
<widget class="QWidget" name="DialogSaverTestWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>20</x>
<y>20</y>
<width>88</width>
<height>34</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QLineEdit" name="lineEdit">
<property name="geometry">
<rect>
<x>120</x>
<y>20</y>
<width>113</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QSpinBox" name="spinBox">
<property name="geometry">
<rect>
<x>120</x>
<y>60</y>
<width>52</width>
<height>32</height>
</rect>
</property>
<property name="singleStep">
<number>0</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
<widget class="QDoubleSpinBox" name="doubleSpinBox">
<property name="geometry">
<rect>
<x>120</x>
<y>100</y>
<width>71</width>
<height>32</height>
</rect>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
<widget class="QSlider" name="verticalSlider">
<property name="geometry">
<rect>
<x>330</x>
<y>30</y>
<width>20</width>
<height>160</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
<widget class="QCheckBox" name="checkBox">
<property name="geometry">
<rect>
<x>130</x>
<y>160</y>
<width>88</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
diff --git a/libs/widgetutils/xmlgui/kcheckaccelerators.cpp b/libs/widgetutils/xmlgui/kcheckaccelerators.cpp
index 49cbae1f1e..58cdc83ed0 100644
--- a/libs/widgetutils/xmlgui/kcheckaccelerators.cpp
+++ b/libs/widgetutils/xmlgui/kcheckaccelerators.cpp
@@ -1,317 +1,317 @@
/* This file is part of the KDE libraries
Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org)
Copyright (C) 1998, 1999, 2000 KDE Team
Copyright (C) 2008 Nick Shaforostoff <shaforostoff@kde.ru>
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 "kcheckaccelerators.h"
#include <QApplication>
#include <QCheckBox>
#include <QDialog>
#include <QShortcutEvent>
#include <QMouseEvent>
#include <QLayout>
#include <QMenuBar>
#include <QPushButton>
#include <QTabBar>
#include <QTextBrowser>
#include <QChar>
#include <QLabel>
#include <QComboBox>
#include <QGroupBox>
#include <QClipboard>
#include <QProcess>
#include <QDialogButtonBox>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <klocalizedstring.h>
#include <kacceleratormanager.h>
class KCheckAcceleratorsInitializer : public QObject
{
Q_OBJECT
public:
- explicit KCheckAcceleratorsInitializer(QObject *parent = Q_NULLPTR)
+ explicit KCheckAcceleratorsInitializer(QObject *parent = 0)
: QObject(parent)
{
}
public Q_SLOTS:
void initiateIfNeeded()
{
KConfigGroup cg(KSharedConfig::openConfig(), "Development");
QString sKey = cg.readEntry("CheckAccelerators").trimmed();
int key = 0;
if (!sKey.isEmpty()) {
QList<QKeySequence> cuts = QKeySequence::listFromString(sKey);
if (!cuts.isEmpty()) {
key = cuts.first()[0];
}
}
const bool autoCheck = cg.readEntry("AutoCheckAccelerators", true);
const bool copyWidgetText = cg.readEntry("CopyWidgetText", false);
if (!copyWidgetText && key == 0 && !autoCheck) {
deleteLater();
return;
}
new KCheckAccelerators(qApp, key, autoCheck, copyWidgetText);
deleteLater();
}
};
static void startupFunc()
{
// Call initiateIfNeeded once we're in the event loop
// This is to prevent using KSharedConfig before main() can set the app name
QCoreApplication *app = QCoreApplication::instance();
KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app);
QMetaObject::invokeMethod(initializer, "initiateIfNeeded", Qt::QueuedConnection);
}
Q_COREAPP_STARTUP_FUNCTION(startupFunc)
KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_, bool copyWidgetText_)
: QObject(parent)
, key(key_)
, block(false)
, autoCheck(autoCheck_)
, copyWidgetText(copyWidgetText_)
, drklash(0)
{
setObjectName(QStringLiteral("kapp_accel_filter"));
KConfigGroup cg(KSharedConfig::openConfig(), "Development");
alwaysShow = cg.readEntry("AlwaysShowCheckAccelerators", false);
copyWidgetTextCommand = cg.readEntry("CopyWidgetTextCommand", QString());
parent->installEventFilter(this);
connect(&autoCheckTimer, SIGNAL(timeout()), SLOT(autoCheckSlot()));
}
bool KCheckAccelerators::eventFilter(QObject *obj, QEvent *e)
{
if (block) {
return false;
}
switch (e->type()) { // just simplify debuggin
case QEvent::ShortcutOverride:
if (key && (static_cast<QKeyEvent *>(e)->key() == key)) {
block = true;
checkAccelerators(false);
block = false;
e->accept();
return true;
}
break;
case QEvent::ChildAdded:
case QEvent::ChildRemoved:
// Only care about widgets; this also avoids starting the timer in other threads
if (!static_cast<QChildEvent *>(e)->child()->isWidgetType()) {
break;
}
Q_FALLTHROUGH();
case QEvent::Resize:
case QEvent::LayoutRequest:
case QEvent::WindowActivate:
case QEvent::WindowDeactivate:
if (autoCheck) {
autoCheckTimer.setSingleShot(true);
autoCheckTimer.start(20); // 20 ms
}
break;
//case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
if (copyWidgetText && static_cast<QMouseEvent *>(e)->button() == Qt::MidButton) {
//kWarning()<<"obj"<<obj;
QWidget *w = static_cast<QWidget *>(obj)->childAt(static_cast<QMouseEvent *>(e)->pos());
if (!w) {
w = static_cast<QWidget *>(obj);
}
if (!w) {
return false;
}
//kWarning()<<"MouseButtonDblClick"<<w;
QString text;
if (qobject_cast<QLabel *>(w)) {
text = static_cast<QLabel *>(w)->text();
} else if (qobject_cast<QAbstractButton *>(w)) {
text = static_cast<QAbstractButton *>(w)->text();
} else if (qobject_cast<QComboBox *>(w)) {
text = static_cast<QComboBox *>(w)->currentText();
} else if (qobject_cast<QTabBar *>(w)) {
text = static_cast<QTabBar *>(w)->tabText(static_cast<QTabBar *>(w)->tabAt(static_cast<QMouseEvent *>(e)->pos()));
} else if (qobject_cast<QGroupBox *>(w)) {
text = static_cast<QGroupBox *>(w)->title();
} else if (qobject_cast<QMenu *>(obj)) {
QAction *a = static_cast<QMenu *>(obj)->actionAt(static_cast<QMouseEvent *>(e)->pos());
if (!a) {
return false;
}
text = a->text();
if (text.isEmpty()) {
text = a->iconText();
}
}
if (text.isEmpty()) {
return false;
}
if (static_cast<QMouseEvent *>(e)->modifiers() == Qt::ControlModifier) {
text.remove(QChar::fromLatin1('&'));
}
//kWarning()<<KGlobal::activeComponent().catalogName()<<text;
if (copyWidgetTextCommand.isEmpty()) {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text);
} else {
QProcess *script = new QProcess(this);
script->start(copyWidgetTextCommand.arg(text).arg(QFile::decodeName(KLocalizedString::applicationDomain())));
connect(script, SIGNAL(finished(int,QProcess::ExitStatus)), script, SLOT(deleteLater()));
}
e->accept();
return true;
//kWarning()<<"MouseButtonDblClick"<<static_cast<QWidget*>(obj)->childAt(static_cast<QMouseEvent*>(e)->globalPos());
}
return false;
case QEvent::Timer:
case QEvent::MouseMove:
case QEvent::Paint:
return false;
default:
// qDebug() << "KCheckAccelerators::eventFilter " << e->type() << " " << autoCheck;
break;
}
return false;
}
void KCheckAccelerators::autoCheckSlot()
{
if (block) {
autoCheckTimer.setSingleShot(true);
autoCheckTimer.start(20);
return;
}
block = true;
checkAccelerators(!alwaysShow);
block = false;
}
void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic)
{
if (drklash) {
return;
}
drklash = new QDialog(actWin);
drklash->setAttribute(Qt::WA_DeleteOnClose);
drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg"));
drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis"));
drklash->resize(500, 460);
QVBoxLayout *layout = new QVBoxLayout(drklash);
drklash_view = new QTextBrowser(drklash);
layout->addWidget(drklash_view);
QCheckBox *disableAutoCheck = 0;
if (automatic) {
disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash);
connect(disableAutoCheck, SIGNAL(toggled(bool)), SLOT(slotDisableCheck(bool)));
layout->addWidget(disableAutoCheck);
}
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash);
layout->addWidget(buttonBox);
connect(buttonBox, SIGNAL(rejected()), drklash, SLOT(close()));
if (disableAutoCheck) {
disableAutoCheck->setFocus();
} else {
drklash_view->setFocus();
}
}
void KCheckAccelerators::slotDisableCheck(bool on)
{
autoCheck = !on;
if (!on) {
autoCheckSlot();
}
}
void KCheckAccelerators::checkAccelerators(bool automatic)
{
QWidget *actWin = qApp->activeWindow();
if (!actWin) {
return;
}
KAcceleratorManager::manage(actWin);
QString a, c, r;
KAcceleratorManager::last_manage(a, c, r);
if (automatic) { // for now we only show dialogs on F12 checks
return;
}
if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) {
return;
}
QString s;
if (! c.isEmpty()) {
s += i18n("<h2>Accelerators changed</h2>");
s += QStringLiteral("<table border><tr><th><b>");
s += i18n("Old Text");
s += QStringLiteral("</b></th><th><b>");
s += i18n("New Text");
s += QStringLiteral("</b></th></tr>");
s += c;
s += QStringLiteral("</table>");
}
if (! r.isEmpty()) {
s += i18n("<h2>Accelerators removed</h2>");
s += QStringLiteral("<table border><tr><th><b>");
s += i18n("Old Text");
s += QStringLiteral("</b></th></tr>");
s += r;
s += QStringLiteral("</table>");
}
if (! a.isEmpty()) {
s += i18n("<h2>Accelerators added (just for your info)</h2>");
s += QStringLiteral("<table border><tr><th><b>");
s += i18n("New Text");
s += QStringLiteral("</b></th></tr>");
s += a;
s += QStringLiteral("</table>");
}
createDialog(actWin, automatic);
drklash_view->setHtml(s);
drklash->show();
drklash->raise();
// dlg will be destroyed before returning
}
#include "kcheckaccelerators.moc"
diff --git a/libs/widgetutils/xmlgui/kkeysequencewidget.cpp b/libs/widgetutils/xmlgui/kkeysequencewidget.cpp
index 5f598f88a5..31f5e8ce13 100644
--- a/libs/widgetutils/xmlgui/kkeysequencewidget.cpp
+++ b/libs/widgetutils/xmlgui/kkeysequencewidget.cpp
@@ -1,786 +1,790 @@
/* This file is part of the KDE libraries
Copyright (C) 1998 Mark Donohoe <donohoe@kde.org>
Copyright (C) 2001 Ellis Whitehead <ellis@kde.org>
Copyright (C) 2007 Andreas Hartmetz <ahartmetz@gmail.com>
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 "kkeysequencewidget.h"
#include "kkeysequencewidget_p.h"
#include "config-xmlgui.h"
#include <QAction>
#include <QKeyEvent>
#include <QTimer>
#include <QHash>
#include <QHBoxLayout>
#include <QToolButton>
#include <QApplication>
#include <QDebug>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include <kkeyserver.h>
#include "kactioncollection.h"
#include <kis_icon_utils.h>
uint qHash(const QKeySequence &seq)
{
return qHash(seq.toString());
}
class KKeySequenceWidgetPrivate
{
public:
KKeySequenceWidgetPrivate(KKeySequenceWidget *q);
void init();
static QKeySequence appendToSequence(const QKeySequence &seq, int keyQt);
static bool isOkWhenModifierless(int keyQt);
void updateShortcutDisplay();
void startRecording();
/**
* Conflicts the key sequence @a seq with a current standard
* shortcut?
*
* Pops up a dialog asking overriding the conflict is OK.
*/
bool conflictWithStandardShortcuts(const QKeySequence &seq);
/**
* Conflicts the key sequence @a seq with a current local
* shortcut?
*/
bool conflictWithLocalShortcuts(const QKeySequence &seq);
/**
* Conflicts the key sequence @a seq conflict with Windows shortcut keys?
*/
bool conflictWithGlobalShortcuts(const QKeySequence &seq);
/**
* Get permission to steal the shortcut @seq from the standard shortcut @a std.
*/
bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq);
bool checkAgainstStandardShortcuts() const
{
return checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts;
}
bool checkAgainstGlobalShortcuts() const
{
return checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts;
}
bool checkAgainstLocalShortcuts() const
{
return checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts;
}
void controlModifierlessTimout()
{
if (nKey != 0 && !modifierKeys) {
// No modifier key pressed currently. Start the timeout
modifierlessTimeout.start(600);
} else {
// A modifier is pressed. Stop the timeout
modifierlessTimeout.stop();
}
}
void cancelRecording()
{
keySequence = oldKeySequence;
doneRecording();
}
//private slot
void doneRecording(bool validate = true);
//members
KKeySequenceWidget *const q;
QHBoxLayout *layout;
KKeySequenceButton *keyButton;
QToolButton *clearButton;
QKeySequence keySequence;
QKeySequence oldKeySequence;
QTimer modifierlessTimeout;
bool allowModifierless;
uint nKey;
uint modifierKeys;
bool isRecording;
bool multiKeyShortcutsAllowed;
QString componentName;
//! Check the key sequence against KStandardShortcut::find()
KKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes;
/**
* The list of action to check against for conflict shortcut
*/
QList<QAction *> checkList; // deprecated
/**
* The list of action collections to check against for conflict shortcut
*/
QList<KActionCollection *> checkActionCollections;
/**
* The action to steal the shortcut from.
*/
QList<QAction *> stealActions;
bool stealShortcuts(const QList<QAction *> &actions, const QKeySequence &seq);
void wontStealShortcut(QAction *item, const QKeySequence &seq);
};
KKeySequenceWidgetPrivate::KKeySequenceWidgetPrivate(KKeySequenceWidget *q)
: q(q)
, layout(0)
, keyButton(0)
, clearButton(0)
, allowModifierless(false)
, nKey(0)
, modifierKeys(0)
, isRecording(false)
, multiKeyShortcutsAllowed(true)
, componentName()
, checkAgainstShortcutTypes(KKeySequenceWidget::LocalShortcuts | KKeySequenceWidget::GlobalShortcuts)
, stealActions()
{}
bool KKeySequenceWidgetPrivate::stealShortcuts(
const QList<QAction *> &actions,
const QKeySequence &seq)
{
const int listSize = actions.size();
QString title = i18ncp("%1 is the number of conflicts", "Shortcut Conflict", "Shortcut Conflicts", listSize);
QString conflictingShortcuts;
Q_FOREACH (const QAction *action, actions) {
conflictingShortcuts += i18n("Shortcut '%1' for action '%2'\n",
action->shortcut().toString(QKeySequence::NativeText),
KLocalizedString::removeAcceleratorMarker(action->text()));
}
QString message = i18ncp("%1 is the number of ambiguous shortcut clashes (hidden)",
"The \"%2\" shortcut is ambiguous with the following shortcut.\n"
"Do you want to assign an empty shortcut to this action?\n"
"%3",
"The \"%2\" shortcut is ambiguous with the following shortcuts.\n"
"Do you want to assign an empty shortcut to these actions?\n"
"%3",
listSize,
seq.toString(QKeySequence::NativeText),
conflictingShortcuts);
if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
return false;
}
return true;
}
void KKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq)
{
QString title(i18n("Shortcut conflict"));
QString msg(i18n("<qt>The '%1' key combination is already used by the <b>%2</b> action.<br>"
"Please select a different one.</qt>", seq.toString(QKeySequence::NativeText),
KLocalizedString::removeAcceleratorMarker(item->text())));
KMessageBox::sorry(q, msg, title);
}
KKeySequenceWidget::KKeySequenceWidget(QWidget *parent)
: QWidget(parent),
d(new KKeySequenceWidgetPrivate(this))
{
d->init();
setFocusProxy(d->keyButton);
connect(d->keyButton, SIGNAL(clicked()), this, SLOT(captureKeySequence()));
connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearKeySequence()));
connect(&d->modifierlessTimeout, SIGNAL(timeout()), this, SLOT(doneRecording()));
//TODO: how to adopt style changes at runtime?
/*QFont modFont = d->clearButton->font();
modFont.setStyleHint(QFont::TypeWriter);
d->clearButton->setFont(modFont);*/
d->updateShortcutDisplay();
}
void KKeySequenceWidgetPrivate::init()
{
layout = new QHBoxLayout(q);
layout->setMargin(0);
keyButton = new KKeySequenceButton(this, q);
keyButton->setFocusPolicy(Qt::StrongFocus);
keyButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("configure")));
keyButton->setToolTip(i18n("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+A: hold the Ctrl key and press A."));
layout->addWidget(keyButton);
clearButton = new QToolButton(q);
layout->addWidget(clearButton);
if (qApp->isLeftToRight()) {
clearButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("edit-clear-locationbar-rtl")));
} else {
clearButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("edit-clear-locationbar-ltr")));
}
}
KKeySequenceWidget::~KKeySequenceWidget()
{
delete d;
}
KKeySequenceWidget::ShortcutTypes KKeySequenceWidget::checkForConflictsAgainst() const
{
return d->checkAgainstShortcutTypes;
}
void KKeySequenceWidget::setComponentName(const QString &componentName)
{
d->componentName = componentName;
}
bool KKeySequenceWidget::multiKeyShortcutsAllowed() const
{
return d->multiKeyShortcutsAllowed;
}
void KKeySequenceWidget::setMultiKeyShortcutsAllowed(bool allowed)
{
d->multiKeyShortcutsAllowed = allowed;
}
void KKeySequenceWidget::setCheckForConflictsAgainst(ShortcutTypes types)
{
d->checkAgainstShortcutTypes = types;
}
void KKeySequenceWidget::setModifierlessAllowed(bool allow)
{
d->allowModifierless = allow;
}
bool KKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const
{
if (keySequence.isEmpty()) {
// qDebug() << "Key sequence" << keySequence.toString() << "is empty and available.";
return true;
}
bool hasConflict = (d->conflictWithLocalShortcuts(keySequence)
|| d->conflictWithGlobalShortcuts(keySequence)
|| d->conflictWithStandardShortcuts(keySequence));
if (hasConflict) {
/* qInfo() << "Key sequence" << keySequence.toString() << "has an unresolvable conflict." <<
QString("Local conflict: %1. Windows conflict: %2. Standard Shortcut conflict: %3") \
.arg(d->conflictWithLocalShortcuts(keySequence)) \
.arg(d->conflictWithGlobalShortcuts(keySequence)) \
.arg(d->conflictWithStandardShortcuts(keySequence)); */
}
return !(hasConflict);
}
bool KKeySequenceWidget::isModifierlessAllowed()
{
return d->allowModifierless;
}
void KKeySequenceWidget::setClearButtonShown(bool show)
{
d->clearButton->setVisible(show);
}
void KKeySequenceWidget::setCheckActionCollections(const QList<KActionCollection *> &actionCollections)
{
d->checkActionCollections = actionCollections;
}
//slot
void KKeySequenceWidget::captureKeySequence()
{
d->startRecording();
}
QKeySequence KKeySequenceWidget::keySequence() const
{
return d->keySequence;
}
//slot
void KKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate)
{
// oldKeySequence holds the key sequence before recording started, if setKeySequence()
// is called while not recording then set oldKeySequence to the existing sequence so
// that the keySequenceChanged() signal is emitted if the new and previous key
// sequences are different
if (!d->isRecording) {
d->oldKeySequence = d->keySequence;
}
d->keySequence = seq;
d->doneRecording(validate == Validate);
}
//slot
void KKeySequenceWidget::clearKeySequence()
{
setKeySequence(QKeySequence());
}
//slot
void KKeySequenceWidget::applyStealShortcut()
{
QSet<KActionCollection *> changedCollections;
Q_FOREACH (QAction *stealAction, d->stealActions) {
// Stealing a shortcut means setting it to an empty one.
stealAction->setShortcuts(QList<QKeySequence>());
// The following code will find the action we are about to
// steal from and save it's actioncollection.
KActionCollection *parentCollection = 0;
foreach (KActionCollection *collection, d->checkActionCollections) {
if (collection->actions().contains(stealAction)) {
parentCollection = collection;
break;
}
}
// Remember the changed collection
if (parentCollection) {
changedCollections.insert(parentCollection);
}
}
Q_FOREACH (KActionCollection *col, changedCollections) {
col->writeSettings();
}
d->stealActions.clear();
}
void KKeySequenceWidgetPrivate::startRecording()
{
nKey = 0;
modifierKeys = 0;
oldKeySequence = keySequence;
keySequence = QKeySequence();
isRecording = true;
keyButton->grabKeyboard();
if (!QWidget::keyboardGrabber()) {
qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active";
}
keyButton->setDown(true);
updateShortcutDisplay();
}
void KKeySequenceWidgetPrivate::doneRecording(bool validate)
{
modifierlessTimeout.stop();
isRecording = false;
keyButton->releaseKeyboard();
keyButton->setDown(false);
stealActions.clear();
if (keySequence == oldKeySequence) {
// The sequence hasn't changed
updateShortcutDisplay();
return;
}
if (validate && !q->isKeySequenceAvailable(keySequence)) {
// The sequence had conflicts and the user said no to stealing it
keySequence = oldKeySequence;
} else {
emit q->keySequenceChanged(keySequence);
}
updateShortcutDisplay();
}
bool KKeySequenceWidgetPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence)
{
// This could hold some OS-specific stuff, or it could be linked back with
// the KDE global shortcut code at some point in the future.
#ifdef Q_OS_WIN
#else
#endif
Q_UNUSED(keySequence);
return false;
}
bool shortcutsConflictWith(const QList<QKeySequence> &shortcuts, const QKeySequence &needle)
{
if (needle.isEmpty() || needle.toString(QKeySequence::NativeText).isEmpty()) {
return false;
}
foreach (const QKeySequence &sequence, shortcuts) {
if (sequence.isEmpty()) {
continue;
}
if (sequence.matches(needle) != QKeySequence::NoMatch
|| needle.matches(sequence) != QKeySequence::NoMatch) {
return true;
}
}
return false;
}
bool KKeySequenceWidgetPrivate::conflictWithLocalShortcuts(const QKeySequence &keySequence)
{
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts)) {
return false;
}
// We have actions both in the deprecated checkList and the
// checkActionCollections list. Add all the actions to a single list to
// be able to process them in a single loop below.
// Note that this can't be done in setCheckActionCollections(), because we
// keep pointers to the action collections, and between the call to
// setCheckActionCollections() and this function some actions might already be
// removed from the collection again.
QList<QAction *> allActions;
allActions += checkList;
foreach (KActionCollection *collection, checkActionCollections) {
allActions += collection->actions();
}
// Because of multikey shortcuts we can have clashes with many shortcuts.
//
// Example 1:
//
// Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F'
// and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as
// 'activatedAmbiguously()' for obvious reasons.
//
// Example 2:
//
// Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'.
// This will shadow 'CTRL-X' for the same reason as above.
//
// Example 3:
//
// Some weird combination of Example 1 and 2 with three shortcuts using
// 1/2/3 key shortcuts. I think you can imagine.
QList<QAction *> conflictingActions;
//find conflicting shortcuts with existing actions
foreach (QAction *qaction, allActions) {
if (shortcutsConflictWith(qaction->shortcuts(), keySequence)) {
// A conflict with a KAction. If that action is configurable
// ask the user what to do. If not reject this keySequence.
if (checkActionCollections.first()->isShortcutsConfigurable(qaction)) {
conflictingActions.append(qaction);
} else {
wontStealShortcut(qaction, keySequence);
return true;
}
}
}
if (conflictingActions.isEmpty()) {
// No conflicting shortcuts found.
return false;
}
if (stealShortcuts(conflictingActions, keySequence)) {
stealActions = conflictingActions;
// Announce that the user agreed to override the other shortcut
Q_FOREACH (QAction *stealAction, stealActions) {
emit q->stealShortcut(
keySequence,
stealAction);
}
return false;
} else {
return true;
}
}
bool KKeySequenceWidgetPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence)
{
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts)) {
return false;
}
KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence);
if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) {
return true;
}
return false;
}
bool KKeySequenceWidgetPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
{
QString title = i18n("Conflict with Standard Application Shortcut");
QString message = i18n("The '%1' key combination is also used for the standard action "
"\"%2\" that some applications use.\n"
"Do you really want to use it as a global shortcut as well?",
seq.toString(QKeySequence::NativeText), KStandardShortcut::label(std));
if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
return false;
}
return true;
}
void KKeySequenceWidgetPrivate::updateShortcutDisplay()
{
//empty string if no non-modifier was pressed
QString s = keySequence.toString(QKeySequence::NativeText);
s.replace(QLatin1Char('&'), QStringLiteral("&&"));
if (isRecording) {
if (modifierKeys) {
if (!s.isEmpty()) {
s.append(QLatin1Char(','));
}
- if (modifierKeys & Qt::META) {
- s += KKeyServer::modToStringUser(Qt::META) + QLatin1Char('+');
+ if (modifierKeys & Qt::MetaModifier) {
+ s += QKeySequence(Qt::MetaModifier).toString(QKeySequence::NativeText);
}
-#if defined(Q_OS_MACOS)
- if (modifierKeys & Qt::ALT) {
- s += KKeyServer::modToStringUser(Qt::ALT) + QLatin1Char('+');
+#if defined(Q_OS_MAC)
+ if (modifierKeys & Qt::AltModifier) {
+ s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText);
}
- if (modifierKeys & Qt::CTRL) {
- s += KKeyServer::modToStringUser(Qt::CTRL) + QLatin1Char('+');
+ if (modifierKeys & Qt::ControlModifier) {
+ s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText);
}
#else
- if (modifierKeys & Qt::CTRL) {
- s += KKeyServer::modToStringUser(Qt::CTRL) + QLatin1Char('+');
+ if (modifierKeys & Qt::ControlModifier) {
+ s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText);
}
- if (modifierKeys & Qt::ALT) {
- s += KKeyServer::modToStringUser(Qt::ALT) + QLatin1Char('+');
+ if (modifierKeys & Qt::AltModifier) {
+ s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText);
}
#endif
- if (modifierKeys & Qt::SHIFT) {
- s += KKeyServer::modToStringUser(Qt::SHIFT) + QLatin1Char('+');
+ if (modifierKeys & Qt::ShiftModifier) {
+ s += QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText);
+ }
+ if (modifierKeys & Qt::KeypadModifier) {
+ s += QKeySequence(Qt::KeypadModifier).toString(QKeySequence::NativeText);
}
} else if (nKey == 0) {
s = i18nc("What the user inputs now will be taken as the new shortcut", "Input");
}
//make it clear that input is still going on
s.append(QStringLiteral(" ..."));
}
if (s.isEmpty()) {
s = i18nc("No shortcut defined", "None");
}
s.prepend(QLatin1Char(' '));
s.append(QLatin1Char(' '));
keyButton->setText(s);
+
}
KKeySequenceButton::~KKeySequenceButton()
{
}
//prevent Qt from special casing Tab and Backtab
bool KKeySequenceButton::event(QEvent *e)
{
if (d->isRecording && e->type() == QEvent::KeyPress) {
keyPressEvent(static_cast<QKeyEvent *>(e));
return true;
}
// The shortcut 'alt+c' ( or any other dialog local action shortcut )
// ended the recording and triggered the action associated with the
// action. In case of 'alt+c' ending the dialog. It seems that those
// ShortcutOverride events get sent even if grabKeyboard() is active.
if (d->isRecording && e->type() == QEvent::ShortcutOverride) {
e->accept();
return true;
}
if (d->isRecording && e->type() == QEvent::ContextMenu) {
// is caused by Qt::Key_Menu
e->accept();
return true;
}
return QPushButton::event(e);
}
void KKeySequenceButton::keyPressEvent(QKeyEvent *e)
{
int keyQt = e->key();
if (keyQt == -1) {
// Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key.
// We cannot do anything useful with those (several keys have -1, indistinguishable)
// and QKeySequence.toString() will also yield a garbage string.
KMessageBox::sorry(this,
i18n("The key you just pressed is not supported by Qt."),
i18n("Unsupported Key"));
return d->cancelRecording();
}
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
//don't have the return or space key appear as first key of the sequence when they
//were pressed to start editing - catch and them and imitate their effect
if (!d->isRecording && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) {
d->startRecording();
d->modifierKeys = newModifiers;
d->updateShortcutDisplay();
return;
}
// We get events even if recording isn't active.
if (!d->isRecording) {
return QPushButton::keyPressEvent(e);
}
e->accept();
d->modifierKeys = newModifiers;
switch (keyQt) {
case Qt::Key_AltGr: //or else we get unicode salad
return;
case Qt::Key_Shift:
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_Meta:
case Qt::Key_Super_L:
case Qt::Key_Super_R:
d->controlModifierlessTimout();
d->updateShortcutDisplay();
break;
default:
if (d->nKey == 0 && !(d->modifierKeys & ~Qt::SHIFT)) {
// It's the first key and no modifier pressed. Check if this is
// allowed
if (!(KKeySequenceWidgetPrivate::isOkWhenModifierless(keyQt)
|| d->allowModifierless)) {
// No it's not
return;
}
}
// We now have a valid key press.
if (keyQt) {
if ((keyQt == Qt::Key_Backtab) && (d->modifierKeys & Qt::SHIFT)) {
keyQt = Qt::Key_Tab | d->modifierKeys;
} else if (KKeyServer::isShiftAsModifierAllowed(keyQt)) {
keyQt |= d->modifierKeys;
} else {
keyQt |= (d->modifierKeys & ~Qt::SHIFT);
}
if (d->nKey == 0) {
d->keySequence = QKeySequence(keyQt);
} else {
d->keySequence =
KKeySequenceWidgetPrivate::appendToSequence(d->keySequence, keyQt);
}
d->nKey++;
if ((!d->multiKeyShortcutsAllowed) || (d->nKey >= 4)) {
d->doneRecording();
return;
}
d->controlModifierlessTimout();
d->updateShortcutDisplay();
}
}
}
void KKeySequenceButton::keyReleaseEvent(QKeyEvent *e)
{
if (e->key() == -1) {
// ignore garbage, see keyPressEvent()
return;
}
if (!d->isRecording) {
return QPushButton::keyReleaseEvent(e);
}
e->accept();
uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
//if a modifier that belongs to the shortcut was released...
if ((newModifiers & d->modifierKeys) < d->modifierKeys) {
d->modifierKeys = newModifiers;
d->controlModifierlessTimout();
d->updateShortcutDisplay();
}
}
//static
QKeySequence KKeySequenceWidgetPrivate::appendToSequence(const QKeySequence &seq, int keyQt)
{
switch (seq.count()) {
case 0:
return QKeySequence(keyQt);
case 1:
return QKeySequence(seq[0], keyQt);
case 2:
return QKeySequence(seq[0], seq[1], keyQt);
case 3:
return QKeySequence(seq[0], seq[1], seq[2], keyQt);
default:
return seq;
}
}
//static
bool KKeySequenceWidgetPrivate::isOkWhenModifierless(int keyQt)
{
//this whole function is a hack, but especially the first line of code
if (QKeySequence(keyQt).toString().length() == 1) {
return false;
}
switch (keyQt) {
case Qt::Key_Return:
case Qt::Key_Space:
case Qt::Key_Tab:
case Qt::Key_Backtab: //does this ever happen?
case Qt::Key_Backspace:
case Qt::Key_Delete:
return false;
default:
return true;
}
}
#include "moc_kkeysequencewidget.cpp"
#include "moc_kkeysequencewidget_p.cpp"
diff --git a/libs/widgetutils/xmlgui/kmenumenuhandler_p.cpp b/libs/widgetutils/xmlgui/kmenumenuhandler_p.cpp
index 91297a857d..e4b968cd73 100644
--- a/libs/widgetutils/xmlgui/kmenumenuhandler_p.cpp
+++ b/libs/widgetutils/xmlgui/kmenumenuhandler_p.cpp
@@ -1,244 +1,249 @@
/* This file is part of the KDE project
Copyright (C) 2006 Olivier Goffart <ogoffart@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License 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 "kmenumenuhandler_p.h"
#include "kxmlguibuilder.h"
#include "kxmlguiclient.h"
#include "kxmlguifactory.h"
#include "kactioncollection.h"
#include "kmainwindow.h"
#include "ktoolbar.h"
#include "kshortcutwidget.h"
#include <QAction>
#include <QContextMenuEvent>
#include <QDialog>
#include <QDialogButtonBox>
#include <QWidget>
#include <QDomDocument>
#include <QDomNode>
#include <QMenu>
#include <QVBoxLayout>
#include <QDebug>
#include <kselectaction.h>
#include <klocalizedstring.h>
namespace KDEPrivate
{
KMenuMenuHandler::KMenuMenuHandler(KXMLGUIBuilder *builder)
: QObject(), m_builder(builder), m_popupMenu(0), m_popupAction(0), m_contextMenu(0)
{
m_toolbarAction = new KSelectAction(i18n("Add to Toolbar"), this);
connect(m_toolbarAction, SIGNAL(triggered(int)), this, SLOT(slotAddToToolBar(int)));
}
void KMenuMenuHandler::insertMenu(QMenu *popup)
{
popup->installEventFilter(this);
}
bool KMenuMenuHandler::eventFilter(QObject *watched, QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress:
if (m_contextMenu && m_contextMenu->isVisible()) {
m_contextMenu->hide();
return true;
}
break;
case QEvent::MouseButtonRelease:
if (m_contextMenu && m_contextMenu->isVisible()) {
return true;
}
break;
case QEvent::ContextMenu: {
QContextMenuEvent *e = static_cast<QContextMenuEvent *>(event);
QMenu *menu = static_cast<QMenu *>(watched);
if (e->reason() == QContextMenuEvent::Mouse) {
showContextMenu(menu, e->pos());
} else if (menu->activeAction()) {
showContextMenu(menu, menu->actionGeometry(menu->activeAction()).center());
}
}
event->accept();
return true;
default:
break;
}
return false;
}
void KMenuMenuHandler::buildToolbarAction()
{
KMainWindow *window = qobject_cast<KMainWindow *>(m_builder->widget());
if (!window) {
return;
}
QStringList toolbarlist;
foreach (KToolBar *b, window->toolBars()) {
toolbarlist << (b->windowTitle().isEmpty() ? b->objectName() : b->windowTitle());
}
m_toolbarAction->setItems(toolbarlist);
}
static KActionCollection *findParentCollection(KXMLGUIFactory *factory, QAction *action)
{
foreach (KXMLGUIClient *client, factory->clients()) {
KActionCollection *collection = client->actionCollection();
// if the call to actions() is too slow, add KActionCollection::contains(QAction*).
if (collection->actions().contains(action)) {
return collection;
}
}
return 0;
}
void KMenuMenuHandler::slotSetShortcut()
{
if (!m_popupMenu || !m_popupAction) {
return;
}
QDialog dialog(m_builder->widget());
dialog.setLayout(new QVBoxLayout);
KShortcutWidget swidget(&dialog);
swidget.setShortcut(m_popupAction->shortcuts());
dialog.layout()->addWidget(&swidget);
QDialogButtonBox box(&dialog);
box.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(&box, SIGNAL(accepted()), &dialog, SLOT(accept()));
connect(&box, SIGNAL(rejected()), &dialog, SLOT(reject()));
dialog.layout()->addWidget(&box);
KActionCollection *parentCollection = 0;
if (dynamic_cast<KXMLGUIClient *>(m_builder)) {
QList<KActionCollection *> checkCollections;
KXMLGUIFactory *factory = dynamic_cast<KXMLGUIClient *>(m_builder)->factory();
parentCollection = findParentCollection(factory, m_popupAction);
foreach (KXMLGUIClient *client, factory->clients()) {
checkCollections += client->actionCollection();
}
swidget.setCheckActionCollections(checkCollections);
}
if (dialog.exec()) {
m_popupAction->setShortcuts(swidget.shortcut());
swidget.applyStealShortcut();
if (parentCollection) {
parentCollection->writeSettings();
}
}
}
void KMenuMenuHandler::slotAddToToolBar(int tb)
{
KMainWindow *window = qobject_cast<KMainWindow *>(m_builder->widget());
if (!window) {
return;
}
if (!m_popupMenu || !m_popupAction) {
return;
}
KXMLGUIFactory *factory = dynamic_cast<KXMLGUIClient *>(m_builder)->factory();
QString actionName = m_popupAction->objectName(); // set by KActionCollection::addAction
KActionCollection *collection = 0;
if (factory) {
collection = findParentCollection(factory, m_popupAction);
}
if (!collection) {
qWarning() << "Cannot find the action collection for action " << actionName;
return;
}
KToolBar *toolbar = window->toolBars()[tb];
toolbar->addAction(m_popupAction);
const KXMLGUIClient *client = collection->parentGUIClient();
QString xmlFile = client->localXMLFile();
QDomDocument document;
document.setContent(KXMLGUIFactory::readConfigFile(client->xmlFile(), client->componentName()));
QDomElement elem = document.documentElement().toElement();
const QLatin1String tagToolBar("ToolBar");
const QLatin1String attrNoEdit("noEdit");
const QLatin1String attrName("name");
QDomElement toolbarElem;
QDomNode n = elem.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
QDomElement elem = n.toElement();
if (!elem.isNull() && elem.tagName() == tagToolBar && elem.attribute(attrName) == toolbar->objectName()) {
if (elem.attribute(attrNoEdit) == QStringLiteral("true")) {
qWarning() << "The toolbar is not editable";
return;
}
toolbarElem = elem;
break;
}
}
if (toolbarElem.isNull()) {
toolbarElem = document.createElement(tagToolBar);
toolbarElem.setAttribute(attrName, toolbar->objectName());
elem.appendChild(toolbarElem);
}
KXMLGUIFactory::findActionByName(toolbarElem, actionName, true);
KXMLGUIFactory::saveConfigFile(document, xmlFile);
}
void KMenuMenuHandler::showContextMenu(QMenu *menu, const QPoint &pos)
{
Q_ASSERT(!m_popupMenu);
Q_ASSERT(!m_popupAction);
Q_ASSERT(!m_contextMenu);
+ QAction *action = menu->actionAt(pos);
+ if (!action || action->isSeparator()) {
+ return;
+ }
+
m_popupMenu = menu;
- m_popupAction = menu->actionAt(pos);
+ m_popupAction = action;
m_contextMenu = new QMenu;
m_contextMenu->addAction(i18n("Configure Shortcut..."), this, SLOT(slotSetShortcut()));
KMainWindow *window = qobject_cast<KMainWindow *>(m_builder->widget());
if (window) {
m_contextMenu->addAction(m_toolbarAction);
buildToolbarAction();
}
m_contextMenu->exec(menu->mapToGlobal(pos));
delete m_contextMenu;
m_contextMenu = 0;
m_popupAction = 0;
m_popupMenu = 0;
}
} //END namespace KDEPrivate
diff --git a/packaging/linux/appimage/build-krita.sh b/packaging/linux/appimage/build-krita.sh
index f94c56db23..84288455e0 100755
--- a/packaging/linux/appimage/build-krita.sh
+++ b/packaging/linux/appimage/build-krita.sh
@@ -1,52 +1,52 @@
#!/bin/bash
# Halt on errors and be verbose about what we are doing
set -e
set -x
# Read in our parameters
export BUILD_PREFIX=$1
export KRITA_SOURCES=$2
# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment.
# That's not always the case, so make sure it is
export LC_ALL=en_US.UTF-8
export LANG=en_us.UTF-8
# We want to use $prefix/deps/usr/ for all our dependencies
export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/
export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/
# Setup variables needed to help everything find what we build
export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib:$LD_LIBRARY_PATH
export PATH=$DEPS_INSTALL_PREFIX/bin:$PATH
export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig:$DEPS_INSTALL_PREFIX/lib/pkgconfig:/usr/lib/pkgconfig:$PKG_CONFIG_PATH
export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH
export PYTHONPATH=$DEPS_INSTALL_PREFIX/sip:$DEPS_INSTALL_PREFIX/lib/python3.5/site-packages:$DEPS_INSTALL_PREFIX/lib/python3.5
export PYTHONHOME=$DEPS_INSTALL_PREFIX
# Make sure our build directory exists
if [ ! -d $BUILD_PREFIX/krita-build/ ] ; then
mkdir -p $BUILD_PREFIX/krita-build/
fi
# Now switch to it
cd $BUILD_PREFIX/krita-build/
# Determine how many CPUs we have
CPU_COUNT=`grep processor /proc/cpuinfo | wc -l`
# Configure Krita
cmake $KRITA_SOURCES \
-DCMAKE_INSTALL_PREFIX:PATH=$BUILD_PREFIX/krita.appdir/usr \
-DDEFINE_NO_DEPRECATED=1 \
-DCMAKE_BUILD_TYPE=Release \
-DFOUNDATION_BUILD=1 \
- -DHIDE_SAFE_ASSERTS=OFF \
+ -DHIDE_SAFE_ASSERTS=ON \
-DBUILD_TESTING=FALSE \
-DPYQT_SIP_DIR_OVERRIDE=$DEPS_INSTALL_PREFIX/share/sip/ \
-DHAVE_MEMORY_LEAK_TRACKER=FALSE
# Build and Install Krita (ready for the next phase)
make -j$CPU_COUNT install
diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml
index f60290ac74..b80c3a1d40 100644
--- a/packaging/linux/snap/snapcraft.yaml
+++ b/packaging/linux/snap/snapcraft.yaml
@@ -1,149 +1,161 @@
name: krita
-version: 4.1.7.101
+version: 4.2.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: qt5-launch usr/bin/krita
plugs: [x11, unity7, home, opengl, network, network-bind, removable-media, desktop, desktop-legacy]
parts:
krita:
plugin: cmake
configflags:
- "-DCMAKE_INSTALL_PREFIX=/usr"
- "-DCMAKE_BUILD_TYPE=Release"
- "-DENABLE_TESTING=OFF"
- "-DBUILD_TESTING=OFF"
- "-DKDE_SKIP_TEST_SETTINGS=ON"
- source: https://download.kde.org/stable/krita/4.1.7/krita-4.1.7.101.tar.gz
+ source: https://download.kde.org/stable/krita/4.2.1/krita-4.2.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
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
- 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-26
- 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
- - libqt5multimedia5
- 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
+ - libquazip5-1
+ - libqt5quick5
+ - libqt5quickparticles5
+ - libqt5quickshapes5
+ - qt5-qmltooling-plugins
+ - libqt5qml5
+ - libqt5multimedia5
+ - libqt5multimediagsttools5
+ - libqt5multimediaquick5
+ - libqt5multimediawidgets5
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: .
organize:
qt5-launch: bin/qt5-launch
kf5-locale-gen: bin/kf5-locale-gen
diff --git a/packaging/macos/default.style b/packaging/macos/default.style
index f112c82fd8..8a3fb53b59 100644
--- a/packaging/macos/default.style
+++ b/packaging/macos/default.style
@@ -1,19 +1,19 @@
tell application "Finder"
tell disk "%s"
open
set current view of container window to icon view
set toolbar visible of container window to false
set statusbar visible of container window to false
- set the bounds of container window to {300, 51, 1070, 487}
+ set the bounds of container window to {273, 122, 1014, 484}
set theViewOptions to the icon view options of container window
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 80
set background picture of theViewOptions to file ".background:%s"
- set position of item "krita.app" of container window to {281, 287}
- set position of item "Applications" of container window to {596, 285}
- set position of item "Terms of Use" of container window to {598, 132}
+ set position of item "krita.app" of container window to {187, 124}
+ set position of item "Applications" of container window to {454, 124}
+ set position of item "Terms of Use" of container window to {592, 124}
update without registering applications
delay 1
close
end tell
-end tell
+end tell
\ No newline at end of file
diff --git a/packaging/macos/krita-4.1_dmgBG.jpg b/packaging/macos/krita-4.1_dmgBG.jpg
deleted file mode 100644
index e7a14cd86d..0000000000
Binary files a/packaging/macos/krita-4.1_dmgBG.jpg and /dev/null differ
diff --git a/packaging/macos/krita_dmgBG.jpg b/packaging/macos/krita_dmgBG.jpg
new file mode 100644
index 0000000000..c425c42701
Binary files /dev/null and b/packaging/macos/krita_dmgBG.jpg differ
diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh
index 6647cadd27..edcb87ce6e 100755
--- a/packaging/macos/osxbuild.sh
+++ b/packaging/macos/osxbuild.sh
@@ -1,524 +1,536 @@
#!/usr/bin/env bash
# osxbuild.sh automates building and installing of krita and krita dependencies
# for OSX, the script only needs you to set BUILDROOT environment to work
# properly.
#
# Run with no args for a short help about each command.
# builddeps: Attempts to build krita dependencies in the necessary order,
# intermediate steps for creating symlinks and fixing rpath of some
# packages midway is also managed. Order goes from top to bottom, to add
# new steps just place them in the proper place.
# rebuilddeps: This re-runs all make and make install of dependencies 3rdparty
# this was needed as deleting the entire install directory an rerunning build
# step for dependencies does not install if they are already built. This step
# forces installation. Have not tested it lately so it might not be needed anymore
# build: Runs cmake build and make step for krita sources. It always run cmake step, so
# it might take a bit longer than a pure <make> on the source tree. The script tries
# to set the make flag -jN to a proper N.
# install: Runs install step for krita sources.
# fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by
# default it fails to set a proper @rpath
# buildinstall: Runs build, install and fixboost steps.#
if test -z $BUILDROOT; then
echo "ERROR: BUILDROOT env not set, exiting!"
echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme"
exit
fi
echo "BUILDROOT set to ${BUILDROOT}"
export KIS_SRC_DIR=${BUILDROOT}/krita
export KIS_TBUILD_DIR=${BUILDROOT}/depbuild
export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall
export KIS_DOWN_DIR=${BUILDROOT}/down
export KIS_BUILD_DIR=${BUILDROOT}/kisbuild
export KIS_INSTALL_DIR=${BUILDROOT}/i
# flags for OSX environment
# Qt only supports from 10.12 up, and https://doc.qt.io/qt-5/macos.html#target-platforms warns against setting it lower
export MACOSX_DEPLOYMENT_TARGET=10.12
export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.12
# Build time variables
if test -z $(which cmake); then
echo "ERROR: cmake not found, exiting!"
exit
fi
export PATH=${KIS_INSTALL_DIR}/bin:$PATH
export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${C_INCLUDE_PATH}
export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${CPLUS_INCLUDE_PATH}
export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:/usr/lib:${LIBRARY_PATH}
# export CPPFLAGS=-I${KIS_INSTALL_DIR}/include
# export LDFLAGS=-L${KIS_INSTALL_DIR}/lib
export FRAMEWORK_PATH=${KIS_INSTALL_DIR}/lib/
# export PYTHONHOME=${KIS_INSTALL_DIR}
# export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5
# This will make the debug output prettier
export KDE_COLOR_DEBUG=1
export QTEST_COLORED=1
-OUPUT_LOG="${BUILDROOT}/osxbuild.log"
-ERROR_LOG="${BUILDROOT}/osxbuild-error.log"
+export OUPUT_LOG="${BUILDROOT}/osxbuild.log"
+export ERROR_LOG="${BUILDROOT}/osxbuild-error.log"
printf "" > "${OUPUT_LOG}"
printf "" > "${ERROR_LOG}"
# configure max core for make compile
((MAKE_THREADS=1))
if test ${OSTYPE} == "darwin*"; then
((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1))
fi
# Prints stderr and stdout to log files
# >(tee) works but breaks sigint
log_cmd () {
if [[ "${VERBOSE}" ]]; then
"$@" 1>> ${OUPUT_LOG} 2>> ${ERROR_LOG}
else
"$@" 2>> ${ERROR_LOG} | tee -a ${OUPUT_LOG} > /dev/null
fi
}
# Log messages to logfile
log () {
if [[ "${VERBOSE}" ]]; then
printf "%s\n" "${@}" | tee -a ${OUPUT_LOG}
else
printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} > /dev/null
fi
}
# if previous command gives error
# print msg
print_if_error() {
error_stat="${?}"
if [ ${error_stat} -ne 0 ]; then
printf "\e[31m%s %s\e[0m\n" "Error:" "${1}" 2>> ${ERROR_LOG}
printf "%s\r" "${error_stat}"
fi
}
# print status messges
print_msg() {
printf "\e[32m%s\e[0m\n" "${1}"
printf "%s\n" "${1}" >> ${OUPUT_LOG}
}
check_dir_path () {
printf "%s" "Checking if ${1} exists and is dir... "
if test -d ${1}; then
echo -e "OK"
elif test -e ${1}; then
echo -e "\n\tERROR: file ${1} exists but is not a directory!" >&2
return 1
else
echo -e "Creating ${1}"
mkdir ${1}
fi
return 0
}
# builds dependencies for the first time
cmake_3rdparty () {
cd ${KIS_TBUILD_DIR}
local build_pkgs=("${@}") # convert to array
if [[ ${2} = "1" ]]; then
local nofix="true"
local build_pkgs=(${build_pkgs[@]:0:1})
fi
for package in ${build_pkgs[@]} ; do
print_msg "Building ${package}"
log_cmd cmake --build . --config RelWithDebInfo --target ${package}
if [[ ! $(print_if_error "Failed build ${package}") ]]; then
print_msg "Build Success! ${package}"
fi
# fixes does not depend on failure
if [[ ! ${nofix} ]]; then
build_3rdparty_fixes ${package}
fi
done
}
build_3rdparty_fixes(){
pkg=${1}
if [[ "${pkg}" = "ext_qt" && -e "${KIS_INSTALL_DIR}/bin/qmake" ]]; then
ln -sf qmake "${KIS_INSTALL_DIR}/bin/qmake-qt5"
elif [[ "${pkg}" = "ext_openexr" ]]; then
# open exr will fail the first time is called
# rpath needs to be fixed an build rerun
log "Fixing rpath on openexr file: b44ExpLogTable"
log "Fixing rpath on openexr file: dwaLookups"
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups
# we must rerun build!
cmake_3rdparty ext_openexr "1"
elif [[ "${pkg}" = "ext_fontconfig" ]]; then
log "fixing rpath on fc-cache"
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_fontconfig/ext_fontconfig-prefix/src/ext_fontconfig-build/fc-cache/.libs/fc-cache
# rerun rebuild
cmake_3rdparty ext_fontconfig "1"
fi
}
build_3rdparty () {
print_msg "building in ${KIS_TBUILD_DIR}"
log "$(check_dir_path ${KIS_TBUILD_DIR})"
log "$(check_dir_path ${KIS_DOWN_DIR})"
log "$(check_dir_path ${KIS_INSTALL_DIR})"
cd ${KIS_TBUILD_DIR}
log_cmd cmake ${KIS_SRC_DIR}/3rdparty/ \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \
-DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \
-DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \
-DINSTALL_ROOT=${KIS_INSTALL_DIR}
# -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \
# -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib
print_msg "finished 3rdparty build setup"
if [[ -n ${1} ]]; then
cmake_3rdparty "${@}"
exit
fi
# build 3rdparty tools
# The order must not be changed!
cmake_3rdparty \
ext_pkgconfig \
ext_gettext \
ext_openssl \
ext_qt \
ext_zlib \
ext_boost \
ext_eigen3 \
ext_exiv2 \
ext_fftw3 \
ext_ilmbase \
ext_jpeg \
ext_lcms2 \
ext_ocio \
ext_openexr
cmake_3rdparty \
ext_png \
ext_tiff \
ext_gsl \
ext_vc \
ext_libraw \
ext_giflib \
ext_freetype \
ext_fontconfig \
ext_poppler
# Stop if qmake link was not created
# this meant qt build fail and further builds will
# also fail.
log_cmd test -L "${KIS_INSTALL_DIR}/bin/qmake-qt5"
if [[ $(print_if_error "qmake link missing!") ]]; then
printf "
link: ${KIS_INSTALL_DIR}/bin/qmake-qt5 missing!
It probably means ext_qt failed!!
check, fix and rerun!\n"
exit
fi
# for python
cmake_3rdparty \
ext_python \
ext_sip \
ext_pyqt
cmake_3rdparty ext_libheif
cmake_3rdparty \
ext_extra_cmake_modules \
ext_kconfig \
ext_kwidgetsaddons \
ext_kcompletion \
ext_kcoreaddons \
ext_kguiaddons \
ext_ki18n \
ext_kitemmodels \
ext_kitemviews \
ext_kimageformats \
ext_kwindowsystem \
ext_quazip
}
# Recall cmake for all 3rd party packages
# make is only on target first run
# subsequent runs only call make install
rebuild_3rdparty () {
print_msg "starting rebuild of 3rdparty packages"
build_install_ext() {
for pkg in ${@:1:${#@}}; do
{
cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-stamp
} || {
cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-stamp
} || {
cd ${KIS_TBUILD_DIR}/ext_heif/${pkg}-prefix/src/${pkg}-stamp
}
log "Installing ${pkg} files..."
rm ${pkg}-configure ${pkg}-build ${pkg}-install
cmake_3rdparty ${pkg}
cd ${KIS_TBUILD_DIR}
done
}
# Do not process complete list only pkgs given.
if ! test -z ${1}; then
build_install_ext ${@}
exit
fi
build_install_ext \
ext_pkgconfig \
ext_iconv \
ext_gettext \
ext_openssl \
ext_qt \
ext_zlib \
ext_boost \
ext_eigen3 \
ext_expat \
ext_exiv2 \
ext_fftw3 \
ext_ilmbase \
ext_jpeg \
ext_patch \
ext_lcms2 \
ext_ocio \
ext_ilmbase \
ext_openexr \
ext_png \
ext_tiff \
ext_gsl \
ext_vc \
ext_libraw \
ext_giflib \
ext_fontconfig \
ext_freetype \
ext_poppler \
ext_python \
ext_sip \
ext_pyqt \
build_install_ext \
ext_yasm \
ext_nasm \
ext_libx265 \
ext_libde265 \
ext_libheif \
# Build kde_frameworks
build_install_ext \
ext_extra_cmake_modules \
ext_kconfig \
ext_kwidgetsaddons \
ext_kcompletion \
ext_kcoreaddons \
ext_kguiaddons \
ext_ki18n \
ext_kitemmodels \
ext_kitemviews \
ext_kimageformats \
ext_kwindowsystem \
ext_quazip
}
#not tested
set_krita_dirs() {
if [[ -n ${1} ]]; then
KIS_BUILD_DIR=${BUILDROOT}/b_${1}
KIS_INSTALL_DIR=${BUILDROOT}/i_${1}
KIS_SRC_DIR=${BUILDROOT}/src_${1}
fi
}
# build_krita
# run cmake krita
build_krita () {
export DYLD_FRAMEWORK_PATH=${FRAMEWORK_PATH}
echo ${KIS_BUILD_DIR}
echo ${KIS_INSTALL_DIR}
log_cmd check_dir_path ${KIS_BUILD_DIR}
cd ${KIS_BUILD_DIR}
cmake ${KIS_SRC_DIR} \
-DFOUNDATION_BUILD=ON \
-DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \
-DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \
-DDEFINE_NO_DEPRECATED=1 \
-DBUILD_TESTING=OFF \
-DHIDE_SAFE_ASSERTS=ON \
-DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \
-DPYQT_SIP_DIR_OVERRIDE=${KIS_INSTALL_DIR}/share/sip/ \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \
-DPYTHON_INCLUDE_DIR=${KIS_INSTALL_DIR}/lib/Python.framework/Headers
# copiling phase
make -j${MAKE_THREADS}
# compile integrations
if test ${OSTYPE} == "darwin*"; then
cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook
make -j${MAKE_THREADS}
fi
}
build_krita_tarball () {
filename="$(basename ${1})"
KIS_CUSTOM_BUILD="${BUILDROOT}/releases/${filename%.tar.gz}"
print_msg "Tarball BUILDROOT is ${KIS_CUSTOM_BUILD}"
filename_dir=$(dirname "${1}")
cd "${filename_dir}"
file_abspath="$(pwd)/${1##*/}"
mkdir "${KIS_CUSTOM_BUILD}" 2> /dev/null
cd "${KIS_CUSTOM_BUILD}"
mkdir "src" "build" 2> /dev/null
tar -xzf "${file_abspath}" --strip-components=1 --directory "src"
if [[ $(print_if_error "Untar ${file_abspath} failed!") ]]; then
exit
fi
KIS_BUILD_DIR="${KIS_CUSTOM_BUILD}/build"
KIS_SRC_DIR="${KIS_CUSTOM_BUILD}/src"
build_krita
print_msg "Build done!"
print_msg "to install run
osxbuild.sh install ${KIS_BUILD_DIR}"
}
install_krita () {
# custom install provided
if [[ -n "${1}" ]]; then
KIS_BUILD_DIR="${1}"
fi
print_msg "Install krita from ${KIS_BUILD_DIR}"
log_cmd check_dir_path ${KIS_BUILD_DIR}
cd ${KIS_BUILD_DIR}
if [[ $(print_if_error "could not cd to ${KIS_BUILD_DIR}") ]]; then
exit
fi
make install
# compile integrations
if test ${OSTYPE} == "darwin*"; then
cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook
make install
fi
}
# Runs all fixes for path and packages.
# Historically only fixed boost @rpath
fix_boost_rpath () {
+ # helpers to define function only once
+ fixboost_find () {
+ for FILE in "${@}"; do
+ if [[ -n "$(otool -L $FILE | grep boost)" ]]; then
+ log "Fixing -- $FILE"
+ log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE
+ fi
+ done
+ }
+
+ batch_fixboost() {
+ xargs -P4 -I FILE bash -c 'fixboost_find "FILE"'
+ }
+
+ export -f fixboost_find
+ export -f log
+ export -f log_cmd
+
print_msg "Fixing boost in... ${KIS_INSTALL_DIR}"
# install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
# echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin"
# install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
- FILES=$(find -L ${KIS_INSTALL_DIR} -name '*so' -o -name '*dylib')
- for FILE in $FILES; do
- if [[ -n "$(otool -L $FILE | grep boost)" ]]; then
- log "Fixing -- $FILE"
- log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE
- fi
- done
+ find -L "${KIS_INSTALL_DIR}" -name '*so' -o -name '*dylib' | batch_fixboost
log "Fixing boost done!"
}
print_usage () {
printf "USAGE: osxbuild.sh <buildstep> [pkg|file]\n"
printf "BUILDSTEPS:\t\t"
printf "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg"
printf "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg
\t\t\t usefull for cleaning install directory and quickly reinstall all deps."
printf "\n fixboost \t\t Fixes broken boost \@rpath on OSX"
printf "\n build \t\t\t Builds krita"
printf "\n buildtarball \t\t\t Builds krita from provided [file] tarball"
printf "\n install \t\t Installs krita. Optionally accepts a [build dir] as argument
\t\t\t this will install krita from given directory"
printf "\n buildinstall \t\t Build and Installs krita, running fixboost after installing"
printf "\n"
}
if [[ ${#} -eq 0 ]]; then
echo "ERROR: No option given!"
print_usage
exit 1
fi
if [[ ${1} = "builddeps" ]]; then
build_3rdparty "${@:2}"
elif [[ ${1} = "rebuilddeps" ]]; then
rebuild_3rdparty "${@:2}"
elif [[ ${1} = "fixboost" ]]; then
if [[ -d ${1} ]]; then
KIS_BUILD_DIR="${1}"
fi
fix_boost_rpath
elif [[ ${1} = "build" ]]; then
build_krita ${2}
elif [[ ${1} = "buildtarball" ]]; then
# uncomment line to optionally change
# install directory providing a third argument
# This is not on by default as build success requires all
# deps installed in the given dir beforehand.
# KIS_INSTALL_DIR=${3}
build_krita_tarball ${2}
elif [[ ${1} = "install" ]]; then
install_krita ${2}
fix_boost_rpath
elif [[ ${1} = "buildinstall" ]]; then
build_krita ${2}
install_krita ${2}
fix_boost_rpath ${2}
elif [[ ${1} = "test" ]]; then
${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
else
echo "Option ${1} not supported"
print_usage
exit 1
fi
# after finishig sometimes it complains about missing matching quotes.
diff --git a/packaging/macos/osxdeploy.sh b/packaging/macos/osxdeploy.sh
index 4d36411969..e23d2e59e2 100755
--- a/packaging/macos/osxdeploy.sh
+++ b/packaging/macos/osxdeploy.sh
@@ -1,562 +1,572 @@
#!/usr/bin/env bash
# Krita tool to create dmg from installed source
# Copies all files to a folder to be converted into the final dmg
# osxdeploy.sh automates the creation of the release DMG.
# default background and style are used if none provided
# A short explanation of what it does:
# - Copies krita.app contents to kritadmg folder
# - Copies i/share to Contents/Resources excluding unnecesary files
# - Copies translations, qml and quicklook PlugIns
# - Copies i/plugins and i/lib/plugins to Contents/PlugIns
# - Runs macdeployqt: macdeployqt is not built by default in ext_qt
# build by:
# cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src
# make sub-macdeployqt-all
# make sub-macdeployqt-install_subtargets
# make install
# the script changes dir to installation/bin to run macdeployqt as it can be buggy
# if not runned from the same folder as the binary is on.
# - Fix rpath from krita bin
# - Find missing libraries from plugins and copy to Framworks or plugins.
# This uses oTool iterative to find all unique libraries, then it searches each
# library fond in <kritadmg> folder, and if not found attempts to copy contents
# to the appropiate folder, either Frameworks (if frameworks is in namefile, or
# library has plugin isnot in path), or plugin if otherwise.
# - Builds DMG
# Building DMG creates a new dmg with the contents of <kritadmg>
# mounts the dmg and sets the style for dmg.
# unmount
# Compress resulting dmg into krita_nightly-<gitsha>.dmg
# deletes temporary files.
if test -z ${BUILDROOT}; then
echo "ERROR: BUILDROOT env not set!"
echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme"
echo "exiting..."
exit
fi
get_script_dir() {
script_source="${BASH_SOURCE[0]}"
# go to target until finding root.
while [ -L "${script_source}" ]; do
script_target="$(readlink ${script_source})"
if [[ "${script_source}" = /* ]]; then
script_source="$script_target"
else
script_dir="$(dirname "${script_source}")"
script_source="${script_dir}/${script_target}"
fi
done
echo "$(dirname ${script_source})"
}
DMG_title="krita" #if changed krita.temp.dmg must be deleted manually
SCRIPT_SOURCE_DIR="$(get_script_dir)"
# There is some duplication between build and deploy scripts
# a config env file could would be a nice idea.
KIS_SRC_DIR=${BUILDROOT}/krita
KIS_INSTALL_DIR=${BUILDROOT}/i
KIS_BUILD_DIR=${BUILDROOT}/kisbuild # only used for getting git sha number
KRITA_DMG=${BUILDROOT}/kritadmg
KRITA_DMG_TEMPLATE=${BUILDROOT}/kritadmg-template
export PATH=${KIS_INSTALL_DIR}/bin:$PATH
# flags for OSX environment
# We only support from 10.11 up
export MACOSX_DEPLOYMENT_TARGET=10.11
export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11
# Attempt to find python_version
local_PY_MAYOR_VERSION=$(python -c "import sys; print(sys.version_info[0])")
local_PY_MINOR_VERSION=$(python -c "import sys; print(sys.version_info[1])")
PY_VERSION="${local_PY_MAYOR_VERSION}.${local_PY_MINOR_VERSION}"
echo "Detected Python ${PY_VERSION}"
print_usage () {
echo "USAGE: osxdeploy.sh [-s=<identity>] [-style=<style.txt>] [-bg=<background-image>]"
echo "\t -s Code sign identity for codesign"
echo "\t -style Style file defined from 'dmgstyle.sh' output"
echo "\t -bg Set a background image for dmg folder"
echo "\t osxdeploy needs an input image to add to the dmg background
\t image recomended size is at least 950x500\n"
}
# Attempt to detach previous mouted DMG
if [[ -d "/Volumes/${DMG_title}" ]]; then
echo "WARNING: Another Krita DMG is mounted!"
echo "Attempting eject…"
hdiutil detach "/Volumes/${DMG_title}"
if [ $? -ne 0 ]; then
exit
fi
echo "Success!"
fi
# Parse input args
for arg in "${@}"; do
if [ "${arg}" = -bg=* -a -f "${arg#*=}" ]; then
DMG_validBG=0
bg_filename=${arg#*=}
echo "attempting to check background is valid jpg or png..."
BG_FORMAT=$(sips --getProperty format ${bg_filename} | awk '{printf $2}')
if [[ "png" = ${BG_FORMAT} || "jpeg" = ${BG_FORMAT} ]];then
echo "valid image file"
DMG_background=$(cd "$(dirname "${bg_filename}")"; pwd -P)/$(basename "${bg_filename}")
DMG_validBG=1
# check imageDPI
BG_DPI=$(sips --getProperty dpiWidth ${DMG_background} | grep dpi | awk '{print $2}')
if [[ $(echo "${BG_DPI} > 150" | bc -l) -eq 1 ]]; then
printf "WARNING: image dpi has an effect on apparent size!
Check dpi is adequate for screen display if image appears very small
Current dpi is: %s\n" ${BG_DPI}
fi
fi
fi
# If string starts with -sign
if [[ ${arg} = -s=* ]]; then
CODE_SIGNATURE="${arg#*=}"
fi
if [[ ${arg} = -style=* ]]; then
style_filename="${arg#*=}"
if [[ -f "${style_filename}" ]]; then
DMG_STYLE="${style_filename}"
fi
fi
if [[ ${arg} = "-h" || ${arg} = "--help" ]]; then
print_usage
exit
fi
done
if [[ ! ${DMG_STYLE} ]]; then
DMG_STYLE="${SCRIPT_SOURCE_DIR}/default.style"
fi
echo "Using style from: ${DMG_STYLE}"
if [[ ${DMG_validBG} -eq 0 ]]; then
echo "No jpg or png valid file detected!!"
echo "Using default style"
- DMG_background="${SCRIPT_SOURCE_DIR}/krita-4.1_dmgBG.jpg"
+ DMG_background="${SCRIPT_SOURCE_DIR}/krita_dmgBG.jpg"
fi
if [[ -z "${CODE_SIGNATURE}" ]]; then
echo "WARNING: No signature provided, Code will not be signed"
else
printf 'Code will be signed with "%s"\n' "${CODE_SIGNATURE}"
fi
# Helper functions
countArgs () {
echo "${#}"
}
stringContains () {
echo "$(grep "${2}" <<< "${1}")"
}
add_lib_to_list() {
local llist=${2}
if test -z "$(grep ${1##*/} <<< ${llist})" ; then
local llist="${llist} ${1##*/} "
fi
echo "${llist}"
}
# Find all @rpath and Absolute to buildroot path libs
# Add to libs_used
# converts absolute buildroot path to @rpath
find_needed_libs () {
# echo "Analizing libraries with oTool..." >&2
local libs_used="" # input lib_lists founded
for libFile in ${@}; do
if test -z "$(file ${libFile} | grep 'Mach-O')" ; then
# echo "skipping ${libFile}" >&2
continue
fi
oToolResult=$(otool -L ${libFile} | awk '{print $1}')
resultArray=(${oToolResult}) # convert to array
for lib in ${resultArray[@]:1}; do
if test "${lib:0:1}" = "@"; then
local libs_used=$(add_lib_to_list "${lib}" "${libs_used}")
fi
if [[ "${lib:0:${#BUILDROOT}}" = "${BUILDROOT}" ]]; then
printf "Fixing %s: %s\n" "${libFile#${KRITA_DMG}/}" "${lib##*/}" >&2
if [[ "${lib##*/}" = "${libFile##*/}" ]]; then
install_name_tool -id ${lib##*/} "${libFile}"
else
install_name_tool -change ${lib} "@rpath/${lib##*${BUILDROOT}/i/lib/}" "${libFile}"
local libs_used=$(add_lib_to_list "${lib}" "${libs_used}")
fi
fi
done
done
echo "${libs_used}" # return updated list
}
find_missing_libs (){
# echo "Searching for missing libs on deployment folders…" >&2
local libs_missing=""
for lib in ${@}; do
if test -z "$(find ${KRITA_DMG}/krita.app/Contents/ -name ${lib})"; then
# echo "Adding ${lib} to missing libraries." >&2
libs_missing="${libs_missing} ${lib}"
fi
done
echo "${libs_missing}"
}
copy_missing_libs () {
for lib in ${@}; do
result=$(find -L "${BUILDROOT}/i" -name "${lib}")
if test $(countArgs ${result}) -eq 1; then
if [ "$(stringContains "${result}" "plugin")" ]; then
cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/PlugIns/
krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/PlugIns/${result##*/}"
else
cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/Frameworks/
krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/Frameworks/${result##*/}"
fi
else
echo "${lib} might be a missing framework"
if [ "$(stringContains "${result}" "framework")" ]; then
echo "copying framework ${BUILDROOT}/i/lib/${lib}.framework to dmg"
# rsync only included ${lib} Resources Versions
rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/${lib} ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/
rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Resources ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/
rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Versions ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/
- krita_findmissinglibs "$(find "${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/" -perm u+x)"
+ krita_findmissinglibs "$(find "${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/" -type f -perm 755)"
fi
fi
done
}
krita_findmissinglibs() {
neededLibs=$(find_needed_libs "${@}")
missingLibs=$(find_missing_libs ${neededLibs})
if test $(countArgs ${missingLibs}) -gt 0; then
printf "Found missing libs: %s\n" "${missingLibs}"
copy_missing_libs ${missingLibs}
fi
}
strip_python_dmginstall() {
# reduce size of framework python
# Removes tests, installers, pyenv, distutils
echo "Removing unnecesary files from Python.Framework to be packaged..."
PythonFrameworkBase="${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework"
cd ${PythonFrameworkBase}
find . -name "test*" -type d | xargs rm -rf
- find "${PythonFrameworkBase}/Versions/${PY_VERSION}/bin" -not -name "python*" | xargs rm -f
+ find "${PythonFrameworkBase}/Versions/${PY_VERSION}/bin" -not -name "python*" \( -type f -or -type l \) | xargs rm -f
cd "${PythonFrameworkBase}/Versions/${PY_VERSION}/lib/python${PY_VERSION}"
rm -rf distutils tkinter ensurepip venv lib2to3 idlelib
}
+# Some libraries require r_path to be removed
+# we must not apply delete rpath globally
+delete_install_rpath() {
+ xargs -P4 -I FILE install_name_tool -delete_rpath "${BUILDROOT}/i/lib" FILE 2> "${BUILDROOT}/deploy_error.log"
+}
+
fix_python_framework() {
# Fix python.framework rpath and slims down installation
- # fix library LD_RPATH excutable_path and loader_path.
- # It is intended to be used for Libraries inside Frameworks.
- fix_framework_library() {
- xargs -P4 -I FILE sh -c "
- install_name_tool -rpath ${KIS_INSTALL_DIR}/lib @loader_path/Frameworks \"${libFile}\" 2> /dev/null
- install_name_tool -add_rpath @loader_path/../../../ \"${libFile}\" 2> /dev/null
- "
- }
- # Start fixing all executables
PythonFrameworkBase="${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework"
- install_name_tool -change @loader_path/../../../../libintl.9.dylib @loader_path/../../../libintl.9.dylib "${PythonFrameworkBase}/Python"
+
+ # Fix main library
+ pythonLib="${PythonFrameworkBase}/Python"
+ install_name_tool -id "${pythonLib##*/}" "${pythonLib}"
+ install_name_tool -add_rpath @loader_path/../../../ "${pythonLib}" 2> /dev/null
+ install_name_tool -change @loader_path/../../../../libintl.9.dylib @loader_path/../../../libintl.9.dylib "${pythonLib}"
+
+ # Fix all executables
install_name_tool -add_rpath @executable_path/../../../../../../../ "${PythonFrameworkBase}/Versions/Current/Resources/Python.app/Contents/MacOS/Python"
+ install_name_tool -change "${KIS_INSTALL_DIR}/lib/Python.framework/Versions/${PY_VERSION}/Python" @executable_path/../../../../../../Python "${PythonFrameworkBase}/Versions/Current/Resources/Python.app/Contents/MacOS/Python"
install_name_tool -add_rpath @executable_path/../../../../ "${PythonFrameworkBase}/Versions/Current/bin/python${PY_VERSION}"
install_name_tool -add_rpath @executable_path/../../../../ "${PythonFrameworkBase}/Versions/Current/bin/python${PY_VERSION}m"
# Fix rpaths from Python.Framework
- # install_name_tool change @loader_path/../../../libz.1.dylib
-
- # Fix main library
- printf ${PythonFrameworkBase}/Python | fix_framework_library
- # find ${PythonFrameworkBase} -name "*.so" -not -type l | fix_framework_library
+ find ${PythonFrameworkBase} -type f -perm 755 | delete_install_rpath
+ find "${PythonFrameworkBase}/Versions/Current/site-packages/PyQt5" -type f -name "*.so" | delete_install_rpath
}
# Checks for macdeployqt
# If not present attempts to install
# If it fails shows an informatve message
# (For now, macdeployqt is fundamental to deploy)
macdeployqt_exists() {
printf "Checking for macdeployqt... "
if [[ ! -e "${KIS_INSTALL_DIR}/bin/macdeployqt" ]]; then
printf "Not Found!\n"
printf "Attempting to install macdeployqt\n"
cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src
make sub-macdeployqt-all
make sub-macdeployqt-install_subtargets
make install
if [[ ! -e "${KIS_INSTALL_DIR}/bin/macdeployqt" ]]; then
printf "
ERROR: Failed to install macdeployqt!
Compile and install from qt source directory
Source code to build could be located in qttools/src in qt source dir:
${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src
From the source dir, build and install:
make sub-macdeployqt-all
make sub-macdeployqt-install_subtargets
make install
"
printf "\nexiting...\n"
exit
else
echo "Done!"
fi
else
echo "Found!"
fi
}
krita_deploy () {
# check for macdeployqt
macdeployqt_exists
cd ${BUILDROOT}
# Update files in krita.app
echo "Deleting previous kritadmg run..."
rm -rf ./krita.dmg ${KRITA_DMG}
# Copy new builtFiles
echo "Preparing ${KRITA_DMG} for deployment..."
echo "Copying krita.app..."
mkdir "${KRITA_DMG}"
rsync -prul ${KIS_INSTALL_DIR}/bin/krita.app ${KRITA_DMG}
mkdir -p ${KRITA_DMG}/krita.app/Contents/PlugIns
mkdir -p ${KRITA_DMG}/krita.app/Contents/Frameworks
echo "Copying share..."
# Deletes old copies of translation and qml to be recreated
cd ${KIS_INSTALL_DIR}/share/
rsync -prul --delete ./ \
--exclude krita_SRCS.icns \
--exclude aclocal \
--exclude doc \
--exclude ECM \
--exclude eigen3 \
--exclude emacs \
--exclude gettext \
--exclude gettext-0.19.8 \
--exclude info \
--exclude kf5 \
--exclude kservices5 \
--exclude man \
--exclude ocio \
--exclude pkgconfig \
--exclude mime \
--exclude translations \
--exclude qml \
${KRITA_DMG}/krita.app/Contents/Resources
cd ${BUILDROOT}
echo "Copying translations..."
rsync -prul ${KIS_INSTALL_DIR}/translations/ \
${KRITA_DMG}/krita.app/Contents/Resources/translations
echo "Copying kritaquicklook..."
mkdir -p ${KRITA_DMG}/krita.app/Contents/Library/QuickLook
rsync -prul ${KIS_INSTALL_DIR}/plugins/kritaquicklook.qlgenerator ${KRITA_DMG}/krita.app/Contents/Library/QuickLook
cd ${KRITA_DMG}/krita.app/Contents
ln -shF Resources share
echo "Copying qml..."
rsync -prul ${KIS_INSTALL_DIR}/qml Resources/qml
echo "Copying plugins..."
# exclude kritaquicklook.qlgenerator/
cd ${KIS_INSTALL_DIR}/plugins/
rsync -prul --delete --delete-excluded ./ \
--exclude kritaquicklook.qlgenerator \
${KRITA_DMG}/krita.app/Contents/PlugIns
cd ${BUILDROOT}
rsync -prul ${KIS_INSTALL_DIR}/lib/kritaplugins/ ${KRITA_DMG}/krita.app/Contents/PlugIns
# rsync -prul {KIS_INSTALL_DIR}/lib/libkrita* Frameworks/
# To avoid errors macdeployqt must be run from bin location
# ext_qt will not build macdeployqt by default so it must be build manually
# cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src
# make sub-macdeployqt-all
# make sub-macdeployqt-install_subtargets
# make install
echo "Running macdeployqt..."
cd ${KIS_INSTALL_DIR}/bin
./macdeployqt ${KRITA_DMG}/krita.app \
-verbose=0 \
-executable=${KRITA_DMG}/krita.app/Contents/MacOS/krita \
-libpath=${KIS_INSTALL_DIR}/lib \
-qmldir=${KIS_INSTALL_DIR}/qml \
# -extra-plugins=${KIS_INSTALL_DIR}/lib/kritaplugins \
# -extra-plugins=${KIS_INSTALL_DIR}/lib/plugins \
# -extra-plugins=${KIS_INSTALL_DIR}/plugins
cd ${BUILDROOT}
echo "macdeployqt done!"
echo "Copying python..."
# Copy this framework last!
# It is best that macdeployqt does not modify Python.framework
# folders with period in name are treated as Frameworks for codesign
rsync -prul ${KIS_INSTALL_DIR}/lib/Python.framework ${KRITA_DMG}/krita.app/Contents/Frameworks/
rsync -prul ${KIS_INSTALL_DIR}/lib/krita-python-libs ${KRITA_DMG}/krita.app/Contents/Frameworks/
# change perms on Python to allow header change
chmod +w ${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework/Python
fix_python_framework
strip_python_dmginstall
# fix python pyc
# precompile all pyc so the dont alter signature
echo "Precompiling all python files..."
cd ${KRITA_DMG}/krita.app
${KIS_INSTALL_DIR}/bin/python -m compileall . &> /dev/null
install_name_tool -delete_rpath @loader_path/../../../../lib ${KRITA_DMG}/krita.app/Contents/MacOS/krita
rm -rf ${KRITA_DMG}/krita.app/Contents/PlugIns/kf5/org.kde.kwindowsystem.platforms
# repair krita for plugins
printf "Searching for missing libraries\n"
- krita_findmissinglibs $(find ${KRITA_DMG}/krita.app/Contents -type f -name "*.dylib" -or -name "*.so" -or -perm u+x)
+ krita_findmissinglibs $(find ${KRITA_DMG}/krita.app/Contents -type f -perm 755 -or -name "*.dylib" -or -name "*.so")
+
+ printf "removing absolute or broken linksys, if any\n"
+ find "${KRITA_DMG}/krita.app/Contents" -type l \( -lname "/*" -or -not -exec test -e {} \; \) -print | xargs rm
+
echo "Done!"
}
# helper to define function only once
batch_codesign() {
xargs -P4 -I FILE codesign -f -s "${CODE_SIGNATURE}" FILE
}
# Code sign must be done as recommended by apple "sign code inside out in individual stages"
signBundle() {
cd ${KRITA_DMG}
# sign Frameworks and libs
cd ${KRITA_DMG}/krita.app/Contents/Frameworks
# remove debug version as both versions cant be signed.
rm ${KRITA_DMG}/krita.app/Contents/Frameworks/QtScript.framework/Versions/Current/QtScript_debug
- find . -type f -name "*.dylib" -or -name "*.so" | batch_codesign
+ find . -type f -perm 755 -or -name "*.dylib" -or -name "*.so" | batch_codesign
find . -type d -name "*.framework" | xargs printf "%s/Versions/Current\n" | batch_codesign
# Sign all other files in Framework (needed)
# there are many files in python do we need to sign them all?
find krita-python-libs -type f | batch_codesign
# find python -type f | batch_codesign
# Sing only libraries and plugins
cd ${KRITA_DMG}/krita.app/Contents/PlugIns
find . -type f | batch_codesign
cd ${KRITA_DMG}/krita.app/Contents/Library/QuickLook
printf "kritaquicklook.qlgenerator" | batch_codesign
# It is recommended to sign every Resource file
cd ${KRITA_DMG}/krita.app/Contents/Resources
find . -type f | batch_codesign
#Finally sign krita and krita.app
printf "${KRITA_DMG}/krita.app/Contents/MacOS/krita" | batch_codesign
printf "${KRITA_DMG}/krita.app" | batch_codesign
}
createDMG () {
printf "Creating of dmg with contents of %s...\n" "${KRITA_DMG}"
cd ${BUILDROOT}
DMG_size=700
## Build dmg from folder
# create dmg on local system
# usage of -fsargs minimze gaps at front of filesystem (reduce size)
hdiutil create -srcfolder "${KRITA_DMG}" -volname "${DMG_title}" -fs HFS+ \
-fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DMG_size}m krita.temp.dmg
# Next line is only useful if we have a dmg as a template!
# previous hdiutil must be uncommented
# cp krita-template.dmg krita.dmg
device=$(hdiutil attach -readwrite -noverify -noautoopen "krita.temp.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}')
# rsync -priul --delete ${KRITA_DMG}/krita.app "/Volumes/${DMG_title}"
# Set style for dmg
if [[ ! -d "/Volumes/${DMG_title}/.background" ]]; then
mkdir "/Volumes/${DMG_title}/.background"
fi
cp -v ${DMG_background} "/Volumes/${DMG_title}/.background/"
mkdir "/Volumes/${DMG_title}/Terms of Use"
cp -v "${KIS_SRC_DIR}/packaging/macos/Terms_of_use.rtf" "/Volumes/${DMG_title}/Terms of Use/"
ln -s "/Applications" "/Volumes/${DMG_title}/Applications"
## Apple script to set style
style="$(<"${DMG_STYLE}")"
printf "${style}" "${DMG_title}" "${DMG_background##*/}" | osascript
#Set Icon for DMG
cp -v "${SCRIPT_SOURCE_DIR}/KritaIcon.icns" "/Volumes/${DMG_title}/.VolumeIcon.icns"
SetFile -a C "/Volumes/${DMG_title}"
chmod -Rf go-w "/Volumes/${DMG_title}"
# ensure all writting operations to dmg are over
sync
hdiutil detach $device
hdiutil convert "krita.temp.dmg" -format UDZO -imagekey -zlib-level=9 -o krita-out.dmg
# Add git version number
GIT_SHA=$(grep "#define KRITA_GIT_SHA1_STRING" ${KIS_BUILD_DIR}/libs/version/kritagitversion.h | awk '{gsub(/"/, "", $3); printf $3}')
mv krita-out.dmg krita-nightly_${GIT_SHA}.dmg
echo "moved krita-out.dmg to krita-nightly_${GIT_SHA}.dmg"
rm krita.temp.dmg
+ if [[ -n "${CODE_SIGNATURE}" ]]; then
+ printf "krita-nightly_${GIT_SHA}.dmg" | batch_codesign
+ fi
+
echo "dmg done!"
}
#######################
# Program starts!!
########################
# Run deploy command, instalation is assumed to exist in BUILDROOT/i
krita_deploy
# Code sign krita.app if signature given
if [[ -n "${CODE_SIGNATURE}" ]]; then
signBundle
fi
# Create DMG from files insiede ${KRITA_DMG} folder
createDMG
diff --git a/plugins/assistants/Assistants/CMakeLists.txt b/plugins/assistants/Assistants/CMakeLists.txt
index 63bcecbb0f..7ac242b6f1 100644
--- a/plugins/assistants/Assistants/CMakeLists.txt
+++ b/plugins/assistants/Assistants/CMakeLists.txt
@@ -1,25 +1,26 @@
set(kritaassistanttool_SOURCES
assistant_tool.cc
ConcentricEllipseAssistant.cc
Ellipse.cc
EllipseAssistant.cc
FisheyePointAssistant.cc
InfiniteRulerAssistant.cc
kis_assistant_tool.cc
ParallelRulerAssistant.cc
PerspectiveAssistant.cc
Ruler.cc
RulerAssistant.cc
SplineAssistant.cc
VanishingPointAssistant.cc
+ EditAssistantsCommand.cpp
)
ki18n_wrap_ui(kritaassistanttool_SOURCES AssistantsToolOptions.ui )
add_library(kritaassistanttool MODULE ${kritaassistanttool_SOURCES})
target_link_libraries(kritaassistanttool kritaui kritaflake )
install(TARGETS kritaassistanttool DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( FILES krita_tool_assistant.png dark_krita_tool_assistant.png light_krita_tool_assistant.png DESTINATION ${DATA_INSTALL_DIR}/krita/pics)
diff --git a/plugins/assistants/Assistants/ConcentricEllipseAssistant.cc b/plugins/assistants/Assistants/ConcentricEllipseAssistant.cc
index 54897228c2..3fd53bd449 100644
--- a/plugins/assistants/Assistants/ConcentricEllipseAssistant.cc
+++ b/plugins/assistants/Assistants/ConcentricEllipseAssistant.cc
@@ -1,195 +1,208 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "ConcentricEllipseAssistant.h"
#include <klocalizedstring.h>
#include "kis_debug.h"
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <kis_algebra_2d.h>
#include <math.h>
ConcentricEllipseAssistant::ConcentricEllipseAssistant()
: KisPaintingAssistant("concentric ellipse", i18n("Concentric Ellipse assistant"))
{
}
+KisPaintingAssistantSP ConcentricEllipseAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new ConcentricEllipseAssistant(*this, handleMap));
+}
+
+ConcentricEllipseAssistant::ConcentricEllipseAssistant(const ConcentricEllipseAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+ , m_ellipse(rhs.m_ellipse)
+ , m_extraEllipse(rhs.m_extraEllipse)
+{
+}
+
QPointF ConcentricEllipseAssistant::project(const QPointF& pt, const QPointF& strokeBegin) const
{
Q_ASSERT(isAssistantComplete());
m_ellipse.set(*handles()[0], *handles()[1], *handles()[2]);
qreal
dx = pt.x() - strokeBegin.x(),
dy = pt.y() - strokeBegin.y();
if (dx * dx + dy * dy < 4.0) {
// allow some movement before snapping
return strokeBegin;
}
//calculate ratio
QPointF initial = m_ellipse.project(strokeBegin);
QPointF center = m_ellipse.boundingRect().center();
qreal Ratio = QLineF(center, strokeBegin).length() /QLineF(center, initial).length();
//calculate the points of the extrapolated ellipse.
QLineF extrapolate0 = QLineF(center, *handles()[0]);
extrapolate0.setLength(extrapolate0.length()*Ratio);
QLineF extrapolate1 = QLineF(center, *handles()[1]);
extrapolate1.setLength(extrapolate1.length()*Ratio);
QLineF extrapolate2 = QLineF(center, *handles()[2]);
extrapolate2.setLength(extrapolate2.length()*Ratio);
//set the extrapolation ellipse.
m_extraEllipse.set(extrapolate0.p2(), extrapolate1.p2(), extrapolate2.p2());
return m_extraEllipse.project(pt);
}
QPointF ConcentricEllipseAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin)
{
return project(pt, strokeBegin);
}
void ConcentricEllipseAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
QPointF mousePos;
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in the ellipse assistant, you may have passed arguments incorrectly:"<<canvas;
}
QTransform initialTransform = converter->documentToWidgetTransform();
if (isSnappingActive() && previewVisible == true){
if (isAssistantComplete()){
if (m_ellipse.set(*handles()[0], *handles()[1], *handles()[2])) {
QPointF initial = m_ellipse.project(initialTransform.inverted().map(mousePos));
QPointF center = m_ellipse.boundingRect().center();
qreal Ratio = QLineF(center, initialTransform.inverted().map(mousePos)).length() /QLineF(center, initial).length();
//line from center to handle 1 * difference.
//set handle1 translated to
// valid ellipse
gc.setTransform(initialTransform);
gc.setTransform(m_ellipse.getInverse(), true);
QPainterPath path;
// Draw the ellipse
path.addEllipse(QPointF(0, 0), m_ellipse.semiMajor()*Ratio, m_ellipse.semiMinor()*Ratio);
drawPreview(gc, path);
}
}
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
void ConcentricEllipseAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false || handles().size() < 2) { // 2 points means a line, so we can continue after 1 point
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
if (handles().size() == 2) {
// just draw the axis
gc.setTransform(initialTransform);
QPainterPath path;
path.moveTo(*handles()[0]);
path.lineTo(*handles()[1]);
drawPath(gc, path, isSnappingActive());
return;
}
if (m_ellipse.set(*handles()[0], *handles()[1], *handles()[2])) {
// valid ellipse
gc.setTransform(initialTransform);
gc.setTransform(m_ellipse.getInverse(), true);
QPainterPath path;
path.moveTo(QPointF(-m_ellipse.semiMajor(), 0)); path.lineTo(QPointF(m_ellipse.semiMajor(), 0));
path.moveTo(QPointF(0, -m_ellipse.semiMinor())); path.lineTo(QPointF(0, m_ellipse.semiMinor()));
// Draw the ellipse
path.addEllipse(QPointF(0, 0), m_ellipse.semiMajor(), m_ellipse.semiMinor());
drawPath(gc, path, isSnappingActive());
}
}
QRect ConcentricEllipseAssistant::boundingRect() const
{
if (!isAssistantComplete()) {
return KisPaintingAssistant::boundingRect();
}
if (m_ellipse.set(*handles()[0], *handles()[1], *handles()[2])) {
return m_ellipse.boundingRect().adjusted(-2, -2, 2, 2).toAlignedRect();
} else {
return QRect();
}
}
QPointF ConcentricEllipseAssistant::buttonPosition() const
{
return (*handles()[0] + *handles()[1]) * 0.5;
}
bool ConcentricEllipseAssistant::isAssistantComplete() const
{
return handles().size() >= 3;
}
ConcentricEllipseAssistantFactory::ConcentricEllipseAssistantFactory()
{
}
ConcentricEllipseAssistantFactory::~ConcentricEllipseAssistantFactory()
{
}
QString ConcentricEllipseAssistantFactory::id() const
{
return "concentric ellipse";
}
QString ConcentricEllipseAssistantFactory::name() const
{
return i18n("Concentric Ellipse");
}
KisPaintingAssistant* ConcentricEllipseAssistantFactory::createPaintingAssistant() const
{
return new ConcentricEllipseAssistant;
}
diff --git a/plugins/assistants/Assistants/ConcentricEllipseAssistant.h b/plugins/assistants/Assistants/ConcentricEllipseAssistant.h
index 0471841598..55200afa59 100644
--- a/plugins/assistants/Assistants/ConcentricEllipseAssistant.h
+++ b/plugins/assistants/Assistants/ConcentricEllipseAssistant.h
@@ -1,57 +1,60 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _CONCENTRIC_ELLIPSE_ASSISTANT_H_
#define _CONCENTRIC_ELLIPSE_ASSISTANT_H_
#include "kis_painting_assistant.h"
#include "Ellipse.h"
#include <QLineF>
#include <QObject>
class ConcentricEllipseAssistant : public KisPaintingAssistant
{
public:
ConcentricEllipseAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
QPointF buttonPosition() const override;
int numHandles() const override { return 3; }
bool isAssistantComplete() const override;
protected:
QRect boundingRect() const override;
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt, const QPointF& strokeBegin) const;
mutable Ellipse m_ellipse;
mutable Ellipse m_extraEllipse;
+ explicit ConcentricEllipseAssistant(const ConcentricEllipseAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
};
class ConcentricEllipseAssistantFactory : public KisPaintingAssistantFactory
{
public:
ConcentricEllipseAssistantFactory();
~ConcentricEllipseAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/EditAssistantsCommand.cpp b/plugins/assistants/Assistants/EditAssistantsCommand.cpp
new file mode 100644
index 0000000000..69ef9eaa50
--- /dev/null
+++ b/plugins/assistants/Assistants/EditAssistantsCommand.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "EditAssistantsCommand.h"
+
+#include <QListIterator>
+
+#include <kis_canvas2.h>
+#include <KisView.h>
+#include <KisDocument.h>
+#include <KisViewManager.h>
+#include <kis_canvas_resource_provider.h>
+#include <kis_painting_assistants_decoration.h>
+
+EditAssistantsCommand::EditAssistantsCommand(QPointer<KisCanvas2> canvas, AssistantSPList origAssistants, AssistantSPList newAssistants, KUndo2Command *parent)
+ : KUndo2Command(kundo2_i18n("Edit Assistants"), parent)
+ , m_canvas(canvas)
+ , m_origAssistants(origAssistants)
+ , m_newAssistants(newAssistants)
+ , m_index(-1)
+ , m_firstRedo(true)
+ , m_type(EDIT)
+{
+}
+
+EditAssistantsCommand::EditAssistantsCommand(QPointer<KisCanvas2> canvas, AssistantSPList origAssistants, AssistantSPList newAssistants, Type type, int index, KUndo2Command *parent)
+ : KUndo2Command((type == ADD ? kundo2_i18n("Add Assistant") : kundo2_i18n("Remove Assistant")), parent)
+ , m_canvas(canvas)
+ , m_origAssistants(origAssistants)
+ , m_newAssistants(newAssistants)
+ , m_index(index)
+ , m_firstRedo(true)
+ , m_type(type)
+{
+ Q_UNUSED(m_index);
+ KIS_ASSERT_RECOVER_RETURN(type != EDIT);
+}
+
+void EditAssistantsCommand::replaceWith(AssistantSPList assistants, Type type)
+{
+ AssistantSPList curAssistants = m_canvas->paintingAssistantsDecoration()->assistants();
+ if (type == EDIT) {
+ KIS_ASSERT_RECOVER_RETURN(curAssistants.size() == assistants.size());
+ } else if (type == ADD) {
+ KIS_ASSERT_RECOVER_RETURN(curAssistants.size() == assistants.size() - 1);
+ } else { // type == REMOVE
+ KIS_ASSERT_RECOVER_RETURN(curAssistants.size() == assistants.size() + 1);
+ }
+
+ Q_FOREACH (KisPaintingAssistantSP assistant, curAssistants) {
+ KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data());
+ if (grid) {
+ m_canvas->viewManager()->canvasResourceProvider()->removePerspectiveGrid(grid);
+ }
+ }
+
+ m_canvas->paintingAssistantsDecoration()->setAssistants(assistants);
+
+ Q_FOREACH (KisPaintingAssistantSP assistant, assistants) {
+ assistant->uncache();
+ KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data());
+ if (grid) {
+ m_canvas->viewManager()->canvasResourceProvider()->addPerspectiveGrid(grid);
+ }
+ }
+
+ m_canvas->updateCanvas();
+}
+
+void EditAssistantsCommand::undo()
+{
+ replaceWith(m_origAssistants, Type(-m_type));
+}
+
+void EditAssistantsCommand::redo()
+{
+ // this is a post-execution command
+ if (m_firstRedo) {
+ m_firstRedo = false;
+ return;
+ }
+ replaceWith(m_newAssistants, m_type);
+}
diff --git a/libs/ui/canvas/kis_change_guides_command.h b/plugins/assistants/Assistants/EditAssistantsCommand.h
similarity index 50%
copy from libs/ui/canvas/kis_change_guides_command.h
copy to plugins/assistants/Assistants/EditAssistantsCommand.h
index a66649b346..0f1ad55d06 100644
--- a/libs/ui/canvas/kis_change_guides_command.h
+++ b/plugins/assistants/Assistants/EditAssistantsCommand.h
@@ -1,48 +1,48 @@
/*
- * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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_CHANGE_GUIDES_COMMAND_H
-#define __KIS_CHANGE_GUIDES_COMMAND_H
+#include <QPointer>
-#include <QScopedPointer>
#include <kundo2command.h>
+#include <kis_painting_assistant.h>
-class KisDocument;
-class KUndo2Command;
-class KisGuidesConfig;
+class KisCanvas2;
-
-class KisChangeGuidesCommand : public KUndo2Command
+class EditAssistantsCommand : public KUndo2Command
{
+ using AssistantSPList = QList<KisPaintingAssistantSP>;
public:
- KisChangeGuidesCommand(KisDocument *doc, const KisGuidesConfig &newGuides);
- KisChangeGuidesCommand(KisDocument *document);
- ~KisChangeGuidesCommand() override;
+ enum Type {
+ ADD = -1,
+ REMOVE = 1,
+ EDIT = 0
+ };
+ EditAssistantsCommand(QPointer<KisCanvas2> canvas, AssistantSPList origAssistants, AssistantSPList newAssistants, KUndo2Command *parent = 0);
+ EditAssistantsCommand(QPointer<KisCanvas2> canvas, AssistantSPList origAssistants, AssistantSPList newAssistants, Type type, int index, KUndo2Command *parent = 0);
void undo() override;
void redo() override;
- int id() const override;
- bool mergeWith(const KUndo2Command *command) override;
-
private:
- struct Private;
- const QScopedPointer<Private> m_d;
+ void replaceWith(AssistantSPList assistants, Type type = EDIT);
+ QPointer<KisCanvas2> m_canvas;
+ AssistantSPList m_origAssistants, m_newAssistants;
+ int m_index;
+ bool m_firstRedo;
+ Type m_type;
};
-
-#endif /* __KIS_CHANGE_GUIDES_COMMAND_H */
diff --git a/plugins/assistants/Assistants/Ellipse.cc b/plugins/assistants/Assistants/Ellipse.cc
index 1acdd7aedf..5ad1f7d12b 100644
--- a/plugins/assistants/Assistants/Ellipse.cc
+++ b/plugins/assistants/Assistants/Ellipse.cc
@@ -1,192 +1,193 @@
/*
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "Ellipse.h"
#include <cmath>
#include <iostream>
Ellipse::Ellipse() : a(-1), b(-1)
{
}
Ellipse::Ellipse(const QPointF& _p1, const QPointF& _p2, const QPointF& _p3) : p1(_p1), p2(_p2), p3(_p3) {
changeMajor();
}
Ellipse::~Ellipse()
{
}
bool Ellipse::set(const QPointF& m1, const QPointF& m2, const QPointF& p)
{
bool changedMajor = m1 != p1 || m2 != p2,
changedMinor = !changedMajor && p != p3;
p1 = m1;
p2 = m2;
p3 = p;
if (changedMajor) {
return changeMajor();
} else if (changedMinor) {
return changeMinor();
} else {
return a > 0 && b > 0;
}
}
QPointF Ellipse::project(const QPointF& pt) const
{
if (a <= 0 || b <= 0) return pt; // not a valid ellipse
QPointF p = matrix.map(pt);
/*
* intersect line from (0,0) to p with the ellipse in canonical position
* the equation of the line is y = py/px x
* the equation of the ellipse is x^2/a^2 + y^2/b^2 = 1
* x=(a*b*px)/sqrt(a^2*py^2+b^2*px^2)
* y=(a*b*py)/sqrt(a^2*py^2+b^2*px^2)
*/
const qreal divisor = sqrt(a * a * p.y() * p.y() + b * b * p.x() * p.x());
if (divisor <= 0) return inverse.map(QPointF(a, 0)); // give up
const qreal ab = a * b, factor = 1.0 / divisor;
QPointF ep(ab * p.x() * factor, ab * p.y() * factor);
return inverse.map(ep);
/* return inverse.map(closest(matrix.map(pt)));*/
}
inline QPointF rotate90(const QPointF& p) {
return QPointF(p.y(), -p.x());
}
QRectF Ellipse::boundingRect() const
{
const QPointF d = rotate90((p2 - p1) * 0.5 * b / a);
const QPointF pts[4] = {
p1 + d,
p1 - d,
p2 + d,
p2 - d
};
QRectF ret;
for (int i = 0; i < 4; ++i) {
ret = ret.united(QRectF(pts[i], QSizeF(0.0001, 0.0001)));
}
return ret;
}
inline qreal sqrlength(const QPointF& vec)
{
return vec.x() * vec.x() + vec.y() * vec.y();
}
inline qreal length(const QPointF& vec)
{
return sqrt(vec.x() * vec.x() + vec.y() * vec.y());
}
bool Ellipse::changeMajor()
{
a = length(p1 - p2) * 0.5;
/*
* calculate transformation matrix
* x' = m11*x + m21*y + dx
* y' = m22*y + m12*x + dy
* m11 = m22, m12 = -m21 (rotations and translations only)
* x' = m11*x - m12*y + dx
* y' = m11*y + m12*x + dy
*
* then, transforming (x1, y1) to (x1', y1') and (x2, y2) to (x2', y2'):
*
* m11 = (y2*y2' + y1 * (y1'-y2') - y2*y1' + x2*x'2 - x1*x'2 + (x1-x2)*x1')
* ------------------------------------------------------------------
* (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2)
* m12 = -(x1*(y2'-y1') - x2*y2' + x2*y1' + x2'*y2 - x1'*y2 + (x1'-x2')*y1)
* ------------------------------------------------------------------
* (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2)
* dx = (x1*(-y2*y2' + y2*y1' - x2*x2') + y1*( x2*y2' - x2*y1' - x2'*y2 - x1'*y2) + x2'*y1^2 + x1^2*x2' + x1'*(y2^2 + x2^2 - x1*x2))
* ----------------------------------------------------------------------------------------------------------------------------
* (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2)
* dy = (x1*(-x2*y2' - x2*y1' + x2'*y2) + y1*(-y2*y2' - y2*y1' - x2*x2' + x2*x1') + y2'*y1^2 + x1^2*y2' + y1'(y2^2 + x2^2) - x1*x1'*y2)
* -------------------------------------------------------------------------------------------------------------------------------
* (y2^2 - 2*y1*y2 + y1^2 + x2^2 - 2*x1*x2 + x1^2)
*
* in our usage, to move the ellipse into canonical position:
*
* (x1, y1) = p1
* (x2, y2) = p2
* (x1', y1') = (-a, 0)
* (x2', y2') = (a, 0)
*/
const qreal
x1 = p1.x(),
x2 = p2.x(),
y1 = p1.y(),
y2 = p2.y(),
x1p = -a,
x2p = a,
x1sqr = x1 * x1,
x2sqr = x2 * x2,
y1sqr = y1 * y1,
y2sqr = y2 * y2,
factor = 1.0 / (x1sqr + y1sqr + x2sqr + y2sqr - 2.0 * y1 * y2 - 2.0 * x1 * x2),
m11 = (x2*x2p - x1*x2p + (x1-x2)*x1p) * factor,
m12 = -(x2p*y2 - x1p*y2 + (x1p-x2p)*y1) * factor,
dx = (x1*(-x2*x2p) + y1*(-x2p*y2 - x1p*y2) + x2p*y1sqr + x1sqr*x2p + x1p*(y2sqr + x2sqr - x1*x2)) * factor,
dy = (x1*(x2p*y2) + y1*(-x2*x2p + x2*x1p) - x1*x1p*y2) * factor;
matrix = QTransform(m11, m12, -m12, m11, dx, dy);
inverse = matrix.inverted();
return changeMinor();
}
bool Ellipse::changeMinor()
{
QPointF p = matrix.map(p3);
/*
* ellipse formula:
* x^2/a^2 + y^2/b^2 = 1
* b = sqrt(y^2 / (1 - x^2/a^2))
*/
const qreal
asqr = a * a,
xsqr = p.x() * p.x(),
ysqr = p.y() * p.y(),
divisor = (1.0 - xsqr / asqr);
if (divisor <= 0) {
// division by zero!
b = -1;
return false;
}
b = sqrt(ysqr / divisor);
return true;
}
bool Ellipse::setMajor1(const QPointF& p)
{
p1 = p;
return changeMajor();
}
bool Ellipse::setMajor2(const QPointF& p) {
p2 = p;
return changeMajor();
}
bool Ellipse::setPoint(const QPointF& p)
{
p3 = p;
return changeMinor();
}
diff --git a/plugins/assistants/Assistants/Ellipse.h b/plugins/assistants/Assistants/Ellipse.h
index 24dcecdeb2..843616e2e7 100644
--- a/plugins/assistants/Assistants/Ellipse.h
+++ b/plugins/assistants/Assistants/Ellipse.h
@@ -1,62 +1,63 @@
/*
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _ELLIPSE_H_
#define _ELLIPSE_H_
#include <QPointF>
#include <QTransform>
class Ellipse
{
public:
Ellipse();
Ellipse(const QPointF& p1, const QPointF& p2, const QPointF& p3);
~Ellipse();
QPointF project(const QPointF&) const; // find a close point on the ellipse
QRectF boundingRect() const; // find an axis-aligned box bounding this ellipse (inexact)
bool set(const QPointF& m1, const QPointF& m2, const QPointF& p); // set all points
const QPointF& major1() const { return p1; }
bool setMajor1(const QPointF& p);
const QPointF& major2() const { return p2; }
bool setMajor2(const QPointF& p);
const QPointF& point() const { return p3; }
bool setPoint(const QPointF& p);
const QTransform& getTransform() const { return matrix; }
const QTransform& getInverse() const { return inverse; }
qreal semiMajor() const { return a; }
qreal semiMinor() const { return b; }
private:
bool changeMajor(); // determine 'a', 'b', 'matrix' and 'inverse'
bool changeMinor(); // determine 'b'
QTransform matrix; // transformation turning p1, p2 and p3 into their corresponding points on the ellipse in canonical position
QTransform inverse; // inverse transformation
qreal a; // semi-major axis: half the distance between p1 and p2 (horizontal axis)
qreal b; // semi-minor axis (vertical axis)
// a may not actually be larger than b, but we don't care that much
QPointF p1;
QPointF p2;
QPointF p3;
};
#endif
diff --git a/plugins/assistants/Assistants/EllipseAssistant.cc b/plugins/assistants/Assistants/EllipseAssistant.cc
index 71bf0bf1b0..4431886c15 100644
--- a/plugins/assistants/Assistants/EllipseAssistant.cc
+++ b/plugins/assistants/Assistants/EllipseAssistant.cc
@@ -1,169 +1,181 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "EllipseAssistant.h"
#include <klocalizedstring.h>
#include "kis_debug.h"
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <math.h>
EllipseAssistant::EllipseAssistant()
: KisPaintingAssistant("ellipse", i18n("Ellipse assistant"))
{
}
+EllipseAssistant::EllipseAssistant(const EllipseAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+ , e(rhs.e)
+{
+}
+
+KisPaintingAssistantSP EllipseAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new EllipseAssistant(*this, handleMap));
+}
+
QPointF EllipseAssistant::project(const QPointF& pt) const
{
Q_ASSERT(isAssistantComplete());
e.set(*handles()[0], *handles()[1], *handles()[2]);
return e.project(pt);
}
QPointF EllipseAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/)
{
return project(pt);
}
void EllipseAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
QPoint mousePos;
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in the ellipse assistant, you may have passed arguments incorrectly:"<<canvas;
}
QTransform initialTransform = converter->documentToWidgetTransform();
if (isSnappingActive() && boundingRect().contains(initialTransform.inverted().map(mousePos), false) && previewVisible==true){
if (isAssistantComplete()){
if (e.set(*handles()[0], *handles()[1], *handles()[2])) {
// valid ellipse
gc.setTransform(initialTransform);
gc.setTransform(e.getInverse(), true);
QPainterPath path;
//path.moveTo(QPointF(-e.semiMajor(), 0)); path.lineTo(QPointF(e.semiMajor(), 0));
//path.moveTo(QPointF(0, -e.semiMinor())); path.lineTo(QPointF(0, e.semiMinor()));
// Draw the ellipse
path.addEllipse(QPointF(0, 0), e.semiMajor(), e.semiMinor());
drawPreview(gc, path);
}
}
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
void EllipseAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false || handles().size() < 2){
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
if (handles().size() == 2) {
// just draw the axis
gc.setTransform(initialTransform);
QPainterPath path;
path.moveTo(*handles()[0]);
path.lineTo(*handles()[1]);
drawPath(gc, path, isSnappingActive());
return;
}
if (e.set(*handles()[0], *handles()[1], *handles()[2])) {
// valid ellipse
gc.setTransform(initialTransform);
gc.setTransform(e.getInverse(), true);
QPainterPath path;
path.moveTo(QPointF(-e.semiMajor(), 0)); path.lineTo(QPointF(e.semiMajor(), 0));
path.moveTo(QPointF(0, -e.semiMinor())); path.lineTo(QPointF(0, e.semiMinor()));
// Draw the ellipse
path.addEllipse(QPointF(0, 0), e.semiMajor(), e.semiMinor());
drawPath(gc, path, isSnappingActive());
}
}
QRect EllipseAssistant::boundingRect() const
{
if (!isAssistantComplete()) {
return KisPaintingAssistant::boundingRect();
}
if (e.set(*handles()[0], *handles()[1], *handles()[2])) {
return e.boundingRect().adjusted(-2, -2, 2, 2).toAlignedRect();
} else {
return QRect();
}
}
QPointF EllipseAssistant::buttonPosition() const
{
return (*handles()[0] + *handles()[1]) * 0.5;
}
bool EllipseAssistant::isAssistantComplete() const
{
return handles().size() >= 3;
}
EllipseAssistantFactory::EllipseAssistantFactory()
{
}
EllipseAssistantFactory::~EllipseAssistantFactory()
{
}
QString EllipseAssistantFactory::id() const
{
return "ellipse";
}
QString EllipseAssistantFactory::name() const
{
return i18n("Ellipse");
}
KisPaintingAssistant* EllipseAssistantFactory::createPaintingAssistant() const
{
return new EllipseAssistant;
}
diff --git a/plugins/assistants/Assistants/EllipseAssistant.h b/plugins/assistants/Assistants/EllipseAssistant.h
index 8354aa22b9..6e3b6b02aa 100644
--- a/plugins/assistants/Assistants/EllipseAssistant.h
+++ b/plugins/assistants/Assistants/EllipseAssistant.h
@@ -1,55 +1,58 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _ELLIPSE_ASSISTANT_H_
#define _ELLIPSE_ASSISTANT_H_
#include "kis_painting_assistant.h"
#include "Ellipse.h"
#include <QObject>
class EllipseAssistant : public KisPaintingAssistant
{
public:
EllipseAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
QPointF buttonPosition() const override;
int numHandles() const override { return 3; }
bool isAssistantComplete() const override;
protected:
QRect boundingRect() const override;
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt) const;
+ explicit EllipseAssistant(const EllipseAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
mutable Ellipse e;
};
class EllipseAssistantFactory : public KisPaintingAssistantFactory
{
public:
EllipseAssistantFactory();
~EllipseAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/FisheyePointAssistant.cc b/plugins/assistants/Assistants/FisheyePointAssistant.cc
index b610d4afb9..610f2664fa 100644
--- a/plugins/assistants/Assistants/FisheyePointAssistant.cc
+++ b/plugins/assistants/Assistants/FisheyePointAssistant.cc
@@ -1,227 +1,238 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "FisheyePointAssistant.h"
#include "kis_debug.h"
#include <klocalizedstring.h>
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <kis_algebra_2d.h>
#include <math.h>
#include <limits>
FisheyePointAssistant::FisheyePointAssistant()
: KisPaintingAssistant("fisheye-point", i18n("Fish Eye Point assistant"))
{
}
+FisheyePointAssistant::FisheyePointAssistant(const FisheyePointAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+ , e(rhs.e)
+ , extraE(rhs.extraE)
+{
+}
+
+KisPaintingAssistantSP FisheyePointAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new FisheyePointAssistant(*this, handleMap));
+}
+
QPointF FisheyePointAssistant::project(const QPointF& pt, const QPointF& strokeBegin)
{
const static QPointF nullPoint(std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN());
Q_ASSERT(isAssistantComplete());
e.set(*handles()[0], *handles()[1], *handles()[2]);
qreal
dx = pt.x() - strokeBegin.x(),
dy = pt.y() - strokeBegin.y();
if (dx * dx + dy * dy < 4.0) {
// allow some movement before snapping
return strokeBegin;
}
//set the extrapolation ellipse.
if (e.set(*handles()[0], *handles()[1], *handles()[2])){
QLineF radius(*handles()[1], *handles()[0]);
radius.setAngle(fmod(radius.angle()+180.0,360.0));
QLineF radius2(*handles()[0], *handles()[1]);
radius2.setAngle(fmod(radius2.angle()+180.0,360.0));
if ( extraE.set(*handles()[0], *handles()[1],strokeBegin ) ) {
return extraE.project(pt);
} else if (extraE.set(radius.p1(), radius.p2(),strokeBegin)) {
return extraE.project(pt);
} else if (extraE.set(radius2.p1(), radius2.p2(),strokeBegin)){
return extraE.project(pt);
}
}
return nullPoint;
}
QPointF FisheyePointAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin)
{
return project(pt, strokeBegin);
}
void FisheyePointAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
- QPointF delta(0,0);
+
QPointF mousePos(0,0);
- QPointF endPoint(0,0);//this is the final point that the line is being extended to, we seek it just outside the view port//
- QPointF otherHandle(0,0);
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<<canvas;
}
QTransform initialTransform = converter->documentToWidgetTransform();
if (isSnappingActive() && previewVisible == true ) {
if (isAssistantComplete()){
if (e.set(*handles()[0], *handles()[1], *handles()[2])) {
if (extraE.set(*handles()[0], *handles()[1], initialTransform.inverted().map(mousePos))){
gc.setTransform(initialTransform);
gc.setTransform(e.getInverse(), true);
QPainterPath path;
// Draw the ellipse
path.addEllipse(QPointF(0, 0), extraE.semiMajor(), extraE.semiMinor());
drawPreview(gc, path);
}
QLineF radius(*handles()[1], *handles()[0]);
radius.setAngle(fmod(radius.angle()+180.0,360.0));
if (extraE.set(radius.p1(), radius.p2(), initialTransform.inverted().map(mousePos))){
gc.setTransform(initialTransform);
gc.setTransform(extraE.getInverse(), true);
QPainterPath path;
// Draw the ellipse
path.addEllipse(QPointF(0, 0), extraE.semiMajor(), extraE.semiMinor());
drawPreview(gc, path);
}
QLineF radius2(*handles()[0], *handles()[1]);
radius2.setAngle(fmod(radius2.angle()+180.0,360.0));
if (extraE.set(radius2.p1(), radius2.p2(), initialTransform.inverted().map(mousePos))){
gc.setTransform(initialTransform);
gc.setTransform(extraE.getInverse(), true);
QPainterPath path;
// Draw the ellipse
path.addEllipse(QPointF(0, 0), extraE.semiMajor(), extraE.semiMinor());
drawPreview(gc, path);
}
}
}
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
void FisheyePointAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false){
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
if (handles().size() == 2) {
// just draw the axis
gc.setTransform(initialTransform);
QPainterPath path;
path.moveTo(*handles()[0]);
path.lineTo(*handles()[1]);
drawPath(gc, path, isSnappingActive());
return;
}
if (e.set(*handles()[0], *handles()[1], *handles()[2])) {
// valid ellipse
gc.setTransform(initialTransform);
gc.setTransform(e.getInverse(), true);
QPainterPath path;
//path.moveTo(QPointF(-e.semiMajor(), -e.semiMinor())); path.lineTo(QPointF(e.semiMajor(), -e.semiMinor()));
path.moveTo(QPointF(-e.semiMajor(), -e.semiMinor())); path.lineTo(QPointF(-e.semiMajor(), e.semiMinor()));
//path.moveTo(QPointF(-e.semiMajor(), e.semiMinor())); path.lineTo(QPointF(e.semiMajor(), e.semiMinor()));
path.moveTo(QPointF(e.semiMajor(), -e.semiMinor())); path.lineTo(QPointF(e.semiMajor(), e.semiMinor()));
path.moveTo(QPointF(-(e.semiMajor()*3), -e.semiMinor())); path.lineTo(QPointF(-(e.semiMajor()*3), e.semiMinor()));
path.moveTo(QPointF((e.semiMajor()*3), -e.semiMinor())); path.lineTo(QPointF((e.semiMajor()*3), e.semiMinor()));
path.moveTo(QPointF(-e.semiMajor(), 0)); path.lineTo(QPointF(e.semiMajor(), 0));
//path.moveTo(QPointF(0, -e.semiMinor())); path.lineTo(QPointF(0, e.semiMinor()));
// Draw the ellipse
path.addEllipse(QPointF(0, 0), e.semiMajor(), e.semiMinor());
drawPath(gc, path, isSnappingActive());
}
}
QRect FisheyePointAssistant::boundingRect() const
{
if (!isAssistantComplete()) {
return KisPaintingAssistant::boundingRect();
}
if (e.set(*handles()[0], *handles()[1], *handles()[2])) {
return e.boundingRect().adjusted(-(e.semiMajor()*2), -2, (e.semiMajor()*2), 2).toAlignedRect();
} else {
return QRect();
}
}
QPointF FisheyePointAssistant::buttonPosition() const
{
return (*handles()[0] + *handles()[1]) * 0.5;
}
bool FisheyePointAssistant::isAssistantComplete() const
{
return handles().size() >= 3;
}
FisheyePointAssistantFactory::FisheyePointAssistantFactory()
{
}
FisheyePointAssistantFactory::~FisheyePointAssistantFactory()
{
}
QString FisheyePointAssistantFactory::id() const
{
return "fisheye-point";
}
QString FisheyePointAssistantFactory::name() const
{
return i18n("Fish Eye Point");
}
KisPaintingAssistant* FisheyePointAssistantFactory::createPaintingAssistant() const
{
return new FisheyePointAssistant;
}
diff --git a/plugins/assistants/Assistants/FisheyePointAssistant.h b/plugins/assistants/Assistants/FisheyePointAssistant.h
index 3c4ce7a521..f2a27d5a48 100644
--- a/plugins/assistants/Assistants/FisheyePointAssistant.h
+++ b/plugins/assistants/Assistants/FisheyePointAssistant.h
@@ -1,63 +1,66 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _FISHEYEPOINT_ASSISTANT_H_
#define _FISHEYEPOINT_ASSISTANT_H_
#include "kis_painting_assistant.h"
#include "Ellipse.h"
#include <QObject>
#include <QPolygonF>
#include <QLineF>
#include <QTransform>
//class FisheyePoint;
class FisheyePointAssistant : public KisPaintingAssistant
{
public:
FisheyePointAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
//virtual void endStroke();
QPointF buttonPosition() const override;
int numHandles() const override { return 3; }
bool isAssistantComplete() const override;
protected:
QRect boundingRect() const override;
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt, const QPointF& strokeBegin);
+ explicit FisheyePointAssistant(const FisheyePointAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
mutable Ellipse e;
mutable Ellipse extraE;
};
class FisheyePointAssistantFactory : public KisPaintingAssistantFactory
{
public:
FisheyePointAssistantFactory();
~FisheyePointAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/InfiniteRulerAssistant.cc b/plugins/assistants/Assistants/InfiniteRulerAssistant.cc
index 7ed5afc528..f85a188367 100644
--- a/plugins/assistants/Assistants/InfiniteRulerAssistant.cc
+++ b/plugins/assistants/Assistants/InfiniteRulerAssistant.cc
@@ -1,162 +1,171 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "InfiniteRulerAssistant.h"
#include "kis_debug.h"
#include <klocalizedstring.h>
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <kis_algebra_2d.h>
#include <math.h>
InfiniteRulerAssistant::InfiniteRulerAssistant()
: KisPaintingAssistant("infinite ruler", i18n("Infinite Ruler assistant"))
{
}
+InfiniteRulerAssistant::InfiniteRulerAssistant(const InfiniteRulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+{
+}
+
+KisPaintingAssistantSP InfiniteRulerAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new InfiniteRulerAssistant(*this, handleMap));
+}
+
QPointF InfiniteRulerAssistant::project(const QPointF& pt, const QPointF& strokeBegin)
{
Q_ASSERT(isAssistantComplete());
//code nicked from the perspective ruler.
qreal
dx = pt.x() - strokeBegin.x(),
dy = pt.y() - strokeBegin.y();
if (dx * dx + dy * dy < 4.0) {
// allow some movement before snapping
return strokeBegin;
}
//dbgKrita<<strokeBegin<< ", " <<*handles()[0];
QLineF snapLine = QLineF(*handles()[0], *handles()[1]);
dx = snapLine.dx();
dy = snapLine.dy();
const qreal
dx2 = dx * dx,
dy2 = dy * dy,
invsqrlen = 1.0 / (dx2 + dy2);
QPointF r(dx2 * pt.x() + dy2 * snapLine.x1() + dx * dy * (pt.y() - snapLine.y1()),
dx2 * snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - snapLine.x1()));
r *= invsqrlen;
return r;
//return pt;
}
QPointF InfiniteRulerAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin)
{
return project(pt, strokeBegin);
}
void InfiniteRulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
- QPointF delta(0,0);//this is the difference between the window and the widget//
QPointF mousePos(0,0);
- QPointF endPoint(0,0);//this is the final point that the line is being extended to, we seek it just outside the view port//
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<<canvas;
}
if (isAssistantComplete() && isSnappingActive() && previewVisible == true) {
//don't draw if invalid.
QTransform initialTransform = converter->documentToWidgetTransform();
QLineF snapLine= QLineF(initialTransform.map(*handles()[0]), initialTransform.map(*handles()[1]));
QRect viewport= gc.viewport();
KisAlgebra2D::intersectLineRect(snapLine, viewport);
QPainterPath path;
path.moveTo(snapLine.p1());
path.lineTo(snapLine.p2());
drawPreview(gc, path);//and we draw the preview.
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
void InfiniteRulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false || !isAssistantComplete()){
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
// Draw the line
QPointF p1 = *handles()[0];
QPointF p2 = *handles()[1];
gc.setTransform(initialTransform);
QPainterPath path;
path.moveTo(p1);
path.lineTo(p2);
drawPath(gc, path, isSnappingActive());
}
QPointF InfiniteRulerAssistant::buttonPosition() const
{
return (*handles()[0]);
}
bool InfiniteRulerAssistant::isAssistantComplete() const
{
return handles().size() >= 2;
}
InfiniteRulerAssistantFactory::InfiniteRulerAssistantFactory()
{
}
InfiniteRulerAssistantFactory::~InfiniteRulerAssistantFactory()
{
}
QString InfiniteRulerAssistantFactory::id() const
{
return "infinite ruler";
}
QString InfiniteRulerAssistantFactory::name() const
{
return i18n("Infinite Ruler");
}
KisPaintingAssistant* InfiniteRulerAssistantFactory::createPaintingAssistant() const
{
return new InfiniteRulerAssistant;
}
diff --git a/plugins/assistants/Assistants/InfiniteRulerAssistant.h b/plugins/assistants/Assistants/InfiniteRulerAssistant.h
index 4843a2a719..db639da29c 100644
--- a/plugins/assistants/Assistants/InfiniteRulerAssistant.h
+++ b/plugins/assistants/Assistants/InfiniteRulerAssistant.h
@@ -1,60 +1,63 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _INFINITERULER_ASSISTANT_H_
#define _INFINITERULER_ASSISTANT_H_
#include "kis_painting_assistant.h"
#include <QObject>
#include <QPolygonF>
#include <QLineF>
#include <QTransform>
/* Design:
*/
class InfiniteRuler;
class InfiniteRulerAssistant : public KisPaintingAssistant
{
public:
InfiniteRulerAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
//virtual void endStroke();
QPointF buttonPosition() const override;
int numHandles() const override { return 2; }
bool isAssistantComplete() const override;
protected:
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt, const QPointF& strokeBegin);
+ explicit InfiniteRulerAssistant(const InfiniteRulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
};
class InfiniteRulerAssistantFactory : public KisPaintingAssistantFactory
{
public:
InfiniteRulerAssistantFactory();
~InfiniteRulerAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/ParallelRulerAssistant.cc b/plugins/assistants/Assistants/ParallelRulerAssistant.cc
index 3579927e05..2d1fa2a943 100644
--- a/plugins/assistants/Assistants/ParallelRulerAssistant.cc
+++ b/plugins/assistants/Assistants/ParallelRulerAssistant.cc
@@ -1,169 +1,178 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "ParallelRulerAssistant.h"
#include "kis_debug.h"
#include <klocalizedstring.h>
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <kis_algebra_2d.h>
#include <math.h>
ParallelRulerAssistant::ParallelRulerAssistant()
: KisPaintingAssistant("parallel ruler", i18n("Parallel Ruler assistant"))
{
}
+KisPaintingAssistantSP ParallelRulerAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new ParallelRulerAssistant(*this, handleMap));
+}
+
+ParallelRulerAssistant::ParallelRulerAssistant(const ParallelRulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+{
+}
+
QPointF ParallelRulerAssistant::project(const QPointF& pt, const QPointF& strokeBegin)
{
Q_ASSERT(isAssistantComplete());
//code nicked from the perspective ruler.
qreal dx = pt.x() - strokeBegin.x();
qreal dy = pt.y() - strokeBegin.y();
if (dx * dx + dy * dy < 4.0) {
return strokeBegin; // allow some movement before snapping
}
//dbgKrita<<strokeBegin<< ", " <<*handles()[0];
QLineF snapLine = QLineF(*handles()[0], *handles()[1]);
QPointF translation = (*handles()[0]-strokeBegin)*-1.0;
snapLine = snapLine.translated(translation);
dx = snapLine.dx();
dy = snapLine.dy();
const qreal
dx2 = dx * dx,
dy2 = dy * dy,
invsqrlen = 1.0 / (dx2 + dy2);
QPointF r(dx2 * pt.x() + dy2 * snapLine.x1() + dx * dy * (pt.y() - snapLine.y1()),
dx2 * snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - snapLine.x1()));
r *= invsqrlen;
return r;
//return pt;
}
QPointF ParallelRulerAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin)
{
return project(pt, strokeBegin);
}
void ParallelRulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
- QPointF delta(0,0);//this is the difference between the vanishing point and the mouse-position//
QPointF mousePos(0,0);
- QPointF endPoint(0,0);//this is the final point that the line is being extended to, we seek it just outside the view port//
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<<canvas;
}
if (isAssistantComplete() && isSnappingActive() && previewVisible==true) {
//don't draw if invalid.
QTransform initialTransform = converter->documentToWidgetTransform();
QLineF snapLine= QLineF(initialTransform.map(*handles()[0]), initialTransform.map(*handles()[1]));
QPointF translation = (initialTransform.map(*handles()[0])-mousePos)*-1.0;
snapLine= snapLine.translated(translation);
QRect viewport= gc.viewport();
KisAlgebra2D::intersectLineRect(snapLine, viewport);
QPainterPath path;
path.moveTo(snapLine.p1());
path.lineTo(snapLine.p2());
drawPreview(gc, path);//and we draw the preview.
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
void ParallelRulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false || !isAssistantComplete()){
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
// Draw the line
QPointF p1 = *handles()[0];
QPointF p2 = *handles()[1];
gc.setTransform(initialTransform);
QPainterPath path;
path.moveTo(p1);
path.lineTo(p2);
drawPath(gc, path, isSnappingActive());
}
QPointF ParallelRulerAssistant::buttonPosition() const
{
return (*handles()[0] + *handles()[1]) * 0.5;
}
bool ParallelRulerAssistant::isAssistantComplete() const
{
return handles().size() >= 2;
}
ParallelRulerAssistantFactory::ParallelRulerAssistantFactory()
{
}
ParallelRulerAssistantFactory::~ParallelRulerAssistantFactory()
{
}
QString ParallelRulerAssistantFactory::id() const
{
return "parallel ruler";
}
QString ParallelRulerAssistantFactory::name() const
{
return i18n("Parallel Ruler");
}
KisPaintingAssistant* ParallelRulerAssistantFactory::createPaintingAssistant() const
{
return new ParallelRulerAssistant;
}
diff --git a/plugins/assistants/Assistants/ParallelRulerAssistant.h b/plugins/assistants/Assistants/ParallelRulerAssistant.h
index 0c3e485729..5c82ad7246 100644
--- a/plugins/assistants/Assistants/ParallelRulerAssistant.h
+++ b/plugins/assistants/Assistants/ParallelRulerAssistant.h
@@ -1,60 +1,63 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _PARALLELRULER_ASSISTANT_H_
#define _PARALLELRULER_ASSISTANT_H_
#include "kis_painting_assistant.h"
#include <QObject>
#include <QPolygonF>
#include <QLineF>
#include <QTransform>
/* Design:
*/
class ParallelRuler;
class ParallelRulerAssistant : public KisPaintingAssistant
{
public:
ParallelRulerAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
//virtual void endStroke();
QPointF buttonPosition() const override;
int numHandles() const override { return 2; }
bool isAssistantComplete() const override;
protected:
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt, const QPointF& strokeBegin);
+ explicit ParallelRulerAssistant(const ParallelRulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
};
class ParallelRulerAssistantFactory : public KisPaintingAssistantFactory
{
public:
ParallelRulerAssistantFactory();
~ParallelRulerAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/PerspectiveAssistant.cc b/plugins/assistants/Assistants/PerspectiveAssistant.cc
index 7f5811b725..8e0c327e16 100644
--- a/plugins/assistants/Assistants/PerspectiveAssistant.cc
+++ b/plugins/assistants/Assistants/PerspectiveAssistant.cc
@@ -1,463 +1,481 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "PerspectiveAssistant.h"
#include "kis_debug.h"
#include <klocalizedstring.h>
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <kis_algebra_2d.h>
#include <math.h>
#include <limits>
PerspectiveAssistant::PerspectiveAssistant(QObject *parent)
: KisAbstractPerspectiveGrid(parent)
, KisPaintingAssistant("perspective", i18n("Perspective assistant"))
{
}
+PerspectiveAssistant::PerspectiveAssistant(const PerspectiveAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisAbstractPerspectiveGrid(rhs.parent())
+ , KisPaintingAssistant(rhs, handleMap)
+ , m_snapLine(rhs.m_snapLine)
+ , m_cachedTransform(rhs.m_cachedTransform)
+ , m_cachedPolygon(rhs.m_cachedPolygon)
+ , m_cacheValid(rhs.m_cacheValid)
+{
+ for (int i = 0; i < 4; ++i) {
+ m_cachedPoints[i] = rhs.m_cachedPoints[i];
+ }
+}
+
+KisPaintingAssistantSP PerspectiveAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new PerspectiveAssistant(*this, handleMap));
+}
// squared distance from a point to a line
inline qreal distsqr(const QPointF& pt, const QLineF& line)
{
// distance = |(p2 - p1) x (p1 - pt)| / |p2 - p1|
// magnitude of (p2 - p1) x (p1 - pt)
const qreal cross = (line.dx() * (line.y1() - pt.y()) - line.dy() * (line.x1() - pt.x()));
return cross * cross / (line.dx() * line.dx() + line.dy() * line.dy());
}
QPointF PerspectiveAssistant::project(const QPointF& pt, const QPointF& strokeBegin)
{
const static QPointF nullPoint(std::numeric_limits<qreal>::quiet_NaN(), std::numeric_limits<qreal>::quiet_NaN());
Q_ASSERT(isAssistantComplete());
if (m_snapLine.isNull()) {
QPolygonF poly;
QTransform transform;
if (!getTransform(poly, transform)) {
return nullPoint;
}
if (!poly.containsPoint(strokeBegin, Qt::OddEvenFill)) {
return nullPoint; // avoid problems with multiple assistants: only snap if starting in the grid
}
const qreal dx = pt.x() - strokeBegin.x();
const qreal dy = pt.y() - strokeBegin.y();
if (dx * dx + dy * dy < 4.0) {
return strokeBegin; // allow some movement before snapping
}
// construct transformation
bool invertible;
const QTransform inverse = transform.inverted(&invertible);
if (!invertible) {
return nullPoint; // shouldn't happen
}
// figure out which direction to go
const QPointF start = inverse.map(strokeBegin);
const QLineF verticalLine = QLineF(strokeBegin, transform.map(start + QPointF(0, 1)));
const QLineF horizontalLine = QLineF(strokeBegin, transform.map(start + QPointF(1, 0)));
// determine whether the horizontal or vertical line is closer to the point
m_snapLine = distsqr(pt, verticalLine) < distsqr(pt, horizontalLine) ? verticalLine : horizontalLine;
}
// snap to line
const qreal
dx = m_snapLine.dx(),
dy = m_snapLine.dy(),
dx2 = dx * dx,
dy2 = dy * dy,
invsqrlen = 1.0 / (dx2 + dy2);
QPointF r(dx2 * pt.x() + dy2 * m_snapLine.x1() + dx * dy * (pt.y() - m_snapLine.y1()),
dx2 * m_snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - m_snapLine.x1()));
r *= invsqrlen;
return r;
}
QPointF PerspectiveAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin)
{
return project(pt, strokeBegin);
}
void PerspectiveAssistant::endStroke()
{
m_snapLine = QLineF();
}
bool PerspectiveAssistant::contains(const QPointF& pt) const
{
QPolygonF poly;
if (!quad(poly)) return false;
return poly.containsPoint(pt, Qt::OddEvenFill);
}
inline qreal lengthSquared(const QPointF& vector)
{
return vector.x() * vector.x() + vector.y() * vector.y();
}
inline qreal localScale(const QTransform& transform, QPointF pt)
{
// const qreal epsilon = 1e-5, epsilonSquared = epsilon * epsilon;
// qreal xSizeSquared = lengthSquared(transform.map(pt + QPointF(epsilon, 0.0)) - orig) / epsilonSquared;
// qreal ySizeSquared = lengthSquared(transform.map(pt + QPointF(0.0, epsilon)) - orig) / epsilonSquared;
// xSizeSquared /= lengthSquared(transform.map(QPointF(0.0, pt.y())) - transform.map(QPointF(1.0, pt.y())));
// ySizeSquared /= lengthSquared(transform.map(QPointF(pt.x(), 0.0)) - transform.map(QPointF(pt.x(), 1.0)));
// when taking the limit epsilon->0:
// xSizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4
// ySizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4
// xSize*ySize=(abs(m13*x+m33)*abs(m13*x+m33+m23)*abs(m23*y+m33)*abs(m23*y+m33+m13))/(m23*y+m13*x+m33)^4
const qreal x = transform.m13() * pt.x(),
y = transform.m23() * pt.y(),
a = x + transform.m33(),
b = y + transform.m33(),
c = x + y + transform.m33(),
d = c * c;
return fabs(a*(a + transform.m23())*b*(b + transform.m13()))/(d * d);
}
// returns the reciprocal of the maximum local scale at the points (0,0),(0,1),(1,0),(1,1)
inline qreal inverseMaxLocalScale(const QTransform& transform)
{
const qreal a = fabs((transform.m33() + transform.m13()) * (transform.m33() + transform.m23())),
b = fabs((transform.m33()) * (transform.m13() + transform.m33() + transform.m23())),
d00 = transform.m33() * transform.m33(),
d11 = (transform.m33() + transform.m23() + transform.m13())*(transform.m33() + transform.m23() + transform.m13()),
s0011 = qMin(d00, d11) / a,
d10 = (transform.m33() + transform.m13()) * (transform.m33() + transform.m13()),
d01 = (transform.m33() + transform.m23()) * (transform.m33() + transform.m23()),
s1001 = qMin(d10, d01) / b;
return qMin(s0011, s1001);
}
qreal PerspectiveAssistant::distance(const QPointF& pt) const
{
QPolygonF poly;
QTransform transform;
if (!getTransform(poly, transform)) {
return 1.0;
}
bool invertible;
QTransform inverse = transform.inverted(&invertible);
if (!invertible) {
return 1.0;
}
if (inverse.m13() * pt.x() + inverse.m23() * pt.y() + inverse.m33() == 0.0) {
return 0.0; // point at infinity
}
return localScale(transform, inverse.map(pt)) * inverseMaxLocalScale(transform);
}
// draw a vanishing point marker
inline QPainterPath drawX(const QPointF& pt)
{
QPainterPath path;
path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0));
path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0));
return path;
}
void PerspectiveAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
QTransform initialTransform = converter->documentToWidgetTransform();
//QTransform reverseTransform = converter->widgetToDocument();
QPolygonF poly;
QTransform transform; // unused, but computed for caching purposes
if (getTransform(poly, transform) && assistantVisible==true) {
// draw vanishing points
QPointF intersection(0, 0);
if (fmod(QLineF(poly[0], poly[1]).angle(), 180.0)>=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) {
if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) {
drawPath(gc, drawX(initialTransform.map(intersection)));
}
}
if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){
if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) {
drawPath(gc, drawX(initialTransform.map(intersection)));
}
}
}
if (isSnappingActive() && getTransform(poly, transform) && previewVisible==true){
//find vanishing point, find mouse, draw line between both.
QPainterPath path2;
QPointF intersection(0, 0);//this is the position of the vanishing point.
QPointF mousePos(0,0);
QLineF snapLine;
QRect viewport= gc.viewport();
QRect bounds;
if (canvas){
//simplest, cheapest way to get the mouse-position
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
}
else {
//...of course, you need to have access to a canvas-widget for that.
mousePos = QCursor::pos(); // this'll give an offset
dbgFile<<"canvas does not exist, you may have passed arguments incorrectly:"<<canvas;
}
//figure out if point is in the perspective grid
QPointF intersectTransformed(0, 0); // dummy for holding transformed intersection so the code is more readable.
if (poly.containsPoint(initialTransform.inverted().map(mousePos), Qt::OddEvenFill)==true){
// check if the lines aren't parallel to each other to avoid calculation errors in the intersection calculation (bug 345754)//
if (fmod(QLineF(poly[0], poly[1]).angle(), 180.0)>=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) {
if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) {
intersectTransformed = initialTransform.map(intersection);
snapLine = QLineF(intersectTransformed, mousePos);
KisAlgebra2D::intersectLineRect(snapLine, viewport);
bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint());
QPainterPath path;
if (bounds.contains(intersectTransformed.toPoint())){
path2.moveTo(intersectTransformed);
path2.lineTo(snapLine.p1());
}
else {
path2.moveTo(snapLine.p1());
path2.lineTo(snapLine.p2());
}
}
}
if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){
if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) {
intersectTransformed = initialTransform.map(intersection);
snapLine = QLineF(intersectTransformed, mousePos);
KisAlgebra2D::intersectLineRect(snapLine, viewport);
bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint());
QPainterPath path;
if (bounds.contains(intersectTransformed.toPoint())){
path2.moveTo(intersectTransformed);
path2.lineTo(snapLine.p1());
}
else {
path2.moveTo(snapLine.p1());
path2.lineTo(snapLine.p2());
}
}
}
drawPreview(gc, path2);
}
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached,canvas, assistantVisible, previewVisible);
}
void PerspectiveAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false) {
return;
}
gc.setTransform(converter->documentToWidgetTransform());
QPolygonF poly;
QTransform transform;
if (!getTransform(poly, transform)) {
// color red for an invalid transform, but not for an incomplete one
if(isAssistantComplete()) {
gc.setPen(QColor(255, 0, 0, 125));
gc.drawPolygon(poly);
} else {
QPainterPath path;
path.addPolygon(poly);
drawPath(gc, path, isSnappingActive());
}
} else {
gc.setPen(QColor(0, 0, 0, 125));
gc.setTransform(transform, true);
QPainterPath path;
for (int y = 0; y <= 8; ++y)
{
path.moveTo(QPointF(0.0, y * 0.125));
path.lineTo(QPointF(1.0, y * 0.125));
}
for (int x = 0; x <= 8; ++x)
{
path.moveTo(QPointF(x * 0.125, 0.0));
path.lineTo(QPointF(x * 0.125, 1.0));
}
drawPath(gc, path, isSnappingActive());
}
}
QPointF PerspectiveAssistant::buttonPosition() const
{
QPointF centroid(0, 0);
for (int i = 0; i < 4; ++i) {
centroid += *handles()[i];
}
return centroid * 0.25;
}
template <typename T> int sign(T a)
{
return (a > 0) - (a < 0);
}
// perpendicular dot product
inline qreal pdot(const QPointF& a, const QPointF& b)
{
return a.x() * b.y() - a.y() * b.x();
}
bool PerspectiveAssistant::quad(QPolygonF& poly) const
{
for (int i = 0; i < handles().size(); ++i) {
poly.push_back(*handles()[i]);
}
if (!isAssistantComplete()) {
return false;
}
int sum = 0;
int signs[4];
for (int i = 0; i < 4; ++i) {
int j = (i == 3) ? 0 : (i + 1);
int k = (j == 3) ? 0 : (j + 1);
signs[i] = sign(pdot(poly[j] - poly[i], poly[k] - poly[j]));
sum += signs[i];
}
if (sum == 0) {
// complex (crossed)
for (int i = 0; i < 4; ++i) {
int j = (i == 3) ? 0 : (i + 1);
if (signs[i] * signs[j] == -1) {
// opposite signs: uncross
std::swap(poly[i], poly[j]);
return true;
}
}
// okay, maybe it's just a line
return false;
} else if (sum != 4 && sum != -4) {
// concave, or a triangle
if (sum == 2 || sum == -2) {
// concave, let's return a triangle instead
for (int i = 0; i < 4; ++i) {
int j = (i == 3) ? 0 : (i + 1);
if (signs[i] != sign(sum)) {
// wrong sign: drop the inside node
poly.remove(j);
return false;
}
}
}
return false;
}
// convex
return true;
}
bool PerspectiveAssistant::getTransform(QPolygonF& poly, QTransform& transform) const
{
if (m_cachedPolygon.size() != 0 && isAssistantComplete()) {
for (int i = 0; i <= 4; ++i) {
if (i == 4) {
poly = m_cachedPolygon;
transform = m_cachedTransform;
return m_cacheValid;
}
if (m_cachedPoints[i] != *handles()[i]) break;
}
}
m_cachedPolygon.clear();
m_cacheValid = false;
if (!quad(poly)) {
m_cachedPolygon = poly;
return false;
}
if (!QTransform::squareToQuad(poly, transform)) {
qWarning("Failed to create perspective mapping");
return false;
}
for (int i = 0; i < 4; ++i) {
m_cachedPoints[i] = *handles()[i];
}
m_cachedPolygon = poly;
m_cachedTransform = transform;
m_cacheValid = true;
return true;
}
bool PerspectiveAssistant::isAssistantComplete() const
{
return handles().size() >= 4; // specify 4 corners to make assistant complete
}
PerspectiveAssistantFactory::PerspectiveAssistantFactory()
{
}
PerspectiveAssistantFactory::~PerspectiveAssistantFactory()
{
}
QString PerspectiveAssistantFactory::id() const
{
return "perspective";
}
QString PerspectiveAssistantFactory::name() const
{
return i18n("Perspective");
}
KisPaintingAssistant* PerspectiveAssistantFactory::createPaintingAssistant() const
{
return new PerspectiveAssistant;
}
diff --git a/plugins/assistants/Assistants/PerspectiveAssistant.h b/plugins/assistants/Assistants/PerspectiveAssistant.h
index 6cb10a56d4..7d4ccac50f 100644
--- a/plugins/assistants/Assistants/PerspectiveAssistant.h
+++ b/plugins/assistants/Assistants/PerspectiveAssistant.h
@@ -1,75 +1,78 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _PERSPECTIVE_ASSISTANT_H_
#define _PERSPECTIVE_ASSISTANT_H_
#include "kis_abstract_perspective_grid.h"
#include "kis_painting_assistant.h"
#include <QObject>
#include <QPolygonF>
#include <QLineF>
#include <QTransform>
class PerspectiveAssistant : public KisAbstractPerspectiveGrid, public KisPaintingAssistant
{
Q_OBJECT
public:
PerspectiveAssistant(QObject * parent = 0);
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
void endStroke() override;
QPointF buttonPosition() const override;
int numHandles() const override { return 4; }
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override;
bool contains(const QPointF& point) const override;
qreal distance(const QPointF& point) const override;
bool isAssistantComplete() const override;
protected:
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt, const QPointF& strokeBegin);
// creates the convex hull, returns false if it's not a quadrilateral
bool quad(QPolygonF& out) const;
// finds the transform from perspective coordinates (a unit square) to the document
bool getTransform(QPolygonF& polyOut, QTransform& transformOut) const;
+ explicit PerspectiveAssistant(const PerspectiveAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
// which direction to snap to (in transformed coordinates)
QLineF m_snapLine;
// cached information
mutable QTransform m_cachedTransform;
mutable QPolygonF m_cachedPolygon;
mutable QPointF m_cachedPoints[4];
mutable bool m_cacheValid;
};
class PerspectiveAssistantFactory : public KisPaintingAssistantFactory
{
public:
PerspectiveAssistantFactory();
~PerspectiveAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/Ruler.cc b/plugins/assistants/Assistants/Ruler.cc
index 696b6f3d45..d8513a0304 100644
--- a/plugins/assistants/Assistants/Ruler.cc
+++ b/plugins/assistants/Assistants/Ruler.cc
@@ -1,50 +1,51 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "Ruler.h"
Ruler::Ruler() : p1(QPointF(10, 10)), p2(QPointF(100, 190))
{
}
Ruler::~Ruler()
{
}
QPointF Ruler::project(const QPointF& pt)
{
double x1 = p1.x();
double y1 = p1.y();
double x2 = p2.x();
double y2 = p2.y();
double a1 = (y2 - y1) / (x2 - x1);
double b1 = y1 - x1 * a1;
double a2 = (x2 - x1) / (y1 - y2);
double b2 = pt.y() - a2 * pt.x();
double xm = (b2 - b1) / (a1 - a2);
return QPointF(xm, xm * a1 + b1);
}
const QPointF& Ruler::point1() const
{
return p1;
}
const QPointF& Ruler::point2() const
{
return p2;
}
diff --git a/plugins/assistants/Assistants/Ruler.h b/plugins/assistants/Assistants/Ruler.h
index c629a6a0af..8fc032e6bb 100644
--- a/plugins/assistants/Assistants/Ruler.h
+++ b/plugins/assistants/Assistants/Ruler.h
@@ -1,42 +1,43 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _RULER_H_
#define _RULER_H_
#include <QPointF>
class Ruler
{
public:
Ruler();
~Ruler();
QPointF project(const QPointF&);
const QPointF& point1() const;
void setPoint1(const QPointF& p) {
p1 = p;
}
const QPointF& point2() const;
void setPoint2(const QPointF& p) {
p2 = p;
}
private:
QPointF p1;
QPointF p2;
};
#endif
diff --git a/plugins/assistants/Assistants/RulerAssistant.cc b/plugins/assistants/Assistants/RulerAssistant.cc
index 88e47b5e0d..ab03b906cb 100644
--- a/plugins/assistants/Assistants/RulerAssistant.cc
+++ b/plugins/assistants/Assistants/RulerAssistant.cc
@@ -1,166 +1,177 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "RulerAssistant.h"
#include "kis_debug.h"
#include <klocalizedstring.h>
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <math.h>
RulerAssistant::RulerAssistant()
: KisPaintingAssistant("ruler", i18n("Ruler assistant"))
{
}
+KisPaintingAssistantSP RulerAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new RulerAssistant(*this, handleMap));
+}
+
+RulerAssistant::RulerAssistant(const RulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+{
+}
+
QPointF RulerAssistant::project(const QPointF& pt) const
{
Q_ASSERT(isAssistantComplete());
QPointF pt1 = *handles()[0];
QPointF pt2 = *handles()[1];
QPointF a = pt - pt1;
QPointF u = pt2 - pt1;
qreal u_norm = sqrt(u.x() * u.x() + u.y() * u.y());
if(u_norm == 0) return pt;
u /= u_norm;
double t = a.x() * u.x() + a.y() * u.y();
if(t < 0.0) return pt1;
if(t > u_norm) return pt2;
return t * u + pt1;
}
QPointF RulerAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/)
{
return project(pt);
}
inline double angle(const QPointF& p1, const QPointF& p2)
{
return atan2(p2.y() - p1.y(), p2.x() - p1.x());
}
inline double norm2(const QPointF& p)
{
return sqrt(p.x() * p.x() + p.y() * p.y());
}
void RulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
QPointF mousePos;
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<<canvas;
}
// don't draw if invalid
if (isAssistantComplete()) {
QTransform initialTransform = converter->documentToWidgetTransform();
// first we find the path that our point create.
QPointF p1 = *handles()[0];
QPointF p2 = *handles()[1];
gc.setTransform(initialTransform);
QPainterPath path;
path.moveTo(p1);
path.lineTo(p2);
//then we use this path to check the bounding rectangle//
if (isSnappingActive() && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){
drawPreview(gc, path);//and we draw the preview.
}
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
void RulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false || !isAssistantComplete()){
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
// Draw the line
QPointF p1 = *handles()[0];
QPointF p2 = *handles()[1];
gc.setTransform(initialTransform);
QPainterPath path;
path.moveTo(p1);
path.lineTo(p2);
drawPath(gc, path, isSnappingActive());
}
QPointF RulerAssistant::buttonPosition() const
{
return (*handles()[0] + *handles()[1]) * 0.5;
}
bool RulerAssistant::isAssistantComplete() const
{
return handles().size() >= 2;
}
RulerAssistantFactory::RulerAssistantFactory()
{
}
RulerAssistantFactory::~RulerAssistantFactory()
{
}
QString RulerAssistantFactory::id() const
{
return "ruler";
}
QString RulerAssistantFactory::name() const
{
return i18n("Ruler");
}
KisPaintingAssistant* RulerAssistantFactory::createPaintingAssistant() const
{
return new RulerAssistant;
}
diff --git a/plugins/assistants/Assistants/RulerAssistant.h b/plugins/assistants/Assistants/RulerAssistant.h
index 462156c0ce..5211c69b90 100644
--- a/plugins/assistants/Assistants/RulerAssistant.h
+++ b/plugins/assistants/Assistants/RulerAssistant.h
@@ -1,52 +1,57 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _RULER_ASSISTANT_H_
#define _RULER_ASSISTANT_H_
+#include <QMap>
+
#include "kis_painting_assistant.h"
class Ruler;
class RulerAssistant : public KisPaintingAssistant
{
public:
RulerAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
QPointF buttonPosition() const override;
int numHandles() const override { return 2; }
bool isAssistantComplete() const override;
protected:
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt) const;
+ explicit RulerAssistant(const RulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
};
class RulerAssistantFactory : public KisPaintingAssistantFactory
{
public:
RulerAssistantFactory();
~RulerAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/SplineAssistant.cc b/plugins/assistants/Assistants/SplineAssistant.cc
index 27644c3c11..7755f38b2e 100644
--- a/plugins/assistants/Assistants/SplineAssistant.cc
+++ b/plugins/assistants/Assistants/SplineAssistant.cc
@@ -1,227 +1,239 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "SplineAssistant.h"
#include <klocalizedstring.h>
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include "kis_debug.h"
#include <math.h>
#include <limits>
#include <algorithm>
SplineAssistant::SplineAssistant()
: KisPaintingAssistant("spline", i18n("Spline assistant"))
{
}
+SplineAssistant::SplineAssistant(const SplineAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+ , m_canvas(rhs.m_canvas)
+{
+}
+
+KisPaintingAssistantSP SplineAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new SplineAssistant(*this, handleMap));
+}
+
// parametric form of a cubic spline (B(t) = (1-t)^3 P0 + 3 (1-t)^2 t P1 + 3 (1-t) t^2 P2 + t^3 P3)
inline QPointF B(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3)
{
const qreal tp = 1 - t;
const qreal tp2 = tp * tp;
const qreal t2 = t * t;
return ( tp2 * tp) * P0 +
(3 * tp2 * t ) * P1 +
(3 * tp * t2) * P2 +
( t * t2) * P3;
}
// squared distance from a point on the spline to given point: we want to minimize this
inline qreal D(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3, const QPointF& p)
{
const qreal
tp = 1 - t,
tp2 = tp * tp,
t2 = t * t,
a = tp2 * tp,
b = 3 * tp2 * t,
c = 3 * tp * t2,
d = t * t2,
x_dist = a*P0.x() + b*P1.x() + c*P2.x() + d*P3.x() - p.x(),
y_dist = a*P0.y() + b*P1.y() + c*P2.y() + d*P3.y() - p.y();
return x_dist * x_dist + y_dist * y_dist;
}
QPointF SplineAssistant::project(const QPointF& pt) const
{
Q_ASSERT(isAssistantComplete());
// minimize d(t), but keep t in the same neighbourhood as before (unless starting a new stroke)
// (this is a rather inefficient method)
qreal min_t = std::numeric_limits<qreal>::max();
qreal d_min_t = std::numeric_limits<qreal>::max();
for (qreal t = 0; t <= 1; t += 1e-3) {
qreal d_t = D(t, *handles()[0], *handles()[2], *handles()[3], *handles()[1], pt);
if (d_t < d_min_t) {
d_min_t = d_t;
min_t = t;
}
}
return B(min_t, *handles()[0], *handles()[2], *handles()[3], *handles()[1]);
}
QPointF SplineAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/)
{
return project(pt);
}
void SplineAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
gc.save();
gc.resetTransform();
QPoint mousePos;
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
m_canvas = canvas;
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in spline, you may have passed arguments incorrectly:"<<canvas;
}
if (handles().size() > 1) {
QTransform initialTransform = converter->documentToWidgetTransform();
// first we find the path that our point create.
QPointF pts[4];
pts[0] = *handles()[0];
pts[1] = *handles()[1];
pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]);
pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]);
gc.setTransform(initialTransform);
// Draw the spline
QPainterPath path;
path.moveTo(pts[0]);
path.cubicTo(pts[2], pts[3], pts[1]);
//then we use this path to check the bounding rectangle//
if (isSnappingActive() && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){
drawPreview(gc, path);//and we draw the preview.
}
}
gc.restore();
// there is some odd rectangle that is getting rendered when there is only one point, so don't start rendering the line until after 2
// this issue only exists with this spline assistant...none of the others
if (handles().size() > 2) {
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
}
void SplineAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (assistantVisible == false || handles().size() < 2 ){
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
QPointF pts[4];
pts[0] = *handles()[0];
pts[1] = *handles()[1];
pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]);
pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]);
gc.setTransform(initialTransform);
{ // Draw bezier handles control lines only if we are editing the assistant
gc.save();
QColor assistantColor = effectiveAssistantColor();
QPen bezierlinePen(assistantColor);
bezierlinePen.setStyle(Qt::DotLine);
bezierlinePen.setWidth(1);
if (m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) {
if (!isSnappingActive()) {
QColor snappingColor = assistantColor;
snappingColor.setAlpha(snappingColor.alpha() * 0.2);
bezierlinePen.setColor(snappingColor);
}
gc.setPen(bezierlinePen);
gc.drawLine(pts[0], pts[2]);
if (isAssistantComplete()) {
gc.drawLine(pts[1], pts[3]);
}
gc.setPen(QColor(0, 0, 0, 125));
}
gc.restore();
}
// Draw the spline
QPainterPath path;
path.moveTo(pts[0]);
path.cubicTo(pts[2], pts[3], pts[1]);
drawPath(gc, path, isSnappingActive());
}
QPointF SplineAssistant::buttonPosition() const
{
return B(0.5, *handles()[0], *handles()[2], *handles()[3], *handles()[1]);
}
bool SplineAssistant::isAssistantComplete() const
{
return handles().size() >= 4; // specify 4 corners to make assistant complete
}
SplineAssistantFactory::SplineAssistantFactory()
{
}
SplineAssistantFactory::~SplineAssistantFactory()
{
}
QString SplineAssistantFactory::id() const
{
return "spline";
}
QString SplineAssistantFactory::name() const
{
return i18n("Spline");
}
KisPaintingAssistant* SplineAssistantFactory::createPaintingAssistant() const
{
return new SplineAssistant;
}
diff --git a/plugins/assistants/Assistants/SplineAssistant.h b/plugins/assistants/Assistants/SplineAssistant.h
index 168af58714..0084a3be80 100644
--- a/plugins/assistants/Assistants/SplineAssistant.h
+++ b/plugins/assistants/Assistants/SplineAssistant.h
@@ -1,55 +1,58 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _SPLINE_ASSISTANT_H_
#define _SPLINE_ASSISTANT_H_
#include "kis_painting_assistant.h"
#include <QObject>
class SplineAssistant : public KisPaintingAssistant
{
public:
SplineAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
QPointF buttonPosition() const override;
int numHandles() const override { return 4; }
bool isAssistantComplete() const override;
protected:
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt) const;
+ explicit SplineAssistant(const SplineAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
/// used for getting the decoration so the bezier handles aren't drawn while editing
KisCanvas2* m_canvas;
};
class SplineAssistantFactory : public KisPaintingAssistantFactory
{
public:
SplineAssistantFactory();
~SplineAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/VanishingPointAssistant.cc b/plugins/assistants/Assistants/VanishingPointAssistant.cc
index 1d0151262b..d3af88d14c 100644
--- a/plugins/assistants/Assistants/VanishingPointAssistant.cc
+++ b/plugins/assistants/Assistants/VanishingPointAssistant.cc
@@ -1,307 +1,318 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "VanishingPointAssistant.h"
#include "kis_debug.h"
#include <klocalizedstring.h>
#include <QPainter>
#include <QLinearGradient>
#include <QTransform>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <kis_algebra_2d.h>
#include <kis_dom_utils.h>
#include <math.h>
VanishingPointAssistant::VanishingPointAssistant()
: KisPaintingAssistant("vanishing point", i18n("Vanishing Point assistant"))
{
}
+VanishingPointAssistant::VanishingPointAssistant(const VanishingPointAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
+ : KisPaintingAssistant(rhs, handleMap)
+ , m_canvas(rhs.m_canvas)
+ , m_referenceLineDensity(rhs.m_referenceLineDensity)
+{
+}
+
+KisPaintingAssistantSP VanishingPointAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
+{
+ return KisPaintingAssistantSP(new VanishingPointAssistant(*this, handleMap));
+}
+
QPointF VanishingPointAssistant::project(const QPointF& pt, const QPointF& strokeBegin)
{
//Q_ASSERT(handles().size() == 1 || handles().size() == 5);
//code nicked from the perspective ruler.
qreal dx = pt.x() - strokeBegin.x();
qreal dy = pt.y() - strokeBegin.y();
if (dx * dx + dy * dy < 4.0) {
// allow some movement before snapping
return strokeBegin;
}
//dbgKrita<<strokeBegin<< ", " <<*handles()[0];
QLineF snapLine = QLineF(*handles()[0], strokeBegin);
dx = snapLine.dx();
dy = snapLine.dy();
const qreal dx2 = dx * dx;
const qreal dy2 = dy * dy;
const qreal invsqrlen = 1.0 / (dx2 + dy2);
QPointF r(dx2 * pt.x() + dy2 * snapLine.x1() + dx * dy * (pt.y() - snapLine.y1()),
dx2 * snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - snapLine.x1()));
r *= invsqrlen;
return r;
}
QPointF VanishingPointAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin)
{
return project(pt, strokeBegin);
}
void VanishingPointAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
// HACK ALERT: side handles aren't saved
// we need to just add a default position for now if we are loading a vanishing point
// we will need to look at how we load/save assistants in general to remedy this
if (sideHandles().isEmpty()) {
QPointF vpPoint = *handles()[0]; // main vanishing point
addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(-70,0)), HandleType::SIDE);
addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(-140,0)), HandleType::SIDE);
addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(70,0)), HandleType::SIDE);
addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(140,0)), HandleType::SIDE);
}
gc.save();
gc.resetTransform();
- QPointF delta(0,0);
QPointF mousePos(0,0);
- QPointF endPoint(0,0);//this is the final point that the line is being extended to, we seek it just outside the view port//
if (canvas){
//simplest, cheapest way to get the mouse-position//
mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
m_canvas = canvas;
}
else {
//...of course, you need to have access to a canvas-widget for that.//
mousePos = QCursor::pos();//this'll give an offset//
dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<<canvas;
}
// draw controls when we are not editing
if (canvas->paintingAssistantsDecoration()->isEditingAssistants() == false && isAssistantComplete()) {
if (isSnappingActive() && previewVisible == true) {
//don't draw if invalid.
QTransform initialTransform = converter->documentToWidgetTransform();
QPointF startPoint = initialTransform.map(*handles()[0]);
QLineF snapLine= QLineF(startPoint, mousePos);
QRect viewport= gc.viewport();
KisAlgebra2D::intersectLineRect(snapLine, viewport);
QRect bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint());
QPainterPath path;
if (bounds.contains(startPoint.toPoint())){
path.moveTo(startPoint);
path.lineTo(snapLine.p1());
}
else
{
path.moveTo(snapLine.p1());
path.lineTo(snapLine.p2());
}
drawPreview(gc, path);//and we draw the preview.
}
}
// editor specific controls display
if (canvas->paintingAssistantsDecoration()->isEditingAssistants()) {
// draws a circle around the vanishing point node while editing
QTransform initialTransform = converter->documentToWidgetTransform();
QPointF p0 = initialTransform.map(*handles()[0]); // main vanishing point
QPointF p1 = initialTransform.map(*sideHandles()[0]);
QPointF p2 = initialTransform.map(*sideHandles()[1]);
QPointF p3 = initialTransform.map(*sideHandles()[2]);
QPointF p4 = initialTransform.map(*sideHandles()[3]);
QRectF ellipse = QRectF(QPointF(p0.x() -15, p0.y() -15), QSizeF(30, 30));
QPainterPath pathCenter;
pathCenter.addEllipse(ellipse);
drawPath(gc, pathCenter, isSnappingActive());
QColor paintingColor = effectiveAssistantColor();
// draw the lines connecting the different nodes
QPen penStyle(paintingColor, 2.0, Qt::SolidLine);
if (!isSnappingActive()) {
QColor snappingColor = paintingColor;
snappingColor.setAlpha(snappingColor.alpha() * 0.2);
penStyle.setColor(snappingColor);
}
gc.save();
gc.setPen(penStyle);
gc.drawLine(p0, p1);
gc.drawLine(p0, p3);
gc.drawLine(p1, p2);
gc.drawLine(p3, p4);
gc.restore();
}
// draw references guide for vanishing points at specified density
// this is shown as part of the preview, so don't show if preview is off
if( canvas->paintingAssistantsDecoration()->outlineVisibility() && this->isSnappingActive() ) {
// cycle through degrees from 0 to 180. We are doing an infinite line, so we don't need to go 360
QTransform initialTransform = converter->documentToWidgetTransform();
QPointF p0 = initialTransform.map(*handles()[0]); // main vanishing point
for (int currentAngle=0; currentAngle <= 180; currentAngle = currentAngle + m_referenceLineDensity ) {
// determine the correct angle based on the iteration
float xPos = cos(currentAngle * M_PI / 180);
float yPos = sin(currentAngle * M_PI / 180);
QPointF unitAngle;
unitAngle.setX(p0.x() + xPos);
unitAngle.setY(p0.y() + yPos);
// find point
QLineF snapLine= QLineF(p0, unitAngle);
QRect viewport= gc.viewport();
KisAlgebra2D::intersectLineRect(snapLine, viewport);
// make a line from VP center to edge of canvas with that angle
QPainterPath path;
path.moveTo(snapLine.p1());
path.lineTo(snapLine.p2());
drawPreview(gc, path);//and we draw the preview.
}
}
gc.restore();
KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
}
void VanishingPointAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
{
if (!m_canvas || !isAssistantComplete()) {
return;
}
if (assistantVisible == false || m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) {
return;
}
QTransform initialTransform = converter->documentToWidgetTransform();
QPointF p0 = initialTransform.map(*handles()[0]);
// draws an "X"
QPainterPath path;
path.moveTo(QPointF(p0.x() - 10.0, p0.y() - 10.0));
path.lineTo(QPointF(p0.x() + 10.0, p0.y() + 10.0));
path.moveTo(QPointF(p0.x() - 10.0, p0.y() + 10.0));
path.lineTo(QPointF(p0.x() + 10.0, p0.y() - 10.0));
drawPath(gc, path, isSnappingActive());
}
QPointF VanishingPointAssistant::buttonPosition() const
{
return (*handles()[0]);
}
void VanishingPointAssistant::setReferenceLineDensity(float value)
{
// cannot have less than 1 degree value
if (value < 1.0) {
value = 1.0;
}
m_referenceLineDensity = value;
}
float VanishingPointAssistant::referenceLineDensity()
{
return m_referenceLineDensity;
}
bool VanishingPointAssistant::isAssistantComplete() const
{
return handles().size() > 0; // only need one point to be ready
}
void VanishingPointAssistant::saveCustomXml(QXmlStreamWriter* xml)
{
xml->writeStartElement("angleDensity");
xml->writeAttribute("value", KisDomUtils::toString( this->referenceLineDensity()));
xml->writeEndElement();
}
bool VanishingPointAssistant::loadCustomXml(QXmlStreamReader* xml)
{
if (xml && xml->name() == "angleDensity") {
this->setReferenceLineDensity((float)KisDomUtils::toDouble(xml->attributes().value("value").toString()));
}
return true;
}
VanishingPointAssistantFactory::VanishingPointAssistantFactory()
{
}
VanishingPointAssistantFactory::~VanishingPointAssistantFactory()
{
}
QString VanishingPointAssistantFactory::id() const
{
return "vanishing point";
}
QString VanishingPointAssistantFactory::name() const
{
return i18n("Vanishing Point");
}
KisPaintingAssistant* VanishingPointAssistantFactory::createPaintingAssistant() const
{
return new VanishingPointAssistant;
}
diff --git a/plugins/assistants/Assistants/VanishingPointAssistant.h b/plugins/assistants/Assistants/VanishingPointAssistant.h
index 9c3dab0a4d..3710e602bf 100644
--- a/plugins/assistants/Assistants/VanishingPointAssistant.h
+++ b/plugins/assistants/Assistants/VanishingPointAssistant.h
@@ -1,82 +1,85 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _VANISHINGPOINT_ASSISTANT_H_
#define _VANISHINGPOINT_ASSISTANT_H_
#include "kis_painting_assistant.h"
#include <QObject>
#include <QPolygonF>
#include <QLineF>
#include <QTransform>
/* Design:
*The idea behind the vanishing point ruler is that in a perspective deformed landscape, a set of parallel
*lines al share a single vanishing point.
*Therefore, a perspective can contain an theoretical infinite of vanishing points.
*It's a pity if we only allowed an artist to access 1, 2 or 3 of these at any given time, as other
*solutions for perspective tools do.
*Hence a vanishing point ruler.
*
*This ruler is relatively simple compared to the other perspective ruler:
*It has only one vanishing point that is required to draw.
*However, it does have it's own weaknesses in how to determine onto which of these infinite rulers to snap.
*Furthermore, it has four extra handles for adding a perspective ruler to a preexisting perspective.
*/
//class VanishingPoint;
class VanishingPointAssistant : public KisPaintingAssistant
{
public:
VanishingPointAssistant();
+ KisPaintingAssistantSP clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const override;
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override;
//virtual void endStroke();
QPointF buttonPosition() const override;
int numHandles() const override { return 1; }
float referenceLineDensity();
void setReferenceLineDensity(float value);
bool isAssistantComplete() const override;
void saveCustomXml(QXmlStreamWriter* xml) override;
bool loadCustomXml(QXmlStreamReader* xml) override;
protected:
void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override;
void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override;
private:
QPointF project(const QPointF& pt, const QPointF& strokeBegin);
+ explicit VanishingPointAssistant(const VanishingPointAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap);
KisCanvas2 *m_canvas;
float m_referenceLineDensity = 15.0;
};
class VanishingPointAssistantFactory : public KisPaintingAssistantFactory
{
public:
VanishingPointAssistantFactory();
~VanishingPointAssistantFactory() override;
QString id() const override;
QString name() const override;
KisPaintingAssistant* createPaintingAssistant() const override;
};
#endif
diff --git a/plugins/assistants/Assistants/assistant_tool.cc b/plugins/assistants/Assistants/assistant_tool.cc
index 430421d45b..69d87c8ffe 100644
--- a/plugins/assistants/Assistants/assistant_tool.cc
+++ b/plugins/assistants/Assistants/assistant_tool.cc
@@ -1,60 +1,61 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "assistant_tool.h"
#include "kis_assistant_tool.h"
#include <kpluginfactory.h>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include <kis_algebra_2d.h>
#include <KoToolRegistry.h>
#include "RulerAssistant.h"
#include "EllipseAssistant.h"
#include "SplineAssistant.h"
#include "PerspectiveAssistant.h"
#include "VanishingPointAssistant.h"
#include "InfiniteRulerAssistant.h"
#include "ParallelRulerAssistant.h"
#include "ConcentricEllipseAssistant.h"
#include "FisheyePointAssistant.h"
//#include "mesh_assistant.h"
K_PLUGIN_FACTORY_WITH_JSON(AssistantToolFactory, "kritaassistanttool.json", registerPlugin<AssistantToolPlugin>();)
AssistantToolPlugin::AssistantToolPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoToolRegistry::instance()->add(new KisAssistantToolFactory());
KisPaintingAssistantFactoryRegistry::instance()->add(new RulerAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new EllipseAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new SplineAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new PerspectiveAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new VanishingPointAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new InfiniteRulerAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new ParallelRulerAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new ConcentricEllipseAssistantFactory);
KisPaintingAssistantFactoryRegistry::instance()->add(new FisheyePointAssistantFactory);
// KisPaintingAssistantFactoryRegistry::instance()->add(new MeshAssistantFactory);
}
AssistantToolPlugin::~AssistantToolPlugin()
{
}
#include "assistant_tool.moc"
diff --git a/plugins/assistants/Assistants/assistant_tool.h b/plugins/assistants/Assistants/assistant_tool.h
index ef76fb0925..739f2a8ce2 100644
--- a/plugins/assistants/Assistants/assistant_tool.h
+++ b/plugins/assistants/Assistants/assistant_tool.h
@@ -1,34 +1,35 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _RULERASSISTANTTOOL_H_
#define _RULERASSISTANTTOOL_H_
#include <QObject>
#include <QVariant>
class AssistantToolPlugin : public QObject
{
Q_OBJECT
public:
AssistantToolPlugin(QObject *parent, const QVariantList &);
~AssistantToolPlugin() override;
};
#endif
diff --git a/plugins/assistants/Assistants/kis_assistant_tool.cc b/plugins/assistants/Assistants/kis_assistant_tool.cc
index 813df4a8d6..e371b6a87b 100644
--- a/plugins/assistants/Assistants/kis_assistant_tool.cc
+++ b/plugins/assistants/Assistants/kis_assistant_tool.cc
@@ -1,1041 +1,1062 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kis_assistant_tool.h>
#include <QPainter>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QStandardPaths>
#include <QFile>
#include <QLineF>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KColorButton>
#include "kis_dom_utils.h"
#include <QMessageBox>
#include <KoIcon.h>
#include <KoFileDialog.h>
#include <KoViewConverter.h>
#include <KoPointerEvent.h>
#include <KoSnapGuide.h>
#include <canvas/kis_canvas2.h>
#include <kis_canvas_resource_provider.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include <KisViewManager.h>
#include <kis_icon.h>
#include <kis_abstract_perspective_grid.h>
#include <kis_painting_assistants_decoration.h>
#include "kis_global.h"
#include "VanishingPointAssistant.h"
+#include "EditAssistantsCommand.h"
+#include <kis_undo_adapter.h>
#include <math.h>
KisAssistantTool::KisAssistantTool(KoCanvasBase * canvas)
: KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast<KisCanvas2*>(canvas)),
m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0)
{
Q_ASSERT(m_canvas);
setObjectName("tool_assistanttool");
}
KisAssistantTool::~KisAssistantTool()
{
}
void KisAssistantTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisTool::activate(toolActivation, shapes);
m_canvas->paintingAssistantsDecoration()->activateAssistantsEditor();
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
m_handleDrag = 0;
m_internalMode = MODE_CREATION;
m_assistantHelperYOffset = 10;
m_handleSize = 17;
m_canvas->paintingAssistantsDecoration()->setHandleSize(m_handleSize);
if (m_optionsWidget) {
m_canvas->paintingAssistantsDecoration()->deselectAssistant();
updateToolOptionsUI();
}
m_canvas->updateCanvas();
}
void KisAssistantTool::deactivate()
{
m_canvas->paintingAssistantsDecoration()->deactivateAssistantsEditor();
m_canvas->updateCanvas();
KisTool::deactivate();
}
void KisAssistantTool::beginPrimaryAction(KoPointerEvent *event)
{
setMode(KisTool::PAINT_MODE);
+ m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants());
+
bool newAssistantAllowed = true;
KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
if (m_newAssistant) {
m_internalMode = MODE_CREATION;
*m_newAssistant->handles().back() = canvasDecoration->snapToGuide(event, QPointF(), false);
if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) {
addAssistant();
} else {
m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
}
m_canvas->updateCanvas();
return;
}
m_handleDrag = 0;
double minDist = 81.0;
QPointF mousePos = m_canvas->viewConverter()->documentToView(canvasDecoration->snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point);
// syncs the assistant handles to the handles reference we store in this tool
// they can get out of sync with the way the actions and paintevents occur
// we probably need to stop storing a reference in m_handles and call the assistants directly
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
// find out which handle on all assistants is closest to the mouse position
// vanishing points have "side handles", so make sure to include that
{
QList<KisPaintingAssistantHandleSP> allAssistantHandles;
allAssistantHandles.append(assistant->handles());
allAssistantHandles.append(assistant->sideHandles());
Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) {
double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
if (dist < minDist) {
minDist = dist;
m_handleDrag = handle;
assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
}
}
}
if(m_handleDrag && assistant->id() == "perspective") {
// Look for the handle which was pressed
if (m_handleDrag == assistant->topLeft()) {
double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y());
m_internalMode = MODE_DRAGGING_NODE;
} else if (m_handleDrag == assistant->topRight()) {
double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_internalMode = MODE_DRAGGING_NODE;
m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
} else if (m_handleDrag == assistant->bottomLeft()) {
double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_internalMode = MODE_DRAGGING_NODE;
m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
} else if (m_handleDrag == assistant->bottomRight()) {
double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_internalMode = MODE_DRAGGING_NODE;
m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
} else if (m_handleDrag == assistant->leftMiddle()) {
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5,
(assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5);
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL );
m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL);
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
} else if (m_handleDrag == assistant->rightMiddle()) {
m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5,
(assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5);
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL);
m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL);
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
} else if (m_handleDrag == assistant->topMiddle()) {
m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5,
(assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5);
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL);
m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL);
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
} else if (m_handleDrag == assistant->bottomMiddle()) {
m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5,
(assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5);
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL);
m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL);
m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
}
m_snapIsRadial = false;
}
else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" ||
assistant->id() == "parallel ruler" ||
assistant->id() == "infinite ruler" ||
assistant->id() == "spline")){
if (m_handleDrag == assistant->handles()[0]) {
m_dragStart = *assistant->handles()[1];
} else if (m_handleDrag == assistant->handles()[1]) {
m_dragStart = *assistant->handles()[0];
} else if(assistant->handles().size()==4){
if (m_handleDrag == assistant->handles()[2]) {
m_dragStart = *assistant->handles()[0];
} else if (m_handleDrag == assistant->handles()[3]) {
m_dragStart = *assistant->handles()[1];
}
}
m_snapIsRadial = false;
} else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" ||
assistant->id() == "concentric ellipse" ||
assistant->id() == "fisheye-point")){
m_snapIsRadial = false;
if (m_handleDrag == assistant->handles()[0]) {
m_dragStart = *assistant->handles()[1];
} else if (m_handleDrag == assistant->handles()[1]) {
m_dragStart = *assistant->handles()[0];
} else if (m_handleDrag == assistant->handles()[2]) {
m_dragStart = assistant->buttonPosition();
m_radius = QLineF(m_dragStart, *assistant->handles()[0]);
m_snapIsRadial = true;
}
} else {
m_dragStart = assistant->buttonPosition();
m_snapIsRadial = false;
}
}
if (m_handleDrag) {
// TODO: Shift-press should now be handled using the alternate actions
// if (event->modifiers() & Qt::ShiftModifier) {
// m_handleDrag->uncache();
// m_handleDrag = m_handleDrag->split()[0];
// m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles();
// }
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
}
m_assistantDrag.clear();
Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
// This code contains the click event behavior.
QTransform initialTransform = m_canvas->coordinatesConverter()->documentToWidgetTransform();
QPointF actionsPosition = initialTransform.map(assistant->buttonPosition());
// for UI editor widget controls with move, show, and delete -- disregard document transforms like rotating and mirroring.
// otherwise the UI controls get awkward to use when they are at 45 degree angles or the order of controls gets flipped backwards
QPointF uiMousePosition = initialTransform.map( canvasDecoration->snapToGuide(event, QPointF(), false));
AssistantEditorData editorShared; // shared position data between assistant tool and decoration
QPointF iconMovePosition(actionsPosition + editorShared.moveIconPosition);
QPointF iconSnapPosition(actionsPosition + editorShared.snapIconPosition);
QPointF iconDeletePosition(actionsPosition + editorShared.deleteIconPosition);
QRectF deleteRect(iconDeletePosition, QSizeF(editorShared.deleteIconSize, editorShared.deleteIconSize));
QRectF visibleRect(iconSnapPosition, QSizeF(editorShared.snapIconSize, editorShared.snapIconSize));
QRectF moveRect(iconMovePosition, QSizeF(editorShared.moveIconSize, editorShared.moveIconSize));
if (moveRect.contains(uiMousePosition)) {
m_assistantDrag = assistant;
m_cursorStart = event->point;
m_currentAdjustment = QPointF();
m_internalMode = MODE_EDITING;
assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
return;
}
if (deleteRect.contains(uiMousePosition)) {
removeAssistant(assistant);
if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) {
m_internalMode = MODE_CREATION;
}
else
m_internalMode = MODE_EDITING;
m_canvas->updateCanvas();
return;
}
if (visibleRect.contains(uiMousePosition)) {
newAssistantAllowed = false;
assistant->setSnappingActive(!assistant->isSnappingActive()); // toggle
assistant->uncache();//this updates the chache of the assistant, very important.
assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
}
}
if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visibility//
QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString();
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant());
m_internalMode = MODE_CREATION;
m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
if (m_newAssistant->numHandles() <= 1) {
addAssistant();
} else {
m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
}
}
if (m_newAssistant) {
m_newAssistant->setAssistantGlobalColorCache(m_canvas->paintingAssistantsDecoration()->globalAssistantsColor());
}
m_canvas->updateCanvas();
}
void KisAssistantTool::continuePrimaryAction(KoPointerEvent *event)
{
KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
if (m_handleDrag) {
*m_handleDrag = event->point;
//ported from the gradient tool... we need to think about this more in the future.
if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) {
QLineF dragRadius = QLineF(m_dragStart, event->point);
dragRadius.setLength(m_radius.length());
*m_handleDrag = dragRadius.p2();
} else if (event->modifiers() == Qt::ShiftModifier ) {
QPointF move = snapToClosestAxis(event->point - m_dragStart);
*m_handleDrag = m_dragStart + move;
} else {
*m_handleDrag = canvasDecoration->snapToGuide(event, QPointF(), false);
}
m_handleDrag->uncache();
m_handleCombine = 0;
if (!(event->modifiers() & Qt::ShiftModifier)) {
double minDist = 49.0;
QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point);
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
if (handle == m_handleDrag)
continue;
double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
if (dist < minDist) {
minDist = dist;
m_handleCombine = handle;
}
}
}
m_canvas->updateCanvas();
} else if (m_assistantDrag) {
QPointF newAdjustment = canvasDecoration->snapToGuide(event, QPointF(), false) - m_cursorStart;
if (event->modifiers() == Qt::ShiftModifier ) {
newAdjustment = snapToClosestAxis(newAdjustment);
}
Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) {
*handle += (newAdjustment - m_currentAdjustment);
}
if (m_assistantDrag->id()== "vanishing point"){
Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) {
*handle += (newAdjustment - m_currentAdjustment);
}
}
m_currentAdjustment = newAdjustment;
m_canvas->updateCanvas();
} else {
event->ignore();
}
bool wasHiglightedNode = m_higlightedNode != 0;
QPointF mousep = m_canvas->viewConverter()->documentToView(event->point);
QList <KisPaintingAssistantSP> pAssistant= m_canvas->paintingAssistantsDecoration()->assistants();
Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) {
if(assistant->id() == "perspective") {
if ((m_higlightedNode = assistant->closestCornerHandleFromPoint(mousep))) {
if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) {
m_higlightedNode = 0;
} else {
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
break;
}
}
}
//this following bit sets the translations for the vanishing-point handles.
if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) {
//for inner handles, the outer handle gets translated.
if (m_handleDrag == assistant->sideHandles()[0]) {
QLineF perspectiveline = QLineF(*assistant->handles()[0],
*assistant->sideHandles()[0]);
qreal length = QLineF(*assistant->sideHandles()[0],
*assistant->sideHandles()[1]).length();
if (length < 2.0){
length = 2.0;
}
length += perspectiveline.length();
perspectiveline.setLength(length);
*assistant->sideHandles()[1] = perspectiveline.p2();
}
else if (m_handleDrag == assistant->sideHandles()[2]){
QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
if (length<2.0){
length=2.0;
}
length += perspectiveline.length();
perspectiveline.setLength(length);
*assistant->sideHandles()[3] = perspectiveline.p2();
} // for outer handles, only the vanishing point is translated, but only if there's an intersection.
else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){
QPointF vanishingpoint(0,0);
QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]);
QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]);
if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){
*assistant->handles()[0] = vanishingpoint;
}
}// and for the vanishing point itself, only the outer handles get translated.
else if (m_handleDrag == assistant->handles()[0]){
QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]);
QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length();
qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
if (length < 2.0) {
length = 2.0;
}
if (length2 < 2.0) {
length2=2.0;
}
length += perspectiveline.length();
length2 += perspectiveline2.length();
perspectiveline.setLength(length);
perspectiveline2.setLength(length2);
*assistant->sideHandles()[1] = perspectiveline.p2();
*assistant->sideHandles()[3] = perspectiveline2.p2();
}
}
}
if (wasHiglightedNode && !m_higlightedNode) {
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
}
}
void KisAssistantTool::endPrimaryAction(KoPointerEvent *event)
{
setMode(KisTool::HOVER_MODE);
- if (m_handleDrag) {
- if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) {
- m_handleCombine->mergeWith(m_handleDrag);
- m_handleCombine->uncache();
- m_handles = m_canvas->paintingAssistantsDecoration()->handles();
+ if (m_handleDrag || m_assistantDrag) {
+ if (m_handleDrag) {
+ if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) {
+ m_handleCombine->mergeWith(m_handleDrag);
+ m_handleCombine->uncache();
+ m_handles = m_canvas->paintingAssistantsDecoration()->handles();
+ }
+ m_handleDrag = m_handleCombine = 0;
+ } else {
+ m_assistantDrag.clear();
}
- m_handleDrag = m_handleCombine = 0;
-
- } else if (m_assistantDrag) {
- m_assistantDrag.clear();
+ dbgUI << "creating undo command...";
+ KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()));
+ m_canvas->viewManager()->undoAdapter()->addCommand(command);
+ dbgUI << "done";
} else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) {
addAssistant();
m_internalMode = MODE_CREATION;
}
else {
event->ignore();
}
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
}
void KisAssistantTool::addAssistant()
{
m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant);
- m_handles = m_canvas->paintingAssistantsDecoration()->handles();
- m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant);
- updateToolOptionsUI(); // vanishing point assistant will get an extra option
KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(m_newAssistant.data());
if (grid) {
m_canvas->viewManager()->canvasResourceProvider()->addPerspectiveGrid(grid);
}
+
+ QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
+ KUndo2Command *addAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(assistants), EditAssistantsCommand::ADD, assistants.indexOf(m_newAssistant));
+ m_canvas->viewManager()->undoAdapter()->addCommand(addAssistantCmd);
+
+ m_handles = m_canvas->paintingAssistantsDecoration()->handles();
+ m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant);
+ updateToolOptionsUI(); // vanishing point assistant will get an extra option
+
m_newAssistant.clear();
}
void KisAssistantTool::removeAssistant(KisPaintingAssistantSP assistant)
{
+ QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
+
KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data());
if (grid) {
m_canvas->viewManager()->canvasResourceProvider()->removePerspectiveGrid(grid);
}
m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant);
- m_handles = m_canvas->paintingAssistantsDecoration()->handles();
+ KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()), EditAssistantsCommand::REMOVE, assistants.indexOf(assistant));
+ m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd);
+
+ m_handles = m_canvas->paintingAssistantsDecoration()->handles();
m_canvas->paintingAssistantsDecoration()->deselectAssistant();
updateToolOptionsUI();
}
void KisAssistantTool::assistantSelected(KisPaintingAssistantSP assistant)
{
m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(assistant);
updateToolOptionsUI();
}
void KisAssistantTool::updateToolOptionsUI()
{
KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
bool hasActiveAssistant = m_selectedAssistant ? true : false;
if (m_selectedAssistant) {
bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point";
m_options.vanishingPointAngleSpinbox->setVisible(isVanishingPointAssistant);
if (isVanishingPointAssistant) {
QSharedPointer <VanishingPointAssistant> assis = qSharedPointerCast<VanishingPointAssistant>(m_selectedAssistant);
m_options.vanishingPointAngleSpinbox->setValue(assis->referenceLineDensity());
}
// load custom color settings from assistant (this happens when changing assistant
m_options.useCustomAssistantColor->setChecked(m_selectedAssistant->useCustomColor());
m_options.customAssistantColorButton->setColor(m_selectedAssistant->assistantCustomColor());
float opacity = (float)m_selectedAssistant->assistantCustomColor().alpha()/255.0 * 100.0 ;
m_options.customColorOpacitySlider->setValue(opacity);
} else {
m_options.vanishingPointAngleSpinbox->setVisible(false); //
}
// show/hide elements if an assistant is selected or not
m_options.assistantsGlobalOpacitySlider->setVisible(hasActiveAssistant);
m_options.assistantsColor->setVisible(hasActiveAssistant);
m_options.globalColorLabel->setVisible(hasActiveAssistant);
m_options.useCustomAssistantColor->setVisible(hasActiveAssistant);
// hide custom color options if use custom color is not selected
bool showCustomColorSettings = m_options.useCustomAssistantColor->isChecked() && hasActiveAssistant;
m_options.customColorOpacitySlider->setVisible(showCustomColorSettings);
m_options.customAssistantColorButton->setVisible(showCustomColorSettings);
// disable global color settings if we are using the custom color
m_options.assistantsGlobalOpacitySlider->setEnabled(!showCustomColorSettings);
m_options.assistantsColor->setEnabled(!showCustomColorSettings);
m_options.globalColorLabel->setEnabled(!showCustomColorSettings);
}
void KisAssistantTool::slotChangeVanishingPointAngle(double value)
{
if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
return;
}
// get the selected assistant and change the angle value
KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
if (m_selectedAssistant) {
bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point";
if (isVanishingPointAssistant) {
QSharedPointer <VanishingPointAssistant> assis = qSharedPointerCast<VanishingPointAssistant>(m_selectedAssistant);
assis->setReferenceLineDensity((float)value);
}
}
m_canvas->canvasWidget()->update();
}
void KisAssistantTool::mouseMoveEvent(KoPointerEvent *event)
{
if (m_newAssistant && m_internalMode == MODE_CREATION) {
*m_newAssistant->handles().back() = event->point;
} else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) {
QPointF translate = event->point - m_dragEnd;
m_dragEnd = event->point;
m_selectedNode1.data()->operator = (QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate);
m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate);
}
m_canvas->updateCanvas();
}
void KisAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter)
{
QRectF canvasSize = QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size()));
// show special display while a new assistant is in the process of being created
if (m_newAssistant) {
QColor assistantColor = m_newAssistant->effectiveAssistantColor();
assistantColor.setAlpha(80);
m_newAssistant->drawAssistant(_gc, canvasSize, m_canvas->coordinatesConverter(), false, m_canvas, true, false);
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) {
QPainterPath path;
path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)));
_gc.save();
_gc.setPen(Qt::NoPen);
_gc.setBrush(assistantColor);
_gc.drawPath(path);
_gc.restore();
}
}
Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
QColor assistantColor = assistant->effectiveAssistantColor();
assistantColor.setAlpha(80);
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
QRectF ellipse(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5),
QSizeF(m_handleSize, m_handleSize));
// render handles differently if it is the one being dragged.
if (handle == m_handleDrag || handle == m_handleCombine) {
QPen stroke(assistantColor, 4);
_gc.save();
_gc.setPen(stroke);
_gc.setBrush(Qt::NoBrush);
_gc.drawEllipse(ellipse);
_gc.restore();
}
}
}
}
void KisAssistantTool::removeAllAssistants()
{
m_canvas->viewManager()->canvasResourceProvider()->clearPerspectiveGrids();
m_canvas->paintingAssistantsDecoration()->removeAll();
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
m_canvas->updateCanvas();
m_canvas->paintingAssistantsDecoration()->deselectAssistant();
updateToolOptionsUI();
}
void KisAssistantTool::loadAssistants()
{
KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant");
dialog.setCaption(i18n("Select an Assistant"));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
QString filename = dialog.filename();
if (filename.isEmpty()) return;
if (!QFileInfo(filename).exists()) return;
QFile file(filename);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
QXmlStreamReader xml(data);
QMap<int, KisPaintingAssistantHandleSP> handleMap;
KisPaintingAssistantSP assistant;
bool errors = false;
while (!xml.atEnd()) {
switch (xml.readNext()) {
case QXmlStreamReader::StartElement:
if (xml.name() == "handle") {
if (assistant && !xml.attributes().value("ref").isEmpty()) {
KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt());
if (handle) {
assistant->addHandle(handle, HandleType::NORMAL);
} else {
errors = true;
}
} else {
QString strId = xml.attributes().value("id").toString(),
strX = xml.attributes().value("x").toString(),
strY = xml.attributes().value("y").toString();
if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
int id = strId.toInt();
double x = strX.toDouble(),
y = strY.toDouble();
if (!handleMap.contains(id)) {
handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
} else {
errors = true;
}
} else {
errors = true;
}
}
} else if (xml.name() == "assistant") {
const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString());
if (factory) {
if (assistant) {
errors = true;
assistant.clear();
}
assistant = toQShared(factory->createPaintingAssistant());
} else {
errors = true;
}
// load custom shared assistant properties
if ( xml.attributes().hasAttribute("useCustomColor")) {
QStringRef useCustomColor = xml.attributes().value("useCustomColor");
bool usingColor = false;
if (useCustomColor.toString() == "1") {
usingColor = true;
}
assistant->setUseCustomColor(usingColor);
}
if ( xml.attributes().hasAttribute("useCustomColor")) {
QStringRef customColor = xml.attributes().value("customColor");
assistant->setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) );
}
}
if (assistant) {
assistant->loadCustomXml(&xml);
}
break;
case QXmlStreamReader::EndElement:
if (xml.name() == "assistant") {
if (assistant) {
if (assistant->handles().size() == assistant->numHandles()) {
if (assistant->id() == "vanishing point"){
//ideally we'd save and load side-handles as well, but this is all I've got//
QPointF pos = *assistant->handles()[0];
assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)), HandleType::SIDE);
assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)), HandleType::SIDE);
assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)), HandleType::SIDE);
assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)), HandleType::SIDE);
}
m_canvas->paintingAssistantsDecoration()->addAssistant(assistant);
KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data());
if (grid) {
m_canvas->viewManager()->canvasResourceProvider()->addPerspectiveGrid(grid);
}
} else {
errors = true;
}
assistant.clear();
}
}
break;
default:
break;
}
}
if (assistant) {
errors = true;
assistant.clear();
}
if (xml.hasError()) {
QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString());
}
if (errors) {
QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded."));
}
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
m_canvas->updateCanvas();
}
void KisAssistantTool::saveAssistants()
{
if (m_handles.isEmpty()) return;
QByteArray data;
QXmlStreamWriter xml(&data);
xml.writeStartDocument();
xml.writeStartElement("paintingassistant");
xml.writeAttribute("color",
KisDomUtils::qColorToQString(
m_canvas->paintingAssistantsDecoration()->globalAssistantsColor())); // global color if no custom color used
xml.writeStartElement("handles");
QMap<KisPaintingAssistantHandleSP, int> handleMap;
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
int id = handleMap.size();
handleMap.insert(handle, id);
xml.writeStartElement("handle");
//xml.writeAttribute("type", handle->handleType());
xml.writeAttribute("id", QString::number(id));
xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeStartElement("assistants");
Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
xml.writeStartElement("assistant");
xml.writeAttribute("type", assistant->id());
xml.writeAttribute("useCustomColor", QString::number(assistant->useCustomColor()));
xml.writeAttribute("customColor", KisDomUtils::qColorToQString(assistant->assistantCustomColor()));
// custom assistant properties like angle density on vanishing point
assistant->saveCustomXml(&xml);
// handle information
xml.writeStartElement("handles");
Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) {
xml.writeStartElement("handle");
xml.writeAttribute("ref", QString::number(handleMap.value(handle)));
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeEndElement();
xml.writeEndDocument();
KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant");
dialog.setCaption(i18n("Save Assistant"));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QFile file(filename);
file.open(QIODevice::WriteOnly);
file.write(data);
}
QWidget *KisAssistantTool::createOptionWidget()
{
if (!m_optionsWidget) {
m_optionsWidget = new QWidget;
m_options.setupUi(m_optionsWidget);
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
m_options.loadAssistantButton->setIcon(KisIconUtils::loadIcon("document-open"));
m_options.saveAssistantButton->setIcon(KisIconUtils::loadIcon("document-save"));
m_options.deleteAllAssistantsButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
QList<KoID> assistants;
Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) {
QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name();
assistants << KoID(key, name);
}
std::sort(assistants.begin(), assistants.end(), KoID::compareNames);
Q_FOREACH(const KoID &id, assistants) {
m_options.availableAssistantsComboBox->addItem(id.name(), id.id());
}
connect(m_options.saveAssistantButton, SIGNAL(clicked()), SLOT(saveAssistants()));
connect(m_options.loadAssistantButton, SIGNAL(clicked()), SLOT(loadAssistants()));
connect(m_options.deleteAllAssistantsButton, SIGNAL(clicked()), SLOT(removeAllAssistants()));
connect(m_options.assistantsColor, SIGNAL(changed(QColor)), SLOT(slotGlobalAssistantsColorChanged(QColor)));
connect(m_options.assistantsGlobalOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotGlobalAssistantOpacityChanged()));
connect(m_options.vanishingPointAngleSpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeVanishingPointAngle(double)));
//ENTER_FUNCTION() << ppVar(m_canvas) << ppVar(m_canvas && m_canvas->paintingAssistantsDecoration());
// initialize UI elements with existing data if possible
if (m_canvas && m_canvas->paintingAssistantsDecoration()) {
const QColor color = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor();
QColor opaqueColor = color;
opaqueColor.setAlpha(255);
//ENTER_FUNCTION() << ppVar(opaqueColor);
m_options.assistantsColor->setColor(opaqueColor);
m_options.customAssistantColorButton->setColor(opaqueColor);
m_options.assistantsGlobalOpacitySlider->setValue(color.alphaF() * 100.0);
} else {
m_options.assistantsColor->setColor(QColor(176, 176, 176, 255)); // grey default for all assistants
m_options.assistantsGlobalOpacitySlider->setValue(100); // 100%
}
m_options.assistantsGlobalOpacitySlider->setPrefix(i18n("Opacity: "));
m_options.assistantsGlobalOpacitySlider->setSuffix(" %");
// custom color of selected assistant
m_options.customColorOpacitySlider->setValue(100); // 100%
m_options.customColorOpacitySlider->setPrefix(i18n("Opacity: "));
m_options.customColorOpacitySlider->setSuffix(" %");
connect(m_options.useCustomAssistantColor, SIGNAL(clicked(bool)), this, SLOT(slotUpdateCustomColor()));
connect(m_options.customAssistantColorButton, SIGNAL(changed(QColor)), this, SLOT(slotUpdateCustomColor()));
connect(m_options.customColorOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotCustomOpacityChanged()));
m_options.vanishingPointAngleSpinbox->setPrefix(i18n("Density: "));
m_options.vanishingPointAngleSpinbox->setSuffix(QChar(Qt::Key_degree));
m_options.vanishingPointAngleSpinbox->setRange(1.0, 180.0);
m_options.vanishingPointAngleSpinbox->setSingleStep(0.5);
m_options.vanishingPointAngleSpinbox->setVisible(false);
}
updateToolOptionsUI();
return m_optionsWidget;
}
void KisAssistantTool::slotGlobalAssistantsColorChanged(const QColor& setColor)
{
// color and alpha are stored separately, so we need to merge the values before sending it on
int oldAlpha = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor().alpha();
QColor newColor = setColor;
newColor.setAlpha(oldAlpha);
m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor);
m_canvas->paintingAssistantsDecoration()->uncache();
m_canvas->canvasWidget()->update();
}
void KisAssistantTool::slotGlobalAssistantOpacityChanged()
{
QColor newColor = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor();
qreal newOpacity = m_options.assistantsGlobalOpacitySlider->value() * 0.01 * 255.0;
newColor.setAlpha(int(newOpacity));
m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor);
m_canvas->paintingAssistantsDecoration()->uncache();
m_canvas->canvasWidget()->update();
}
void KisAssistantTool::slotUpdateCustomColor()
{
// get the selected assistant and change the angle value
KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
if (m_selectedAssistant) {
m_selectedAssistant->setUseCustomColor(m_options.useCustomAssistantColor->isChecked());
// changing color doesn't keep alpha, so update that before we send it on
QColor newColor = m_options.customAssistantColorButton->color();
newColor.setAlpha(m_selectedAssistant->assistantCustomColor().alpha());
m_selectedAssistant->setAssistantCustomColor(newColor);
m_selectedAssistant->uncache();
}
updateToolOptionsUI();
m_canvas->canvasWidget()->update();
}
void KisAssistantTool::slotCustomOpacityChanged()
{
KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
if (m_selectedAssistant) {
QColor newColor = m_selectedAssistant->assistantCustomColor();
qreal newOpacity = m_options.customColorOpacitySlider->value() * 0.01 * 255.0;
newColor.setAlpha(int(newOpacity));
m_selectedAssistant->setAssistantCustomColor(newColor);
m_selectedAssistant->uncache();
}
// this forces the canvas to refresh to see the changes immediately
m_canvas->paintingAssistantsDecoration()->uncache();
m_canvas->canvasWidget()->update();
}
diff --git a/plugins/assistants/Assistants/kis_assistant_tool.h b/plugins/assistants/Assistants/kis_assistant_tool.h
index d209782d04..71c4ca524c 100644
--- a/plugins/assistants/Assistants/kis_assistant_tool.h
+++ b/plugins/assistants/Assistants/kis_assistant_tool.h
@@ -1,175 +1,177 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_ASSISTANT_TOOL_H_
#define _KIS_ASSISTANT_TOOL_H_
#include <QPointer>
#include <KoToolFactoryBase.h>
#include <KoIcon.h>
#include <kis_tool.h>
#include "kis_painting_assistant.h"
#include <kis_icon.h>
#include <kis_canvas2.h>
#include "ui_AssistantsToolOptions.h"
/* The assistant tool allows artists to create special guides on the canvas
* to help them with things like perspective and parallel lines
* This tool has its own canvas decoration on it that only appears when the tool
* is active. This decoration allows people to edit assistant points as well as delete assistants
* Many of the operations here are forwarded on to another class (kis_painting_assistant_decoration)
* that stores the assistant information as well as the decoration information with lines
*
* Drawing in two separate classes creates an issue where the editor controls in this class
* are covered by the kis_painting_assistant_decoration class. In the future, we probably need to
* do all the drawing in one class so we have better control of what is in front
*/
class KisAssistantTool : public KisTool
{
Q_OBJECT
enum PerspectiveAssistantEditionMode {
MODE_CREATION, // This is the mode when there is not yet a perspective grid
MODE_EDITING, // This is the mode when the grid has been created, and we are waiting for the user to click on a control box
MODE_DRAGGING_NODE, // In this mode one node is translated
MODE_DRAGGING_TRANSLATING_TWONODES // This mode is used when creating a new sub perspective grid
};
public:
KisAssistantTool(KoCanvasBase * canvas);
~KisAssistantTool() override;
virtual quint32 priority() {
return 3;
}
/* this is a very big function that has to figure out if we are adding a new assistant,
* or editing an existing one when we click on the canvas. There is also a lot of logic
* in here that is specific to certain assistants and how they should be handled.
* The editor widget is not a UI file, so the move, delete, preview areas have manual
* hitbox regions specified to know if a click is doing any of those actions.
*/
void beginPrimaryAction(KoPointerEvent *event) override;
void continuePrimaryAction(KoPointerEvent *event) override;
void endPrimaryAction(KoPointerEvent *event) override;
void mouseMoveEvent(KoPointerEvent *event) override;
QWidget *createOptionWidget() override;
private:
// adds and removes assistant.
// this is event is forwarded to the kis_painting_decoration class
// perspective grids seem to be managed in two places with these calls
void addAssistant();
void removeAssistant(KisPaintingAssistantSP assistant);
void assistantSelected(KisPaintingAssistantSP assistant);
public Q_SLOTS:
void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) override;
void deactivate() override;
void slotChangeVanishingPointAngle(double value);
private Q_SLOTS:
void removeAllAssistants();
void saveAssistants();
void loadAssistants();
void updateToolOptionsUI();
/// send the color and opacity information from the UI to the kis_painting_decoration
/// which manages the assistants
void slotGlobalAssistantsColorChanged(const QColor&);
void slotGlobalAssistantOpacityChanged();
void slotUpdateCustomColor();
void slotCustomOpacityChanged();
protected:
/// Draws the editor widget controls with move, activate, and delete
/// This also creates a lot of assistant specific stuff for vanishing points and perspective grids
/// Whatever is painted here will be underneath the content painted in the kis_painting_assistant_decoration
/// The kis_painting_assistant_decoration paints the final assistant, so this is more of just editor controls
void paint(QPainter& gc, const KoViewConverter &converter) override;
protected:
/// this class manipulates the kis_painting_assistant_decorations a lot, so this class is a helper
/// to get a reference to it and call "updateCanvas" which refreshes the display
QPointer<KisCanvas2> m_canvas;
/// the handles are retrieved from the kis_painting_decoration originally
/// They are used here to generate and manipulate editor handles with the tool's primary action
QList<KisPaintingAssistantHandleSP> m_handles;
QList<KisPaintingAssistantHandleSP> m_sideHandles;
KisPaintingAssistantHandleSP m_handleDrag;
KisPaintingAssistantHandleSP m_handleCombine;
KisPaintingAssistantSP m_assistantDrag;
/// Used while a new assistant is being created. Most assistants need multiple points to exist
/// so this helps manage the visual state while this creation process is going on
KisPaintingAssistantSP m_newAssistant;
QPointF m_cursorStart;
QPointF m_currentAdjustment;
Ui::AssistantsToolOptions m_options;
QWidget* m_optionsWidget;
QPointF m_dragStart;
QLineF m_radius;
bool m_snapIsRadial;
QPointF m_dragEnd;
int m_handleSize; // how large the editor handles will appear
private:
void drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& _gc);
PerspectiveAssistantEditionMode m_internalMode;
KisPaintingAssistantHandleSP m_selectedNode1, m_selectedNode2, m_higlightedNode;
int m_assistantHelperYOffset; // used by the assistant editor icons for placement on the canvas.
+ QList<KisPaintingAssistantSP> m_origAssistantList;
};
class KisAssistantToolFactory : public KoToolFactoryBase
{
public:
KisAssistantToolFactory()
: KoToolFactoryBase("KisAssistantTool") {
setToolTip(i18n("Assistant Tool"));
setSection(TOOL_TYPE_VIEW);
setIconName(koIconNameCStr("krita_tool_assistant"));
setPriority(0);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
}
~KisAssistantToolFactory() override {}
KoToolBase * createTool(KoCanvasBase * canvas) override {
return new KisAssistantTool(canvas);
}
};
#endif
diff --git a/plugins/dockers/CMakeLists.txt b/plugins/dockers/CMakeLists.txt
index 6952b67468..add898a795 100644
--- a/plugins/dockers/CMakeLists.txt
+++ b/plugins/dockers/CMakeLists.txt
@@ -1,35 +1,36 @@
add_subdirectory(layerdocker)
if(HAVE_OPENEXR)
add_subdirectory(smallcolorselector)
endif()
add_subdirectory(specificcolorselector)
add_subdirectory(digitalmixer)
add_subdirectory(advancedcolorselector)
add_subdirectory(presetdocker)
add_subdirectory(historydocker)
add_subdirectory(channeldocker)
add_subdirectory(artisticcolorselector)
add_subdirectory(tasksetdocker)
add_subdirectory(compositiondocker)
add_subdirectory(patterndocker)
add_subdirectory(griddocker)
add_subdirectory(arrangedocker)
if(HAVE_OCIO)
add_subdirectory(lut)
endif()
add_subdirectory(overview)
add_subdirectory(palettedocker)
add_subdirectory(animation)
add_subdirectory(presethistory)
add_subdirectory(svgcollectiondocker)
add_subdirectory(histogram)
add_subdirectory(gamutmask)
if(NOT APPLE AND HAVE_QT_QUICK)
add_subdirectory(touchdocker)
option(ENABLE_CPU_THROTTLE "Build the CPU Throttle Docker" OFF)
if (ENABLE_CPU_THROTTLE)
add_subdirectory(throttle)
endif()
endif()
add_subdirectory(logdocker)
+add_subdirectory(snapshotdocker)
\ No newline at end of file
diff --git a/plugins/dockers/advancedcolorselector/colorselectorng.cpp b/plugins/dockers/advancedcolorselector/colorselectorng.cpp
index 3cec60c825..b4ce2ade0f 100644
--- a/plugins/dockers/advancedcolorselector/colorselectorng.cpp
+++ b/plugins/dockers/advancedcolorselector/colorselectorng.cpp
@@ -1,86 +1,87 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 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 "colorselectorng.h"
#include <kpluginfactory.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "kis_color_selector_ng_dock.h"
#include "kis_color_selector_settings.h"
#include "kis_preference_set_registry.h"
K_PLUGIN_FACTORY_WITH_JSON(ColorSelectorNgPluginFactory, "krita_colorselectorng.json", registerPlugin<ColorSelectorNgPlugin>();)
class ColorSelectorNgDockFactory : public KoDockFactoryBase
{
public:
ColorSelectorNgDockFactory() {
}
QString id() const override {
return QString("ColorSelectorNg");
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const {
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override {
KisColorSelectorNgDock * dockWidget = new KisColorSelectorNgDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
ColorSelectorNgPlugin::ColorSelectorNgPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new ColorSelectorNgDockFactory());
KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
KisColorSelectorSettingsFactory* settingsFactory = new KisColorSelectorSettingsFactory();
//load and save preferences
//if something in kritarc is missing, then the default from this load function will be used and saved back to kconfig.
//this way, cfg.readEntry() in any part won't be able to set its own default
KisPreferenceSet* settings = settingsFactory->createPreferenceSet();
Q_ASSERT(settings);
settings->loadPreferences();
settings->savePreferences();
delete settings;
preferenceSetRegistry->add("KisColorSelectorSettingsFactory", settingsFactory);
}
ColorSelectorNgPlugin::~ColorSelectorNgPlugin()
{
}
#include "colorselectorng.moc"
diff --git a/plugins/dockers/advancedcolorselector/colorselectorng.h b/plugins/dockers/advancedcolorselector/colorselectorng.h
index 08da99cf15..32a93bbb8b 100644
--- a/plugins/dockers/advancedcolorselector/colorselectorng.h
+++ b/plugins/dockers/advancedcolorselector/colorselectorng.h
@@ -1,37 +1,38 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 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 _COLORSELECTORNG_H_
#define _COLORSELECTORNG_H_
#include <QObject>
#include <QVariant>
/**
* Template of view plugin
*/
class ColorSelectorNgPlugin : public QObject
{
Q_OBJECT
public:
ColorSelectorNgPlugin(QObject *parent, const QVariantList &);
~ColorSelectorNgPlugin() override;
};
#endif
diff --git a/plugins/dockers/advancedcolorselector/kis_color_history.cpp b/plugins/dockers/advancedcolorselector/kis_color_history.cpp
index f4312b67a6..0dfdc0a3b0 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_history.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_history.cpp
@@ -1,80 +1,81 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_history.h"
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "KisView.h"
#include "kis_canvas_resource_provider.h"
#include <KoCompositeOpRegistry.h>
#include <QColor>
KisColorHistory::KisColorHistory(QWidget *parent)
: KisColorPatches("lastUsedColors", parent)
, m_resourceProvider(0)
{
}
void KisColorHistory::unsetCanvas()
{
KisColorPatches::unsetCanvas();
m_resourceProvider = 0;
}
void KisColorHistory::setCanvas(KisCanvas2 *canvas)
{
if (!canvas) return;
KisColorPatches::setCanvas(canvas);
if (m_resourceProvider) {
m_resourceProvider->disconnect(this);
}
m_resourceProvider = canvas->imageView()->resourceProvider();
connect(canvas->imageView()->resourceProvider(), SIGNAL(sigFGColorUsed(KoColor)),
this, SLOT(addColorToHistory(KoColor)), Qt::UniqueConnection);
}
KisColorSelectorBase* KisColorHistory::createPopup() const
{
KisColorHistory* ret = new KisColorHistory();
ret->setCanvas(m_canvas);
ret->setColors(colors());
ret->m_colorHistory=m_colorHistory;
return ret;
}
void KisColorHistory::addColorToHistory(const KoColor& color)
{
// don't add color in erase mode. See https://bugs.kde.org/show_bug.cgi?id=298940
if (m_resourceProvider && m_resourceProvider->currentCompositeOp() == COMPOSITE_ERASE) return;
m_colorHistory.removeAll(color);
m_colorHistory.prepend(color);
//the history holds 200 colors, but not all are displayed
if (m_colorHistory.size()>200) {
m_colorHistory.removeLast();
}
setColors(m_colorHistory);
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_history.h b/plugins/dockers/advancedcolorselector/kis_color_history.h
index f6055fe0ab..630e3cf2f8 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_history.h
+++ b/plugins/dockers/advancedcolorselector/kis_color_history.h
@@ -1,45 +1,46 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_COLOR_HISTORY_H
#define KIS_COLOR_HISTORY_H
#include "kis_color_patches.h"
class KisCanvasResourceProvider;
class KisColorHistory : public KisColorPatches
{
Q_OBJECT
public:
explicit KisColorHistory(QWidget *parent = 0);
void setCanvas(KisCanvas2 *canvas) override;
void unsetCanvas() override;
protected:
KisColorSelectorBase* createPopup() const override;
public Q_SLOTS:
void addColorToHistory(const KoColor& color);
private:
QList<KoColor> m_colorHistory;
KisCanvasResourceProvider *m_resourceProvider; // to disconnect...
};
#endif // KIS_COLOR_HISTORY_H
diff --git a/plugins/dockers/advancedcolorselector/kis_color_patches.cpp b/plugins/dockers/advancedcolorselector/kis_color_patches.cpp
index f54557494c..260a70872d 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_patches.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_patches.cpp
@@ -1,352 +1,353 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_patches.h"
#include <QApplication>
#include <QPainter>
#include <QWheelEvent>
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include "kis_canvas2.h"
#include "KoCanvasResourceProvider.h"
#include "kis_display_color_converter.h"
KisColorPatches::KisColorPatches(QString configPrefix, QWidget *parent) :
KisColorSelectorBase(parent), m_allowColorListChangeGuard(true), m_scrollValue(0), m_configPrefix(configPrefix)
{
resize(1, 1);
updateSettings();
}
void KisColorPatches::setColors(QList<KoColor>colors)
{
if (m_allowColorListChangeGuard) {
m_colors = colors;
m_allowColorListChangeGuard=false;
KisColorPatches* parent = dynamic_cast<KisColorPatches*>(m_parent);
if (parent) parent->setColors(colors);
KisColorPatches* popup = dynamic_cast<KisColorPatches*>(m_popup);
if (popup) popup->setColors(colors);
m_allowColorListChangeGuard=true;
update();
}
}
void KisColorPatches::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
if(m_allowScrolling) {
if(m_direction == Vertical)
painter.translate(0, m_scrollValue);
else
painter.translate(m_scrollValue, 0);
}
int widgetWidth = width();
int numPatchesInARow = qMax(widgetWidth/m_patchWidth, 1);
int widgetHeight = height();
int numPatchesInACol = qMax(widgetHeight/m_patchHeight, 1);
for(int i = m_buttonList.size(); i < qMin(fieldCount(), m_colors.size() + m_buttonList.size()); i++) {
int row;
int col;
if(m_direction == Vertical) {
row = i /numPatchesInARow;
col = i % numPatchesInARow;
}
else {
row= i % numPatchesInACol;
col = i / numPatchesInACol;
}
QColor qcolor = converter()->toQColor(m_colors.at(i - m_buttonList.size()));
painter.fillRect(col*m_patchWidth,
row*m_patchHeight,
m_patchWidth,
m_patchHeight,
qcolor);
}
QWidget::paintEvent(e);
}
void KisColorPatches::wheelEvent(QWheelEvent* event)
{
m_scrollValue+=event->delta()/2;
if(m_direction == Vertical) {
if(m_scrollValue < -1*(heightOfAllPatches()-height()))
m_scrollValue = -1*(heightOfAllPatches()-height());
}
else {
if(m_scrollValue < -1*(widthOfAllPatches()-width()))
m_scrollValue = -1*(widthOfAllPatches()-width());
}
if(m_scrollValue>0) m_scrollValue=0;
update();
}
void KisColorPatches::resizeEvent(QResizeEvent* event)
{
if(size()==QSize(1, 1))
return;
QWheelEvent dummyWheelEvent(QPoint(), 0, Qt::NoButton, Qt::NoModifier);
wheelEvent(&dummyWheelEvent);
if(parentWidget()==0) {
// this instance is a popup
setMinimumWidth(m_patchWidth*(m_patchCount/4));
setMaximumWidth(minimumWidth());
}
if (m_allowScrolling == false && event->oldSize() != event->size()) {
if(m_direction == Horizontal) {
setMaximumHeight(heightForWidth(width()));
setMinimumHeight(heightForWidth(width()));
}
else {
setMaximumWidth(widthForHeight(height()));
setMinimumWidth(widthForHeight(height()));
}
}
}
void KisColorPatches::mouseReleaseEvent(QMouseEvent* event)
{
KisColorSelectorBase::mouseReleaseEvent(event);
event->setAccepted(false);
KisColorSelectorBase::mouseReleaseEvent(event);
if (event->isAccepted() || !rect().contains(event->pos()))
return;
if (!m_canvas) return;
KoColor color;
if(colorAt(event->pos(), &color)) {
if (event->button()==Qt::LeftButton)
m_canvas->resourceManager()->setForegroundColor(color);
else if (event->button()==Qt::RightButton)
m_canvas->resourceManager()->setBackgroundColor(color);
}
}
void KisColorPatches::mousePressEvent(QMouseEvent *event)
{
KoColor koColor;
if(!colorAt(event->pos(), &koColor))
return;
KisColorSelectorBase::mousePressEvent(event);
if(event->isAccepted())
return;
updateColorPreview(koColor);
if (event->button() == Qt::LeftButton)
m_dragStartPos = event->pos();
}
void KisColorPatches::mouseMoveEvent(QMouseEvent *event)
{
event->ignore();
KisColorSelectorBase::mouseMoveEvent(event);
if(event->isAccepted())
return;
if (!(event->buttons() & Qt::LeftButton))
return;
if ((event->pos() - m_dragStartPos).manhattanLength()
< QApplication::startDragDistance())
return;
KoColor koColor;
if(!colorAt(m_dragStartPos, &koColor))
return;
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
QColor color = converter()->toQColor(koColor);
mimeData->setColorData(color);
mimeData->setText(color.name());
drag->setMimeData(mimeData);
drag->exec(Qt::CopyAction);
event->accept();
}
int KisColorPatches::patchCount() const
{
return m_patchCount;
}
bool KisColorPatches::colorAt(const QPoint &pos, KoColor *result) const
{
if(!rect().contains(pos))
return false;
int scrollX = m_direction==Horizontal?m_scrollValue:0;
int scrollY = m_direction==Vertical?m_scrollValue:0;
int column = (pos.x()-scrollX)/m_patchWidth;
int row = (pos.y()-scrollY)/m_patchHeight;
int patchNr;
if(m_direction == Vertical) {
int patchesInARow = width()/m_patchWidth;
patchNr=row*patchesInARow+column;
}
else {
// Vertical
int patchesInAColumn = height()/m_patchHeight;
patchNr=column*patchesInAColumn+row;
}
patchNr-=m_buttonList.size();
if(patchNr>=0 && patchNr<m_colors.size()) {
(*result)=m_colors.at(patchNr);
return true;
}
else return false;
}
void KisColorPatches::setAdditionalButtons(QList<QWidget*> buttonList)
{
for(int i=0; i<buttonList.size(); i++) {
buttonList.at(i)->setParent(this);
}
m_buttonList = buttonList;
}
void KisColorPatches::updateSettings()
{
KisColorSelectorBase::updateSettings();
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
if(cfg.readEntry(m_configPrefix+"Alignment", false))
m_direction=Vertical;
else
m_direction=Horizontal;
m_allowScrolling=cfg.readEntry(m_configPrefix+"Scrolling", true);
m_numCols=cfg.readEntry(m_configPrefix+"NumCols", 1);
m_numRows=cfg.readEntry(m_configPrefix+"NumRows", 1);
m_patchCount=cfg.readEntry(m_configPrefix+"Count", 15);
m_patchWidth=cfg.readEntry(m_configPrefix+"Width", 20);
m_patchHeight=cfg.readEntry(m_configPrefix+"Height", 20);
if(m_patchHeight == 0) {
m_patchHeight = 1;
}
if(parentWidget()==0) {
// this instance is a popup
m_allowScrolling = false;
m_direction = Horizontal;
m_patchWidth*=2;
m_patchHeight*=2;
}
for(int i=0; i<m_buttonList.size(); i++) {
m_buttonList.at(i)->setGeometry(0, i*m_patchHeight, m_patchWidth, m_patchHeight);
}
setMaximumWidth(QWIDGETSIZE_MAX);
setMinimumWidth(1);
setMaximumHeight(QWIDGETSIZE_MAX);
setMinimumHeight(1);
if(m_allowScrolling && m_direction == Horizontal) {
setMaximumHeight(m_numRows*m_patchHeight);
setMinimumHeight(m_numRows*m_patchHeight);
}
if(m_allowScrolling && m_direction == Vertical) {
setMaximumWidth(m_numCols*m_patchWidth);
setMinimumWidth(m_numCols*m_patchWidth);
}
if(m_allowScrolling == false) {
m_scrollValue = 0;
}
QResizeEvent dummy(size(), QSize(-1,-1));
resizeEvent(&dummy);
setPopupBehaviour(false, false);
update();
}
int KisColorPatches::widthOfAllPatches()
{
return (fieldCount()/m_numRows)*m_patchWidth;
}
int KisColorPatches::heightOfAllPatches()
{
return (fieldCount()/m_numCols)*m_patchHeight;
}
int KisColorPatches::heightForWidth(int width) const
{
int numPatchesInARow = width / m_patchWidth;
int numRows = qMax((fieldCount() - 1), 1) / qMax(numPatchesInARow + 1, 1);
return qMax(numRows * m_patchHeight, m_patchHeight);
}
int KisColorPatches::widthForHeight(int height) const
{
if (height == 0) {
return 0;
}
if (m_patchHeight == 0) {
return 0;
}
int numPatchesInACol = height / m_patchHeight;
int numCols = (fieldCount() - 1) / (numPatchesInACol + 1);
return qMax(numCols*m_patchWidth, m_patchWidth);
}
int KisColorPatches::fieldCount() const
{
return m_patchCount+m_buttonList.size();
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_patches.h b/plugins/dockers/advancedcolorselector/kis_color_patches.h
index 1905d4c102..e9bc5c1c0e 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_patches.h
+++ b/plugins/dockers/advancedcolorselector/kis_color_patches.h
@@ -1,89 +1,90 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_COLOR_PATCHES_H
#define KIS_COLOR_PATCHES_H
#include "kis_color_selector_base.h"
#include "KoColor.h"
class KoColor;
class KisColorPatches : public KisColorSelectorBase
{
Q_OBJECT
public:
explicit KisColorPatches(QString configPrefix, QWidget *parent = 0);
enum Direction { Horizontal, Vertical };
public Q_SLOTS:
void updateSettings() override;
protected:
void setColors(QList<KoColor> colors);
QList<KoColor> colors() const {return m_colors;}
void paintEvent(QPaintEvent *) override;
void wheelEvent(QWheelEvent *) override;
void resizeEvent(QResizeEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
int patchCount() const;
bool colorAt(const QPoint &, KoColor *result) const;
public:
/// set buttons, that should be drawn additionally to the patches
/// this class takes ownership of them and will delete them
/// they will be resized to the patchsize
void setAdditionalButtons(QList<QWidget*> buttonList);
private:
int m_patchWidth;
int m_patchHeight;
int m_patchCount;
QList<KoColor> m_colors;
bool m_allowColorListChangeGuard;
int m_scrollValue;
Direction m_direction;
bool m_allowScrolling;
int m_numCols;
int m_numRows;
QList<QWidget*> m_buttonList;
/// returns width of the patchfield, if there are only m_numRows allowed
int widthOfAllPatches();
/// returns height of the patchfield, if there are only m_numCols allowed
int heightOfAllPatches();
/// returns height, that is needed to display all patches with the given width
int heightForWidth(int width) const override;
/// returns width, that is needed to display all patches with the given height
int widthForHeight(int height) const;
/// returns count of colors and buttons
int fieldCount() const;
QString m_configPrefix;
QPoint m_dragStartPos;
};
#endif
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector.cpp
index c90e1a5052..83c6a29aba 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector.cpp
@@ -1,395 +1,396 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_selector.h"
#include <cmath>
#include <QHBoxLayout>
#include <QColor>
#include <QPainter>
#include <QMouseEvent>
#include <QTimer>
#include <QPushButton>
#include <QApplication>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <kis_debug.h>
#include <KoCanvasResourceProvider.h>
#include <kis_icon.h>
#include "kis_color_selector_ring.h"
#include "kis_color_selector_triangle.h"
#include "kis_color_selector_simple.h"
#include "kis_color_selector_wheel.h"
#include "kis_color_selector_container.h"
#include "kis_canvas2.h"
#include "kis_signal_compressor.h"
#include "KisViewManager.h"
KisColorSelector::KisColorSelector(KisColorSelectorConfiguration conf, QWidget* parent)
: KisColorSelectorBase(parent),
m_ring(0),
m_triangle(0),
m_slider(0),
m_square(0),
m_wheel(0),
m_mainComponent(0),
m_subComponent(0),
m_grabbingComponent(0),
m_blipDisplay(true)
{
init();
updateSettings();
setConfiguration(conf);
}
KisColorSelector::KisColorSelector(QWidget* parent)
: KisColorSelectorBase(parent),
m_ring(0),
m_triangle(0),
m_slider(0),
m_square(0),
m_wheel(0),
m_button(0),
m_mainComponent(0),
m_subComponent(0),
m_grabbingComponent(0),
m_blipDisplay(true)
{
init();
updateSettings();
}
KisColorSelectorBase* KisColorSelector::createPopup() const
{
KisColorSelectorBase* popup = new KisColorSelector(0);
popup->setColor(m_lastRealColor);
return popup;
}
void KisColorSelector::setConfiguration(KisColorSelectorConfiguration conf)
{
m_configuration = conf;
if(m_mainComponent!=0) {
Q_ASSERT(m_subComponent!=0);
m_mainComponent->setGeometry(0, 0, 0, 0);
m_subComponent->setGeometry(0, 0, 0, 0);
m_mainComponent->disconnect();
m_subComponent->disconnect();
}
switch (m_configuration.mainType) {
case KisColorSelectorConfiguration::Square:
m_mainComponent=m_square;
break;
case KisColorSelectorConfiguration::Wheel:
m_mainComponent=m_wheel;
break;
case KisColorSelectorConfiguration::Triangle:
m_mainComponent=m_triangle;
break;
default:
Q_ASSERT(false);
}
switch (m_configuration.subType) {
case KisColorSelectorConfiguration::Ring:
m_subComponent=m_ring;
break;
case KisColorSelectorConfiguration::Slider:
m_subComponent=m_slider;
break;
default:
Q_ASSERT(false);
}
connect(m_mainComponent, SIGNAL(paramChanged(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)),
m_subComponent, SLOT(setParam(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)), Qt::UniqueConnection);
connect(m_subComponent, SIGNAL(paramChanged(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)),
m_mainComponent, SLOT(setParam(qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal,qreal)), Qt::UniqueConnection);
connect(m_mainComponent, SIGNAL(update()), m_signalCompressor, SLOT(start()), Qt::UniqueConnection);
connect(m_subComponent, SIGNAL(update()), m_signalCompressor, SLOT(start()), Qt::UniqueConnection);
m_mainComponent->setConfiguration(m_configuration.mainTypeParameter, m_configuration.mainType);
m_subComponent->setConfiguration(m_configuration.subTypeParameter, m_configuration.subType);
QResizeEvent event(QSize(width(), height()), QSize());
resizeEvent(&event);
}
KisColorSelectorConfiguration KisColorSelector::configuration() const
{
return m_configuration;
}
void KisColorSelector::updateSettings()
{
KisColorSelectorBase::updateSettings();
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
setConfiguration(KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())));
}
void KisColorSelector::slotGamutMaskSet(KoGamutMask *gamutMask)
{
m_mainComponent->setGamutMask(gamutMask);
m_subComponent->setGamutMask(gamutMask);
slotGamutMaskToggle(true);
}
void KisColorSelector::slotGamutMaskUnset()
{
m_mainComponent->unsetGamutMask();
m_subComponent->unsetGamutMask();
slotGamutMaskToggle(false);
}
void KisColorSelector::slotGamutMaskPreviewUpdate()
{
m_mainComponent->updateGamutMaskPreview();
m_subComponent->updateGamutMaskPreview();
}
void KisColorSelector::slotGamutMaskToggle(bool state)
{
m_mainComponent->toggleGamutMask(state);
m_subComponent->toggleGamutMask(state);
}
void KisColorSelector::updateIcons() {
if (m_button) {
m_button->setIcon(KisIconUtils::loadIcon("configure"));
}
}
void KisColorSelector::hasAtLeastOneDocument(bool value)
{
m_hasAtLeastOneDocumentOpen = value;
}
void KisColorSelector::reset()
{
if (m_mainComponent) {
m_mainComponent->setDirty();
}
if (m_subComponent) {
m_subComponent->setDirty();
}
KisColorSelectorBase::reset();
}
void KisColorSelector::paintEvent(QPaintEvent* e)
{
Q_UNUSED(e);
QPainter p(this);
p.fillRect(0,0,width(), height(), QColor(128,128,128));
p.setRenderHint(QPainter::Antialiasing);
// this variable name isn't entirely accurate to what always happens. see definition in header file to understand it better
if (!m_hasAtLeastOneDocumentOpen) {
p.setOpacity(0.2);
}
m_mainComponent->paintEvent(&p);
m_subComponent->paintEvent(&p);
p.setOpacity(1.0);
}
inline int iconSize(qreal width, qreal height) {
qreal radius = qMin(width, height)/2.;
qreal xm = width/2.;
qreal ym = height/2.;
if(xm>=2*ym || ym>=2*xm)
return qBound<qreal>(5., radius, 32.);
qreal a=-2;
qreal b=2.*(xm+ym);
qreal c=radius*radius-xm*xm-ym*ym;
return qBound<qreal>(5., ((-b+sqrt(b*b-4*a*c))/(2*a)), 32.);
}
void KisColorSelector::resizeEvent(QResizeEvent* e) {
if (m_configuration.subType == KisColorSelectorConfiguration::Ring) {
m_ring->setGeometry(0,0,width(), height());
if (displaySettingsButton()) {
int size = iconSize(width(), height());
m_button->setGeometry(0, 0, size, size);
}
if (m_configuration.mainType == KisColorSelectorConfiguration::Triangle) {
m_triangle->setGeometry(width()/2-m_ring->innerRadius(),
height()/2-m_ring->innerRadius(),
m_ring->innerRadius()*2,
m_ring->innerRadius()*2);
}
else {
int size = m_ring->innerRadius()*2/sqrt(2.);
m_square->setGeometry(width()/2-size/2,
height()/2-size/2,
size,
size);
}
}
else {
// type wheel and square
if (m_configuration.mainType == KisColorSelectorConfiguration::Wheel) {
if(displaySettingsButton()) {
int size = iconSize(width(), height()*0.9);
m_button->setGeometry(0, height()*0.1, size, size);
}
m_mainComponent->setGeometry(0, height()*0.1, width(), height()*0.9);
m_subComponent->setGeometry( 0, 0, width(), height()*0.1);
}
else {
int buttonSize = 0;
if(displaySettingsButton()) {
buttonSize = qBound(20, int(0.1*height()), 32);
m_button->setGeometry(0, 0, buttonSize, buttonSize);
}
if(height()>width()) {
int selectorHeight=height()-buttonSize;
m_mainComponent->setGeometry(0, buttonSize+selectorHeight*0.1, width(), selectorHeight*0.9);
m_subComponent->setGeometry( 0, buttonSize, width(), selectorHeight*0.1);
}
else {
int selectorWidth=width()-buttonSize;
m_mainComponent->setGeometry(buttonSize, height()*0.1, selectorWidth, height()*0.9);
m_subComponent->setGeometry( buttonSize, 0, selectorWidth, height()*0.1);
}
}
}
// reset the correct color after resizing the widget
setColor(m_lastRealColor);
KisColorSelectorBase::resizeEvent(e);
}
void KisColorSelector::mousePressEvent(QMouseEvent* e)
{
e->setAccepted(false);
KisColorSelectorBase::mousePressEvent(e);
if(!e->isAccepted()) {
if(m_mainComponent->wantsGrab(e->x(), e->y()))
m_grabbingComponent=m_mainComponent;
else if(m_subComponent->wantsGrab(e->x(), e->y()))
m_grabbingComponent=m_subComponent;
mouseEvent(e);
updatePreviousColorPreview();
e->accept();
}
}
void KisColorSelector::mouseMoveEvent(QMouseEvent* e)
{
KisColorSelectorBase::mouseMoveEvent(e);
mouseEvent(e);
e->accept();
}
void KisColorSelector::mouseReleaseEvent(QMouseEvent* e)
{
e->setAccepted(false);
KisColorSelectorBase::mouseReleaseEvent(e);
if(!e->isAccepted() &&
!(m_lastRealColor == m_currentRealColor)) {
m_lastRealColor = m_currentRealColor;
m_lastColorRole = Acs::buttonToRole(e->button());
updateColor(m_lastRealColor, m_lastColorRole, false);
updateBaseColorPreview(m_currentRealColor);
e->accept();
}
m_grabbingComponent=0;
}
bool KisColorSelector::displaySettingsButton()
{
return dynamic_cast<KisColorSelectorContainer*>(parent());
}
void KisColorSelector::setColor(const KoColor &color)
{
m_mainComponent->setColor(color);
m_subComponent->setColor(color);
m_lastRealColor = color;
m_signalCompressor->start();
}
void KisColorSelector::mouseEvent(QMouseEvent *e)
{
if (m_grabbingComponent && (e->buttons() & Qt::LeftButton || e->buttons() & Qt::RightButton)) {
m_grabbingComponent->mouseEvent(e->x(), e->y());
KoColor color = m_mainComponent->currentColor();
Acs::ColorRole role = Acs::buttonsToRole(e->button(), e->buttons());
m_currentRealColor = color;
requestUpdateColorAndPreview(color, role);
}
}
void KisColorSelector::init()
{
setAcceptDrops(true);
m_lastColorRole = Acs::Foreground;
m_ring = new KisColorSelectorRing(this);
m_triangle = new KisColorSelectorTriangle(this);
m_slider = new KisColorSelectorSimple(this);
m_square = new KisColorSelectorSimple(this);
m_wheel = new KisColorSelectorWheel(this);
if(displaySettingsButton()) {
m_button = new QPushButton(this);
m_button->setIcon(KisIconUtils::loadIcon("configure"));
m_button->setFlat(true);
connect(m_button, SIGNAL(clicked()), SIGNAL(settingsButtonClicked()));
}
// a tablet can send many more signals, than a mouse
// this causes many repaints, if updating after every signal.
m_signalCompressor = new KisSignalCompressor(20, KisSignalCompressor::FIRST_INACTIVE, this);
connect(m_signalCompressor, SIGNAL(timeout()), SLOT(update()));
setMinimumSize(40, 40);
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector.h b/plugins/dockers/advancedcolorselector/kis_color_selector.h
index ef0f68ec7b..5d73ba5489 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector.h
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector.h
@@ -1,104 +1,105 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_COLOR_SELECTOR_H
#define KIS_COLOR_SELECTOR_H
#include "kis_color_selector_base.h"
#include <KisColorSelectorConfiguration.h>
class KisColorSelectorRing;
class KisColorSelectorComponent;
class KisColorSelectorSimple;
class KisColorSelectorWheel;
class QPushButton;
class KisSignalCompressor;
class KisColorSelector : public KisColorSelectorBase
{
Q_OBJECT
public:
KisColorSelector(KisColorSelectorConfiguration conf, QWidget* parent = 0);
KisColorSelector(QWidget* parent=0);
KisColorSelectorBase* createPopup() const override;
void setConfiguration(KisColorSelectorConfiguration conf);
KisColorSelectorConfiguration configuration() const;
void setColor(const KoColor &color) override;
/// update icons when a theme update happens
void updateIcons();
void hasAtLeastOneDocument(bool value);
public Q_SLOTS:
void reset() override;
void updateSettings() override;
void slotGamutMaskSet(KoGamutMask* gamutMask);
void slotGamutMaskUnset();
void slotGamutMaskPreviewUpdate();
void slotGamutMaskToggle(bool state);
Q_SIGNALS:
void settingsButtonClicked();
protected:
void paintEvent(QPaintEvent*) override;
void resizeEvent(QResizeEvent*) override;
void mousePressEvent(QMouseEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
bool displaySettingsButton();
private:
void mouseEvent(QMouseEvent* e);
void init();
KisColorSelectorRing* m_ring;
KisColorSelectorComponent* m_triangle;
KisColorSelectorSimple* m_slider;
KisColorSelectorSimple* m_square;
KisColorSelectorWheel* m_wheel;
QPushButton* m_button;
KisColorSelectorComponent* m_mainComponent;
KisColorSelectorComponent* m_subComponent;
KisColorSelectorComponent* m_grabbingComponent;
KisSignalCompressor *m_signalCompressor;
KisColorSelectorConfiguration m_configuration;
KoColor m_lastRealColor;
KoColor m_currentRealColor;
bool m_blipDisplay;
Acs::ColorRole m_lastColorRole;
/// if Krita starts with a reference to this component that is attached to a canvas, it will call setCanvas()
/// that check will be what ultimately decides whether this component will look enabled or disabled
/// This color selector is sometimes not attached to the canvas, so we shouldn't disable it in that situation
/// One instance of that is when you select the color wheel type from the settings.
bool m_hasAtLeastOneDocumentOpen = true;
public:
void setDisplayBlip(bool disp) {m_blipDisplay = disp;}
bool displayBlip() const {return m_blipDisplay;}
};
#endif // KIS_COLSELNG_COLOR_SELECTOR_H
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp
index 22e4b1399c..621772de50 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_component.cpp
@@ -1,253 +1,253 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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
*/
#include "kis_color_selector_component.h"
#include "kis_color_selector_base.h"
#include "KoColorSpace.h"
#include <QPainter>
#include <QMouseEvent>
#include <resources/KoGamutMask.h>
KisColorSelectorComponent::KisColorSelectorComponent(KisColorSelector* parent) :
QObject(parent),
m_hue(0),
m_hsvSaturation(1),
m_value(1),
m_hslSaturation(1),
m_lightness(0.5),
m_hsiSaturation(1),
m_intensity(0.333),
m_hsySaturation(1),
m_luma(0.299),
m_parent(parent),
m_gamutMaskOn(false),
m_currentGamutMask(nullptr),
m_maskPreviewActive(true),
m_lastX(0),
m_lastY(0),
m_x(0),
m_y(0),
m_width(0),
m_height(0),
m_dirty(true),
m_lastColorSpace(0)
{
Q_ASSERT(parent);
}
void KisColorSelectorComponent::setGeometry(int x, int y, int width, int height)
{
m_x=x;
m_y=y;
m_width=width;
m_height=height;
m_dirty=true;
}
void KisColorSelectorComponent::paintEvent(QPainter* painter)
{
painter->save();
painter->translate(m_x, m_y);
paint(painter);
painter->restore();
m_dirty=false;
m_lastColorSpace=colorSpace();
}
void KisColorSelectorComponent::mouseEvent(int x, int y)
{
int newX=qBound(0, (x-m_x), width());
int newY=qBound(0, (y-m_y), height());
- if (allowsColorSelectionAtPoint(QPoint(x, y))) {
+ if (allowsColorSelectionAtPoint(QPoint(newX, newY))) {
m_lastSelectedColor = selectColor(newX, newY);
m_lastX=newX;
m_lastY=newY;
}
}
const KoColorSpace* KisColorSelectorComponent::colorSpace() const
{
const KoColorSpace* cs = m_parent->colorSpace();
Q_ASSERT(cs);
return cs;
}
void KisColorSelectorComponent::setDirty()
{
m_dirty = true;
setColor(m_lastSelectedColor);
}
void KisColorSelectorComponent::setGamutMask(KoGamutMask *gamutMask)
{
m_currentGamutMask = gamutMask;
m_gamutMaskOn = true;
}
void KisColorSelectorComponent::unsetGamutMask()
{
m_gamutMaskOn = false;
m_currentGamutMask = nullptr;
}
void KisColorSelectorComponent::updateGamutMaskPreview()
{
setDirty();
update();
}
void KisColorSelectorComponent::toggleGamutMask(bool state)
{
m_gamutMaskOn = state;
setDirty();
update();
}
bool KisColorSelectorComponent::isDirty() const
{
return m_dirty || m_lastColorSpace!=colorSpace();
}
bool KisColorSelectorComponent::containsPointInComponentCoords(int x, int y) const
{
if(x>=0 && y>=0 && x<=width() && y<=height())
return true;
else
return false;
}
bool KisColorSelectorComponent::allowsColorSelectionAtPoint(const QPoint & /*pt*/) const
{
return true;
}
KoColor KisColorSelectorComponent::currentColor()
{
return selectColor(m_lastX, m_lastY);
}
void KisColorSelectorComponent::setParam(qreal hue, qreal hsvSaturation, qreal value, qreal hslSaturation, qreal lightness, qreal hsiSaturation, qreal intensity, qreal hsySaturation, qreal luma)
{
if(qFuzzyCompare(m_hue, hue) &&
qFuzzyCompare(m_hsvSaturation, hsvSaturation) &&
qFuzzyCompare(m_value, value) &&
qFuzzyCompare(m_hslSaturation, hslSaturation) &&
qFuzzyCompare(m_lightness, lightness) &&
qFuzzyCompare(m_hsiSaturation, hsiSaturation) &&
qFuzzyCompare(m_intensity, intensity) &&
qFuzzyCompare(m_hsySaturation, hsySaturation) &&
qFuzzyCompare(m_luma, luma))
return;
if(hue>=0. && hue<=1.)
m_hue=hue;
if(hsvSaturation>=0. && hsvSaturation<=1.) {
m_hsvSaturation=hsvSaturation;
m_hslSaturation=-1;
m_hsiSaturation=-1;
m_hsySaturation=-1;
}
if(value>=0. && value<=1.) {
m_value=value;
m_intensity=-1;
m_luma=-1;
m_lightness=-1;
}
if(hslSaturation>=0. && hslSaturation<=1.) {
m_hslSaturation=hslSaturation;
m_hsvSaturation=-1;
m_hsiSaturation=-1;
m_hsySaturation=-1;
}
if(lightness>=0. && lightness<=1.) {
m_lightness=lightness;
m_value=-1;
m_luma=-1;
m_intensity=-1;
}
if(hsiSaturation>=0. && hsiSaturation<=1.) {
m_hsiSaturation=hsiSaturation;
m_hsvSaturation=-1;
m_hslSaturation=-1;
m_hsySaturation=-1;
}
if(intensity>=0. && intensity<=1.) {
m_intensity=intensity;
m_value=-1;
m_luma=-1;
m_lightness=-1;
}
if(hsySaturation>=0. && hsySaturation<=1.) {
m_hsySaturation=hsySaturation;
m_hsvSaturation=-1;
m_hsiSaturation=-1;
m_hslSaturation=-1;
}
if(luma>=0. && luma<=1.) {
m_intensity=-1;
m_value=-1;
m_luma=luma;
m_lightness=-1;
}
m_dirty=true;
emit update();
}
int KisColorSelectorComponent::width() const
{
return m_width;
}
int KisColorSelectorComponent::height() const
{
return m_height;
}
void KisColorSelectorComponent::setConfiguration(Parameter param, Type type)
{
m_parameter = param;
m_type = type;
}
void KisColorSelectorComponent::setColor(const KoColor &color)
{
m_lastSelectedColor = color;
}
void KisColorSelectorComponent::setLastMousePosition(int x, int y)
{
// prevent movement due to rounding errors
if (abs((int)m_lastX - x) > 1 || abs((int)m_lastY - y) > 1) {
m_lastX = x;
m_lastY = y;
}
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.cpp
index 93968eea1b..2080940e35 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.cpp
@@ -1,49 +1,50 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_selector_ng_dock.h"
#include <klocalizedstring.h>
#include "kis_canvas2.h"
#include "kis_color_selector_ng_docker_widget.h"
KisColorSelectorNgDock::KisColorSelectorNgDock()
: QDockWidget()
{
m_colorSelectorNgWidget = new KisColorSelectorNgDockerWidget(this);
setWidget(m_colorSelectorNgWidget);
m_colorSelectorNgWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setWindowTitle(i18n("Advanced Color Selector"));
}
void KisColorSelectorNgDock::setCanvas(KoCanvasBase * canvas)
{
setEnabled(canvas != 0);
m_colorSelectorNgWidget->setCanvas(dynamic_cast<KisCanvas2*>(canvas));
}
void KisColorSelectorNgDock::unsetCanvas()
{
setEnabled(false);
m_colorSelectorNgWidget->unsetCanvas();
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.h b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.h
index 7df03d9a10..21e8f69d82 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.h
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_dock.h
@@ -1,41 +1,42 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_COLOR_SELECTOR_NG_DOCKER_H
#define KIS_COLOR_SELECTOR_NG_DOCKER_H
#include <QDockWidget>
#include <KoCanvasObserverBase.h>
class KisColorSelectorNgDockerWidget;
class KisColorSelectorNgDock : public QDockWidget, public KoCanvasObserverBase
{
Q_OBJECT
public:
KisColorSelectorNgDock();
QString observerName() override { return "KisColorSelectorNgDock"; }
/// reimplemented from KoCanvasObserverBase
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
private:
KisColorSelectorNgDockerWidget *m_colorSelectorNgWidget;
};
#endif
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp
index 511701c76a..494747a628 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp
@@ -1,285 +1,286 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_selector_ng_docker_widget.h"
#include "ui_wdg_color_selector_settings.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QToolButton>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <kis_icon_utils.h>
#include <QAction>
#include <kactioncollection.h>
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_node_manager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_color_space_selector.h"
#include "kis_preference_set_registry.h"
#include "kis_node.h"
#include "kis_paint_device.h"
#include "kis_color_history.h"
#include "kis_common_colors.h"
#include "kis_color_selector_settings.h"
#include "kis_color_selector_container.h"
#include "kis_action_registry.h"
KisColorSelectorNgDockerWidget::KisColorSelectorNgDockerWidget(QWidget *parent) :
QWidget(parent),
m_colorHistoryAction(0),
m_commonColorsAction(0),
m_widgetLayout(0),
m_mainLayout(0),
m_horizontalPatchesContainer(0),
m_sidebarLayout(0),
m_verticalColorPatchesLayout(0),
m_horizontalColorPatchesLayout(0),
m_fallbackSettingsButton(new QToolButton(this)),
m_canvas(0)
{
setAutoFillBackground(true);
m_colorSelectorContainer = new KisColorSelectorContainer(this);
m_colorHistoryWidget = new KisColorHistory(this);
m_commonColorsWidget = new KisCommonColors(this);
//default settings
//remember to also change the default in the ui file
//shade selector
// fallback settings button when the color selector is disabled
m_fallbackSettingsButton->setIcon(KisIconUtils::loadIcon("configure"));
m_fallbackSettingsButton->setIconSize(QSize(22,22));
m_fallbackSettingsButton->setAutoRaise(true);
m_fallbackSettingsButton->hide();
//layout
m_widgetLayout = new QHBoxLayout();
m_widgetLayout->setSpacing(0);
m_widgetLayout->setMargin(0);
m_mainLayout = new QVBoxLayout();
m_mainLayout->setSpacing(0);
m_mainLayout->setMargin(0);
m_horizontalPatchesContainer = new QHBoxLayout();
m_horizontalPatchesContainer->setSpacing(0);
m_horizontalPatchesContainer->setMargin(0);
m_sidebarLayout = new QVBoxLayout();
m_sidebarLayout->setSpacing(0);
m_sidebarLayout->setMargin(0);
m_verticalColorPatchesLayout = new QHBoxLayout();
m_verticalColorPatchesLayout->setSpacing(0);
m_verticalColorPatchesLayout->setMargin(0);
m_horizontalColorPatchesLayout = new QVBoxLayout();
m_horizontalColorPatchesLayout->setSpacing(0);
m_horizontalColorPatchesLayout->setMargin(0);
m_horizontalPatchesContainer->addLayout(m_horizontalColorPatchesLayout);
m_mainLayout->addWidget(m_colorSelectorContainer);
m_mainLayout->addLayout(m_horizontalPatchesContainer);
m_sidebarLayout->addLayout(m_verticalColorPatchesLayout);
m_widgetLayout->addLayout(m_mainLayout);
m_widgetLayout->addLayout(m_sidebarLayout);
setLayout(m_widgetLayout);
updateLayout();
connect(m_colorSelectorContainer, SIGNAL(openSettings()), this, SLOT(openSettings()));
//emit settingsChanged() if the settings are changed in krita preferences
KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
KisColorSelectorSettingsFactory* factory =
dynamic_cast<KisColorSelectorSettingsFactory*>(preferenceSetRegistry->get("KisColorSelectorSettingsFactory"));
Q_ASSERT(factory);
connect(&(factory->repeater), SIGNAL(settingsUpdated()), this, SIGNAL(settingsChanged()), Qt::UniqueConnection);
connect(this, SIGNAL(settingsChanged()), this, SLOT(updateLayout()), Qt::UniqueConnection);
connect(this, SIGNAL(settingsChanged()), m_commonColorsWidget, SLOT(updateSettings()), Qt::UniqueConnection);
connect(this, SIGNAL(settingsChanged()), m_colorHistoryWidget, SLOT(updateSettings()), Qt::UniqueConnection);
connect(this, SIGNAL(settingsChanged()), m_colorSelectorContainer, SIGNAL(settingsChanged()), Qt::UniqueConnection);
connect(this, SIGNAL(settingsChanged()), this, SLOT(update()), Qt::UniqueConnection);
emit settingsChanged();
m_colorHistoryAction = KisActionRegistry::instance()->makeQAction("show_color_history", this);
connect(m_colorHistoryAction, SIGNAL(triggered()), m_colorHistoryWidget, SLOT(showPopup()), Qt::UniqueConnection);
m_commonColorsAction = KisActionRegistry::instance()->makeQAction("show_common_colors", this);
connect(m_commonColorsAction, SIGNAL(triggered()), m_commonColorsWidget, SLOT(showPopup()), Qt::UniqueConnection);
connect(m_fallbackSettingsButton, SIGNAL(clicked()), this, SLOT(openSettings()));
}
void KisColorSelectorNgDockerWidget::unsetCanvas()
{
m_canvas = 0;
m_commonColorsWidget->unsetCanvas();
m_colorHistoryWidget->unsetCanvas();
m_colorSelectorContainer->unsetCanvas();
}
void KisColorSelectorNgDockerWidget::setCanvas(KisCanvas2 *canvas)
{
if (m_canvas) {
m_canvas->disconnect(this);
KActionCollection *ac = m_canvas->viewManager()->actionCollection();
ac->takeAction(ac->action("show_color_history"));
ac->takeAction(ac->action("show_common_colors"));
}
m_canvas = canvas;
m_commonColorsWidget->setCanvas(canvas);
m_colorHistoryWidget->setCanvas(canvas);
m_colorSelectorContainer->setCanvas(canvas);
if (m_canvas && m_canvas->viewManager()) {
if (m_canvas->viewManager()->nodeManager()) {
connect(m_canvas->viewManager()->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), SLOT(reactOnLayerChange()), Qt::UniqueConnection);
}
KActionCollection* actionCollection = canvas->viewManager()->actionCollection();
actionCollection->addAction("show_color_history", m_colorHistoryAction);
actionCollection->addAction("show_common_colors", m_commonColorsAction);
connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), m_colorSelectorContainer, SLOT(slotUpdateIcons()), Qt::UniqueConnection);
}
reactOnLayerChange();
}
void KisColorSelectorNgDockerWidget::openSettings()
{
if (!m_canvas) return;
KisColorSelectorSettingsDialog settings;
if(settings.exec()==QDialog::Accepted) {
emit settingsChanged();
}
}
void KisColorSelectorNgDockerWidget::updateLayout()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
bool showColorSelector = (bool) cfg.readEntry("showColorSelector", true);
//color patches
bool m_lastColorsShow = cfg.readEntry("lastUsedColorsShow", true);
KisColorPatches::Direction m_lastColorsDirection;
if(cfg.readEntry("lastUsedColorsAlignment", false))
m_lastColorsDirection=KisColorPatches::Vertical;
else
m_lastColorsDirection=KisColorPatches::Horizontal;
bool m_commonColorsShow = cfg.readEntry("commonColorsShow", true);
KisColorPatches::Direction m_commonColorsDirection;
if(cfg.readEntry("commonColorsAlignment", false))
m_commonColorsDirection=KisColorPatches::Vertical;
else
m_commonColorsDirection=KisColorPatches::Horizontal;
m_verticalColorPatchesLayout->removeWidget(m_colorHistoryWidget);
m_verticalColorPatchesLayout->removeWidget(m_commonColorsWidget);
m_horizontalColorPatchesLayout->removeWidget(m_colorHistoryWidget);
m_horizontalColorPatchesLayout->removeWidget(m_commonColorsWidget);
m_sidebarLayout->removeWidget(m_fallbackSettingsButton);
m_mainLayout->removeWidget(m_fallbackSettingsButton);
if(m_lastColorsShow==false)
m_colorHistoryWidget->hide();
else
m_colorHistoryWidget->show();
if(m_commonColorsShow==false) {
m_commonColorsWidget->hide();
}
else {
m_commonColorsWidget->show();
}
bool fallbackSettingsButtonVertical = true;
if(m_lastColorsShow && m_lastColorsDirection==KisColorPatches::Vertical) {
m_verticalColorPatchesLayout->addWidget(m_colorHistoryWidget);
}
if(m_commonColorsShow && m_commonColorsDirection==KisColorPatches::Vertical) {
m_verticalColorPatchesLayout->addWidget(m_commonColorsWidget);
}
if(m_lastColorsShow && m_lastColorsDirection==KisColorPatches::Horizontal) {
m_horizontalColorPatchesLayout->addWidget(m_colorHistoryWidget);
fallbackSettingsButtonVertical = false;
}
if(m_commonColorsShow && m_commonColorsDirection==KisColorPatches::Horizontal) {
m_horizontalColorPatchesLayout->addWidget(m_commonColorsWidget);
fallbackSettingsButtonVertical = false;
}
// prefer the vertical column, if patch components have different layout
if (m_commonColorsDirection != m_lastColorsDirection) {
fallbackSettingsButtonVertical = true;
}
if (!showColorSelector) {
if (fallbackSettingsButtonVertical) {
m_sidebarLayout->addWidget(m_fallbackSettingsButton);
} else {
m_horizontalPatchesContainer->addWidget(m_fallbackSettingsButton);
}
m_fallbackSettingsButton->show();
} else {
m_fallbackSettingsButton->hide();
}
updateGeometry();
}
void KisColorSelectorNgDockerWidget::reactOnLayerChange()
{
/**
* Trigger the update for the case if some legacy code needs it.
* Now the node's color space is managed by the
* KisDisplayColorConverter and KisColorSelectorBase objects, so
* technically this call is not needed anymore. Please remove it
* when you are totally sure this will not break something.
*/
emit settingsChanged();
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h
index 1b39587a4c..e9d51d3c06 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.h
@@ -1,75 +1,76 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_COLOR_SELECTOR_NG_DOCKER_WIDGET_H
#define KIS_COLOR_SELECTOR_NG_DOCKER_WIDGET_H
#include <QWidget>
#include <QPointer>
#include <QToolButton>
#include <kis_canvas2.h>
class QAction;
class KisCommonColors;
class KisColorHistory;
class KisColorSelectorContainer;
class QVBoxLayout;
class QHBoxLayout;
class KisColorSelectorNgDockerWidget : public QWidget
{
Q_OBJECT
public:
explicit KisColorSelectorNgDockerWidget(QWidget *parent = 0);
void setCanvas(KisCanvas2* canvas);
void unsetCanvas();
public Q_SLOTS:
void openSettings();
Q_SIGNALS:
void settingsChanged();
protected Q_SLOTS:
void updateLayout();
void reactOnLayerChange();
private:
KisColorSelectorContainer* m_colorSelectorContainer;
KisColorHistory* m_colorHistoryWidget;
KisCommonColors* m_commonColorsWidget;
QAction * m_colorHistoryAction;
QAction * m_commonColorsAction;
QHBoxLayout* m_widgetLayout;
QVBoxLayout* m_mainLayout;
QHBoxLayout* m_horizontalPatchesContainer;
QVBoxLayout* m_sidebarLayout;
QHBoxLayout* m_verticalColorPatchesLayout; // vertical color patches should be added here
QVBoxLayout* m_horizontalColorPatchesLayout;//horizontal ----------"----------------------
QToolButton* m_fallbackSettingsButton;
QPointer<KisCanvas2> m_canvas;
};
#endif
diff --git a/plugins/dockers/advancedcolorselector/kis_common_colors.cpp b/plugins/dockers/advancedcolorselector/kis_common_colors.cpp
index f8512ea1b3..6664ac6004 100644
--- a/plugins/dockers/advancedcolorselector/kis_common_colors.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_common_colors.cpp
@@ -1,139 +1,140 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_common_colors.h"
#include <QImage>
#include <QList>
#include <QPushButton>
#include <QColor>
#include <QRunnable>
#include <QThreadPool>
#include <QApplication>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <klocalizedstring.h>
#include <kis_icon.h>
#include "KoColor.h"
#include "kis_canvas2.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_config.h"
#include "kis_common_colors_recalculation_runner.h"
KisCommonColors::KisCommonColors(QWidget *parent) :
KisColorPatches("commonColors", parent)
{
m_reloadButton = new QPushButton();
m_reloadButton->setIcon(KisIconUtils::loadIcon("view-refresh"));
m_reloadButton->setToolTip(i18n("Create a list of colors from the image"));
connect(m_reloadButton, SIGNAL(clicked()), this, SLOT(recalculate()));
QList<QWidget*> tmpList;
tmpList.append(m_reloadButton);
setAdditionalButtons(tmpList);
updateSettings();
m_recalculationTimer.setInterval(2000);
m_recalculationTimer.setSingleShot(true);
connect(&m_recalculationTimer, SIGNAL(timeout()),
this, SLOT(recalculate()));
}
void KisCommonColors::setCanvas(KisCanvas2 *canvas)
{
KisColorPatches::setCanvas(canvas);
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
if (cfg.readEntry("commonColorsAutoUpdate", false)) {
if (m_image) {
m_image->disconnect(this);
}
if (m_canvas) {
connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)),
&m_recalculationTimer, SLOT(start()), Qt::UniqueConnection);
m_image = m_canvas->image();
}
else {
m_image = 0;
}
}
}
KisColorSelectorBase* KisCommonColors::createPopup() const
{
KisCommonColors* ret = new KisCommonColors();
ret->setCanvas(m_canvas);
ret->setColors(colors());
return ret;
}
void KisCommonColors::updateSettings()
{
KisColorPatches::updateSettings();
if(!(m_canvas && m_canvas->image()))
return;
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
if (cfg.readEntry("commonColorsAutoUpdate", false)) {
connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)),
&m_recalculationTimer, SLOT(start()), Qt::UniqueConnection);
}
else {
disconnect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)),
&m_recalculationTimer, SLOT(start()));
}
m_reloadButton->setEnabled(true);
}
void KisCommonColors::setColors(QList<KoColor> colors)
{
QMutexLocker locker(&m_mutex);
KisColorPatches::setColors(colors);
m_reloadButton->setEnabled(true);
m_calculatedColors = colors;
}
void KisCommonColors::recalculate()
{
if (!m_canvas) {
return;
}
if(m_reloadButton->isEnabled()==false) {
// on old computation is still running
// try later to recalculate
m_recalculationTimer.start();
return;
}
m_reloadButton->setEnabled(false);
qApp->processEvents();
KisImageWSP kisImage = m_canvas->image();
QImage image = kisImage->projection()->createThumbnail(1024, 1024, kisImage->bounds(), 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
KisCommonColorsRecalculationRunner* runner = new KisCommonColorsRecalculationRunner(image, patchCount(), this);
QThreadPool::globalInstance()->start(runner);
}
diff --git a/plugins/dockers/advancedcolorselector/kis_common_colors.h b/plugins/dockers/advancedcolorselector/kis_common_colors.h
index 47470af25e..77024a2569 100644
--- a/plugins/dockers/advancedcolorselector/kis_common_colors.h
+++ b/plugins/dockers/advancedcolorselector/kis_common_colors.h
@@ -1,50 +1,51 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_COMMON_COLORS_H
#define KIS_COMMON_COLORS_H
#include <QMutex>
#include <QTimer>
#include "kis_color_patches.h"
#include <kis_types.h>
class QPushButton;
class KisCommonColors : public KisColorPatches
{
Q_OBJECT
public:
explicit KisCommonColors(QWidget *parent = 0);
void setCanvas(KisCanvas2 *canvas) override;
void unsetCanvas() override {}
KisColorSelectorBase* createPopup() const override;
public Q_SLOTS:
void setColors(QList<KoColor> colors);
void updateSettings() override;
void recalculate();
private:
QMutex m_mutex;
QTimer m_recalculationTimer;
QPushButton* m_reloadButton;
QList<KoColor> m_calculatedColors;
KisImageWSP m_image;
};
#endif
diff --git a/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp b/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp
index f3fc6b6d30..b33c736e6a 100644
--- a/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.cpp
@@ -1,182 +1,183 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_minimal_shade_selector.h"
#include <QColor>
#include <QVBoxLayout>
#include <QPainter>
#include <QMouseEvent>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include "KoCanvasResourceProvider.h"
#include "kis_shade_selector_line.h"
#include "kis_color_selector_base_proxy.h"
KisMinimalShadeSelector::KisMinimalShadeSelector(QWidget *parent)
: KisColorSelectorBase(parent)
, m_canvas(0)
, m_proxy(new KisColorSelectorBaseProxyObject(this))
{
setAcceptDrops(true);
QVBoxLayout* l = new QVBoxLayout(this);
l->setSpacing(0);
l->setMargin(0);
updateSettings();
setMouseTracking(true);
}
KisMinimalShadeSelector::~KisMinimalShadeSelector()
{
}
void KisMinimalShadeSelector::unsetCanvas()
{
KisColorSelectorBase::unsetCanvas();
m_canvas = 0;
}
void KisMinimalShadeSelector::setCanvas(KisCanvas2 *canvas)
{
KisColorSelectorBase::setCanvas(canvas);
m_canvas = canvas;
}
void KisMinimalShadeSelector::setColor(const KoColor& color)
{
m_lastRealColor = color;
for(int i=0; i<m_shadingLines.size(); i++) {
m_shadingLines.at(i)->setColor(color);
}
}
void KisMinimalShadeSelector::updateSettings()
{
KisColorSelectorBase::updateSettings();
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
QString stri = cfg.readEntry("minimalShadeSelectorLineConfig", "0|0.2|0|0");
QStringList strili = stri.split(';', QString::SkipEmptyParts);
int lineCount = strili.size();
while(lineCount-m_shadingLines.size() > 0) {
KisShadeSelectorLine *line = new KisShadeSelectorLine(m_proxy.data(), this);
m_shadingLines.append(line);
m_shadingLines.last()->setLineNumber(m_shadingLines.size()-1);
layout()->addWidget(m_shadingLines.last());
}
while(lineCount-m_shadingLines.size() < 0) {
layout()->removeWidget(m_shadingLines.last());
delete m_shadingLines.takeLast();
}
for(int i=0; i<strili.size(); i++) {
m_shadingLines.at(i)->fromString(strili.at(i));
}
int lineHeight = cfg.readEntry("minimalShadeSelectorLineHeight", 20);
setMinimumHeight(lineCount*lineHeight+2*lineCount);
setMaximumHeight(lineCount*lineHeight+2*lineCount);
for(int i=0; i<m_shadingLines.size(); i++)
m_shadingLines.at(i)->updateSettings();
setPopupBehaviour(false, false);
}
void KisMinimalShadeSelector::mousePressEvent(QMouseEvent * e)
{
Q_FOREACH (KisShadeSelectorLine* line, m_shadingLines) {
QMouseEvent newEvent(e->type(),
line->mapFromGlobal(e->globalPos()),
e->button(),
e->buttons(),
e->modifiers());
if(line->rect().contains(newEvent.pos()))
line->mousePressEvent(&newEvent);
}
KisColorSelectorBase::mousePressEvent(e);
}
void KisMinimalShadeSelector::mouseMoveEvent(QMouseEvent * e)
{
Q_FOREACH (KisShadeSelectorLine* line, m_shadingLines) {
QMouseEvent newEvent(e->type(),
line->mapFromGlobal(e->globalPos()),
e->button(),
e->buttons(),
e->modifiers());
if(line->rect().contains(newEvent.pos()))
line->mouseMoveEvent(&newEvent);
}
KisColorSelectorBase::mouseMoveEvent(e);
}
void KisMinimalShadeSelector::mouseReleaseEvent(QMouseEvent * e)
{
Q_FOREACH (KisShadeSelectorLine* line, m_shadingLines) {
QMouseEvent newEvent(e->type(),
line->mapFromGlobal(e->globalPos()),
e->button(),
e->buttons(),
e->modifiers());
if(line->rect().contains(newEvent.pos()))
line->mouseReleaseEvent(&newEvent);
}
KisColorSelectorBase::mouseReleaseEvent(e);
}
void KisMinimalShadeSelector::canvasResourceChanged(int key, const QVariant &v)
{
if(m_colorUpdateAllowed==false)
return;
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
bool onForeground = cfg.readEntry("shadeSelectorUpdateOnForeground", false);
bool onBackground = cfg.readEntry("shadeSelectorUpdateOnBackground", true);
if ((key == KoCanvasResourceProvider::ForegroundColor && onForeground)
|| (key == KoCanvasResourceProvider::BackgroundColor && onBackground)) {
setColor(v.value<KoColor>());
}
}
void KisMinimalShadeSelector::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(0,0,width(), height(), QColor(128,128,128));
}
KisColorSelectorBase* KisMinimalShadeSelector::createPopup() const
{
KisMinimalShadeSelector* popup = new KisMinimalShadeSelector(0);
popup->setColor(m_lastRealColor);
return popup;
}
diff --git a/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.h b/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.h
index 0b86c095e2..2d4aa38ba1 100644
--- a/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.h
+++ b/plugins/dockers/advancedcolorselector/kis_minimal_shade_selector.h
@@ -1,63 +1,64 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MINIMAL_SHADE_SELECTOR_H
#define KIS_MINIMAL_SHADE_SELECTOR_H
#include <QPointer>
#include <kis_canvas2.h>
#include "kis_color_selector_base.h"
class KisShadeSelectorLine;
class KisCanvas2;
class KisColorSelectorBaseProxy;
class KisMinimalShadeSelector : public KisColorSelectorBase
{
Q_OBJECT
public:
explicit KisMinimalShadeSelector(QWidget *parent = 0);
~KisMinimalShadeSelector() override;
void unsetCanvas() override;
void setCanvas(KisCanvas2* canvas) override;
protected:
void setColor(const KoColor& color) override;
void mouseMoveEvent(QMouseEvent *) override;
void mousePressEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
public Q_SLOTS:
void updateSettings() override;
protected Q_SLOTS:
void canvasResourceChanged(int key, const QVariant& v) override;
protected:
void paintEvent(QPaintEvent *) override;
KisColorSelectorBase* createPopup() const override;
private:
QList<KisShadeSelectorLine*> m_shadingLines;
KoColor m_lastRealColor;
QPointer<KisCanvas2> m_canvas;
QScopedPointer<KisColorSelectorBaseProxy> m_proxy;
};
#endif
diff --git a/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.cpp b/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.cpp
index b7083534a3..e5f1f36e55 100644
--- a/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.cpp
@@ -1,303 +1,304 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
* Copyright (c) 2008 Martin Renold <martinxyz@gmx.ch>
* Copyright (c) 2009 Ilya Portnov <nomail>
*
* This class is based on "lib/colorchanger.hpp" from MyPaint (mypaint.intilinux.com)
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_my_paint_shade_selector.h"
#include <cmath>
#include <cstdlib>
#include <QImage>
#include <QColor>
#include <QPainter>
#include <QMouseEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QtGlobal>
#include <QTimer>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include "KoColorSpace.h"
#include "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoCanvasResourceProvider.h"
#include "kis_paint_device.h"
#include "kis_painter.h"
#include "kis_display_color_converter.h"
inline int sqr(int x);
inline qreal sqr2(qreal x);
inline int signedSqr(int x);
KisMyPaintShadeSelector::KisMyPaintShadeSelector(QWidget *parent) :
KisColorSelectorBase(parent),
m_updateTimer(new QTimer(this))
{
setAcceptDrops(true);
updateSettings();
setMinimumSize(80, 80);
setColor(KoColor(Qt::red, colorSpace()));
m_updateTimer->setInterval(1);
m_updateTimer->setSingleShot(true);
connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
}
void KisMyPaintShadeSelector::paintEvent(QPaintEvent *) {
// Hint to the casual reader: some of the calculation here do not
// what Martin Renold originally intended. Not everything here will make sense.
// It does not matter in the end, as long as the result looks good.
// This selector was ported from MyPaint in 2010
if (m_cachedColorSpace != colorSpace()) {
m_realPixelCache = new KisPaintDevice(colorSpace());
m_realCircleBorder = new KisPaintDevice(colorSpace());
m_cachedColorSpace = colorSpace();
}
else {
m_realPixelCache->clear();
m_realCircleBorder->clear();
}
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
QString shadeMyPaintType=cfg.readEntry("shadeMyPaintType", "HSV");
int size = qMin(width(), height());
int s_radius = size/2.6;
for (int x=0; x<width(); x++) {
for (int y=0; y<height(); y++) {
float v_factor = 0.6f;
float s_factor = 0.6f;
float v_factor2 = 0.013f;
float s_factor2 = 0.013f;
int stripe_width = 15*size/255.;
float h = 0;
float s = 0;
float v = 0;
int dx = x-width()/2;
int dy = y-height()/2;
int diag = sqrt(2.0)*size/2;
int dxs, dys;
if (dx > 0)
dxs = dx - stripe_width;
else
dxs = dx + stripe_width;
if (dy > 0)
dys = dy - stripe_width;
else
dys = dy + stripe_width;
qreal r = std::sqrt(qreal(sqr(dxs)+sqr(dys)));
if (qMin(abs(dx), abs(dy)) < stripe_width) {
// horizontal and vertical lines
dx = (dx/qreal(width()))*255;
dy = (dy/qreal(height()))*255;
h = 0;
// x-axis = value, y-axis = saturation
v = dx*v_factor + signedSqr(dx)*v_factor2;
s = - (dy*s_factor + signedSqr(dy)*s_factor2);
// but not both at once
if (std::abs(dx) > std::abs(dy)) {
// horizontal stripe
s = 0.0;
} else {
// vertical stripe
v = 0.0;
}
}
else if (r < s_radius+1) {
// hue
if (dx > 0)
h = 90*sqr2(r/s_radius);
else
h = 360 - 90*sqr2(r/s_radius);
s = 256*(atan2f(std::abs(dxs),dys)/M_PI) - 128;
if (r > s_radius) {
// antialiasing boarder
qreal aaFactor = r-floor(r); // part after the decimal point
aaFactor = 1-aaFactor;
qreal fh = m_colorH + h/360.0;
qreal fs = m_colorS + s/255.0;
qreal fv = m_colorV + v/255.0;
fh -= floor(fh);
fs = qBound(qreal(0.0), fs, qreal(1.0));
fv = qBound(qreal(0.01), fv, qreal(1.0));
KoColor color;
//KoColor color = converter()->fromHsvF(fh, fs, fv);
if(shadeMyPaintType=="HSV"){color = converter()->fromHsvF(fh, fs, fv);}
else if(shadeMyPaintType=="HSL"){color = converter()->fromHslF(fh, fs, fv);}
else if(shadeMyPaintType=="HSI"){color = converter()->fromHsiF(fh, fs, fv);}
else if(shadeMyPaintType=="HSY"){color = converter()->fromHsyF(fh, fs, fv, R, G, B);}
else{dbgKrita<<"MyPaint Color selector don't work right.";
color = converter()->fromHsvF(fh, fs, fv);}
//dbgKrita<<color->toQcolor();
color.setOpacity(aaFactor);
Acs::setColor(m_realCircleBorder, QPoint(x, y), color);
h = 180 + 180*atan2f(dys,-dxs)/M_PI;
v = 255*(r-s_radius)/(diag-s_radius) - 128;
}
}
else {
// background (hue+darkness gradient)
h = 180 + 180*atan2f(dys,-dxs)/M_PI;
v = 255*(r-s_radius)/(diag-s_radius) - 128;
}
qreal fh = m_colorH + h/360.0;
qreal fs = m_colorS + s/255.0;
qreal fv = m_colorV + v/255.0;
fh -= floor(fh);
fs = qBound(qreal(0.0), fs, qreal(1.0));
fv = qBound(qreal(0.01), fv, qreal(1.0));
KoColor color;
//KoColor color = converter()->fromHsvF(fh, fs, fv);
if(shadeMyPaintType=="HSV"){color = converter()->fromHsvF(fh, fs, fv);}
else if(shadeMyPaintType=="HSL"){color = converter()->fromHslF(fh, fs, fv);}
else if(shadeMyPaintType=="HSI"){color = converter()->fromHsiF(fh, fs, fv);}
else if(shadeMyPaintType=="HSY"){color = converter()->fromHsyF(fh, fs, fv);}
else{dbgKrita<<"MyPaint Color selector don't work right.";
color = converter()->fromHsvF(fh, fs, fv);}
Acs::setColor(m_realPixelCache, QPoint(x, y), color);
}
}
KisPainter gc(m_realPixelCache);
gc.bitBlt(QPoint(0,0), m_realCircleBorder, rect());
QPainter painter(this);
QImage renderedImage = converter()->toQImage(m_realPixelCache);
painter.drawImage(0, 0, renderedImage);
}
void KisMyPaintShadeSelector::mousePressEvent(QMouseEvent* e)
{
e->setAccepted(false);
KisColorSelectorBase::mousePressEvent(e);
}
void KisMyPaintShadeSelector::mouseMoveEvent(QMouseEvent *e)
{
if(rect().contains(e->pos())) {
KoColor color(Acs::pickColor(m_realPixelCache, e->pos()));
this->updateColorPreview(color);
}
KisColorSelectorBase::mouseMoveEvent(e);
}
void KisMyPaintShadeSelector::mouseReleaseEvent(QMouseEvent *e)
{
e->setAccepted(false);
KisColorSelectorBase::mouseReleaseEvent(e);
if(!e->isAccepted()) {
KoColor color(Acs::pickColor(m_realPixelCache, e->pos()));
Acs::ColorRole role = Acs::buttonToRole(e->button());
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
bool onRightClick = cfg.readEntry("shadeSelectorUpdateOnRightClick", false);
bool onLeftClick = cfg.readEntry("shadeSelectorUpdateOnLeftClick", false);
bool explicitColorReset =
(e->button() == Qt::LeftButton && onLeftClick) ||
(e->button() == Qt::RightButton && onRightClick);
this->updateColor(color, role, explicitColorReset);
e->accept();
}
}
KisColorSelectorBase* KisMyPaintShadeSelector::createPopup() const
{
KisColorSelectorBase* popup = new KisMyPaintShadeSelector(0);
popup->setColor(m_lastRealColor);
return popup;
}
void KisMyPaintShadeSelector::setColor(const KoColor &color) {
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
QString shadeMyPaintType=cfg.readEntry("shadeMyPaintType", "HSV");
R = cfg.readEntry("lumaR", 0.2126);
G = cfg.readEntry("lumaG", 0.7152);
B = cfg.readEntry("lumaB", 0.0722);
if(shadeMyPaintType=="HSV"){this->converter()->getHsvF(color, &m_colorH, &m_colorS, &m_colorV);}
if(shadeMyPaintType=="HSL"){this->converter()->getHslF(color, &m_colorH, &m_colorS, &m_colorV);}
if(shadeMyPaintType=="HSI"){this->converter()->getHsiF(color, &m_colorH, &m_colorS, &m_colorV);}
if(shadeMyPaintType=="HSY"){this->converter()->getHsyF(color, &m_colorH, &m_colorS, &m_colorV, R, G, B);}
m_lastRealColor = color;
this->updateColorPreview(color);
m_updateTimer->start();
}
void KisMyPaintShadeSelector::canvasResourceChanged(int key, const QVariant &v)
{
if(m_colorUpdateAllowed==false)
return;
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
bool onForeground = cfg.readEntry("shadeSelectorUpdateOnForeground", false);
bool onBackground = cfg.readEntry("shadeSelectorUpdateOnBackground", true);
if ((key == KoCanvasResourceProvider::ForegroundColor && onForeground) ||
(key == KoCanvasResourceProvider::BackgroundColor && onBackground)) {
setColor(v.value<KoColor>());
}
}
inline int sqr(int x) {
return x*x;
}
inline qreal sqr2(qreal x) {
return (x*x)/2+x/2;
}
inline int signedSqr(int x) {
int sign = x>0?1:-1;
return x*x*sign;
}
diff --git a/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.h b/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.h
index f0e5bab939..319551f460 100644
--- a/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.h
+++ b/plugins/dockers/advancedcolorselector/kis_my_paint_shade_selector.h
@@ -1,64 +1,65 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
* Copyright (c) 2008 Martin Renold <martinxyz@gmx.ch>
* Copyright (c) 2009 Ilya Portnov <nomail>
*
* This class is based on "lib/colorchanger.hpp" from MyPaint (mypaint.intilinux.com)
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MY_PAINT_SHADE_SELECTOR_H
#define KIS_MY_PAINT_SHADE_SELECTOR_H
#include "kis_color_selector_base.h"
#include <QColor>
#include <QImage>
#include <KoColor.h>
class KoColorSpace;
class QTimer;
class KisMyPaintShadeSelector : public KisColorSelectorBase
{
Q_OBJECT
public:
KisMyPaintShadeSelector(QWidget *parent = 0);
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
public:
void setColor(const KoColor &color) override;
protected Q_SLOTS:
void canvasResourceChanged(int key, const QVariant& v) override;
protected:
void paintEvent(QPaintEvent *) override;
KisColorSelectorBase* createPopup() const override;
private:
qreal m_colorH, m_colorS, m_colorV;
qreal R, G, B;
QTimer* m_updateTimer;
KoColor m_lastRealColor;
KisPaintDeviceSP m_realPixelCache;
KisPaintDeviceSP m_realCircleBorder;
const KoColorSpace *m_cachedColorSpace;
};
#endif // KIS_MY_PAINT_SHADE_SELECTOR_H
diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_line.cpp b/plugins/dockers/advancedcolorselector/kis_shade_selector_line.cpp
index fa743d6bfd..03fa380c66 100644
--- a/plugins/dockers/advancedcolorselector/kis_shade_selector_line.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_line.cpp
@@ -1,275 +1,276 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_shade_selector_line.h"
#include <QPainter>
#include <QColor>
#include <QMouseEvent>
#include <ksharedconfig.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <klocalizedstring.h>
#include <KoColorSpaceRegistry.h>
#include "kis_canvas2.h"
#include "kis_color_selector_base_proxy.h"
#include "kis_display_color_converter.h"
#include "kis_paint_device.h"
KisShadeSelectorLine::KisShadeSelectorLine(KisColorSelectorBaseProxy *parentProxy,
QWidget *parent)
: KisShadeSelectorLineBase(parent),
m_cachedColorSpace(0),
m_displayHelpText(false),
m_parentProxy(parentProxy)
{
setParam(0, 0, 0, 0, 0, 0);
updateSettings();
setMouseTracking(true);
m_mouseX=width()/2;
m_isDown=false;
}
KisShadeSelectorLine::KisShadeSelectorLine(qreal hueDelta, qreal satDelta, qreal valDelta,
KisColorSelectorBaseProxy *parentProxy, QWidget *parent, qreal hueShift, qreal satShift, qreal valShift) :
KisShadeSelectorLineBase(parent),
m_cachedColorSpace(0),
m_displayHelpText(false),
m_parentProxy(parentProxy)
{
setParam(hueDelta, satDelta, valDelta, hueShift, satShift, valShift);
updateSettings();
m_mouseX=width()/2;
m_isDown=false;
}
KisShadeSelectorLine::~KisShadeSelectorLine()
{
}
void KisShadeSelectorLine::setParam(qreal hueDelta, qreal satDelta, qreal valDelta, qreal hueShift, qreal satShift, qreal valShift)
{
m_hueDelta = hueDelta;
m_saturationDelta = satDelta;
m_valueDelta = valDelta;
m_hueShift = hueShift;
m_saturationShift = satShift;
m_valueShift = valShift;
}
void KisShadeSelectorLine::setColor(const KoColor &color)
{
m_realColor = color;
m_realColor.convertTo(m_parentProxy->colorSpace());
m_mouseX=width()/2;
update();
}
void KisShadeSelectorLine::updateSettings()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
m_gradient = cfg.readEntry("minimalShadeSelectorAsGradient", false);
m_patchCount = cfg.readEntry("minimalShadeSelectorPatchCount", 10);
m_lineHeight = cfg.readEntry("minimalShadeSelectorLineHeight", 20);
setMaximumHeight(m_lineHeight);
setMinimumHeight(m_lineHeight);
}
QString KisShadeSelectorLine::toString() const
{
return QString("%1|%2|%3|%4|%5|%6|%7").arg(m_lineNumber).arg(m_hueDelta).arg(m_saturationDelta).arg(m_valueDelta).arg(m_hueShift).arg(m_saturationShift).arg(m_valueShift);
}
void KisShadeSelectorLine::fromString(const QString& string)
{
QStringList strili = string.split('|');
m_lineNumber = strili.at(0).toInt();
m_hueDelta = strili.at(1).toDouble();
m_saturationDelta = strili.at(2).toDouble();
m_valueDelta = strili.at(3).toDouble();
if(strili.size()==4) return; // don't crash, if reading old config files.
m_hueShift = strili.at(4).toDouble();
m_saturationShift = strili.at(5).toDouble();
m_valueShift = strili.at(6).toDouble();
}
void KisShadeSelectorLine::paintEvent(QPaintEvent *)
{
if (m_cachedColorSpace != m_parentProxy->colorSpace()) {
m_realPixelCache = new KisPaintDevice(m_parentProxy->colorSpace());
m_cachedColorSpace = m_parentProxy->colorSpace();
}
else {
m_realPixelCache->clear();
}
int patchCount;
int patchSpacing;
if(m_gradient) {
patchCount = width();
patchSpacing = 0;
}
else {
patchCount = m_patchCount;
patchSpacing = 3;
}
qreal patchWidth = (width()-patchSpacing*patchCount)/qreal(patchCount);
qreal hueStep;
qreal saturationStep;
qreal valueStep;
if(m_gradient){
hueStep=m_hueDelta/qreal(patchCount-10);
saturationStep=m_saturationDelta/qreal(patchCount-10);
valueStep=m_valueDelta/qreal(patchCount-10);
}
else{
hueStep=m_hueDelta/qreal(patchCount);
saturationStep=m_saturationDelta/qreal(patchCount);
valueStep=m_valueDelta/qreal(patchCount);
}
qreal baseHue;
qreal baseSaturation;
qreal baseValue;
m_parentProxy->converter()->
getHsvF(m_realColor, &baseHue, &baseSaturation, &baseValue);
int z=0;
for(int i=-patchCount/2; i<=patchCount/2; i++) {
if(i==0 && patchCount%2==0) continue;
qreal hue = baseHue + (i * hueStep) + m_hueShift;
while (hue < 0.0) hue += 1.0;
while (hue > 1.0) hue -= 1.0;
qreal saturation = qBound<qreal>(0., baseSaturation + (i * saturationStep) + m_saturationShift, 1.);
qreal value = qBound<qreal>(0., baseValue + (i * valueStep) + m_valueShift, 1.);
if (qAbs(i) <= 5 && m_gradient) {
hue = baseHue;
saturation=baseSaturation;
value=baseValue;
}
QRect patchRect(z * (patchWidth + patchSpacing), 0, patchWidth, m_lineHeight);
KoColor patchColor = m_parentProxy->converter()->fromHsvF(hue, saturation, value);
patchColor.convertTo(m_realPixelCache->colorSpace());
m_realPixelCache->fill(patchRect, patchColor);
z++;
}
QPainter wpainter(this);
QImage renderedImage = m_parentProxy->converter()->toQImage(m_realPixelCache);
wpainter.drawImage(0, 0, renderedImage);
if (m_gradient) {
wpainter.setPen(QColor(175,175,175));
wpainter.drawRect(renderedImage.width()/2-5,0,10,renderedImage.height()-1);
wpainter.setPen(QColor(75,75,75));
wpainter.drawRect(renderedImage.width()/2-4,0,8,renderedImage.height()-1);
wpainter.setPen(QColor(175,175,175));
qreal mouseX = (qBound(5.0,m_mouseX,m_width-5));
wpainter.drawRect(mouseX-5,0,10,renderedImage.height()-1);
wpainter.setPen(QColor(75,75,75));
wpainter.drawRect(mouseX-4,0,8,renderedImage.height()-1);
}
m_width=width();
if(m_displayHelpText) {
QString helpText(i18n("delta h=%1 s=%2 v=%3 shift h=%4 s=%5 v=%6",
m_hueDelta,
m_saturationDelta,
m_valueDelta,
m_hueShift,
m_saturationShift,
m_valueShift));
wpainter.setPen(QColor(255,255,255));
wpainter.drawText(rect(), helpText);
}
}
void KisShadeSelectorLine::resizeEvent(QResizeEvent *e)
{
QSize m_old = e->oldSize();
QSize m_new = e->size();
int m_nWidth = m_new.width();
int m_oWidth = m_old.width();
m_width = width();
m_mouseX = m_mouseX * m_nWidth / m_oWidth;
}
void KisShadeSelectorLine::mousePressEvent(QMouseEvent* e)
{
if(e->button()!=Qt::LeftButton && e->button()!=Qt::RightButton) {
e->setAccepted(false);
return;
}
if (e->y() > 0 && e->y() < height()) {
m_parentProxy->showColorPreview();
e->accept();
m_mouseX = e->x();
m_isDown=true;
update();
}
}
void KisShadeSelectorLine::mouseMoveEvent(QMouseEvent *e)
{
if ((m_isDown) && (e->buttons() & Qt::LeftButton)) {
m_mouseX=e->x();
const QPoint mouseEv ((qBound(5.0,m_mouseX,m_width-5)),5);
KoColor color(Acs::pickColor(m_realPixelCache, mouseEv));
m_parentProxy->updateColorPreview(color);
update();
}
}
void KisShadeSelectorLine::mouseReleaseEvent(QMouseEvent * e)
{
if (e->button() != Qt::LeftButton && e->button() != Qt::RightButton) {
e->ignore();
return;
}
m_mouseX=e->x();
const QPoint mouseEv ((qBound(5.0,m_mouseX,m_width-5)),5);
KoColor color(Acs::pickColor(m_realPixelCache, mouseEv));
m_parentProxy->updateColorPreview(color);
Acs::ColorRole role = Acs::buttonToRole(e->button());
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
bool onRightClick = cfg.readEntry("shadeSelectorUpdateOnRightClick", false);
bool onLeftClick = cfg.readEntry("shadeSelectorUpdateOnLeftClick", false);
bool explicitColorReset =
(e->button() == Qt::LeftButton && onLeftClick) ||
(e->button() == Qt::RightButton && onRightClick);
m_parentProxy->updateColor(color, role, explicitColorReset);
e->accept();
m_isDown=false;
}
diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_line.h b/plugins/dockers/advancedcolorselector/kis_shade_selector_line.h
index 2ac5ec8c7a..508d327672 100644
--- a/plugins/dockers/advancedcolorselector/kis_shade_selector_line.h
+++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_line.h
@@ -1,96 +1,97 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SHADE_SELECTOR_LINE_H
#define KIS_SHADE_SELECTOR_LINE_H
#include <QWidget>
#include <KoColor.h>
#include "kis_types.h"
class KisCanvas2;
class KisShadeSelectorLineComboBox;
class KisColorSelectorBaseProxy;
class KoColorSpace;
class KisShadeSelectorLineBase : public QWidget {
public:
KisShadeSelectorLineBase(QWidget* parent) : QWidget(parent)
{}
void setLineNumber(int n) {m_lineNumber=n;}
virtual QString toString() const = 0;
virtual void fromString(const QString& string) = 0;
protected:
int m_lineNumber;
};
class KisShadeSelectorLine : public KisShadeSelectorLineBase
{
Q_OBJECT
public:
explicit KisShadeSelectorLine(KisColorSelectorBaseProxy *parentProxy,
QWidget *parent = 0);
explicit KisShadeSelectorLine(qreal hueDelta, qreal satDelta, qreal valDelta,
KisColorSelectorBaseProxy *parentProxy, QWidget *parent = 0, qreal hueShift = 0, qreal satShift = 0, qreal valShift = 0);
~KisShadeSelectorLine() override;
void setParam(qreal hue, qreal sat, qreal val, qreal hueShift, qreal satShift, qreal shiftVal);
void setColor(const KoColor& color);
void updateSettings();
void setCanvas(KisCanvas2* canvas);
void showHelpText() {m_displayHelpText=true;}
QString toString() const override;
void fromString(const QString& string) override;
void paintEvent(QPaintEvent *) override;
void resizeEvent(QResizeEvent *) override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
private:
qreal m_hueDelta;
qreal m_saturationDelta;
qreal m_valueDelta;
qreal m_hueShift;
qreal m_saturationShift;
qreal m_valueShift;
KoColor m_realColor;
KisPaintDeviceSP m_realPixelCache;
const KoColorSpace *m_cachedColorSpace;
bool m_gradient;
int m_patchCount;
int m_lineHeight;
bool m_displayHelpText;
qreal m_mouseX;
QPoint m_ev;
qreal m_width;
bool m_isDown;
friend class KisShadeSelectorLineComboBox;
KisColorSelectorBaseProxy* m_parentProxy;
};
#endif // KIS_SHADE_SELECTOR_LINE_H
diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp
index 1e2ecb2367..f43ece889c 100644
--- a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp
@@ -1,173 +1,174 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_shade_selector_line_combo_box.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QGridLayout>
#include <QPainter>
#include <klocalizedstring.h>
#include "kis_shade_selector_line.h"
#include "kis_shade_selector_line_combo_box_popup.h"
#include "kis_color_selector_base_proxy.h"
#include "kis_global.h"
KisShadeSelectorLineComboBox::KisShadeSelectorLineComboBox(QWidget *parent) :
QComboBox(parent),
m_popup(new KisShadeSelectorLineComboBoxPopup(this)),
m_parentProxy(new KisColorSelectorBaseProxyNoop()),
m_currentLine(new KisShadeSelectorLine(0,0,0, m_parentProxy.data(), this))
{
QGridLayout* l = new QGridLayout(this);
l->addWidget(m_currentLine);
m_currentLine->setEnabled(false);
KoColor color;
color.fromQColor(QColor(190, 50, 50));
m_currentLine->setColor(color);
updateSettings();
}
KisShadeSelectorLineComboBox::~KisShadeSelectorLineComboBox()
{
}
void KisShadeSelectorLineComboBox::hidePopup()
{
QComboBox::hidePopup();
m_popup->hide();
}
void KisShadeSelectorLineComboBox::showPopup()
{
QComboBox::showPopup();
m_popup->show();
const int widgetMargin = 20;
const QRect fitRect = kisGrowRect(QApplication::desktop()->screenGeometry(), -widgetMargin);
QRect popupRect = m_popup->rect();
popupRect.moveTo(mapToGlobal(QPoint()));
popupRect = kisEnsureInRect(popupRect, fitRect);
m_popup->move(popupRect.topLeft());
m_popup->setConfiguration(m_currentLine->toString());
}
void KisShadeSelectorLineComboBox::setConfiguration(const QString &stri)
{
m_currentLine->fromString(stri);
update();
}
QString KisShadeSelectorLineComboBox::configuration() const
{
return m_currentLine->toString();
}
void KisShadeSelectorLineComboBox::setLineNumber(int n)
{
m_currentLine->setLineNumber(n);
for(int i=0; i<m_popup->layout()->count(); i++) {
KisShadeSelectorLine* item = dynamic_cast<KisShadeSelectorLine*>(m_popup->layout()->itemAt(i)->widget());
if(item!=0) {
item->setLineNumber(n);
}
}
}
void KisShadeSelectorLineComboBox::resizeEvent(QResizeEvent *e)
{
Q_UNUSED(e);
m_currentLine->setMaximumWidth(width()-30-m_popup->spacing);
m_popup->setMinimumWidth(qMax(280, width()));
m_popup->setMaximumWidth(qMax(280, width()));
}
void KisShadeSelectorLineComboBox::updateSettings()
{
m_currentLine->updateSettings();
for(int i=0; i<m_popup->layout()->count(); i++) {
KisShadeSelectorLine* item = dynamic_cast<KisShadeSelectorLine*>(m_popup->layout()->itemAt(i)->widget());
if(item!=0) {
item->updateSettings();
item->m_lineHeight=30;
item->setMaximumHeight(30);
item->setMinimumHeight(30);
}
}
setLineHeight(m_currentLine->m_lineHeight);
}
void KisShadeSelectorLineComboBox::setGradient(bool b)
{
m_currentLine->m_gradient=b;
for(int i=0; i<m_popup->layout()->count(); i++) {
KisShadeSelectorLine* item = dynamic_cast<KisShadeSelectorLine*>(m_popup->layout()->itemAt(i)->widget());
if(item!=0) {
item->m_gradient=b;
}
}
update();
}
void KisShadeSelectorLineComboBox::setPatches(bool b)
{
m_currentLine->m_gradient=!b;
for(int i=0; i<m_popup->layout()->count(); i++) {
KisShadeSelectorLine* item = dynamic_cast<KisShadeSelectorLine*>(m_popup->layout()->itemAt(i)->widget());
if(item!=0) {
item->m_gradient=!b;
}
}
update();
}
void KisShadeSelectorLineComboBox::setPatchCount(int count)
{
m_currentLine->m_patchCount=count;
for(int i=0; i<m_popup->layout()->count(); i++) {
KisShadeSelectorLine* item = dynamic_cast<KisShadeSelectorLine*>(m_popup->layout()->itemAt(i)->widget());
if(item!=0) {
item->m_patchCount=count;
}
}
update();
}
void KisShadeSelectorLineComboBox::setLineHeight(int height)
{
m_currentLine->m_lineHeight=height;
m_currentLine->setMinimumHeight(height);
m_currentLine->setMaximumHeight(height);
setMinimumHeight(height+m_popup->spacing);
setMaximumHeight(height+m_popup->spacing);
update();
}
diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.h b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.h
index 72e069a631..66f97ec525 100644
--- a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.h
+++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.h
@@ -1,56 +1,57 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SHADE_SELECTOR_LINE_COMBO_BOX_H
#define KIS_SHADE_SELECTOR_LINE_COMBO_BOX_H
#include <QComboBox>
class KisShadeSelectorLineComboBoxPopup;
class KisShadeSelectorLine;
class KisColorSelectorBaseProxy;
class KisShadeSelectorLineComboBox : public QComboBox
{
Q_OBJECT
public:
explicit KisShadeSelectorLineComboBox(QWidget *parent = 0);
~KisShadeSelectorLineComboBox() override;
void hidePopup() override;
void showPopup() override;
QString configuration() const;
void setLineNumber(int n);
protected:
void resizeEvent(QResizeEvent *e) override;
public Q_SLOTS:
void setConfiguration(const QString& stri);
void updateSettings();
void setGradient(bool);
void setPatches(bool);
void setPatchCount(int count);
void setLineHeight(int height);
private:
KisShadeSelectorLineComboBoxPopup* m_popup;
QScopedPointer<KisColorSelectorBaseProxy> m_parentProxy;
KisShadeSelectorLine* m_currentLine;
};
#endif // KIS_SHADE_SELECTOR_LINE_COMBO_BOX_H
diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.cpp b/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.cpp
index b998933527..81d173321d 100644
--- a/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.cpp
@@ -1,88 +1,89 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_shade_selector_lines_settings.h"
#include <QVBoxLayout>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include "kis_shade_selector_line_combo_box.h"
KisShadeSelectorLinesSettings::KisShadeSelectorLinesSettings(QWidget *parent) :
QWidget(parent)
{
QVBoxLayout* l = new QVBoxLayout(this);
l->setSpacing(0);
l->setMargin(0);
}
QString KisShadeSelectorLinesSettings::toString() const
{
QString result;
for(int i=0; i<m_lineList.size(); i++) {
result.append(m_lineList.at(i)->configuration());
result.append(';');
}
return result;
}
void KisShadeSelectorLinesSettings::fromString(const QString &stri)
{
QStringList strili = stri.split(';', QString::SkipEmptyParts);
setLineCount(strili.size());
for(int i=0; i<strili.size(); i++) {
m_lineList.at(i)->setConfiguration(strili.at(i));
}
}
void KisShadeSelectorLinesSettings::updateSettings()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
fromString(cfg.readEntry("minimalShadeSelectorLineConfig", "0|0.2|0|0"));
for(int i=0; i<m_lineList.size(); i++) {
m_lineList.at(i)->updateSettings();
}
}
void KisShadeSelectorLinesSettings::setLineCount(int count)
{
bool emitSignal = (m_lineList.size()!=count)?true:false;
while(count-m_lineList.size() > 0) {
m_lineList.append(new KisShadeSelectorLineComboBox(this));
m_lineList.last()->setLineNumber(m_lineList.size()-1);
layout()->addWidget(m_lineList.last());
}
while(count-m_lineList.size() < 0) {
layout()->removeWidget(m_lineList.last());
delete m_lineList.takeLast();
}
for(int i=0; i<m_lineList.size(); i++) {
connect(this, SIGNAL(setGradient(bool)), m_lineList.at(i), SLOT(setGradient(bool)), Qt::UniqueConnection);
connect(this, SIGNAL(setPatches(bool)), m_lineList.at(i), SLOT(setPatches(bool)), Qt::UniqueConnection);
connect(this, SIGNAL(setLineHeight(int)), m_lineList.at(i), SLOT(setLineHeight(int)), Qt::UniqueConnection);
connect(this, SIGNAL(setPatchCount(int)), m_lineList.at(i), SLOT(setPatchCount(int)), Qt::UniqueConnection);
}
if(emitSignal)
emit lineCountChanged(count);
}
diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.h b/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.h
index 42b5138d5c..7cfeec74b2 100644
--- a/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.h
+++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_lines_settings.h
@@ -1,51 +1,52 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SHADE_SELECTOR_LINES_SETTINGS_H
#define KIS_SHADE_SELECTOR_LINES_SETTINGS_H
#include <QWidget>
class KisShadeSelectorLineComboBox;
class KisShadeSelectorLinesSettings : public QWidget
{
Q_OBJECT
public:
explicit KisShadeSelectorLinesSettings(QWidget *parent = 0);
QString toString() const;
void fromString(const QString& stri);
public Q_SLOTS:
void updateSettings();
void setLineCount(int count);
Q_SIGNALS:
void setGradient(bool);
void setPatches(bool);
void setPatchCount(int count);
void setLineHeight(int height);
Q_SIGNALS:
void lineCountChanged(int newLineCount);
private:
QList<KisShadeSelectorLineComboBox*> m_lineList;
};
#endif // KIS_SHADE_SELECTOR_LINES_SETTINGS_H
diff --git a/plugins/dockers/animation/kis_equalizer_widget.cpp b/plugins/dockers/animation/kis_equalizer_widget.cpp
index 055a446458..3d09df567f 100644
--- a/plugins/dockers/animation/kis_equalizer_widget.cpp
+++ b/plugins/dockers/animation/kis_equalizer_widget.cpp
@@ -1,153 +1,154 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_equalizer_widget.h"
#include <QMouseEvent>
#include <QApplication>
#include <QHBoxLayout>
#include "kis_equalizer_column.h"
#include "kis_signal_compressor.h"
#include "timeline_color_scheme.h"
#include "kis_debug.h"
struct KisEqualizerWidget::Private
{
Private()
: maxDistance(0),
updateCompressor(300, KisSignalCompressor::FIRST_ACTIVE)
{
}
QMap<int, KisEqualizerColumn*> columns;
int maxDistance;
KisSignalCompressor updateCompressor;
};
KisEqualizerWidget::KisEqualizerWidget(int maxDistance, QWidget *parent)
: QWidget(parent),
m_d(new Private)
{
m_d->maxDistance = maxDistance;
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setSpacing(0);
+ layout->setMargin(0);
for (int i = -m_d->maxDistance; i <= m_d->maxDistance; i++) {
KisEqualizerColumn *c = new KisEqualizerColumn(this, i, QString::number(i));
layout->addWidget(c, i == 0 ? 2 : 1);
if (i == m_d->maxDistance) {
c->setRightmost(true);
}
m_d->columns.insert(i, c);
connect(c, SIGNAL(sigColumnChanged(int,bool,int)),
&m_d->updateCompressor, SLOT(start()));
}
connect(&m_d->updateCompressor, SIGNAL(timeout()), SIGNAL(sigConfigChanged()));
connect(m_d->columns[0], SIGNAL(sigColumnChanged(int,bool,int)), this, SLOT(slotMasterColumnChanged(int,bool,int)));
setLayout(layout);
}
KisEqualizerWidget::~KisEqualizerWidget()
{
}
KisEqualizerWidget::EqualizerValues KisEqualizerWidget::getValues() const
{
EqualizerValues v;
v.maxDistance = m_d->maxDistance;
for (int i = -m_d->maxDistance; i <= m_d->maxDistance; i++) {
v.value.insert(i, m_d->columns[i]->value());
v.state.insert(i, m_d->columns[i]->state());
}
return v;
}
void KisEqualizerWidget::setValues(const EqualizerValues &v)
{
for (int i = -m_d->maxDistance; i <= m_d->maxDistance; i++) {
if (qAbs(i) <= v.maxDistance) {
m_d->columns[i]->setValue(v.value[i]);
m_d->columns[i]->setState(v.state[i]);
} else {
m_d->columns[i]->setState(false);
}
}
}
void KisEqualizerWidget::toggleMasterSwitch()
{
const bool currentState = m_d->columns[0]->state();
m_d->columns[0]->setState(!currentState);
}
void KisEqualizerWidget::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event);
const QSize newSize = m_d->columns[1]->size();
QFont font =
TimelineColorScheme::instance()->getOnionSkinsFont(
QString::number(100), newSize);
if (font.pointSize() != this->font().pointSize()) {
setFont(font);
for (int i = -m_d->maxDistance; i <= m_d->maxDistance; i++) {
m_d->columns[i]->setFont(font);
}
}
}
void KisEqualizerWidget::mouseMoveEvent(QMouseEvent *ev)
{
if (!(ev->modifiers() & Qt::ShiftModifier)) return;
QPoint globalPos = ev->globalPos();
QWidget *w = qApp->widgetAt(globalPos);
if (w && w->inherits("QAbstractSlider")) {
QMouseEvent newEv(ev->type(),
w->mapFromGlobal(globalPos),
globalPos,
ev->button(),
ev->buttons(),
ev->modifiers() & ~Qt::ShiftModifier);
qApp->sendEvent(w, &newEv);
}
}
void KisEqualizerWidget::slotMasterColumnChanged(int, bool state, int)
{
for (int i = 1; i <= m_d->maxDistance; i++) {
m_d->columns[i]->setForceDisabled(!state);
m_d->columns[-i]->setForceDisabled(!state);
}
}
diff --git a/plugins/dockers/animation/onion_skins_docker.cpp b/plugins/dockers/animation/onion_skins_docker.cpp
index b6de9b09f4..84739711b4 100644
--- a/plugins/dockers/animation/onion_skins_docker.cpp
+++ b/plugins/dockers/animation/onion_skins_docker.cpp
@@ -1,256 +1,271 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
*
* 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 "onion_skins_docker.h"
#include "ui_onion_skins_docker.h"
#include <QSlider>
#include <QFrame>
#include <QGridLayout>
#include "kis_icon_utils.h"
#include "kis_image_config.h"
#include "kis_onion_skin_compositor.h"
#include "kis_signals_blocker.h"
#include "kis_node_view_color_scheme.h"
#include "KisViewManager.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include <KoColorSpaceRegistry.h>
#include "kis_equalizer_widget.h"
OnionSkinsDocker::OnionSkinsDocker(QWidget *parent) :
QDockWidget(i18n("Onion Skins"), parent),
ui(new Ui::OnionSkinsDocker),
m_updatesCompressor(300, KisSignalCompressor::FIRST_ACTIVE),
m_toggleOnionSkinsAction(0)
{
QWidget* mainWidget = new QWidget(this);
setWidget(mainWidget);
KisImageConfig config(true);
ui->setupUi(mainWidget);
+ mainWidget->setContentsMargins(10, 10, 10, 10);
+
+
ui->doubleTintFactor->setMinimum(0);
ui->doubleTintFactor->setMaximum(100);
ui->doubleTintFactor->setPrefix(i18n("Tint: "));
ui->doubleTintFactor->setSuffix("%");
ui->btnBackwardColor->setToolTip(i18n("Tint color for past frames"));
ui->btnForwardColor->setToolTip(i18n("Tint color for future frames"));
QVBoxLayout *layout = ui->slidersLayout;
m_equalizerWidget = new KisEqualizerWidget(10, this);
connect(m_equalizerWidget, SIGNAL(sigConfigChanged()), &m_updatesCompressor, SLOT(start()));
layout->addWidget(m_equalizerWidget, 1);
connect(ui->btnBackwardColor, SIGNAL(changed(KoColor)), &m_updatesCompressor, SLOT(start()));
connect(ui->btnForwardColor, SIGNAL(changed(KoColor)), &m_updatesCompressor, SLOT(start()));
connect(ui->doubleTintFactor, SIGNAL(valueChanged(qreal)), &m_updatesCompressor, SLOT(start()));
connect(&m_updatesCompressor, SIGNAL(timeout()),
SLOT(changed()));
{
const bool isShown = config.showAdditionalOnionSkinsSettings();
ui->btnShowHide->setChecked(isShown);
connect(ui->btnShowHide, SIGNAL(toggled(bool)), SLOT(slotShowAdditionalSettings(bool)));
slotShowAdditionalSettings(isShown);
}
// create colored checkboxes for onion skin filtering
KisNodeViewColorScheme scm;
QPalette filterColorPalette;
QPixmap iconPixmap(10, 10);
//iconPixmap.fill(scm.colorLabel(0));
//ui->colorFilter0_checkbox->setIcon(iconPixmap); // default(no) color
iconPixmap.fill(scm.colorLabel(1));
ui->colorFilter1_checkbox->setIcon(QIcon(iconPixmap));
iconPixmap.fill(scm.colorLabel(2));
ui->colorFilter2_checkbox->setIcon(QIcon(iconPixmap));
iconPixmap.fill(scm.colorLabel(3));
ui->colorFilter3_checkbox->setIcon(QIcon(iconPixmap));
iconPixmap.fill(scm.colorLabel(4));
ui->colorFilter4_checkbox->setIcon(QIcon(iconPixmap));
iconPixmap.fill(scm.colorLabel(5));
ui->colorFilter5_checkbox->setIcon(QIcon(iconPixmap));
iconPixmap.fill(scm.colorLabel(6));
ui->colorFilter6_checkbox->setIcon(QIcon(iconPixmap));
iconPixmap.fill(scm.colorLabel(7));
ui->colorFilter7_checkbox->setIcon(QIcon(iconPixmap));
iconPixmap.fill(scm.colorLabel(8));
ui->colorFilter8_checkbox->setIcon(QIcon(iconPixmap));
// assign click events to color filters and group checkbox
connect(ui->colorFilter0_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter1_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter2_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter3_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter4_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter5_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter6_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter7_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilter8_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
connect(ui->colorFilterGroupbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged()));
loadSettings();
KisOnionSkinCompositor::instance()->configChanged();
+ // this mostly hides the checkboxes since no filtering is done by default
+ slotFilteredColorsChanged();
+
resize(sizeHint());
}
OnionSkinsDocker::~OnionSkinsDocker()
{
delete ui;
}
void OnionSkinsDocker::setCanvas(KoCanvasBase *canvas)
{
Q_UNUSED(canvas);
}
void OnionSkinsDocker::unsetCanvas()
{
}
void OnionSkinsDocker::setViewManager(KisViewManager *view)
{
KisActionManager *actionManager = view->actionManager();
m_toggleOnionSkinsAction = actionManager->createAction("toggle_onion_skin");
connect(m_toggleOnionSkinsAction, SIGNAL(triggered()), SLOT(slotToggleOnionSkins()));
slotUpdateIcons();
connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons()));
}
void OnionSkinsDocker::slotToggleOnionSkins()
{
m_equalizerWidget->toggleMasterSwitch();
}
void OnionSkinsDocker::slotFilteredColorsChanged()
{
// what colors are selected to filter??
QList<int> selectedFilterColors;
if (ui->colorFilter0_checkbox->isChecked()) selectedFilterColors << 0;
if (ui->colorFilter1_checkbox->isChecked()) selectedFilterColors << 1;
if (ui->colorFilter2_checkbox->isChecked()) selectedFilterColors << 2;
if (ui->colorFilter3_checkbox->isChecked()) selectedFilterColors << 3;
if (ui->colorFilter4_checkbox->isChecked()) selectedFilterColors << 4;
if (ui->colorFilter5_checkbox->isChecked()) selectedFilterColors << 5;
if (ui->colorFilter6_checkbox->isChecked()) selectedFilterColors << 6;
if (ui->colorFilter7_checkbox->isChecked()) selectedFilterColors << 7;
if (ui->colorFilter8_checkbox->isChecked()) selectedFilterColors << 8;
// show all colors if the filter is off and ignore the checkboxes
if(ui->colorFilterGroupbox->isChecked() == false) {
selectedFilterColors.clear();
selectedFilterColors << 0 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8; // show everything
}
+ ui->colorFilter0_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter1_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter2_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter3_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter4_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter5_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter6_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter7_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
+ ui->colorFilter8_checkbox->setVisible(ui->colorFilterGroupbox->isChecked());
// existing code
KisOnionSkinCompositor::instance()->setColorLabelFilter(selectedFilterColors);
KisOnionSkinCompositor::instance()->configChanged();
}
void OnionSkinsDocker::slotUpdateIcons()
{
if (m_toggleOnionSkinsAction) {
m_toggleOnionSkinsAction->setIcon(KisIconUtils::loadIcon("onion_skin_options"));
}
}
void OnionSkinsDocker::slotShowAdditionalSettings(bool value)
{
ui->lblPrevColor->setVisible(value);
ui->lblNextColor->setVisible(value);
ui->btnBackwardColor->setVisible(value);
ui->btnForwardColor->setVisible(value);
ui->doubleTintFactor->setVisible(value);
QIcon icon = KisIconUtils::loadIcon(value ? "arrow-down" : "arrow-up");
ui->btnShowHide->setIcon(icon);
KisImageConfig(false).setShowAdditionalOnionSkinsSettings(value);
}
void OnionSkinsDocker::changed()
{
KisImageConfig config(false);
KisEqualizerWidget::EqualizerValues v = m_equalizerWidget->getValues();
config.setNumberOfOnionSkins(v.maxDistance);
for (int i = -v.maxDistance; i <= v.maxDistance; i++) {
config.setOnionSkinOpacity(i, v.value[i] * 255.0 / 100.0);
config.setOnionSkinState(i, v.state[i]);
}
config.setOnionSkinTintFactor(ui->doubleTintFactor->value() * 255.0 / 100.0);
config.setOnionSkinTintColorBackward(ui->btnBackwardColor->color().toQColor());
config.setOnionSkinTintColorForward(ui->btnForwardColor->color().toQColor());
KisOnionSkinCompositor::instance()->configChanged();
}
void OnionSkinsDocker::loadSettings()
{
KisImageConfig config(true);
KisSignalsBlocker b(ui->doubleTintFactor,
ui->btnBackwardColor,
ui->btnForwardColor,
m_equalizerWidget);
ui->doubleTintFactor->setValue(qRound(config.onionSkinTintFactor() * 100.0 / 255));
KoColor bcol(KoColorSpaceRegistry::instance()->rgb8());
bcol.fromQColor(config.onionSkinTintColorBackward());
ui->btnBackwardColor->setColor(bcol);
bcol.fromQColor(config.onionSkinTintColorForward());
ui->btnForwardColor->setColor(bcol);
KisEqualizerWidget::EqualizerValues v;
v.maxDistance = 10;
for (int i = -v.maxDistance; i <= v.maxDistance; i++) {
v.value.insert(i, qRound(config.onionSkinOpacity(i) * 100.0 / 255.0));
v.state.insert(i, config.onionSkinState(i));
}
m_equalizerWidget->setValues(v);
}
diff --git a/plugins/dockers/animation/onion_skins_docker.ui b/plugins/dockers/animation/onion_skins_docker.ui
index 426e4abd46..e16b4003d5 100644
--- a/plugins/dockers/animation/onion_skins_docker.ui
+++ b/plugins/dockers/animation/onion_skins_docker.ui
@@ -1,226 +1,258 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OnionSkinsDocker</class>
<widget class="QWidget" name="OnionSkinsDocker">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>281</width>
+ <width>336</width>
<height>282</height>
</rect>
</property>
+ <property name="font">
+ <font>
+ <kerning>false</kerning>
+ </font>
+ </property>
<property name="windowTitle">
<string>Onion skin options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>12</number>
+ </property>
+ <property name="topMargin">
+ <number>12</number>
+ </property>
+ <property name="rightMargin">
+ <number>12</number>
+ </property>
+ <property name="bottomMargin">
+ <number>12</number>
+ </property>
<item>
<layout class="QVBoxLayout" name="slidersLayout">
<property name="spacing">
<number>0</number>
</property>
</layout>
</item>
<item>
<widget class="QGroupBox" name="colorFilterGroupbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Filter Frames by Color</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>4</number>
</property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
<item>
<widget class="QCheckBox" name="colorFilter0_checkbox">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>None</string>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter1_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter2_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter3_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter4_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter5_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter6_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter7_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="colorFilter8_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="buttonsLayout">
<property name="spacing">
<number>6</number>
</property>
<item>
<layout class="QHBoxLayout" name="hideShowButtonlayout">
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnShowHide">
<property name="text">
<string>+</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="KisDoubleSliderSpinBox" name="doubleTintFactor" native="true">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="KisColorButton" name="btnBackwardColor">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblPrevColor">
<property name="text">
<string>Previous frames</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>13</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="lblNextColor">
<property name="text">
<string>Next frames</string>
</property>
</widget>
</item>
<item>
<widget class="KisColorButton" name="btnForwardColor">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisColorButton</class>
<extends>QPushButton</extends>
<header>kis_color_button.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/dockers/arrangedocker/arrange_docker_widget.cpp b/plugins/dockers/arrangedocker/arrange_docker_widget.cpp
index 760ff036da..bd133d385b 100644
--- a/plugins/dockers/arrangedocker/arrange_docker_widget.cpp
+++ b/plugins/dockers/arrangedocker/arrange_docker_widget.cpp
@@ -1,102 +1,103 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "arrange_docker_widget.h"
#include "ui_arrange_docker_widget.h"
#include "kis_debug.h"
#include "kactioncollection.h"
#include <QAction>
#include <QToolButton>
struct ArrangeDockerWidget::Private
{
};
ArrangeDockerWidget::ArrangeDockerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ArrangeDockerWidget),
m_d(new Private)
{
ui->setupUi(this);
}
ArrangeDockerWidget::~ArrangeDockerWidget()
{
}
void replaceAction(QToolButton *button, QAction *newAction)
{
Q_FOREACH (QAction *action, button->actions()) {
button->removeAction(action);
}
if (newAction) {
button->setDefaultAction(newAction);
}
}
void ArrangeDockerWidget::setActionCollection(KActionCollection *collection)
{
const bool enabled = collection->action("object_order_front");
if (enabled) {
replaceAction(ui->bringToFront, collection->action("object_order_front"));
replaceAction(ui->raiseLevel, collection->action("object_order_raise"));
replaceAction(ui->lowerLevel, collection->action("object_order_lower"));
replaceAction(ui->sendBack, collection->action("object_order_back"));
replaceAction(ui->leftAlign, collection->action("object_align_horizontal_left"));
replaceAction(ui->hCenterAlign, collection->action("object_align_horizontal_center"));
replaceAction(ui->rightAlign, collection->action("object_align_horizontal_right"));
replaceAction(ui->topAlign, collection->action("object_align_vertical_top"));
replaceAction(ui->vCenterAlign, collection->action("object_align_vertical_center"));
replaceAction(ui->bottomAlign, collection->action("object_align_vertical_bottom"));
replaceAction(ui->hDistributeLeft, collection->action("object_distribute_horizontal_left"));
replaceAction(ui->hDistributeCenter, collection->action("object_distribute_horizontal_center"));
replaceAction(ui->hDistributeRight, collection->action("object_distribute_horizontal_right"));
replaceAction(ui->hDistributeGaps, collection->action("object_distribute_horizontal_gaps"));
replaceAction(ui->vDistributeTop, collection->action("object_distribute_vertical_top"));
replaceAction(ui->vDistributeCenter, collection->action("object_distribute_vertical_center"));
replaceAction(ui->vDistributeBottom, collection->action("object_distribute_vertical_bottom"));
replaceAction(ui->vDistributeGaps, collection->action("object_distribute_vertical_gaps"));
replaceAction(ui->group, collection->action("object_group"));
replaceAction(ui->ungroup, collection->action("object_ungroup"));
}
setEnabled(enabled);
}
void ArrangeDockerWidget::switchState(bool enabled)
{
if (enabled) {
ui->buttons->show();
ui->disabledLabel->hide();
} else {
ui->buttons->hide();
ui->disabledLabel->show();
}
}
diff --git a/plugins/dockers/arrangedocker/arrange_docker_widget.h b/plugins/dockers/arrangedocker/arrange_docker_widget.h
index 4054dbc5b9..17768cd0e7 100644
--- a/plugins/dockers/arrangedocker/arrange_docker_widget.h
+++ b/plugins/dockers/arrangedocker/arrange_docker_widget.h
@@ -1,49 +1,50 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 ARRANGE_DOCKER_WIDGET_H
#define ARRANGE_DOCKER_WIDGET_H
#include <QWidget>
#include <QScopedPointer>
#include "kactioncollection.h"
namespace Ui {
class ArrangeDockerWidget;
}
class ArrangeDockerWidget : public QWidget
{
Q_OBJECT
public:
explicit ArrangeDockerWidget(QWidget *parent = 0);
~ArrangeDockerWidget() override;
void setActionCollection(KActionCollection *collection);
void switchState(bool enabled);
private:
Ui::ArrangeDockerWidget *ui;
struct Private;
const QScopedPointer<Private> m_d;
};
#endif // ARRANGE_DOCKER_WIDGET_H
diff --git a/plugins/dockers/arrangedocker/arrangedocker.cpp b/plugins/dockers/arrangedocker/arrangedocker.cpp
index 63f5929832..b1fbb406cd 100644
--- a/plugins/dockers/arrangedocker/arrangedocker.cpp
+++ b/plugins/dockers/arrangedocker/arrangedocker.cpp
@@ -1,77 +1,78 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "arrangedocker.h"
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "KisViewManager.h"
#include "arrangedocker_dock.h"
K_PLUGIN_FACTORY_WITH_JSON(ArrangeDockerPluginFactory, "krita_arrangedocker.json", registerPlugin<ArrangeDockerPlugin>();)
class ArrangeDockerDockFactory : public KoDockFactoryBase {
public:
ArrangeDockerDockFactory()
{
}
QString id() const override
{
return QString( "ArrangeDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
ArrangeDockerDock * dockWidget = new ArrangeDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
ArrangeDockerPlugin::ArrangeDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new ArrangeDockerDockFactory());
}
ArrangeDockerPlugin::~ArrangeDockerPlugin()
{
m_view = 0;
}
#include "arrangedocker.moc"
diff --git a/plugins/dockers/arrangedocker/arrangedocker.h b/plugins/dockers/arrangedocker/arrangedocker.h
index 3e1880a238..dde0404187 100644
--- a/plugins/dockers/arrangedocker/arrangedocker.h
+++ b/plugins/dockers/arrangedocker/arrangedocker.h
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _ARRANGE_DOCKER_H_
#define _ARRANGE_DOCKER_H_
#include <QObject>
#include <QVariant>
class KisViewManager;
/**
* Template of view plugin
*/
class ArrangeDockerPlugin : public QObject
{
Q_OBJECT
public:
ArrangeDockerPlugin(QObject *parent, const QVariantList &);
~ArrangeDockerPlugin() override;
private:
KisViewManager* m_view;
};
#endif
diff --git a/plugins/dockers/arrangedocker/arrangedocker_dock.cpp b/plugins/dockers/arrangedocker/arrangedocker_dock.cpp
index 0208d8e25c..32c11c305f 100644
--- a/plugins/dockers/arrangedocker/arrangedocker_dock.cpp
+++ b/plugins/dockers/arrangedocker/arrangedocker_dock.cpp
@@ -1,90 +1,91 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "arrangedocker_dock.h"
#include <klocalizedstring.h>
#include "kis_canvas2.h"
#include <KisViewManager.h>
#include "arrange_docker_widget.h"
#include <KoToolProxy.h>
#include <KoShapeManager.h>
ArrangeDockerDock::ArrangeDockerDock( )
: QDockWidget(i18n("Arrange"))
, m_canvas(0)
{
m_configWidget = new ArrangeDockerWidget(this);
m_configWidget->switchState(false);
setWidget(m_configWidget);
setEnabled(m_canvas);
}
ArrangeDockerDock::~ArrangeDockerDock()
{
}
void ArrangeDockerDock::setCanvas(KoCanvasBase * canvas)
{
if(canvas && m_canvas == canvas)
return;
if (m_canvas) {
m_canvasConnections.clear();
m_canvas->disconnectCanvasObserver(this);
m_canvas->image()->disconnect(this);
}
m_canvas = canvas ? dynamic_cast<KisCanvas2*>(canvas) : 0;
setEnabled(m_canvas);
if (m_canvas) {
m_canvasConnections.addConnection(
m_canvas->toolProxy(),
SIGNAL(toolChanged(QString)),
this,
SLOT(slotToolChanged(QString)));
m_canvasConnections.addConnection(
m_canvas->shapeManager(),
SIGNAL(selectionChanged()),
this,
SLOT(slotToolChanged()));
slotToolChanged();
}
}
void ArrangeDockerDock::unsetCanvas()
{
setCanvas(0);
}
void ArrangeDockerDock::slotToolChanged()
{
KActionCollection *collection = m_canvas->viewManager()->actionCollection();
m_configWidget->setActionCollection(collection);
}
void ArrangeDockerDock::slotToolChanged(QString toolId)
{
bool enableWidget = (toolId == "InteractionTool") ? true : false;
m_configWidget->switchState(enableWidget);
slotToolChanged();
}
diff --git a/plugins/dockers/arrangedocker/arrangedocker_dock.h b/plugins/dockers/arrangedocker/arrangedocker_dock.h
index ffcb2eac08..ff03550cd2 100644
--- a/plugins/dockers/arrangedocker/arrangedocker_dock.h
+++ b/plugins/dockers/arrangedocker/arrangedocker_dock.h
@@ -1,49 +1,50 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _GRID_DOCK_H_
#define _GRID_DOCK_H_
#include <QDockWidget>
#include <KoCanvasObserverBase.h>
#include "kis_signal_auto_connection.h"
class KisCanvas2;
class ArrangeDockerWidget;
class KisSignalAutoConnection;
class ArrangeDockerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
ArrangeDockerDock();
~ArrangeDockerDock() override;
QString observerName() override { return "ArrangeDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
private Q_SLOTS:
void slotToolChanged();
void slotToolChanged(QString toolId);
private:
ArrangeDockerWidget *m_configWidget;
QPointer<KisCanvas2> m_canvas;
KisSignalAutoConnectionsStore m_canvasConnections;
};
#endif
diff --git a/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.cpp b/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.cpp
index 8b700a76c8..58c9788fe0 100644
--- a/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.cpp
+++ b/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.cpp
@@ -1,459 +1,460 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoCanvasResourceProvider.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServerObserver.h>
#include <KoResourceServerAdapter.h>
#include <KoCanvasBase.h>
#include <kis_canvas2.h>
#include <KoColor.h>
#include <resources/KoGamutMask.h>
#include <kis_icon_utils.h>
#include <KisPart.h>
#include <kis_shape_layer.h>
#include <kis_types.h>
#include <KisDocument.h>
#include <kis_node_selection_adapter.h>
#include <kis_group_layer.h>
#include <KisView.h>
#include <KoResourceItemChooser.h>
#include <kis_display_color_converter.h>
#include <QWidget>
#include <QMenu>
#include <QButtonGroup>
#include <QRegExpValidator>
#include <QRegExp>
#include <QFileInfo>
#include "artisticcolorselector_dock.h"
#include <KisViewManager.h>
#include <kis_canvas_resource_provider.h>
#include <kis_arcs_constants.h>
#include <KisGamutMaskToolbar.h>
#include "ui_wdgArtisticColorSelector.h"
#include "ui_wdgARCSSettings.h"
#include "ui_wdgWheelPreferencesPopup.h"
class KisMainWindow;
struct ArtisticColorSelectorUI: public QWidget, public Ui_wdgArtisticColorSelector
{
ArtisticColorSelectorUI() {
setupUi(this);
}
};
struct ARCSSettingsUI: public QWidget, public Ui_wdgARCSSettings
{
ARCSSettingsUI() {
setupUi(this);
}
};
struct WheelPreferencesPopupUI: public QWidget, public Ui_wdgWheelPreferencesPopup
{
WheelPreferencesPopupUI() {
setupUi(this);
}
};
ArtisticColorSelectorDock::ArtisticColorSelectorDock()
: QDockWidget(i18n("Artistic Color Selector"))
, m_canvas(nullptr)
, m_resourceProvider(0)
, m_selectedMask(nullptr)
{
setEnabled(false);
m_hsxButtons = new QButtonGroup();
m_preferencesUI = new ARCSSettingsUI();
m_wheelPrefsUI = new WheelPreferencesPopupUI();
m_selectorUI = new ArtisticColorSelectorUI();
QPixmap hueStepsPixmap = KisIconUtils::loadIcon("wheel-sectors").pixmap(16,16);
QPixmap saturationStepsPixmap = KisIconUtils::loadIcon("wheel-rings").pixmap(16,16);
QPixmap valueScaleStepsPixmap = KisIconUtils::loadIcon("wheel-light").pixmap(16,16);
QIcon infinityIcon = KisIconUtils::loadIcon("infinity");
m_infinityPixmap = infinityIcon.pixmap(16,16);
m_selectorUI->colorSelector->loadSettings();
m_selectorUI->bnWheelPrefs->setIcon(KisIconUtils::loadIcon("wheel-sectors"));
m_selectorUI->bnWheelPrefs->setPopupWidget(m_wheelPrefsUI);
m_selectorUI->bnDockerPrefs->setPopupWidget(m_preferencesUI);
m_selectorUI->bnDockerPrefs->setIcon(KisIconUtils::loadIcon("configure"));
//preferences
m_hsxButtons->addButton(m_preferencesUI->bnHsy, KisColor::HSY);
m_hsxButtons->addButton(m_preferencesUI->bnHsi, KisColor::HSI);
m_hsxButtons->addButton(m_preferencesUI->bnHsl, KisColor::HSL);
m_hsxButtons->addButton(m_preferencesUI->bnHsv, KisColor::HSV);
m_wheelPrefsUI->bnInverseSat->setChecked(m_selectorUI->colorSelector->isSaturationInverted());
m_wheelPrefsUI->labelHueSteps->setPixmap(hueStepsPixmap);
m_wheelPrefsUI->labelSaturationSteps->setPixmap(saturationStepsPixmap);
m_wheelPrefsUI->labelValueScaleSteps->setPixmap(valueScaleStepsPixmap);
m_wheelPrefsUI->numHueSteps->setRange(MIN_NUM_UI_HUE_PIECES, MAX_NUM_HUE_PIECES);
m_wheelPrefsUI->numSaturationSteps->setRange(MIN_NUM_SATURATION_RINGS, MAX_NUM_SATURATION_RINGS);
m_wheelPrefsUI->numValueScaleSteps->setRange(MIN_NUM_UI_LIGHT_PIECES, MAX_NUM_LIGHT_PIECES);
m_wheelPrefsUI->bnInfHueSteps->setIcon(infinityIcon);
m_wheelPrefsUI->bnInfValueScaleSteps->setIcon(infinityIcon);
m_wheelPrefsUI->bnInfHueSteps->setToolTip(i18n("Continuous Mode"));
m_wheelPrefsUI->bnInfValueScaleSteps->setToolTip(i18n("Continuous Mode"));
int selectorHueSteps = m_selectorUI->colorSelector->getNumPieces();
if (selectorHueSteps == 1) {
m_wheelPrefsUI->bnInfHueSteps->setChecked(true);
} else {
m_wheelPrefsUI->bnInfHueSteps->setChecked(false);
}
m_wheelPrefsUI->numHueSteps->setValue(selectorHueSteps);
m_wheelPrefsUI->numSaturationSteps->setValue(m_selectorUI->colorSelector->getNumRings());
int selectorValueScaleSteps = m_selectorUI->colorSelector->getNumLightPieces();
if (selectorValueScaleSteps == 1) {
m_wheelPrefsUI->bnInfValueScaleSteps->setChecked(true);
} else {
m_wheelPrefsUI->bnInfValueScaleSteps->setChecked(false);
}
m_wheelPrefsUI->numValueScaleSteps->setValue(m_selectorUI->colorSelector->getNumLightPieces());
m_preferencesUI->bnDefInfHueSteps->setIcon(infinityIcon);
m_preferencesUI->bnDefInfValueScaleSteps->setIcon(infinityIcon);
m_preferencesUI->labelDefHueSteps->setPixmap(hueStepsPixmap);
m_preferencesUI->labelDefSaturationSteps->setPixmap(saturationStepsPixmap);
m_preferencesUI->labelDefValueScaleSteps->setPixmap(valueScaleStepsPixmap);
m_preferencesUI->defaultHueSteps->setRange(MIN_NUM_HUE_PIECES, MAX_NUM_HUE_PIECES);
m_preferencesUI->defaultSaturationSteps->setRange(MIN_NUM_SATURATION_RINGS, MAX_NUM_SATURATION_RINGS);
m_preferencesUI->defaultValueScaleSteps->setRange(MIN_NUM_LIGHT_PIECES, MAX_NUM_LIGHT_PIECES);
m_preferencesUI->defaultHueSteps->setValue(m_selectorUI->colorSelector->getDefaultHueSteps());
m_preferencesUI->defaultSaturationSteps->setValue(m_selectorUI->colorSelector->getDefaultSaturationSteps());
m_preferencesUI->defaultValueScaleSteps->setValue(m_selectorUI->colorSelector->getDefaultValueScaleSteps());
m_preferencesUI->showBgColor->setChecked(m_selectorUI->colorSelector->getShowBgColor());
m_preferencesUI->showValueScaleNumbers->setChecked(m_selectorUI->colorSelector->getShowValueScaleNumbers());
m_preferencesUI->enforceGamutMask->setChecked(m_selectorUI->colorSelector->enforceGamutMask());
m_preferencesUI->permissiveGamutMask->setChecked(!m_selectorUI->colorSelector->enforceGamutMask());
m_preferencesUI->spLumaR->setValue(m_selectorUI->colorSelector->lumaR());
m_preferencesUI->spLumaG->setValue(m_selectorUI->colorSelector->lumaG());
m_preferencesUI->spLumaB->setValue(m_selectorUI->colorSelector->lumaB());
m_preferencesUI->spLumaGamma->setValue(m_selectorUI->colorSelector->lumaGamma());
switch(m_selectorUI->colorSelector->getColorSpace())
{
case KisColor::HSV: { m_preferencesUI->bnHsv->setChecked(true); } break;
case KisColor::HSI: { m_preferencesUI->bnHsi->setChecked(true); } break;
case KisColor::HSL: { m_preferencesUI->bnHsl->setChecked(true); } break;
case KisColor::HSY: { m_preferencesUI->bnHsy->setChecked(true); } break;
}
if (m_selectorUI->colorSelector->getColorSpace() == KisColor::HSY) {
m_preferencesUI->lumaCoefficientBox->show();
} else {
m_preferencesUI->lumaCoefficientBox->hide();
}
connect(m_wheelPrefsUI->numValueScaleSteps , SIGNAL(valueChanged(int)) , SLOT(slotPreferenceChanged()));
connect(m_wheelPrefsUI->numHueSteps , SIGNAL(valueChanged(int)) , SLOT(slotPreferenceChanged()));
connect(m_wheelPrefsUI->numSaturationSteps , SIGNAL(valueChanged(int)) , SLOT(slotPreferenceChanged()));
connect(m_wheelPrefsUI->bnInverseSat , SIGNAL(clicked(bool)) , SLOT(slotPreferenceChanged()));
connect(m_wheelPrefsUI->bnInfHueSteps , SIGNAL(clicked(bool)) , SLOT(slotPreferenceChanged()));
connect(m_wheelPrefsUI->bnInfValueScaleSteps, SIGNAL(clicked(bool)) , SLOT(slotPreferenceChanged()));
connect(m_wheelPrefsUI->bnDefault , SIGNAL(clicked(bool)) , SLOT(slotResetDefaultSettings()));
connect(m_preferencesUI->defaultHueSteps , SIGNAL(valueChanged(int)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->defaultSaturationSteps, SIGNAL(valueChanged(int)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->defaultValueScaleSteps, SIGNAL(valueChanged(int)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->bnDefInfHueSteps , SIGNAL(clicked(bool)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->bnDefInfValueScaleSteps, SIGNAL(clicked(bool)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->showBgColor , SIGNAL(toggled(bool)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->showValueScaleNumbers, SIGNAL(toggled(bool)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->enforceGamutMask , SIGNAL(toggled(bool)) , SLOT(slotPreferenceChanged()));
connect(m_preferencesUI->spLumaR , SIGNAL(valueChanged(qreal)), SLOT(slotColorSpaceSelected()));
connect(m_preferencesUI->spLumaG , SIGNAL(valueChanged(qreal)), SLOT(slotColorSpaceSelected()));
connect(m_preferencesUI->spLumaB , SIGNAL(valueChanged(qreal)), SLOT(slotColorSpaceSelected()));
connect(m_preferencesUI->spLumaGamma , SIGNAL(valueChanged(qreal)), SLOT(slotColorSpaceSelected()));
connect(m_selectorUI->colorSelector , SIGNAL(sigFgColorChanged(KisColor)) , SLOT(slotFgColorChanged(KisColor)));
connect(m_selectorUI->colorSelector , SIGNAL(sigBgColorChanged(KisColor)) , SLOT(slotBgColorChanged(KisColor)));
// gamut mask connections
connect(m_selectorUI->gamutMaskToolbar, SIGNAL(sigGamutMaskToggle(bool)), SLOT(slotGamutMaskToggle(bool)));
connect(m_hsxButtons , SIGNAL(buttonClicked(int)) , SLOT(slotColorSpaceSelected()));
setWidget(m_selectorUI);
}
ArtisticColorSelectorDock::~ArtisticColorSelectorDock()
{
m_selectorUI->colorSelector->saveSettings();
delete m_hsxButtons;
}
void ArtisticColorSelectorDock::setViewManager(KisViewManager* kisview)
{
m_resourceProvider = kisview->canvasResourceProvider();
m_selectorUI->colorSelector->setFgColor(m_resourceProvider->resourceManager()->foregroundColor());
m_selectorUI->colorSelector->setBgColor(m_resourceProvider->resourceManager()->backgroundColor());
connect(m_resourceProvider, SIGNAL(sigGamutMaskChanged(KoGamutMask*)),
this, SLOT(slotGamutMaskSet(KoGamutMask*)), Qt::UniqueConnection);
connect(m_resourceProvider, SIGNAL(sigGamutMaskUnset()),
this, SLOT(slotGamutMaskUnset()), Qt::UniqueConnection);
connect(m_resourceProvider, SIGNAL(sigGamutMaskPreviewUpdate()),
this, SLOT(slotGamutMaskPreviewUpdate()), Qt::UniqueConnection);
m_selectorUI->gamutMaskToolbar->connectMaskSignals(m_resourceProvider);
}
void ArtisticColorSelectorDock::slotCanvasResourceChanged(int key, const QVariant& value)
{
if(key == KoCanvasResourceProvider::ForegroundColor)
m_selectorUI->colorSelector->setFgColor(value.value<KoColor>());
if(key == KoCanvasResourceProvider::BackgroundColor)
m_selectorUI->colorSelector->setBgColor(value.value<KoColor>());
}
void ArtisticColorSelectorDock::slotFgColorChanged(const KisColor& color)
{
m_resourceProvider->resourceManager()->setForegroundColor(
KoColor(color.toKoColor(), m_resourceProvider->resourceManager()->foregroundColor().colorSpace())
);
}
void ArtisticColorSelectorDock::slotBgColorChanged(const KisColor& color)
{
m_resourceProvider->resourceManager()->setBackgroundColor(
KoColor(color.toKoColor(), m_resourceProvider->resourceManager()->backgroundColor().colorSpace())
);
}
void ArtisticColorSelectorDock::slotColorSpaceSelected()
{
KisColor::Type type = static_cast<KisColor::Type>(
m_hsxButtons->id(m_hsxButtons->checkedButton()));
m_selectorUI->colorSelector->setColorSpace(type);
if (type == KisColor::HSY) {
m_preferencesUI->lumaCoefficientBox->show();
} else {
m_preferencesUI->lumaCoefficientBox->hide();
}
m_selectorUI->colorSelector->setLumaCoefficients(
m_preferencesUI->spLumaR->value(),
m_preferencesUI->spLumaG->value(),
m_preferencesUI->spLumaB->value(),
m_preferencesUI->spLumaGamma->value()
);
}
void ArtisticColorSelectorDock::slotPreferenceChanged()
{
int hueSteps = DEFAULT_HUE_STEPS;
if (m_wheelPrefsUI->bnInfHueSteps->isChecked()) {
m_wheelPrefsUI->numHueSteps->setEnabled(false);
hueSteps = 1;
} else {
m_wheelPrefsUI->numHueSteps->setEnabled(true);
hueSteps = m_wheelPrefsUI->numHueSteps->value();
}
m_selectorUI->colorSelector->setNumPieces(hueSteps);
m_selectorUI->colorSelector->setNumRings(m_wheelPrefsUI->numSaturationSteps->value());
int valueScaleSteps;
if (m_wheelPrefsUI->bnInfValueScaleSteps->isChecked()) {
m_wheelPrefsUI->numValueScaleSteps->setEnabled(false);
valueScaleSteps = 1;
} else {
valueScaleSteps = m_wheelPrefsUI->numValueScaleSteps->value();
m_wheelPrefsUI->numValueScaleSteps->setEnabled(true);
}
m_selectorUI->colorSelector->setNumLightPieces(valueScaleSteps);
int defHueSteps;
if (m_preferencesUI->bnDefInfHueSteps->isChecked()) {
m_preferencesUI->defaultHueSteps->setEnabled(false);
defHueSteps = 1;
} else {
m_preferencesUI->defaultHueSteps->setEnabled(true);
defHueSteps = m_preferencesUI->defaultHueSteps->value();
}
m_selectorUI->colorSelector->setDefaultHueSteps(defHueSteps);
m_selectorUI->colorSelector->setDefaultSaturationSteps(m_preferencesUI->defaultSaturationSteps->value());
int defValueScaleSteps;
if (m_preferencesUI->bnDefInfValueScaleSteps->isChecked()) {
m_preferencesUI->defaultValueScaleSteps->setEnabled(false);
defValueScaleSteps = 1;
} else {
m_preferencesUI->defaultValueScaleSteps->setEnabled(true);
defValueScaleSteps = m_preferencesUI->defaultValueScaleSteps->value();
}
m_selectorUI->colorSelector->setDefaultValueScaleSteps(defValueScaleSteps);
m_selectorUI->colorSelector->setShowBgColor(m_preferencesUI->showBgColor->isChecked());
m_selectorUI->colorSelector->setShowValueScaleNumbers(m_preferencesUI->showValueScaleNumbers->isChecked());
m_selectorUI->colorSelector->setEnforceGamutMask(m_preferencesUI->enforceGamutMask->isChecked());
m_selectorUI->colorSelector->setInverseSaturation(m_wheelPrefsUI->bnInverseSat->isChecked());
}
void ArtisticColorSelectorDock::slotResetDefaultSettings()
{
quint32 hueSteps = m_selectorUI->colorSelector->getDefaultHueSteps();
quint32 saturationSteps = m_selectorUI->colorSelector->getDefaultSaturationSteps();
quint32 valueScaleSteps = m_selectorUI->colorSelector->getDefaultValueScaleSteps();
m_selectorUI->colorSelector->setNumRings(saturationSteps);
m_wheelPrefsUI->numSaturationSteps->blockSignals(true);
m_wheelPrefsUI->numSaturationSteps->setValue(saturationSteps);
m_wheelPrefsUI->numSaturationSteps->blockSignals(false);
m_selectorUI->colorSelector->setNumPieces(hueSteps);
m_wheelPrefsUI->numHueSteps->blockSignals(true);
m_wheelPrefsUI->numHueSteps->setValue(hueSteps);
m_wheelPrefsUI->numHueSteps->blockSignals(false);
if (hueSteps == 1) {
m_wheelPrefsUI->numHueSteps->setEnabled(false);
m_wheelPrefsUI->bnInfHueSteps->setChecked(true);
} else {
m_wheelPrefsUI->numHueSteps->setEnabled(true);
m_wheelPrefsUI->bnInfHueSteps->setChecked(false);
}
m_selectorUI->colorSelector->setNumLightPieces(valueScaleSteps);
m_wheelPrefsUI->numValueScaleSteps->blockSignals(true);
m_wheelPrefsUI->numValueScaleSteps->setValue(valueScaleSteps);
m_wheelPrefsUI->numValueScaleSteps->blockSignals(false);
if (valueScaleSteps == 1) {
m_wheelPrefsUI->numValueScaleSteps->setEnabled(false);
m_wheelPrefsUI->bnInfValueScaleSteps->setChecked(true);
} else {
m_wheelPrefsUI->numValueScaleSteps->setEnabled(true);
m_wheelPrefsUI->bnInfValueScaleSteps->setChecked(false);
}
}
void ArtisticColorSelectorDock::slotGamutMaskToggle(bool checked)
{
bool b = (!m_selectedMask) ? false : checked;
if (b == true) {
m_selectorUI->colorSelector->setGamutMask(m_selectedMask);
}
m_selectorUI->colorSelector->setGamutMaskOn(b);
}
void ArtisticColorSelectorDock::setCanvas(KoCanvasBase *canvas)
{
if (!canvas) {
return;
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
}
if (m_canvas) {
connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
SLOT(slotCanvasResourceChanged(int,QVariant)), Qt::UniqueConnection);
connect(m_canvas->displayColorConverter(), SIGNAL(displayConfigurationChanged()),
SLOT(slotSelectorSettingsChanged()), Qt::UniqueConnection);
m_selectorUI->colorSelector->setColorConverter(m_canvas->displayColorConverter());
setEnabled(true);
}
}
void ArtisticColorSelectorDock::unsetCanvas()
{
setEnabled(false);
m_canvas = nullptr;
m_selectorUI->colorSelector->setColorConverter(KisDisplayColorConverter::dumbConverterInstance());
}
void ArtisticColorSelectorDock::slotGamutMaskSet(KoGamutMask *mask)
{
if (!mask) {
return;
}
m_selectedMask = mask;
if (m_selectedMask) {
m_selectorUI->colorSelector->setGamutMask(m_selectedMask);
slotGamutMaskToggle(true);
} else {
slotGamutMaskToggle(false);
}
}
void ArtisticColorSelectorDock::slotGamutMaskUnset()
{
if (!m_selectedMask) {
return;
}
m_selectedMask = nullptr;
slotGamutMaskToggle(false);
m_selectorUI->colorSelector->setGamutMask(m_selectedMask);
}
void ArtisticColorSelectorDock::slotGamutMaskPreviewUpdate()
{
m_selectorUI->colorSelector->setDirty();
}
void ArtisticColorSelectorDock::slotSelectorSettingsChanged()
{
m_selectorUI->colorSelector->setDirty();
}
diff --git a/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.h b/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.h
index 23112382c8..68172f0c43 100644
--- a/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.h
+++ b/plugins/dockers/artisticcolorselector/artisticcolorselector_dock.h
@@ -1,86 +1,87 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 H_ARTISTIC_COLOR_SELECTOR_DOCK_H
#define H_ARTISTIC_COLOR_SELECTOR_DOCK_H
#include <QDockWidget>
#include <QPointer>
#include <QRegExpValidator>
#include <KoCanvasObserverBase.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServerAdapter.h>
#include <KoResourceServerObserver.h>
#include <resources/KoGamutMask.h>
#include <KisDocument.h>
#include <kis_types.h>
#include <KoResourceItemChooser.h>
#include <kis_mainwindow_observer.h>
class KisCanvasResourceProvider;
class KisColor;
class QButtonGroup;
class QMenu;
struct ArtisticColorSelectorUI;
struct ARCSSettingsUI;
struct WheelPreferencesPopupUI;
class ArtisticColorSelectorDock: public QDockWidget, public KisMainwindowObserver
{
Q_OBJECT
public:
ArtisticColorSelectorDock();
~ArtisticColorSelectorDock() override;
QString observerName() override { return "ArtisticColorSelectorDock"; }
void setViewManager(KisViewManager* kisview) override;
void setCanvas(KoCanvasBase* canvas) override;
void unsetCanvas() override;
private Q_SLOTS:
void slotCanvasResourceChanged(int key, const QVariant& value);
void slotFgColorChanged(const KisColor& color);
void slotBgColorChanged(const KisColor& color);
void slotColorSpaceSelected();
void slotPreferenceChanged();
void slotResetDefaultSettings();
void slotGamutMaskToggle(bool value);
void slotGamutMaskSet(KoGamutMask* mask);
void slotGamutMaskUnset();
void slotGamutMaskPreviewUpdate();
void slotSelectorSettingsChanged();
private:
KisCanvas2* m_canvas;
KisCanvasResourceProvider* m_resourceProvider;
QButtonGroup* m_hsxButtons;
ArtisticColorSelectorUI* m_selectorUI;
ARCSSettingsUI* m_preferencesUI;
WheelPreferencesPopupUI* m_wheelPrefsUI;
KoGamutMask* m_selectedMask;
QIcon m_iconMaskOff;
QIcon m_iconMaskOn;
QPixmap m_infinityPixmap;
};
#endif // H_ARTISTIC_COLOR_SELECTOR_DOCK_H
diff --git a/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.cpp b/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.cpp
index 7051282cd8..be960ebc89 100644
--- a/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.cpp
+++ b/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.cpp
@@ -1,57 +1,58 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "artisticcolorselector_plugin.h"
#include "artisticcolorselector_dock.h"
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(PaletteDockPluginFactory, "krita_artisticcolorselector.json", registerPlugin<ArtisticColorSelectorPlugin>();)
class ArtisticColorSelectorDockFactory: public KoDockFactoryBase
{
public:
QString id() const override {
return QString("ArtisticColorSelector");
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const {
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override {
ArtisticColorSelectorDock* dockWidget = new ArtisticColorSelectorDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockMinimized;
}
};
ArtisticColorSelectorPlugin::ArtisticColorSelectorPlugin(QObject* parent, const QVariantList &):
QObject(parent)
{
KoDockRegistry::instance()->add(new ArtisticColorSelectorDockFactory());
}
#include "artisticcolorselector_plugin.moc"
diff --git a/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.h b/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.h
index 808acba69c..98e0e2070d 100644
--- a/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.h
+++ b/plugins/dockers/artisticcolorselector/artisticcolorselector_plugin.h
@@ -1,30 +1,31 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 H_ARTISTIC_COLOR_SELECTOR_PLUGIN_H
#define H_ARTISTIC_COLOR_SELECTOR_PLUGIN_H
#include <QObject>
#include <QVariant>
class ArtisticColorSelectorPlugin: public QObject
{
public:
ArtisticColorSelectorPlugin(QObject *parent, const QVariantList &);
};
#endif // H_ARTISTIC_COLOR_SELECTOR_PLUGIN_H
diff --git a/plugins/dockers/artisticcolorselector/kis_arcs_constants.h b/plugins/dockers/artisticcolorselector/kis_arcs_constants.h
index 4a40cbbfb4..c0421db5ef 100644
--- a/plugins/dockers/artisticcolorselector/kis_arcs_constants.h
+++ b/plugins/dockers/artisticcolorselector/kis_arcs_constants.h
@@ -1,54 +1,55 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_ARCS_CONSTANTS_H
#define KIS_ARCS_CONSTANTS_H
#include <QString>
#include <QColor>
static const int MIN_NUM_HUE_PIECES = 1;
static const int MIN_NUM_UI_HUE_PIECES = 2;
static const int MAX_NUM_HUE_PIECES = 48;
static const int MIN_NUM_LIGHT_PIECES = 1;
static const int MIN_NUM_UI_LIGHT_PIECES = 2;
static const int MAX_NUM_LIGHT_PIECES = 30;
static const int MIN_NUM_SATURATION_RINGS = 1;
static const int MAX_NUM_SATURATION_RINGS = 20;
static const int DEFAULT_HUE_STEPS = 12;
static const int DEFAULT_SATURATION_STEPS = 7;
static const int DEFAULT_VALUE_SCALE_STEPS = 11;
static const qreal DEFAULT_LUMA_R = 0.2126;
static const qreal DEFAULT_LUMA_G = 0.7152;
static const qreal DEFAULT_LUMA_B = 0.0722;
static const qreal DEFAULT_LUMA_GAMMA = 2.2;
// color scheme for the selector
static const QColor COLOR_MIDDLE_GRAY = QColor(128,128,128,255);
static const QColor COLOR_DARK = QColor(50,50,50,255);
static const QColor COLOR_LIGHT = QColor(200,200,200,255);
static const QColor COLOR_SELECTED_DARK = QColor(30,30,30,255);
static const QColor COLOR_SELECTED_LIGHT = QColor(220,220,220,255);
static const QColor COLOR_MASK_FILL = COLOR_MIDDLE_GRAY;
static const QColor COLOR_MASK_OUTLINE = COLOR_LIGHT;
static const QColor COLOR_MASK_CLEAR = COLOR_LIGHT;
static const QColor COLOR_NORMAL_OUTLINE = COLOR_MIDDLE_GRAY;
#endif // KIS_ARCS_CONSTANTS_H
diff --git a/plugins/dockers/channeldocker/channeldocker.cpp b/plugins/dockers/channeldocker/channeldocker.cpp
index 0b8b0239b0..cb24487c99 100644
--- a/plugins/dockers/channeldocker/channeldocker.cpp
+++ b/plugins/dockers/channeldocker/channeldocker.cpp
@@ -1,87 +1,88 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "channeldocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "channeldocker_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(ChannelDockerPluginFactory, "krita_channeldocker.json", registerPlugin<ChannelDockerPlugin>();)
class ChannelDockerDockFactory : public KoDockFactoryBase {
public:
ChannelDockerDockFactory()
{
}
QString id() const override
{
return QString( "ChannelDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
ChannelDockerDock * dockWidget = new ChannelDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
ChannelDockerPlugin::ChannelDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new ChannelDockerDockFactory());
}
ChannelDockerPlugin::~ChannelDockerPlugin()
{
}
#include "channeldocker.moc"
diff --git a/plugins/dockers/channeldocker/channeldocker.h b/plugins/dockers/channeldocker/channeldocker.h
index bf374e8302..b68e493228 100644
--- a/plugins/dockers/channeldocker/channeldocker.h
+++ b/plugins/dockers/channeldocker/channeldocker.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 CHANNELDOCKER_H
#define CHANNELDOCKER_H
#include <QObject>
#include <QVariant>
/**
* Docker showing the channels of the current layer
*/
class ChannelDockerPlugin : public QObject
{
Q_OBJECT
public:
ChannelDockerPlugin(QObject *parent, const QVariantList &);
~ChannelDockerPlugin() override;
};
#endif
diff --git a/plugins/dockers/channeldocker/channeldocker_dock.cpp b/plugins/dockers/channeldocker/channeldocker_dock.cpp
index 57f385b1d7..ea692a07ab 100644
--- a/plugins/dockers/channeldocker/channeldocker_dock.cpp
+++ b/plugins/dockers/channeldocker/channeldocker_dock.cpp
@@ -1,126 +1,127 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "channeldocker_dock.h"
#include <QGridLayout>
#include <QTableView>
#include <QHeaderView>
#include <klocalizedstring.h>
#include <KoCanvasBase.h>
#include "channelmodel.h"
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <kis_layer.h>
#include <kis_node_manager.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_device.h>
#include "kis_signal_compressor.h"
#include <KisView.h>
#include <kis_idle_watcher.h>
ChannelDockerDock::ChannelDockerDock( ) :
QDockWidget(i18n("Channels")),
m_imageIdleWatcher(new KisIdleWatcher(250, this)),
m_canvas(0)
{
m_channelTable = new QTableView(this);
m_model = new ChannelModel(this);
m_channelTable->setModel(m_model);
m_channelTable->setShowGrid(false);
m_channelTable->horizontalHeader()->setStretchLastSection(true);
m_channelTable->verticalHeader()->setVisible(false);
m_channelTable->horizontalHeader()->setVisible(false);
m_channelTable->setSelectionBehavior( QAbstractItemView::SelectRows );
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(m_channelTable);
if (scroller){
connect(scroller, SIGNAL(stateChanged(QScroller::State)),
this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
setWidget(m_channelTable);
connect(m_channelTable,&QTableView::activated, m_model, &ChannelModel::rowActivated);
}
void ChannelDockerDock::setCanvas(KoCanvasBase * canvas)
{
if(m_canvas == canvas)
return;
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_canvas->image()->disconnect(this);
}
if (!canvas) {
m_canvas = 0;
return;
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if ( m_canvas && m_canvas->image() ) {
m_model->slotSetCanvas(m_canvas);
KisPaintDeviceSP dev = m_canvas->image()->projection();
m_imageIdleWatcher->setTrackedImage(m_canvas->image());
connect(m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &ChannelDockerDock::updateChannelTable);
connect(dev, SIGNAL(colorSpaceChanged(const KoColorSpace*)), m_model, SLOT(slotColorSpaceChanged(const KoColorSpace*)));
connect(dev, SIGNAL(colorSpaceChanged(const KoColorSpace*)), m_canvas, SLOT(channelSelectionChanged()));
connect(m_model, SIGNAL(channelFlagsChanged()), m_canvas, SLOT(channelSelectionChanged()));
m_imageIdleWatcher->startCountdown();
}
}
void ChannelDockerDock::unsetCanvas()
{
setEnabled(false);
m_canvas = 0;
m_model->unsetCanvas();
}
void ChannelDockerDock::showEvent(QShowEvent *event)
{
Q_UNUSED(event);
m_imageIdleWatcher->startCountdown();
}
void ChannelDockerDock::startUpdateCanvasProjection()
{
m_imageIdleWatcher->startCountdown();
}
void ChannelDockerDock::updateChannelTable()
{
if (isVisible() && m_canvas && m_canvas->image()){
m_model->updateData(m_canvas);
m_channelTable->resizeRowsToContents();
m_channelTable->resizeColumnsToContents();
}
}
diff --git a/plugins/dockers/channeldocker/channeldocker_dock.h b/plugins/dockers/channeldocker/channeldocker_dock.h
index 57ba3531dd..9a295a33db 100644
--- a/plugins/dockers/channeldocker/channeldocker_dock.h
+++ b/plugins/dockers/channeldocker/channeldocker_dock.h
@@ -1,60 +1,61 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 CHANNELDOCKER_DOCK_H
#define CHANNELDOCKER_DOCK_H
#include <QPointer>
#include <QDockWidget>
#include <KoCanvasObserverBase.h>
#include <KisKineticScroller.h>
#include <kis_canvas2.h>
class ChannelModel;
class QTableView;
class KisSignalCompressor;
class KisIdleWatcher;
class ChannelDockerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
ChannelDockerDock();
QString observerName() override { return "ChannelDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
void showEvent(QShowEvent *event) override;
public Q_SLOTS:
void startUpdateCanvasProjection();
void slotScrollerStateChanged(QScroller::State state){KisKineticScroller::updateCursor(this, state);}
private Q_SLOTS:
void updateChannelTable(void);
private:
KisIdleWatcher* m_imageIdleWatcher;
KisSignalCompressor *m_compressor;
QPointer<KisCanvas2> m_canvas;
QTableView *m_channelTable;
ChannelModel *m_model;
};
#endif
diff --git a/plugins/dockers/channeldocker/channelmodel.cpp b/plugins/dockers/channeldocker/channelmodel.cpp
index 9b0f9b4f0b..23fa4bdcc4 100644
--- a/plugins/dockers/channeldocker/channelmodel.cpp
+++ b/plugins/dockers/channeldocker/channelmodel.cpp
@@ -1,263 +1,264 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "channelmodel.h"
#include <QImage>
#include <KoColorSpace.h>
#include <KoChannelInfo.h>
#include <kis_painter.h>
#include <kis_group_layer.h>
#include <kis_paint_device.h>
#include <kis_iterator_ng.h>
#include <kis_default_bounds.h>
#include <kis_canvas2.h>
ChannelModel::ChannelModel(QObject* parent):
QAbstractTableModel(parent),
m_canvas(nullptr), m_oversampleRatio(2), m_channelCount(0)
{
setThumbnailSizeLimit(QSize(64, 64));
}
ChannelModel::~ChannelModel()
{
}
QVariant ChannelModel::data(const QModelIndex& index, int role) const
{
if (m_canvas && index.isValid()) {
KisGroupLayerSP rootLayer = m_canvas->image()->rootLayer();
const KoColorSpace* cs = rootLayer->colorSpace();
QList<KoChannelInfo*> channels = cs->channels();
int channelIndex = index.row();
switch (role) {
case Qt::DisplayRole: {
if (index.column() == 2) {
return channels.at(channelIndex)->name();
}
return QVariant();
}
case Qt::DecorationRole: {
if (index.column() == 1) {
Q_ASSERT(m_thumbnails.count() > index.row());
return QVariant(m_thumbnails.at(index.row()));
}
return QVariant();
}
case Qt::CheckStateRole: {
Q_ASSERT(index.row() < rowCount());
Q_ASSERT(index.column() < columnCount());
if (index.column() == 0) {
QBitArray flags = rootLayer->channelFlags();
return (flags.isEmpty() || flags.testBit(channelIndex)) ? Qt::Checked : Qt::Unchecked;
}
return QVariant();
}
}
}
return QVariant();
}
QVariant ChannelModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(section); Q_UNUSED(orientation); Q_UNUSED(role);
return QVariant();
}
int ChannelModel::rowCount(const QModelIndex& /*parent*/) const
{
if (!m_canvas) return 0;
return m_channelCount;
}
int ChannelModel::columnCount(const QModelIndex& /*parent*/) const
{
if (!m_canvas) return 0;
//columns are: checkbox, thumbnail, channel name
return 3;
}
bool ChannelModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (m_canvas && m_canvas->image()) {
KisGroupLayerSP rootLayer = m_canvas->image()->rootLayer();
const KoColorSpace* cs = rootLayer->colorSpace();
QList<KoChannelInfo*> channels = cs->channels();
Q_ASSERT(index.row() <= channels.count());
int channelIndex = index.row();
if (role == Qt::CheckStateRole) {
QBitArray flags = rootLayer->channelFlags();
flags = flags.isEmpty() ? cs->channelFlags(true, true) : flags;
Q_ASSERT(!flags.isEmpty());
flags.setBit(channelIndex, value.toInt() == Qt::Checked);
rootLayer->setChannelFlags(flags);
emit channelFlagsChanged();
emit dataChanged(this->index(0, 0), this->index(channels.count(), 0));
return true;
}
}
return false;
}
//User double clicked on a row (but on channel checkbox)
//we select this channel, and deselect all other channels (except alpha, which we don't touch)
//this makes it fast to select single color channel
void ChannelModel::rowActivated(const QModelIndex &index)
{
if (m_canvas && m_canvas->image()) {
KisGroupLayerWSP rootLayer = m_canvas->image()->rootLayer();
const KoColorSpace* cs = rootLayer->colorSpace();
QList<KoChannelInfo*> channels = cs->channels();
Q_ASSERT(index.row() <= channels.count());
int channelIndex = index.row();
QBitArray flags = rootLayer->channelFlags();
flags = flags.isEmpty() ? cs->channelFlags(true, true) : flags;
Q_ASSERT(!flags.isEmpty());
for (int i = 0; i < channels.count(); ++i) {
if (channels[i]->channelType() != KoChannelInfo::ALPHA) {
flags.setBit(i, (i == channelIndex));
}
}
rootLayer->setChannelFlags(flags);
emit channelFlagsChanged();
emit dataChanged(this->index(0, 0), this->index(channels.count(), 0));
}
}
Qt::ItemFlags ChannelModel::flags(const QModelIndex& /*index*/) const
{
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
return flags;
}
void ChannelModel::unsetCanvas()
{
m_canvas = 0;
}
void ChannelModel::setThumbnailSizeLimit(QSize size)
{
m_thumbnailSizeLimit = size;
updateData(m_canvas);
}
void ChannelModel::slotSetCanvas(KisCanvas2 *canvas)
{
if (m_canvas != canvas) {
beginResetModel();
m_canvas = canvas;
if (m_canvas && m_canvas->image()) {
m_channelCount = m_canvas->image()->colorSpace()->channelCount();
updateThumbnails();
} else {
m_channelCount = 0;
}
endResetModel();
}
}
void ChannelModel::slotColorSpaceChanged(const KoColorSpace *colorSpace)
{
Q_UNUSED(colorSpace);
beginResetModel();
updateThumbnails();
endResetModel();
}
void ChannelModel::updateData(KisCanvas2 *canvas)
{
beginResetModel();
m_canvas = canvas;
m_channelCount = (m_canvas) ? m_canvas->image()->colorSpace()->channelCount() : 0;
updateThumbnails();
endResetModel();
}
//Create thumbnails from full image.
//Assumptions: thumbnail size is small compared to the original image and thumbnail quality
//doesn't need to be high, so we use fast but not very accurate algorithm.
void ChannelModel::updateThumbnails(void)
{
if (m_canvas && m_canvas->image()) {
KisImageSP canvas_image = m_canvas->image();
const KoColorSpace* cs = canvas_image->colorSpace();
m_channelCount = cs->channelCount();
KisPaintDeviceSP dev = canvas_image->projection();
//make sure thumbnail maintains aspect ratio of the original image
QSize thumbnailSize(canvas_image->bounds().size());
thumbnailSize.scale(m_thumbnailSizeLimit, Qt::KeepAspectRatio);
KisPaintDeviceSP thumbnailDev = dev->createThumbnailDeviceOversampled(thumbnailSize.width(), thumbnailSize.height(),
m_oversampleRatio, canvas_image->bounds());
m_thumbnails.resize(m_channelCount);
for (quint32 i = 0; i < (quint32)m_channelCount; ++i) {
#if QT_VERSION >= 0x050500
m_thumbnails[i] = QImage(thumbnailSize, QImage::Format_Grayscale8);
#else
m_thumbnails[i] = QImage(thumbnailSize, QImage::Format_ARGB32_Premultiplied);
#endif
}
KisSequentialConstIterator it(thumbnailDev, QRect(0, 0, thumbnailSize.width(), thumbnailSize.height()));
for (int y = 0; y < thumbnailSize.height(); y++) {
for (int x = 0; x < thumbnailSize.width(); x++) {
it.nextPixel();
const quint8* pixel = it.rawDataConst();
for (int chan = 0; chan < m_channelCount; ++chan) {
QImage &img = m_thumbnails[chan];
#if QT_VERSION >= 0x050500
*(img.scanLine(y) + x) = cs->scaleToU8(pixel, chan);
#else
quint8 v = cs->scaleToU8(pixel, chan);
img.setPixel(x, y, qRgb(v, v, v));
#endif
}
}
}
} else {
m_channelCount = 0;
}
}
#include "moc_channelmodel.cpp"
diff --git a/plugins/dockers/channeldocker/channelmodel.h b/plugins/dockers/channeldocker/channelmodel.h
index a05b73c068..992ae0a8e4 100644
--- a/plugins/dockers/channeldocker/channelmodel.h
+++ b/plugins/dockers/channeldocker/channelmodel.h
@@ -1,70 +1,71 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 CHANNELMODEL_H
#define CHANNELMODEL_H
#include <QModelIndex>
#include <QSize>
#include <QPointer>
#include <kis_canvas2.h>
#include <kis_types.h>
class KoColorSpace;
class ChannelModel : public QAbstractTableModel
{
Q_OBJECT
public:
ChannelModel(QObject* parent = 0);
~ChannelModel() override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
void unsetCanvas( void );
//set maximum size of the thumbnail image. This should be set based on screen resolution, etc.
void setThumbnailSizeLimit(QSize size);
public Q_SLOTS:
void slotSetCanvas(KisCanvas2* canvas);
void slotColorSpaceChanged(const KoColorSpace *colorSpace);
void updateData(KisCanvas2 *canvas);
void rowActivated(const QModelIndex &index);
Q_SIGNALS:
void channelFlagsChanged();
private:
void updateThumbnails();
private:
QPointer<KisCanvas2> m_canvas;
QVector<QImage> m_thumbnails;
QSize m_thumbnailSizeLimit;
int m_oversampleRatio;
int m_channelCount;
};
#endif // CHANNELMODEL_H
diff --git a/plugins/dockers/compositiondocker/compositiondocker.cpp b/plugins/dockers/compositiondocker/compositiondocker.cpp
index acd1324500..afa79677b6 100644
--- a/plugins/dockers/compositiondocker/compositiondocker.cpp
+++ b/plugins/dockers/compositiondocker/compositiondocker.cpp
@@ -1,86 +1,87 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "compositiondocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "compositiondocker_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(CompositionDockerPluginFactory, "krita_compositiondocker.json", registerPlugin<CompositionDockerPlugin>();)
class CompositionDockerDockFactory : public KoDockFactoryBase {
public:
CompositionDockerDockFactory()
{
}
QString id() const override
{
return QString( "CompositionDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
CompositionDockerDock * dockWidget = new CompositionDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
CompositionDockerPlugin::CompositionDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new CompositionDockerDockFactory());
}
CompositionDockerPlugin::~CompositionDockerPlugin()
{
}
#include "compositiondocker.moc"
diff --git a/plugins/dockers/compositiondocker/compositiondocker.h b/plugins/dockers/compositiondocker/compositiondocker.h
index d715d38316..247132944d 100644
--- a/plugins/dockers/compositiondocker/compositiondocker.h
+++ b/plugins/dockers/compositiondocker/compositiondocker.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 COMPOSITIONDOCKER_H
#define COMPOSITIONDOCKER_H
#include <QObject>
#include <QVariant>
/**
* Docker compositions of the image
*/
class CompositionDockerPlugin : public QObject
{
Q_OBJECT
public:
CompositionDockerPlugin(QObject *parent, const QVariantList &);
~CompositionDockerPlugin() override;
};
#endif
diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp
index 7b7eaacfa7..bfa840a8b7 100644
--- a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp
+++ b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp
@@ -1,309 +1,310 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "compositiondocker_dock.h"
#include <QGridLayout>
#include <QListView>
#include <QHeaderView>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QInputDialog>
#include <QThread>
#include <QAction>
#include <QStandardPaths>
#include <QMenu>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <kis_icon.h>
#include <KoCanvasBase.h>
#include <KoFileDialog.h>
#include <KisPart.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <KisKineticScroller.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_painter.h>
#include <kis_paint_layer.h>
#include <kis_action.h>
#include <kis_action_manager.h>
#include <kis_action_registry.h>
#include "compositionmodel.h"
CompositionDockerDock::CompositionDockerDock( ) : QDockWidget(i18n("Compositions")), m_canvas(0)
{
QWidget* widget = new QWidget(this);
setupUi(widget);
m_model = new CompositionModel(this);
compositionView->setModel(m_model);
compositionView->installEventFilter(this);
deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
saveButton->setIcon(KisIconUtils::loadIcon("list-add"));
exportButton->setIcon(KisIconUtils::loadIcon("document-export"));
deleteButton->setToolTip(i18n("Delete Composition"));
saveButton->setToolTip(i18n("New Composition"));
exportButton->setToolTip(i18n("Export Composition"));
setWidget(widget);
connect( compositionView, SIGNAL(doubleClicked(QModelIndex)),
this, SLOT(activated(QModelIndex)) );
compositionView->setContextMenuPolicy(Qt::CustomContextMenu);
connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(customContextMenuRequested(QPoint)));
connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked()));
connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked()));
connect( exportButton, SIGNAL(clicked(bool)), this, SLOT(exportClicked()));
saveNameEdit->setPlaceholderText(i18n("Insert Name"));
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(compositionView);
if (scroller) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
}
CompositionDockerDock::~CompositionDockerDock()
{
}
void CompositionDockerDock::setCanvas(KoCanvasBase * canvas)
{
if (m_canvas && m_canvas->viewManager()) {
Q_FOREACH (KisAction *action, m_actions) {
m_canvas->viewManager()->actionManager()->takeAction(action);
}
}
unsetCanvas();
setEnabled(canvas != 0);
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas && m_canvas->viewManager()) {
if (m_actions.isEmpty()) {
KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition");
connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition()));
m_actions.append(updateAction);
KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition");
connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition()));
m_actions.append(renameAction);
} else {
Q_FOREACH (KisAction *action, m_actions) {
m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action);
}
}
updateModel();
}
}
void CompositionDockerDock::unsetCanvas()
{
setEnabled(false);
m_canvas = 0;
m_model->setCompositions(QList<KisLayerCompositionSP>());
}
void CompositionDockerDock::activated(const QModelIndex& index)
{
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
composition->apply();
}
void CompositionDockerDock::deleteClicked()
{
QModelIndex index = compositionView->currentIndex();
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
m_canvas->viewManager()->image()->removeComposition(composition);
updateModel();
}
}
void CompositionDockerDock::saveClicked()
{
KisImageWSP image = m_canvas->viewManager()->image();
if (!image) return;
// format as 001, 002 ...
QString name = saveNameEdit->text();
if (name.isEmpty()) {
bool found = false;
int i = 1;
do {
name = QString("%1").arg(i, 3, 10, QChar('0'));
found = false;
Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) {
if (composition->name() == name) {
found = true;
break;
}
}
i++;
} while(found && i < 1000);
}
KisLayerCompositionSP composition(new KisLayerComposition(image, name));
composition->store();
image->addComposition(composition);
saveNameEdit->clear();
updateModel();
compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0));
image->setModified();
}
void CompositionDockerDock::updateModel()
{
if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
m_model->setCompositions(m_canvas->viewManager()->image()->compositions());
}
}
void CompositionDockerDock::exportClicked()
{
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
QString path;
KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
dialog.setCaption(i18n("Select a Directory"));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
path = dialog.filename();
if (path.isNull()) return;
if (!path.endsWith('/')) {
path.append('/');
}
KisImageWSP image = m_canvas->viewManager()->image();
QString filename = m_canvas->viewManager()->document()->localFilePath();
if (!filename.isEmpty()) {
QFileInfo info(filename);
path += info.baseName() + '_';
}
Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) {
if (!composition->isExportEnabled()) {
continue;
}
composition->apply();
image->refreshGraph();
image->lock();
#if 0
image->rootLayer()->projection()->convertToQImage(0, 0, 0, image->width(), image->height()).save(path + composition->name() + ".png");
#else
QRect r = image->bounds();
KisDocument *d = KisPart::instance()->createDocument();
KisImageSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name());
dst->setResolution(image->xRes(), image->yRes());
d->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8);
KisPainter gc(paintLayer->paintDevice());
gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->refreshGraph();
d->setFileBatchMode(true);
const QByteArray outputFormat("image/png");
d->exportDocumentSync(QUrl::fromLocalFile(path + composition->name() + ".png"), outputFormat);
delete d;
#endif
image->unlock();
}
}
}
bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::KeyPress ) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) {
// new index will be set after the method is called
QTimer::singleShot(0, this, SLOT(activateCurrentIndex()));
}
return false;
} else {
return QObject::eventFilter(obj, event);
}
}
void CompositionDockerDock::activateCurrentIndex()
{
QModelIndex index = compositionView->currentIndex();
if (index.isValid()) {
activated(index);
}
}
void CompositionDockerDock::customContextMenuRequested(QPoint pos)
{
if (m_actions.isEmpty()) return;
QMenu menu;
Q_FOREACH (KisAction *action, m_actions) {
menu.addAction(action);
}
menu.exec(compositionView->mapToGlobal(pos));
}
void CompositionDockerDock::updateComposition()
{
QModelIndex index = compositionView->currentIndex();
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
composition->store();
m_canvas->image()->setModified();
}
}
void CompositionDockerDock::renameComposition()
{
dbgKrita << "rename";
QModelIndex index = compositionView->currentIndex();
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
bool ok;
QString name = QInputDialog::getText(this, i18n("Rename Composition"),
i18n("New Name:"), QLineEdit::Normal,
composition->name(), &ok);
if (ok && !name.isEmpty()) {
composition->setName(name);
m_canvas->image()->setModified();
}
}
}
diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.h b/plugins/dockers/compositiondocker/compositiondocker_dock.h
index 853fe0ab99..41bcbe33db 100644
--- a/plugins/dockers/compositiondocker/compositiondocker_dock.h
+++ b/plugins/dockers/compositiondocker/compositiondocker_dock.h
@@ -1,70 +1,71 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 COMPOSITIONDOCKER_DOCK_H
#define COMPOSITIONDOCKER_DOCK_H
#include <QDockWidget>
#include <QModelIndex>
#include <QPointer>
#include <KoCanvasObserverBase.h>
#include <kis_canvas2.h>
#include "ui_wdgcompositiondocker.h"
#include <KisKineticScroller.h>
class CompositionModel;
class KisAction;
class CompositionDockerDock : public QDockWidget, public KoCanvasObserverBase, public Ui_WdgCompositionDocker {
Q_OBJECT
public:
CompositionDockerDock();
~CompositionDockerDock() override;
QString observerName() override { return "CompositionDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
void updateModel();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
public Q_SLOTS:
void slotScrollerStateChanged(QScroller::State state){ KisKineticScroller::updateCursor(this, state); }
private Q_SLOTS:
void activated (const QModelIndex& index);
void deleteClicked();
void saveClicked();
void exportClicked();
void activateCurrentIndex();
void customContextMenuRequested(QPoint pos);
void updateComposition();
void renameComposition();
private:
QPointer<KisCanvas2> m_canvas;
CompositionModel *m_model;
QVector<KisAction*> m_actions;
};
#endif
diff --git a/plugins/dockers/compositiondocker/compositionmodel.cpp b/plugins/dockers/compositiondocker/compositionmodel.cpp
index b8561b4aef..2a9f686a25 100644
--- a/plugins/dockers/compositiondocker/compositionmodel.cpp
+++ b/plugins/dockers/compositiondocker/compositionmodel.cpp
@@ -1,110 +1,111 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "compositionmodel.h"
#include <kis_icon.h>
#include <QAction>
#include <klocalizedstring.h>
CompositionModel::CompositionModel(QObject* parent): QAbstractTableModel(parent)
{
}
CompositionModel::~CompositionModel()
{
}
QVariant CompositionModel::data(const QModelIndex& index, int role) const
{
if (index.isValid()) {
switch (role) {
case Qt::DisplayRole:
{
return m_compositions.at(index.row())->name();
}
case Qt::DecorationRole:
{
return KisIconUtils::loadIcon("tools-wizard");
}
case Qt::CheckStateRole: {
return m_compositions.at(index.row())->isExportEnabled() ? Qt::Checked : Qt::Unchecked;
}
}
}
return QVariant();
}
bool CompositionModel::setData ( const QModelIndex& index, const QVariant& value, int role )
{
if (index.isValid()) {
if (role == Qt::CheckStateRole) {
Q_ASSERT(index.row() < rowCount());
Q_ASSERT(index.column() < columnCount());
if (index.column() == 0) {
bool exportEnabled = value.toInt() == Qt::Checked;
KisLayerCompositionSP layerComposition = m_compositions.at(index.row());
if (layerComposition) {
layerComposition->setExportEnabled(exportEnabled);
}
}
}
return true;
}
return false;
}
QVariant CompositionModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const
{
return i18n("Composition");
}
int CompositionModel::rowCount(const QModelIndex& /*parent*/) const
{
return m_compositions.count();
}
int CompositionModel::columnCount(const QModelIndex& /*parent*/) const
{
return 2;
}
Qt::ItemFlags CompositionModel::flags(const QModelIndex& /*index*/) const
{
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
return flags;
}
KisLayerCompositionSP CompositionModel::compositionFromIndex(const QModelIndex& index)
{
if(index.isValid()) {
return m_compositions.at(index.row());
}
return KisLayerCompositionSP();
}
void CompositionModel::setCompositions(QList< KisLayerCompositionSP > compositions)
{
m_compositions = compositions;
beginResetModel();
endResetModel();
}
diff --git a/plugins/dockers/compositiondocker/compositionmodel.h b/plugins/dockers/compositiondocker/compositionmodel.h
index 637b6f8123..51061efb5c 100644
--- a/plugins/dockers/compositiondocker/compositionmodel.h
+++ b/plugins/dockers/compositiondocker/compositionmodel.h
@@ -1,49 +1,50 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 COMPOSITIONMODEL_H
#define COMPOSITIONMODEL_H
#include <QModelIndex>
#include <kis_types.h>
#include <kis_layer_composition.h>
class CompositionModel : public QAbstractTableModel
{
Q_OBJECT
public:
CompositionModel(QObject* parent = 0);
~CompositionModel() override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
bool setData ( const QModelIndex& index, const QVariant& value, int role = Qt::EditRole ) override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
KisLayerCompositionSP compositionFromIndex(const QModelIndex& index);
void setCompositions(QList<KisLayerCompositionSP> compositions);
// public Q_SLOTS:
// void clear();
private:
QList<KisLayerCompositionSP> m_compositions;
};
#endif // TASKSETMODEL_H
diff --git a/plugins/dockers/digitalmixer/digitalmixer.cc b/plugins/dockers/digitalmixer/digitalmixer.cc
index 743b60ac99..0ba5eac3bc 100644
--- a/plugins/dockers/digitalmixer/digitalmixer.cc
+++ b/plugins/dockers/digitalmixer/digitalmixer.cc
@@ -1,88 +1,89 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "digitalmixer.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "digitalmixer_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(DigitalMixerPluginFactory, "krita_digitalmixer.json", registerPlugin<DigitalMixerPlugin>();)
class DigitalMixerDockFactory : public KoDockFactoryBase {
public:
DigitalMixerDockFactory()
{
}
QString id() const override
{
return QString( "DigitalMixer" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
DigitalMixerDock * dockWidget = new DigitalMixerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
DigitalMixerPlugin::DigitalMixerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new DigitalMixerDockFactory());
}
DigitalMixerPlugin::~DigitalMixerPlugin()
{
m_view = 0;
}
#include "digitalmixer.moc"
diff --git a/plugins/dockers/digitalmixer/digitalmixer.h b/plugins/dockers/digitalmixer/digitalmixer.h
index c5b0510c40..8e0dbe726c 100644
--- a/plugins/dockers/digitalmixer/digitalmixer.h
+++ b/plugins/dockers/digitalmixer/digitalmixer.h
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _DIGITALMIXER_H_
#define _DIGITALMIXER_H_
#include <QObject>
#include <QVariant>
class KisViewManager;
/**
* Template of view plugin
*/
class DigitalMixerPlugin : public QObject
{
Q_OBJECT
public:
DigitalMixerPlugin(QObject *parent, const QVariantList &);
~DigitalMixerPlugin() override;
private:
KisViewManager* m_view;
};
#endif
diff --git a/plugins/dockers/digitalmixer/digitalmixer_dock.cc b/plugins/dockers/digitalmixer/digitalmixer_dock.cc
index eb8b955984..19f8c25b5e 100644
--- a/plugins/dockers/digitalmixer/digitalmixer_dock.cc
+++ b/plugins/dockers/digitalmixer/digitalmixer_dock.cc
@@ -1,167 +1,168 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "digitalmixer_dock.h"
#include <QGridLayout>
#include <QToolButton>
#include <QSignalMapper>
#include <klocalizedstring.h>
#include <KoColorPatch.h>
#include <KoColorSlider.h>
#include <KoColorPopupAction.h>
#include <KoColorSpaceRegistry.h>
#include <KoCanvasResourceProvider.h>
#include <KoCanvasBase.h>
#include <kis_color_button.h>
class DigitalMixerPatch : public KoColorPatch {
public:
DigitalMixerPatch(QWidget* parent) : KoColorPatch(parent) {}
QSize sizeHint() const override
{
return QSize(24,24);
}
};
DigitalMixerDock::DigitalMixerDock( )
: QDockWidget(i18n("Digital Colors Mixer")), m_canvas(0)
, m_tellCanvas(true)
{
const KoColorSpace *sRGB = KoColorSpaceRegistry::instance()->rgb8();
KoColor initColors[6] = { KoColor(Qt::black, sRGB),
KoColor(Qt::white, sRGB),
KoColor(Qt::red, sRGB),
KoColor(Qt::green, sRGB),
KoColor(Qt::blue, sRGB),
KoColor(Qt::yellow, sRGB) };
QWidget* widget = new QWidget(this);
QGridLayout* layout = new QGridLayout( widget );
// Current Color
m_currentColorPatch = new KoColorPatch(this);
m_currentColorPatch->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_currentColorPatch->setMinimumWidth(48);
layout->addWidget(m_currentColorPatch, 0, 0,3,1);
// Create the sliders
QSignalMapper* signalMapperSelectColor = new QSignalMapper(this);
connect(signalMapperSelectColor, SIGNAL(mapped(int)), SLOT(popupColorChanged(int)));
QSignalMapper* signalMapperColorSlider = new QSignalMapper(this);
connect(signalMapperColorSlider, SIGNAL(mapped(int)), SLOT(colorSliderChanged(int)));
QSignalMapper* signalMapperTargetColor = new QSignalMapper(this);
connect(signalMapperTargetColor, SIGNAL(mapped(int)), SLOT(targetColorChanged(int)));
for(int i = 0; i < 6; ++i)
{
Mixer mixer;
mixer.targetColor = new DigitalMixerPatch(this);
mixer.targetColor->setFixedSize(32, 22);
layout->addWidget(mixer.targetColor, 0, i + 1);
mixer.targetSlider = new KoColorSlider(Qt::Vertical, this);
mixer.targetSlider->setFixedWidth(22);
mixer.targetSlider->setMinimumHeight(66);
layout->addWidget(mixer.targetSlider, 1, i + 1);
mixer.actionColor = new KisColorButton( this );
mixer.actionColor->setColor(initColors[i]);
mixer.actionColor->setFixedWidth(22);
layout->addWidget(mixer.actionColor, 2, i + 1);
m_mixers.push_back(mixer);
connect(mixer.actionColor, SIGNAL(changed(KoColor)), signalMapperSelectColor, SLOT(map()));
signalMapperSelectColor->setMapping(mixer.actionColor, i);
connect(mixer.targetSlider, SIGNAL(valueChanged(int)), signalMapperColorSlider, SLOT(map()));
signalMapperColorSlider->setMapping(mixer.targetSlider, i);
mixer.targetSlider->setValue(125);
connect(mixer.targetColor, SIGNAL(triggered(KoColorPatch*)), signalMapperTargetColor, SLOT(map()));
signalMapperTargetColor->setMapping(mixer.targetColor, i);
}
setCurrentColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()));
setWidget( widget );
}
void DigitalMixerDock::setCanvas(KoCanvasBase * canvas)
{
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
}
m_canvas = canvas;
if (m_canvas) {
connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(canvasResourceChanged(int,QVariant)));
m_tellCanvas=false;
setCurrentColor(m_canvas->resourceManager()->foregroundColor());
m_tellCanvas=true;
}
}
void DigitalMixerDock::popupColorChanged(int i)
{
KoColor color = m_mixers[i].actionColor->color();
color.convertTo(m_currentColor.colorSpace());
m_mixers[i].targetSlider->setColors( color, m_currentColor);
colorSliderChanged(i);
}
void DigitalMixerDock::colorSliderChanged(int i)
{
m_mixers[i].targetColor->setColor(m_mixers[i].targetSlider->currentColor());
}
void DigitalMixerDock::targetColorChanged(int i)
{
setCurrentColor(m_mixers[i].targetColor->color());
}
void DigitalMixerDock::setCurrentColor(const KoColor& color)
{
m_currentColor = color;
m_currentColorPatch->setColor(color);
for(int i = 0; i < m_mixers.size(); ++i)
{
popupColorChanged(i);
colorSliderChanged(i);
}
if (m_canvas && m_tellCanvas)
{
m_canvas->resourceManager()->setForegroundColor(m_currentColor);
}
}
void DigitalMixerDock::canvasResourceChanged(int key, const QVariant& v)
{
m_tellCanvas = false;
if (key == KoCanvasResourceProvider::ForegroundColor)
setCurrentColor(v.value<KoColor>());
m_tellCanvas = true;
}
diff --git a/plugins/dockers/digitalmixer/digitalmixer_dock.h b/plugins/dockers/digitalmixer/digitalmixer_dock.h
index 251d83fa51..9552f7820b 100644
--- a/plugins/dockers/digitalmixer/digitalmixer_dock.h
+++ b/plugins/dockers/digitalmixer/digitalmixer_dock.h
@@ -1,63 +1,64 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _DIGITALMIXER_DOCK_H_
#define _DIGITALMIXER_DOCK_H_
#include <QPointer>
#include <QDockWidget>
#include <KoColor.h>
#include <KoCanvasObserverBase.h>
#include <KoCanvasBase.h>
class KoColorPopupAction;
class KoColorSlider;
class KoColorPatch;
class KisColorButton;
class DigitalMixerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
DigitalMixerDock( );
QString observerName() override { return "DigitalMixerDock"; }
/// reimplemented from KoCanvasObserverBase
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override { m_canvas = 0; setEnabled(false);}
public Q_SLOTS:
void setCurrentColor(const KoColor& );
void canvasResourceChanged(int, const QVariant&);
private Q_SLOTS:
void popupColorChanged(int i);
void colorSliderChanged(int i);
void targetColorChanged(int);
private:
QPointer<KoCanvasBase> m_canvas;
KoColor m_currentColor;
KoColorPatch* m_currentColorPatch;
struct Mixer {
KoColorPatch* targetColor;
KoColorSlider* targetSlider;
KisColorButton* actionColor;
};
QList<Mixer> m_mixers;
bool m_tellCanvas;
};
#endif
diff --git a/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp b/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp
index 19cf385cc7..f0dd21399a 100644
--- a/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp
+++ b/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp
@@ -1,251 +1,252 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 "KisGamutMaskChooser.h"
#include <QWidget>
#include <QVBoxLayout>
#include <QAbstractItemDelegate>
#include <QMenu>
#include <QActionGroup>
#include <QToolButton>
#include <QFontMetrics>
#include <QTextDocument>
#include <QTextLayout>
#include <KoResourceServer.h>
#include <KoResourceServerProvider.h>
#include <KoResourceItemChooser.h>
#include <KoResourceServerAdapter.h>
#include <kis_icon_utils.h>
#include <kis_config.h>
/// The resource item delegate for rendering the resource preview
class KisGamutMaskDelegate: public QAbstractItemDelegate
{
public:
KisGamutMaskDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent)
, m_mode(KisGamutMaskChooser::ViewMode::THUMBNAIL) {}
~KisGamutMaskDelegate() override {}
/// reimplemented
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
/// reimplemented
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override {
return option.decorationSize;
}
void setViewMode(KisGamutMaskChooser::ViewMode mode) {
m_mode = mode;
}
private:
KisGamutMaskChooser::ViewMode m_mode;
};
void KisGamutMaskDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
if (!index.isValid())
return;
KoResource* resource = static_cast<KoResource*>(index.internalPointer());
KoGamutMask* mask = static_cast<KoGamutMask*>(resource);
if (!mask) {
return;
}
QImage preview = mask->image();
if(preview.isNull()) {
return;
}
QRect paintRect = option.rect.adjusted(1, 1, -1, -1);
if (m_mode == KisGamutMaskChooser::ViewMode::THUMBNAIL) {
painter->drawImage(paintRect.x(), paintRect.y(),
preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
if (option.state & QStyle::State_Selected) {
painter->setCompositionMode(QPainter::CompositionMode_Overlay);
painter->setOpacity(0.5);
painter->fillRect(paintRect, Qt::white);
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setOpacity(1.0);
painter->setPen(QPen(option.palette.highlight(), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
QRect selectedBorder = option.rect.adjusted(1, 1, -1, -1);
painter->drawRect(selectedBorder);
}
} else {
QSize previewSize(paintRect.height(), paintRect.height());
painter->drawImage(paintRect.x(), paintRect.y(),
preview.scaled(previewSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
int leftMargin = 8;
int rightMargin = 7;
int vertMargin = 4;
int descOffset = 7;
QFont font = option.font;
font.setBold(true);
painter->setFont(font);
QRectF titleRect(QPointF(previewSize.width() + leftMargin, paintRect.y() + vertMargin),
QPointF(paintRect.width() - rightMargin, paintRect.y() + descOffset + painter->fontMetrics().lineSpacing()));
painter->drawText(titleRect, Qt::AlignLeft,
painter->fontMetrics().elidedText(
mask->title(), Qt::ElideRight, titleRect.width()
)
);
if (!mask->description().isEmpty() && !mask->description().isNull()) {
font.setPointSize(font.pointSize()-1);
font.setBold(false);
font.setStyle(QFont::StyleItalic);
painter->setFont(font);
QRectF descRect(QPointF(previewSize.width() + leftMargin, paintRect.y() + descOffset + painter->fontMetrics().lineSpacing()),
QPointF(paintRect.right() - rightMargin, paintRect.bottom() - vertMargin));
int numLines = floor(((float)descRect.height() / (float)painter->fontMetrics().lineSpacing()));
if (numLines > 0) {
int elideWidth = 0;
QTextLayout textLayout(mask->description());
textLayout.beginLayout();
for (int i = 0; i < numLines; i++) {
QTextLine line = textLayout.createLine();
if (line.isValid()) {
line.setLineWidth(descRect.width());
elideWidth += line.naturalTextWidth();
}
}
QString elidedText = painter->fontMetrics().elidedText(mask->description(), Qt::ElideRight, elideWidth);
painter->drawText(descRect, Qt::AlignLeft|Qt::TextWordWrap, elidedText);
}
}
}
painter->restore();
}
KisGamutMaskChooser::KisGamutMaskChooser(QWidget *parent) : QWidget(parent)
{
m_delegate = new KisGamutMaskDelegate(this);
KoResourceServer<KoGamutMask>* rServer = KoResourceServerProvider::instance()->gamutMaskServer();
QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<KoGamutMask>(rServer));
m_itemChooser = new KoResourceItemChooser(adapter, this);
m_itemChooser->setItemDelegate(m_delegate);
m_itemChooser->showTaggingBar(true);
m_itemChooser->showButtons(false);
m_itemChooser->setColumnCount(4);
m_itemChooser->setSynced(true);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0,0,0,0);
// TODO: menu for view mode change
QMenu* menu = new QMenu(this);
menu->setStyleSheet("margin: 6px");
menu->addSection(i18n("Display"));
QActionGroup *actionGroup = new QActionGroup(this);
KisConfig cfg(true);
m_mode = KisGamutMaskChooser::ViewMode(cfg.readEntry<quint32>("GamutMasks.viewMode", KisGamutMaskChooser::THUMBNAIL));
QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"),
i18n("Thumbnails"), this, SLOT(slotSetModeThumbnail()));
action->setCheckable(true);
action->setChecked(m_mode == KisGamutMaskChooser::THUMBNAIL);
action->setActionGroup(actionGroup);
action = menu->addAction(KisIconUtils::loadIcon("view-list-details"),
i18n("Details"), this, SLOT(slotSetModeDetail()));
action->setCheckable(true);
action->setChecked(m_mode == KisGamutMaskChooser::DETAIL);
action->setActionGroup(actionGroup);
// setting the view mode
setViewMode(m_mode);
m_itemChooser->setViewModeButtonVisible(true);
QToolButton* viewModeButton = m_itemChooser->viewModeButton();
viewModeButton->setMenu(menu);
layout->addWidget(m_itemChooser);
setLayout(layout);
connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*)));
}
KisGamutMaskChooser::~KisGamutMaskChooser()
{
}
void KisGamutMaskChooser::setCurrentResource(KoResource *resource)
{
m_itemChooser->setCurrentResource(resource);
}
void KisGamutMaskChooser::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateViewSettings();
}
void KisGamutMaskChooser::setViewMode(KisGamutMaskChooser::ViewMode mode)
{
m_mode = mode;
updateViewSettings();
}
void KisGamutMaskChooser::updateViewSettings()
{
KisConfig cfg(false);
cfg.writeEntry("GamutMasks.viewMode", qint32(m_mode));
if (m_mode == KisGamutMaskChooser::THUMBNAIL) {
m_itemChooser->setSynced(true);
m_delegate->setViewMode(m_mode);
} else if (m_mode == KisGamutMaskChooser::DETAIL) {
m_itemChooser->setSynced(false);
m_itemChooser->setColumnCount(1);
m_itemChooser->setRowHeight(this->fontMetrics().lineSpacing()*4);
m_itemChooser->setColumnWidth(m_itemChooser->width());
m_delegate->setViewMode(m_mode);
}
}
void KisGamutMaskChooser::resourceSelected(KoResource* resource)
{
emit sigGamutMaskSelected(static_cast<KoGamutMask*>(resource));
}
void KisGamutMaskChooser::slotSetModeThumbnail()
{
setViewMode(KisGamutMaskChooser::THUMBNAIL);
}
void KisGamutMaskChooser::slotSetModeDetail()
{
setViewMode(KisGamutMaskChooser::DETAIL);
}
diff --git a/plugins/dockers/gamutmask/KisGamutMaskChooser.h b/plugins/dockers/gamutmask/KisGamutMaskChooser.h
index 7ed187b96c..56865508e8 100644
--- a/plugins/dockers/gamutmask/KisGamutMaskChooser.h
+++ b/plugins/dockers/gamutmask/KisGamutMaskChooser.h
@@ -1,61 +1,63 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 KISGAMUTMASKCHOOSER_H
#define KISGAMUTMASKCHOOSER_H
#include <QWidget>
class KoResourceItemChooser;
class KoResource;
class KoGamutMask;
class KisGamutMaskDelegate;
class KisGamutMaskChooser : public QWidget
{
Q_OBJECT
public:
explicit KisGamutMaskChooser(QWidget *parent = nullptr);
~KisGamutMaskChooser() override;
enum ViewMode {
THUMBNAIL, // Shows thumbnails
DETAIL // Shows thumbsnails with text next to it
};
void setCurrentResource(KoResource* resource);
protected:
void resizeEvent(QResizeEvent* event) override;
Q_SIGNALS:
void sigGamutMaskSelected(KoGamutMask* mask);
private Q_SLOTS:
void resourceSelected(KoResource* resource);
void slotSetModeThumbnail();
void slotSetModeDetail();
private:
void setViewMode(ViewMode mode);
void updateViewSettings();
KoResourceItemChooser* m_itemChooser;
KisGamutMaskDelegate* m_delegate;
ViewMode m_mode;
};
#endif // KISGAMUTMASKCHOOSER_H
diff --git a/plugins/dockers/gamutmask/gamutmask_dock.cpp b/plugins/dockers/gamutmask/gamutmask_dock.cpp
index f6333ef32c..75385386ea 100644
--- a/plugins/dockers/gamutmask/gamutmask_dock.cpp
+++ b/plugins/dockers/gamutmask/gamutmask_dock.cpp
@@ -1,629 +1,630 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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_debug.h>
#include <klocalizedstring.h>
#include <KoCanvasResourceProvider.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServerObserver.h>
#include <KoResourceServerAdapter.h>
#include <KoCanvasBase.h>
#include <KoColor.h>
#include <resources/KoGamutMask.h>
#include <kis_icon_utils.h>
#include <KisPart.h>
#include <kis_shape_layer.h>
#include <kis_types.h>
#include <KisDocument.h>
#include <kis_node_selection_adapter.h>
#include <kis_group_layer.h>
#include <KisView.h>
#include <KoResourceItemChooser.h>
#include <QWidget>
#include <QMenu>
#include <QButtonGroup>
#include <QRegularExpressionValidator>
#include <QRegularExpression>
#include <QFileInfo>
#include <QMessageBox>
#include "gamutmask_dock.h"
#include <KisViewManager.h>
#include <kis_canvas_resource_provider.h>
#include <KoColorBackground.h>
#include <KoShapeStroke.h>
#include <ctime>
#include "ui_wdgGamutMaskChooser.h"
class KisMainWindow;
struct GamutMaskChooserUI: public QWidget, public Ui_wdgGamutMaskChooser
{
GamutMaskChooserUI() {
setupUi(this);
}
};
GamutMaskDock::GamutMaskDock()
: QDockWidget(i18n("Gamut Masks"))
, m_resourceProvider(0)
, m_selfClosingTemplate(false)
, m_externalTemplateClose(false)
, m_creatingNewMask(false)
, m_templatePrevSaved(false)
, m_selfSelectingMask(false)
, m_selectedMask(nullptr)
, m_maskDocument(nullptr)
, m_view(nullptr)
{
m_dockerUI = new GamutMaskChooserUI();
m_dockerUI->bnMaskEditor->setIcon(KisIconUtils::loadIcon("dirty-preset"));
m_dockerUI->bnMaskDelete->setIcon(KisIconUtils::loadIcon("deletelayer"));
m_dockerUI->bnMaskNew->setIcon(KisIconUtils::loadIcon("list-add"));
m_dockerUI->bnMaskDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer"));
m_dockerUI->maskPropertiesBox->setVisible(false);
m_dockerUI->bnSaveMask->setIcon(KisIconUtils::loadIcon("document-save"));
m_dockerUI->bnCancelMaskEdit->setIcon(KisIconUtils::loadIcon("dialog-cancel"));
m_dockerUI->bnPreviewMask->setIcon(KisIconUtils::loadIcon("visible"));
QRegularExpression maskTitleRegex("^[-_\\(\\)\\sA-Za-z0-9]+$");
QRegularExpressionValidator* m_maskTitleValidator = new QRegularExpressionValidator(maskTitleRegex, this);
m_dockerUI->maskTitleEdit->setValidator(m_maskTitleValidator);
KoResourceServer<KoGamutMask>* rServer = KoResourceServerProvider::instance()->gamutMaskServer();
rServer->addObserver(this);
// gamut mask connections
connect(m_dockerUI->bnSaveMask , SIGNAL(clicked()) , SLOT(slotGamutMaskSave()));
connect(m_dockerUI->bnCancelMaskEdit , SIGNAL(clicked()) , SLOT(slotGamutMaskCancelEdit()));
connect(m_dockerUI->bnPreviewMask , SIGNAL(clicked()) , SLOT(slotGamutMaskPreview()));
connect(m_dockerUI->bnMaskEditor , SIGNAL(clicked()) , SLOT(slotGamutMaskEdit()));
connect(m_dockerUI->maskChooser, SIGNAL(sigGamutMaskSelected(KoGamutMask*)), SLOT(slotGamutMaskSelected(KoGamutMask*)));
connect(m_dockerUI->bnMaskNew , SIGNAL(clicked()) , SLOT(slotGamutMaskCreateNew()));
connect(m_dockerUI->bnMaskDelete , SIGNAL(clicked()) , SLOT(slotGamutMaskDelete()));
connect(m_dockerUI->bnMaskDuplicate , SIGNAL(clicked()) , SLOT(slotGamutMaskDuplicate()));
setWidget(m_dockerUI);
}
GamutMaskDock::~GamutMaskDock()
{
KoResourceServer<KoGamutMask>* rServer = KoResourceServerProvider::instance()->gamutMaskServer();
rServer->removeObserver(this);
}
void GamutMaskDock::setViewManager(KisViewManager* kisview)
{
m_resourceProvider = kisview->canvasResourceProvider();
selectMask(m_resourceProvider->currentGamutMask());
connect(this, SIGNAL(sigGamutMaskSet(KoGamutMask*)), m_resourceProvider, SLOT(slotGamutMaskActivated(KoGamutMask*)), Qt::UniqueConnection);
connect(this, SIGNAL(sigGamutMaskChanged(KoGamutMask*)), m_resourceProvider, SLOT(slotGamutMaskActivated(KoGamutMask*)), Qt::UniqueConnection);
connect(this, SIGNAL(sigGamutMaskUnset()), m_resourceProvider, SLOT(slotGamutMaskUnset()), Qt::UniqueConnection);
connect(this, SIGNAL(sigGamutMaskPreviewUpdate()), m_resourceProvider, SLOT(slotGamutMaskPreviewUpdate()), Qt::UniqueConnection);
connect(KisPart::instance(), SIGNAL(sigDocumentRemoved(QString)), this, SLOT(slotDocumentRemoved(QString)), Qt::UniqueConnection);
}
void GamutMaskDock::slotGamutMaskEdit()
{
if (!m_selectedMask) {
return;
}
openMaskEditor();
}
bool GamutMaskDock::openMaskEditor()
{
if (!m_selectedMask) {
return false;
}
// find the template resource first, so we can abort the action early on
QString maskTemplateFile = KoResourcePaths::findResource("ko_gamutmasks", "GamutMaskTemplate.kra");
if (maskTemplateFile.isEmpty() || maskTemplateFile.isNull() || !QFile::exists(maskTemplateFile)) {
dbgPlugins << "GamutMaskDock::openMaskEditor(): maskTemplateFile (" << maskTemplateFile << ") was not found on the system";
getUserFeedback(i18n("Could not open gamut mask for editing."),
i18n("The editor template was not found."),
QMessageBox::Ok, QMessageBox::Ok, QMessageBox::Critical);
return false;
}
m_dockerUI->maskPropertiesBox->setVisible(true);
m_dockerUI->maskPropertiesBox->setEnabled(true);
m_dockerUI->editControlsBox->setEnabled(false);
m_dockerUI->editControlsBox->setVisible(false);
m_dockerUI->maskTitleEdit->setText(m_selectedMask->title());
m_dockerUI->maskDescriptionEdit->setPlainText(m_selectedMask->description());
m_maskDocument = KisPart::instance()->createDocument();
KisPart::instance()->addDocument(m_maskDocument);
m_maskDocument->openUrl(QUrl::fromLocalFile(maskTemplateFile), KisDocument::DontAddToRecent);
// template document needs a proper autogenerated filename,
// to avoid collision with other documents,
// otherwise bugs happen when slotDocumentRemoved is called
// (e.g. user closes another view, the template stays open, but the edit operation is canceled)
m_maskDocument->setInfiniteAutoSaveInterval();
QString maskPath = QString("%1%2%3_%4.kra")
.arg(QDir::tempPath())
.arg(QDir::separator())
.arg("GamutMaskTemplate")
.arg(std::time(nullptr));
m_maskDocument->setUrl(QUrl::fromLocalFile(maskPath));
m_maskDocument->setLocalFilePath(maskPath);
KisShapeLayerSP shapeLayer = getShapeLayer();
// pass only copies of shapes to the layer,
// so the originals don't disappear from the mask later
for (KoShape *shape: m_selectedMask->koShapes()) {
KoShape* newShape = shape->cloneShape();
newShape->setStroke(KoShapeStrokeModelSP());
newShape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(QColor(255,255,255))));
shapeLayer->addShape(newShape);
}
m_maskDocument->setPreActivatedNode(shapeLayer);
// set document as active
KisMainWindow* mainWindow = KisPart::instance()->currentMainwindow();
KIS_ASSERT(mainWindow);
m_view = mainWindow->addViewAndNotifyLoadingCompleted(m_maskDocument);
KIS_ASSERT(m_view);
for(KisView *view: KisPart::instance()->views()) {
if (view->document() == m_maskDocument) {
view->activateWindow();
break;
}
}
connect(m_view->viewManager(), SIGNAL(viewChanged()), this, SLOT(slotViewChanged()));
connect(m_maskDocument, SIGNAL(completed()), this, SLOT(slotDocumentSaved()));
return true;
}
void GamutMaskDock::cancelMaskEdit()
{
if (m_creatingNewMask) {
deleteMask();
}
if (m_selectedMask) {
m_selectedMask->clearPreview();
if (m_resourceProvider->currentGamutMask() == m_selectedMask) {
emit sigGamutMaskChanged(m_selectedMask);
}
}
closeMaskDocument();
}
void GamutMaskDock::selectMask(KoGamutMask *mask, bool notifyItemChooser)
{
if (!mask) {
return;
}
m_selectedMask = mask;
if (notifyItemChooser) {
m_selfSelectingMask = true;
m_dockerUI->maskChooser->setCurrentResource(m_selectedMask);
m_selfSelectingMask = false;
}
emit sigGamutMaskSet(m_selectedMask);
}
bool GamutMaskDock::saveSelectedMaskResource()
{
if (!m_selectedMask || !m_maskDocument) {
return false;
}
bool maskSaved = false;
if (m_selectedMask) {
QList<KoShape*> shapes = getShapesFromLayer();
if (shapes.count() > 0) {
m_selectedMask->setMaskShapes(shapes);
m_selectedMask->setImage(
m_maskDocument->image()->convertToQImage(m_maskDocument->image()->bounds()
, m_maskDocument->image()->profile()
)
);
m_selectedMask->setDescription(m_dockerUI->maskDescriptionEdit->toPlainText());
m_selectedMask->clearPreview();
m_selectedMask->save();
maskSaved = true;
} else {
getUserFeedback(i18n("Saving of gamut mask '%1' was aborted.", m_selectedMask->title()),
i18n("<p>The mask template is invalid.</p>"
"<p>Please check that:"
"<ul>"
"<li>your template contains a vector layer named 'maskShapesLayer'</li>"
"<li>there are one or more vector shapes on the 'maskShapesLayer'</li>"
"</ul></p>"
),
QMessageBox::Ok, QMessageBox::Ok);
}
}
return maskSaved;
}
void GamutMaskDock::deleteMask()
{
KoResourceServer<KoGamutMask>* rServer = KoResourceServerProvider::instance()->gamutMaskServer();
rServer->removeResourceAndBlacklist(m_selectedMask);
m_selectedMask = nullptr;
}
int GamutMaskDock::getUserFeedback(QString text, QString informativeText,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton,
QMessageBox::Icon severity)
{
QMessageBox msgBox;
msgBox.setWindowTitle(i18nc("@title:window", "Krita"));
msgBox.setText(QString("<p><b>%1</b></p>").arg(text));
msgBox.setInformativeText(informativeText);
msgBox.setStandardButtons(buttons);
msgBox.setDefaultButton(defaultButton);
msgBox.setIcon(severity);
int res = msgBox.exec();
return res;
}
int GamutMaskDock::saveOrCancel(QMessageBox::StandardButton defaultAction)
{
int response = 0;
if (m_maskDocument->isModified()) {
response = getUserFeedback(i18n("Gamut mask <b>'%1'</b> has been modified.", m_selectedMask->title()),
i18n("Do you want to save it?"),
QMessageBox::Cancel | QMessageBox::Close | QMessageBox::Save, defaultAction);
} else if (m_templatePrevSaved && defaultAction != QMessageBox::Close) {
response = QMessageBox::Save;
} else if (!m_templatePrevSaved) {
response = QMessageBox::Close;
} else {
response = defaultAction;
}
switch (response) {
case QMessageBox::Save : {
slotGamutMaskSave();
break;
}
case QMessageBox::Close : {
cancelMaskEdit();
break;
}
}
return response;
}
KoGamutMask *GamutMaskDock::createMaskResource(KoGamutMask* sourceMask, QString newTitle)
{
m_creatingNewMask = true;
KoGamutMask* newMask = nullptr;
if (sourceMask) {
newMask = new KoGamutMask(sourceMask);
newMask->setImage(sourceMask->image());
} else {
newMask = new KoGamutMask();
QString defaultPreviewPath = KoResourcePaths::findResource("ko_gamutmasks", "empty_mask_preview.png");
KIS_SAFE_ASSERT_RECOVER_NOOP(!(defaultPreviewPath.isEmpty() || defaultPreviewPath.isNull() || !QFile::exists(defaultPreviewPath)));
newMask->setImage(QImage(defaultPreviewPath, "PNG"));
}
QPair<QString,QFileInfo> maskFile = resolveMaskTitle(newTitle);
QString maskTitle = maskFile.first;
QFileInfo fileInfo = maskFile.second;
newMask->setTitle(maskTitle);
newMask->setFilename(fileInfo.filePath());
newMask->setValid(true);
KoResourceServer<KoGamutMask>* rServer = KoResourceServerProvider::instance()->gamutMaskServer();
rServer->removeFromBlacklist(newMask);
rServer->addResource(newMask, false);
return newMask;
}
QPair<QString, QFileInfo> GamutMaskDock::resolveMaskTitle(QString suggestedTitle)
{
KoResourceServer<KoGamutMask>* rServer = KoResourceServerProvider::instance()->gamutMaskServer();
QString saveLocation = rServer->saveLocation();
QString processedTitle = suggestedTitle.trimmed();
QString resourceName = processedTitle;
while (rServer->resourceByName(resourceName)) {
resourceName = resourceName + QString(" (Copy)");
}
QString maskTitle = resourceName;
QString maskFile = maskTitle + ".kgm";
QString path = saveLocation + maskFile.replace(QRegularExpression("\\s+"), "_");
QFileInfo fileInfo(path);
return QPair<QString, QFileInfo>(maskTitle, fileInfo);
}
void GamutMaskDock::closeMaskDocument()
{
if (!m_externalTemplateClose) {
if (m_maskDocument) {
// set the document to not modified to bypass confirmation dialog
// the close is already confirmed
m_maskDocument->setModified(false);
m_maskDocument->closeUrl();
m_view->closeView();
m_view->deleteLater();
// set a flag that we are doing it ourselves, so the docker does not react to
// removing signal from KisPart
m_selfClosingTemplate = true;
KisPart::instance()->removeView(m_view);
KisPart::instance()->removeDocument(m_maskDocument);
m_selfClosingTemplate = false;
}
}
m_dockerUI->maskPropertiesBox->setVisible(false);
m_dockerUI->editControlsBox->setVisible(true);
m_dockerUI->editControlsBox->setEnabled(true);
disconnect(m_view->viewManager(), SIGNAL(viewChanged()), this, SLOT(slotViewChanged()));
disconnect(m_maskDocument, SIGNAL(completed()), this, SLOT(slotDocumentSaved()));
// the template file is meant as temporary, if the user saved it, delete now
if (QFile::exists(m_maskDocument->localFilePath())) {
QFile::remove(m_maskDocument->localFilePath());
}
m_maskDocument = nullptr;
m_view = nullptr;
m_creatingNewMask = false;
m_templatePrevSaved = false;
}
QList<KoShape*> GamutMaskDock::getShapesFromLayer()
{
KisShapeLayerSP shapeLayer = getShapeLayer();
// create a deep copy of the shapes to save in the mask,
// otherwise they vanish when the template closes
QList<KoShape*> newShapes;
if (shapeLayer) {
for (KoShape* sh: shapeLayer->shapes()) {
KoShape* newShape = sh->cloneShape();
KoShapeStrokeSP border(new KoShapeStroke(0.5f, Qt::white));
newShape->setStroke(border);
newShape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(QColor(255,255,255,0))));
newShapes.append(newShape);
}
}
return newShapes;
}
KisShapeLayerSP GamutMaskDock::getShapeLayer()
{
KisNodeSP node = m_maskDocument->image()->rootLayer()->findChildByName("maskShapesLayer");
return KisShapeLayerSP(dynamic_cast<KisShapeLayer*>(node.data()));
}
void GamutMaskDock::slotGamutMaskSave()
{
if (!m_selectedMask || !m_maskDocument) {
return;
}
QString newTitle = m_dockerUI->maskTitleEdit->text();
if (m_selectedMask->title() != newTitle) {
// title has changed, rename
KoGamutMask* newMask = createMaskResource(m_selectedMask, newTitle);
// delete old mask and select new
deleteMask();
selectMask(newMask);
}
bool maskSaved = saveSelectedMaskResource();
if (maskSaved) {
emit sigGamutMaskSet(m_selectedMask);
closeMaskDocument();
}
}
void GamutMaskDock::slotGamutMaskCancelEdit()
{
if (!m_selectedMask) {
return;
}
saveOrCancel(QMessageBox::Close);
}
void GamutMaskDock::slotGamutMaskPreview()
{
if (!m_selectedMask) {
return;
}
m_selectedMask->setPreviewMaskShapes(getShapesFromLayer());
emit sigGamutMaskPreviewUpdate();
}
void GamutMaskDock::slotGamutMaskSelected(KoGamutMask *mask)
{
if (!m_selfSelectingMask) {
if (m_maskDocument) {
int res = saveOrCancel();
if (res == QMessageBox::Cancel) {
return;
}
}
selectMask(mask, false);
}
}
void GamutMaskDock::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
}
void GamutMaskDock::unsetCanvas()
{
setEnabled(false);
}
void GamutMaskDock::unsetResourceServer()
{
KoResourceServer<KoGamutMask>* rServer = KoResourceServerProvider::instance()->gamutMaskServer();
rServer->removeObserver(this);
}
void GamutMaskDock::removingResource(KoGamutMask *resource)
{
// if deleting previously set mask, notify selectors to unset their mask
if (resource == m_resourceProvider->currentGamutMask()) {
emit sigGamutMaskUnset();
m_selectedMask = nullptr;
}
}
void GamutMaskDock::resourceChanged(KoGamutMask *resource)
{
// if currently set mask has been changed, notify selectors
if (resource == m_resourceProvider->currentGamutMask()) {
selectMask(resource);
}
}
void GamutMaskDock::slotGamutMaskCreateNew()
{
KoGamutMask* newMask = createMaskResource(nullptr, "new mask");
selectMask(newMask);
bool editorOpened = openMaskEditor();
if (!editorOpened) {
deleteMask();
}
}
void GamutMaskDock::slotGamutMaskDuplicate()
{
if (!m_selectedMask) {
return;
}
KoGamutMask* newMask = createMaskResource(m_selectedMask, m_selectedMask->title());
selectMask(newMask);
bool editorOpened = openMaskEditor();
if (!editorOpened) {
deleteMask();
}
}
void GamutMaskDock::slotGamutMaskDelete()
{
if (!m_selectedMask) {
return;
}
int res = getUserFeedback(i18n("Are you sure you want to delete mask <b>'%1'</b>?"
, m_selectedMask->title()));
if (res == QMessageBox::Yes) {
deleteMask();
}
}
void GamutMaskDock::slotDocumentRemoved(QString filename)
{
if (!m_maskDocument) {
return;
}
m_externalTemplateClose = true;
// we do not want to run this if it is we who close the file
if (!m_selfClosingTemplate) {
// KisPart called, that a document will be removed
// if it's ours, cancel the mask edit operation
if (m_maskDocument->url().toLocalFile() == filename) {
m_maskDocument->waitForSavingToComplete();
saveOrCancel();
}
}
m_externalTemplateClose = false;
}
void GamutMaskDock::slotViewChanged()
{
if (!m_maskDocument || !m_view) {
return;
}
if (m_view->viewManager()->document() == m_maskDocument) {
m_dockerUI->maskPropertiesBox->setEnabled(true);
} else {
m_dockerUI->maskPropertiesBox->setEnabled(false);
}
}
void GamutMaskDock::slotDocumentSaved()
{
m_templatePrevSaved = true;
}
diff --git a/plugins/dockers/gamutmask/gamutmask_dock.h b/plugins/dockers/gamutmask/gamutmask_dock.h
index 43e14d0af7..023e0c036a 100644
--- a/plugins/dockers/gamutmask/gamutmask_dock.h
+++ b/plugins/dockers/gamutmask/gamutmask_dock.h
@@ -1,125 +1,126 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 H_GAMUT_MASK_DOCK_H
#define H_GAMUT_MASK_DOCK_H
#include <QDockWidget>
#include <QPointer>
#include <QRegExpValidator>
#include <QMessageBox>
#include <KoCanvasObserverBase.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServerAdapter.h>
#include <KoResourceServerObserver.h>
#include <resources/KoGamutMask.h>
#include <KisDocument.h>
#include <KisView.h>
#include <kis_types.h>
#include <KoResourceItemChooser.h>
#include <kis_mainwindow_observer.h>
class KisCanvasResourceProvider;
class QButtonGroup;
class QMenu;
struct GamutMaskChooserUI;
class GamutMaskDock: public QDockWidget, public KisMainwindowObserver, public KoResourceServerObserver<KoGamutMask>
{
Q_OBJECT
public:
GamutMaskDock();
~GamutMaskDock() override;
QString observerName() override { return "GamutMaskDock"; }
void setViewManager(KisViewManager* kisview) override;
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
public: // KoResourceServerObserver
void unsetResourceServer() override;
void resourceAdded(KoGamutMask* /*resource*/) override {};
void removingResource(KoGamutMask* resource) override;
void resourceChanged(KoGamutMask* resource) override;
void syncTaggedResourceView() override {}
void syncTagAddition(const QString&) override {}
void syncTagRemoval(const QString&) override {}
Q_SIGNALS:
void sigGamutMaskSet(KoGamutMask* mask);
void sigGamutMaskChanged(KoGamutMask* mask);
void sigGamutMaskUnset();
void sigGamutMaskPreviewUpdate();
private Q_SLOTS:
void slotGamutMaskEdit();
void slotGamutMaskSave();
void slotGamutMaskCancelEdit();
void slotGamutMaskSelected(KoGamutMask* mask);
void slotGamutMaskPreview();
void slotGamutMaskCreateNew();
void slotGamutMaskDuplicate();
void slotGamutMaskDelete();
void slotDocumentRemoved(QString filename);
void slotViewChanged();
void slotDocumentSaved();
private:
void closeMaskDocument();
bool openMaskEditor();
void cancelMaskEdit();
void selectMask(KoGamutMask* mask, bool notifyItemChooser = true);
bool saveSelectedMaskResource();
void deleteMask();
int getUserFeedback(QString text, QString informativeText = "",
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
QMessageBox::StandardButton defaultButton = QMessageBox::Yes,
QMessageBox::Icon severity = QMessageBox::Warning);
int saveOrCancel(QMessageBox::StandardButton defaultAction = QMessageBox::Save);
KoGamutMask* createMaskResource(KoGamutMask* sourceMask, QString newTitle);
QPair<QString, QFileInfo> resolveMaskTitle(QString suggestedTitle);
QList<KoShape*> getShapesFromLayer();
KisShapeLayerSP getShapeLayer();
KisCanvasResourceProvider* m_resourceProvider;
bool m_selfClosingTemplate;
bool m_externalTemplateClose;
bool m_creatingNewMask;
bool m_templatePrevSaved;
bool m_selfSelectingMask;
GamutMaskChooserUI* m_dockerUI;
KoResourceItemChooser* m_maskChooser;
KoGamutMask* m_selectedMask;
QRegExpValidator* m_maskTitleValidator;
KisDocument* m_maskDocument;
KisView* m_view;
};
#endif // H_GAMUT_MASK_DOCK_H
diff --git a/plugins/dockers/gamutmask/gamutmask_plugin.cpp b/plugins/dockers/gamutmask/gamutmask_plugin.cpp
index 5c94818b63..cd5ca10d09 100644
--- a/plugins/dockers/gamutmask/gamutmask_plugin.cpp
+++ b/plugins/dockers/gamutmask/gamutmask_plugin.cpp
@@ -1,57 +1,58 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 "gamutmask_plugin.h"
#include "gamutmask_dock.h"
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(PaletteDockPluginFactory, "krita_gamutmask.json", registerPlugin<GamutMaskPlugin>();)
class GamutMaskDockFactory: public KoDockFactoryBase
{
public:
QString id() const override {
return QString("GamutMask");
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const {
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override {
GamutMaskDock* dockWidget = new GamutMaskDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockMinimized;
}
};
GamutMaskPlugin::GamutMaskPlugin(QObject* parent, const QVariantList &):
QObject(parent)
{
KoDockRegistry::instance()->add(new GamutMaskDockFactory());
}
#include "gamutmask_plugin.moc"
diff --git a/plugins/dockers/gamutmask/gamutmask_plugin.h b/plugins/dockers/gamutmask/gamutmask_plugin.h
index 005a3fdd25..e26ca0ea9e 100644
--- a/plugins/dockers/gamutmask/gamutmask_plugin.h
+++ b/plugins/dockers/gamutmask/gamutmask_plugin.h
@@ -1,30 +1,31 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@gmail.com>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 H_GAMUT_MASK_PLUGIN_H
#define H_GAMUT_MASK_PLUGIN_H
#include <QObject>
#include <QVariant>
class GamutMaskPlugin: public QObject
{
public:
GamutMaskPlugin(QObject *parent, const QVariantList &);
};
#endif // H_GAMUT_MASK_PLUGIN_H
diff --git a/plugins/dockers/griddocker/grid_config_widget.cpp b/plugins/dockers/griddocker/grid_config_widget.cpp
index b6f8d934d2..198cd0e385 100644
--- a/plugins/dockers/griddocker/grid_config_widget.cpp
+++ b/plugins/dockers/griddocker/grid_config_widget.cpp
@@ -1,362 +1,359 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "grid_config_widget.h"
#include "ui_grid_config_widget.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_debug.h"
#include "kis_aspect_ratio_locker.h"
#include "kis_int_parse_spin_box.h"
#include <kis_config.h>
#include <kis_config_notifier.h>
#include <QStandardItem>
#include <QStandardItemModel>
struct GridConfigWidget::Private
{
Private() : guiSignalsBlocked(false) {}
KisGridConfig gridConfig;
KisGuidesConfig guidesConfig;
bool guiSignalsBlocked {false};
};
GridConfigWidget::GridConfigWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::GridConfigWidget),
m_d(new Private)
{
ui->setupUi(this);
ui->colorMain->setAlphaChannelEnabled(true);
ui->colorSubdivision->setAlphaChannelEnabled(true);
ui->colorGuides->setAlphaChannelEnabled(true);
ui->angleLeftSpinbox->setSuffix(QChar(Qt::Key_degree));
ui->angleRightSpinbox->setSuffix(QChar(Qt::Key_degree));
ui->cellSpacingSpinbox->setSuffix(i18n(" px"));
ui->gridTypeCombobox->addItem(i18n("Rectangle"));
ui->gridTypeCombobox->addItem(i18n("Isometric"));
ui->gridTypeCombobox->setCurrentIndex(0); // set to rectangle by default
slotGridTypeChanged(); // update the UI to hide any elements we don't need
connect(ui->gridTypeCombobox, SIGNAL(currentIndexChanged(int)), SLOT(slotGridTypeChanged()));
setGridConfig(m_d->gridConfig);
setGuidesConfig(m_d->guidesConfig);
// hide offset UI elements if offset is disabled
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblXOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->lblYOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intXOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->intYOffset, SLOT(setVisible(bool)));
connect(ui->chkOffset, SIGNAL(toggled(bool)), ui->offsetAspectButton, SLOT(setVisible(bool)));
ui->lblXOffset->setVisible(false);
ui->lblYOffset->setVisible(false);
ui->intXOffset->setVisible(false);
ui->intYOffset->setVisible(false);
ui->offsetAspectButton->setVisible(false);
connect(ui->chkShowGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->chkSnapToGrid, SIGNAL(stateChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->chkShowGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->chkSnapToGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->chkLockGuides, SIGNAL(stateChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->intSubdivision, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->angleLeftSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->angleRightSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->cellSpacingSpinbox, SIGNAL(valueChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->selectMainStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->colorMain, SIGNAL(changed(QColor)), SLOT(slotGridGuiChanged()));
connect(ui->selectSubdivisionStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGridGuiChanged()));
connect(ui->colorSubdivision, SIGNAL(changed(QColor)), SLOT(slotGridGuiChanged()));
connect(ui->selectGuidesStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotGuidesGuiChanged()));
connect(ui->colorGuides, SIGNAL(changed(QColor)), SLOT(slotGuidesGuiChanged()));
ui->chkOffset->setChecked(false);
KisAspectRatioLocker *offsetLocker = new KisAspectRatioLocker(this);
offsetLocker->connectSpinBoxes(ui->intXOffset, ui->intYOffset, ui->offsetAspectButton);
KisAspectRatioLocker *spacingLocker = new KisAspectRatioLocker(this);
spacingLocker->connectSpinBoxes(ui->intHSpacing, ui->intVSpacing, ui->spacingAspectButton);
connect(offsetLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged()));
connect(offsetLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged()));
connect(spacingLocker, SIGNAL(sliderValueChanged()), SLOT(slotGridGuiChanged()));
connect(spacingLocker, SIGNAL(aspectButtonChanged()), SLOT(slotGridGuiChanged()));
connect(ui->chkShowRulers,SIGNAL(toggled(bool)),SIGNAL(showRulersChanged(bool)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotPreferencesUpdated()));
}
GridConfigWidget::~GridConfigWidget()
{
delete ui;
}
void GridConfigWidget::setGridConfig(const KisGridConfig &value)
{
KisGridConfig currentConfig = fetchGuiGridConfig();
if (currentConfig == value) return;
setGridConfigImpl(value);
}
void GridConfigWidget::setGuidesConfig(const KisGuidesConfig &value)
{
KisGuidesConfig currentConfig = fetchGuiGuidesConfig();
if (currentConfig == value) return;
setGuidesConfigImpl(value);
}
void GridConfigWidget::setGridConfigImpl(const KisGridConfig &value)
{
m_d->gridConfig = value;
m_d->guiSignalsBlocked = true;
ui->offsetAspectButton->setKeepAspectRatio(m_d->gridConfig.offsetAspectLocked());
ui->spacingAspectButton->setKeepAspectRatio(m_d->gridConfig.spacingAspectLocked());
ui->chkShowGrid->setChecked(m_d->gridConfig.showGrid());
ui->intHSpacing->setValue(m_d->gridConfig.spacing().x());
+ ui->intHSpacing->setMaximum(std::numeric_limits<int>::max());
ui->intVSpacing->setValue(m_d->gridConfig.spacing().y());
+ ui->intVSpacing->setMaximum(std::numeric_limits<int>::max());
ui->intXOffset->setValue(m_d->gridConfig.offset().x());
ui->intYOffset->setValue(m_d->gridConfig.offset().y());
ui->intSubdivision->setValue(m_d->gridConfig.subdivision());
ui->chkSnapToGrid->setChecked(m_d->gridConfig.snapToGrid());
ui->angleLeftSpinbox->setValue(m_d->gridConfig.angleLeft());
ui->angleRightSpinbox->setValue(m_d->gridConfig.angleRight());
ui->cellSpacingSpinbox->setValue(m_d->gridConfig.cellSpacing());
ui->selectMainStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeMain()));
ui->selectSubdivisionStyle->setCurrentIndex(int(m_d->gridConfig.lineTypeSubdivision()));
ui->gridTypeCombobox->setCurrentIndex(m_d->gridConfig.gridType());
ui->colorMain->setColor(m_d->gridConfig.colorMain());
ui->colorSubdivision->setColor(m_d->gridConfig.colorSubdivision());
m_d->guiSignalsBlocked = false;
emit gridValueChanged();
}
void GridConfigWidget::setGuidesConfigImpl(const KisGuidesConfig &value)
{
m_d->guidesConfig = value;
m_d->guiSignalsBlocked = true;
ui->chkShowGuides->setChecked(m_d->guidesConfig.showGuides());
ui->chkSnapToGuides->setChecked(m_d->guidesConfig.snapToGuides());
ui->chkLockGuides->setChecked(m_d->guidesConfig.lockGuides());
ui->selectGuidesStyle->setCurrentIndex(int(m_d->guidesConfig.guidesLineType()));
ui->colorGuides->setColor(m_d->guidesConfig.guidesColor());
m_d->guiSignalsBlocked = false;
emit guidesValueChanged();
}
KisGridConfig GridConfigWidget::gridConfig() const
{
return m_d->gridConfig;
}
KisGuidesConfig GridConfigWidget::guidesConfig() const
{
return m_d->guidesConfig;
}
-void GridConfigWidget::setGridDivision(int w, int h)
-{
- ui->intHSpacing->setMaximum(w);
- ui->intVSpacing->setMaximum(h);
-}
-
KisGridConfig GridConfigWidget::fetchGuiGridConfig() const
{
KisGridConfig config;
config.setShowGrid(ui->chkShowGrid->isChecked());
config.setSnapToGrid(ui->chkSnapToGrid->isChecked());
QPoint pt;
pt.rx() = ui->intHSpacing->value();
pt.ry() = ui->intVSpacing->value();
config.setSpacing(pt);
pt.rx() = ui->intXOffset->value();
pt.ry() = ui->intYOffset->value();
config.setOffset(pt);
config.setSubdivision(ui->intSubdivision->value());
config.setAngleLeft(ui->angleLeftSpinbox->value());
config.setAngleRight(ui->angleRightSpinbox->value());
config.setCellSpacing(ui->cellSpacingSpinbox->value());
config.setGridType(ui->gridTypeCombobox->currentIndex());
config.setOffsetAspectLocked(ui->offsetAspectButton->keepAspectRatio());
config.setSpacingAspectLocked(ui->spacingAspectButton->keepAspectRatio());
config.setLineTypeMain(KisGridConfig::LineTypeInternal(ui->selectMainStyle->currentIndex()));
config.setLineTypeSubdivision(KisGridConfig::LineTypeInternal(ui->selectSubdivisionStyle->currentIndex()));
config.setColorMain(ui->colorMain->color());
config.setColorSubdivision(ui->colorSubdivision->color());
return config;
}
KisGuidesConfig GridConfigWidget::fetchGuiGuidesConfig() const
{
KisGuidesConfig config = m_d->guidesConfig;
config.setShowGuides(ui->chkShowGuides->isChecked());
config.setSnapToGuides(ui->chkSnapToGuides->isChecked());
config.setLockGuides(ui->chkLockGuides->isChecked());
config.setGuidesLineType(KisGuidesConfig::LineTypeInternal(ui->selectGuidesStyle->currentIndex()));
config.setGuidesColor(ui->colorGuides->color());
return config;
}
void GridConfigWidget::slotGridGuiChanged()
{
if (m_d->guiSignalsBlocked) return;
KisGridConfig currentConfig = fetchGuiGridConfig();
if (currentConfig == m_d->gridConfig) return;
setGridConfigImpl(currentConfig);
}
void GridConfigWidget::slotPreferencesUpdated()
{
KisConfig cfg(true);
enableIsometricGrid(cfg.useOpenGL()); // Isometric view needs OpenGL
}
void GridConfigWidget::slotGuidesGuiChanged()
{
if (m_d->guiSignalsBlocked) return;
KisGuidesConfig currentConfig = fetchGuiGuidesConfig();
if (currentConfig == m_d->guidesConfig) return;
setGuidesConfigImpl(currentConfig);
}
void GridConfigWidget::slotGridTypeChanged() {
bool showRectangleControls = ui->gridTypeCombobox->currentIndex() == 0;
// specific rectangle UI controls
ui->lblXSpacing->setVisible(showRectangleControls);
ui->lblYSpacing->setVisible(showRectangleControls);
ui->intHSpacing->setVisible(showRectangleControls);
ui->intVSpacing->setVisible(showRectangleControls);
ui->spacingAspectButton->setVisible(showRectangleControls);
ui->lblSubdivision->setVisible(showRectangleControls);
ui->intSubdivision->setVisible(showRectangleControls);
ui->lblSubdivisionStyle->setVisible(showRectangleControls);
ui->selectSubdivisionStyle->setVisible(showRectangleControls);
ui->colorSubdivision->setVisible(showRectangleControls);
// specific isometric UI controls
ui->leftAngleLabel->setVisible(!showRectangleControls);
ui->rightAngleLabel->setVisible(!showRectangleControls);
ui->angleLeftSpinbox->setVisible(!showRectangleControls);
ui->angleRightSpinbox->setVisible(!showRectangleControls);
ui->cellSpacingLabel->setVisible(!showRectangleControls);
ui->cellSpacingSpinbox->setVisible(!showRectangleControls);
// disable snapping for isometric grid type for now
// remember if we had snapping enabled if it was on the rectangule mode
if (!showRectangleControls) {
m_isGridEnabled = ui->chkSnapToGrid->isChecked();
ui->chkSnapToGrid->setEnabled(false);
ui->chkSnapToGrid->setChecked(false);
} else {
ui->chkSnapToGrid->setEnabled(true);
ui->chkSnapToGrid->setChecked(m_isGridEnabled);
}
slotGridGuiChanged();
}
bool GridConfigWidget::showRulers() const
{
return ui->chkShowRulers->isChecked();
}
void GridConfigWidget::enableIsometricGrid(bool value)
{
m_isIsometricGridEnabled = value;
// Isometric grids disabled if OpenGL is disabled
QStandardItemModel *model = qobject_cast<QStandardItemModel*>(ui->gridTypeCombobox->model());
QStandardItem *item = model->item(1); // isometric option
// item->setFlags(m_isIsometricGridEnabled ? item->flags() & ~Qt::ItemIsEnabled:
// item->flags() | Qt::ItemIsEnabled);
item->setEnabled(m_isIsometricGridEnabled);
if (m_isIsometricGridEnabled) {
item->setText(i18n("Isometric"));
} else {
item->setText(i18n("Isometric (requires OpenGL)"));
// change drop down index to Rectangular in case it was previously set to isometric
ui->gridTypeCombobox->setCurrentIndex(0);
}
}
void GridConfigWidget::setShowRulers(bool value)
{
ui->chkShowRulers->setChecked(value);
}
diff --git a/plugins/dockers/griddocker/grid_config_widget.h b/plugins/dockers/griddocker/grid_config_widget.h
index d308974195..e89bad031f 100644
--- a/plugins/dockers/griddocker/grid_config_widget.h
+++ b/plugins/dockers/griddocker/grid_config_widget.h
@@ -1,83 +1,82 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 GRID_CONFIG_WIDGET_H
#define GRID_CONFIG_WIDGET_H
#include <QWidget>
#include <QScopedPointer>
namespace Ui {
class GridConfigWidget;
}
class KisGridConfig;
class KisGuidesConfig;
class GridConfigWidget : public QWidget
{
Q_OBJECT
public:
explicit GridConfigWidget(QWidget *parent = 0);
~GridConfigWidget() override;
void setGridConfig(const KisGridConfig &value);
KisGridConfig gridConfig() const;
void setGuidesConfig(const KisGuidesConfig &value);
KisGuidesConfig guidesConfig() const;
- void setGridDivision(int w, int h);
-
bool showRulers() const;
void enableIsometricGrid(bool value);
public Q_SLOTS:
void setShowRulers(bool value);
private Q_SLOTS:
void slotGridGuiChanged();
void slotGuidesGuiChanged();
void slotGridTypeChanged();
void slotPreferencesUpdated();
Q_SIGNALS:
void gridValueChanged();
void guidesValueChanged();
void showRulersChanged(bool);
private:
KisGridConfig fetchGuiGridConfig() const;
void setGridConfigImpl(const KisGridConfig &value);
KisGuidesConfig fetchGuiGuidesConfig() const;
void setGuidesConfigImpl(const KisGuidesConfig &value);
private:
Ui::GridConfigWidget *ui;
struct Private;
const QScopedPointer<Private> m_d;
bool m_isGridEnabled {false};
bool m_isIsometricGridEnabled {true};
};
#endif // GRID_CONFIG_WIDGET_H
diff --git a/plugins/dockers/griddocker/griddocker.cpp b/plugins/dockers/griddocker/griddocker.cpp
index 3dd6a87f7b..d5aeb698c7 100644
--- a/plugins/dockers/griddocker/griddocker.cpp
+++ b/plugins/dockers/griddocker/griddocker.cpp
@@ -1,87 +1,88 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "griddocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "griddocker_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(GridDockerPluginFactory, "krita_griddocker.json", registerPlugin<GridDockerPlugin>();)
class GridDockerDockFactory : public KoDockFactoryBase {
public:
GridDockerDockFactory()
{
}
QString id() const override
{
return QString( "GridDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
GridDockerDock * dockWidget = new GridDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
GridDockerPlugin::GridDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new GridDockerDockFactory());
}
GridDockerPlugin::~GridDockerPlugin()
{
m_view = 0;
}
#include "griddocker.moc"
diff --git a/plugins/dockers/griddocker/griddocker.h b/plugins/dockers/griddocker/griddocker.h
index fa7df907cf..9de7f52fe2 100644
--- a/plugins/dockers/griddocker/griddocker.h
+++ b/plugins/dockers/griddocker/griddocker.h
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _GRID_DOCKER_H_
#define _GRID_DOCKER_H_
#include <QObject>
#include <QVariant>
class KisViewManager;
/**
* Template of view plugin
*/
class GridDockerPlugin : public QObject
{
Q_OBJECT
public:
GridDockerPlugin(QObject *parent, const QVariantList &);
~GridDockerPlugin() override;
private:
KisViewManager* m_view;
};
#endif
diff --git a/plugins/dockers/griddocker/griddocker_dock.cpp b/plugins/dockers/griddocker/griddocker_dock.cpp
index c05f17ea37..0c6a1be2e8 100644
--- a/plugins/dockers/griddocker/griddocker_dock.cpp
+++ b/plugins/dockers/griddocker/griddocker_dock.cpp
@@ -1,130 +1,129 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "griddocker_dock.h"
//#include "gridwidget.h"
// #include <QLabel>
// #include <QVBoxLayout>
#include <QStatusBar>
#include <klocalizedstring.h>
#include <kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_zoom_manager.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_signal_compressor.h>
#include <kis_grid_manager.h>
#include <kis_grid_config.h>
#include <kis_guides_manager.h>
#include <kis_guides_config.h>
#include <kis_action.h>
#include <KisDocument.h>
#include "grid_config_widget.h"
GridDockerDock::GridDockerDock( )
: QDockWidget(i18n("Grid and Guides"))
, m_canvas(0)
{
m_configWidget = new GridConfigWidget(this);
connect(m_configWidget, SIGNAL(gridValueChanged()), SLOT(slotGuiGridConfigChanged()));
connect(m_configWidget, SIGNAL(guidesValueChanged()), SLOT(slotGuiGuidesConfigChanged()));
setWidget(m_configWidget);
setEnabled(m_canvas);
}
GridDockerDock::~GridDockerDock()
{
}
void GridDockerDock::setCanvas(KoCanvasBase * canvas)
{
if(canvas && m_canvas == canvas)
return;
if (m_canvas) {
m_canvasConnections.clear();
m_canvas->disconnectCanvasObserver(this);
m_canvas->image()->disconnect(this);
}
m_canvas = canvas ? dynamic_cast<KisCanvas2*>(canvas) : 0;
setEnabled(m_canvas);
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) {
m_canvasConnections.addConnection(
m_canvas->viewManager()->gridManager(),
SIGNAL(sigRequestUpdateGridConfig(KisGridConfig)),
this,
SLOT(slotGridConfigUpdateRequested(KisGridConfig)));
slotGridConfigUpdateRequested(m_canvas->viewManager()->document()->gridConfig());
KisAction* action = m_canvas->viewManager()->actionManager()->actionByName("view_ruler");
m_canvasConnections.addConnection(m_configWidget,SIGNAL(showRulersChanged(bool)),action,SLOT(setChecked(bool)));
m_canvasConnections.addConnection(action,SIGNAL(toggled(bool)),m_configWidget,SLOT(setShowRulers(bool)));
m_configWidget->setShowRulers(action->isChecked());
m_canvasConnections.addConnection(
m_canvas->viewManager()->guidesManager(),
SIGNAL(sigRequestUpdateGuidesConfig(KisGuidesConfig)),
this,
SLOT(slotGuidesConfigUpdateRequested(KisGuidesConfig)));
slotGuidesConfigUpdateRequested(m_canvas->viewManager()->document()->guidesConfig());
- QRect rc = m_canvas->image()->bounds();
- m_configWidget->setGridDivision(rc.width() / 2, rc.height() / 2);
// isometric grid only available with OpenGL
if (m_canvas->canvasIsOpenGL()) {
m_configWidget->enableIsometricGrid(true);
} else {
m_configWidget->enableIsometricGrid(false);
}
}
}
void GridDockerDock::unsetCanvas()
{
setCanvas(0);
}
void GridDockerDock::slotGuiGridConfigChanged()
{
if (!m_canvas) return;
m_canvas->viewManager()->gridManager()->setGridConfig(m_configWidget->gridConfig());
}
void GridDockerDock::slotGridConfigUpdateRequested(const KisGridConfig &config)
{
m_configWidget->setGridConfig(config);
}
void GridDockerDock::slotGuiGuidesConfigChanged()
{
if (!m_canvas) return;
m_canvas->viewManager()->guidesManager()->setGuidesConfig(m_configWidget->guidesConfig());
}
void GridDockerDock::slotGuidesConfigUpdateRequested(const KisGuidesConfig &config)
{
m_configWidget->setGuidesConfig(config);
}
diff --git a/plugins/dockers/griddocker/griddocker_dock.h b/plugins/dockers/griddocker/griddocker_dock.h
index d7d1a775d8..b156c23eab 100644
--- a/plugins/dockers/griddocker/griddocker_dock.h
+++ b/plugins/dockers/griddocker/griddocker_dock.h
@@ -1,55 +1,56 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _GRID_DOCK_H_
#define _GRID_DOCK_H_
#include <QDockWidget>
#include <KoCanvasObserverBase.h>
#include "kis_signal_auto_connection.h"
class QVBoxLayout;
class KisCanvas2;
class GridConfigWidget;
class KisSignalAutoConnection;
class KisGridConfig;
class KisGuidesConfig;
class GridDockerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
GridDockerDock();
~GridDockerDock() override;
QString observerName() override { return "GridDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
public Q_SLOTS:
void slotGuiGridConfigChanged();
void slotGridConfigUpdateRequested(const KisGridConfig &config);
void slotGuiGuidesConfigChanged();
void slotGuidesConfigUpdateRequested(const KisGuidesConfig &config);
private:
GridConfigWidget *m_configWidget;
QPointer<KisCanvas2> m_canvas;
KisSignalAutoConnectionsStore m_canvasConnections;
};
#endif
diff --git a/plugins/dockers/histogram/histogramdocker.cpp b/plugins/dockers/histogram/histogramdocker.cpp
index 623bce5f92..93dd11349a 100644
--- a/plugins/dockers/histogram/histogramdocker.cpp
+++ b/plugins/dockers/histogram/histogramdocker.cpp
@@ -1,87 +1,88 @@
/*
* Copyright (c) 2016 Eugene Ingerman <geneing at gmail dot com>
*
* 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.
+ * the Free Software Foundation; 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 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 "histogramdocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "histogramdocker_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(HistogramDockerPluginFactory, "krita_histogramdocker.json", registerPlugin<HistogramDockerPlugin>();)
class HistogramDockerDockFactory : public KoDockFactoryBase {
public:
HistogramDockerDockFactory()
{
}
QString id() const override
{
return QString( "HistogramDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
HistogramDockerDock * dockWidget = new HistogramDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockRight;
}
private:
};
HistogramDockerPlugin::HistogramDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new HistogramDockerDockFactory());
}
HistogramDockerPlugin::~HistogramDockerPlugin()
{
m_view = 0;
}
#include "histogramdocker.moc"
diff --git a/plugins/dockers/histogram/histogramdocker.h b/plugins/dockers/histogram/histogramdocker.h
index 0e73af0b5e..4bb0047f43 100644
--- a/plugins/dockers/histogram/histogramdocker.h
+++ b/plugins/dockers/histogram/histogramdocker.h
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2016 Eugene Ingerman <geneing at gmail dot com>
*
* 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.
+ * the Free Software Foundation; 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 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 _HISTOGRAM_DOCKER_H_
#define _HISTOGRAM_DOCKER_H_
#include <QObject>
#include <QVariant>
class KisViewManager;
/**
* Template of view plugin
*/
class HistogramDockerPlugin : public QObject
{
Q_OBJECT
public:
HistogramDockerPlugin(QObject *parent, const QVariantList &);
~HistogramDockerPlugin() override;
private:
KisViewManager* m_view;
};
#endif
diff --git a/plugins/dockers/histogram/histogramdocker_dock.cpp b/plugins/dockers/histogram/histogramdocker_dock.cpp
index 4028c25c7f..e537851c74 100644
--- a/plugins/dockers/histogram/histogramdocker_dock.cpp
+++ b/plugins/dockers/histogram/histogramdocker_dock.cpp
@@ -1,111 +1,112 @@
/*
* Copyright (c) 2016 Eugene Ingerman <geneing at gmail dot com>
*
* 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.
+ * the Free Software Foundation; 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 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 "histogramdocker_dock.h"
#include <QLabel>
#include <QVBoxLayout>
#include <klocalizedstring.h>
#include "kis_canvas2.h"
#include <KisViewManager.h>
#include <kis_zoom_manager.h>
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_idle_watcher.h"
#include "histogramdockerwidget.h"
HistogramDockerDock::HistogramDockerDock()
: QDockWidget(i18n("Histogram")),
m_imageIdleWatcher(new KisIdleWatcher(250, this)),
m_canvas(0)
{
QWidget *page = new QWidget(this);
m_layout = new QVBoxLayout(page);
m_histogramWidget = new HistogramDockerWidget(this);
m_histogramWidget->setBackgroundRole(QPalette::AlternateBase);
m_histogramWidget->setAutoFillBackground(true); // paints background role before paint()
m_histogramWidget->setMinimumHeight(50);
//m_histogramWidget->setSmoothHistogram(false);
m_layout->addWidget(m_histogramWidget, 1);
setWidget(page);
connect(m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &HistogramDockerDock::updateHistogram);
}
void HistogramDockerDock::setCanvas(KoCanvasBase * canvas)
{
if (m_canvas == canvas)
return;
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_canvas->image()->disconnect(this);
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas) {
m_histogramWidget->setPaintDevice(m_canvas);
m_imageIdleWatcher->setTrackedImage(m_canvas->image());
connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)), this, SLOT(startUpdateCanvasProjection()), Qt::UniqueConnection);
connect(m_canvas->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SLOT(sigColorSpaceChanged(const KoColorSpace*)), Qt::UniqueConnection);
m_imageIdleWatcher->startCountdown();
}
}
void HistogramDockerDock::unsetCanvas()
{
setEnabled(false);
m_canvas = 0;
m_histogramWidget->setPaintDevice(m_canvas);
m_imageIdleWatcher->startCountdown();
}
void HistogramDockerDock::startUpdateCanvasProjection()
{
if (isVisible()) {
m_imageIdleWatcher->startCountdown();
}
}
void HistogramDockerDock::showEvent(QShowEvent *event)
{
Q_UNUSED(event);
m_imageIdleWatcher->startCountdown();
}
void HistogramDockerDock::sigColorSpaceChanged(const KoColorSpace */*cs*/)
{
if (isVisible()) {
m_imageIdleWatcher->startCountdown();
}
}
void HistogramDockerDock::updateHistogram()
{
if (isVisible()) {
m_histogramWidget->updateHistogram();
}
}
diff --git a/plugins/dockers/histogram/histogramdocker_dock.h b/plugins/dockers/histogram/histogramdocker_dock.h
index 583a95e70c..53c074d26a 100644
--- a/plugins/dockers/histogram/histogramdocker_dock.h
+++ b/plugins/dockers/histogram/histogramdocker_dock.h
@@ -1,59 +1,60 @@
/*
* Copyright (c) 2016 Eugene Ingerman <geneing at gmail dot com>
*
* 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.
+ * the Free Software Foundation; 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 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 _HISTOGRAM_DOCK_H_
#define _HISTOGRAM_DOCK_H_
#include <QDockWidget>
#include <QPointer>
#include <KoCanvasObserverBase.h>
#include <kis_paint_device.h>
#include <kis_canvas2.h>
class QVBoxLayout;
class KisIdleWatcher;
class KoHistogramProducer;
class HistogramDockerWidget;
class HistogramDockerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
HistogramDockerDock();
QString observerName() override { return "HistogramDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
public Q_SLOTS:
void startUpdateCanvasProjection();
void sigColorSpaceChanged(const KoColorSpace* cs);
void updateHistogram();
protected:
void showEvent(QShowEvent *event) override;
private:
QVBoxLayout *m_layout;
KisIdleWatcher *m_imageIdleWatcher;
HistogramDockerWidget *m_histogramWidget;
QPointer<KisCanvas2> m_canvas;
};
#endif
diff --git a/plugins/dockers/histogram/histogramdockerwidget.cpp b/plugins/dockers/histogram/histogramdockerwidget.cpp
index 30e51285ab..5c8e29d929 100644
--- a/plugins/dockers/histogram/histogramdockerwidget.cpp
+++ b/plugins/dockers/histogram/histogramdockerwidget.cpp
@@ -1,198 +1,199 @@
/*
* Copyright (c) 2016 Eugene Ingerman <geneing at gmail dot com>
*
* 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.
+ * the Free Software Foundation; 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 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 "histogramdockerwidget.h"
#include <QThread>
#include <QVector>
#include <limits>
#include <algorithm>
#include <QTime>
#include <QPainter>
#include <functional>
#include "KoChannelInfo.h"
#include "kis_paint_device.h"
#include "KoColorSpace.h"
#include "kis_iterator_ng.h"
#include "kis_canvas2.h"
HistogramDockerWidget::HistogramDockerWidget(QWidget *parent, const char *name, Qt::WindowFlags f)
: QLabel(parent, f), m_paintDevice(nullptr), m_smoothHistogram(true)
{
setObjectName(name);
}
HistogramDockerWidget::~HistogramDockerWidget()
{
}
void HistogramDockerWidget::setPaintDevice(KisCanvas2* canvas)
{
if (canvas) {
m_paintDevice = canvas->image()->projection();
m_bounds = canvas->image()->bounds();
} else {
m_paintDevice.clear();
m_bounds = QRect();
m_histogramData.clear();
}
}
void HistogramDockerWidget::updateHistogram()
{
if (!m_paintDevice.isNull()) {
KisPaintDeviceSP m_devClone = new KisPaintDevice(m_paintDevice->colorSpace());
m_devClone->makeCloneFrom(m_paintDevice, m_bounds);
HistogramComputationThread *workerThread = new HistogramComputationThread(m_devClone, m_bounds);
connect(workerThread, &HistogramComputationThread::resultReady, this, &HistogramDockerWidget::receiveNewHistogram);
connect(workerThread, &HistogramComputationThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
} else {
m_histogramData.clear();
update();
}
}
void HistogramDockerWidget::receiveNewHistogram(HistVector *histogramData)
{
m_histogramData = *histogramData;
update();
}
void HistogramDockerWidget::paintEvent(QPaintEvent *event)
{
if (m_paintDevice && !m_histogramData.empty()) {
int nBins = m_histogramData.at(0).size();
const KoColorSpace* cs = m_paintDevice->colorSpace();
QLabel::paintEvent(event);
QPainter painter(this);
painter.fillRect(0, 0, this->width(), this->height(), this->palette().dark().color());
painter.setPen(this->palette().light().color());
const int NGRID = 4;
for (int i = 0; i <= NGRID; ++i) {
painter.drawLine(this->width()*i / NGRID, 0., this->width()*i / NGRID, this->height());
painter.drawLine(0., this->height()*i / NGRID, this->width(), this->height()*i / NGRID);
}
unsigned int nChannels = cs->channelCount();
QList<KoChannelInfo *> channels = cs->channels();
unsigned int highest = 0;
//find the most populous bin in the histogram to scale it properly
for (int chan = 0; chan < channels.size(); chan++) {
if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) {
std::vector<quint32> histogramTemp = m_histogramData.at(chan);
//use 98th percentile, rather than max for better visual appearance
int nthPercentile = 2 * histogramTemp.size() / 100;
//unsigned int max = *std::max_element(m_histogramData.at(chan).begin(),m_histogramData.at(chan).end());
std::nth_element(histogramTemp.begin(),
histogramTemp.begin() + nthPercentile, histogramTemp.end(), std::greater<int>());
unsigned int max = *(histogramTemp.begin() + nthPercentile);
highest = std::max(max, highest);
}
}
painter.setWindow(QRect(-1, 0, nBins + 1, highest));
painter.setCompositionMode(QPainter::CompositionMode_Plus);
for (int chan = 0; chan < (int)nChannels; chan++) {
if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) {
QColor color = channels.at(chan)->color();
//special handling of grayscale color spaces. can't use color returned above.
if(cs->colorChannelCount()==1){
color = QColor(Qt::gray);
}
QColor fill_color = color;
fill_color.setAlphaF(.25);
painter.setBrush(fill_color);
QPen pen = QPen(color);
pen.setWidth(0);
painter.setPen(pen);
if (m_smoothHistogram) {
QPainterPath path;
path.moveTo(QPointF(-1, highest));
for (qint32 i = 0; i < nBins; ++i) {
float v = std::max((float)highest - m_histogramData[chan][i], 0.f);
path.lineTo(QPointF(i, v));
}
path.lineTo(QPointF(nBins + 1, highest));
path.closeSubpath();
painter.drawPath(path);
} else {
pen.setWidth(1);
painter.setPen(pen);
for (qint32 i = 0; i < nBins; ++i) {
float v = std::max((float)highest - m_histogramData[chan][i], 0.f);
painter.drawLine(QPointF(i, highest), QPointF(i, v));
}
}
}
}
}
}
void HistogramComputationThread::run()
{
const KoColorSpace *cs = m_dev->colorSpace();
quint32 channelCount = m_dev->channelCount();
quint32 pixelSize = m_dev->pixelSize();
quint32 imageSize = m_bounds.width() * m_bounds.height();
quint32 nSkip = 1 + (imageSize >> 20); //for speed use about 1M pixels for computing histograms
//allocate space for the histogram data
bins.resize((int)channelCount);
for (auto &bin : bins) {
bin.resize(std::numeric_limits<quint8>::max() + 1);
}
QRect bounds = m_dev->exactBounds();
if (bounds.isEmpty())
return;
quint32 toSkip = nSkip;
KisSequentialConstIterator it(m_dev, m_dev->exactBounds());
int numConseqPixels = it.nConseqPixels();
while (it.nextPixels(numConseqPixels)) {
numConseqPixels = it.nConseqPixels();
const quint8* pixel = it.rawDataConst();
for (int k = 0; k < numConseqPixels; ++k) {
if (--toSkip == 0) {
for (int chan = 0; chan < (int)channelCount; ++chan) {
bins[chan][cs->scaleToU8(pixel, chan)]++;
}
toSkip = nSkip;
}
pixel += pixelSize;
}
}
emit resultReady(&bins);
}
diff --git a/plugins/dockers/histogram/histogramdockerwidget.h b/plugins/dockers/histogram/histogramdockerwidget.h
index a482583c11..156db1fad2 100644
--- a/plugins/dockers/histogram/histogramdockerwidget.h
+++ b/plugins/dockers/histogram/histogramdockerwidget.h
@@ -1,74 +1,75 @@
/*
* Copyright (c) 2016 Eugene Ingerman <geneing at gmail dot com>
*
* 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.
+ * the Free Software Foundation; 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 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 HISTOGRAMDOCKERWIDGET_H
#define HISTOGRAMDOCKERWIDGET_H
#include <QObject>
#include <QWidget>
#include <QLabel>
#include <QThread>
#include "kis_types.h"
#include <vector>
class KisCanvas2;
typedef std::vector<std::vector<quint32> > HistVector; //Don't use QVector here - it's too slow for this purpose
class HistogramComputationThread : public QThread
{
Q_OBJECT
public:
HistogramComputationThread(KisPaintDeviceSP _dev, const QRect& _bounds) : m_dev(_dev), m_bounds(_bounds)
{}
void run() override;
Q_SIGNALS:
void resultReady(HistVector*);
private:
KisPaintDeviceSP m_dev;
QRect m_bounds;
HistVector bins;
};
class HistogramDockerWidget : public QLabel
{
Q_OBJECT
public:
HistogramDockerWidget(QWidget *parent = 0, const char *name = 0, Qt::WindowFlags f = 0);
~HistogramDockerWidget() override;
void setPaintDevice(KisCanvas2* canvas);
void paintEvent(QPaintEvent *event) override;
public Q_SLOTS:
void updateHistogram();
void receiveNewHistogram(HistVector*);
private:
KisPaintDeviceSP m_paintDevice;
HistVector m_histogramData;
QRect m_bounds;
bool m_smoothHistogram;
};
#endif // HISTOGRAMDOCKERWIDGET_H
diff --git a/plugins/dockers/layerdocker/LayerBox.cpp b/plugins/dockers/layerdocker/LayerBox.cpp
index f425e3c3dc..aebf3caca7 100644
--- a/plugins/dockers/layerdocker/LayerBox.cpp
+++ b/plugins/dockers/layerdocker/LayerBox.cpp
@@ -1,1081 +1,1101 @@
/*
* LayerBox.cc - part of Krita aka Krayon aka KimageShop
*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (C) 2006 Gábor Lehel <illissius@gmail.com>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* 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 "LayerBox.h"
#include <QToolButton>
#include <QLayout>
#include <QMouseEvent>
#include <QPainter>
#include <QPoint>
#include <QRect>
#include <QString>
#include <QToolTip>
#include <QWidget>
#include <QComboBox>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QPixmap>
#include <QList>
#include <QVector>
#include <QLabel>
#include <QMenu>
#include <QWidgetAction>
#include <QProxyStyle>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <kis_icon.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KisDocument.h>
#include <kis_types.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_layer.h>
#include <kis_group_layer.h>
#include <kis_mask.h>
#include <kis_node.h>
#include <kis_base_node.h>
#include <kis_composite_ops_model.h>
#include <kis_keyframe_channel.h>
#include <kis_image_animation_interface.h>
#include <KoProperties.h>
#include <kis_action.h>
#include "kis_action_manager.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_slider_spin_box.h"
#include "KisViewManager.h"
#include "kis_node_manager.h"
#include "kis_node_model.h"
#include "canvas/kis_canvas2.h"
#include "kis_dummies_facade_base.h"
#include "kis_shape_controller.h"
#include "kis_selection_mask.h"
#include "kis_config.h"
#include "KisView.h"
#include "krita_utils.h"
#include "kis_color_label_selector_widget.h"
#include "kis_signals_blocker.h"
#include "kis_color_filter_combo.h"
#include "kis_node_filter_proxy_model.h"
#include "kis_selection.h"
#include "kis_processing_applicator.h"
#include "commands/kis_set_global_selection_command.h"
#include "KisSelectionActionsAdapter.h"
#include "kis_layer_utils.h"
#include "ui_WdgLayerBox.h"
#include "NodeView.h"
#include "SyncButtonAndAction.h"
class LayerBoxStyle : public QProxyStyle
{
public:
LayerBoxStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {}
void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const
{
if (element == QStyle::PE_IndicatorItemViewItemDrop)
{
QColor color(widget->palette().color(QPalette::Highlight).lighter());
if (option->rect.height() == 0) {
QBrush brush(color);
QRect r(option->rect);
r.setTop(r.top() - 2);
r.setBottom(r.bottom() + 2);
painter->fillRect(r, brush);
} else {
color.setAlpha(200);
QBrush brush(color);
painter->fillRect(option->rect, brush);
}
}
else
{
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
};
inline void LayerBox::connectActionToButton(KisViewManager* viewManager, QAbstractButton *button, const QString &id)
{
if (!viewManager || !button) return;
KisAction *action = viewManager->actionManager()->actionByName(id);
if (!action) return;
connect(button, SIGNAL(clicked()), action, SLOT(trigger()));
connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool)));
connect(viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons()));
}
inline void LayerBox::addActionToMenu(QMenu *menu, const QString &id)
{
if (m_canvas) {
menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id));
}
}
LayerBox::LayerBox()
: QDockWidget(i18n("Layers"))
, m_canvas(0)
, m_wdgLayerBox(new Ui_WdgLayerBox)
, m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE)
, m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE)
, m_thumbnailSizeCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
{
KisConfig cfg(false);
QWidget* mainWidget = new QWidget(this);
setWidget(mainWidget);
m_opacityDelayTimer.setSingleShot(true);
m_wdgLayerBox->setupUi(mainWidget);
m_wdgLayerBox->listLayers->setStyle(new LayerBoxStyle(m_wdgLayerBox->listLayers->style()));
connect(m_wdgLayerBox->listLayers,
SIGNAL(contextMenuRequested(QPoint,QModelIndex)),
this, SLOT(slotContextMenuRequested(QPoint,QModelIndex)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(collapsed(QModelIndex)), SLOT(slotCollapsed(QModelIndex)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(expanded(QModelIndex)), SLOT(slotExpanded(QModelIndex)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(selectionChanged(QModelIndexList)), SLOT(selectionChanged(QModelIndexList)));
slotUpdateIcons();
m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnLower->setEnabled(false);
m_wdgLayerBox->bnRaise->setEnabled(false);
if (cfg.sliderLabels()) {
m_wdgLayerBox->opacityLabel->hide();
m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity")));
}
m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0);
m_wdgLayerBox->doubleOpacity->setSuffix("%");
connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal)));
connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged()));
connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int)));
m_newLayerMenu = new QMenu(this);
m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu);
m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup);
m_nodeModel = new KisNodeModel(this);
m_filteringModel = new KisNodeFilterProxyModel(this);
m_filteringModel->setNodeModel(m_nodeModel);
/**
* Connect model updateUI() to enable/disable controls.
* Note: nodeActivated() is connected separately in setImage(), because
* it needs particular order of calls: first the connection to the
* node manager should be called, then updateUI()
*/
connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset()));
KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this);
showGlobalSelectionMask->setObjectName("show-global-selection-mask");
showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE);
showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in <b>Layers</b> docker"));
showGlobalSelectionMask->setCheckable(true);
connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool)));
m_actions.append(showGlobalSelectionMask);
showGlobalSelectionMask->setChecked(cfg.showGlobalSelection());
m_colorSelector = new KisColorLabelSelectorWidget(this);
connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int)));
m_colorSelectorAction = new QWidgetAction(this);
m_colorSelectorAction->setDefaultWidget(m_colorSelector);
connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_colorLabelCompressor, SLOT(start()));
m_wdgLayerBox->listLayers->setModel(m_filteringModel);
// this connection should be done *after* the setModel() call to
// happen later than the internal selection model
connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved,
this, &LayerBox::slotAboutToRemoveRows);
connect(m_wdgLayerBox->cmbFilter, SIGNAL(selectedColorsChanged()), SLOT(updateLayerFiltering()));
setEnabled(false);
connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail()));
connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels()));
// set up the configure menu for changing thumbnail size
QMenu* configureMenu = new QMenu(this);
configureMenu->setStyleSheet("margin: 6px");
configureMenu->addSection(i18n("Thumbnail Size"));
m_wdgLayerBox->configureLayerDockerToolbar->setMenu(configureMenu);
m_wdgLayerBox->configureLayerDockerToolbar->setIcon(KisIconUtils::loadIcon("configure"));
m_wdgLayerBox->configureLayerDockerToolbar->setPopupMode(QToolButton::InstantPopup);
// add horizontal slider
thumbnailSizeSlider = new QSlider(this);
thumbnailSizeSlider->setOrientation(Qt::Horizontal);
thumbnailSizeSlider->setRange(20, 80);
thumbnailSizeSlider->setValue(cfg.layerThumbnailSize(false)); // grab this from the kritarc
thumbnailSizeSlider->setMinimumHeight(20);
thumbnailSizeSlider->setMinimumWidth(40);
thumbnailSizeSlider->setTickInterval(5);
QWidgetAction *sliderAction= new QWidgetAction(this);
sliderAction->setDefaultWidget(thumbnailSizeSlider);
configureMenu->addAction(sliderAction);
connect(thumbnailSizeSlider, SIGNAL(sliderMoved(int)), &m_thumbnailSizeCompressor, SLOT(start()));
connect(&m_thumbnailSizeCompressor, SIGNAL(timeout()), SLOT(slotUpdateThumbnailIconSize()));
+
}
LayerBox::~LayerBox()
{
delete m_wdgLayerBox;
}
void expandNodesRecursively(KisNodeSP root, QPointer<KisNodeFilterProxyModel> filteringModel, NodeView *nodeView)
{
if (!root) return;
if (filteringModel.isNull()) return;
if (!nodeView) return;
nodeView->blockSignals(true);
KisNodeSP node = root->firstChild();
while (node) {
QModelIndex idx = filteringModel->indexFromNode(node);
if (idx.isValid()) {
nodeView->setExpanded(idx, !node->collapsed());
}
if (!node->collapsed() && node->childCount() > 0) {
expandNodesRecursively(node, filteringModel, nodeView);
}
node = node->nextSibling();
}
nodeView->blockSignals(false);
}
void LayerBox::slotAddLayerBnClicked()
{
if (m_canvas) {
KisNodeList nodes = m_nodeManager->selectedNodes();
if (nodes.size() == 1) {
KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("add_new_paint_layer");
action->trigger();
} else {
KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("create_quick_group");
action->trigger();
}
}
}
void LayerBox::setViewManager(KisViewManager* kisview)
{
m_nodeManager = kisview->nodeManager();
Q_FOREACH (KisAction *action, m_actions) {
kisview->actionManager()->
addAction(action->objectName(),
action);
}
connect(m_wdgLayerBox->bnAdd, SIGNAL(clicked()), this, SLOT(slotAddLayerBnClicked()));
connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer");
KisActionManager *actionManager = kisview->actionManager();
KisAction *action = actionManager->createAction("RenameCurrentLayer");
Q_ASSERT(action);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode()));
m_propertiesAction = actionManager->createAction("layer_properties");
Q_ASSERT(m_propertiesAction);
new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this);
connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked()));
m_removeAction = actionManager->createAction("remove_layer");
Q_ASSERT(m_removeAction);
new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this);
connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked()));
action = actionManager->createAction("move_layer_up");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked()));
action = actionManager->createAction("move_layer_down");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked()));
+
+ m_changeCloneSourceAction = actionManager->createAction("set-copy-from");
+ Q_ASSERT(m_changeCloneSourceAction);
+ connect(m_changeCloneSourceAction, &KisAction::triggered,
+ this, &LayerBox::slotChangeCloneSourceClicked);
}
void LayerBox::setCanvas(KoCanvasBase *canvas)
{
if (m_canvas == canvas)
return;
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0);
m_selectionActionsAdapter.reset();
if (m_image) {
KisImageAnimationInterface *animation = m_image->animationInterface();
animation->disconnect(this);
}
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas) {
m_image = m_canvas->image();
connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start()));
KisDocument* doc = static_cast<KisDocument*>(m_canvas->imageView()->document());
KisShapeController *kritaShapeController =
dynamic_cast<KisShapeController*>(doc->shapeController());
KisDummiesFacadeBase *kritaDummiesFacade =
static_cast<KisDummiesFacadeBase*>(kritaShapeController);
m_selectionActionsAdapter.reset(new KisSelectionActionsAdapter(m_canvas->viewManager()->selectionManager()));
m_nodeModel->setDummiesFacade(kritaDummiesFacade,
m_image,
kritaShapeController,
m_selectionActionsAdapter.data(),
m_nodeManager);
connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()));
connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged()));
// cold start
if (m_nodeManager) {
setCurrentNode(m_nodeManager->activeNode());
// Connection KisNodeManager -> LayerBox
connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)),
this, SLOT(setCurrentNode(KisNodeSP)));
connect(m_nodeManager,
SIGNAL(sigUiNeedChangeSelectedNodes(QList<KisNodeSP>)),
SLOT(slotNodeManagerChangedSelection(QList<KisNodeSP>)));
}
else {
setCurrentNode(m_canvas->imageView()->currentNode());
}
// Connection LayerBox -> KisNodeManager (isolate layer)
connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()),
m_nodeManager, SLOT(toggleIsolateActiveNode()));
KisImageAnimationInterface *animation = m_image->animationInterface();
connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &LayerBox::slotImageTimeChanged);
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex());
updateAvailableLabels();
addActionToMenu(m_newLayerMenu, "add_new_paint_layer");
addActionToMenu(m_newLayerMenu, "add_new_group_layer");
addActionToMenu(m_newLayerMenu, "add_new_clone_layer");
addActionToMenu(m_newLayerMenu, "add_new_shape_layer");
addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer");
addActionToMenu(m_newLayerMenu, "add_new_fill_layer");
addActionToMenu(m_newLayerMenu, "add_new_file_layer");
m_newLayerMenu->addSeparator();
addActionToMenu(m_newLayerMenu, "add_new_transparency_mask");
addActionToMenu(m_newLayerMenu, "add_new_filter_mask");
addActionToMenu(m_newLayerMenu, "add_new_colorize_mask");
addActionToMenu(m_newLayerMenu, "add_new_transform_mask");
addActionToMenu(m_newLayerMenu, "add_new_selection_mask");
}
}
void LayerBox::unsetCanvas()
{
setEnabled(false);
if (m_canvas) {
m_newLayerMenu->clear();
}
m_filteringModel->unsetDummiesFacade();
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
m_canvas = 0;
}
void LayerBox::notifyImageDeleted()
{
setCanvas(0);
}
void LayerBox::updateUI()
{
if (!m_canvas) return;
if (!m_nodeManager) return;
KisNodeSP activeNode = m_nodeManager->activeNode();
if (activeNode != m_activeNode) {
if( !m_activeNode.isNull() )
m_activeNode->disconnect(this);
m_activeNode = activeNode;
if (activeNode) {
KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false);
if (opacityChannel) {
watchOpacityChannel(opacityChannel);
} else {
watchOpacityChannel(0);
connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &LayerBox::slotKeyframeChannelAdded);
}
}
}
m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false));
m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false));
m_wdgLayerBox->cmbComposite->validate(m_image->colorSpace());
if (activeNode) {
if (activeNode->inherits("KisColorizeMask") ||
activeNode->inherits("KisLayer")) {
m_wdgLayerBox->doubleOpacity->setEnabled(true);
if (!m_wdgLayerBox->doubleOpacity->isDragging()) {
slotSetOpacity(activeNode->opacity() * 100.0 / 255);
}
const KoCompositeOp* compositeOp = activeNode->compositeOp();
if (compositeOp) {
slotSetCompositeOp(compositeOp);
} else {
m_wdgLayerBox->cmbComposite->setEnabled(false);
}
const KisGroupLayer *group = qobject_cast<const KisGroupLayer*>(activeNode.data());
bool compositeSelectionActive = !(group && group->passThroughMode());
m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive);
} else if (activeNode->inherits("KisMask")) {
m_wdgLayerBox->cmbComposite->setEnabled(false);
m_wdgLayerBox->doubleOpacity->setEnabled(false);
}
}
}
/**
* This method is called *only* when non-GUI code requested the
* change of the current node
*/
void LayerBox::setCurrentNode(KisNodeSP node)
{
m_filteringModel->setActiveNode(node);
QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex();
m_filteringModel->setData(index, true, KisNodeModel::ActiveRole);
updateUI();
}
void LayerBox::slotModelReset()
{
if(m_nodeModel->hasDummiesFacade()) {
QItemSelection selection;
Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) {
const QModelIndex &idx = m_filteringModel->indexFromNode(node);
if(idx.isValid()){
QItemSelectionRange selectionRange(idx);
selection << selectionRange;
}
}
m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
}
updateUI();
}
void LayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp)
{
KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id());
m_wdgLayerBox->cmbComposite->blockSignals(true);
m_wdgLayerBox->cmbComposite->selectCompositeOp(opId);
m_wdgLayerBox->cmbComposite->blockSignals(false);
}
// range: 0-100
void LayerBox::slotSetOpacity(double opacity)
{
Q_ASSERT(opacity >= 0 && opacity <= 100);
m_wdgLayerBox->doubleOpacity->blockSignals(true);
m_wdgLayerBox->doubleOpacity->setValue(opacity);
m_wdgLayerBox->doubleOpacity->blockSignals(false);
}
void LayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
KisNodeSP activeNode = m_nodeManager->activeNode();
if (nodes.isEmpty() || !activeNode) return;
if (m_canvas) {
QMenu menu;
const bool singleLayer = nodes.size() == 1;
if (index.isValid()) {
menu.addAction(m_propertiesAction);
if (singleLayer) {
addActionToMenu(&menu, "layer_style");
}
+ Q_FOREACH(KisNodeSP node, nodes) {
+ if (node && node->inherits("KisCloneLayer")) {
+ menu.addAction(m_changeCloneSourceAction);
+ break;
+ }
+ }
+
{
KisSignalsBlocker b(m_colorSelector);
m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1);
}
menu.addAction(m_colorSelectorAction);
menu.addSeparator();
addActionToMenu(&menu, "cut_layer_clipboard");
addActionToMenu(&menu, "copy_layer_clipboard");
addActionToMenu(&menu, "paste_layer_from_clipboard");
menu.addAction(m_removeAction);
addActionToMenu(&menu, "duplicatelayer");
addActionToMenu(&menu, "merge_layer");
if (singleLayer) {
addActionToMenu(&menu, "flatten_image");
addActionToMenu(&menu, "flatten_layer");
}
menu.addSeparator();
QMenu *selectMenu = menu.addMenu(i18n("&Select"));
addActionToMenu(selectMenu, "select_all_layers");
addActionToMenu(selectMenu, "select_visible_layers");
addActionToMenu(selectMenu, "select_invisible_layers");
addActionToMenu(selectMenu, "select_locked_layers");
addActionToMenu(selectMenu, "select_unlocked_layers");
QMenu *groupMenu = menu.addMenu(i18n("&Group"));
addActionToMenu(groupMenu, "create_quick_group");
addActionToMenu(groupMenu, "create_quick_clipping_group");
addActionToMenu(groupMenu, "quick_ungroup");
QMenu *locksMenu = menu.addMenu(i18n("&Toggle Locks && Visibility"));
addActionToMenu(locksMenu, "toggle_layer_visibility");
addActionToMenu(locksMenu, "toggle_layer_lock");
addActionToMenu(locksMenu, "toggle_layer_inherit_alpha");
addActionToMenu(locksMenu, "toggle_layer_alpha_lock");
if (singleLayer) {
QMenu *addLayerMenu = menu.addMenu(i18n("&Add"));
addActionToMenu(addLayerMenu, "add_new_transparency_mask");
addActionToMenu(addLayerMenu, "add_new_filter_mask");
addActionToMenu(addLayerMenu, "add_new_colorize_mask");
addActionToMenu(addLayerMenu, "add_new_transform_mask");
addActionToMenu(addLayerMenu, "add_new_selection_mask");
QMenu *convertToMenu = menu.addMenu(i18n("&Convert"));
addActionToMenu(convertToMenu, "convert_to_paint_layer");
addActionToMenu(convertToMenu, "convert_to_transparency_mask");
addActionToMenu(convertToMenu, "convert_to_filter_mask");
addActionToMenu(convertToMenu, "convert_to_selection_mask");
addActionToMenu(convertToMenu, "convert_to_file_layer");
QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha"));
addActionToMenu(splitAlphaMenu, "split_alpha_into_mask");
addActionToMenu(splitAlphaMenu, "split_alpha_write");
addActionToMenu(splitAlphaMenu, "split_alpha_save_merged");
}
menu.addSeparator();
addActionToMenu(&menu, "show_in_timeline");
if (singleLayer) {
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node && !node->inherits("KisTransformMask")) {
addActionToMenu(&menu, "isolate_layer");
}
addActionToMenu(&menu, "selectopaque");
+
}
}
menu.exec(pos);
}
}
void LayerBox::slotMinimalView()
{
m_wdgLayerBox->listLayers->setDisplayMode(NodeView::MinimalMode);
}
void LayerBox::slotDetailedView()
{
m_wdgLayerBox->listLayers->setDisplayMode(NodeView::DetailedMode);
}
void LayerBox::slotThumbnailView()
{
m_wdgLayerBox->listLayers->setDisplayMode(NodeView::ThumbnailMode);
}
void LayerBox::slotRmClicked()
{
if (!m_canvas) return;
m_nodeManager->removeNode();
}
void LayerBox::slotRaiseClicked()
{
if (!m_canvas) return;
m_nodeManager->raiseNode();
}
void LayerBox::slotLowerClicked()
{
if (!m_canvas) return;
m_nodeManager->lowerNode();
}
void LayerBox::slotPropertiesClicked()
{
if (!m_canvas) return;
if (KisNodeSP active = m_nodeManager->activeNode()) {
m_nodeManager->nodeProperties(active);
}
}
+void LayerBox::slotChangeCloneSourceClicked()
+{
+ if (!m_canvas) return;
+ m_nodeManager->changeCloneSource();
+}
+
void LayerBox::slotCompositeOpChanged(int index)
{
Q_UNUSED(index);
if (!m_canvas) return;
QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id();
m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp));
}
void LayerBox::slotOpacityChanged()
{
if (!m_canvas) return;
m_blockOpacityUpdate = true;
m_nodeManager->nodeOpacityChanged(m_newOpacity);
m_blockOpacityUpdate = false;
}
void LayerBox::slotOpacitySliderMoved(qreal opacity)
{
m_newOpacity = opacity;
m_opacityDelayTimer.start(200);
}
void LayerBox::slotCollapsed(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(true);
}
}
void LayerBox::slotExpanded(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(false);
}
}
void LayerBox::slotSelectOpaque()
{
if (!m_canvas) return;
QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque");
if (action) {
action->trigger();
}
}
void LayerBox::slotNodeCollapsedChanged()
{
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
}
inline bool isSelectionMask(KisNodeSP node)
{
return dynamic_cast<KisSelectionMask*>(node.data());
}
KisNodeSP LayerBox::findNonHidableNode(KisNodeSP startNode)
{
if (KisNodeManager::isNodeHidden(startNode, true) &&
startNode->parent() &&
!startNode->parent()->parent()) {
KisNodeSP node = startNode->prevSibling();
while (node && KisNodeManager::isNodeHidden(node, true)) {
node = node->prevSibling();
}
if (!node) {
node = startNode->nextSibling();
while (node && KisNodeManager::isNodeHidden(node, true)) {
node = node->nextSibling();
}
}
if (!node) {
node = m_image->root()->lastChild();
while (node && KisNodeManager::isNodeHidden(node, true)) {
node = node->prevSibling();
}
}
KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!");
startNode = node;
}
return startNode;
}
void LayerBox::slotEditGlobalSelection(bool showSelections)
{
KisNodeSP lastActiveNode = m_nodeManager->activeNode();
KisNodeSP activateNode = lastActiveNode;
KisSelectionMaskSP globalSelectionMask;
if (!showSelections) {
activateNode = findNonHidableNode(activateNode);
}
m_nodeModel->setShowGlobalSelection(showSelections);
globalSelectionMask = m_image->rootLayer()->selectionMask();
// try to find deactivated, but visible masks
if (!globalSelectionMask) {
KoProperties properties;
properties.setProperty("visible", true);
QList<KisNodeSP> masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties);
if (!masks.isEmpty()) {
globalSelectionMask = dynamic_cast<KisSelectionMask*>(masks.first().data());
}
}
// try to find at least any selection mask
if (!globalSelectionMask) {
KoProperties properties;
QList<KisNodeSP> masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties);
if (!masks.isEmpty()) {
globalSelectionMask = dynamic_cast<KisSelectionMask*>(masks.first().data());
}
}
if (globalSelectionMask) {
if (showSelections) {
activateNode = globalSelectionMask;
}
}
if (activateNode != lastActiveNode) {
m_nodeManager->slotNonUiActivatedNode(activateNode);
} else if (lastActiveNode) {
setCurrentNode(lastActiveNode);
}
if (showSelections && !globalSelectionMask) {
KisProcessingApplicator applicator(m_image, 0,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Quick Selection Mask"));
applicator.applyCommand(
new KisLayerUtils::KeepNodesSelectedCommand(
m_nodeManager->selectedNodes(), KisNodeList(),
lastActiveNode, 0, m_image, false),
KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KisSetEmptyGlobalSelectionCommand(m_image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KisLayerUtils::SelectGlobalSelectionMask(m_image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.end();
} else if (!showSelections &&
globalSelectionMask &&
globalSelectionMask->selection()->selectedRect().isEmpty()) {
KisProcessingApplicator applicator(m_image, 0,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Cancel Quick Selection Mask"));
applicator.applyCommand(new KisSetGlobalSelectionCommand(m_image, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
applicator.end();
}
}
void LayerBox::selectionChanged(const QModelIndexList selection)
{
if (!m_nodeManager) return;
/**
* When the user clears the extended selection by clicking on the
* empty area of the docker, the selection should be reset on to
* the active layer, which might be even unselected(!).
*/
if (selection.isEmpty() && m_nodeManager->activeNode()) {
QModelIndex selectedIndex =
m_filteringModel->indexFromNode(m_nodeManager->activeNode());
m_wdgLayerBox->listLayers->selectionModel()->
setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect);
return;
}
QList<KisNodeSP> selectedNodes;
Q_FOREACH (const QModelIndex &idx, selection) {
selectedNodes << m_filteringModel->nodeFromIndex(idx);
}
m_nodeManager->slotSetSelectedNodes(selectedNodes);
updateUI();
}
void LayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end)
{
/**
* Qt has changed its behavior when deleting an item. Previously
* the selection priority was on the next item in the list, and
* now it has shanged to the previous item. Here we just adjust
* the selected item after the node removal. Please take care that
* this method overrides what was done by the corresponding method
* of QItemSelectionModel, which *has already done* its work. That
* is why we use (start - 1) and (end + 1) in the activation
* condition.
*
* See bug: https://bugs.kde.org/show_bug.cgi?id=345601
*/
QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex();
QAbstractItemModel *model = m_filteringModel;
if (currentIndex.isValid() && parent == currentIndex.parent()
&& currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) {
QModelIndex old = currentIndex;
if (model && end < model->rowCount(parent) - 1) // there are rows left below the change
currentIndex = model->index(end + 1, old.column(), parent);
else if (start > 0) // there are rows left above the change
currentIndex = model->index(start - 1, old.column(), parent);
else // there are no rows left in the table
currentIndex = QModelIndex();
if (currentIndex.isValid() && currentIndex != old) {
m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex);
}
}
}
void LayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes)
{
if (!m_nodeManager) return;
QModelIndexList newSelection;
Q_FOREACH(KisNodeSP node, nodes) {
newSelection << m_filteringModel->indexFromNode(node);
}
QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel();
if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) {
return;
}
QItemSelection selection;
Q_FOREACH(const QModelIndex &idx, newSelection) {
selection.select(idx, idx);
}
model->select(selection, QItemSelectionModel::ClearAndSelect);
}
void LayerBox::updateThumbnail()
{
m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex());
}
void LayerBox::slotRenameCurrentNode()
{
m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex());
}
void LayerBox::slotColorLabelChanged(int label)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
Q_FOREACH(KisNodeSP node, nodes) {
auto applyLabelFunc =
[label](KisNodeSP node) {
node->setColorLabelIndex(label);
};
KisLayerUtils::recursiveApplyNodes(node, applyLabelFunc);
}
}
void LayerBox::updateAvailableLabels()
{
if (!m_image) return;
m_wdgLayerBox->cmbFilter->updateAvailableLabels(m_image->root());
}
void LayerBox::updateLayerFiltering()
{
m_filteringModel->setAcceptedLabels(m_wdgLayerBox->cmbFilter->selectedColors());
}
void LayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel)
{
if (channel->id() == KisKeyframeChannel::Opacity.id()) {
watchOpacityChannel(channel);
}
}
void LayerBox::watchOpacityChannel(KisKeyframeChannel *channel)
{
if (m_opacityChannel) {
m_opacityChannel->disconnect(this);
}
m_opacityChannel = channel;
if (m_opacityChannel) {
connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
}
}
void LayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe)
{
Q_UNUSED(keyframe);
if (m_blockOpacityUpdate) return;
updateUI();
}
void LayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime)
{
Q_UNUSED(fromTime);
slotOpacityKeyframeChanged(keyframe);
}
void LayerBox::slotImageTimeChanged(int time)
{
Q_UNUSED(time);
updateUI();
}
void LayerBox::slotUpdateIcons() {
m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer"));
m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr"));
m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer"));
m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown"));
m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties"));
m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer"));
// call child function about needing to update icons
m_wdgLayerBox->listLayers->slotUpdateIcons();
}
void LayerBox::slotUpdateThumbnailIconSize()
{
KisConfig cfg(false);
cfg.setLayerThumbnailSize(thumbnailSizeSlider->value());
// this is a hack to force the layers list to update its display and
// re-layout all the layers with the new thumbnail size
resize(this->width()+1, this->height()+1);
resize(this->width()-1, this->height()-1);
}
#include "moc_LayerBox.cpp"
diff --git a/plugins/dockers/layerdocker/LayerBox.h b/plugins/dockers/layerdocker/LayerBox.h
index 8bef83e1c2..61e6d6ea75 100644
--- a/plugins/dockers/layerdocker/LayerBox.h
+++ b/plugins/dockers/layerdocker/LayerBox.h
@@ -1,200 +1,202 @@
/*
* LayerBox.h - part of Krita aka Krayon aka KimageShop
*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (C) 2006 Gábor Lehel <illissius@gmail.com>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007-2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_LAYERBOX_H
#define KIS_LAYERBOX_H
#include <QFrame>
#include <QList>
#include <QDockWidget>
#include <QPointer>
#include <QTimer>
#include <kis_debug.h>
#include <KoColorSpace.h>
#include <KoDockFactoryBase.h>
#include <kis_types.h>
#include "kis_action.h"
#include "KisViewManager.h"
#include "kis_mainwindow_observer.h"
#include "kis_signal_compressor.h"
#include <QSlider>
class QModelIndex;
typedef QList<QModelIndex> QModelIndexList;
class QMenu;
class QAbstractButton;
class KoCompositeOp;
class KisCanvas2;
class KisNodeModel;
class KisNodeFilterProxyModel;
class Ui_WdgLayerBox;
class KisNodeJugglerCompressed;
class KisColorLabelSelectorWidget;
class QWidgetAction;
class KisKeyframeChannel;
class KisSelectionActionsAdapter;
/**
* A widget that shows a visualization of the layer structure.
*
* The center of the layer box is KisNodeModel, which shows the actual layers.
* This widget adds docking functionality and command buttons.
*
*/
class LayerBox : public QDockWidget, public KisMainwindowObserver
{
Q_OBJECT
public:
LayerBox();
~LayerBox() override;
QString observerName() override { return "LayerBox"; }
/// reimplemented from KisMainwindowObserver
void setViewManager(KisViewManager* kisview) override;
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
private Q_SLOTS:
void notifyImageDeleted();
void slotContextMenuRequested(const QPoint &pos, const QModelIndex &index);
void slotMinimalView();
void slotDetailedView();
void slotThumbnailView();
// From the node manager to the layerbox
void slotSetCompositeOp(const KoCompositeOp* compositeOp);
void slotSetOpacity(double opacity);
void updateUI();
void setCurrentNode(KisNodeSP node);
void slotModelReset();
// from the layerbox to the node manager
void slotRmClicked();
void slotRaiseClicked();
void slotLowerClicked();
void slotPropertiesClicked();
+ void slotChangeCloneSourceClicked();
void slotCompositeOpChanged(int index);
void slotOpacityChanged();
void slotOpacitySliderMoved(qreal opacity);
void slotCollapsed(const QModelIndex &index);
void slotExpanded(const QModelIndex &index);
void slotSelectOpaque();
void slotNodeCollapsedChanged();
void slotEditGlobalSelection(bool showSelections);
void slotRenameCurrentNode();
void slotAboutToRemoveRows(const QModelIndex &parent, int first, int last);
void selectionChanged(const QModelIndexList selection);
void slotNodeManagerChangedSelection(const QList<KisNodeSP> &nodes);
void slotColorLabelChanged(int index);
void slotUpdateIcons();
void slotAddLayerBnClicked();
void updateThumbnail();
void updateAvailableLabels();
void updateLayerFiltering();
void slotUpdateThumbnailIconSize();
// Opacity keyframing
void slotKeyframeChannelAdded(KisKeyframeChannel *channel);
void slotOpacityKeyframeChanged(KisKeyframeSP keyframe);
void slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime);
void slotImageTimeChanged(int time);
private:
inline void connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id);
inline void addActionToMenu(QMenu *menu, const QString &id);
void watchOpacityChannel(KisKeyframeChannel *channel);
KisNodeSP findNonHidableNode(KisNodeSP startNode);
private:
QPointer<KisCanvas2> m_canvas;
QScopedPointer<KisSelectionActionsAdapter> m_selectionActionsAdapter;
QMenu *m_newLayerMenu;
KisImageWSP m_image;
QPointer<KisNodeModel> m_nodeModel;
QPointer<KisNodeFilterProxyModel> m_filteringModel;
QPointer<KisNodeManager> m_nodeManager;
QPointer<KisColorLabelSelectorWidget> m_colorSelector;
QPointer<QWidgetAction> m_colorSelectorAction;
Ui_WdgLayerBox* m_wdgLayerBox;
QTimer m_opacityDelayTimer;
int m_newOpacity;
QVector<KisAction*> m_actions;
KisAction* m_removeAction;
KisAction* m_propertiesAction;
+ KisAction* m_changeCloneSourceAction;
KisSignalCompressor m_thumbnailCompressor;
KisSignalCompressor m_colorLabelCompressor;
KisSignalCompressor m_thumbnailSizeCompressor;
QSlider* thumbnailSizeSlider;
KisNodeSP m_activeNode;
QPointer<KisKeyframeChannel> m_opacityChannel;
bool m_blockOpacityUpdate {false};
};
class LayerBoxFactory : public KoDockFactoryBase
{
public:
LayerBoxFactory() { }
QString id() const override {
return QString("KisLayerBox");
}
QDockWidget* createDockWidget() override {
LayerBox * dockWidget = new LayerBox();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
#endif // KIS_LAYERBOX_H
diff --git a/plugins/dockers/layerdocker/LayerDocker.cpp b/plugins/dockers/layerdocker/LayerDocker.cpp
index cbef015e08..ba5683c8e3 100644
--- a/plugins/dockers/layerdocker/LayerDocker.cpp
+++ b/plugins/dockers/layerdocker/LayerDocker.cpp
@@ -1,41 +1,42 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 "LayerDocker.h"
#include <kpluginfactory.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "kis_debug.h"
#include "LayerBox.h"
K_PLUGIN_FACTORY_WITH_JSON(KritaLayerDockerPluginFactory, "kritalayerdocker.json", registerPlugin<KritaLayerDockerPlugin>();)
KritaLayerDockerPlugin::KritaLayerDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new LayerBoxFactory());
}
KritaLayerDockerPlugin::~KritaLayerDockerPlugin()
{
}
#include "LayerDocker.moc"
diff --git a/plugins/dockers/layerdocker/LayerDocker.h b/plugins/dockers/layerdocker/LayerDocker.h
index e7c0729930..be323bd581 100644
--- a/plugins/dockers/layerdocker/LayerDocker.h
+++ b/plugins/dockers/layerdocker/LayerDocker.h
@@ -1,37 +1,38 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 _DEFAULT_DOCKERS_H
#define _DEFAULT_DOCKERS_H
#include <QObject>
#include <QVariant>
/**
* Template of view plugin
*/
class KritaLayerDockerPlugin : public QObject
{
Q_OBJECT
public:
KritaLayerDockerPlugin(QObject *parent, const QVariantList &);
~KritaLayerDockerPlugin() override;
};
#endif
diff --git a/plugins/dockers/layerdocker/NodeDelegate.cpp b/plugins/dockers/layerdocker/NodeDelegate.cpp
index e9182a530a..90ec1eec41 100644
--- a/plugins/dockers/layerdocker/NodeDelegate.cpp
+++ b/plugins/dockers/layerdocker/NodeDelegate.cpp
@@ -1,1015 +1,1014 @@
/*
Copyright (c) 2006 Gábor Lehel <illissius@gmail.com>
Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kis_config.h"
#include "NodeDelegate.h"
#include "kis_node_model.h"
#include "NodeToolTip.h"
#include "NodeView.h"
#include "KisPart.h"
#include "input/kis_input_manager.h"
#include <QtDebug>
#include <QApplication>
#include <QKeyEvent>
#include <QLineEdit>
#include <QModelIndex>
#include <QMouseEvent>
#include <QPainter>
#include <QPointer>
#include <QStyle>
#include <QStyleOptionViewItem>
#include <klocalizedstring.h>
#include "kis_node_view_color_scheme.h"
#include "kis_icon_utils.h"
#include "kis_layer_properties_icons.h"
#include "krita_utils.h"
#include "kis_config_notifier.h"
typedef KisBaseNode::Property* OptionalProperty;
#include <kis_base_node.h>
class NodeDelegate::Private
{
public:
Private() : view(0), edit(0) { }
NodeView *view;
QPointer<QWidget> edit;
NodeToolTip tip;
QColor checkersColor1;
QColor checkersColor2;
QList<OptionalProperty> rightmostProperties(const KisBaseNode::PropertyList &props) const;
int numProperties(const QModelIndex &index) const;
OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const;
OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const;
void toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty prop, bool controlPressed, const QModelIndex &index);
};
NodeDelegate::NodeDelegate(NodeView *view, QObject *parent)
: QAbstractItemDelegate(parent)
, d(new Private)
{
d->view = view;
QApplication::instance()->installEventFilter(this);
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
slotConfigChanged();
}
NodeDelegate::~NodeDelegate()
{
delete d;
}
QSize NodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
return QSize(option.rect.width(), scm.rowHeight());
}
void NodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const
{
p->save();
{
QStyleOptionViewItem option = getOptions(o, index);
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget);
bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool();
if (shouldGrayOut) {
option.state &= ~QStyle::State_Enabled;
}
p->setFont(option.font);
drawColorLabel(p, option, index);
drawFrame(p, option, index);
drawThumbnail(p, option, index);
drawText(p, option, index); // BUG: Creating group moves things around (RTL-layout alignment)
drawIcons(p, option, index);
drawVisibilityIconHijack(p, option, index); // TODO hide when dragging
drawDecoration(p, option, index);
drawExpandButton(p, option, index);
drawBranch(p, option, index);
drawProgressBar(p, option, index);
}
p->restore();
}
void NodeDelegate::drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QModelIndex tmp = index.parent();
// there is no indention if we have no parent group, so don't draw a branch
if (!tmp.isValid()) return;
KisNodeViewColorScheme scm;
int rtlNum = (option.direction == Qt::RightToLeft) ? 1 : -1;
QRect baseRect = scm.relThumbnailRect();
// Move to current index
baseRect.moveTop(option.rect.topLeft().y());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
baseRect.moveLeft(option.rect.topRight().x());
} else {
baseRect.moveRight(option.rect.topLeft().x());
}
QPoint base = baseRect.adjusted(rtlNum*scm.indentation(), 0,
rtlNum*scm.indentation(), 0).center() + QPoint(0, scm.iconSize()/4);
QPen oldPen = p->pen();
const qreal oldOpacity = p->opacity(); // remember previous opacity
p->setOpacity(1.0);
QColor color = scm.gridColor(option, d->view);
QColor bgColor = option.state & QStyle::State_Selected ?
qApp->palette().color(QPalette::Base) :
qApp->palette().color(QPalette::Text);
color = KritaUtils::blendColors(color, bgColor, 0.9);
// TODO: if we are a mask type, use dotted lines for the branch style
// p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin));
p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
QPoint p2 = base - QPoint(rtlNum*(scm.iconSize()/2), 0);
QPoint p3 = base - QPoint(0, scm.iconSize()/2);
p->drawLine(base, p2);
p->drawLine(base, p3);
// draw parent lines (keep drawing until x position is less than 0
QPoint parentBase1 = base + QPoint(rtlNum*scm.indentation(), 0);
QPoint parentBase2 = p3 + QPoint(rtlNum*scm.indentation(), 0);
// indent lines needs to be very subtle to avoid making the docker busy looking
color = KritaUtils::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines
p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
if (tmp.isValid()) {
tmp = tmp.parent(); // Ignore the first group as it was already painted
}
while (tmp.isValid()) {
p->drawLine(parentBase1, parentBase2);
parentBase1 += QPoint(rtlNum*scm.indentation(), 0);
parentBase2 += QPoint(rtlNum*scm.indentation(), 0);
tmp = tmp.parent();
}
p->setPen(oldPen);
p->setOpacity(oldOpacity);
}
void NodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt();
QColor color = scm.colorLabel(label);
if (color.alpha() <= 0) return;
QColor bgColor = qApp->palette().color(QPalette::Base);
if ((option.state & QStyle::State_MouseOver) && !(option.state & QStyle::State_Selected)) {
color = KritaUtils::blendColors(color, bgColor, 0.6);
} else {
color = KritaUtils::blendColors(color, bgColor, 0.3);
}
QRect optionRect = option.rect.adjusted(0, 0, scm.indentation(), 0);
if (option.state & QStyle::State_Selected) {
optionRect = iconsRect(option, index);
}
if (option.direction == Qt::RightToLeft) {
optionRect.moveLeft(option.rect.topLeft().x());
} else {
optionRect.moveRight(option.rect.topRight().x());
}
p->fillRect(optionRect, color);
}
void NodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
QPen oldPen = p->pen();
p->setPen(scm.gridColor(option, d->view));
const QRect visibilityRect = visibilityClickRect(option, index);
const QRect thumbnailRect = thumbnailClickRect(option, index);
const QRect decorationRect = decorationClickRect(option, index);
const QRect iconsRectR = iconsRect(option, index);
const float topY = thumbnailRect.topLeft().y();
const float bottomY = thumbnailRect.bottomLeft().y();
QPoint bottomLeftPoint;
QPoint bottomRightPoint;
if (option.direction == Qt::RightToLeft) {
bottomLeftPoint = iconsRectR.bottomLeft();
bottomRightPoint = visibilityRect.bottomRight();
} else {
bottomLeftPoint = visibilityRect.bottomLeft();
bottomRightPoint = iconsRectR.bottomRight();
}
// bottom running horizontal line
p->drawLine(bottomLeftPoint.x(), bottomY,
bottomRightPoint.x(), bottomY);
// visiblity icon vertical line - left
p->drawLine(visibilityRect.topLeft().x()-1, topY,
visibilityRect.bottomLeft().x()-1, bottomY);
// visiblity icon vertical line - right
p->drawLine(visibilityRect.topRight().x()+1, topY,
visibilityRect.bottomRight().x()+1, bottomY);
// thumbnail vertical line - left
p->drawLine(thumbnailRect.topLeft().x(), topY,
thumbnailRect.bottomLeft().x(), bottomY);
// thumbnail vertical line - right
p->drawLine(thumbnailRect.topRight().x(), topY,
thumbnailRect.bottomRight().x(), bottomY);
// decoration vertical line - left
p->drawLine(decorationRect.topLeft().x(), topY,
decorationRect.bottomLeft().x(), bottomY);
// decoration vertical line - right
p->drawLine(decorationRect.topRight().x(), topY,
decorationRect.bottomRight().x(), bottomY);
// icons' lines are drawn by drawIcons
//// For debugging purposes only
p->setPen(Qt::blue);
//KritaUtils::renderExactRect(p, iconsRectR);
//KritaUtils::renderExactRect(p, textRect(option, index));
//KritaUtils::renderExactRect(p, visibilityRect);
p->setPen(oldPen);
}
QRect NodeDelegate::thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = scm.relThumbnailRect();
// Move to current index
rc.moveTop(option.rect.topLeft().y());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
rc.moveLeft(option.rect.topRight().x());
} else {
rc.moveRight(option.rect.topLeft().x());
}
return rc;
}
void NodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const int thumbSize = scm.thumbnailSize();
const qreal oldOpacity = p->opacity(); // remember previous opacity
QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSize).value<QImage>();
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.35);
}
QRect fitRect = thumbnailClickRect(option, index);
// Shrink to icon rect
fitRect = kisGrowRect(fitRect, -(scm.thumbnailMargin()+scm.border()));
// paint in a checkerboard pattern behind the layer contents to represent transparent
const int step = scm.thumbnailSize() / 6;
QImage checkers(2 * step, 2 * step, QImage::Format_ARGB32);
QPainter gc(&checkers);
gc.fillRect(QRect(0, 0, step, step), d->checkersColor1);
gc.fillRect(QRect(step, 0, step, step), d->checkersColor2);
gc.fillRect(QRect(step, step, step, step), d->checkersColor1);
gc.fillRect(QRect(0, step, step, step), d->checkersColor2);
QBrush brush(checkers);
p->fillRect(fitRect, brush);
p->drawImage(fitRect, img);
p->setOpacity(oldOpacity); // restore old opacity
QRect borderRect = kisGrowRect(fitRect, 1);
KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view));
}
QRect NodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
int propCount = d->numProperties(index);
const int iconsWidth =
propCount * (scm.iconSize() + 2 * scm.iconMargin()) +
(propCount + 1) * scm.border();
QRect fitRect = QRect(0, 0,
iconsWidth, scm.rowHeight() - scm.border());
// Move to current index
fitRect.moveTop(option.rect.topLeft().y());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
fitRect.moveLeft(option.rect.topLeft().x());
} else {
fitRect.moveRight(option.rect.topRight().x());
}
return fitRect;
}
QRect NodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
static QFont f;
static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely
if (minbearing == 2003 || f != option.font) {
f = option.font; //getting your bearings can be expensive, so we cache them
minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing();
}
const QRect decoRect = decorationClickRect(option, index);
const QRect iconRect = iconsRect(option, index);
QRect rc = QRect((option.direction == Qt::RightToLeft) ? iconRect.topRight() : decoRect.topRight(),
(option.direction == Qt::RightToLeft) ? decoRect.bottomLeft() : iconRect.bottomLeft());
rc.adjust(-(scm.border()+minbearing), 0,
(scm.border()+minbearing), 0);
return rc;
}
void NodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const QRect rc = textRect(option, index).adjusted(scm.textMargin(), 0,
-scm.textMargin(), 0);
QPen oldPen = p->pen();
const qreal oldOpacity = p->opacity(); // remember previous opacity
p->setPen(option.palette.color(QPalette::Active,QPalette::Text ));
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.55);
}
const QString text = index.data(Qt::DisplayRole).toString();
const QString elided = p->fontMetrics().elidedText(text, Qt::ElideRight, rc.width());
p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided);
p->setPen(oldPen); // restore pen settings
p->setOpacity(oldOpacity);
}
QList<OptionalProperty> NodeDelegate::Private::rightmostProperties(const KisBaseNode::PropertyList &props) const
{
QList<OptionalProperty> list;
QList<OptionalProperty> prependList;
list << OptionalProperty(0);
list << OptionalProperty(0);
list << OptionalProperty(0);
KisBaseNode::PropertyList::const_iterator it = props.constBegin();
KisBaseNode::PropertyList::const_iterator end = props.constEnd();
for (; it != end; ++it) {
if (!it->isMutable) continue;
if (it->id == KisLayerPropertiesIcons::visible.id()) {
// noop...
} else if (it->id == KisLayerPropertiesIcons::locked.id()) {
list[0] = OptionalProperty(&(*it));
} else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) {
list[1] = OptionalProperty(&(*it));
} else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) {
list[2] = OptionalProperty(&(*it));
} else {
prependList.prepend(OptionalProperty(&(*it)));
}
}
{
QMutableListIterator<OptionalProperty> i(prependList);
i.toBack();
while (i.hasPrevious()) {
OptionalProperty val = i.previous();
int emptyIndex = list.lastIndexOf(0);
if (emptyIndex < 0) break;
list[emptyIndex] = val;
i.remove();
}
}
return prependList + list;
}
int NodeDelegate::Private::numProperties(const QModelIndex &index) const
{
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
QList<OptionalProperty> realProps = rightmostProperties(props);
return realProps.size();
}
OptionalProperty NodeDelegate::Private::findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const
{
KisBaseNode::PropertyList::iterator it = props.begin();
KisBaseNode::PropertyList::iterator end = props.end();
for (; it != end; ++it) {
if (it->id == refProp->id) {
return &(*it);
}
}
return 0;
}
OptionalProperty NodeDelegate::Private::findVisibilityProperty(KisBaseNode::PropertyList &props) const
{
KisBaseNode::PropertyList::iterator it = props.begin();
KisBaseNode::PropertyList::iterator end = props.end();
for (; it != end; ++it) {
if (it->id == KisLayerPropertiesIcons::visible.id()) {
return &(*it);
}
}
return 0;
}
void NodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const QRect rc = iconsRect(option, index);
QTransform oldTransform = p->transform();
QPen oldPen = p->pen();
p->setTransform(QTransform::fromTranslate(rc.x(), rc.y()));
p->setPen(scm.gridColor(option, d->view));
int x = 0;
const int y = (scm.rowHeight() - scm.border() - scm.iconSize()) / 2;
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
QList<OptionalProperty> realProps = d->rightmostProperties(props);
if (option.direction == Qt::RightToLeft) {
std::reverse(realProps.begin(), realProps.end());
}
Q_FOREACH (OptionalProperty prop, realProps) {
if (option.direction == Qt::LeftToRight)
p->drawLine(x, 0, x, scm.rowHeight() - scm.border());
x += scm.iconMargin();
if (prop) {
QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled;
const qreal oldOpacity = p->opacity(); // remember previous opacity
if (fullColor) {
p->setOpacity(1.0);
}
else {
p->setOpacity(0.35);
}
p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal));
p->setOpacity(oldOpacity); // restore old opacity
}
x += scm.iconSize() + scm.iconMargin();
if (!(option.direction == Qt::LeftToRight))
p->drawLine(x, 0, x, scm.rowHeight() - scm.border());
x += scm.border();
}
p->setTransform(oldTransform);
p->setPen(oldPen);
}
QRect NodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = scm.relVisibilityRect();
rc.setHeight(scm.rowHeight());
// Move to current index
rc.moveCenter(option.rect.center());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
// HACK: Without the -5, the right edge is outside the view
rc.moveRight(d->view->width()-5);
} else {
rc.moveLeft(0);
}
return rc;
}
QRect NodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = scm.relDecorationRect();
// Move to current index
rc.moveTop(option.rect.topLeft().y());
rc.setHeight(scm.rowHeight());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
rc.moveRight(option.rect.topRight().x());
} else {
rc.moveLeft(option.rect.topLeft().x());
}
return rc;
}
void NodeDelegate::drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
/**
* Small hack Alert:
*
* Here wepaint over the area that sits basically outside our layer's
* row. Anyway, just update it later...
*/
KisNodeViewColorScheme scm;
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = d->findVisibilityProperty(props);
if (!prop) return;
QRect fitRect = visibilityClickRect(option, index);
// Shrink to icon rect
fitRect = kisGrowRect(fitRect, -(scm.visibilityMargin()+scm.border()));
QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
// if we are not showing the layer, make the icon slightly transparent like other inactive icons
const qreal oldOpacity = p->opacity();
if (!prop->state.toBool()) {
p->setOpacity(0.35);
}
p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2,
icon.pixmap(scm.visibilitySize(), QIcon::Normal));
p->setOpacity(oldOpacity);
//// For debugging purposes only
// // // p->save();
// // // p->setPen(Qt::blue);
// // // KritaUtils::renderExactRect(p, visibilityClickRect(option, index));
// // // p->restore();
}
void NodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
if (!icon.isNull()) {
QPixmap pixmap = icon.pixmap(scm.decorationSize(),
(option.state & QStyle::State_Enabled) ?
QIcon::Normal : QIcon::Disabled);
QRect rc = decorationClickRect(option, index);
// Shrink to icon rect
rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
const qreal oldOpacity = p->opacity(); // remember previous opacity
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.35);
}
p->drawPixmap(rc.topLeft()-QPoint(0, 1), pixmap);
p->setOpacity(oldOpacity); // restore old opacity
}
}
void NodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = decorationClickRect(option, index);
// Move to current index
// rc.moveTop(option.rect.topLeft().y());
// Shrink to icon rect
rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
if (!(option.state & QStyle::State_Children)) return;
QString iconName = option.state & QStyle::State_Open ?
"arrow-down" : ((option.direction == Qt::RightToLeft) ? "arrow-left" : "arrow-right");
QIcon icon = KisIconUtils::loadIcon(iconName);
QPixmap pixmap = icon.pixmap(rc.width(),
(option.state & QStyle::State_Enabled) ?
QIcon::Normal : QIcon::Disabled);
p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), pixmap);
}
void NodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty clickedProperty, bool controlPressed, const QModelIndex &index)
{
QAbstractItemModel *model = view->model();
// Using Ctrl+click to enter stasis
if (controlPressed && clickedProperty->canHaveStasis) {
// STEP 0: Prepare to Enter or Leave control key stasis
quint16 numberOfLeaves = model->rowCount(index.parent());
QModelIndex eachItem;
// STEP 1: Go.
if (clickedProperty->isInStasis == false) { // Enter
/* Make every leaf of this node go State = False, saving the old property value to stateInStasis */
for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent())
eachItem = model->index(i, 1, index.parent());
// The entire property list has to be altered because model->setData cannot set individual properties
KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = findProperty(eachPropertyList, clickedProperty);
if (!prop) continue;
prop->stateInStasis = prop->state.toBool();
prop->state = eachItem == index;
prop->isInStasis = true;
model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole);
}
for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent())
eachItem = model->index(i, 1, index.parent());
KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = findProperty(eachPropertyList, clickedProperty);
if (!prop) continue;
}
} else { // Leave
/* Make every leaf of this node go State = stateInStasis */
for (quint16 i = 0; i < numberOfLeaves; ++i) {
eachItem = model->index(i, 1, index.parent());
// The entire property list has to be altered because model->setData cannot set individual properties
KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = findProperty(eachPropertyList, clickedProperty);
if (!prop) continue;
prop->state = prop->stateInStasis;
prop->isInStasis = false;
model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole);
}
}
} else {
clickedProperty->state = !clickedProperty->state.toBool();
model->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
}
}
bool NodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
KisNodeViewColorScheme scm;
QStyleOptionViewItem newOption = option;
newOption.rect = d->view->originalVisualRect(index);
if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
&& (index.flags() & Qt::ItemIsEnabled))
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
/**
* Small hack Alert:
*
* Here we handle clicking even when it happened outside
* the rectangle of the current index. The point is, we
* use some virtual scroling offset to move the tree to the
* right of the visibility icon. So the icon itself is placed
* in an empty area that doesn't belong to any index. But we still
* handle it.
*/
const QRect visibilityRect = visibilityClickRect(newOption, index);
const bool visibilityClicked = visibilityRect.isValid() &&
visibilityRect.contains(mouseEvent->pos());
const QRect thumbnailRect = thumbnailClickRect(newOption, index);
const bool thumbnailClicked = thumbnailRect.isValid() &&
thumbnailRect.contains(mouseEvent->pos());
const QRect decorationRect = decorationClickRect(newOption, index);
const bool decorationClicked = decorationRect.isValid() &&
decorationRect.contains(mouseEvent->pos());
const QRect iconsRect = this->iconsRect(newOption, index);
const bool iconsClicked = iconsRect.isValid() &&
iconsRect.contains(mouseEvent->pos());
const bool leftButton = mouseEvent->buttons() & Qt::LeftButton;
if (leftButton && iconsClicked) {
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
QList<OptionalProperty> realProps = d->rightmostProperties(props);
if (newOption.direction == Qt::RightToLeft) {
std::reverse(realProps.begin(), realProps.end());
}
const int numProps = realProps.size();
const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border();
const int xPos = mouseEvent->pos().x() - iconsRect.left();
const int clickedIcon = xPos / iconWidth;
const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth);
if (iconsClicked &&
clickedIcon >= 0 &&
clickedIcon < numProps &&
distToBorder > scm.iconMargin()) {
OptionalProperty clickedProperty = realProps[clickedIcon];
if (!clickedProperty) return false;
d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index);
return true;
}
} else if (leftButton && visibilityClicked) {
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty clickedProperty = d->findVisibilityProperty(props);
if (!clickedProperty) return false;
d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index);
return true;
} else if (leftButton && decorationClicked) {
bool isExpandable = model->hasChildren(index);
if (isExpandable) {
bool isExpanded = d->view->isExpanded(index);
d->view->setExpanded(index, !isExpanded);
}
return true;
} else if (leftButton && thumbnailClicked) {
bool hasCorrectModifier = false;
SelectionAction action = SELECTION_REPLACE;
if (mouseEvent->modifiers() == Qt::ControlModifier) {
action = SELECTION_REPLACE;
hasCorrectModifier = true;
} else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
action = SELECTION_ADD;
hasCorrectModifier = true;
} else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) {
action = SELECTION_SUBTRACT;
hasCorrectModifier = true;
} else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
action = SELECTION_INTERSECT;
hasCorrectModifier = true;
}
if (hasCorrectModifier) {
model->setData(index, QVariant(int(action)), KisNodeModel::SelectOpaqueRole);
}
- return true; //If not here then the item is !expanded when reaching return false;
+ d->view->setCurrentIndex(index);
+ return hasCorrectModifier; //If not here then the item is !expanded when reaching return false;
}
if (mouseEvent->button() == Qt::LeftButton &&
mouseEvent->modifiers() == Qt::AltModifier) {
d->view->setCurrentIndex(index);
model->setData(index, true, KisNodeModel::AlternateActiveRole);
return true;
}
}
else if (event->type() == QEvent::ToolTip) {
if (!KisConfig(true).hidePopups()) {
QHelpEvent *helpEvent = static_cast<QHelpEvent*>(event);
d->tip.showTip(d->view, helpEvent->pos(), newOption, index);
}
return true;
} else if (event->type() == QEvent::Leave) {
d->tip.hide();
}
return false;
}
-QWidget *NodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const
+QWidget *NodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex &index) const
{
- d->edit = new QLineEdit(parent);
+ // #400357 do not override QAbstractItemDelegate::setEditorData to update editor's text
+ // because replacing the text while user type is confusing
+ const QString &text = index.data(Qt::DisplayRole).toString();
+ d->edit = new QLineEdit(text, parent);
d->edit->setFocusPolicy(Qt::StrongFocus);
d->edit->installEventFilter(const_cast<NodeDelegate*>(this)); //hack?
return d->edit;
}
-void NodeDelegate::setEditorData(QWidget *widget, const QModelIndex &index) const
-{
- QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
- Q_ASSERT(edit);
-
- edit->setText(index.data(Qt::DisplayRole).toString());
-}
-
void NodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const
{
QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
Q_ASSERT(edit);
model->setData(index, edit->text(), Qt::DisplayRole);
}
void NodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
widget->setGeometry(option.rect);
}
// PROTECTED
bool NodeDelegate::eventFilter(QObject *object, QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress: {
if (d->edit) {
QMouseEvent *me = static_cast<QMouseEvent*>(event);
if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) {
emit commitData(d->edit);
emit closeEditor(d->edit);
}
}
} break;
case QEvent::KeyPress: {
QLineEdit *edit = qobject_cast<QLineEdit*>(object);
if (edit && edit == d->edit) {
QKeyEvent *ke = static_cast<QKeyEvent*>(event);
switch (ke->key()) {
case Qt::Key_Escape:
emit closeEditor(edit);
return true;
case Qt::Key_Tab:
emit commitData(edit);
emit closeEditor(edit, EditNextItem);
return true;
case Qt::Key_Backtab:
emit commitData(edit);
emit closeEditor(edit, EditPreviousItem);
return true;
case Qt::Key_Return:
case Qt::Key_Enter:
emit commitData(edit);
emit closeEditor(edit);
return true;
default: break;
}
}
} break;
case QEvent::ShortcutOverride : {
QLineEdit *edit = qobject_cast<QLineEdit*>(object);
if (edit && edit == d->edit){
auto* key = static_cast<QKeyEvent*>(event);
if (key->modifiers() == Qt::NoModifier){
switch (key->key()){
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
case Qt::Key_Return:
case Qt::Key_Enter:
event->accept();
return true;
default: break;
}
}
}
} break;
case QEvent::FocusOut : {
QLineEdit *edit = qobject_cast<QLineEdit*>(object);
if (edit && edit == d->edit) {
emit commitData(edit);
emit closeEditor(edit);
}
}
default: break;
}
return QAbstractItemDelegate::eventFilter(object, event);
}
// PRIVATE
QStyleOptionViewItem NodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index)
{
QStyleOptionViewItem option = o;
QVariant v = index.data(Qt::FontRole);
if (v.isValid()) {
option.font = v.value<QFont>();
option.fontMetrics = QFontMetrics(option.font);
}
v = index.data(Qt::TextAlignmentRole);
if (v.isValid())
option.displayAlignment = QFlag(v.toInt());
v = index.data(Qt::TextColorRole);
if (v.isValid())
option.palette.setColor(QPalette::Text, v.value<QColor>());
v = index.data(Qt::BackgroundColorRole);
if (v.isValid())
option.palette.setColor(QPalette::Window, v.value<QColor>());
return option;
}
void NodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QVariant value = index.data(KisNodeModel::ProgressRole);
if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) {
/// The progress bar will display under the layer name area. The bars have accurate data, so we
/// probably don't need to also show the actual number for % complete
KisNodeViewColorScheme scm;
const QRect thumbnailRect = thumbnailClickRect(option, index);
const QRect iconsRectR = iconsRect(option, index);
const int height = 5;
const QRect rc = QRect(
- ((option.direction == Qt::RightToLeft) ? iconsRectR.bottomRight()
- : thumbnailRect.bottomRight()) - QPoint(0, height),
- ((option.direction == Qt::RightToLeft) ? thumbnailRect.bottomLeft()
- : iconsRectR.bottomLeft()));
+ ((option.direction == Qt::RightToLeft) ?
+ iconsRectR.bottomRight() :
+ thumbnailRect.bottomRight()) - QPoint(0, height),
+ ((option.direction == Qt::RightToLeft) ?
+ thumbnailRect.bottomLeft() :
+ iconsRectR.bottomLeft()));
p->save();
{
p->setClipRect(rc);
QStyle* style = QApplication::style();
QStyleOptionProgressBar opt;
+ opt.rect = rc;
opt.minimum = 0;
opt.maximum = 100;
opt.progress = value.toInt();
opt.textVisible = false;
opt.textAlignment = Qt::AlignHCenter;
opt.text = i18n("%1 %", opt.progress);
opt.orientation = Qt::Horizontal;
opt.state = option.state;
style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0);
}
p->restore();
}
}
void NodeDelegate::slotConfigChanged()
{
KisConfig cfg(true);
d->checkersColor1 = cfg.checkersColor1();
d->checkersColor2 = cfg.checkersColor2();
}
void NodeDelegate::slotUpdateIcon()
{
KisLayerPropertiesIcons::instance()->updateIcons();
}
diff --git a/plugins/dockers/layerdocker/NodeDelegate.h b/plugins/dockers/layerdocker/NodeDelegate.h
index a323e16886..de819b8357 100644
--- a/plugins/dockers/layerdocker/NodeDelegate.h
+++ b/plugins/dockers/layerdocker/NodeDelegate.h
@@ -1,89 +1,88 @@
/*
Copyright (c) 2006 Gábor Lehel <illissius@gmail.com>
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_DOCUMENT_SECTION_DELEGATE_H
#define KIS_DOCUMENT_SECTION_DELEGATE_H
#include <QAbstractItemDelegate>
class NodeView;
class KisNodeModel;
/**
* See KisNodeModel and NodeView.
*
* A delegate provides the gui machinery, using Qt's model/view terminology.
* This class is owned by NodeView to do the work of generating the
* graphical representation of each item.
*/
class NodeDelegate: public QAbstractItemDelegate
{
Q_OBJECT
public:
explicit NodeDelegate(NodeView *view, QObject *parent = 0);
~NodeDelegate() override;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
- void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex& index) const override;
void slotUpdateIcon();
protected:
bool eventFilter(QObject *object, QEvent *event) override;
private:
typedef KisNodeModel Model;
typedef NodeView View;
class Private;
Private* const d;
static QStyleOptionViewItem getOptions(const QStyleOptionViewItem &option, const QModelIndex &index);
void drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
private Q_SLOTS:
void slotConfigChanged();
};
#endif
diff --git a/plugins/dockers/logdocker/LogDocker.cpp b/plugins/dockers/logdocker/LogDocker.cpp
index e65a9e4a3d..8146da5426 100644
--- a/plugins/dockers/logdocker/LogDocker.cpp
+++ b/plugins/dockers/logdocker/LogDocker.cpp
@@ -1,80 +1,80 @@
/*
-* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 "LogDocker.h"
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "LogDockerDock.h"
K_PLUGIN_FACTORY_WITH_JSON(LogDockerPluginFactory,
"krita_logdocker.json",
registerPlugin<LogDockerPlugin>();)
class LogDockerDockFactory : public KoDockFactoryBase {
public:
LogDockerDockFactory()
{
}
virtual ~LogDockerDockFactory()
{
}
QString id() const override
{
return QString( "LogDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
LogDockerDock * dockWidget = new LogDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
LogDockerPlugin::LogDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new LogDockerDockFactory());
}
LogDockerPlugin::~LogDockerPlugin()
{
}
#include "LogDocker.moc"
diff --git a/plugins/dockers/logdocker/LogDocker.h b/plugins/dockers/logdocker/LogDocker.h
index 40ac6a099e..07b4872f77 100644
--- a/plugins/dockers/logdocker/LogDocker.h
+++ b/plugins/dockers/logdocker/LogDocker.h
@@ -1,32 +1,34 @@
/*
* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 _LOG_DOCKER_H_
#define _LOG_DOCKER_H_
#include <QObject>
#include <QVariant>
class LogDockerPlugin : public QObject
{
Q_OBJECT
public:
LogDockerPlugin(QObject *parent, const QVariantList &);
~LogDockerPlugin() override;
};
#endif
diff --git a/plugins/dockers/logdocker/LogDockerDock.cpp b/plugins/dockers/logdocker/LogDockerDock.cpp
index dedbed5e6a..6271ab7156 100644
--- a/plugins/dockers/logdocker/LogDockerDock.cpp
+++ b/plugins/dockers/logdocker/LogDockerDock.cpp
@@ -1,336 +1,338 @@
/*
* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 "LogDockerDock.h"
#include <QHBoxLayout>
#include <QToolButton>
#include <QScrollBar>
#include <QStandardPaths>
#include <QDateTime>
#include <QCheckBox>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <KisPart.h>
#include <KoDialog.h>
#include <KoCanvasBase.h>
#include <KoIcon.h>
#include <KoFileDialog.h>
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_config.h"
MessageSender *LogDockerDock::s_messageSender {new MessageSender()};
QTextCharFormat LogDockerDock::s_debug;
QTextCharFormat LogDockerDock::s_info;
QTextCharFormat LogDockerDock::s_warning;
QTextCharFormat LogDockerDock::s_critical;
QTextCharFormat LogDockerDock::s_fatal;
LogDockerDock::LogDockerDock( )
: QDockWidget(i18n("Log Viewer"))
{
QWidget *page = new QWidget(this);
setupUi(page);
setWidget(page);
bnToggle->setIcon(koIcon("view-list-text"));
connect(bnToggle, SIGNAL(clicked(bool)), SLOT(toggleLogging(bool)));
bnToggle->setChecked(KisConfig(true).readEntry<bool>("logviewer_enabled", false));
toggleLogging(KisConfig(true).readEntry<bool>("logviewer_enabled", false));
bnClear->setIcon(koIcon("edit-clear"));
connect(bnClear, SIGNAL(clicked(bool)), SLOT(clearLog()));
bnSave->setIcon(koIcon("document-save"));
connect(bnSave, SIGNAL(clicked(bool)), SLOT(saveLog()));
bnSettings->setIcon(koIcon("configure"));
connect(bnSettings, SIGNAL(clicked(bool)), SLOT(settings()));
qRegisterMetaType<QtMsgType>("QtMsgType");
connect(s_messageSender, SIGNAL(emitMessage(QtMsgType,QString)), this, SLOT(insertMessage(QtMsgType,QString)), Qt::AutoConnection);
applyCategories();
changeTheme();
}
void LogDockerDock::setCanvas(KoCanvasBase *)
{
setEnabled(true);
}
void LogDockerDock::setViewManager(KisViewManager *kisview)
{
connect(static_cast<KisMainWindow*>(kisview->mainWindow()), SIGNAL(themeChanged()), SLOT(changeTheme()));
}
void LogDockerDock::toggleLogging(bool toggle)
{
KisConfig(false).writeEntry<bool>("logviewer_enabled", toggle);
if (toggle) {
qInstallMessageHandler(messageHandler);
applyCategories();
}
else {
qInstallMessageHandler(0);
}
}
void LogDockerDock::clearLog()
{
txtLogViewer->document()->clear();
}
void LogDockerDock::saveLog()
{
KoFileDialog fileDialog(this, KoFileDialog::SaveFile, "logfile");
fileDialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/" + QString("krita_%1.log").arg(QDateTime::currentDateTime().toString()));
QString filename = fileDialog.filename();
if (!filename.isEmpty()) {
QFile f(filename);
f.open(QFile::WriteOnly);
f.write(txtLogViewer->document()->toPlainText().toUtf8());
f.close();
}
}
void LogDockerDock::settings()
{
KoDialog dlg(this);
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setCaption(i18n("Log Settings"));
QWidget *page = new QWidget(&dlg);
dlg.setMainWidget(page);
QVBoxLayout *layout = new QVBoxLayout(page);
KConfigGroup cfg( KSharedConfig::openConfig(), "LogDocker");
QCheckBox *chkKrita = new QCheckBox(i18n("General"), page);
chkKrita->setChecked(cfg.readEntry("krita_41000", false));
layout->addWidget(chkKrita);
QCheckBox *chkResources = new QCheckBox(i18n("Resource Management"), page);
chkResources->setChecked(cfg.readEntry("resources_30009", false));
layout->addWidget(chkResources);
QCheckBox *chkImage = new QCheckBox(i18n("Image Core"), page);
chkImage->setChecked(cfg.readEntry("image_41001", false));
layout->addWidget(chkImage);
QCheckBox *chkRegistry = new QCheckBox(i18n("Registries"), page);
chkRegistry->setChecked(cfg.readEntry("registry_41002", false));
layout->addWidget(chkRegistry);
QCheckBox *chkTools = new QCheckBox(i18n("Tools"), page);
chkTools->setChecked(cfg.readEntry("tools_41003", false));
layout->addWidget(chkTools);
QCheckBox *chkTiles = new QCheckBox(i18n("Tile Engine"), page);
chkTiles->setChecked(cfg.readEntry("tiles_41004", false));
layout->addWidget(chkTiles);
QCheckBox *chkFilters = new QCheckBox(i18n("Filters"), page);
chkFilters->setChecked(cfg.readEntry("filters_41005", false));
layout->addWidget(chkFilters);
QCheckBox *chkPlugins = new QCheckBox(i18n("Plugin Management"), page);
chkPlugins->setChecked(cfg.readEntry("plugins_41006", false));
layout->addWidget(chkPlugins);
QCheckBox *chkUi = new QCheckBox(i18n("User Interface"), page);
chkUi->setChecked(cfg.readEntry("ui_41007", false));
layout->addWidget(chkUi);
QCheckBox *chkFile = new QCheckBox(i18n("File loading and saving"), page);
chkFile->setChecked(cfg.readEntry("file_41008", false));
layout->addWidget(chkFile);
QCheckBox *chkMath = new QCheckBox(i18n("Mathematics and calculations"), page);
chkMath->setChecked(cfg.readEntry("math_41009", false));
layout->addWidget(chkMath);
QCheckBox *chkRender = new QCheckBox(i18n("Image Rendering"), page);
chkRender->setChecked(cfg.readEntry("render_41010", false));
layout->addWidget(chkRender);
QCheckBox *chkScript = new QCheckBox(i18n("Scripting"), page);
chkScript->setChecked(cfg.readEntry("script_41011", false));
layout->addWidget(chkScript);
QCheckBox *chkInput = new QCheckBox(i18n("Input handling"), page);
chkInput->setChecked(cfg.readEntry("input_41012", false));
layout->addWidget(chkInput);
QCheckBox *chkAction = new QCheckBox(i18n("Actions"), page);
chkAction->setChecked(cfg.readEntry("action_41013", false));
layout->addWidget(chkAction);
QCheckBox *chkTablet = new QCheckBox(i18n("Tablet Handling"), page);
chkTablet->setChecked(cfg.readEntry("tablet_41014", false));
layout->addWidget(chkTablet);
QCheckBox *chkOpenGL = new QCheckBox(i18n("GPU Canvas"), page);
chkOpenGL->setChecked(cfg.readEntry("opengl_41015", false));
layout->addWidget(chkOpenGL);
QCheckBox *chkMetaData = new QCheckBox(i18n("Metadata"), page);
chkMetaData->setChecked(cfg.readEntry("metadata_41016", false));
layout->addWidget(chkMetaData);
QCheckBox *chkPigment = new QCheckBox(i18n("Color Management"), page);
chkPigment->setChecked(cfg.readEntry("pigment", false));
layout->addWidget(chkPigment);
if (dlg.exec()) {
// Apply the new settings
cfg.writeEntry("resources_30009", chkResources->isChecked());
cfg.writeEntry("krita_41000", chkKrita->isChecked());
cfg.writeEntry("image_41001", chkImage->isChecked());
cfg.writeEntry("registry_41002", chkRegistry->isChecked());
cfg.writeEntry("tools_41003", chkTools->isChecked());
cfg.writeEntry("tiles_41004", chkTiles->isChecked());
cfg.writeEntry("filters_41005", chkFilters->isChecked());
cfg.writeEntry("plugins_41006", chkPlugins->isChecked());
cfg.writeEntry("ui_41007", chkUi->isChecked());
cfg.writeEntry("file_41008", chkFile->isChecked());
cfg.writeEntry("math_41009", chkMath->isChecked());
cfg.writeEntry("render_41010", chkRender->isChecked());
cfg.writeEntry("script_41011", chkScript->isChecked());
cfg.writeEntry("input_41012", chkInput->isChecked());
cfg.writeEntry("action_41013", chkAction->isChecked());
cfg.writeEntry("tablet_41014", chkTablet->isChecked());
cfg.writeEntry("opengl_41015", chkOpenGL->isChecked());
cfg.writeEntry("metadata_41016", chkMetaData->isChecked());
cfg.writeEntry("pigment", chkPigment->isChecked());
applyCategories();
}
}
QString cfgToString(QString tpl, bool cfg)
{
return tpl.arg(cfg ? "true" : "false");
}
void LogDockerDock::applyCategories()
{
QStringList filters;
KConfigGroup cfg( KSharedConfig::openConfig(), "LogDocker");
filters << cfgToString("krita.general=%1", cfg.readEntry("krita_41000", false));
filters << cfgToString("krita.lib.resources=%1", cfg.readEntry("resources_30009", false));
filters << cfgToString("krita.core=%1", cfg.readEntry("image_41001", false));
filters << cfgToString("krita.registry=%1", cfg.readEntry("registry_41002", false));
filters << cfgToString("krita.tools=%1", cfg.readEntry("tools_41003", false));
filters << cfgToString("krita.lib.flake=%1", cfg.readEntry("tools_41003", false));
filters << cfgToString("krita.tiles=%1", cfg.readEntry("tiles_41004", false));
filters << cfgToString("krita.filters=%1", cfg.readEntry("filters_41005", false));
filters << cfgToString("krita.plugins=%1", cfg.readEntry("plugins_41006", false));
filters << cfgToString("krita.lib.plugin=%1", cfg.readEntry("plugins_41006", false));
filters << cfgToString("krita.ui=%1", cfg.readEntry("ui_41007", false));
filters << cfgToString("krita.widgets=%1", cfg.readEntry("ui_41007", false));
filters << cfgToString("krita.widgetutils=%1", cfg.readEntry("ui_41007", false));
filters << cfgToString("krita.file=%1", cfg.readEntry("file_41008", false));
filters << cfgToString("krita.lib.store=%1", cfg.readEntry("file_41008", false));
filters << cfgToString("krita.lib.odf=%1", cfg.readEntry("file_41008", false));
filters << cfgToString("krita.math=%1", cfg.readEntry("math_41009", false));
filters << cfgToString("krita.grender=%1", cfg.readEntry("render_41010", false));
filters << cfgToString("krita.scripting=%1", cfg.readEntry("script_41011", false));
filters << cfgToString("krita.input=%1", cfg.readEntry("input_41012", false));
filters << cfgToString("krita.action=%1", cfg.readEntry("action_41013", false));
filters << cfgToString("krita.tablet=%1", cfg.readEntry("tablet_41014", false));
filters << cfgToString("krita.opengl=%1", cfg.readEntry("opengl_41015", false));
filters << cfgToString("krita.metadata=%1", cfg.readEntry("metadata_41016", false));
filters << cfgToString("krita.lib.pigment=%1", cfg.readEntry("pigment", false));
QLoggingCategory::setFilterRules(filters.join("\n"));
}
void LogDockerDock::messageHandler(QtMsgType type, const QMessageLogContext &/*context*/, const QString &msg)
{
s_messageSender->sendMessage(type, msg);
}
void LogDockerDock::insertMessage(QtMsgType type, const QString &msg)
{
QTextDocument *doc = txtLogViewer->document();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
cursor.beginEditBlock();
switch (type) {
case QtDebugMsg:
cursor.insertText(msg + "\n", s_debug);
break;
case QtInfoMsg:
cursor.insertText(msg + "\n", s_info);
break;
case QtWarningMsg:
cursor.insertText(msg + "\n", s_warning);
break;
case QtCriticalMsg:
cursor.insertText(msg + "\n", s_critical);
break;
case QtFatalMsg:
cursor.insertText(msg + "\n", s_fatal);
break;
}
cursor.endEditBlock();
txtLogViewer->verticalScrollBar()->setValue(txtLogViewer->verticalScrollBar()->maximum());
}
void LogDockerDock::changeTheme()
{
clearLog();
QColor background = qApp->palette().background().color();
if (background.value() > 100) {
s_debug.setForeground(Qt::black);
s_info.setForeground(Qt::darkGreen);
s_warning.setForeground(Qt::darkYellow);
s_critical.setForeground(Qt::darkRed);
s_fatal.setForeground(Qt::darkRed);
}
else {
s_debug.setForeground(Qt::white);
s_info.setForeground(Qt::green);
s_warning.setForeground(Qt::yellow);
s_critical.setForeground(Qt::red);
s_fatal.setForeground(Qt::red);
}
s_fatal.setFontWeight(QFont::Bold);
}
void MessageSender::sendMessage(QtMsgType type, const QString &msg)
{
emit emitMessage(type, msg);
}
diff --git a/plugins/dockers/logdocker/LogDockerDock.h b/plugins/dockers/logdocker/LogDockerDock.h
index 4bb749365a..75647661a5 100644
--- a/plugins/dockers/logdocker/LogDockerDock.h
+++ b/plugins/dockers/logdocker/LogDockerDock.h
@@ -1,75 +1,76 @@
/*
* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
- * 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 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 library is distributed in the hope that it will be useful,
+ * 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 Lesser General Public License for more details.
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU Lesser General Public License
+ * 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 _LOGDOCKER_DOCK_H_
#define _LOGDOCKER_DOCK_H_
#include <QDockWidget>
#include <kis_mainwindow_observer.h>
#include "ui_WdgLogDocker.h"
class MessageSender : public QObject
{
Q_OBJECT
public:
MessageSender() : QObject() {}
~MessageSender() override {}
void sendMessage(QtMsgType type, const QString &msg);
Q_SIGNALS:
void emitMessage(QtMsgType type, const QString &msg);
};
class LogDockerDock : public QDockWidget, public KisMainwindowObserver, public Ui_WdgLogDocker {
Q_OBJECT
public:
LogDockerDock( );
QString observerName() override { return "LogDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override {}
void setViewManager(KisViewManager* kisview) override;
private Q_SLOTS:
void toggleLogging(bool toggle);
void clearLog();
void saveLog();
void settings();
void insertMessage(QtMsgType type, const QString &msg);
void changeTheme();
private:
void applyCategories();
static MessageSender *s_messageSender;
static QTextCharFormat s_debug;
static QTextCharFormat s_info;
static QTextCharFormat s_warning;
static QTextCharFormat s_critical;
static QTextCharFormat s_fatal;
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
};
#endif
diff --git a/plugins/dockers/lut/lutdocker.cpp b/plugins/dockers/lut/lutdocker.cpp
index 78cf0845ff..e00c5a1399 100644
--- a/plugins/dockers/lut/lutdocker.cpp
+++ b/plugins/dockers/lut/lutdocker.cpp
@@ -1,89 +1,90 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "lutdocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "lutdocker_dock.h"
#include <KoDockRegistry.h>
#include <OpenColorIO/OpenColorIO.h>
namespace OCIO = OCIO_NAMESPACE;
K_PLUGIN_FACTORY_WITH_JSON(LutDockerPluginFactory, "krita_lutdocker.json", registerPlugin<LutDockerPlugin>();)
class LutDockerDockFactory : public KoDockFactoryBase {
public:
LutDockerDockFactory()
{
}
QString id() const override
{
return QString( "LutDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
LutDockerDock * dockWidget = new LutDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
OCIO::ConstConfigRcPtr m_config;
};
LutDockerPlugin::LutDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new LutDockerDockFactory());
}
LutDockerPlugin::~LutDockerPlugin()
{
}
#include "lutdocker.moc"
diff --git a/plugins/dockers/lut/lutdocker.h b/plugins/dockers/lut/lutdocker.h
index f51fe9edc3..b965e36ad3 100644
--- a/plugins/dockers/lut/lutdocker.h
+++ b/plugins/dockers/lut/lutdocker.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _LUT_DOCKER_H_
#define _LUT_DOCKER_H_
#include <QObject>
#include <QVariant>
/**
* Template of view plugin
*/
class LutDockerPlugin : public QObject
{
Q_OBJECT
public:
LutDockerPlugin(QObject *parent, const QVariantList &);
virtual ~LutDockerPlugin();
};
#endif
diff --git a/plugins/dockers/overview/overviewdocker.cpp b/plugins/dockers/overview/overviewdocker.cpp
index 173730168c..cebe84a5cf 100644
--- a/plugins/dockers/overview/overviewdocker.cpp
+++ b/plugins/dockers/overview/overviewdocker.cpp
@@ -1,87 +1,88 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "overviewdocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "overviewdocker_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(OverviewDockerPluginFactory, "krita_overviewdocker.json", registerPlugin<OverviewDockerPlugin>();)
class OverviewDockerDockFactory : public KoDockFactoryBase {
public:
OverviewDockerDockFactory()
{
}
QString id() const override
{
return QString( "OverviewDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
OverviewDockerDock * dockWidget = new OverviewDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
OverviewDockerPlugin::OverviewDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new OverviewDockerDockFactory());
}
OverviewDockerPlugin::~OverviewDockerPlugin()
{
m_view = 0;
}
#include "overviewdocker.moc"
diff --git a/plugins/dockers/overview/overviewdocker.h b/plugins/dockers/overview/overviewdocker.h
index 3820926ddf..de453f6616 100644
--- a/plugins/dockers/overview/overviewdocker.h
+++ b/plugins/dockers/overview/overviewdocker.h
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _OVERVIEW_DOCKER_H_
#define _OVERVIEW_DOCKER_H_
#include <QObject>
#include <QVariant>
class KisViewManager;
/**
* Template of view plugin
*/
class OverviewDockerPlugin : public QObject
{
Q_OBJECT
public:
OverviewDockerPlugin(QObject *parent, const QVariantList &);
~OverviewDockerPlugin() override;
private:
KisViewManager* m_view;
};
#endif
diff --git a/plugins/dockers/overview/overviewdocker_dock.cpp b/plugins/dockers/overview/overviewdocker_dock.cpp
index 18d41ba05e..2cfa57a2bc 100644
--- a/plugins/dockers/overview/overviewdocker_dock.cpp
+++ b/plugins/dockers/overview/overviewdocker_dock.cpp
@@ -1,153 +1,154 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "overviewdocker_dock.h"
#include "overviewwidget.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QToolButton>
#include <QStatusBar>
#include <kis_slider_spin_box.h>
#include <klocalizedstring.h>
#include "kis_canvas2.h"
#include <KisViewManager.h>
#include <kactioncollection.h>
#include <kis_action.h>
#include <kis_zoom_manager.h>
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_signal_compressor.h"
#include "kis_canvas_controller.h"
#include "kis_icon_utils.h"
#include "kis_signals_blocker.h"
OverviewDockerDock::OverviewDockerDock( )
: QDockWidget(i18n("Overview"))
, m_zoomSlider(nullptr)
, m_rotateSlider(nullptr)
, m_mirrorCanvas(nullptr)
, m_canvas(nullptr)
{
QWidget *page = new QWidget(this);
m_layout = new QVBoxLayout(page);
m_horizontalLayout = new QHBoxLayout();
m_overviewWidget = new OverviewWidget(this);
m_overviewWidget->setMinimumHeight(50);
m_overviewWidget->setBackgroundRole(QPalette::AlternateBase);
m_overviewWidget->setAutoFillBackground(true); // paints background role before paint()
m_layout->addWidget(m_overviewWidget, 1);
setWidget(page);
}
void OverviewDockerDock::setCanvas(KoCanvasBase * canvas)
{
if(m_canvas == canvas)
return;
setEnabled(canvas != nullptr);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_canvas->image()->disconnect(this);
}
if (m_zoomSlider) {
m_layout->removeWidget(m_zoomSlider);
delete m_zoomSlider;
m_zoomSlider = nullptr;
}
if (m_rotateSlider) {
m_horizontalLayout->removeWidget(m_rotateSlider);
delete m_rotateSlider;
m_rotateSlider = nullptr;
}
if (m_mirrorCanvas) {
m_horizontalLayout->removeWidget(m_mirrorCanvas);
delete m_mirrorCanvas;
m_mirrorCanvas = nullptr;
}
m_layout->removeItem(m_horizontalLayout);
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
m_overviewWidget->setCanvas(canvas);
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->zoomController() && m_canvas->viewManager()->zoomController()->zoomAction()) {
m_zoomSlider = m_canvas->viewManager()->zoomController()->zoomAction()->createWidget(m_canvas->imageView()->KisView::statusBar());
m_layout->addWidget(m_zoomSlider);
m_rotateSlider = new KisDoubleSliderSpinBox();
m_rotateSlider->setRange(-180, 180, 2);
m_rotateSlider->setValue(m_canvas->rotationAngle());
m_rotateSlider->setPrefix(i18n("Rotation: "));
m_rotateSlider->setSuffix("°");
connect(m_rotateSlider, SIGNAL(valueChanged(qreal)), this, SLOT(rotateCanvasView(qreal)), Qt::UniqueConnection);
connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(updateSlider()));
m_mirrorCanvas = new QToolButton();
QList<QAction *> actions = m_canvas->viewManager()->actionCollection()->actions();
Q_FOREACH(QAction* action, actions) {
if (action->objectName()=="mirror_canvas") {
m_mirrorCanvas->setDefaultAction(action);
}
}
m_horizontalLayout->addWidget(m_mirrorCanvas);
m_horizontalLayout->addWidget(m_rotateSlider);
m_layout->addLayout(m_horizontalLayout);
}
}
void OverviewDockerDock::unsetCanvas()
{
setEnabled(false);
m_canvas = nullptr;
m_overviewWidget->unsetCanvas();
}
void OverviewDockerDock::rotateCanvasView(qreal rotation)
{
KisCanvasController *canvasController =
dynamic_cast<KisCanvasController*>(m_canvas->viewManager()->canvasBase()->canvasController());
if (canvasController) {
canvasController->rotateCanvas(rotation-m_canvas->rotationAngle());
}
}
void OverviewDockerDock::updateSlider()
{
KisSignalsBlocker l(m_rotateSlider);
qreal rotation = m_canvas->rotationAngle();
if (rotation > 180) {
rotation = rotation - 360;
} else if (rotation < -180) {
rotation = rotation + 360;
}
if (m_rotateSlider->value() != rotation) {
m_rotateSlider->setValue(rotation);
}
}
diff --git a/plugins/dockers/overview/overviewdocker_dock.h b/plugins/dockers/overview/overviewdocker_dock.h
index 0270b04478..2fbc1f6d2e 100644
--- a/plugins/dockers/overview/overviewdocker_dock.h
+++ b/plugins/dockers/overview/overviewdocker_dock.h
@@ -1,56 +1,57 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _OVERVIEW_DOCK_H_
#define _OVERVIEW_DOCK_H_
#include <QPointer>
#include <QDockWidget>
#include <kis_slider_spin_box.h>
#include <KoCanvasObserverBase.h>
#include <kis_canvas2.h>
class QVBoxLayout;
class QHBoxLayout;
class QToolButton;
class OverviewWidget;
class OverviewDockerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
OverviewDockerDock();
QString observerName() override { return "OverviewDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
public Q_SLOTS:
void rotateCanvasView(qreal rotation);
void updateSlider();
private:
QVBoxLayout *m_layout;
QHBoxLayout *m_horizontalLayout;
OverviewWidget *m_overviewWidget;
QWidget *m_zoomSlider;
KisDoubleSliderSpinBox *m_rotateSlider;
QToolButton *m_mirrorCanvas;
QPointer<KisCanvas2> m_canvas;
};
#endif
diff --git a/plugins/dockers/overview/overviewwidget.cc b/plugins/dockers/overview/overviewwidget.cc
index 20c4bcf926..201c60a13e 100644
--- a/plugins/dockers/overview/overviewwidget.cc
+++ b/plugins/dockers/overview/overviewwidget.cc
@@ -1,381 +1,382 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2014 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "overviewwidget.h"
#include <QMouseEvent>
#include <QPainter>
#include <QCursor>
#include <QMutex>
#include <KoCanvasController.h>
#include <KoZoomController.h>
#include <kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_signal_compressor.h>
#include <kis_config.h>
#include "kis_idle_watcher.h"
#include "krita_utils.h"
#include "kis_painter.h"
#include <KoUpdater.h>
#include "kis_transform_worker.h"
#include "kis_filter_strategy.h"
#include <KoColorSpaceRegistry.h>
#include <QApplication>
const qreal oversample = 2.;
const int thumbnailTileDim = 128;
struct OverviewThumbnailStrokeStrategy::Private {
class ProcessData : public KisStrokeJobData
{
public:
ProcessData(KisPaintDeviceSP _dev, KisPaintDeviceSP _thumbDev, const QSize& _thumbnailSize, const QRect &_rect)
: KisStrokeJobData(CONCURRENT),
dev(_dev), thumbDev(_thumbDev), thumbnailSize(_thumbnailSize), tileRect(_rect)
{}
KisPaintDeviceSP dev;
KisPaintDeviceSP thumbDev;
QSize thumbnailSize;
QRect tileRect;
};
class FinishProcessing : public KisStrokeJobData
{
public:
FinishProcessing(KisPaintDeviceSP _thumbDev, const QSize& _thumbnailSize)
: KisStrokeJobData(SEQUENTIAL),
thumbDev(_thumbDev), thumbnailSize(_thumbnailSize)
{}
KisPaintDeviceSP thumbDev;
QSize thumbnailSize;
};
};
OverviewWidget::OverviewWidget(QWidget * parent)
: QWidget(parent)
, m_canvas(0)
, m_dragging(false)
, m_imageIdleWatcher(250)
{
setMouseTracking(true);
KisConfig cfg(true);
slotThemeChanged();
}
OverviewWidget::~OverviewWidget()
{
}
void OverviewWidget::setCanvas(KoCanvasBase * canvas)
{
if (m_canvas) {
m_canvas->image()->disconnect(this);
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas) {
m_imageIdleWatcher.setTrackedImage(m_canvas->image());
connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &OverviewWidget::generateThumbnail);
connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)),SLOT(startUpdateCanvasProjection()));
connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF,QPointF)),SLOT(startUpdateCanvasProjection()));
connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(update()), Qt::UniqueConnection);
connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotThemeChanged()));
generateThumbnail();
}
}
QSize OverviewWidget::recalculatePreviewSize()
{
QSize imageSize(m_canvas->image()->bounds().size());
const qreal hScale = 1.0 * this->width() / imageSize.width();
const qreal vScale = 1.0 * this->height() / imageSize.height();
m_previewScale = qMin(hScale, vScale);
return imageSize * m_previewScale;
}
QPointF OverviewWidget::previewOrigin()
{
const QSize previewSize = recalculatePreviewSize();
return QPointF((width() - previewSize.width()) / 2.0f, (height() - previewSize.height()) / 2.0f);
}
QPolygonF OverviewWidget::previewPolygon()
{
if (m_canvas) {
const QRectF &canvasRect = QRectF(m_canvas->canvasWidget()->rect());
return canvasToPreviewTransform().map(canvasRect);
}
return QPolygonF();
}
QTransform OverviewWidget::previewToCanvasTransform()
{
QTransform previewToImage =
QTransform::fromTranslate(-this->width() / 2.0, -this->height() / 2.0) *
QTransform::fromScale(1.0 / m_previewScale, 1.0 / m_previewScale) *
QTransform::fromTranslate(m_canvas->image()->width() / 2.0, m_canvas->image()->height() / 2.0);
return previewToImage * m_canvas->coordinatesConverter()->imageToWidgetTransform();
}
QTransform OverviewWidget::canvasToPreviewTransform()
{
return previewToCanvasTransform().inverted();
}
void OverviewWidget::startUpdateCanvasProjection()
{
m_imageIdleWatcher.startCountdown();
}
void OverviewWidget::showEvent(QShowEvent *event)
{
Q_UNUSED(event);
m_imageIdleWatcher.startCountdown();
}
void OverviewWidget::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event);
if (m_canvas) {
if (!m_oldPixmap.isNull()) {
QSize newSize = recalculatePreviewSize();
m_pixmap = m_oldPixmap.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
m_imageIdleWatcher.startCountdown();
}
}
void OverviewWidget::mousePressEvent(QMouseEvent* event)
{
if (m_canvas) {
QPointF previewPos = event->pos();
if (!previewPolygon().containsPoint(previewPos, Qt::WindingFill)) {
const QRect& canvasRect = m_canvas->canvasWidget()->rect();
const QPointF newCanvasPos = previewToCanvasTransform().map(previewPos) -
QPointF(canvasRect.width() / 2.0f, canvasRect.height() / 2.0f);
m_canvas->canvasController()->pan(newCanvasPos.toPoint());
}
m_lastPos = previewPos;
m_dragging = true;
}
event->accept();
update();
}
void OverviewWidget::mouseMoveEvent(QMouseEvent* event)
{
if (m_dragging) {
QPointF previewPos = event->pos();
const QPointF lastCanvasPos = previewToCanvasTransform().map(m_lastPos);
const QPointF newCanvasPos = previewToCanvasTransform().map(event->pos());
QPointF diff = newCanvasPos - lastCanvasPos;
m_canvas->canvasController()->pan(diff.toPoint());
m_lastPos = previewPos;
}
event->accept();
}
void OverviewWidget::mouseReleaseEvent(QMouseEvent* event)
{
m_dragging = false;
event->accept();
update();
}
void OverviewWidget::wheelEvent(QWheelEvent* event)
{
float delta = event->delta();
if (delta > 0) {
m_canvas->viewManager()->zoomController()->zoomAction()->zoomIn();
} else {
m_canvas->viewManager()->zoomController()->zoomAction()->zoomOut();
}
}
void OverviewWidget::generateThumbnail()
{
if (isVisible()) {
QMutexLocker locker(&mutex);
if (m_canvas) {
QSize previewSize = recalculatePreviewSize();
if(previewSize.isValid()){
KisImageSP image = m_canvas->image();
if (!strokeId.isNull()) {
image->cancelStroke(strokeId);
strokeId.clear();
}
OverviewThumbnailStrokeStrategy* stroke = new OverviewThumbnailStrokeStrategy(image);
connect(stroke, SIGNAL(thumbnailUpdated(QImage)), this, SLOT(updateThumbnail(QImage)));
strokeId = image->startStroke(stroke);
KisPaintDeviceSP dev = image->projection();
KisPaintDeviceSP thumbDev = new KisPaintDevice(dev->colorSpace());
//creating a special stroke that computes thumbnail image in small chunks that can be quickly interrupted
//if user starts painting
QList<KisStrokeJobData*> jobs = OverviewThumbnailStrokeStrategy::createJobsData(dev, image->bounds(), thumbDev, previewSize);
Q_FOREACH (KisStrokeJobData *jd, jobs) {
image->addJob(strokeId, jd);
}
image->endStroke(strokeId);
}
}
}
}
void OverviewWidget::updateThumbnail(QImage pixmap)
{
m_pixmap = QPixmap::fromImage(pixmap);
m_oldPixmap = m_pixmap.copy();
update();
}
void OverviewWidget::slotThemeChanged()
{
m_outlineColor = qApp->palette().color(QPalette::Highlight);
}
void OverviewWidget::paintEvent(QPaintEvent* event)
{
QWidget::paintEvent(event);
if (m_canvas) {
QPainter p(this);
const QSize previewSize = recalculatePreviewSize();
const QRectF previewRect = QRectF(previewOrigin(), previewSize);
p.drawPixmap(previewRect.toRect(), m_pixmap);
QRect r = rect();
QPolygonF outline;
outline << r.topLeft() << r.topRight() << r.bottomRight() << r.bottomLeft();
QPen pen;
pen.setColor(m_outlineColor);
pen.setStyle(Qt::DashLine);
p.setPen(pen);
p.drawPolygon(outline.intersected(previewPolygon()));
pen.setStyle(Qt::SolidLine);
p.setPen(pen);
p.drawPolygon(previewPolygon());
}
}
OverviewThumbnailStrokeStrategy::OverviewThumbnailStrokeStrategy(KisImageWSP image)
: KisSimpleStrokeStrategy("OverviewThumbnail"), m_image(image)
{
enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE);
//enableJob(KisSimpleStrokeStrategy::JOB_FINISH);
enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
setRequestsOtherStrokesToEnd(false);
setClearsRedoOnStart(false);
setCanForgetAboutMe(true);
}
QList<KisStrokeJobData *> OverviewThumbnailStrokeStrategy::createJobsData(KisPaintDeviceSP dev, const QRect& imageRect, KisPaintDeviceSP thumbDev, const QSize& thumbnailSize)
{
QSize thumbnailOversampledSize = oversample * thumbnailSize;
if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) {
thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio);
}
QVector<QRect> tileRects = KritaUtils::splitRectIntoPatches(QRect(QPoint(0, 0), thumbnailOversampledSize), QSize(thumbnailTileDim, thumbnailTileDim));
QList<KisStrokeJobData*> jobsData;
Q_FOREACH (const QRect &tileRectangle, tileRects) {
jobsData << new OverviewThumbnailStrokeStrategy::Private::ProcessData(dev, thumbDev, thumbnailOversampledSize, tileRectangle);
}
jobsData << new OverviewThumbnailStrokeStrategy::Private::FinishProcessing(thumbDev, thumbnailSize);
return jobsData;
}
OverviewThumbnailStrokeStrategy::~OverviewThumbnailStrokeStrategy()
{
}
void OverviewThumbnailStrokeStrategy::initStrokeCallback()
{
}
void OverviewThumbnailStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
Private::ProcessData *d_pd = dynamic_cast<Private::ProcessData*>(data);
if (d_pd) {
//we aren't going to use oversample capability of createThumbnailDevice because it recomputes exact bounds for each small patch, which is
//slow. We'll handle scaling separately.
KisPaintDeviceSP thumbnailTile = d_pd->dev->createThumbnailDeviceOversampled(d_pd->thumbnailSize.width(), d_pd->thumbnailSize.height(), 1, m_image->bounds(), d_pd->tileRect);
{
QMutexLocker locker(&m_thumbnailMergeMutex);
KisPainter gc(d_pd->thumbDev);
gc.bitBlt(QPoint(d_pd->tileRect.x(), d_pd->tileRect.y()), thumbnailTile, d_pd->tileRect);
}
return;
}
Private::FinishProcessing *d_fp = dynamic_cast<Private::FinishProcessing*>(data);
if (d_fp) {
QImage overviewImage;
KoDummyUpdater updater;
KisTransformWorker worker(d_fp->thumbDev, 1 / oversample, 1 / oversample, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
&updater, KisFilterStrategyRegistry::instance()->value("Bilinear"));
worker.run();
overviewImage = d_fp->thumbDev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(),
QRect(QPoint(0,0), d_fp->thumbnailSize));
emit thumbnailUpdated(overviewImage);
return;
}
}
void OverviewThumbnailStrokeStrategy::finishStrokeCallback()
{
}
void OverviewThumbnailStrokeStrategy::cancelStrokeCallback()
{
}
diff --git a/plugins/dockers/overview/overviewwidget.h b/plugins/dockers/overview/overviewwidget.h
index ff69aba503..b52c60d2d2 100644
--- a/plugins/dockers/overview/overviewwidget.h
+++ b/plugins/dockers/overview/overviewwidget.h
@@ -1,117 +1,118 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2014 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 OVERVIEWWIDGET_H
#define OVERVIEWWIDGET_H
#include <QObject>
#include <QWidget>
#include <QPixmap>
#include <QPointer>
#include <QMutex>
#include "kis_idle_watcher.h"
#include "kis_simple_stroke_strategy.h"
#include <kis_canvas2.h>
class KisSignalCompressor;
class KoCanvasBase;
class OverviewThumbnailStrokeStrategy : public QObject, public KisSimpleStrokeStrategy
{
Q_OBJECT
public:
OverviewThumbnailStrokeStrategy(KisImageWSP image);
~OverviewThumbnailStrokeStrategy() override;
static QList<KisStrokeJobData*> createJobsData(KisPaintDeviceSP dev, const QRect& imageRect, KisPaintDeviceSP thumbDev, const QSize &thumbnailSize);
private:
void initStrokeCallback() override;
void doStrokeCallback(KisStrokeJobData *data) override;
void finishStrokeCallback() override;
void cancelStrokeCallback() override;
Q_SIGNALS:
//Emitted when thumbnail is updated and overviewImage is fully generated.
void thumbnailUpdated(QImage pixmap);
private:
struct Private;
const QScopedPointer<Private> m_d;
QMutex m_thumbnailMergeMutex;
KisImageSP m_image;
};
class OverviewWidget : public QWidget
{
Q_OBJECT
public:
OverviewWidget(QWidget * parent = 0);
~OverviewWidget() override;
virtual void setCanvas(KoCanvasBase *canvas);
virtual void unsetCanvas()
{
m_canvas = 0;
}
public Q_SLOTS:
void startUpdateCanvasProjection();
void generateThumbnail();
void updateThumbnail(QImage pixmap);
void slotThemeChanged();
protected:
void resizeEvent(QResizeEvent *event) override;
void showEvent(QShowEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
private:
QSize recalculatePreviewSize();
QPointF previewOrigin();
QTransform canvasToPreviewTransform();
QTransform previewToCanvasTransform();
QPolygonF previewPolygon();
qreal m_previewScale;
QPixmap m_oldPixmap;
QPixmap m_pixmap;
QPointer<KisCanvas2> m_canvas;
bool m_dragging;
QPointF m_lastPos;
QColor m_outlineColor;
KisIdleWatcher m_imageIdleWatcher;
KisStrokeId strokeId;
QMutex mutex;
};
#endif /* OVERVIEWWIDGET_H */
diff --git a/plugins/dockers/palettedocker/palettedocker.h b/plugins/dockers/palettedocker/palettedocker.h
index 5b6c082b39..3d9745a8a0 100644
--- a/plugins/dockers/palettedocker/palettedocker.h
+++ b/plugins/dockers/palettedocker/palettedocker.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 PALETTEDOCKER_H
#define PALETTEDOCKER_H
#include <QObject>
#include <QVariant>
/**
* Docker showing the channels of the current layer
*/
class PaletteDockerPlugin : public QObject
{
Q_OBJECT
public:
PaletteDockerPlugin(QObject *parent, const QVariantList &);
~PaletteDockerPlugin() override;
};
#endif
diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp
index a271729de6..722026f156 100644
--- a/plugins/dockers/palettedocker/palettedocker_dock.cpp
+++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp
@@ -1,383 +1,402 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "palettedocker_dock.h"
#include <QPainter>
#include <QGridLayout>
#include <QTableView>
#include <QHeaderView>
#include <QWheelEvent>
#include <QColorDialog>
#include <QCompleter>
#include <QComboBox>
#include <QAction>
#include <QMenu>
#include <QCheckBox>
#include <QFormLayout>
#include <QLineEdit>
#include <KisSqueezedComboBox.h>
#include <klocalizedstring.h>
#include <KoResourceServerProvider.h>
#include <KoColorSpaceRegistry.h>
#include <KoFileDialog.h>
#include <kis_icon.h>
#include <kis_config.h>
#include <kis_node_manager.h>
#include <kis_workspace_resource.h>
#include <kis_canvas_resource_provider.h>
#include <KisMainWindow.h>
#include <KisViewManager.h>
#include <kis_display_color_converter.h>
#include <kis_canvas2.h>
#include <KoDialog.h>
#include <kis_color_button.h>
#include <KisDocument.h>
#include <KisPart.h>
#include <KisPaletteModel.h>
#include <KisPaletteDelegate.h>
#include <kis_palette_view.h>
#include <KisPaletteListWidget.h>
#include <KisPaletteEditor.h>
#include <dialogs/KisDlgPaletteEditor.h>
#include "ui_wdgpalettedock.h"
PaletteDockerDock::PaletteDockerDock( )
: QDockWidget(i18n("Palette"))
, m_ui(new Ui_WdgPaletteDock())
, m_model(new KisPaletteModel(this))
, m_paletteChooser(new KisPaletteListWidget(this))
- , m_view(Q_NULLPTR)
- , m_resourceProvider(Q_NULLPTR)
+ , m_view(0)
+ , m_resourceProvider(0)
, m_rServer(KoResourceServerProvider::instance()->paletteServer())
- , m_activeDocument(Q_NULLPTR)
+ , m_activeDocument(0)
, m_paletteEditor(new KisPaletteEditor)
, m_actAdd(new QAction(KisIconUtils::loadIcon("list-add"), i18n("Add a color")))
, m_actRemove(new QAction(KisIconUtils::loadIcon("edit-delete"), i18n("Delete color")))
, m_actModify(new QAction(KisIconUtils::loadIcon("edit-rename"), i18n("Modify this spot")))
, m_actEditPalette(new QAction(KisIconUtils::loadIcon("groupLayer"), i18n("Edit this palette")))
, m_colorSelfUpdate(false)
{
QWidget *mainWidget = new QWidget(this);
setWidget(mainWidget);
m_ui->setupUi(mainWidget);
m_ui->bnAdd->setDefaultAction(m_actAdd.data());
m_ui->bnRemove->setDefaultAction(m_actRemove.data());
m_ui->bnRename->setDefaultAction(m_actModify.data());
m_ui->bnEditPalette->setDefaultAction(m_actEditPalette.data());
// to make sure their icons have the same size
m_ui->bnRemove->setIconSize(QSize(16, 16));
m_ui->bnRename->setIconSize(QSize(16, 16));
m_ui->bnAdd->setIconSize(QSize(16, 16));
m_ui->bnEditPalette->setIconSize(QSize(16, 16));
m_ui->paletteView->setPaletteModel(m_model);
m_ui->paletteView->setAllowModification(true);
m_ui->cmbNameList->setCompanionView(m_ui->paletteView);
m_paletteEditor->setPaletteModel(m_model);
connect(m_actAdd.data(), SIGNAL(triggered()), SLOT(slotAddColor()));
connect(m_actRemove.data(), SIGNAL(triggered()), SLOT(slotRemoveColor()));
connect(m_actModify.data(), SIGNAL(triggered()), SLOT(slotEditEntry()));
connect(m_actEditPalette.data(), SIGNAL(triggered()), SLOT(slotEditPalette()));
connect(m_ui->paletteView, SIGNAL(sigIndexSelected(QModelIndex)),
SLOT(slotPaletteIndexSelected(QModelIndex)));
connect(m_ui->paletteView, SIGNAL(clicked(QModelIndex)),
SLOT(slotPaletteIndexClicked(QModelIndex)));
connect(m_ui->paletteView, SIGNAL(doubleClicked(QModelIndex)),
SLOT(slotPaletteIndexDoubleClicked(QModelIndex)));
+ connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(const KoColor&)), SLOT(slotNameListSelection(const KoColor&)));
m_viewContextMenu.addAction(m_actModify.data());
m_viewContextMenu.addAction(m_actRemove.data());
connect(m_ui->paletteView, SIGNAL(pressed(QModelIndex)), SLOT(slotContextMenu(QModelIndex)));
m_paletteChooser->setAllowModification(true);
connect(m_paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), SLOT(slotSetColorSet(KoColorSet*)));
connect(m_paletteChooser, SIGNAL(sigAddPalette()), SLOT(slotAddPalette()));
connect(m_paletteChooser, SIGNAL(sigImportPalette()), SLOT(slotImportPalette()));
connect(m_paletteChooser, SIGNAL(sigRemovePalette(KoColorSet*)), SLOT(slotRemovePalette(KoColorSet*)));
connect(m_paletteChooser, SIGNAL(sigExportPalette(KoColorSet*)), SLOT(slotExportPalette(KoColorSet*)));
m_ui->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library"));
m_ui->bnColorSets->setToolTip(i18n("Choose palette"));
m_ui->bnColorSets->setPopupWidget(m_paletteChooser);
KisConfig cfg(true);
QString defaultPaletteName = cfg.defaultPalette();
KoColorSet* defaultPalette = m_rServer->resourceByName(defaultPaletteName);
if (defaultPalette) {
slotSetColorSet(defaultPalette);
} else {
m_ui->bnAdd->setEnabled(false);
m_ui->bnRename->setEnabled(false);
m_ui->bnRemove->setEnabled(false);
m_ui->bnEditPalette->setEnabled(false);
m_ui->paletteView->setAllowModification(false);
}
}
PaletteDockerDock::~PaletteDockerDock()
{ }
void PaletteDockerDock::setViewManager(KisViewManager* kisview)
{
m_view = kisview;
m_resourceProvider = kisview->canvasResourceProvider();
connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResource*)),
SLOT(saveToWorkspace(KisWorkspaceResource*)));
connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResource*)),
SLOT(loadFromWorkspace(KisWorkspaceResource*)));
connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)),
this, SLOT(slotFGColorResourceChanged(KoColor)));
-
kisview->nodeManager()->disconnect(m_model);
}
void PaletteDockerDock::slotContextMenu(const QModelIndex &)
{
if (QApplication::mouseButtons() == Qt::RightButton) {
m_viewContextMenu.exec(QCursor::pos());
}
}
void PaletteDockerDock::slotAddPalette()
{
m_paletteEditor->addPalette();
}
void PaletteDockerDock::slotRemovePalette(KoColorSet *cs)
{
m_paletteEditor->removePalette(cs);
}
void PaletteDockerDock::slotImportPalette()
{
m_paletteEditor->importPalette();
}
void PaletteDockerDock::slotExportPalette(KoColorSet *palette)
{
KoFileDialog dialog(this, KoFileDialog::SaveFile, "Save Palette");
dialog.setDefaultDir(palette->filename());
dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset");
QString newPath;
bool isStandAlone = palette->isGlobal();
QString oriPath = palette->filename();
if ((newPath = dialog.filename()).isEmpty()) { return; }
palette->setFilename(newPath);
palette->setIsGlobal(true);
palette->save();
palette->setFilename(oriPath);
palette->setIsGlobal(isStandAlone);
}
void PaletteDockerDock::setCanvas(KoCanvasBase *canvas)
{
- setEnabled(canvas != Q_NULLPTR);
+ setEnabled(canvas != 0);
if (canvas) {
KisCanvas2 *cv = qobject_cast<KisCanvas2*>(canvas);
m_ui->paletteView->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface());
}
if (m_activeDocument) {
+ m_connections.clear();
for (KoColorSet * &cs : m_activeDocument->paletteList()) {
KoColorSet *tmpAddr = cs;
cs = new KoColorSet(*cs);
m_rServer->removeResourceFromServer(tmpAddr);
}
}
if (m_view && m_view->document()) {
m_activeDocument = m_view->document();
m_paletteEditor->setView(m_view);
for (KoColorSet *cs : m_activeDocument->paletteList()) {
m_rServer->addResource(cs);
}
+ m_connections.addConnection(m_activeDocument, &KisDocument::sigPaletteListChanged,
+ this, &PaletteDockerDock::slotUpdatePaletteList);
}
if (!m_currentColorSet) {
- slotSetColorSet(Q_NULLPTR);
+ slotSetColorSet(0);
}
}
void PaletteDockerDock::unsetCanvas()
{
setEnabled(false);
- m_ui->paletteView->setDisplayRenderer(Q_NULLPTR);
- m_paletteEditor->setView(Q_NULLPTR);
+ m_ui->paletteView->setDisplayRenderer(0);
+ m_paletteEditor->setView(0);
for (KoResource *r : m_rServer->resources()) {
KoColorSet *c = static_cast<KoColorSet*>(r);
if (!c->isGlobal()) {
m_rServer->removeResourceFromServer(c);
}
}
if (!m_currentColorSet) {
- slotSetColorSet(Q_NULLPTR);
+ slotSetColorSet(0);
}
}
void PaletteDockerDock::slotSetColorSet(KoColorSet* colorSet)
{
if (colorSet && colorSet->isEditable()) {
m_ui->bnAdd->setEnabled(true);
m_ui->bnRename->setEnabled(true);
m_ui->bnRemove->setEnabled(true);
m_ui->bnEditPalette->setEnabled(true);
m_ui->paletteView->setAllowModification(true);
} else {
m_ui->bnAdd->setEnabled(false);
m_ui->bnRename->setEnabled(false);
m_ui->bnRemove->setEnabled(false);
m_ui->bnEditPalette->setEnabled(false);
m_ui->paletteView->setAllowModification(false);
}
m_currentColorSet = colorSet;
m_model->setPalette(colorSet);
if (colorSet) {
KisConfig cfg(true);
cfg.setDefaultPalette(colorSet->name());
m_ui->lblPaletteName->setTextElideMode(Qt::ElideLeft);
m_ui->lblPaletteName->setText(colorSet->name());
} else {
m_ui->lblPaletteName->setText("");
}
}
void PaletteDockerDock::slotEditPalette()
{
KisDlgPaletteEditor dlg;
if (!m_currentColorSet) { return; }
dlg.setPaletteModel(m_model);
dlg.setView(m_view);
if (dlg.exec() != QDialog::Accepted){ return; }
slotSetColorSet(m_currentColorSet); // update GUI
}
void PaletteDockerDock::slotAddColor()
{
if (m_resourceProvider) {
m_paletteEditor->addEntry(m_resourceProvider->fgColor());
}
}
void PaletteDockerDock::slotRemoveColor()
{
QModelIndex index = m_ui->paletteView->currentIndex();
if (!index.isValid()) {
return;
}
m_paletteEditor->removeEntry(index);
m_ui->bnRemove->setEnabled(false);
}
void PaletteDockerDock::setFGColorByPalette(const KisSwatch &entry)
{
if (m_resourceProvider) {
m_colorSelfUpdate = true;
m_resourceProvider->setFGColor(entry.color());
m_colorSelfUpdate = false;
}
}
void PaletteDockerDock::saveToWorkspace(KisWorkspaceResource* workspace)
{
if (!m_currentColorSet.isNull()) {
workspace->setProperty("palette", m_currentColorSet->name());
}
}
void PaletteDockerDock::loadFromWorkspace(KisWorkspaceResource* workspace)
{
if (workspace->hasProperty("palette")) {
KoResourceServer<KoColorSet>* rServer = KoResourceServerProvider::instance()->paletteServer();
KoColorSet* colorSet = rServer->resourceByName(workspace->getString("palette"));
if (colorSet) {
slotSetColorSet(colorSet);
}
}
}
void PaletteDockerDock::slotFGColorResourceChanged(const KoColor &color)
{
if (!m_colorSelfUpdate) {
- m_ui->paletteView->slotFGColorResourceChanged(color);
+ m_ui->paletteView->slotFGColorChanged(color);
}
}
void PaletteDockerDock::slotPaletteIndexSelected(const QModelIndex &index)
{
bool occupied = qvariant_cast<bool>(index.data(KisPaletteModel::CheckSlotRole));
if (occupied) {
if (!qvariant_cast<bool>(index.data(KisPaletteModel::IsGroupNameRole))) {
m_ui->bnRemove->setEnabled(true);
KisSwatch entry = m_model->getEntry(index);
setFGColorByPalette(entry);
}
}
if (!m_currentColorSet->isEditable()) { return; }
m_ui->bnRemove->setEnabled(occupied);
}
void PaletteDockerDock::slotPaletteIndexClicked(const QModelIndex &index)
{
if (!(qvariant_cast<bool>(index.data(KisPaletteModel::CheckSlotRole)))) {
setEntryByForeground(index);
}
}
void PaletteDockerDock::slotPaletteIndexDoubleClicked(const QModelIndex &index)
{
m_paletteEditor->modifyEntry(index);
}
void PaletteDockerDock::setEntryByForeground(const QModelIndex &index)
{
m_paletteEditor->setEntry(m_resourceProvider->fgColor(), index);
if (m_currentColorSet->isEditable()) {
m_ui->bnRemove->setEnabled(true);
}
}
void PaletteDockerDock::slotEditEntry()
{
QModelIndex index = m_ui->paletteView->currentIndex();
if (!index.isValid()) {
return;
}
m_paletteEditor->modifyEntry(index);
}
void PaletteDockerDock::slotNameListSelection(const KoColor &color)
{
m_colorSelfUpdate = true;
+ m_ui->paletteView->selectClosestColor(color);
m_resourceProvider->setFGColor(color);
m_colorSelfUpdate = false;
}
+
+void PaletteDockerDock::slotUpdatePaletteList(const QList<KoColorSet *> &oldPaletteList, const QList<KoColorSet *> &newPaletteList)
+{
+ for (KoColorSet *cs : oldPaletteList) {
+ m_rServer->removeResourceFromServer(cs);
+ }
+
+ for (KoColorSet *cs : newPaletteList) {
+ m_rServer->addResource(cs);
+ }
+
+ if (!m_currentColorSet) {
+ slotSetColorSet(0);
+ }
+}
diff --git a/plugins/dockers/palettedocker/palettedocker_dock.h b/plugins/dockers/palettedocker/palettedocker_dock.h
index 3776d58bbe..fff55179a2 100644
--- a/plugins/dockers/palettedocker/palettedocker_dock.h
+++ b/plugins/dockers/palettedocker/palettedocker_dock.h
@@ -1,115 +1,119 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef PALETTEDOCKER_DOCK_H
#define PALETTEDOCKER_DOCK_H
#include <QDockWidget>
#include <QModelIndex>
#include <QScopedPointer>
#include <QVector>
#include <QPointer>
#include <QPair>
#include <QAction>
#include <QMenu>
#include <KoResourceServerObserver.h>
#include <KoResourceServer.h>
#include <resources/KoColorSet.h>
#include <kis_canvas2.h>
#include <kis_mainwindow_observer.h>
#include <KisView.h>
+#include <kis_signal_auto_connection.h>
class KisViewManager;
class KisCanvasResourceProvider;
class KisWorkspaceResource;
class KisPaletteListWidget;
class KisPaletteModel;
class KisPaletteEditor;
class Ui_WdgPaletteDock;
class PaletteDockerDock : public QDockWidget, public KisMainwindowObserver
{
Q_OBJECT
public:
PaletteDockerDock();
~PaletteDockerDock() override;
public: // QDockWidget
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
public: // KisMainWindowObserver
void setViewManager(KisViewManager* kisview) override;
private Q_SLOTS:
void slotContextMenu(const QModelIndex &);
void slotAddPalette();
void slotRemovePalette(KoColorSet *);
void slotImportPalette();
void slotExportPalette(KoColorSet *);
void slotAddColor();
void slotRemoveColor();
void slotEditEntry();
void slotEditPalette();
void slotPaletteIndexSelected(const QModelIndex &index);
void slotPaletteIndexClicked(const QModelIndex &index);
void slotPaletteIndexDoubleClicked(const QModelIndex &index);
void slotNameListSelection(const KoColor &color);
void slotSetColorSet(KoColorSet* colorSet);
void saveToWorkspace(KisWorkspaceResource* workspace);
void loadFromWorkspace(KisWorkspaceResource* workspace);
void slotFGColorResourceChanged(const KoColor& color);
+ void slotUpdatePaletteList(const QList<KoColorSet *> &oldPaletteList, const QList<KoColorSet *> &newPaletteList);
private:
void setEntryByForeground(const QModelIndex &index);
void setFGColorByPalette(const KisSwatch &entry);
private /* member variables */:
QScopedPointer<Ui_WdgPaletteDock> m_ui;
KisPaletteModel *m_model;
KisPaletteListWidget *m_paletteChooser;
QPointer<KisViewManager> m_view;
KisCanvasResourceProvider *m_resourceProvider;
KoResourceServer<KoColorSet> * const m_rServer;
QPointer<KisDocument> m_activeDocument;
QPointer<KoColorSet> m_currentColorSet;
QScopedPointer<KisPaletteEditor> m_paletteEditor;
QScopedPointer<QAction> m_actAdd;
QScopedPointer<QAction> m_actRemove;
QScopedPointer<QAction> m_actModify;
QScopedPointer<QAction> m_actEditPalette;
QMenu m_viewContextMenu;
bool m_colorSelfUpdate;
+
+ KisSignalAutoConnectionsStore m_connections;
};
#endif
diff --git a/plugins/dockers/patterndocker/patterndocker.cpp b/plugins/dockers/patterndocker/patterndocker.cpp
index cb22ea5f96..ff7c59e587 100644
--- a/plugins/dockers/patterndocker/patterndocker.cpp
+++ b/plugins/dockers/patterndocker/patterndocker.cpp
@@ -1,87 +1,88 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "patterndocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include <kis_debug.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "patterndocker_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(PatternDockerPluginFactory, "krita_patterndocker.json", registerPlugin<PatternDockerPlugin>();)
class PatternDockerDockFactory : public KoDockFactoryBase {
public:
PatternDockerDockFactory()
{
}
QString id() const override
{
return QString( "PatternDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
PatternDockerDock * dockWidget = new PatternDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
PatternDockerPlugin::PatternDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new PatternDockerDockFactory());
}
PatternDockerPlugin::~PatternDockerPlugin()
{
m_view = 0;
}
#include "patterndocker.moc"
diff --git a/plugins/dockers/patterndocker/patterndocker.h b/plugins/dockers/patterndocker/patterndocker.h
index 1d4b908b10..0c440d052a 100644
--- a/plugins/dockers/patterndocker/patterndocker.h
+++ b/plugins/dockers/patterndocker/patterndocker.h
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _PATTERN_DOCKER_H_
#define _PATTERN_DOCKER_H_
#include <QObject>
#include <QVariant>
class KisViewManager;
/**
* Template of view plugin
*/
class PatternDockerPlugin : public QObject
{
Q_OBJECT
public:
PatternDockerPlugin(QObject *parent, const QVariantList &);
~PatternDockerPlugin() override;
private:
KisViewManager* m_view;
};
#endif
diff --git a/plugins/dockers/patterndocker/patterndocker_dock.cpp b/plugins/dockers/patterndocker/patterndocker_dock.cpp
index c30692885b..0b43d6b1fb 100644
--- a/plugins/dockers/patterndocker/patterndocker_dock.cpp
+++ b/plugins/dockers/patterndocker/patterndocker_dock.cpp
@@ -1,69 +1,70 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "patterndocker_dock.h"
#include <QHBoxLayout>
#include <QPushButton>
#include <klocalizedstring.h>
#include <kis_canvas_resource_provider.h>
#include <kis_pattern_chooser.h>
#include <KisViewManager.h>
#include <resources/KoPattern.h>
PatternDockerDock::PatternDockerDock( )
: QDockWidget(i18n("Patterns"))
{
m_patternChooser = new KisPatternChooser(this);
m_patternChooser->setPreviewOrientation(Qt::Vertical);
m_patternChooser->setCurrentItem(0,0);
m_patternChooser->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_patternChooser->setMinimumHeight(160);
setWidget(m_patternChooser);
}
void PatternDockerDock::setViewManager(KisViewManager* kisview)
{
KisCanvasResourceProvider* resourceProvider = kisview->canvasResourceProvider();
connect(resourceProvider, SIGNAL(sigPatternChanged(KoPattern*)),
this, SLOT(patternChanged(KoPattern*)));
connect(m_patternChooser, SIGNAL(resourceSelected(KoResource*)),
resourceProvider, SLOT(slotPatternActivated(KoResource*)));
}
void PatternDockerDock::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
}
void PatternDockerDock::unsetCanvas()
{
setEnabled(false);
}
void PatternDockerDock::patternChanged(KoPattern *pattern)
{
m_patternChooser->setCurrentPattern(pattern);
}
diff --git a/plugins/dockers/patterndocker/patterndocker_dock.h b/plugins/dockers/patterndocker/patterndocker_dock.h
index bc4113b6c5..29f40fd71a 100644
--- a/plugins/dockers/patterndocker/patterndocker_dock.h
+++ b/plugins/dockers/patterndocker/patterndocker_dock.h
@@ -1,46 +1,47 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _PATTERN_DOCK_H_
#define _PATTERN_DOCK_H_
#include <QDockWidget>
#include <kis_mainwindow_observer.h>
class KoPattern;
class KisPatternChooser;
class PatternDockerDock : public QDockWidget, public KisMainwindowObserver {
Q_OBJECT
public:
PatternDockerDock( );
void setViewManager(KisViewManager* kisview) override;
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
QString observerName() override { return "PatternDockerDock"; }
public Q_SLOTS:
void patternChanged(KoPattern *pattern);
private Q_SLOTS:
private:
KisPatternChooser* m_patternChooser;
};
#endif
diff --git a/plugins/dockers/presetdocker/presetdocker.cpp b/plugins/dockers/presetdocker/presetdocker.cpp
index b6870de8d0..47547d0feb 100644
--- a/plugins/dockers/presetdocker/presetdocker.cpp
+++ b/plugins/dockers/presetdocker/presetdocker.cpp
@@ -1,87 +1,88 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "presetdocker.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "presetdocker_dock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(PresetDockerPluginFactory, "krita_presetdocker.json", registerPlugin<PresetDockerPlugin>();)
class PresetDockerDockFactory : public KoDockFactoryBase {
public:
PresetDockerDockFactory()
{
}
QString id() const override
{
return QString( "PresetDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
PresetDockerDock * dockWidget = new PresetDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
PresetDockerPlugin::PresetDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new PresetDockerDockFactory());
}
PresetDockerPlugin::~PresetDockerPlugin()
{
m_view = 0;
}
#include "presetdocker.moc"
diff --git a/plugins/dockers/presetdocker/presetdocker.h b/plugins/dockers/presetdocker/presetdocker.h
index 0635425c62..ab8cf1ffa5 100644
--- a/plugins/dockers/presetdocker/presetdocker.h
+++ b/plugins/dockers/presetdocker/presetdocker.h
@@ -1,39 +1,40 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _PRESET_DOCKER_H_
#define _PRESET_DOCKER_H_
#include <QObject>
#include <QVariant>
class KisViewManager;
/**
* Template of view plugin
*/
class PresetDockerPlugin : public QObject
{
Q_OBJECT
public:
PresetDockerPlugin(QObject *parent, const QVariantList &);
~PresetDockerPlugin() override;
private:
KisViewManager* m_view;
};
#endif
diff --git a/plugins/dockers/presetdocker/presetdocker_dock.cpp b/plugins/dockers/presetdocker/presetdocker_dock.cpp
index 9ee9c152d2..82322f09b5 100644
--- a/plugins/dockers/presetdocker/presetdocker_dock.cpp
+++ b/plugins/dockers/presetdocker/presetdocker_dock.cpp
@@ -1,85 +1,86 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "presetdocker_dock.h"
#include <QHBoxLayout>
#include <QPushButton>
#include <klocalizedstring.h>
#include <KoCanvasResourceProvider.h>
#include <KoCanvasBase.h>
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_paintop_box.h"
#include "kis_paintop_presets_chooser_popup.h"
#include "kis_canvas_resource_provider.h"
#include <brushengine/kis_paintop_preset.h>
PresetDockerDock::PresetDockerDock( )
: QDockWidget(i18n("Brush Presets"))
, m_canvas(0)
{
m_presetChooser = new KisPaintOpPresetsChooserPopup(this);
m_presetChooser->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_presetChooser->showButtons(false);
setWidget(m_presetChooser);
}
void PresetDockerDock::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_presetChooser->disconnect(m_canvas->viewManager()->paintOpBox());
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (!m_canvas || !m_canvas->viewManager() || !m_canvas->resourceManager()) return;
connect(m_presetChooser, SIGNAL(resourceSelected(KoResource*)),
m_canvas->viewManager()->paintOpBox(), SLOT(resourceSelected(KoResource*)));
connect(m_presetChooser, SIGNAL(resourceClicked(KoResource*)),
m_canvas->viewManager()->paintOpBox(), SLOT(resourceSelected(KoResource*)));
connect(canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(canvasResourceChanged(int,QVariant)));
connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), m_presetChooser, SLOT(slotThemeChanged()));
canvasResourceChanged();
}
void PresetDockerDock::canvasResourceChanged(int /*key*/, const QVariant& /*v*/)
{
if (m_canvas && m_canvas->resourceManager()) {
if (sender()) sender()->blockSignals(true);
KisPaintOpPresetSP preset = m_canvas->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if(preset)
m_presetChooser->canvasResourceChanged(preset);
if (sender()) sender()->blockSignals(false);
m_presetChooser->updateViewSettings();
}
}
diff --git a/plugins/dockers/presetdocker/presetdocker_dock.h b/plugins/dockers/presetdocker/presetdocker_dock.h
index 6d77ef2289..9ae08a239a 100644
--- a/plugins/dockers/presetdocker/presetdocker_dock.h
+++ b/plugins/dockers/presetdocker/presetdocker_dock.h
@@ -1,43 +1,44 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _PRESETDOCKER_DOCK_H_
#define _PRESETDOCKER_DOCK_H_
#include <QPointer>
#include <QDockWidget>
#include <KoCanvasObserverBase.h>
#include <kis_canvas2.h>
class KisPaintOpPresetsChooserPopup;
class PresetDockerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
PresetDockerDock( );
QString observerName() override { return "PresetDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override { m_canvas = 0; setEnabled(false);}
public Q_SLOTS:
void canvasResourceChanged(int key = 0, const QVariant& v = QVariant());
private:
QPointer<KisCanvas2> m_canvas;
KisPaintOpPresetsChooserPopup* m_presetChooser;
};
#endif
diff --git a/plugins/dockers/presethistory/presethistory.cpp b/plugins/dockers/presethistory/presethistory.cpp
index b2e67bc232..0a40a024a1 100644
--- a/plugins/dockers/presethistory/presethistory.cpp
+++ b/plugins/dockers/presethistory/presethistory.cpp
@@ -1,70 +1,71 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 "presethistory.h"
#include <kpluginfactory.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "presethistory_dock.h"
K_PLUGIN_FACTORY_WITH_JSON(PresetHistoryPluginFactory, "krita_presethistory.json", registerPlugin<PresetHistoryPlugin>();)
class PresetHistoryDockFactory : public KoDockFactoryBase
{
public:
PresetHistoryDockFactory()
{
}
QString id() const override
{
return QString( "PresetHistory" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
PresetHistoryDock * dockWidget = new PresetHistoryDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
};
PresetHistoryPlugin::PresetHistoryPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new PresetHistoryDockFactory());
}
PresetHistoryPlugin::~PresetHistoryPlugin()
{
}
#include "presethistory.moc"
diff --git a/plugins/dockers/presethistory/presethistory.h b/plugins/dockers/presethistory/presethistory.h
index 41cfc3f59a..70c12efdbc 100644
--- a/plugins/dockers/presethistory/presethistory.h
+++ b/plugins/dockers/presethistory/presethistory.h
@@ -1,35 +1,36 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 _PRESET_DOCKER_H_
#define _PRESET_DOCKER_H_
#include <QObject>
#include <QVariant>
/**
* Shows the last used presets
*/
class PresetHistoryPlugin : public QObject
{
Q_OBJECT
public:
PresetHistoryPlugin(QObject *parent, const QVariantList &);
~PresetHistoryPlugin() override;
};
#endif
diff --git a/plugins/dockers/presethistory/presethistory_dock.cpp b/plugins/dockers/presethistory/presethistory_dock.cpp
index cb7023c848..d231b910db 100644
--- a/plugins/dockers/presethistory/presethistory_dock.cpp
+++ b/plugins/dockers/presethistory/presethistory_dock.cpp
@@ -1,150 +1,151 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 "presethistory_dock.h"
#include <QHBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QImage>
#include <klocalizedstring.h>
#include <KoCanvasResourceProvider.h>
#include <KoCanvasBase.h>
#include "kis_config.h"
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_paintop_box.h"
#include "kis_paintop_presets_chooser_popup.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include <KisKineticScroller.h>
#include <brushengine/kis_paintop_preset.h>
#include <kis_types.h>
#define ICON_SIZE 48
PresetHistoryDock::PresetHistoryDock( )
: QDockWidget(i18n("Brush Preset History"))
, m_canvas(0)
, m_block(false)
, m_initialized(false)
{
m_presetHistory = new QListWidget(this);
m_presetHistory->setIconSize(QSize(ICON_SIZE, ICON_SIZE));
m_presetHistory->setDragEnabled(false);
m_presetHistory->setSelectionBehavior(QAbstractItemView::SelectRows);
m_presetHistory->setSelectionMode(QAbstractItemView::SingleSelection);
m_presetHistory->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
setWidget(m_presetHistory);
QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(m_presetHistory);
if( scroller ) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
connect(m_presetHistory, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(presetSelected(QListWidgetItem*)));
}
void PresetHistoryDock::setCanvas(KoCanvasBase * canvas)
{
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
disconnect(m_canvas->resourceManager());
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (!m_canvas || !m_canvas->viewManager() || !m_canvas->resourceManager()) return;
connect(canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(canvasResourceChanged(int,QVariant)));
if (!m_initialized) {
KisConfig cfg(true);
QStringList presetHistory = cfg.readEntry<QString>("presethistory", "").split(",", QString::SkipEmptyParts);
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
Q_FOREACH (const QString &p, presetHistory) {
KisPaintOpPresetSP preset = rserver->resourceByName(p);
addPreset(preset);
}
m_initialized = true;
}
}
void PresetHistoryDock::unsetCanvas()
{
m_canvas = 0;
setEnabled(false);
QStringList presetHistory;
for(int i = m_presetHistory->count() -1; i >=0; --i) {
QListWidgetItem *item = m_presetHistory->item(i);
QVariant v = item->data(Qt::UserRole);
KisPaintOpPresetSP preset = v.value<KisPaintOpPresetSP>();
presetHistory << preset->name();
}
KisConfig cfg(false);
cfg.writeEntry("presethistory", presetHistory.join(","));
}
void PresetHistoryDock::presetSelected(QListWidgetItem *item)
{
if (item) {
QVariant v = item->data(Qt::UserRole);
KisPaintOpPresetSP preset = v.value<KisPaintOpPresetSP>();
m_block = true;
m_canvas->viewManager()->paintOpBox()->resourceSelected(preset.data());
m_block = false;
}
}
void PresetHistoryDock::canvasResourceChanged(int key, const QVariant& /*v*/)
{
if (m_block) return;
if (m_canvas && key == KisCanvasResourceProvider::CurrentPaintOpPreset) {
KisPaintOpPresetSP preset = m_canvas->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if (preset) {
for (int i = 0; i < m_presetHistory->count(); ++i) {
if (preset->name() == m_presetHistory->item(i)->text()) {
m_presetHistory->setCurrentRow(i);
return;
}
}
addPreset(preset);
}
}
}
void PresetHistoryDock::addPreset(KisPaintOpPresetSP preset)
{
if (preset) {
QListWidgetItem *item = new QListWidgetItem(QPixmap::fromImage(preset->image()), preset->name());
QVariant v = QVariant::fromValue<KisPaintOpPresetSP>(preset);
item->setData(Qt::UserRole, v);
m_presetHistory->insertItem(0, item);
m_presetHistory->setCurrentRow(0);
if (m_presetHistory->count() > 10) {
m_presetHistory->takeItem(10);
}
}
}
diff --git a/plugins/dockers/presethistory/presethistory_dock.h b/plugins/dockers/presethistory/presethistory_dock.h
index b25e54d229..7af9674812 100644
--- a/plugins/dockers/presethistory/presethistory_dock.h
+++ b/plugins/dockers/presethistory/presethistory_dock.h
@@ -1,55 +1,56 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 _PRESETHISTORY_DOCK_H_
#define _PRESETHISTORY_DOCK_H_
#include <QDockWidget>
#include <QPointer>
#include <KisKineticScroller.h>
#include <KoCanvasObserverBase.h>
#include <kis_canvas2.h>
#include <kis_types.h>
class QListWidget;
class QListWidgetItem;
class PresetHistoryDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
public:
PresetHistoryDock();
QString observerName() override { return "PresetHistoryDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
public Q_SLOTS:
void slotScrollerStateChanged(QScroller::State state){ KisKineticScroller::updateCursor(this, state); }
private Q_SLOTS:
void presetSelected(QListWidgetItem* item);
void canvasResourceChanged(int key, const QVariant& v);
private:
void addPreset(KisPaintOpPresetSP preset);
private:
QPointer<KisCanvas2> m_canvas;
QListWidget *m_presetHistory;
bool m_block;
bool m_initialized;
};
#endif
diff --git a/plugins/dockers/smallcolorselector/kis_small_color_widget.cc b/plugins/dockers/smallcolorselector/kis_small_color_widget.cc
index db47426e6d..db65f16ec6 100644
--- a/plugins/dockers/smallcolorselector/kis_small_color_widget.cc
+++ b/plugins/dockers/smallcolorselector/kis_small_color_widget.cc
@@ -1,516 +1,517 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_small_color_widget.h"
#include <QTimer>
#include "kis_slider_spin_box.h"
#include <QVBoxLayout>
#include "kis_signal_compressor.h"
#include <ImfRgba.h>
#include <KoColorConversions.h>
#include <KoColorProfile.h>
#include "kis_debug.h"
#include "kis_assert.h"
#include <KoColor.h>
#include "KisGLImageF16.h"
#include "KisGLImageWidget.h"
#include "KisClickableGLImageWidget.h"
#include "kis_display_color_converter.h"
#include "kis_signal_auto_connection.h"
#include "kis_signal_compressor_with_param.h"
#include <KoColorModelStandardIds.h>
#include <KoColorSpaceRegistry.h>
#include "kis_fixed_paint_device.h"
#include <opengl/KisOpenGLModeProber.h>
struct KisSmallColorWidget::Private {
qreal hue; // 0 ... 1.0
qreal value; // 0 ... 1.0
qreal saturation; // 0 ... 1.0
bool updateAllowed;
KisClickableGLImageWidget *hueWidget;
KisClickableGLImageWidget *valueWidget;
KisSignalCompressor *repaintCompressor;
KisSignalCompressor *resizeUpdateCompressor;
KisSignalCompressor *valueSliderUpdateCompressor;
KisSignalCompressor *colorChangedSignalCompressor;
KisSignalCompressorWithParam<int> *dynamicRangeCompressor;
int huePreferredHeight = 32;
KisSliderSpinBox *dynamicRange = 0;
qreal currentRelativeDynamicRange = 1.0;
KisDisplayColorConverter *displayColorConverter = KisDisplayColorConverter::dumbConverterInstance();
KisSignalAutoConnectionsStore colorConverterConnections;
bool hasHDR = false;
bool hasHardwareHDR = false;
qreal effectiveRelativeDynamicRange() const {
return hasHDR ? currentRelativeDynamicRange : 1.0;
}
const KoColorSpace *outputColorSpace() {
return
KoColorSpaceRegistry::instance()->
colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(),
displayColorConverter->openGLCanvasSurfaceProfile());
}
const KoColorSpace *generationColorSpace() {
const KoColorSpace *result = displayColorConverter->paintingColorSpace();
if (!result || result->colorModelId() != RGBAColorModelID) {
result = outputColorSpace();
} else if (result->colorDepthId() != Float32BitsColorDepthID) {
result = KoColorSpaceRegistry::instance()->
colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), result->profile());
}
// PQ color space we deliniearize into linear one
if (result->colorModelId() == RGBAColorModelID &&
result->profile()->uniqueId() == KoColorSpaceRegistry::instance()->p2020PQProfile()->uniqueId()) {
result = KoColorSpaceRegistry::instance()->
colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p2020G10Profile());
}
return result;
}
};
KisSmallColorWidget::KisSmallColorWidget(QWidget* parent)
: QWidget(parent),
d(new Private)
{
d->hue = 0.0;
d->value = 0;
d->saturation = 0;
d->updateAllowed = true;
d->repaintCompressor = new KisSignalCompressor(20, KisSignalCompressor::FIRST_ACTIVE, this);
connect(d->repaintCompressor, SIGNAL(timeout()), SLOT(update()));
d->resizeUpdateCompressor = new KisSignalCompressor(200, KisSignalCompressor::FIRST_ACTIVE, this);
connect(d->resizeUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdatePalettes()));
d->valueSliderUpdateCompressor = new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE, this);
connect(d->valueSliderUpdateCompressor, SIGNAL(timeout()), SLOT(updateSVPalette()));
d->colorChangedSignalCompressor = new KisSignalCompressor(20, KisSignalCompressor::FIRST_ACTIVE, this);
connect(d->colorChangedSignalCompressor, SIGNAL(timeout()), SLOT(slotTellColorChanged()));
{
using namespace std::placeholders;
std::function<void (qreal)> callback(
std::bind(&KisSmallColorWidget::updateDynamicRange, this, _1));
d->dynamicRangeCompressor = new KisSignalCompressorWithParam<int>(50, callback);
}
const KisSurfaceColorSpace colorSpace = KisSurfaceColorSpace::DefaultColorSpace;
d->hueWidget = new KisClickableGLImageWidget(colorSpace, this);
d->hueWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
d->hueWidget->setHandlePaintingStrategy(new KisClickableGLImageWidget::VerticalLineHandleStrategy);
connect(d->hueWidget, SIGNAL(selected(const QPointF&)), SLOT(slotHueSliderChanged(const QPointF&)));
d->valueWidget = new KisClickableGLImageWidget(colorSpace, this);
d->valueWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
d->valueWidget->setHandlePaintingStrategy(new KisClickableGLImageWidget::CircularHandleStrategy);
connect(d->valueWidget, SIGNAL(selected(const QPointF&)), SLOT(slotValueSliderChanged(const QPointF&)));
d->hasHardwareHDR = KisOpenGLModeProber::instance()->useHDRMode();
if (d->hasHardwareHDR) {
d->dynamicRange = new KisSliderSpinBox(this);
d->dynamicRange->setRange(80, 10000);
d->dynamicRange->setExponentRatio(3.0);
d->dynamicRange->setSingleStep(1);
d->dynamicRange->setPageStep(100);
d->dynamicRange->setSuffix("cd/m²");
d->dynamicRange->setValue(80.0 * d->currentRelativeDynamicRange);
connect(d->dynamicRange, SIGNAL(valueChanged(int)), SLOT(slotInitiateUpdateDynamicRange(int)));
}
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(d->hueWidget, 0);
layout->addWidget(d->valueWidget, 1);
if (d->dynamicRange) {
layout->addSpacing(16);
layout->addWidget(d->dynamicRange, 0);
}
setLayout(layout);
slotUpdatePalettes();
}
KisSmallColorWidget::~KisSmallColorWidget()
{
delete d;
}
void KisSmallColorWidget::setHue(qreal h)
{
h = qBound(0.0, h, 1.0);
d->hue = h;
d->colorChangedSignalCompressor->start();
d->valueSliderUpdateCompressor->start();
d->repaintCompressor->start();
}
void KisSmallColorWidget::setHSV(qreal h, qreal s, qreal v, bool notifyChanged)
{
h = qBound(0.0, h, 1.0);
s = qBound(0.0, s, 1.0);
v = qBound(0.0, v, 1.0);
bool newH = !qFuzzyCompare(d->hue, h);
d->hue = h;
d->value = v;
d->saturation = s;
// TODO: remove and make acyclic!
if (notifyChanged) {
d->colorChangedSignalCompressor->start();
}
if(newH) {
d->valueSliderUpdateCompressor->start();
}
d->repaintCompressor->start();
}
void KisSmallColorWidget::setColor(const KoColor &color)
{
if (!d->updateAllowed) return;
KIS_SAFE_ASSERT_RECOVER(!d->dynamicRange || d->hasHDR == d->dynamicRange->isEnabled()) {
slotDisplayConfigurationChanged();
}
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->hasHDR || d->hasHardwareHDR);
const KoColorSpace *cs = d->generationColorSpace();
KIS_SAFE_ASSERT_RECOVER_RETURN(cs);
KoColor newColor(color);
newColor.convertTo(cs);
QVector<float> channels(4);
cs->normalisedChannelsValue(newColor.data(), channels);
float r, g, b;
if (cs->colorDepthId() == Integer8BitsColorDepthID) {
r = channels[2];
g = channels[1];
b = channels[0];
} else {
r = channels[0];
g = channels[1];
b = channels[2];
}
if (d->hasHDR) {
qreal rangeCoeff = d->effectiveRelativeDynamicRange();
if (rangeCoeff < r || rangeCoeff < g || rangeCoeff < b) {
rangeCoeff = std::max({r, g, b}) * 1.10f;
const int newMaxLuminance = qRound(80.0 * rangeCoeff);
updateDynamicRange(newMaxLuminance);
d->dynamicRange->setValue(newMaxLuminance);
}
r /= rangeCoeff;
g /= rangeCoeff;
b /= rangeCoeff;
} else {
r = qBound(0.0f, r, 1.0f);
g = qBound(0.0f, g, 1.0f);
b = qBound(0.0f, b, 1.0f);
}
float denormHue, saturation, value;
RGBToHSV(r, g, b, &denormHue, &saturation, &value);
d->hueWidget->setNormalizedPos(QPointF(denormHue / 360.0, 0.0));
d->valueWidget->setNormalizedPos(QPointF(saturation, 1.0 - value));
setHSV(denormHue / 360.0, saturation, value, false);
}
void KisSmallColorWidget::slotUpdatePalettes()
{
updateHuePalette();
updateSVPalette();
}
namespace {
struct FillHPolicy {
static inline void getRGB(qreal /*hue*/, float xPortionCoeff, float /*yPortionCoeff*/,
int x, int /*y*/, float *r, float *g, float *b) {
HSVToRGB(xPortionCoeff * x * 360.0f, 1.0, 1.0, r, g, b);
}
};
struct FillSVPolicy {
static inline void getRGB(qreal hue, float xPortionCoeff, float yPortionCoeff,
int x, int y, float *r, float *g, float *b) {
HSVToRGB(hue * 360.0, xPortionCoeff * x, 1.0 - yPortionCoeff * y, r, g, b);
}
};
}
template<class FillPolicy>
void KisSmallColorWidget::uploadPaletteData(KisGLImageWidget *widget, const QSize &size)
{
if (size.isEmpty()) return;
KisGLImageF16 image(size);
const float xPortionCoeff = 1.0 / image.width();
const float yPortionCoeff = 1.0 / image.height();
const float rangeCoeff = d->effectiveRelativeDynamicRange();
const KoColorSpace *generationColorSpace = d->generationColorSpace();
if (d->displayColorConverter->canSkipDisplayConversion(generationColorSpace)) {
half *pixelPtr = image.data();
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
Imf::Rgba &pxl = reinterpret_cast<Imf::Rgba &>(*pixelPtr);
float r, g, b;
FillPolicy::getRGB(d->hue, xPortionCoeff, yPortionCoeff, x, y,
&r, &g, &b);
pxl.r = r * rangeCoeff;
pxl.g = g * rangeCoeff;
pxl.b = b * rangeCoeff;
pxl.a = 1.0;
pixelPtr += 4;
}
}
} else {
KIS_SAFE_ASSERT_RECOVER_RETURN(d->displayColorConverter);
KisFixedPaintDeviceSP device = new KisFixedPaintDevice(generationColorSpace);
device->setRect(QRect(QPoint(), image.size()));
device->reallocateBufferWithoutInitialization();
float *devicePtr = reinterpret_cast<float*>(device->data());
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
FillPolicy::getRGB(d->hue, xPortionCoeff, yPortionCoeff, x, y,
devicePtr, devicePtr + 1, devicePtr + 2);
devicePtr[0] *= rangeCoeff;
devicePtr[1] *= rangeCoeff;
devicePtr[2] *= rangeCoeff;
devicePtr[3] = 1.0;
devicePtr += 4;
}
}
d->displayColorConverter->applyDisplayFilteringF32(device, Float32BitsColorDepthID);
half *imagePtr = image.data();
devicePtr = reinterpret_cast<float*>(device->data());
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
imagePtr[0] = devicePtr[0];
imagePtr[1] = devicePtr[1];
imagePtr[2] = devicePtr[2];
imagePtr[3] = devicePtr[3];
devicePtr += 4;
imagePtr += 4;
}
}
}
widget->loadImage(image);
}
void KisSmallColorWidget::updateHuePalette()
{
uploadPaletteData<FillHPolicy>(d->hueWidget, QSize(d->hueWidget->width(), d->huePreferredHeight));
}
void KisSmallColorWidget::updateSVPalette()
{
const int maxSize = 256;
QSize newSize = d->valueWidget->size();
newSize.rwidth() = qMin(maxSize, newSize.width());
newSize.rheight() = qMin(maxSize, newSize.height());
uploadPaletteData<FillSVPolicy>(d->valueWidget, newSize);
}
void KisSmallColorWidget::slotHueSliderChanged(const QPointF &pos)
{
const qreal newHue = pos.x();
if (!qFuzzyCompare(newHue, d->hue)) {
setHue(newHue);
}
}
void KisSmallColorWidget::slotValueSliderChanged(const QPointF &pos)
{
const qreal newSaturation = pos.x();
const qreal newValue = 1.0 - pos.y();
if (!qFuzzyCompare(newSaturation, d->saturation) ||
!qFuzzyCompare(newValue, d->value)) {
setHSV(d->hue, newSaturation, newValue);
}
}
void KisSmallColorWidget::slotInitiateUpdateDynamicRange(int maxLuminance)
{
d->dynamicRangeCompressor->start(maxLuminance);
}
void KisSmallColorWidget::updateDynamicRange(int maxLuminance)
{
const qreal oldRange = d->currentRelativeDynamicRange;
const qreal newRange = qreal(maxLuminance) / 80.0;
if (qFuzzyCompare(oldRange, newRange)) return;
float r, g, b;
float denormHue = d->hue * 360.0;
float saturation = d->saturation;
float value = d->value;
HSVToRGB(denormHue, saturation, value, &r, &g, &b);
const qreal transformCoeff = oldRange / newRange;
r = qBound(0.0, r * transformCoeff, 1.0);
g = qBound(0.0, g * transformCoeff, 1.0);
b = qBound(0.0, b * transformCoeff, 1.0);
RGBToHSV(r, g, b, &denormHue, &saturation, &value);
d->currentRelativeDynamicRange = newRange;
slotUpdatePalettes();
setHSV(denormHue / 360.0, saturation, value, false);
d->hueWidget->setNormalizedPos(QPointF(denormHue / 360.0, 0));
d->valueWidget->setNormalizedPos(QPointF(saturation, 1.0 - value));
}
void KisSmallColorWidget::setDisplayColorConverter(KisDisplayColorConverter *converter)
{
d->colorConverterConnections.clear();
if (!converter) {
converter = KisDisplayColorConverter::dumbConverterInstance();
}
d->displayColorConverter = converter;
if (d->displayColorConverter) {
d->colorConverterConnections.addConnection(
d->displayColorConverter, SIGNAL(displayConfigurationChanged()),
this, SLOT(slotDisplayConfigurationChanged()));
}
slotDisplayConfigurationChanged();
}
void KisSmallColorWidget::slotDisplayConfigurationChanged()
{
d->hasHDR = false;
if (d->hasHardwareHDR) {
const KoColorSpace *cs = d->displayColorConverter->paintingColorSpace();
d->hasHDR = cs->colorModelId() == RGBAColorModelID &&
(cs->colorDepthId() == Float16BitsColorDepthID ||
cs->colorDepthId() == Float32BitsColorDepthID ||
cs->colorDepthId() == Float64BitsColorDepthID ||
cs->profile()->uniqueId() == KoColorSpaceRegistry::instance()->p2020PQProfile()->uniqueId());
}
if (d->dynamicRange) {
d->dynamicRange->setEnabled(d->hasHDR);
}
d->hueWidget->setUseHandleOpacity(!d->hasHDR);
d->valueWidget->setUseHandleOpacity(!d->hasHDR);
slotUpdatePalettes();
// TODO: also set the currently selected color again
}
void KisSmallColorWidget::slotTellColorChanged()
{
d->updateAllowed = false;
float r, g, b;
HSVToRGB(d->hue * 360.0, d->saturation, d->value, &r, &g, &b);
if (d->hasHDR) {
const float rangeCoeff = d->effectiveRelativeDynamicRange();
r *= rangeCoeff;
g *= rangeCoeff;
b *= rangeCoeff;
}
const KoColorSpace *cs = d->generationColorSpace();
KIS_SAFE_ASSERT_RECOVER_RETURN(cs);
QVector<float> values(4);
if (cs->colorDepthId() == Integer8BitsColorDepthID) {
values[0] = b;
values[1] = g;
values[2] = r;
values[3] = 1.0f;
} else {
values[0] = r;
values[1] = g;
values[2] = b;
values[3] = 1.0f;
}
KoColor c(cs);
cs->fromNormalisedChannelsValue(c.data(), values);
emit colorChanged(c);
d->updateAllowed = true;
}
void KisSmallColorWidget::resizeEvent(QResizeEvent * event)
{
QWidget::resizeEvent(event);
update();
d->resizeUpdateCompressor->start();
}
diff --git a/plugins/dockers/smallcolorselector/kis_small_color_widget.h b/plugins/dockers/smallcolorselector/kis_small_color_widget.h
index e771d053af..40af9a5624 100644
--- a/plugins/dockers/smallcolorselector/kis_small_color_widget.h
+++ b/plugins/dockers/smallcolorselector/kis_small_color_widget.h
@@ -1,76 +1,77 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_SMALL_COLOR_WIDGET_H_
#define _KIS_SMALL_COLOR_WIDGET_H_
#include <QWidget>
class KoColor;
class KisDisplayColorConverter;
class KisGLImageWidget;
class KisSmallColorWidget : public QWidget
{
Q_OBJECT
public:
KisSmallColorWidget(QWidget* parent);
~KisSmallColorWidget() override;
public:
void resizeEvent(QResizeEvent * event) override;
void setDisplayColorConverter(KisDisplayColorConverter *converter);
public:
public Q_SLOTS:
void setHue(qreal h);
void setHSV(qreal h, qreal s, qreal v, bool notifyChanged = true);
void setColor(const KoColor &color);
void slotUpdatePalettes();
void updateSVPalette();
Q_SIGNALS:
void colorChanged(const KoColor&);
void sigTellColorChangedInternal();
private Q_SLOTS:
void slotHueSliderChanged(const QPointF &pos);
void slotValueSliderChanged(const QPointF &pos);
void slotInitiateUpdateDynamicRange(int maxLuminance);
void slotDisplayConfigurationChanged();
void slotTellColorChanged();
private:
void updateDynamicRange(int maxLuminance);
private:
void updateHuePalette();
template<class FillPolicy>
void uploadPaletteData(KisGLImageWidget *widget, const QSize &size);
private:
struct Private;
Private* const d;
};
#endif
diff --git a/plugins/dockers/smallcolorselector/smallcolorselector.cc b/plugins/dockers/smallcolorselector/smallcolorselector.cc
index b90d5ac00a..308bf0ad8b 100644
--- a/plugins/dockers/smallcolorselector/smallcolorselector.cc
+++ b/plugins/dockers/smallcolorselector/smallcolorselector.cc
@@ -1,68 +1,69 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "smallcolorselector.h"
#include <kpluginfactory.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "smallcolorselector_dock.h"
K_PLUGIN_FACTORY_WITH_JSON(SmallColorSelectorPluginFactory, "krita_smallcolorselector.json", registerPlugin<SmallColorSelectorPlugin>();)
class SmallColorSelectorDockFactory : public KoDockFactoryBase
{
public:
SmallColorSelectorDockFactory() {
}
QString id() const override {
return QString("SmallColorSelector");
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const {
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override {
SmallColorSelectorDock * dockWidget = new SmallColorSelectorDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
SmallColorSelectorPlugin::SmallColorSelectorPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new SmallColorSelectorDockFactory());
}
SmallColorSelectorPlugin::~SmallColorSelectorPlugin()
{
}
#include "smallcolorselector.moc"
diff --git a/plugins/dockers/smallcolorselector/smallcolorselector.h b/plugins/dockers/smallcolorselector/smallcolorselector.h
index 8c78bfb170..fb8752b776 100644
--- a/plugins/dockers/smallcolorselector/smallcolorselector.h
+++ b/plugins/dockers/smallcolorselector/smallcolorselector.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _SMALLCOLORSELECTOR_H_
#define _SMALLCOLORSELECTOR_H_
#include <QObject>
#include <QVariant>
/**
* Template of view plugin
*/
class SmallColorSelectorPlugin : public QObject
{
Q_OBJECT
public:
SmallColorSelectorPlugin(QObject *parent, const QVariantList &);
~SmallColorSelectorPlugin() override;
};
#endif
diff --git a/plugins/dockers/smallcolorselector/smallcolorselector_dock.cc b/plugins/dockers/smallcolorselector/smallcolorselector_dock.cc
index 50fb2ca903..dc4279392c 100644
--- a/plugins/dockers/smallcolorselector/smallcolorselector_dock.cc
+++ b/plugins/dockers/smallcolorselector/smallcolorselector_dock.cc
@@ -1,84 +1,85 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "smallcolorselector_dock.h"
#include <klocalizedstring.h>
#include "kis_canvas2.h"
#include "kis_small_color_widget.h"
#include "kis_canvas_resource_provider.h"
#include <KoColorSpaceRegistry.h>
#include <QVBoxLayout>
SmallColorSelectorDock::SmallColorSelectorDock()
: QDockWidget()
, m_canvas(0)
{
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
m_smallColorWidget = new KisSmallColorWidget(this);
layout->addWidget(m_smallColorWidget, 1);
page->setLayout(layout);
setWidget(page);
m_smallColorWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
connect(m_smallColorWidget, SIGNAL(colorChanged(KoColor)),
this, SLOT(colorChangedProxy(KoColor)));
connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)),
m_smallColorWidget, SLOT(update()));
setWindowTitle(i18n("Small Color Selector"));
}
void SmallColorSelectorDock::setCanvas(KoCanvasBase * canvas)
{
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_smallColorWidget->setColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()));
m_smallColorWidget->setDisplayColorConverter(0);
}
m_canvas = canvas;
if (m_canvas && m_canvas->resourceManager()) {
connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(canvasResourceChanged(int,QVariant)));
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas);
m_smallColorWidget->setDisplayColorConverter(kisCanvas->displayColorConverter());
m_smallColorWidget->setColor(m_canvas->resourceManager()->foregroundColor());
}
}
void SmallColorSelectorDock::colorChangedProxy(const KoColor& c)
{
if (m_canvas)
m_canvas->resourceManager()->setForegroundColor(c);
}
void SmallColorSelectorDock::canvasResourceChanged(int key, const QVariant& v)
{
if (key == KoCanvasResourceProvider::ForegroundColor) {
m_smallColorWidget->setColor(v.value<KoColor>());
}
}
diff --git a/plugins/dockers/smallcolorselector/smallcolorselector_dock.h b/plugins/dockers/smallcolorselector/smallcolorselector_dock.h
index c3c36a747d..b25b0601b2 100644
--- a/plugins/dockers/smallcolorselector/smallcolorselector_dock.h
+++ b/plugins/dockers/smallcolorselector/smallcolorselector_dock.h
@@ -1,48 +1,49 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _SMALLCOLORSELECTOR_DOCK_H_
#define _SMALLCOLORSELECTOR_DOCK_H_
#include <QPointer>
#include <QDockWidget>
#include <KoCanvasBase.h>
#include <KoCanvasObserverBase.h>
class KoColor;
class KisSmallColorWidget;
class SmallColorSelectorDock : public QDockWidget, public KoCanvasObserverBase
{
Q_OBJECT
public:
SmallColorSelectorDock();
QString observerName() override { return "SmallColorSelectorDock"; }
/// reimplemented from KoCanvasObserverBase
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override { m_canvas = 0; setEnabled(false); }
public Q_SLOTS:
void colorChangedProxy(const KoColor &);
void canvasResourceChanged(int, const QVariant&);
private:
KisSmallColorWidget* m_smallColorWidget;
QPointer<KoCanvasBase> m_canvas;
};
#endif
diff --git a/plugins/dockers/snapshotdocker/CMakeLists.txt b/plugins/dockers/snapshotdocker/CMakeLists.txt
new file mode 100644
index 0000000000..0d3c5ba7fa
--- /dev/null
+++ b/plugins/dockers/snapshotdocker/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(kritasnapshotdocker_SOURCES
+ KisSnapshotModel.cpp
+ SnapshotDocker.cpp
+ SnapshotPlugin.cpp
+ KisSnapshotView.cpp
+ )
+
+add_library(kritasnapshotdocker MODULE ${kritasnapshotdocker_SOURCES})
+target_link_libraries(kritasnapshotdocker kritaimage kritaui)
+install(TARGETS kritasnapshotdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
\ No newline at end of file
diff --git a/plugins/dockers/snapshotdocker/KisSnapshotModel.cpp b/plugins/dockers/snapshotdocker/KisSnapshotModel.cpp
new file mode 100644
index 0000000000..7ff4c7a6f7
--- /dev/null
+++ b/plugins/dockers/snapshotdocker/KisSnapshotModel.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "KisSnapshotModel.h"
+
+#include <QMap>
+#include <QList>
+#include <QPointer>
+#include <QPair>
+#include <QString>
+
+#include <KisDocument.h>
+#include <KisView.h>
+#include <KisViewManager.h>
+#include <kis_node_manager.h>
+#include <kis_name_server.h>
+
+struct KisSnapshotModel::Private
+{
+ Private();
+ virtual ~Private();
+
+ QPointer<KisDocument> curDocument();
+ QScopedPointer<KisNameServer> curNameServer;
+ bool switchToDocument(QPointer<KisDocument> doc);
+
+ using DocPList = QList<QPair<QString, QPointer<KisDocument> > >;
+
+ DocPList curDocList;
+
+ QMap<KisDocument *, DocPList> documentGroups;
+ QMap<KisDocument *, KisNameServer *> nameServers;
+ QPointer<KisCanvas2> curCanvas;
+};
+
+KisSnapshotModel::Private::Private()
+{
+}
+
+KisSnapshotModel::Private::~Private()
+{
+}
+
+QPointer<KisDocument> KisSnapshotModel::Private::curDocument()
+{
+ if (curCanvas && curCanvas->imageView()) {
+ return curCanvas->imageView()->document();
+ }
+ return 0;
+}
+
+bool KisSnapshotModel::Private::switchToDocument(QPointer<KisDocument> doc)
+{
+ if (curCanvas && curCanvas->imageView()) {
+ KisView *view = curCanvas->imageView();
+ KisDocument *curDoc = curDocument();
+ if (curDoc && doc) {
+ curDoc->copyFromDocument(*doc);
+ view->viewManager()->nodeManager()->slotNonUiActivatedNode(curDoc->preActivatedNode());
+ }
+ // FIXME: more things need to be done
+ return true;
+ }
+ return false;
+}
+
+KisSnapshotModel::KisSnapshotModel()
+ : QAbstractListModel()
+ , m_d(new Private)
+{
+}
+
+KisSnapshotModel::~KisSnapshotModel()
+{
+}
+
+int KisSnapshotModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ return 0;
+ } else {
+ return m_d->curDocList.size();
+ }
+}
+
+QVariant KisSnapshotModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= rowCount(QModelIndex())) {
+ return QVariant();
+ }
+ int i = index.row();
+ switch (role) {
+ case Qt::DisplayRole:
+ case Qt::EditRole:
+ return m_d->curDocList[i].first;
+ break;
+ }
+ return QVariant();
+}
+
+bool KisSnapshotModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid() || index.row() >= rowCount(QModelIndex())) {
+ return false;
+ }
+ int i = index.row();
+ switch (role) {
+ case Qt::DisplayRole:
+ case Qt::EditRole:
+ m_d->curDocList[i].first = value.toString();
+ emit dataChanged(index, index);
+ return true;
+ break;
+ }
+ return false;
+}
+
+Qt::ItemFlags KisSnapshotModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid()) {
+ return Qt::ItemIsEnabled;
+ }
+
+ return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
+}
+
+void KisSnapshotModel::setCanvas(QPointer<KisCanvas2> canvas)
+{
+ if (m_d->curCanvas == canvas) {
+ return;
+ }
+
+ if (m_d->curDocument()) {
+ m_d->documentGroups.insert(m_d->curDocument(), m_d->curDocList);
+ m_d->nameServers.insert(m_d->curDocument(), m_d->curNameServer.take());
+ } else {
+ m_d->curNameServer.reset(0);
+ Q_FOREACH (auto const &i, m_d->curDocList) {
+ delete i.second.data();
+ }
+ }
+
+ if (!m_d->curDocList.isEmpty()) {
+ beginRemoveRows(QModelIndex(), 0, m_d->curDocList.size() - 1);
+ m_d->curDocList.clear();
+ endRemoveRows();
+ }
+ m_d->curCanvas = canvas;
+
+ QPointer<KisDocument> curDoc = m_d->curDocument();
+ if (curDoc) {
+ Private::DocPList docList = m_d->documentGroups.take(curDoc);
+ beginInsertRows(QModelIndex(), docList.size(), docList.size());
+ m_d->curDocList = docList;
+ endInsertRows();
+
+ KisNameServer *nameServer = m_d->nameServers.take(curDoc);
+ if (!nameServer) {
+ nameServer = new KisNameServer;
+ }
+ m_d->curNameServer.reset(nameServer);
+ }
+
+}
+
+bool KisSnapshotModel::slotCreateSnapshot()
+{
+ if (!m_d->curDocument()) {
+ return false;
+ }
+ QPointer<KisDocument> clonedDoc(m_d->curDocument()->lockAndCreateSnapshot());
+ if (clonedDoc) {
+ beginInsertRows(QModelIndex(), m_d->curDocList.size(), m_d->curDocList.size());
+ m_d->curDocList << qMakePair(i18nc("snapshot names, e.g. \"Snapshot 1\"", "Snapshot %1", m_d->curNameServer->number()), clonedDoc);
+ endInsertRows();
+ return true;
+ }
+ return false;
+}
+
+bool KisSnapshotModel::slotRemoveSnapshot(const QModelIndex &index)
+{
+ if (!index.isValid() || index.row() >= m_d->curDocList.size()) {
+ return false;
+ }
+ int i = index.row();
+ beginRemoveRows(QModelIndex(), i, i);
+ QPair<QString, QPointer<KisDocument> > pair = m_d->curDocList.takeAt(i);
+ endRemoveRows();
+ delete pair.second.data();
+ return true;
+}
+
+bool KisSnapshotModel::slotSwitchToSnapshot(const QModelIndex &index)
+{
+ if (!index.isValid() || index.row() >= m_d->curDocList.size()) {
+ return false;
+ }
+
+ return m_d->switchToDocument(m_d->curDocList[index.row()].second);
+}
diff --git a/plugins/dockers/snapshotdocker/KisSnapshotModel.h b/plugins/dockers/snapshotdocker/KisSnapshotModel.h
new file mode 100644
index 0000000000..92addb1671
--- /dev/null
+++ b/plugins/dockers/snapshotdocker/KisSnapshotModel.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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_SNAPSHOT_MODEL_H_
+#define KIS_SNAPSHOT_MODEL_H_
+
+#include <QAbstractListModel>
+#include <QScopedPointer>
+#include <QPointer>
+
+#include <kis_canvas2.h>
+
+class KisSnapshotModel : public QAbstractListModel
+{
+public:
+ KisSnapshotModel();
+ ~KisSnapshotModel() override;
+
+ int rowCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+ // this function is re-implemented to make the items editable
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ void setCanvas(QPointer<KisCanvas2> canvas);
+
+public Q_SLOTS:
+ bool slotCreateSnapshot();
+ bool slotRemoveSnapshot(const QModelIndex &index);
+ bool slotSwitchToSnapshot(const QModelIndex &index);
+
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
+};
+
+#endif // KIS_SNAPSHOT_MODEL_H_
diff --git a/plugins/dockers/snapshotdocker/KisSnapshotView.cpp b/plugins/dockers/snapshotdocker/KisSnapshotView.cpp
new file mode 100644
index 0000000000..657d2ca7c0
--- /dev/null
+++ b/plugins/dockers/snapshotdocker/KisSnapshotView.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "KisSnapshotView.h"
+#include "KisSnapshotModel.h"
+
+#include <kis_assert.h>
+
+struct KisSnapshotView::Private
+{
+ KisSnapshotModel *model;
+};
+
+KisSnapshotView::KisSnapshotView()
+ : QListView()
+ , m_d(new Private)
+{
+ setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked);
+}
+
+KisSnapshotView::~KisSnapshotView()
+{
+}
+
+void KisSnapshotView::setModel(QAbstractItemModel *model)
+{
+ KisSnapshotModel *snapshotModel = dynamic_cast<KisSnapshotModel *>(model);
+ if (snapshotModel) {
+ QListView::setModel(model);
+ m_d->model = snapshotModel;
+ }
+}
+
+void KisSnapshotView::slotSwitchToSelectedSnapshot()
+{
+ KIS_ASSERT_RECOVER_RETURN(m_d->model);
+ QModelIndexList indexes = selectedIndexes();
+ if (indexes.size() != 1) {
+ return;
+ }
+ m_d->model->slotSwitchToSnapshot(indexes[0]);
+}
+
+void KisSnapshotView::slotRemoveSelectedSnapshot()
+{
+ KIS_ASSERT_RECOVER_RETURN(m_d->model);
+ QModelIndexList indexes = selectedIndexes();
+ Q_FOREACH (QModelIndex index, indexes) {
+ m_d->model->slotRemoveSnapshot(index);
+ }
+}
+
diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.h b/plugins/dockers/snapshotdocker/KisSnapshotView.h
similarity index 62%
copy from plugins/impex/libkra/tests/kis_kra_loader_test.h
copy to plugins/dockers/snapshotdocker/KisSnapshotView.h
index 88da2d5448..3539329acb 100644
--- a/plugins/impex/libkra/tests/kis_kra_loader_test.h
+++ b/plugins/dockers/snapshotdocker/KisSnapshotView.h
@@ -1,37 +1,40 @@
/*
- * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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_KRA_LOADER_TEST_H
-#define KIS_KRA_LOADER_TEST_H
+#ifndef KIS_SNAPSHOT_VIEW_H_
+#define KIS_SNAPSHOT_VIEW_H_
-#include <QtTest>
+#include <QListView>
-class KisKraLoaderTest : public QObject
+class KisSnapshotView : public QListView
{
- Q_OBJECT
-private Q_SLOTS:
- void initTestCase();
+public:
+ KisSnapshotView();
+ ~KisSnapshotView() override;
- void testLoading();
- void testObligeSingleChild();
- void testObligeSingleChildNonTranspPixel();
+ void setModel(QAbstractItemModel *model) override;
- void testLoadAnimated();
+public Q_SLOTS:
+ void slotSwitchToSelectedSnapshot();
+ void slotRemoveSelectedSnapshot();
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
};
#endif
diff --git a/plugins/dockers/snapshotdocker/SnapshotDocker.cpp b/plugins/dockers/snapshotdocker/SnapshotDocker.cpp
new file mode 100644
index 0000000000..8b9218d826
--- /dev/null
+++ b/plugins/dockers/snapshotdocker/SnapshotDocker.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * 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 "SnapshotDocker.h"
+
+#include <QWidget>
+#include <QListView>
+#include <QToolButton>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+
+#include "KisSnapshotModel.h"
+#include "KisSnapshotView.h"
+
+#include <kis_canvas2.h>
+#include <kis_icon_utils.h>
+
+struct SnapshotDocker::Private
+{
+ Private();
+ ~Private();
+
+ QScopedPointer<KisSnapshotModel> model;
+ QPointer<KisSnapshotView> view;
+ QPointer<KisCanvas2> canvas;
+ QPointer<QToolButton> bnAdd;
+ QPointer<QToolButton> bnSwitchTo;
+ QPointer<QToolButton> bnRemove;
+};
+
+SnapshotDocker::Private::Private()
+ : model(new KisSnapshotModel)
+ , view(new KisSnapshotView)
+ , canvas(0)
+ , bnAdd(new QToolButton)
+ , bnSwitchTo(new QToolButton)
+ , bnRemove(new QToolButton)
+{
+}
+
+SnapshotDocker::Private::~Private()
+{
+}
+
+SnapshotDocker::SnapshotDocker()
+ : QDockWidget()
+ , m_d(new Private)
+{
+ QWidget *widget = new QWidget(this);
+ QVBoxLayout *mainLayout = new QVBoxLayout(widget);
+ m_d->view->setModel(m_d->model.data());
+ mainLayout->addWidget(m_d->view);
+
+ QHBoxLayout *buttonsLayout = new QHBoxLayout(widget);
+ m_d->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer"));
+ connect(m_d->bnAdd, &QToolButton::clicked, m_d->model.data(), &KisSnapshotModel::slotCreateSnapshot);
+ buttonsLayout->addWidget(m_d->bnAdd);
+ m_d->bnSwitchTo->setIcon(KisIconUtils::loadIcon("draw-freehand")); /// XXX: which icon to use?
+ connect(m_d->bnSwitchTo, &QToolButton::clicked, m_d->view, &KisSnapshotView::slotSwitchToSelectedSnapshot);
+ buttonsLayout->addWidget(m_d->bnSwitchTo);
+ m_d->bnRemove->setIcon(KisIconUtils::loadIcon("deletelayer"));
+ connect(m_d->bnRemove, &QToolButton::clicked, m_d->view, &KisSnapshotView::slotRemoveSelectedSnapshot);
+ buttonsLayout->addWidget(m_d->bnRemove);
+ mainLayout->addLayout(buttonsLayout);
+
+ setWidget(widget);
+ setWindowTitle(i18n("Snapshot Docker"));
+}
+
+SnapshotDocker::~SnapshotDocker()
+{
+}
+
+void SnapshotDocker::setCanvas(KoCanvasBase *canvas)
+{
+ KisCanvas2 *c = dynamic_cast<KisCanvas2 *>(canvas);
+ if (c) {
+ if (m_d->canvas == c) {
+ return;
+ }
+ }
+ m_d->canvas = c;
+ m_d->model->setCanvas(c);
+}
+
+void SnapshotDocker::unsetCanvas()
+{
+ setCanvas(0);
+}
+
+#include "SnapshotDocker.moc"
diff --git a/libs/flake/tools/KoZoomToolWidget.h b/plugins/dockers/snapshotdocker/SnapshotDocker.h
similarity index 58%
copy from libs/flake/tools/KoZoomToolWidget.h
copy to plugins/dockers/snapshotdocker/SnapshotDocker.h
index aff3e89019..b3ccfe8b31 100644
--- a/libs/flake/tools/KoZoomToolWidget.h
+++ b/plugins/dockers/snapshotdocker/SnapshotDocker.h
@@ -1,50 +1,47 @@
/* This file is part of the KDE project
- * Copyright (C) 2008 Martin Pfeiffer <hubipete@gmx.net>
+ * Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
*
* 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 KOZOOMTOOLWIDGET_H
-#define KOZOOMTOOLWIDGET_H
+#ifndef SNAPSHOT_DOCKER_H_
+#define SNAPSHOT_DOCKER_H_
-#include <QWidget>
-#include <QPixmap>
-#include "ui_KoZoomToolWidget.h"
+#include <QDockWidget>
+#include <QScopedPointer>
-class KoZoomTool;
+#include <KoCanvasObserverBase.h>
+#include <klocalizedstring.h>
-class KoZoomToolWidget : public QWidget, Ui::ZoomToolWidget
+#include <KoShapeController.h>
+#include <KoCanvasBase.h>
+
+class SnapshotDocker : public QDockWidget, public KoCanvasObserverBase
{
Q_OBJECT
public:
- explicit KoZoomToolWidget(KoZoomTool* tool, QWidget *parent = 0);
- ~KoZoomToolWidget() override;
-
-protected:
- bool eventFilter(QObject *object, QEvent *event) override;
+ SnapshotDocker();
+ ~SnapshotDocker() override;
-private Q_SLOTS:
- void changeZoomMode();
+ QString observerName() override { return "SnapshotDocker"; }
+ void setCanvas(KoCanvasBase *canvas) override;
+ void unsetCanvas() override;
private:
- void paintBirdEye();
-
- bool m_dirtyThumbnail;
- QRect m_birdEyeRect;
- QPixmap m_thumbnail;
- KoZoomTool *m_tool;
+ struct Private;
+ QScopedPointer<Private> m_d;
};
#endif
diff --git a/plugins/dockers/snapshotdocker/SnapshotPlugin.cpp b/plugins/dockers/snapshotdocker/SnapshotPlugin.cpp
new file mode 100644
index 0000000000..a7eae99da2
--- /dev/null
+++ b/plugins/dockers/snapshotdocker/SnapshotPlugin.cpp
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
+ *
+ * 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 "SnapshotPlugin.h"
+
+
+#include <kpluginfactory.h>
+#include <klocalizedstring.h>
+
+#include <KoDockFactoryBase.h>
+#include <KoDockRegistry.h>
+
+#include "SnapshotDocker.h"
+
+K_PLUGIN_FACTORY_WITH_JSON(SnapshotPluginFactory, "kritasnapshotdocker.json", registerPlugin<SnapshotPlugin>();)
+
+class SnapshotDockFactory : public KoDockFactoryBase
+{
+public:
+ SnapshotDockFactory() {
+ }
+
+ QString id() const override {
+ return QString("Snapshot");
+ }
+
+ virtual Qt::DockWidgetArea defaultDockWidgetArea() const {
+ return Qt::RightDockWidgetArea;
+ }
+
+ QDockWidget *createDockWidget() override {
+ SnapshotDocker *dockWidget = new SnapshotDocker();
+ dockWidget->setObjectName(id());
+
+ return dockWidget;
+ }
+
+ DockPosition defaultDockPosition() const override {
+ return DockRight;
+ }
+};
+
+
+SnapshotPlugin::SnapshotPlugin(QObject *parent, const QVariantList &)
+ : QObject(parent)
+{
+
+ KoDockRegistry::instance()->add(new SnapshotDockFactory());
+}
+
+SnapshotPlugin::~SnapshotPlugin()
+{
+}
+
+#include "SnapshotPlugin.moc"
diff --git a/plugins/tools/svgtexttool/SvgRichTextCtrl.h b/plugins/dockers/snapshotdocker/SnapshotPlugin.h
similarity index 71%
copy from plugins/tools/svgtexttool/SvgRichTextCtrl.h
copy to plugins/dockers/snapshotdocker/SnapshotPlugin.h
index 9409120d9b..bd3d32c463 100644
--- a/plugins/tools/svgtexttool/SvgRichTextCtrl.h
+++ b/plugins/dockers/snapshotdocker/SnapshotPlugin.h
@@ -1,36 +1,34 @@
/* This file is part of the KDE project
- *
- * Copyright 2018 Mehmet Salih Çalışkan <msalihcaliskan@gmail.com>
+ * Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
*
* 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 SNAPSHOT_PLUGIN_H_
+#define SNAPSHOT_PLUGIN_H_
-#ifndef SVGRICHTEXTCTRL_H
-#define SVGRICHTEXTCTRL_H
-
-#include <QTextEdit>
+#include <QObject>
+#include <QVariant>
-class SvgRichTextCtrl : public QTextEdit
+class SnapshotPlugin : public QObject
{
+ Q_OBJECT
public:
- SvgRichTextCtrl(QWidget* parent = nullptr);
-
-protected:
- void insertFromMimeData(const QMimeData* source) override;
+ SnapshotPlugin(QObject *parent, const QVariantList &);
+ ~SnapshotPlugin() override;
};
-#endif // SVGRICHTEXTCTRL_H
+#endif
diff --git a/plugins/dockers/snapshotdocker/kritasnapshotdocker.json b/plugins/dockers/snapshotdocker/kritasnapshotdocker.json
new file mode 100644
index 0000000000..c81539b4a0
--- /dev/null
+++ b/plugins/dockers/snapshotdocker/kritasnapshotdocker.json
@@ -0,0 +1,9 @@
+{
+ "Id": "Krita Snapshot Docker plugin",
+ "Type": "Service",
+ "X-KDE-Library": "kritasnapshotdocker",
+ "X-KDE-ServiceTypes": [
+ "Krita/Dock"
+ ],
+ "X-Krita-Version": "28"
+}
diff --git a/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.cc b/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.cc
index 4aacf59369..94f5cab633 100644
--- a/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.cc
+++ b/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.cc
@@ -1,269 +1,270 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2015 Moritz Molch <kde@moritzmolch.de>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_specific_color_selector_widget.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QCheckBox>
#include <QSpacerItem>
#include <klocalizedstring.h>
#include <kconfiggroup.h>
#include <kconfig.h>
#include <ksharedconfig.h>
#include <KoChannelInfo.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <kis_color_input.h>
#include <KoColorProfile.h>
#include <kis_debug.h>
#include <kis_color_space_selector.h>
#include <kis_signal_compressor.h>
#include <kis_display_color_converter.h>
#include <kis_popup_button.h>
#include <kis_icon_utils.h>
#include "ui_wdgSpecificColorSelectorWidget.h"
KisSpecificColorSelectorWidget::KisSpecificColorSelectorWidget(QWidget* parent)
: QWidget(parent)
, m_colorSpace(0)
, m_spacer(0)
, m_updateCompressor(new KisSignalCompressor(10, KisSignalCompressor::POSTPONE, this))
, m_customColorSpaceSelected(false)
, m_displayConverter(0)
{
m_ui = new Ui_wdgSpecificColorSelectorWidget();
m_ui->setupUi(this);
m_updateAllowed = true;
connect(m_updateCompressor, SIGNAL(timeout()), SLOT(updateTimeout()));
m_colorspaceSelector = new KisColorSpaceSelector(this);
connect(m_colorspaceSelector, SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(setCustomColorSpace(const KoColorSpace*)));
m_ui->colorspacePopupButton->setPopupWidget(m_colorspaceSelector);
connect(m_ui->chkUsePercentage, SIGNAL(toggled(bool)), this, SLOT(onChkUsePercentageChanged(bool)));
KConfigGroup cfg = KSharedConfig::openConfig()->group(QString());
m_ui->chkUsePercentage->setChecked(cfg.readEntry("SpecificColorSelector/UsePercentage", false));
m_ui->chkUsePercentage->setIcon(KisIconUtils::loadIcon("ratio"));
m_colorspaceSelector->showColorBrowserButton(false);
m_spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
m_ui->slidersLayout->addItem(m_spacer);
}
KisSpecificColorSelectorWidget::~KisSpecificColorSelectorWidget()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group(QString());
cfg.writeEntry("SpecificColorSelector/UsePercentage", m_ui->chkUsePercentage->isChecked());
}
bool KisSpecificColorSelectorWidget::customColorSpaceUsed()
{
return m_customColorSpaceSelected;
}
void KisSpecificColorSelectorWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (m_colorSpace) {
QString elidedColorspaceName = m_ui->colorspacePopupButton->fontMetrics().elidedText(
m_colorSpace->name(), Qt::ElideRight,
m_ui->colorspacePopupButton->width()
);
m_ui->colorspacePopupButton->setText(elidedColorspaceName);
}
}
void KisSpecificColorSelectorWidget::setDisplayConverter(KisDisplayColorConverter *displayConverter)
{
const bool needsForceUpdate = m_displayConverter != displayConverter;
m_displayConverter = displayConverter;
if (m_displayConverter) {
m_converterConnection.clear();
m_converterConnection.addConnection(m_displayConverter, SIGNAL(displayConfigurationChanged()), this, SLOT(rereadCurrentColorSpace()), Qt::UniqueConnection);
}
rereadCurrentColorSpace(needsForceUpdate);
}
void KisSpecificColorSelectorWidget::rereadCurrentColorSpace(bool force)
{
if (m_displayConverter && !m_customColorSpaceSelected) {
m_colorSpace = m_displayConverter->paintingColorSpace();
}
setColorSpace(m_colorSpace, force);
setColor(m_color);
}
void KisSpecificColorSelectorWidget::setColorSpace(const KoColorSpace* cs, bool force)
{
Q_ASSERT(cs);
dbgPlugins << cs->id() << " " << cs->profile()->name();
if (*m_colorSpace == *cs && !force) {
Q_FOREACH (KisColorInput* input, m_inputs) {
input->update();
}
return;
}
if (cs->colorDepthId() == Integer8BitsColorDepthID || cs->colorDepthId() == Integer16BitsColorDepthID) {
m_ui->chkUsePercentage->setVisible(true);
} else {
m_ui->chkUsePercentage->setVisible(false);
}
m_colorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile());
Q_ASSERT(m_colorSpace);
Q_ASSERT(*m_colorSpace == *cs);
QString elidedColorspaceName = m_ui->colorspacePopupButton->fontMetrics().elidedText(
m_colorSpace->name(), Qt::ElideRight,
m_ui->colorspacePopupButton->width()
);
m_ui->colorspacePopupButton->setText(elidedColorspaceName);
m_color = KoColor(m_color, m_colorSpace);
Q_FOREACH (KisColorInput* input, m_inputs) {
delete input;
}
m_inputs.clear();
m_ui->slidersLayout->removeItem(m_spacer);
QList<KoChannelInfo *> channels = KoChannelInfo::displayOrderSorted(m_colorSpace->channels());
KoColorDisplayRendererInterface *displayRenderer =
m_displayConverter ?
m_displayConverter->displayRendererInterface() :
KisDisplayColorConverter::dumbConverterInstance()->displayRendererInterface();
Q_FOREACH (KoChannelInfo* channel, channels) {
if (channel->channelType() == KoChannelInfo::COLOR) {
KisColorInput* input = 0;
switch (channel->channelValueType()) {
case KoChannelInfo::UINT8:
case KoChannelInfo::UINT16:
case KoChannelInfo::UINT32: {
input = new KisIntegerColorInput(this, channel, &m_color, displayRenderer, m_ui->chkUsePercentage->isChecked());
}
break;
case KoChannelInfo::FLOAT16:
case KoChannelInfo::FLOAT32: {
input = new KisFloatColorInput(this, channel, &m_color, displayRenderer);
}
break;
default:
Q_ASSERT(false);
input = 0;
}
if (input) {
connect(input, SIGNAL(updated()), this, SLOT(update()));
connect(this, SIGNAL(updated()), input, SLOT(update()));
m_inputs.append(input);
m_ui->slidersLayout->addWidget(input);
}
}
}
QList<QLabel*> labels;
int labelWidth = 0;
Q_FOREACH (KisColorInput* input, m_inputs) {
Q_FOREACH (QLabel* label, input->findChildren<QLabel*>()) {
labels.append(label);
labelWidth = qMax(labelWidth, label->sizeHint().width());
}
}
Q_FOREACH (QLabel *label, labels) {
label->setMinimumWidth(labelWidth);
}
bool allChannels8Bit = true;
Q_FOREACH (KoChannelInfo* channel, channels) {
if (channel->channelType() == KoChannelInfo::COLOR && channel->channelValueType() != KoChannelInfo::UINT8) {
allChannels8Bit = false;
}
}
if (allChannels8Bit) {
KisColorInput* input = new KisHexColorInput(this, &m_color, displayRenderer);
m_inputs.append(input);
m_ui->slidersLayout->addWidget(input);
connect(input, SIGNAL(updated()), this, SLOT(update()));
connect(this, SIGNAL(updated()), input, SLOT(update()));
}
m_ui->slidersLayout->addItem(m_spacer);
m_colorspaceSelector->blockSignals(true);
m_colorspaceSelector->setCurrentColorSpace(cs);
m_colorspaceSelector->blockSignals(false);
m_updateAllowed = false;
emit(updated());
m_updateAllowed = true;
}
void KisSpecificColorSelectorWidget::update()
{
if (m_updateAllowed) {
m_updateCompressor->start();
}
}
void KisSpecificColorSelectorWidget::setColor(const KoColor& c)
{
m_updateAllowed = false;
m_color.fromKoColor(c);
emit(updated());
m_updateAllowed = true;
}
void KisSpecificColorSelectorWidget::updateTimeout()
{
emit(colorChanged(m_color));
}
void KisSpecificColorSelectorWidget::setCustomColorSpace(const KoColorSpace *colorSpace)
{
m_customColorSpaceSelected = true;
setColorSpace(colorSpace);
setColor(m_color);
}
void KisSpecificColorSelectorWidget::onChkUsePercentageChanged(bool isChecked)
{
for (auto input: m_inputs) {
input->setPercentageWise(isChecked);
}
emit(updated());
}
diff --git a/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.h b/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.h
index 2b2b3c9340..272a3c54c1 100644
--- a/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.h
+++ b/plugins/dockers/specificcolorselector/kis_specific_color_selector_widget.h
@@ -1,80 +1,81 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_SPECIFIC_COLOR_SELECTOR_WIDGET_H_
#define _KIS_SPECIFIC_COLOR_SELECTOR_WIDGET_H_
#include <QWidget>
#include <KoColor.h>
#include "kis_signal_auto_connection.h"
#include "ui_wdgSpecificColorSelectorWidget.h"
class KoColorSpace;
class QVBoxLayout;
class KisColorInput;
class KisColorSpaceSelector;
class QCheckBox;
class KisSignalCompressor;
class QSpacerItem;
class KisDisplayColorConverter;
class KisPopupButton;
class KisSpecificColorSelectorWidget : public QWidget
{
Q_OBJECT
public:
KisSpecificColorSelectorWidget(QWidget* parent);
~KisSpecificColorSelectorWidget() override;
bool customColorSpaceUsed();
protected:
void resizeEvent(QResizeEvent* event) override;
public Q_SLOTS:
void setDisplayConverter(KisDisplayColorConverter *colorConverter);
void setColorSpace(const KoColorSpace *cs, bool force = false);
void setColor(const KoColor&);
private Q_SLOTS:
void update();
void updateTimeout();
void setCustomColorSpace(const KoColorSpace *);
void rereadCurrentColorSpace(bool force = false);
void onChkUsePercentageChanged(bool);
Q_SIGNALS:
void colorChanged(const KoColor&);
void updated();
private:
QList<KisColorInput*> m_inputs;
const KoColorSpace* m_colorSpace;
QSpacerItem *m_spacer;
KoColor m_color;
bool m_updateAllowed;
KisSignalCompressor *m_updateCompressor;
KisColorSpaceSelector *m_colorspaceSelector;
bool m_customColorSpaceSelected;
Ui_wdgSpecificColorSelectorWidget* m_ui;
KisDisplayColorConverter *m_displayConverter;
KisSignalAutoConnectionsStore m_converterConnection;
};
#endif
diff --git a/plugins/dockers/specificcolorselector/specificcolorselector.cc b/plugins/dockers/specificcolorselector/specificcolorselector.cc
index 4a70c35028..2b163da342 100644
--- a/plugins/dockers/specificcolorselector/specificcolorselector.cc
+++ b/plugins/dockers/specificcolorselector/specificcolorselector.cc
@@ -1,81 +1,82 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "specificcolorselector.h"
#include <stdlib.h>
#include <QTimer>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "kis_config.h"
#include "kis_cursor.h"
#include "kis_global.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "specificcolorselector_dock.h"
K_PLUGIN_FACTORY_WITH_JSON(SpecificColorSelectorPluginFactory, "krita_specificcolorselector.json", registerPlugin<SpecificColorSelectorPlugin>();)
class SpecificColorSelectorDockFactory : public KoDockFactoryBase
{
public:
SpecificColorSelectorDockFactory() {
}
QString id() const override {
return QString("SpecificColorSelector");
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const {
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override {
SpecificColorSelectorDock * dockWidget = new SpecificColorSelectorDock();
dockWidget->setObjectName(id());
return dockWidget;
}
KoDockFactoryBase::DockPosition defaultDockPosition() const override {
return DockMinimized;
}
};
SpecificColorSelectorPlugin::SpecificColorSelectorPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
dbgPlugins << "SpecificColorSelectorPlugin";
KoDockRegistry::instance()->add(new SpecificColorSelectorDockFactory());
}
SpecificColorSelectorPlugin::~SpecificColorSelectorPlugin()
{
}
#include "specificcolorselector.moc"
diff --git a/plugins/dockers/specificcolorselector/specificcolorselector.h b/plugins/dockers/specificcolorselector/specificcolorselector.h
index 52bec70c0b..42b3671483 100644
--- a/plugins/dockers/specificcolorselector/specificcolorselector.h
+++ b/plugins/dockers/specificcolorselector/specificcolorselector.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _SPECIFICCOLORSELECTOR_H_
#define _SPECIFICCOLORSELECTOR_H_
#include <QObject>
#include <QVariant>
/**
* Template of view plugin
*/
class SpecificColorSelectorPlugin : public QObject
{
Q_OBJECT
public:
SpecificColorSelectorPlugin(QObject *parent, const QVariantList &);
~SpecificColorSelectorPlugin() override;
};
#endif
diff --git a/plugins/dockers/specificcolorselector/specificcolorselector_dock.cc b/plugins/dockers/specificcolorselector/specificcolorselector_dock.cc
index cc51c97ab8..04d9dd6939 100644
--- a/plugins/dockers/specificcolorselector/specificcolorselector_dock.cc
+++ b/plugins/dockers/specificcolorselector/specificcolorselector_dock.cc
@@ -1,74 +1,75 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "specificcolorselector_dock.h"
#include <klocalizedstring.h>
#include <QLayout>
#include <kis_layer.h>
#include <KisViewManager.h>
#include <canvas/kis_canvas2.h>
#include <kis_canvas_resource_provider.h>
#include <kis_image.h>
#include <kis_display_color_converter.h>
#include "kis_specific_color_selector_widget.h"
SpecificColorSelectorDock::SpecificColorSelectorDock()
: QDockWidget(i18n("Specific Color Selector"))
, m_canvas(0)
, m_view(0)
, m_colorSelector(new KisSpecificColorSelectorWidget(this))
{
setWidget(m_colorSelector);
widget()->setContentsMargins(4,4,4,0);
}
void SpecificColorSelectorDock::setCanvas(KoCanvasBase * canvas)
{
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
}
KisCanvas2* kisCanvas = dynamic_cast<KisCanvas2*>(canvas);
m_canvas = kisCanvas;
if (!kisCanvas) {
return;
}
m_colorSelector->setDisplayConverter(kisCanvas->displayColorConverter());
}
void SpecificColorSelectorDock::unsetCanvas()
{
setEnabled(false);
m_canvas = 0;
m_colorSelector->setDisplayConverter(0);
}
void SpecificColorSelectorDock::setViewManager(KisViewManager* kisview)
{
m_view = kisview;
connect(m_view->canvasResourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), m_colorSelector, SLOT(setColor(KoColor)));
connect(m_colorSelector, SIGNAL(colorChanged(KoColor)), m_view->canvasResourceProvider(), SLOT(slotSetFGColor(KoColor)));
}
#include "moc_specificcolorselector_dock.cpp"
diff --git a/plugins/dockers/specificcolorselector/specificcolorselector_dock.h b/plugins/dockers/specificcolorselector/specificcolorselector_dock.h
index 619b3a3981..8ab881fe45 100644
--- a/plugins/dockers/specificcolorselector/specificcolorselector_dock.h
+++ b/plugins/dockers/specificcolorselector/specificcolorselector_dock.h
@@ -1,50 +1,51 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _SPECIFICCOLORSELECTOR_DOCK_H_
#define _SPECIFICCOLORSELECTOR_DOCK_H_
#include <QDockWidget>
#include <QPointer>
#include <kis_types.h>
#include <kis_mainwindow_observer.h>
#include <kis_canvas2.h>
class KisViewManager;
class KisSpecificColorSelectorWidget;
class SpecificColorSelectorDock : public QDockWidget, public KisMainwindowObserver
{
Q_OBJECT
public:
SpecificColorSelectorDock();
QString observerName() override { return "SpecificColorSelectorDock"; }
/// reimplemented from KoCanvasObserverBase/KisMainwindowObserver
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
void setViewManager(KisViewManager* kisview) override;
private:
QPointer<KisCanvas2> m_canvas;
KisViewManager *m_view;
KisSpecificColorSelectorWidget* m_colorSelector;
};
#endif
diff --git a/plugins/dockers/tasksetdocker/taskset_resource.cpp b/plugins/dockers/tasksetdocker/taskset_resource.cpp
index f61a2835f3..ca6a81ede1 100644
--- a/plugins/dockers/tasksetdocker/taskset_resource.cpp
+++ b/plugins/dockers/tasksetdocker/taskset_resource.cpp
@@ -1,128 +1,129 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "taskset_resource.h"
#include <QFile>
#include <QDomDocument>
#include <QTextStream>
#include <QBuffer>
#include <QByteArray>
#include <kis_debug.h>
#define TASKSET_VERSION 1
TasksetResource::TasksetResource(const QString& f)
: KoResource(f)
{
}
TasksetResource::~TasksetResource()
{
}
bool TasksetResource::save()
{
if (filename().isEmpty())
return false;
QFile file(filename());
file.open(QIODevice::WriteOnly);
bool res = saveToDevice(&file);
file.close();
return res;
}
bool TasksetResource::load()
{
QString fn = filename();
if (fn.isEmpty()) return false;
QFile file(fn);
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnKrita << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool TasksetResource::loadFromDevice(QIODevice *dev)
{
QDomDocument doc;
if (!doc.setContent(dev)) {
return false;
}
QDomElement element = doc.documentElement();
setName(element.attribute("name"));
QDomNode node = element.firstChild();
while (!node.isNull()) {
QDomElement child = node.toElement();
if (!child.isNull() && child.tagName() == "action") {
m_actions.append(child.text());
}
node = node.nextSibling();
}
setValid(true);
return true;
}
QString TasksetResource::defaultFileExtension() const
{
return QString(".kts");
}
void TasksetResource::setActionList(const QStringList actions)
{
m_actions = actions;
}
QStringList TasksetResource::actionList()
{
return m_actions;
}
bool TasksetResource::saveToDevice(QIODevice *io) const
{
QDomDocument doc;
QDomElement root = doc.createElement("Taskset");
root.setAttribute("name", name() );
root.setAttribute("version", TASKSET_VERSION);
Q_FOREACH (const QString& action, m_actions) {
QDomElement element = doc.createElement("action");
element.appendChild(doc.createTextNode(action));
root.appendChild(element);
}
doc.appendChild(root);
QTextStream textStream(io);
textStream.setCodec("UTF-8");
doc.save(textStream, 4);
KoResource::saveToDevice(io);
return true;
}
diff --git a/plugins/dockers/tasksetdocker/taskset_resource.h b/plugins/dockers/tasksetdocker/taskset_resource.h
index 39ee14b2e4..5128ccb561 100644
--- a/plugins/dockers/tasksetdocker/taskset_resource.h
+++ b/plugins/dockers/tasksetdocker/taskset_resource.h
@@ -1,48 +1,49 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 TASKSET_RESOURCE_H
#define TASKSET_RESOURCE_H
#include <resources/KoResource.h>
#include <QStringList>
class TasksetResource : public KoResource
{
public:
TasksetResource(const QString& filename);
~TasksetResource() override;
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
QString defaultFileExtension() const override;
void setActionList(const QStringList actions);
QStringList actionList();
private:
QStringList m_actions;
};
#endif // TASKSET_RESOURCE_H
diff --git a/plugins/dockers/tasksetdocker/tasksetdocker.cpp b/plugins/dockers/tasksetdocker/tasksetdocker.cpp
index 667f8caf49..7d36f07813 100644
--- a/plugins/dockers/tasksetdocker/tasksetdocker.cpp
+++ b/plugins/dockers/tasksetdocker/tasksetdocker.cpp
@@ -1,77 +1,78 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "tasksetdocker.h"
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include "tasksetdocker_dock.h"
K_PLUGIN_FACTORY_WITH_JSON(TasksetDockerPluginFactory,
"krita_tasksetdocker.json",
registerPlugin<TasksetDockerPlugin>();)
class TasksetDockerDockFactory : public KoDockFactoryBase {
public:
TasksetDockerDockFactory()
{
}
QString id() const override
{
return QString( "TasksetDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
TasksetDockerDock * dockWidget = new TasksetDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
private:
};
TasksetDockerPlugin::TasksetDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new TasksetDockerDockFactory());
}
TasksetDockerPlugin::~TasksetDockerPlugin()
{
}
#include "tasksetdocker.moc"
diff --git a/plugins/dockers/tasksetdocker/tasksetdocker.h b/plugins/dockers/tasksetdocker/tasksetdocker.h
index fbacf0eec4..57dd4893fa 100644
--- a/plugins/dockers/tasksetdocker/tasksetdocker.h
+++ b/plugins/dockers/tasksetdocker/tasksetdocker.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 TASKSETDOCKER_H
#define TASKSETDOCKER_H
#include <QObject>
#include <QVariant>
/**
* Docker showing the channels of the current layer
*/
class TasksetDockerPlugin : public QObject
{
Q_OBJECT
public:
TasksetDockerPlugin(QObject *parent, const QVariantList &);
~TasksetDockerPlugin() override;
};
#endif
diff --git a/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp b/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp
index 16f7c0b6b9..b78ff73ee5 100644
--- a/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp
+++ b/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp
@@ -1,241 +1,242 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "tasksetdocker_dock.h"
#include <QGridLayout>
#include <QListView>
#include <QHeaderView>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QInputDialog>
#include <QAction>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <kis_icon.h>
#include <KoCanvasBase.h>
#include <KoResourceItemChooser.h>
#include <KoResourceServerAdapter.h>
#include <KoResourceServerProvider.h>
#include <KisResourceServerProvider.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <KisMainWindow.h>
#include "tasksetmodel.h"
class KisTasksetDelegate : public QStyledItemDelegate
{
public:
KisTasksetDelegate(QObject * parent = 0) : QStyledItemDelegate(parent) {}
~KisTasksetDelegate() override {}
/// reimplemented
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const override {
return QSize(QStyledItemDelegate::sizeHint(option, index).width(),
qMin(QStyledItemDelegate::sizeHint(option, index).width(), 25));
}
};
class KisTasksetResourceDelegate : public QStyledItemDelegate
{
public:
KisTasksetResourceDelegate(QObject * parent = 0) : QStyledItemDelegate(parent) {}
~KisTasksetResourceDelegate() override {}
/// reimplemented
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
};
void KisTasksetResourceDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
if (! index.isValid())
return;
TasksetResource* taskset = static_cast<TasksetResource*>(index.internalPointer());
if (option.state & QStyle::State_Selected) {
painter->setPen(QPen(option.palette.highlight(), 2.0));
painter->fillRect(option.rect, option.palette.highlight());
painter->setBrush(option.palette.highlightedText());
}
else {
painter->setBrush(option.palette.text());
}
painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, taskset->name());
}
TasksetDockerDock::TasksetDockerDock( ) : QDockWidget(i18n("Task Sets")), m_canvas(0), m_blocked(false)
{
QWidget* widget = new QWidget(this);
setupUi(widget);
m_model = new TasksetModel(this);
tasksetView->setModel(m_model);
tasksetView->setItemDelegate(new KisTasksetDelegate(this));
recordButton->setIcon(KisIconUtils::loadIcon("media-record"));
recordButton->setCheckable(true);
clearButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
saveButton->setIcon(KisIconUtils::loadIcon("document-save"));
saveButton->setEnabled(false);
chooserButton->setIcon(KisIconUtils::loadIcon("edit-copy"));
m_rserver = new KoResourceServerSimpleConstruction<TasksetResource>("kis_taskset", "*.kts");
if (!QFileInfo(m_rserver->saveLocation()).exists()) {
QDir().mkpath(m_rserver->saveLocation());
}
QSharedPointer<KoAbstractResourceServerAdapter> adapter (new KoResourceServerAdapter<TasksetResource>(m_rserver));
m_rserver->loadResources(KoResourceServerProvider::blacklistFileNames(m_rserver->fileNames(), m_rserver->blackListedFiles()));
m_rserver->loadTags();
KoResourceItemChooser* itemChooser = new KoResourceItemChooser(adapter, this);
itemChooser->setItemDelegate(new KisTasksetResourceDelegate(this));
itemChooser->setFixedSize(500, 250);
itemChooser->setRowHeight(30);
itemChooser->setColumnCount(1);
itemChooser->showTaggingBar(true);
chooserButton->setPopupWidget(itemChooser);
connect(itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*)));
setWidget(widget);
connect( tasksetView, SIGNAL(clicked(QModelIndex)),
this, SLOT(activated(QModelIndex)) );
connect( recordButton, SIGNAL(toggled(bool)), this, SLOT(recordClicked()));
connect( clearButton, SIGNAL(clicked(bool)), this, SLOT(clearClicked()));
connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked()));
}
TasksetDockerDock::~TasksetDockerDock()
{
delete m_rserver;
}
void TasksetDockerDock::setCanvas(KoCanvasBase * canvas)
{
if (m_canvas && m_canvas->viewManager()) {
m_canvas->viewManager()->actionCollection()->disconnect(this);
Q_FOREACH (KXMLGUIClient* client, m_canvas->viewManager()->mainWindow()->childClients()) {
client->actionCollection()->disconnect(this);
}
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
}
void TasksetDockerDock::unsetCanvas()
{
m_canvas = 0;
m_model->clear();
}
void TasksetDockerDock::actionTriggered(QAction* action)
{
if(action && !action->objectName().isEmpty() &&
!m_blocked && recordButton->isChecked()) {
m_model->addAction(action);
saveButton->setEnabled(true);
}
}
void TasksetDockerDock::activated(const QModelIndex& index)
{
QAction* action = m_model->actionFromIndex(index);
m_blocked = true;
action->trigger();
m_blocked = false;
}
void TasksetDockerDock::recordClicked()
{
if(m_canvas) {
KisViewManager* view = m_canvas->viewManager();
connect(view->actionCollection(), SIGNAL(actionTriggered(QAction*)),
this, SLOT(actionTriggered(QAction*)), Qt::UniqueConnection);
Q_FOREACH (KXMLGUIClient* client, view->mainWindow()->childClients()) {
connect(client->actionCollection(), SIGNAL(actionTriggered(QAction*)),
this, SLOT(actionTriggered(QAction*)), Qt::UniqueConnection);
}
}
}
void TasksetDockerDock::saveClicked()
{
bool ok;
QString name = QInputDialog::getText(this, i18n("Taskset Name"),
i18n("Name:"), QLineEdit::Normal,
QString(), &ok);
if (!ok) {
return;
}
TasksetResource* taskset = new TasksetResource(QString());
QStringList actionNames;
Q_FOREACH (QAction* action, m_model->actions()) {
actionNames.append(action->objectName());
}
taskset->setActionList(actionNames);
taskset->setValid(true);
QString saveLocation = m_rserver->saveLocation();
bool newName = false;
if(name.isEmpty()) {
newName = true;
name = i18n("Taskset");
}
QFileInfo fileInfo(saveLocation + name + taskset->defaultFileExtension());
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + taskset->defaultFileExtension());
i++;
}
taskset->setFilename(fileInfo.filePath());
if(newName) {
name = i18n("Taskset %1", i);
}
taskset->setName(name);
m_rserver->addResource(taskset);
}
void TasksetDockerDock::clearClicked()
{
saveButton->setEnabled(false);
m_model->clear();
}
void TasksetDockerDock::resourceSelected(KoResource* resource)
{
if(!m_canvas) {
return;
}
m_model->clear();
saveButton->setEnabled(true);
Q_FOREACH (const QString& actionName, static_cast<TasksetResource*>(resource)->actionList()) {
QAction* action = m_canvas->viewManager()->actionCollection()->action(actionName);
if(action) {
m_model->addAction(action);
}
}
}
diff --git a/plugins/dockers/tasksetdocker/tasksetdocker_dock.h b/plugins/dockers/tasksetdocker/tasksetdocker_dock.h
index 68efa9ccf3..6aecd2c25a 100644
--- a/plugins/dockers/tasksetdocker/tasksetdocker_dock.h
+++ b/plugins/dockers/tasksetdocker/tasksetdocker_dock.h
@@ -1,61 +1,62 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 TASKSETDOCKER_DOCK_H
#define TASKSETDOCKER_DOCK_H
#include <QDockWidget>
#include <QModelIndex>
#include <QPointer>
#include <KoCanvasObserverBase.h>
#include <KoResourceServer.h>
#include <kis_canvas2.h>
#include "taskset_resource.h"
#include "ui_wdgtasksetdocker.h"
class TasksetModel;
class TasksetDockerDock : public QDockWidget, public KoCanvasObserverBase, public Ui_WdgTasksetDocker {
Q_OBJECT
public:
TasksetDockerDock();
~TasksetDockerDock() override;
QString observerName() override { return "TasksetDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
private Q_SLOTS:
void actionTriggered(QAction* action);
void activated (const QModelIndex& index);
void recordClicked();
void saveClicked();
void clearClicked();
void resourceSelected( KoResource * resource );
private:
QPointer<KisCanvas2> m_canvas;
TasksetModel *m_model;
bool m_blocked;
KoResourceServer<TasksetResource>* m_rserver;
};
#endif
diff --git a/plugins/dockers/tasksetdocker/tasksetmodel.cpp b/plugins/dockers/tasksetdocker/tasksetmodel.cpp
index 26661e1301..a95f09fff5 100644
--- a/plugins/dockers/tasksetdocker/tasksetmodel.cpp
+++ b/plugins/dockers/tasksetdocker/tasksetmodel.cpp
@@ -1,102 +1,103 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "tasksetmodel.h"
#include <QAction>
#include <klocalizedstring.h>
#include <kis_icon.h>
TasksetModel::TasksetModel(QObject* parent): QAbstractTableModel(parent)
{
}
TasksetModel::~TasksetModel()
{
}
QVariant TasksetModel::data(const QModelIndex& index, int role) const
{
if (index.isValid()) {
switch (role) {
case Qt::DisplayRole:
{
return m_actions.at(index.row())->iconText();
}
case Qt::DecorationRole:
{
const QIcon icon = m_actions.at(index.row())->icon();
if (icon.isNull()) {
return KisIconUtils::loadIcon("tools-wizard");
}
return icon;
}
}
}
return QVariant();
}
QVariant TasksetModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const
{
return i18n("Task");
}
int TasksetModel::rowCount(const QModelIndex& /*parent*/) const
{
return m_actions.count();
}
int TasksetModel::columnCount(const QModelIndex& /*parent*/) const
{
return 1;
}
Qt::ItemFlags TasksetModel::flags(const QModelIndex& /*index*/) const
{
Qt::ItemFlags flags = /*Qt::ItemIsSelectable |*/ Qt::ItemIsEnabled;
return flags;
}
void TasksetModel::addAction(QAction* action)
{
m_actions.append(action);
beginResetModel();
endResetModel();
}
QVector< QAction* > TasksetModel::actions()
{
return m_actions;
}
QAction* TasksetModel::actionFromIndex(const QModelIndex& index)
{
if(index.isValid()) {
return m_actions.at(index.row());
}
return 0;
}
void TasksetModel::clear()
{
m_actions.clear();
beginResetModel();
endResetModel();
}
diff --git a/plugins/dockers/tasksetdocker/tasksetmodel.h b/plugins/dockers/tasksetdocker/tasksetmodel.h
index 524d75ad28..c8ac1c2af1 100644
--- a/plugins/dockers/tasksetdocker/tasksetmodel.h
+++ b/plugins/dockers/tasksetdocker/tasksetmodel.h
@@ -1,49 +1,50 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 TASKSETMODEL_H
#define TASKSETMODEL_H
#include <QModelIndex>
#include <kis_types.h>
class QAction;
class TasksetModel : public QAbstractTableModel
{
Q_OBJECT
public:
TasksetModel(QObject* parent = 0);
~TasksetModel() override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
void addAction(QAction* action);
QAction* actionFromIndex(const QModelIndex& index);
QVector<QAction*> actions();
public Q_SLOTS:
void clear();
private:
QVector<QAction*> m_actions;
};
#endif // TASKSETMODEL_H
diff --git a/plugins/dockers/throttle/Throttle.h b/plugins/dockers/throttle/Throttle.h
index 84e7a0a9b9..64af3f6b62 100644
--- a/plugins/dockers/throttle/Throttle.h
+++ b/plugins/dockers/throttle/Throttle.h
@@ -1,61 +1,62 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 THROTTLE_H
#define THROTTLE_H
#include <QtQuickWidgets/QQuickWidget>
class KisCanvas2;
class KisSignalCompressor;
class ThreadManager : public QObject {
Q_OBJECT
Q_PROPERTY(int threadCount READ threadCount WRITE setThreadCount NOTIFY threadCountChanged)
Q_PROPERTY(int maxThreadCount READ maxThreadCount)
public:
ThreadManager(QObject *parent = 0);
~ThreadManager() override;
void setThreadCount(int threadCount);
int threadCount() const;
int maxThreadCount() const;
private Q_SLOTS:
void slotDoUpdateConfig();
Q_SIGNALS:
void threadCountChanged();
private:
int m_threadCount = 0;
KisSignalCompressor *m_configUpdateCompressor;
};
class Throttle : public QQuickWidget {
Q_OBJECT
public:
Throttle(QWidget *parent);
~Throttle() override;
private:
ThreadManager *m_threadManager {0};
};
#endif
diff --git a/plugins/dockers/throttle/ThrottlePlugin.h b/plugins/dockers/throttle/ThrottlePlugin.h
index 3fb02c1868..2b3d0d16eb 100644
--- a/plugins/dockers/throttle/ThrottlePlugin.h
+++ b/plugins/dockers/throttle/ThrottlePlugin.h
@@ -1,49 +1,50 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 THROTTLEPLUGIN_H
#define THROTTLEPLUGIN_H
#include <KisActionPlugin.h>
#include <QVariant>
#include <QDockWidget>
#include <klocalizedstring.h>
#include <KoCanvasObserverBase.h>
class Throttle;
class BasicDocker : public QDockWidget, public KoCanvasObserverBase
{
Q_OBJECT
public:
BasicDocker() : QDockWidget(i18n("CPU Throttle")) {}
~BasicDocker() override {}
QString observerName() override { return "ThrottleDocker"; }
void setCanvas(KoCanvasBase *) override {}
void unsetCanvas() override {}
};
class ThrottlePlugin : public QObject
{
Q_OBJECT
public:
ThrottlePlugin(QObject *parent, const QVariantList &);
~ThrottlePlugin() override;
};
#endif
diff --git a/plugins/dockers/touchdocker/TouchDockerDock.cpp b/plugins/dockers/touchdocker/TouchDockerDock.cpp
index 281e087913..c81463d8a5 100644
--- a/plugins/dockers/touchdocker/TouchDockerDock.cpp
+++ b/plugins/dockers/touchdocker/TouchDockerDock.cpp
@@ -1,428 +1,429 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 "TouchDockerDock.h"
#include <QtQuickWidgets/QQuickWidget>
#include <QQmlEngine>
#include <QQmlContext>
#include <QAction>
#include <QUrl>
#include <QKeyEvent>
#include <QApplication>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <kis_action_registry.h>
#include <KoDialog.h>
#include <KoResourcePaths.h>
#include <kis_icon.h>
#include <KoCanvasBase.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <KisMainWindow.h>
#include <kis_config.h>
#include <KisPart.h>
#include <KisDocument.h>
#include <KisMimeDatabase.h>
#include <kis_action_manager.h>
#include <kis_action.h>
#include <Theme.h>
#include <Settings.h>
#include <DocumentManager.h>
#include <KisSketchView.h>
#include <QVersionNumber>
namespace
{
bool shouldSetAcceptTouchEvents()
{
// See https://bugreports.qt.io/browse/QTBUG-66718
static QVersionNumber qtVersion = QVersionNumber::fromString(qVersion());
static bool retval = qtVersion > QVersionNumber(5, 9, 3) && qtVersion.normalized() != QVersionNumber(5, 10);
return retval;
}
} // namespace
class TouchDockerDock::Private
{
public:
Private()
{
}
TouchDockerDock *q;
bool allowClose {true};
KisSketchView *sketchView {0};
QString currentSketchPage;
KoDialog *openDialog {0};
KoDialog *saveAsDialog {0};
QMap<QString, QString> buttonMapping;
bool shiftOn {false};
bool ctrlOn {false};
bool altOn {false};
};
TouchDockerDock::TouchDockerDock()
: QDockWidget(i18n("Touch Docker"))
, d(new Private())
{
QStringList defaultMapping = QStringList() << "decrease_opacity"
<< "increase_opacity"
<< "make_brush_color_lighter"
<< "make_brush_color_darker"
<< "decrease_brush_size"
<< "increase_brush_size"
<< "previous_preset"
<< "clear";
QStringList mapping = KisConfig(true).readEntry<QString>("touchdockermapping", defaultMapping.join(',')).split(',');
for (int i = 0; i < 8; ++i) {
if (i < mapping.size()) {
d->buttonMapping[QString("button%1").arg(i + 1)] = mapping[i];
}
else if (i < defaultMapping.size()) {
d->buttonMapping[QString("button%1").arg(i + 1)] = defaultMapping[i];
}
}
m_quickWidget = new QQuickWidget(this);
if (shouldSetAcceptTouchEvents()) {
m_quickWidget->setAttribute(Qt::WA_AcceptTouchEvents);
}
setWidget(m_quickWidget);
setEnabled(true);
m_quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this);
m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
Settings *settings = new Settings(this);
DocumentManager::instance()->setSettingsManager(settings);
m_quickWidget->engine()->rootContext()->setContextProperty("Settings", settings);
Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry<QString>("theme", "default"),
m_quickWidget->engine());
if (theme) {
settings->setTheme(theme);
}
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml"));
}
TouchDockerDock::~TouchDockerDock()
{
}
bool TouchDockerDock::allowClose() const
{
return d->allowClose;
}
void TouchDockerDock::setAllowClose(bool allow)
{
d->allowClose = allow;
}
QString TouchDockerDock::currentSketchPage() const
{
return d->currentSketchPage;
}
void TouchDockerDock::setCurrentSketchPage(QString newPage)
{
d->currentSketchPage = newPage;
emit currentSketchPageChanged();
}
void TouchDockerDock::closeEvent(QCloseEvent* event)
{
if (!d->allowClose) {
event->ignore();
emit closeRequested();
} else {
event->accept();
}
}
void TouchDockerDock::slotButtonPressed(const QString &id)
{
if (id == "fileOpenButton") {
showFileOpenDialog();
}
else if (id == "fileSaveButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) {
bool batchMode = m_canvas->viewManager()->document()->fileBatchMode();
m_canvas->viewManager()->document()->setFileBatchMode(true);
m_canvas->viewManager()->document()->save(true, 0);
m_canvas->viewManager()->document()->setFileBatchMode(batchMode);
}
else if (id == "fileSaveAsButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) {
showFileSaveAsDialog();
}
else {
QAction *a = action(id);
if (a) {
if (a->isCheckable()) {
a->toggle();
}
else {
a->trigger();
}
}
else if (id == "shift") {
// set shift state for the next pointer event, somehow
QKeyEvent event(d->shiftOn ? QEvent::KeyRelease : QEvent::KeyPress,
0,
Qt::ShiftModifier);
QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event);
d->shiftOn = !d->shiftOn;
}
else if (id == "ctrl") {
// set ctrl state for the next pointer event, somehow
QKeyEvent event(d->ctrlOn ? QEvent::KeyRelease : QEvent::KeyPress,
0,
Qt::ControlModifier);
QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event);
d->ctrlOn = !d->ctrlOn;
}
else if (id == "alt") {
// set alt state for the next pointer event, somehow
QKeyEvent event(d->altOn ? QEvent::KeyRelease : QEvent::KeyPress,
0,
Qt::AltModifier);
QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event);
d->altOn = !d->altOn;
}
}
}
void TouchDockerDock::slotOpenImage(QString path)
{
if (d->openDialog) {
d->openDialog->accept();
}
KisPart::instance()->currentMainwindow()->openDocument(QUrl::fromLocalFile(path), KisMainWindow::None);
}
void TouchDockerDock::slotSaveAs(QString path, QString mime)
{
if (d->saveAsDialog) {
d->saveAsDialog->accept();
}
m_canvas->viewManager()->document()->saveAs(QUrl::fromLocalFile(path), mime.toLatin1(), true);
m_canvas->viewManager()->document()->waitForSavingToComplete();
}
void TouchDockerDock::hideFileOpenDialog()
{
if (d->openDialog) {
d->openDialog->accept();
}
}
void TouchDockerDock::hideFileSaveAsDialog()
{
if (d->saveAsDialog) {
d->saveAsDialog->accept();
}
}
QString TouchDockerDock::imageForButton(QString id)
{
if (d->buttonMapping.contains(id)) {
id = d->buttonMapping[id];
}
if (KisActionRegistry::instance()->hasAction(id)) {
QString a = KisActionRegistry::instance()->getActionProperty(id, "icon");
if (!a.isEmpty()) {
return "image://icon/" + a;
}
}
return QString();
}
QString TouchDockerDock::textForButton(QString id)
{
if (d->buttonMapping.contains(id)) {
id = d->buttonMapping[id];
}
if (KisActionRegistry::instance()->hasAction(id)) {
QString a = KisActionRegistry::instance()->getActionProperty(id, "iconText");
if (a.isEmpty()) {
a = KisActionRegistry::instance()->getActionProperty(id, "text");
}
return a;
}
return id;
}
QAction *TouchDockerDock::action(QString id) const
{
if (m_canvas && m_canvas->viewManager()) {
if (d->buttonMapping.contains(id)) {
id = d->buttonMapping[id];
}
return m_canvas->viewManager()->actionManager()->actionByName(id);
}
return 0;
}
void TouchDockerDock::showFileOpenDialog()
{
if (!d->openDialog) {
d->openDialog = createDialog("qrc:/opendialog.qml");
}
d->openDialog->exec();
}
void TouchDockerDock::showFileSaveAsDialog()
{
if (!d->saveAsDialog) {
d->saveAsDialog = createDialog("qrc:/saveasdialog.qml");
}
d->saveAsDialog->exec();
}
void TouchDockerDock::changeEvent(QEvent *event)
{
if (event->type() == QEvent::PaletteChange) {
m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml"));
event->accept();
} else {
event->ignore();
}
}
void TouchDockerDock::tabletEvent(QTabletEvent *event)
{
#ifdef Q_OS_WIN
/**
* On Windows (only in WinInk mode), unless we accept the tablet event,
* OS will start windows gestures, like click+hold for right click.
* It will block any mouse events generation.
*
* In our own (hacky) implementation, if we accept the event, we block
* the gesture, but still generate a fake mouse event.
*/
event->accept();
#else
QDockWidget::tabletEvent(event);
#endif
}
KoDialog *TouchDockerDock::createDialog(const QString qml)
{
KoDialog *dlg = new KoDialog(this);
dlg->setButtons(KoDialog::None);
QQuickWidget *quickWidget = new QQuickWidget(this);
if (shouldSetAcceptTouchEvents()) {
quickWidget->setAttribute(Qt::WA_AcceptTouchEvents);
}
dlg->setMainWidget(quickWidget);
setEnabled(true);
quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this);
quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
Settings *settings = new Settings(this);
DocumentManager::instance()->setSettingsManager(settings);
quickWidget->engine()->rootContext()->setContextProperty("Settings", settings);
Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry<QString>("theme", "default"),
quickWidget->engine());
settings->setTheme(theme);
quickWidget->setSource(QUrl(qml));
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
dlg->setMinimumSize(1280, 768);
return dlg;
}
QObject *TouchDockerDock::sketchKisView() const
{
return d->sketchView;
}
void TouchDockerDock::setSketchKisView(QObject* newView)
{
if (d->sketchView) {
d->sketchView->disconnect(this);
}
if (d->sketchView != newView) {
d->sketchView = qobject_cast<KisSketchView*>(newView);
emit sketchKisViewChanged();
}
}
void TouchDockerDock::setCanvas(KoCanvasBase *canvas)
{
setEnabled(true);
if (m_canvas == canvas) {
return;
}
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
}
if (!canvas) {
m_canvas = 0;
return;
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
}
void TouchDockerDock::unsetCanvas()
{
setEnabled(true);
m_canvas = 0;
}
diff --git a/plugins/dockers/touchdocker/TouchDockerDock.h b/plugins/dockers/touchdocker/TouchDockerDock.h
index 478bc0c530..058b1d265b 100644
--- a/plugins/dockers/touchdocker/TouchDockerDock.h
+++ b/plugins/dockers/touchdocker/TouchDockerDock.h
@@ -1,93 +1,94 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 TOUCHDOCKER_DOCK_H
#define TOUCHDOCKER_DOCK_H
#include <QDockWidget>
#include <QIcon>
#include <KoCanvasObserverBase.h>
#include <QPointer>
#include <kis_canvas2.h>
class KoDialog;
class QQuickWidget;
class TouchDockerDock : public QDockWidget, public KoCanvasObserverBase {
Q_OBJECT
Q_PROPERTY(bool allowClose READ allowClose WRITE setAllowClose)
Q_PROPERTY(QString currentSketchPage READ currentSketchPage WRITE setCurrentSketchPage NOTIFY currentSketchPageChanged)
Q_PROPERTY(QObject* sketchKisView READ sketchKisView WRITE setSketchKisView NOTIFY sketchKisViewChanged)
public:
TouchDockerDock();
~TouchDockerDock() override;
QString observerName() override { return "TouchDockerDock"; }
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
bool allowClose() const;
void setAllowClose(bool allow);
QString currentSketchPage() const;
void setCurrentSketchPage(QString newPage);
QObject *sketchKisView() const;
void setSketchKisView(QObject *newView);
virtual void closeEvent(QCloseEvent *event);
Q_SIGNALS:
void closeRequested();
void currentSketchPageChanged();
void sketchKisViewChanged();
public Q_SLOTS:
void slotButtonPressed(const QString &id);
void slotOpenImage(QString path);
void slotSaveAs(QString path, QString mime);
void hideFileOpenDialog();
void hideFileSaveAsDialog();
QString imageForButton(QString id);
QString textForButton(QString id);
QAction *action(QString id) const;
private:
void showFileOpenDialog();
void showFileSaveAsDialog();
void changeEvent(QEvent* event) override;
void tabletEvent(QTabletEvent *event) override;
KoDialog *createDialog(const QString qml);
QPointer<KisCanvas2> m_canvas;
QQuickWidget *m_quickWidget {0};
class Private;
const QScopedPointer<Private> d;
};
#endif
diff --git a/plugins/dockers/touchdocker/TouchDockerPlugin.cpp b/plugins/dockers/touchdocker/TouchDockerPlugin.cpp
index ef16fc8ee5..97bdf0200b 100644
--- a/plugins/dockers/touchdocker/TouchDockerPlugin.cpp
+++ b/plugins/dockers/touchdocker/TouchDockerPlugin.cpp
@@ -1,77 +1,78 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 "TouchDockerPlugin.h"
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoDockFactoryBase.h>
#include "kis_config.h"
#include "kis_types.h"
#include "KisViewManager.h"
#include "TouchDockerDock.h"
#include <KoDockRegistry.h>
K_PLUGIN_FACTORY_WITH_JSON(TouchDockerPluginFactory, "krita_touchdocker.json", registerPlugin<TouchDockerPlugin>();)
class TouchDockerDockFactory : public KoDockFactoryBase {
public:
TouchDockerDockFactory()
{
}
QString id() const override
{
return QString( "TouchDocker" );
}
virtual Qt::DockWidgetArea defaultDockWidgetArea() const
{
return Qt::RightDockWidgetArea;
}
QDockWidget* createDockWidget() override
{
TouchDockerDock * dockWidget = new TouchDockerDock();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override
{
return DockMinimized;
}
};
TouchDockerPlugin::TouchDockerPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoDockRegistry::instance()->add(new TouchDockerDockFactory());
}
TouchDockerPlugin::~TouchDockerPlugin()
{
}
#include "TouchDockerPlugin.moc"
diff --git a/plugins/dockers/touchdocker/TouchDockerPlugin.h b/plugins/dockers/touchdocker/TouchDockerPlugin.h
index fc120672dc..73c38764c7 100644
--- a/plugins/dockers/touchdocker/TouchDockerPlugin.h
+++ b/plugins/dockers/touchdocker/TouchDockerPlugin.h
@@ -1,32 +1,33 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 TOUCHDOCKERPLUGIN_H
#define TOUCHDOCKERPLUGIN_H
#include <QObject>
#include <QVariant>
class TouchDockerPlugin : public QObject
{
Q_OBJECT
public:
TouchDockerPlugin(QObject *parent, const QVariantList &);
~TouchDockerPlugin() override;
};
#endif
diff --git a/plugins/extensions/animationrenderer/AnimationRenderer.cpp b/plugins/extensions/animationrenderer/AnimationRenderer.cpp
index 8bbb553aa6..718941a02e 100644
--- a/plugins/extensions/animationrenderer/AnimationRenderer.cpp
+++ b/plugins/extensions/animationrenderer/AnimationRenderer.cpp
@@ -1,206 +1,206 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "AnimationRenderer.h"
#include <QMessageBox>
#include <klocalizedstring.h>
#include <kpluginfactory.h>
#include <kis_image.h>
#include <KisViewManager.h>
#include <KoUpdater.h>
#include <kis_node_manager.h>
#include <kis_image_manager.h>
#include <kis_action.h>
#include <kis_image_animation_interface.h>
#include <kis_properties_configuration.h>
#include <kis_config.h>
#include <KisDocument.h>
#include <KisMimeDatabase.h>
#include <kis_time_range.h>
#include <KisImportExportManager.h>
+#include <KisImportExportErrorCode.h>
#include "DlgAnimationRenderer.h"
#include <dialogs/KisAsyncAnimationFramesSaveDialog.h>
#include "video_saver.h"
#include "KisAnimationRenderingOptions.h"
K_PLUGIN_FACTORY_WITH_JSON(AnimaterionRendererFactory, "kritaanimationrenderer.json", registerPlugin<AnimaterionRenderer>();)
AnimaterionRenderer::AnimaterionRenderer(QObject *parent, const QVariantList &)
: KisActionPlugin(parent)
{
// Shows the big dialog
KisAction *action = createAction("render_animation");
action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenderAnimation()));
// Re-renders the image sequence as defined in the last render
action = createAction("render_animation_again");
action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenderSequenceAgain()));
}
AnimaterionRenderer::~AnimaterionRenderer()
{
}
void AnimaterionRenderer::slotRenderAnimation()
{
KisImageWSP image = viewManager()->image();
if (!image) return;
if (!image->animationInterface()->hasAnimation()) return;
KisDocument *doc = viewManager()->document();
DlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow());
dlgAnimationRenderer.setCaption(i18n("Render Animation"));
if (dlgAnimationRenderer.exec() == QDialog::Accepted) {
KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions();
renderAnimationImpl(doc, encoderOptions);
}
}
void AnimaterionRenderer::slotRenderSequenceAgain()
{
KisImageWSP image = viewManager()->image();
if (!image) return;
if (!image->animationInterface()->hasAnimation()) return;
KisDocument *doc = viewManager()->document();
KisConfig cfg(true);
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
KisAnimationRenderingOptions encoderOptions;
encoderOptions.fromProperties(settings);
renderAnimationImpl(doc, encoderOptions);
}
void AnimaterionRenderer::renderAnimationImpl(KisDocument *doc, KisAnimationRenderingOptions encoderOptions)
{
const QString frameMimeType = encoderOptions.frameMimeType;
const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory();
const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first();
const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory)
.arg(encoderOptions.basename)
.arg(extension);
/**
* The dialog should ensure that the size of the video is even
*/
KIS_SAFE_ASSERT_RECOVER(
!((encoderOptions.width & 0x1 || encoderOptions.height & 0x1)
&& (encoderOptions.videoMimeType == "video/mp4" ||
encoderOptions.videoMimeType == "video/x-matroska"))) {
encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1);
encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1);
}
const QSize scaledSize =
doc->image()->bounds().size().scaled(
encoderOptions.width, encoderOptions.height,
Qt::KeepAspectRatio);
if ((scaledSize.width() & 0x1 || scaledSize.height() & 0x1)
&& (encoderOptions.videoMimeType == "video/mp4" ||
encoderOptions.videoMimeType == "video/x-matroska")) {
QString m = "Mastroska (.mkv)";
if (encoderOptions.videoMimeType == "video/mp4") {
m = "Mpeg4 (.mp4)";
}
qWarning() << m <<"requires width and height to be even, resize and try again!";
doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", m));
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage()));
return;
}
const bool batchMode = false; // TODO: fetch correctly!
KisAsyncAnimationFramesSaveDialog exporter(doc->image(),
KisTimeRange::fromTime(encoderOptions.firstFrame,
encoderOptions.lastFrame),
baseFileName,
encoderOptions.sequenceStart,
encoderOptions.frameExportConfig);
exporter.setBatchMode(batchMode);
KisAsyncAnimationFramesSaveDialog::Result result =
exporter.regenerateRange(viewManager()->mainWindow()->viewManager());
// the folder could have been read-only or something else could happen
if (encoderOptions.shouldEncodeVideo &&
result == KisAsyncAnimationFramesSaveDialog::RenderComplete) {
const QString savedFilesMask = exporter.savedFilesMask();
const QString resultFile = encoderOptions.resolveAbsoluteVideoFilePath();
KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute())
{
const QFileInfo info(resultFile);
QDir dir(info.absolutePath());
if (!dir.exists()) {
dir.mkpath(info.absolutePath());
}
KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists());
}
- KisImportExportFilter::ConversionStatus res;
+ KisImportExportErrorCode res;
QFile fi(resultFile);
if (!fi.open(QIODevice::WriteOnly)) {
qWarning() << "Could not open" << fi.fileName() << "for writing!";
- doc->setErrorMessage(i18n("Could not open %1 for writing!", fi.fileName()));
- res = KisImportExportFilter::CreationError;
+ res = KisImportExportErrorCannotWrite(fi.error());
} else {
fi.close();
}
QScopedPointer<VideoSaver> encoder(new VideoSaver(doc, batchMode));
res = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode);
- if (res != KisImportExportFilter::OK) {
- QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage()));
+ if (!res.isOk()) {
+ QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", res.errorMessage()));
}
if (encoderOptions.shouldDeleteSequence) {
QDir d(framesDirectory);
QStringList sequenceFiles = d.entryList(QStringList() << encoderOptions.basename + "*." + extension, QDir::Files);
Q_FOREACH(const QString &f, sequenceFiles) {
d.remove(f);
}
}
} else if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) {
viewManager()->mainWindow()->viewManager()->showFloatingMessage(i18n("Failed to render animation frames!"), QIcon());
}
}
#include "AnimationRenderer.moc"
diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp
index 83fc6ffb75..a48d47335e 100644
--- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp
+++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp
@@ -1,594 +1,594 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "DlgAnimationRenderer.h"
#include <QStandardPaths>
#include <QPluginLoader>
#include <QJsonObject>
#include <QMessageBox>
#include <QStringList>
#include <QProcess>
#include <klocalizedstring.h>
#include <kpluginfactory.h>
#include <KoResourcePaths.h>
#include <kis_properties_configuration.h>
#include <kis_debug.h>
#include <KisMimeDatabase.h>
#include <KoJsonTrader.h>
#include <KisImportExportFilter.h>
#include <kis_image.h>
#include <kis_image_animation_interface.h>
#include <kis_time_range.h>
#include <KisImportExportManager.h>
#include <kis_config_widget.h>
#include <KisDocument.h>
#include <QHBoxLayout>
#include <kis_config.h>
#include <kis_file_name_requester.h>
#include <KoDialog.h>
#include "kis_slider_spin_box.h"
#include "kis_acyclic_signal_connector.h"
#include "video_saver.h"
#include "KisAnimationRenderingOptions.h"
#include "video_export_options_dialog.h"
DlgAnimationRenderer::DlgAnimationRenderer(KisDocument *doc, QWidget *parent)
: KoDialog(parent)
, m_image(doc->image())
, m_doc(doc)
{
KisConfig cfg(true);
setCaption(i18n("Render Animation"));
setButtons(Ok | Cancel);
setDefaultButton(Ok);
m_page = new WdgAnimationRenderer(this);
m_page->layout()->setMargin(0);
m_page->dirRequester->setMode(KoFileDialog::OpenDirectory);
- m_page->intStart->setMinimum(doc->image()->animationInterface()->fullClipRange().start());
+ m_page->intStart->setMinimum(0);
m_page->intStart->setMaximum(doc->image()->animationInterface()->fullClipRange().end());
m_page->intStart->setValue(doc->image()->animationInterface()->playbackRange().start());
m_page->intEnd->setMinimum(doc->image()->animationInterface()->fullClipRange().start());
// animators sometimes want to export after end frame
//m_page->intEnd->setMaximum(doc->image()->animationInterface()->fullClipRange().end());
m_page->intEnd->setValue(doc->image()->animationInterface()->playbackRange().end());
m_page->intHeight->setMinimum(1);
m_page->intHeight->setMaximum(10000);
m_page->intHeight->setValue(doc->image()->height());
m_page->intWidth->setMinimum(1);
m_page->intWidth->setMaximum(10000);
m_page->intWidth->setValue(doc->image()->width());
// try to lock the width and height being updated
KisAcyclicSignalConnector *constrainsConnector = new KisAcyclicSignalConnector(this);
constrainsConnector->createCoordinatedConnector()->connectBackwardInt(m_page->intWidth, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsWidth(int)));
constrainsConnector->createCoordinatedConnector()->connectForwardInt(m_page->intHeight, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsHeight(int)));
m_page->intFramesPerSecond->setValue(doc->image()->animationInterface()->framerate());
QFileInfo audioFileInfo(doc->image()->animationInterface()->audioChannelFileName());
const bool hasAudio = audioFileInfo.exists();
m_page->chkIncludeAudio->setEnabled(hasAudio);
m_page->chkIncludeAudio->setChecked(hasAudio && !doc->image()->animationInterface()->isAudioMuted());
QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
mimes.sort();
Q_FOREACH(const QString &mime, mimes) {
QString description = KisMimeDatabase::descriptionForMimeType(mime);
if (description.isEmpty()) {
description = mime;
}
m_page->cmbMimetype->addItem(description, mime);
if (mime == "image/png") {
m_page->cmbMimetype->setCurrentIndex(m_page->cmbMimetype->count() - 1);
}
}
setMainWidget(m_page);
QVector<QString> supportedMimeType;
supportedMimeType << "video/x-matroska";
supportedMimeType << "image/gif";
supportedMimeType << "video/ogg";
supportedMimeType << "video/mp4";
Q_FOREACH (const QString &mime, supportedMimeType) {
QString description = KisMimeDatabase::descriptionForMimeType(mime);
if (description.isEmpty()) {
description = mime;
}
m_page->cmbRenderType->addItem(description, mime);
}
m_page->videoFilename->setMode(KoFileDialog::SaveFile);
connect(m_page->bnExportOptions, SIGNAL(clicked()), this, SLOT(sequenceMimeTypeOptionsClicked()));
connect(m_page->bnRenderOptions, SIGNAL(clicked()), this, SLOT(selectRenderOptions()));
m_page->ffmpegLocation->setMode(KoFileDialog::OpenFile);
m_page->cmbRenderType->setCurrentIndex(cfg.readEntry<int>("AnimationRenderer/render_type", 0));
connect(m_page->shouldExportOnlyImageSequence, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged()));
connect(m_page->shouldExportOnlyVideo, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged()));
connect(m_page->shouldExportAll, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged()));
connect(m_page->intFramesPerSecond, SIGNAL(valueChanged(int)), SLOT(frameRateChanged(int)));
// connect and cold init
connect(m_page->cmbRenderType, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRenderType(int)));
selectRenderType(m_page->cmbRenderType->currentIndex());
resize(m_page->sizeHint());
connect(this, SIGNAL(accepted()), SLOT(slotDialogAccepted()));
{
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
KisAnimationRenderingOptions options;
options.fromProperties(settings);
loadAnimationOptions(options);
}
}
DlgAnimationRenderer::~DlgAnimationRenderer()
{
delete m_page;
}
void DlgAnimationRenderer::getDefaultVideoEncoderOptions(const QString &mimeType,
KisPropertiesConfigurationSP cfg,
QString *customFFMpegOptionsString,
bool *forceHDRVideo)
{
const VideoExportOptionsDialog::ContainerType containerType =
mimeType == "video/ogg" ?
VideoExportOptionsDialog::OGV :
VideoExportOptionsDialog::DEFAULT;
QScopedPointer<VideoExportOptionsDialog> encoderConfigWidget(
new VideoExportOptionsDialog(containerType, 0));
// we always enable HDR, letting the user to force it
encoderConfigWidget->setSupportsHDR(true);
encoderConfigWidget->setConfiguration(cfg);
*customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString();
*forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames();
}
void DlgAnimationRenderer::loadAnimationOptions(const KisAnimationRenderingOptions &options)
{
const QString documentPath = m_doc->localFilePath();
m_page->txtBasename->setText(options.basename);
if (!options.lastDocuemntPath.isEmpty() &&
options.lastDocuemntPath == documentPath) {
m_page->intStart->setValue(options.firstFrame);
m_page->intEnd->setValue(options.lastFrame);
m_page->sequenceStart->setValue(options.sequenceStart);
m_page->intWidth->setValue(options.width);
m_page->intHeight->setValue(options.height);
m_page->intFramesPerSecond->setValue(options.frameRate);
m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->videoFilename->setFileName(options.videoFileName);
m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->dirRequester->setFileName(options.directory);
} else {
m_page->intStart->setValue(m_image->animationInterface()->playbackRange().start());
m_page->intEnd->setValue(m_image->animationInterface()->playbackRange().end());
m_page->sequenceStart->setValue(m_image->animationInterface()->playbackRange().start());
m_page->intWidth->setValue(m_image->width());
m_page->intHeight->setValue(m_image->height());
m_page->intFramesPerSecond->setValue(m_image->animationInterface()->framerate());
m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->videoFilename->setFileName(defaultVideoFileName(m_doc, options.videoMimeType));
m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->dirRequester->setFileName(options.directory);
}
for (int i = 0; i < m_page->cmbMimetype->count(); ++i) {
if (m_page->cmbMimetype->itemData(i).toString() == options.frameMimeType) {
m_page->cmbMimetype->setCurrentIndex(i);
break;
}
}
for (int i = 0; i < m_page->cmbRenderType->count(); ++i) {
if (m_page->cmbRenderType->itemData(i).toString() == options.videoMimeType) {
m_page->cmbRenderType->setCurrentIndex(i);
break;
}
}
m_page->chkIncludeAudio->setChecked(options.includeAudio);
if (options.shouldDeleteSequence) {
KIS_SAFE_ASSERT_RECOVER_NOOP(options.shouldEncodeVideo);
m_page->shouldExportOnlyVideo->setChecked(true);
} else if (!options.shouldEncodeVideo) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!options.shouldDeleteSequence);
m_page->shouldExportOnlyImageSequence->setChecked(true);
} else {
m_page->shouldExportAll->setChecked(true); // export to both
}
{
KisConfig cfg(true);
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER");
getDefaultVideoEncoderOptions(options.videoMimeType, settings,
&m_customFFMpegOptionsString,
&m_forceHDRVideo);
}
m_page->ffmpegLocation->setStartDir(QFileInfo(m_doc->localFilePath()).path());
m_page->ffmpegLocation->setFileName(findFFMpeg(options.ffmpegPath));
}
QString DlgAnimationRenderer::defaultVideoFileName(KisDocument *doc, const QString &mimeType)
{
const QString docFileName = !doc->localFilePath().isEmpty() ?
doc->localFilePath() : i18n("Untitled");
return
QString("%1.%2")
.arg(QFileInfo(docFileName).completeBaseName())
.arg(KisMimeDatabase::suffixesForMimeType(mimeType).first());
}
void DlgAnimationRenderer::selectRenderType(int index)
{
const QString mimeType = m_page->cmbRenderType->itemData(index).toString();
m_page->bnRenderOptions->setEnabled(mimeType != "image/gif");
m_page->lblGifWarning->setVisible((mimeType == "image/gif" && m_page->intFramesPerSecond->value() > 50));
QString videoFileName = defaultVideoFileName(m_doc, mimeType);
if (!m_page->videoFilename->fileName().isEmpty()) {
const QFileInfo info = QFileInfo(m_page->videoFilename->fileName());
const QString baseName = info.completeBaseName();
const QString path = info.path();
videoFileName =
QString("%1%2%3.%4").arg(path).arg(QDir::separator()).arg(baseName).arg(KisMimeDatabase::suffixesForMimeType(mimeType).first());
}
m_page->videoFilename->setMimeTypeFilters(QStringList() << mimeType, mimeType);
m_page->videoFilename->setFileName(videoFileName);
}
void DlgAnimationRenderer::selectRenderOptions()
{
const int index = m_page->cmbRenderType->currentIndex();
const QString mimetype = m_page->cmbRenderType->itemData(index).toString();
const VideoExportOptionsDialog::ContainerType containerType =
mimetype == "video/ogg" ?
VideoExportOptionsDialog::OGV :
VideoExportOptionsDialog::DEFAULT;
VideoExportOptionsDialog *encoderConfigWidget =
new VideoExportOptionsDialog(containerType, this);
// we always enable HDR, letting the user to force it
encoderConfigWidget->setSupportsHDR(true);
{
KisConfig cfg(true);
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER");
encoderConfigWidget->setConfiguration(settings);
}
KoDialog dlg(this);
dlg.setMainWidget(encoderConfigWidget);
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
if (dlg.exec() == QDialog::Accepted) {
KisConfig cfg(false);
cfg.setExportConfiguration("VIDEO_ENCODER", encoderConfigWidget->configuration());
m_customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString();
m_forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames();
}
dlg.setMainWidget(0);
encoderConfigWidget->deleteLater();
}
void DlgAnimationRenderer::sequenceMimeTypeOptionsClicked()
{
int index = m_page->cmbMimetype->currentIndex();
KisConfigWidget *frameExportConfigWidget = 0;
QString mimetype = m_page->cmbMimetype->itemData(index).toString();
QSharedPointer<KisImportExportFilter> filter(KisImportExportManager::filterForMimeType(mimetype, KisImportExportManager::Export));
if (filter) {
frameExportConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1());
if (frameExportConfigWidget) {
KisPropertiesConfigurationSP config = filter->lastSavedConfiguration("", mimetype.toLatin1());
if (config) {
KisImportExportManager::fillStaticExportConfigurationProperties(config, m_image);
}
frameExportConfigWidget->setConfiguration(config);
KoDialog dlg(this);
dlg.setMainWidget(frameExportConfigWidget);
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
if (dlg.exec() == QDialog::Accepted) {
KisConfig cfg(false);
cfg.setExportConfiguration(mimetype, frameExportConfigWidget->configuration());
}
frameExportConfigWidget->hide();
dlg.setMainWidget(0);
frameExportConfigWidget->setParent(0);
frameExportConfigWidget->deleteLater();
}
}
}
inline int roundByTwo(int value) {
return value + (value & 0x1);
}
KisAnimationRenderingOptions DlgAnimationRenderer::getEncoderOptions() const
{
KisAnimationRenderingOptions options;
options.lastDocuemntPath = m_doc->localFilePath();
options.videoMimeType = m_page->cmbRenderType->currentData().toString();
options.frameMimeType = m_page->cmbMimetype->currentData().toString();
options.basename = m_page->txtBasename->text();
options.directory = m_page->dirRequester->fileName();
options.firstFrame = m_page->intStart->value();
options.lastFrame = m_page->intEnd->value();
options.sequenceStart = m_page->sequenceStart->value();
options.shouldEncodeVideo = !m_page->shouldExportOnlyImageSequence->isChecked();
options.shouldDeleteSequence = m_page->shouldExportOnlyVideo->isChecked();
options.includeAudio = m_page->chkIncludeAudio->isChecked();
options.ffmpegPath = m_page->ffmpegLocation->fileName();
options.frameRate = m_page->intFramesPerSecond->value();
if (options.frameRate > 50 && options.videoMimeType == "image/gif") {
options.frameRate = 50;
}
options.width = roundByTwo(m_page->intWidth->value());
options.height = roundByTwo(m_page->intHeight->value());
options.videoFileName = m_page->videoFilename->fileName();
options.customFFMpegOptions = m_customFFMpegOptionsString;
{
KisConfig config(true);
KisPropertiesConfigurationSP cfg = config.exportConfiguration(options.frameMimeType);
if (cfg) {
KisImportExportManager::fillStaticExportConfigurationProperties(cfg, m_image);
}
const bool forceHDR = m_forceHDRVideo && !m_page->shouldExportOnlyImageSequence->isChecked();
if (forceHDR) {
KIS_SAFE_ASSERT_RECOVER_NOOP(options.frameMimeType == "image/png");
cfg->setProperty("forceSRGB", false);
cfg->setProperty("saveAsHDR", true);
}
options.frameExportConfig = cfg;
}
return options;
}
void DlgAnimationRenderer::slotButtonClicked(int button)
{
if (button == KoDialog::Ok && !m_page->shouldExportOnlyImageSequence->isChecked()) {
QString ffmpeg = m_page->ffmpegLocation->fileName();
if (m_page->videoFilename->fileName().isEmpty()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Please enter a file name to render to."));
return;
}
else if (ffmpeg.isEmpty()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is unknown. Please install FFmpeg first: Krita cannot render animations without FFmpeg. (<a href=\"https://www.ffmpeg.org\">www.ffmpeg.org</a>)"));
return;
}
else {
QFileInfo fi(ffmpeg);
if (!fi.exists()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is invalid. Please select the correct location of the FFmpeg executable on your system."));
return;
}
}
}
KoDialog::slotButtonClicked(button);
}
void DlgAnimationRenderer::slotDialogAccepted()
{
KisConfig cfg(false);
KisAnimationRenderingOptions options = getEncoderOptions();
cfg.setExportConfiguration("ANIMATION_EXPORT", options.toProperties());
}
QString DlgAnimationRenderer::findFFMpeg(const QString &customLocation)
{
QString result;
QStringList proposedPaths;
if (!customLocation.isEmpty()) {
proposedPaths << customLocation;
proposedPaths << customLocation + QDir::separator() + "ffmpeg";
}
proposedPaths << KoResourcePaths::getApplicationRoot() +
QDir::separator() + "bin" + QDir::separator() + "ffmpeg";
#ifndef Q_OS_WIN
proposedPaths << QDir::homePath() + "/bin/ffmpeg";
proposedPaths << "/usr/bin/ffmpeg";
proposedPaths << "/usr/local/bin/ffmpeg";
#endif
Q_FOREACH (QString path, proposedPaths) {
if (path.isEmpty()) continue;
#ifdef Q_OS_WIN
path = QDir::toNativeSeparators(QDir::cleanPath(path));
if (path.endsWith(QDir::separator())) {
continue;
}
if (!path.endsWith(".exe")) {
if (!QFile::exists(path)) {
path += ".exe";
if (!QFile::exists(path)) {
continue;
}
}
}
#endif
QProcess testProcess;
testProcess.start(path, QStringList() << "-version");
if (testProcess.waitForStarted(1000)) {
testProcess.waitForFinished(1000);
}
const bool successfulStart =
testProcess.state() == QProcess::NotRunning &&
testProcess.error() == QProcess::UnknownError;
if (successfulStart) {
result = path;
break;
}
}
return result;
}
void DlgAnimationRenderer::slotExportTypeChanged()
{
KisConfig cfg(false);
bool willEncodeVideo =
m_page->shouldExportAll->isChecked() || m_page->shouldExportOnlyVideo->isChecked();
// if a video format needs to be outputted
if (willEncodeVideo) {
// videos always uses PNG for creating video, so disable the ability to change the format
m_page->cmbMimetype->setEnabled(false);
for (int i = 0; i < m_page->cmbMimetype->count(); ++i) {
if (m_page->cmbMimetype->itemData(i).toString() == "image/png") {
m_page->cmbMimetype->setCurrentIndex(i);
break;
}
}
}
m_page->intWidth->setVisible(willEncodeVideo);
m_page->intHeight->setVisible(willEncodeVideo);
m_page->intFramesPerSecond->setVisible(willEncodeVideo);
m_page->fpsLabel->setVisible(willEncodeVideo);
m_page->lblWidth->setVisible(willEncodeVideo);
m_page->lblHeight->setVisible(willEncodeVideo);
// if only exporting video
if (m_page->shouldExportOnlyVideo->isChecked()) {
m_page->cmbMimetype->setEnabled(false); // allow to change image format
m_page->imageSequenceOptionsGroup->setVisible(false);
m_page->videoOptionsGroup->setVisible(false); //shrinks the horizontal space temporarily to help resize() work
m_page->videoOptionsGroup->setVisible(true);
}
// if only an image sequence needs to be output
if (m_page->shouldExportOnlyImageSequence->isChecked()) {
m_page->cmbMimetype->setEnabled(true); // allow to change image format
m_page->videoOptionsGroup->setVisible(false);
m_page->imageSequenceOptionsGroup->setVisible(false);
m_page->imageSequenceOptionsGroup->setVisible(true);
}
// show all options
if (m_page->shouldExportAll->isChecked() ) {
m_page->imageSequenceOptionsGroup->setVisible(true);
m_page->videoOptionsGroup->setVisible(true);
}
// for the resize to work as expected, try to hide elements first before displaying other ones.
// if the widget gets bigger at any point, the resize will use that, even if elements are hidden later to make it smaller
resize(m_page->sizeHint());
}
void DlgAnimationRenderer::frameRateChanged(int framerate)
{
const QString mimeType = m_page->cmbRenderType->itemData(m_page->cmbRenderType->currentIndex()).toString();
m_page->lblGifWarning->setVisible((mimeType == "image/gif" && framerate > 50));
}
void DlgAnimationRenderer::slotLockAspectRatioDimensionsWidth(int width)
{
Q_UNUSED(width);
float aspectRatio = (float)m_image->width() / (float)m_image->height();
// update height here
float newHeight = m_page->intWidth->value() / aspectRatio ;
m_page->intHeight->setValue(newHeight);
}
void DlgAnimationRenderer::slotLockAspectRatioDimensionsHeight(int height)
{
Q_UNUSED(height);
float aspectRatio = (float)m_image->width() / (float)m_image->height();
// update width here
float newWidth = aspectRatio * m_page->intHeight->value();
m_page->intWidth->setValue(newWidth);
}
diff --git a/plugins/extensions/animationrenderer/video_saver.cpp b/plugins/extensions/animationrenderer/video_saver.cpp
index 7c0d8f7840..2394d54e2b 100644
--- a/plugins/extensions/animationrenderer/video_saver.cpp
+++ b/plugins/extensions/animationrenderer/video_saver.cpp
@@ -1,342 +1,331 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 "video_saver.h"
#include <QDebug>
#include <QFileInfo>
#include <KisDocument.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoResourcePaths.h>
#include <kis_image.h>
#include <kis_image_animation_interface.h>
#include <kis_time_range.h>
#include "kis_config.h"
#include "KisAnimationRenderingOptions.h"
#include <QFileSystemWatcher>
#include <QProcess>
#include <QProgressDialog>
#include <QEventLoop>
#include <QTemporaryFile>
#include <QTemporaryDir>
#include <QTime>
#include "KisPart.h"
class KisFFMpegProgressWatcher : public QObject {
Q_OBJECT
public:
KisFFMpegProgressWatcher(QFile &progressFile, int totalFrames)
: m_progressFile(progressFile),
m_totalFrames(totalFrames)
{
connect(&m_progressWatcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged()));
m_progressWatcher.addPath(m_progressFile.fileName());
}
private Q_SLOTS:
void slotFileChanged() {
int currentFrame = -1;
bool isEnded = false;
while(!m_progressFile.atEnd()) {
QString line = QString(m_progressFile.readLine()).remove(QChar('\n'));
QStringList var = line.split("=");
if (var[0] == "frame") {
currentFrame = var[1].toInt();
} else if (var[0] == "progress") {
isEnded = var[1] == "end";
}
}
if (isEnded) {
emit sigProgressChanged(100);
emit sigProcessingFinished();
} else {
emit sigProgressChanged(100 * currentFrame / m_totalFrames);
}
}
Q_SIGNALS:
void sigProgressChanged(int percent);
void sigProcessingFinished();
private:
QFileSystemWatcher m_progressWatcher;
QFile &m_progressFile;
int m_totalFrames;
};
class KisFFMpegRunner
{
public:
KisFFMpegRunner(const QString &ffmpegPath)
: m_cancelled(false),
m_ffmpegPath(ffmpegPath) {}
public:
- KisImageBuilder_Result runFFMpeg(const QStringList &specialArgs,
+ KisImportExportErrorCode runFFMpeg(const QStringList &specialArgs,
const QString &actionName,
const QString &logPath,
int totalFrames)
{
dbgFile << "runFFMpeg: specialArgs" << specialArgs
<< "actionName" << actionName
<< "logPath" << logPath
<< "totalFrames" << totalFrames;
QTemporaryFile progressFile(QDir::tempPath() + QDir::separator() + "KritaFFmpegProgress.XXXXXX");
progressFile.open();
m_process.setStandardOutputFile(logPath);
m_process.setProcessChannelMode(QProcess::MergedChannels);
QStringList args;
args << "-v" << "debug"
<< "-nostdin"
<< "-progress" << progressFile.fileName()
<< specialArgs;
qDebug() << "\t" << m_ffmpegPath << args.join(" ");
m_cancelled = false;
m_process.start(m_ffmpegPath, args);
return waitForFFMpegProcess(actionName, progressFile, m_process, totalFrames);
}
void cancel() {
m_cancelled = true;
m_process.kill();
}
private:
- KisImageBuilder_Result waitForFFMpegProcess(const QString &message,
+ KisImportExportErrorCode waitForFFMpegProcess(const QString &message,
QFile &progressFile,
QProcess &ffmpegProcess,
int totalFrames)
{
KisFFMpegProgressWatcher watcher(progressFile, totalFrames);
QProgressDialog progress(message, "", 0, 0, KisPart::instance()->currentMainwindow());
progress.setWindowModality(Qt::ApplicationModal);
progress.setCancelButton(0);
progress.setMinimumDuration(0);
progress.setValue(0);
progress.setRange(0, 100);
QEventLoop loop;
loop.connect(&watcher, SIGNAL(sigProcessingFinished()), SLOT(quit()));
loop.connect(&ffmpegProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(quit()));
loop.connect(&ffmpegProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(quit()));
loop.connect(&watcher, SIGNAL(sigProgressChanged(int)), &progress, SLOT(setValue(int)));
if (ffmpegProcess.state() != QProcess::NotRunning) {
loop.exec();
// wait for some erroneous case
ffmpegProcess.waitForFinished(5000);
}
- KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK;
+ KisImportExportErrorCode retval = ImportExportCodes::OK;
if (ffmpegProcess.state() != QProcess::NotRunning) {
// sorry...
ffmpegProcess.kill();
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
} else if (m_cancelled) {
- retval = KisImageBuilder_RESULT_CANCEL;
+ retval = ImportExportCodes::Cancelled;
} else if (ffmpegProcess.exitCode()) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
}
return retval;
}
private:
QProcess m_process;
bool m_cancelled;
QString m_ffmpegPath;
};
VideoSaver::VideoSaver(KisDocument *doc, bool batchMode)
: m_image(doc->image())
, m_doc(doc)
, m_batchMode(batchMode)
{
}
VideoSaver::~VideoSaver()
{
}
KisImageSP VideoSaver::image()
{
return m_image;
}
-KisImageBuilder_Result VideoSaver::encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options)
+KisImportExportErrorCode VideoSaver::encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options)
{
if (!QFileInfo(options.ffmpegPath).exists()) {
m_doc->setErrorMessage(i18n("ffmpeg could not be found at %1", options.ffmpegPath));
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::Failure;
}
- KisImageBuilder_Result result = KisImageBuilder_RESULT_OK;
+ KisImportExportErrorCode resultOuter = ImportExportCodes::OK;
KisImageAnimationInterface *animation = m_image->animationInterface();
const int sequenceNumberingOffset = options.sequenceStart;
const KisTimeRange clipRange(sequenceNumberingOffset + options.firstFrame,
sequenceNumberingOffset + options.lastFrame);
// export dimensions could be off a little bit, so the last force option tweaks the pixels for the export to work
const QString exportDimensions =
QString("scale=w=")
.append(QString::number(options.width))
.append(":h=")
.append(QString::number(options.height))
.append(":force_original_aspect_ratio=decrease");
const QString resultFile = options.resolveAbsoluteVideoFilePath();
const QDir videoDir(QFileInfo(resultFile).absolutePath());
const QFileInfo info(resultFile);
const QString suffix = info.suffix().toLower();
const QString palettePath = videoDir.filePath("palette.png");
const QStringList additionalOptionsList = options.customFFMpegOptions.split(' ', QString::SkipEmptyParts);
QScopedPointer<KisFFMpegRunner> runner(new KisFFMpegRunner(options.ffmpegPath));
if (suffix == "gif") {
{
QStringList args;
args << "-r" << QString::number(options.frameRate)
<< "-start_number" << QString::number(clipRange.start())
<< "-i" << savedFilesMask
<< "-vf" << "palettegen"
<< "-y" << palettePath;
- KisImageBuilder_Result result =
+ KisImportExportErrorCode result =
runner->runFFMpeg(args, i18n("Fetching palette..."),
videoDir.filePath("log_generate_palette_gif.log"),
clipRange.duration());
- if (result != KisImageBuilder_RESULT_OK) {
+ if (!result.isOk()) {
return result;
}
}
{
QStringList args;
args << "-r" << QString::number(options.frameRate)
<< "-start_number" << QString::number(clipRange.start())
<< "-i" << savedFilesMask
<< "-i" << palettePath
<< "-lavfi" << "[0:v][1:v] paletteuse"
<< "-y" << resultFile;
// if we are exporting out at a different image size, we apply scaling filter
if (m_image->width() != options.width || m_image->height() != options.height) {
args << "-vf" << exportDimensions;
}
dbgFile << "savedFilesMask" << savedFilesMask << "start" << QString::number(clipRange.start()) << "duration" << clipRange.duration();
- KisImageBuilder_Result result =
+ KisImportExportErrorCode result =
runner->runFFMpeg(args, i18n("Encoding frames..."),
videoDir.filePath("log_encode_gif.log"),
clipRange.duration());
- if (result != KisImageBuilder_RESULT_OK) {
+ if (!result.isOk()) {
return result;
}
}
} else {
QStringList args;
args << "-r" << QString::number(options.frameRate)
<< "-start_number" << QString::number(clipRange.start())
<< "-i" << savedFilesMask;
QFileInfo audioFileInfo = animation->audioChannelFileName();
if (options.includeAudio && audioFileInfo.exists()) {
const int msecStart = clipRange.start() * 1000 / animation->framerate();
const int msecDuration = clipRange.duration() * 1000 / animation->framerate();
const QTime startTime = QTime::fromMSecsSinceStartOfDay(msecStart);
const QTime durationTime = QTime::fromMSecsSinceStartOfDay(msecDuration);
const QString ffmpegTimeFormat("H:m:s.zzz");
args << "-ss" << startTime.toString(ffmpegTimeFormat);
args << "-t" << durationTime.toString(ffmpegTimeFormat);
args << "-i" << audioFileInfo.absoluteFilePath();
}
// if we are exporting out at a different image size, we apply scaling filter
// export options HAVE to go after input options, so make sure this is after the audio import
if (m_image->width() != options.width || m_image->height() != options.height) {
args << "-vf" << exportDimensions;
}
args << additionalOptionsList
<< "-y" << resultFile;
- result = runner->runFFMpeg(args, i18n("Encoding frames..."),
+ resultOuter = runner->runFFMpeg(args, i18n("Encoding frames..."),
videoDir.filePath("log_encode.log"),
clipRange.duration());
}
- return result;
+ return resultOuter;
}
-KisImportExportFilter::ConversionStatus VideoSaver::convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode)
+KisImportExportErrorCode VideoSaver::convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode)
{
VideoSaver videoSaver(document, batchMode);
- KisImageBuilder_Result res = videoSaver.encode(savedFilesMask, options);
-
- if (res == KisImageBuilder_RESULT_OK) {
- return KisImportExportFilter::OK;
-
- } else if (res == KisImageBuilder_RESULT_CANCEL) {
- return KisImportExportFilter::ProgressCancelled;
-
- }else {
- document->setErrorMessage(i18n("FFMpeg failed to convert the image sequence. Check the logfile in your output directory for more information."));
- }
-
- return KisImportExportFilter::InternalError;
+ KisImportExportErrorCode res = videoSaver.encode(savedFilesMask, options);
+ return res;
}
#include "video_saver.moc"
diff --git a/plugins/extensions/animationrenderer/video_saver.h b/plugins/extensions/animationrenderer/video_saver.h
index 9f2fd15751..e9cb05e217 100644
--- a/plugins/extensions/animationrenderer/video_saver.h
+++ b/plugins/extensions/animationrenderer/video_saver.h
@@ -1,73 +1,73 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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 VIDEO_SAVER_H_
#define VIDEO_SAVER_H_
#include <QObject>
#include "kis_types.h"
#include "KisImageBuilderResult.h"
#include <KisImportExportFilter.h>
class KisFFMpegRunner;
/* The KisImageBuilder_Result definitions come from kis_png_converter.h here */
class KisDocument;
class KisAnimationRenderingOptions;
class VideoSaver : public QObject {
Q_OBJECT
public:
/**
* @brief VideoSaver
* This is the object that takes an animation document and config and tells ffmpeg
* to render it. Log files are generated here too.
* @param doc the document to use for rendering.
* @param ffmpegPath the path to the ffmpeg executable.
* @param batchMode whether Krita is in batchmde and we can thus not show gui widgets.
*/
VideoSaver(KisDocument* doc, bool batchMode);
~VideoSaver() override;
/**
* @brief image
* @return get the image used by this exporter.
*/
KisImageSP image();
/**
* @brief encode the main encoding function.
* This in turn calls runFFMpeg, which is a private function inside this class.
* @param filename the filename to which to render the animation.
* @param configuration the configuration
* @return whether it is successful or had another failure.
*/
- KisImageBuilder_Result encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options);
+ KisImportExportErrorCode encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options);
- static KisImportExportFilter::ConversionStatus convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode);
+ static KisImportExportErrorCode convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode);
private:
KisImageSP m_image;
KisDocument* m_doc;
bool m_batchMode;
};
#endif
diff --git a/plugins/filters/CMakeLists.txt b/plugins/filters/CMakeLists.txt
index b8fbbf50ef..9da7efe518 100644
--- a/plugins/filters/CMakeLists.txt
+++ b/plugins/filters/CMakeLists.txt
@@ -1,30 +1,32 @@
add_subdirectory( tests )
add_subdirectory( blur )
add_subdirectory( colors )
add_subdirectory( colorsfilters )
add_subdirectory( convolutionfilters )
+add_subdirectory( guassianhighpass)
add_subdirectory( embossfilter )
add_subdirectory( example )
add_subdirectory( fastcolortransfer )
add_subdirectory( imageenhancement )
add_subdirectory( noisefilter )
add_subdirectory( oilpaintfilter )
add_subdirectory( pixelizefilter )
add_subdirectory( raindropsfilter )
add_subdirectory( randompickfilter )
add_subdirectory( roundcorners )
add_subdirectory( smalltilesfilter )
add_subdirectory( unsharp )
add_subdirectory( wavefilter )
add_subdirectory( levelfilter )
add_subdirectory( dodgeburn )
add_subdirectory( phongbumpmap )
add_subdirectory( posterize )
add_subdirectory( indexcolors )
add_subdirectory( normalize )
add_subdirectory( gradientmap )
add_subdirectory( threshold )
add_subdirectory( halftone )
add_subdirectory( edgedetection )
add_subdirectory( convertheightnormalmap )
add_subdirectory( asccdl )
+add_subdirectory( palettize )
diff --git a/plugins/filters/dodgeburn/DodgeBurn.cpp b/plugins/filters/dodgeburn/DodgeBurn.cpp
index 322f767ba4..f5526f255c 100644
--- a/plugins/filters/dodgeburn/DodgeBurn.cpp
+++ b/plugins/filters/dodgeburn/DodgeBurn.cpp
@@ -1,112 +1,113 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "DodgeBurn.h"
#include <filter/kis_filter_category_ids.h>
#include <filter/kis_color_transformation_configuration.h>
#include <kis_paint_device.h>
#include "ui_DodgeBurnConfigurationBaseWidget.h"
KisFilterDodgeBurn::KisFilterDodgeBurn(const QString& id, const QString& prefix, const QString& name ) : KisColorTransformationFilter(KoID(id, name), FiltersCategoryAdjustId, name), m_prefix(prefix)
{
setColorSpaceIndependence(FULLY_INDEPENDENT);
setSupportsPainting(true);
}
KisConfigWidget * KisFilterDodgeBurn::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const
{
Q_UNUSED(dev);
return new KisDodgeBurnConfigWidget(parent, id());
}
KoColorTransformation* KisFilterDodgeBurn::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const
{
QHash<QString, QVariant> params;
QString suffix = "Midtones";
if (config) {
params["exposure"] = config->getDouble("exposure", 0.5);
int type = config->getInt("type", KisFilterDodgeBurn::MIDTONES);
switch(type)
{
case KisFilterDodgeBurn::HIGHLIGHTS:
suffix = "Highlights";
break;
case KisFilterDodgeBurn::SHADOWS:
suffix = "Shadows";
break;
default:
break;
}
}
return cs->createColorTransformation(m_prefix + suffix, params);
}
KisDodgeBurnConfigWidget::KisDodgeBurnConfigWidget(QWidget * parent, const QString& id) : KisConfigWidget(parent), m_id(id)
{
m_page = new Ui_DodgeBurnConfigurationBaseWidget();
m_page->setupUi(this);
connect(m_page->radioButtonHighlights, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->radioButtonMidtones, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->radioButtonShadows, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->sliderExposure, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
}
KisDodgeBurnConfigWidget::~KisDodgeBurnConfigWidget()
{
delete m_page;
}
KisPropertiesConfigurationSP KisDodgeBurnConfigWidget::configuration() const
{
KisColorTransformationConfigurationSP c = new KisColorTransformationConfiguration(m_id, 0);
int type = 0;
if(m_page->radioButtonHighlights->isChecked())
{
type = KisFilterDodgeBurn::HIGHLIGHTS;
} else if(m_page->radioButtonShadows->isChecked())
{
type = KisFilterDodgeBurn::SHADOWS;
} else {
type = KisFilterDodgeBurn::MIDTONES;
}
c->setProperty("type", type);
c->setProperty("exposure", m_page->sliderExposure->value() / 100.0);
return c;
}
void KisDodgeBurnConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
int type = config->getInt("type", KisFilterDodgeBurn::MIDTONES);
switch(type)
{
case KisFilterDodgeBurn::HIGHLIGHTS:
m_page->radioButtonHighlights->setChecked(true);
break;
case KisFilterDodgeBurn::SHADOWS:
m_page->radioButtonShadows->setChecked(true);
break;
default:
case KisFilterDodgeBurn::MIDTONES:
m_page->radioButtonMidtones->setChecked(true);
break;
}
m_page->sliderExposure->setValue(config->getDouble("exposure", 0.5) * 100);
}
diff --git a/plugins/filters/dodgeburn/DodgeBurn.h b/plugins/filters/dodgeburn/DodgeBurn.h
index 06dc1fa757..b7123ca13c 100644
--- a/plugins/filters/dodgeburn/DodgeBurn.h
+++ b/plugins/filters/dodgeburn/DodgeBurn.h
@@ -1,58 +1,59 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 DODGE_BURN_H
#define DODGE_BURN_H
#include "filter/kis_color_transformation_filter.h"
#include "kis_config_widget.h"
class KisFilterDodgeBurn : public KisColorTransformationFilter
{
public:
enum Type {
SHADOWS,
MIDTONES,
HIGHLIGHTS
};
public:
KisFilterDodgeBurn(const QString& id, const QString& prefix, const QString& name );
public:
KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override;
KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override;
private:
QString m_prefix;
};
class Ui_DodgeBurnConfigurationBaseWidget;
class KisDodgeBurnConfigWidget : public KisConfigWidget
{
public:
KisDodgeBurnConfigWidget(QWidget * parent, const QString& id);
~KisDodgeBurnConfigWidget() override;
KisPropertiesConfigurationSP configuration() const override;
void setConfiguration(const KisPropertiesConfigurationSP config) override;
QString m_id;
Ui_DodgeBurnConfigurationBaseWidget * m_page;
};
#endif
diff --git a/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp b/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp
index 4f1dc7d695..942ad93fbe 100644
--- a/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp
+++ b/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp
@@ -1,38 +1,39 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "DodgeBurnPlugin.h"
#include <kpluginfactory.h>
#include <filter/kis_filter_registry.h>
#include "DodgeBurn.h"
K_PLUGIN_FACTORY_WITH_JSON(DodgeBurnPluginFactory, "kritadodgeburn.json", registerPlugin<DodgeBurnPlugin>();)
DodgeBurnPlugin::DodgeBurnPlugin(QObject *parent, const QVariantList &)
{
Q_UNUSED(parent);
KisFilterRegistry::instance()->add(new KisFilterDodgeBurn("dodge", "Dodge", i18n("Dodge")));
KisFilterRegistry::instance()->add(new KisFilterDodgeBurn("burn", "Burn", i18n("Burn")));
}
DodgeBurnPlugin::~DodgeBurnPlugin()
{
}
#include "DodgeBurnPlugin.moc"
diff --git a/plugins/filters/dodgeburn/DodgeBurnPlugin.h b/plugins/filters/dodgeburn/DodgeBurnPlugin.h
index b039ae9de0..2379e37145 100644
--- a/plugins/filters/dodgeburn/DodgeBurnPlugin.h
+++ b/plugins/filters/dodgeburn/DodgeBurnPlugin.h
@@ -1,32 +1,33 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _DODGE_BURN_PLUGIN_H_
#define _DODGE_BURN_PLUGIN_H_
#include <QObject>
#include <QVariant>
class DodgeBurnPlugin : public QObject
{
Q_OBJECT
public:
DodgeBurnPlugin(QObject *parent, const QVariantList &);
~DodgeBurnPlugin() override;
};
#endif
diff --git a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp
index 1e2049502b..b99a1b89cf 100644
--- a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp
+++ b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp
@@ -1,144 +1,144 @@
/*
* This file is part of Krita
*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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_wdg_fastcolortransfer.h"
#include <QLayout>
#include <KisImportExportFilter.h>
#include <KisImportExportManager.h>
#include <filter/kis_filter_configuration.h>
#include <KisDocument.h>
#include <KisPart.h>
#include <kis_image.h>
#include <kis_iterator_ng.h>
#include <kis_paint_device.h>
#include <kundo2command.h>
#include <KoColorSpaceRegistry.h>
#include <kis_file_name_requester.h>
#include "ui_wdgfastcolortransfer.h"
KisWdgFastColorTransfer::KisWdgFastColorTransfer(QWidget * parent) : KisConfigWidget(parent)
{
m_widget = new Ui_WdgFastColorTransfer();
m_widget->setupUi(this);
m_widget->fileNameURLRequester->setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import));
connect(m_widget->fileNameURLRequester, SIGNAL(textChanged(QString)), this, SIGNAL(sigConfigurationItemChanged()));
}
KisWdgFastColorTransfer::~KisWdgFastColorTransfer()
{
delete m_widget;
}
void KisWdgFastColorTransfer::setConfiguration(const KisPropertiesConfigurationSP config)
{
QVariant value;
if (config->getProperty("filename", value)) {
widget()->fileNameURLRequester->setFileName(value.toString());
}
}
KisPropertiesConfigurationSP KisWdgFastColorTransfer::configuration() const
{
KisFilterConfigurationSP config = new KisFilterConfiguration("colortransfer", 1);
QString fileName = this->widget()->fileNameURLRequester->fileName();
if (fileName.isEmpty()) return config;
KisPaintDeviceSP ref;
dbgPlugins << "Use as reference file : " << fileName;
KisDocument *d = KisPart::instance()->createDocument();
KisImportExportManager manager(d);
- KisImportExportFilter::ConversionStatus status = manager.importDocument(fileName, QString());
- dbgPlugins << "import returned status" << status;
+ KisImportExportErrorCode status = manager.importDocument(fileName, QString());
+ dbgPlugins << "import returned status" << status.errorMessage();
KisImageWSP importedImage = d->image();
if (importedImage) {
ref = importedImage->projection();
}
if (!ref) {
dbgPlugins << "No reference image was specified.";
delete d;
return config;
}
// Convert ref to LAB
const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16();
if (!labCS) {
dbgPlugins << "The LAB colorspace is not available.";
delete d;
return config;
}
dbgPlugins << "convert ref to lab";
ref->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
// Compute the means and sigmas of ref
double meanL_ref = 0., meanA_ref = 0., meanB_ref = 0.;
double sigmaL_ref = 0., sigmaA_ref = 0., sigmaB_ref = 0.;
KisSequentialConstIterator refIt(ref, importedImage->bounds());
while (refIt.nextPixel()) {
const quint16* data = reinterpret_cast<const quint16*>(refIt.oldRawData());
quint32 L = data[0];
quint32 A = data[1];
quint32 B = data[2];
meanL_ref += L;
meanA_ref += A;
meanB_ref += B;
sigmaL_ref += L * L;
sigmaA_ref += A * A;
sigmaB_ref += B * B;
}
double totalSize = 1. / (importedImage->width() * importedImage->height());
meanL_ref *= totalSize;
meanA_ref *= totalSize;
meanB_ref *= totalSize;
sigmaL_ref *= totalSize;
sigmaA_ref *= totalSize;
sigmaB_ref *= totalSize;
dbgPlugins << totalSize << "" << meanL_ref << "" << meanA_ref << "" << meanB_ref << "" << sigmaL_ref << "" << sigmaA_ref << "" << sigmaB_ref;
config->setProperty("filename", fileName);
config->setProperty("meanL", meanL_ref);
config->setProperty("meanA", meanA_ref);
config->setProperty("meanB", meanB_ref);
config->setProperty("sigmaL", sigmaL_ref);
config->setProperty("sigmaA", sigmaA_ref);
config->setProperty("sigmaB", sigmaB_ref);
delete d;
return config;
}
diff --git a/plugins/filters/gradientmap/gradientmap.cpp b/plugins/filters/gradientmap/gradientmap.cpp
index a2d504c979..eaee269cfb 100644
--- a/plugins/filters/gradientmap/gradientmap.cpp
+++ b/plugins/filters/gradientmap/gradientmap.cpp
@@ -1,125 +1,134 @@
/*
* This file is part of the KDE project
*
* Copyright (c) 2016 Spencer Brown <sbrown655@gmail.com>
* 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 "QObject"
#include "gradientmap.h"
#include <kpluginfactory.h>
#include <filter/kis_filter_registry.h>
#include "krita_filter_gradient_map.h"
#include "KoResourceServerProvider.h"
#include "kis_config_widget.h"
#include <KoAbstractGradient.h>
#include <KoStopGradient.h>
#include <KoGradientBackground.h>
#include <KoResourceServerAdapter.h>
K_PLUGIN_FACTORY_WITH_JSON(KritaGradientMapFactory, "kritagradientmap.json", registerPlugin<KritaGradientMap>();)
KritaGradientMapConfigWidget::KritaGradientMapConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WindowFlags f)
: KisConfigWidget(parent, f)
{
Q_UNUSED(dev)
m_page = new WdgGradientMap(this);
QHBoxLayout *l = new QHBoxLayout(this);
Q_CHECK_PTR(l);
l->addWidget(m_page);
l->setContentsMargins(0, 0, 0, 0);
KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance();
QSharedPointer<KoAbstractResourceServerAdapter> gradientResourceAdapter(
new KoResourceServerAdapter<KoAbstractGradient>(serverProvider->gradientServer()));
m_gradientChangedCompressor = new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE);
m_gradientPopUp = new KoResourcePopupAction(gradientResourceAdapter,
m_page->btnGradientChooser);
m_activeGradient = KoStopGradient::fromQGradient(dynamic_cast<KoAbstractGradient*>(gradientResourceAdapter->resources().first())->toQGradient());
m_page->gradientEditor->setGradient(m_activeGradient);
m_page->gradientEditor->setCompactMode(true);
m_page->gradientEditor->setEnabled(true);
m_page->btnGradientChooser->setDefaultAction(m_gradientPopUp);
m_page->btnGradientChooser->setPopupMode(QToolButton::InstantPopup);
connect(m_gradientPopUp, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground>)), this, SLOT(setAbstractGradientToEditor()));
connect(m_page->gradientEditor, SIGNAL(sigGradientChanged()), m_gradientChangedCompressor, SLOT(start()));
connect(m_gradientChangedCompressor, SIGNAL(timeout()), this, SIGNAL(sigConfigurationItemChanged()));
+
+ QObject::connect(m_page->ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged);
+ QObject::connect(m_page->ditherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged);
}
KritaGradientMapConfigWidget::~KritaGradientMapConfigWidget()
{
delete m_page;
}
void KritaGradientMapConfigWidget::setAbstractGradientToEditor()
{
QSharedPointer<KoGradientBackground> bg =
qSharedPointerDynamicCast<KoGradientBackground>(
m_gradientPopUp->currentBackground());
m_activeGradient = KoStopGradient::fromQGradient(bg->gradient());
m_page->gradientEditor->setGradient(m_activeGradient);
}
KisPropertiesConfigurationSP KritaGradientMapConfigWidget::configuration() const
{
KisFilterConfigurationSP cfg = new KisFilterConfiguration("gradientmap", 2);
if (m_activeGradient) {
QDomDocument doc;
QDomElement elt = doc.createElement("gradient");
m_activeGradient->toXML(doc, elt);
doc.appendChild(elt);
cfg->setProperty("gradientXML", doc.toString());
}
+ cfg->setProperty("ditherEnabled", m_page->ditherGroupBox->isChecked());
+ m_page->ditherWidget->configuration(*cfg, "dither/");
+
return cfg;
}
//-----------------------------
void KritaGradientMapConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
Q_ASSERT(config);
QDomDocument doc;
if (config->hasProperty("gradientXML")) {
doc.setContent(config->getString("gradientXML", ""));
KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement());
if (gradient.stops().size()>0) {
m_activeGradient->setStops(gradient.stops());
}
}
+
+ m_page->ditherGroupBox->setChecked(config->getBool("ditherEnabled"));
+ m_page->ditherWidget->setConfiguration(*config, "dither/");
}
void KritaGradientMapConfigWidget::setView(KisViewManager *view)
{
Q_UNUSED(view)
}
//------------------------------
KritaGradientMap::KritaGradientMap(QObject *parent, const QVariantList &)
: QObject(parent)
{
KisFilterRegistry::instance()->add(KisFilterSP(new KritaFilterGradientMap()));
}
KritaGradientMap::~KritaGradientMap()
{
}
//-----------------------------
#include "gradientmap.moc"
diff --git a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp
index ae5dc8f7c0..c66ba1a80d 100644
--- a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp
+++ b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp
@@ -1,104 +1,126 @@
/*
* This file is part of the KDE project
*
* Copyright (c) 2016 Spencer Brown <sbrown655@gmail.com>
* 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 "krita_filter_gradient_map.h"
#include <KoColorSpace.h>
#include <KoColor.h>
#include <kis_paint_device.h>
#include <kis_global.h>
#include <kis_types.h>
#include <filter/kis_filter_category_ids.h>
#include "kis_config_widget.h"
#include <KoResourceServerProvider.h>
#include <KoResourceServer.h>
#include <KoAbstractGradient.h>
#include <KoStopGradient.h>
#include <KoColorSet.h>
#include "gradientmap.h"
+#include <KisDitherUtil.h>
#include <KisSequentialIteratorProgress.h>
KritaFilterGradientMap::KritaFilterGradientMap() : KisFilter(id(), FiltersCategoryMapId, i18n("&Gradient Map..."))
{
setColorSpaceIndependence(FULLY_INDEPENDENT);
setShowConfigurationWidget(true);
setSupportsLevelOfDetail(true);
setSupportsPainting(true);
setSupportsAdjustmentLayers(true);
setSupportsThreading(true);
}
void KritaFilterGradientMap::processImpl(KisPaintDeviceSP device,
const QRect& applyRect,
const KisFilterConfigurationSP config,
KoUpdater *progressUpdater) const
{
Q_ASSERT(!device.isNull());
QDomDocument doc;
if (config->version()==1) {
QDomElement elt = doc.createElement("gradient");
KoAbstractGradient *gradientAb = KoResourceServerProvider::instance()->gradientServer()->resourceByName(config->getString("gradientName"));
if (!gradientAb) {
qWarning() << "Could not find gradient" << config->getString("gradientName");
}
gradientAb = KoResourceServerProvider::instance()->gradientServer()->resources().first();
KoStopGradient::fromQGradient(gradientAb->toQGradient())->toXML(doc, elt);
doc.appendChild(elt);
} else {
doc.setContent(config->getString("gradientXML", ""));
}
KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement());
+ const bool ditherEnabled = config->getBool("ditherEnabled");
+ KisDitherUtil ditherUtil;
+ if (ditherEnabled) ditherUtil.setConfiguration(*config, "dither/");
+
KoColor outColor(Qt::white, device->colorSpace());
KisSequentialIteratorProgress it(device, applyRect, progressUpdater);
- quint8 grey;
+ qreal grey;
const int pixelSize = device->colorSpace()->pixelSize();
while (it.nextPixel()) {
- grey = device->colorSpace()->intensity8(it.oldRawData());
- gradient.colorAt(outColor,(qreal)grey/255);
+ grey = qreal(device->colorSpace()->intensity8(it.oldRawData())) / 255;
+ if (ditherEnabled) {
+ KoGradientStop leftStop, rightStop;
+ if (!gradient.stopsAt(leftStop, rightStop, grey)) continue;
+ qreal localT = (grey - leftStop.first) / (rightStop.first - leftStop.first);
+ if (localT < ditherUtil.threshold(QPoint(it.x(), it.y()))) {
+ outColor = leftStop.second;
+ }
+ else {
+ outColor = rightStop.second;
+ }
+ }
+ else {
+ gradient.colorAt(outColor, grey);
+ }
outColor.setOpacity(qMin(KoColor(it.oldRawData(), device->colorSpace()).opacityF(), outColor.opacityF()));
outColor.convertTo(device->colorSpace());
memcpy(it.rawData(), outColor.data(), pixelSize);
}
}
KisFilterConfigurationSP KritaFilterGradientMap::factoryConfiguration() const
{
KisFilterConfigurationSP config = new KisFilterConfiguration("gradientmap", 2);
KoAbstractGradient *gradient = KoResourceServerProvider::instance()->gradientServer()->resources().first();
KoStopGradient stopGradient;
stopGradient.fromQGradient(gradient->toQGradient());
QDomDocument doc;
QDomElement elt = doc.createElement("gradient");
stopGradient.toXML(doc, elt);
doc.appendChild(elt);
config->setProperty("gradientXML", doc.toString());
+
+ config->setProperty("ditherEnabled", false);
+ KisDitherWidget::factoryConfiguration(*config, "dither/");
+
return config;
}
KisConfigWidget * KritaFilterGradientMap::createConfigurationWidget(QWidget * parent, const KisPaintDeviceSP dev, bool) const
{
return new KritaGradientMapConfigWidget(parent, dev);
}
diff --git a/plugins/filters/gradientmap/wdg_gradientmap.ui b/plugins/filters/gradientmap/wdg_gradientmap.ui
index 16bf69656e..f9b7c59850 100644
--- a/plugins/filters/gradientmap/wdg_gradientmap.ui
+++ b/plugins/filters/gradientmap/wdg_gradientmap.ui
@@ -1,76 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgGradientMap</class>
<widget class="QWidget" name="WdgGradientMap">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>361</width>
<height>341</height>
</rect>
</property>
<property name="windowTitle">
<string>Gradient Map</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="KoColorPopupButton" name="btnGradientChooser">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="KisStopGradientEditor" name="gradientEditor" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
+ <item>
+ <widget class="QGroupBox" name="ditherGroupBox">
+ <property name="title">
+ <string>Dither</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0" colspan="2">
+ <widget class="KisDitherWidget" name="ditherWidget" native="true"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KoColorPopupButton</class>
<extends>QToolButton</extends>
<header>KoColorPopupButton.h</header>
</customwidget>
<customwidget>
<class>KisStopGradientEditor</class>
<extends>QWidget</extends>
<header>kis_stopgradient_editor.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>KisDitherWidget</class>
+ <extends>QWidget</extends>
+ <header location="global">KisDitherWidget.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/filters/guassianhighpass/CMakeLists.txt b/plugins/filters/guassianhighpass/CMakeLists.txt
new file mode 100644
index 0000000000..a6af1c8e11
--- /dev/null
+++ b/plugins/filters/guassianhighpass/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(kritahighpassfilter_SOURCES
+ guassianhighpass.cpp
+ wdg_guassianhighpass.cpp
+ guassianhighpass_filter.cpp
+ )
+
+ki18n_wrap_ui(kritahighpassfilter_SOURCES
+ wdgguassianhighpass.ui
+ )
+
+add_library(kritaguassianhighpassfilter MODULE ${kritahighpassfilter_SOURCES})
+
+target_link_libraries(kritaguassianhighpassfilter kritaui)
+
+install(TARGETS kritaguassianhighpassfilter DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/filters/guassianhighpass/guassianhighpass.cpp
similarity index 54%
copy from plugins/impex/ppm/tests/kis_ppm_test.cpp
copy to plugins/filters/guassianhighpass/guassianhighpass.cpp
index 6aa3859d0a..4a7030f4d2 100644
--- a/plugins/impex/ppm/tests/kis_ppm_test.cpp
+++ b/plugins/filters/guassianhighpass/guassianhighpass.cpp
@@ -1,39 +1,41 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Miguel Lopez <reptillia39@live.com>
*
* 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_ppm_test.h"
+#include "guassianhighpass.h"
+#include <kpluginfactory.h>
+#include "guassianhighpass_filter.h"
-#include <QTest>
-#include <QCoreApplication>
+#include <filter/kis_filter_registry.h>
-#include <sdk/tests/kistest.h>
+K_PLUGIN_FACTORY_WITH_JSON(GuassianHighPassPluginFactory, "kritaguassianhighpassfilter.json", registerPlugin<GuassianHighPassPlugin>();)
-#include "filestest.h"
+GuassianHighPassPlugin::GuassianHighPassPlugin(QObject *parent, const QVariantList &)
+ : QObject(parent)
+{
+ KisFilterRegistry::instance()->add(new KisGuassianHighPassFilter());
-#ifndef FILES_DATA_DIR
-#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita"
-#endif
+}
-void KisPPMTest::testFiles()
+GuassianHighPassPlugin::~GuassianHighPassPlugin()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1);
}
-KISTEST_MAIN(KisPPMTest)
-
+#include "guassianhighpass.moc"
diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.h b/plugins/filters/guassianhighpass/guassianhighpass.h
similarity index 67%
copy from plugins/impex/libkra/tests/kis_kra_loader_test.h
copy to plugins/filters/guassianhighpass/guassianhighpass.h
index 88da2d5448..636139759d 100644
--- a/plugins/impex/libkra/tests/kis_kra_loader_test.h
+++ b/plugins/filters/guassianhighpass/guassianhighpass.h
@@ -1,37 +1,35 @@
/*
- * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Miguel Lopez <reptillia39@live.com>
*
* 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_KRA_LOADER_TEST_H
-#define KIS_KRA_LOADER_TEST_H
+#ifndef _GUASSIANHIGHPASS_PLUGIN_H_
+#define _GUASSIANHIGHPASS_PLUGIN_H_
-#include <QtTest>
+#include <QObject>
+#include <QVariant>
-class KisKraLoaderTest : public QObject
+class GuassianHighPassPlugin : public QObject
{
Q_OBJECT
-private Q_SLOTS:
- void initTestCase();
-
- void testLoading();
- void testObligeSingleChild();
- void testObligeSingleChildNonTranspPixel();
-
- void testLoadAnimated();
+public:
+ GuassianHighPassPlugin(QObject *parent, const QVariantList &);
+ ~GuassianHighPassPlugin() override;
};
#endif
diff --git a/plugins/filters/guassianhighpass/guassianhighpass_filter.cpp b/plugins/filters/guassianhighpass/guassianhighpass_filter.cpp
new file mode 100644
index 0000000000..c36f1e9b96
--- /dev/null
+++ b/plugins/filters/guassianhighpass/guassianhighpass_filter.cpp
@@ -0,0 +1,126 @@
+/*
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Miguel Lopez <reptillia39@live.com>
+ *
+ * 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 <compositeops/KoVcMultiArchBuildSupport.h> //MSVC requires that Vc come first
+#include "guassianhighpass_filter.h"
+#include <QBitArray>
+
+#include <KoColorSpace.h>
+#include <KoChannelInfo.h>
+#include <KoColor.h>
+#include <kis_painter.h>
+#include <kis_paint_device.h>
+#include <kis_paint_layer.h>
+#include <kis_group_layer.h>
+#include <KoCompositeOps.h>
+
+#include <kis_mask_generator.h>
+#include <kis_gaussian_kernel.h>
+#include <filter/kis_filter_category_ids.h>
+#include <filter/kis_filter_configuration.h>
+#include <kis_processing_information.h>
+#include <KoProgressUpdater.h>
+#include <KoUpdater.h>
+#include <KoMixColorsOp.h>
+#include <kis_paint_device.h>
+#include "kis_lod_transform.h"
+
+#include "wdg_guassianhighpass.h"
+#include "ui_wdgguassianhighpass.h"
+#include "KoColorSpaceTraits.h"
+#include <KisSequentialIteratorProgress.h>
+
+
+KisGuassianHighPassFilter::KisGuassianHighPassFilter() : KisFilter(id(), FiltersCategoryEdgeDetectionId, i18n("&Guassian High Pass..."))
+{
+ setSupportsPainting(true);
+ setSupportsAdjustmentLayers(true);
+ setSupportsThreading(true);
+ setSupportsLevelOfDetail(true);
+ setColorSpaceIndependence(FULLY_INDEPENDENT);
+}
+
+KisConfigWidget * KisGuassianHighPassFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool /* useForMasks */) const
+{
+ return new KisWdgGuassianHighPass(parent);
+}
+
+KisFilterConfigurationSP KisGuassianHighPassFilter::factoryConfiguration() const
+{
+ KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1);
+ config->setProperty("blurAmount", 1);
+ return config;
+}
+
+void KisGuassianHighPassFilter::processImpl(KisPaintDeviceSP device,
+ const QRect& applyRect,
+ const KisFilterConfigurationSP _config,
+ KoUpdater *
+ ) const
+{
+ QPointer<KoUpdater> convolutionUpdater = 0;
+
+ KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1);
+
+ QVariant value;
+ KisLodTransformScalar t(device);
+ const qreal blurAmount = t.scale(config->getProperty("blurAmount", value) ? value.toDouble() : 1.0);
+ QBitArray channelFlags = config->channelFlags();
+
+ const QRect gaussNeedRect = this->neededRect(applyRect, config, device->defaultBounds()->currentLevelOfDetail());
+
+ KisPaintDeviceSP blur = m_cachedPaintDevice.getDevice(device);
+ KisPainter::copyAreaOptimizedOldData(gaussNeedRect.topLeft(), device, blur, gaussNeedRect);
+ KisGaussianKernel::applyGaussian(blur, applyRect,
+ blurAmount, blurAmount,
+ channelFlags,
+ convolutionUpdater,
+ true); // make sure we cerate an internal transaction on temp device
+
+ KisPainter painter(device);
+ painter.setCompositeOp(blur->colorSpace()->compositeOp(COMPOSITE_GRAIN_EXTRACT));
+ painter.bitBlt(applyRect.topLeft(), blur, applyRect);
+ painter.end();
+
+ m_cachedPaintDevice.putDevice(blur);
+}
+
+
+QRect KisGuassianHighPassFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const
+{
+ KisLodTransformScalar t(lod);
+
+ QVariant value;
+
+ const int halfSize = config->getProperty("blurAmount", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5;
+
+ return rect.adjusted( -halfSize * 2, -halfSize * 2, halfSize * 2, halfSize * 2);
+}
+
+QRect KisGuassianHighPassFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP config, int lod) const
+{
+ KisLodTransformScalar t(lod);
+
+ QVariant value;
+
+ const int halfSize = config->getProperty("blurAmount", value) ? KisGaussianKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5;
+
+ return rect.adjusted( -halfSize, -halfSize, halfSize, halfSize);
+}
diff --git a/plugins/filters/guassianhighpass/guassianhighpass_filter.h b/plugins/filters/guassianhighpass/guassianhighpass_filter.h
new file mode 100644
index 0000000000..8b31c1408c
--- /dev/null
+++ b/plugins/filters/guassianhighpass/guassianhighpass_filter.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Miguel Lopez <reptillia39@live.com>
+ *
+ * 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_GUASSIANHIGHPASS_FILTER_H
+#define KIS_GUASSIANHIGHPASS_FILTER_H
+
+#include "filter/kis_filter.h"
+#include "kis_cached_paint_device.h"
+
+
+class KisGuassianHighPassFilter : public KisFilter
+{
+public:
+
+ KisGuassianHighPassFilter();
+
+ void processImpl(KisPaintDeviceSP device,
+ const QRect& applyRect,
+ const KisFilterConfigurationSP config,
+ KoUpdater *
+ ) const override;
+
+ static inline KoID id() {
+ return KoID("guassianhighpass", i18n("Guassian High Pass"));
+ }
+
+ KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override;
+ KisFilterConfigurationSP factoryConfiguration() const override;
+
+ QRect changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override;
+ QRect neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const override;
+
+private:
+ mutable KisCachedPaintDevice m_cachedPaintDevice;
+};
+
+#endif
diff --git a/plugins/filters/guassianhighpass/kritaguassianhighpassfilter.json b/plugins/filters/guassianhighpass/kritaguassianhighpassfilter.json
new file mode 100644
index 0000000000..27ad1e22ce
--- /dev/null
+++ b/plugins/filters/guassianhighpass/kritaguassianhighpassfilter.json
@@ -0,0 +1,9 @@
+{
+ "Id": "Edge Detection Filter",
+ "Type": "Service",
+ "X-KDE-Library": "kritaguassianhighpass",
+ "X-KDE-ServiceTypes": [
+ "Krita/Filter"
+ ],
+ "X-Krita-Version": "40"
+}
diff --git a/plugins/filters/guassianhighpass/wdg_guassianhighpass.cpp b/plugins/filters/guassianhighpass/wdg_guassianhighpass.cpp
new file mode 100644
index 0000000000..038409fb93
--- /dev/null
+++ b/plugins/filters/guassianhighpass/wdg_guassianhighpass.cpp
@@ -0,0 +1,57 @@
+/*
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Miguel Lopez <reptillia39@live.com>
+ *
+ * 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 "wdg_guassianhighpass.h"
+#include <QLayout>
+#include <QToolButton>
+
+#include <filter/kis_filter.h>
+#include <filter/kis_filter_configuration.h>
+#include <kis_processing_information.h>
+
+#include "ui_wdgguassianhighpass.h"
+
+KisWdgGuassianHighPass::KisWdgGuassianHighPass(QWidget * parent) : KisConfigWidget(parent)
+{
+ m_widget = new Ui_WdgGuassianHighPass();
+ m_widget->setupUi(this);
+
+ connect(widget()->doubleblurAmount, SIGNAL(valueChanged(double)), SIGNAL(sigConfigurationItemChanged()));
+}
+
+KisWdgGuassianHighPass::~KisWdgGuassianHighPass()
+{
+ delete m_widget;
+}
+
+void KisWdgGuassianHighPass::setConfiguration(const KisPropertiesConfigurationSP config)
+{
+ QVariant value;
+ widget()->doubleblurAmount->setValue((config->getProperty("blurAmount", value)) ? value.toDouble() : 1.0);
+}
+
+KisPropertiesConfigurationSP KisWdgGuassianHighPass::configuration() const
+{
+ KisFilterConfigurationSP config = new KisFilterConfiguration("guassianhighpass", 1);
+ config->setProperty("blurAmount", widget()->doubleblurAmount->value());
+ return config;
+}
+
+
diff --git a/plugins/impex/psd/psd_loader.h b/plugins/filters/guassianhighpass/wdg_guassianhighpass.h
similarity index 55%
copy from plugins/impex/psd/psd_loader.h
copy to plugins/filters/guassianhighpass/wdg_guassianhighpass.h
index 0618f803f5..52e7ea6eb0 100644
--- a/plugins/impex/psd/psd_loader.h
+++ b/plugins/filters/guassianhighpass/wdg_guassianhighpass.h
@@ -1,59 +1,43 @@
/*
- * Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Miguel Lopez <reptillia39@live.com>
*
* 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 _PSD_LOADER_H_
-#define _PSD_LOADER_H_
-
-#include <stdio.h>
-#include <QObject>
+#ifndef _KIS_WDG_GUASSIANHIGHPASS_H_
+#define _KIS_WDG_GUASSIANHIGHPASS_H_
-#include <QFileInfo>
+#include <kis_config_widget.h>
-#include "kis_types.h"
-#include <KisImageBuilderResult.h>
-class KisDocument;
-
-class PSDLoader : public QObject {
+class Ui_WdgGuassianHighPass;
+class KisWdgGuassianHighPass : public KisConfigWidget
+{
Q_OBJECT
-
public:
-
- PSDLoader(KisDocument *doc);
- ~PSDLoader() override;
-
- KisImageBuilder_Result buildImage(QIODevice *io);
-
- KisImageSP image();
-
-public Q_SLOTS:
-
- virtual void cancel();
-
+ KisWdgGuassianHighPass(QWidget * parent);
+ ~KisWdgGuassianHighPass() override;
+ inline const Ui_WdgGuassianHighPass* widget() const {
+ return m_widget;
+ }
+ void setConfiguration(const KisPropertiesConfigurationSP) override;
+ KisPropertiesConfigurationSP configuration() const override;
private:
-
- KisImageBuilder_Result decode(QIODevice *io);
-
-private:
-
- KisImageSP m_image;
- KisDocument *m_doc;
- bool m_stop;
+ Ui_WdgGuassianHighPass* m_widget;
};
#endif
diff --git a/plugins/filters/guassianhighpass/wdgguassianhighpass.ui b/plugins/filters/guassianhighpass/wdgguassianhighpass.ui
new file mode 100644
index 0000000000..cb46f41c83
--- /dev/null
+++ b/plugins/filters/guassianhighpass/wdgguassianhighpass.ui
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>WdgGuassianHighPass</class>
+ <widget class="QWidget" name="WdgGuassianHighPass">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>331</width>
+ <height>167</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="textLabel1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Radius:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" rowspan="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1">
+ <widget class="KisDoubleParseSpinBox" name="doubleblurAmount">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <double>250.000000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>21</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KisDoubleParseSpinBox</class>
+ <extends>QDoubleSpinBox</extends>
+ <header>kis_double_parse_spin_box.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/filters/palettize/CMakeLists.txt b/plugins/filters/palettize/CMakeLists.txt
new file mode 100644
index 0000000000..3aa4ae992d
--- /dev/null
+++ b/plugins/filters/palettize/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(kritapalettize_SOURCES palettize.cpp)
+ki18n_wrap_ui(kritapalettize_SOURCES palettize.ui)
+add_library(kritapalettize MODULE ${kritapalettize_SOURCES})
+target_link_libraries(kritapalettize kritaui)
+install(TARGETS kritapalettize DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/filters/palettize/kritapalettize.json b/plugins/filters/palettize/kritapalettize.json
new file mode 100644
index 0000000000..96142193ee
--- /dev/null
+++ b/plugins/filters/palettize/kritapalettize.json
@@ -0,0 +1,9 @@
+{
+ "Id": "Palettize Filter",
+ "Type": "Service",
+ "X-KDE-Library": "kritapalettize",
+ "X-KDE-ServiceTypes": [
+ "Krita/Filter"
+ ],
+ "X-Krita-Version": "30"
+}
diff --git a/plugins/filters/palettize/palettize.cpp b/plugins/filters/palettize/palettize.cpp
new file mode 100644
index 0000000000..e4742a7649
--- /dev/null
+++ b/plugins/filters/palettize/palettize.cpp
@@ -0,0 +1,287 @@
+/*
+ * This file is part of Krita
+ *
+ * Copyright (c) 2019 Carl Olsson <carl.olsson@gmail.com>
+ *
+ * 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 "palettize.h"
+
+#include <kis_types.h>
+#include <kpluginfactory.h>
+#include <kis_config_widget.h>
+#include <kis_filter_registry.h>
+#include <kis_filter_configuration.h>
+#include <kis_filter_category_ids.h>
+#include <kis_filter_configuration.h>
+#include <KoUpdater.h>
+#include <KisSequentialIteratorProgress.h>
+#include <KoResourceServerProvider.h>
+#include <KoResourceServer.h>
+#include <KoResourceServerAdapter.h>
+#include <KoResourceItemChooser.h>
+#include <KoColorSet.h>
+#include <KoPattern.h>
+#include <kis_random_generator.h>
+#include <KisDitherUtil.h>
+
+K_PLUGIN_FACTORY_WITH_JSON(PalettizeFactory, "kritapalettize.json", registerPlugin<Palettize>();)
+
+Palettize::Palettize(QObject *parent, const QVariantList &)
+ : QObject(parent)
+{
+ KisFilterRegistry::instance()->add(new KisFilterPalettize());
+}
+
+#include "palettize.moc"
+
+KisFilterPalettize::KisFilterPalettize() : KisFilter(id(), FiltersCategoryMapId, i18n("&Palettize..."))
+{
+ setColorSpaceIndependence(FULLY_INDEPENDENT);
+ setSupportsPainting(true);
+ setShowConfigurationWidget(true);
+}
+
+KisPalettizeWidget::KisPalettizeWidget(QWidget* parent)
+ : KisConfigWidget(parent)
+{
+ setupUi(this);
+
+ paletteIconWidget->setFixedSize(32, 32);
+ KoResourceServer<KoColorSet>* paletteServer = KoResourceServerProvider::instance()->paletteServer();
+ QSharedPointer<KoAbstractResourceServerAdapter> paletteAdapter(new KoResourceServerAdapter<KoColorSet>(paletteServer));
+ m_paletteWidget = new KoResourceItemChooser(paletteAdapter, this, false);
+ paletteIconWidget->setPopupWidget(m_paletteWidget);
+ QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, paletteIconWidget, &KisIconWidget::setResource);
+ QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ QObject::connect(colorspaceComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ QObject::connect(ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ QObject::connect(ditherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ QObject::connect(colorModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ offsetScaleSpinBox->setPrefix(QString("%1 ").arg(i18n("Offset Scale:")));
+ offsetScaleSpinBox->setRange(0.0, 1.0, 3);
+ offsetScaleSpinBox->setSingleStep(0.125);
+ QObject::connect(offsetScaleSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ QObject::connect(alphaGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ QObject::connect(alphaModeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ alphaClipSpinBox->setPrefix(QString("%1 ").arg(i18n("Clip:")));
+ alphaClipSpinBox->setRange(0.0, 1.0, 3);
+ alphaClipSpinBox->setSingleStep(0.125);
+ QObject::connect(alphaClipSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged);
+
+ alphaIndexSpinBox->setPrefix(QString("%1 ").arg(i18n("Index:")));
+ alphaIndexSpinBox->setRange(0, 255);
+ QObject::connect(alphaIndexSpinBox, &KisSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged);
+ QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, [this](){
+ const KoColorSet* const palette = static_cast<const KoColorSet*>(m_paletteWidget->currentResource());
+ alphaIndexSpinBox->setMaximum(palette ? int(palette->colorCount() - 1) : 0);
+ alphaIndexSpinBox->setValue(std::min(alphaIndexSpinBox->value(), alphaIndexSpinBox->maximum()));
+ });
+
+ QObject::connect(alphaDitherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged);
+}
+
+void KisPalettizeWidget::setConfiguration(const KisPropertiesConfigurationSP config)
+{
+ KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette"));
+ if (palette) m_paletteWidget->setCurrentResource(palette);
+ colorspaceComboBox->setCurrentIndex(config->getInt("colorspace"));
+ ditherGroupBox->setChecked(config->getBool("ditherEnabled"));
+ ditherWidget->setConfiguration(*config, "dither/");
+ colorModeComboBox->setCurrentIndex(config->getInt("dither/colorMode"));
+ offsetScaleSpinBox->setValue(config->getDouble("dither/offsetScale"));
+ alphaGroupBox->setChecked(config->getBool("alphaEnabled"));
+ alphaModeComboBox->setCurrentIndex(config->getInt("alphaMode"));
+ alphaClipSpinBox->setValue(config->getDouble("alphaClip"));
+ alphaIndexSpinBox->setValue(config->getInt("alphaIndex"));
+ alphaDitherWidget->setConfiguration(*config, "alphaDither/");
+}
+
+KisPropertiesConfigurationSP KisPalettizeWidget::configuration() const
+{
+ KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1);
+
+ if (m_paletteWidget->currentResource()) config->setProperty("palette", QVariant(m_paletteWidget->currentResource()->name()));
+ config->setProperty("colorspace", colorspaceComboBox->currentIndex());
+ config->setProperty("ditherEnabled", ditherGroupBox->isChecked());
+ ditherWidget->configuration(*config, "dither/");
+ config->setProperty("dither/colorMode", colorModeComboBox->currentIndex());
+ config->setProperty("dither/offsetScale", offsetScaleSpinBox->value());
+ config->setProperty("alphaEnabled", alphaGroupBox->isChecked());
+ config->setProperty("alphaMode", alphaModeComboBox->currentIndex());
+ config->setProperty("alphaClip", alphaClipSpinBox->value());
+ config->setProperty("alphaIndex", alphaIndexSpinBox->value());
+ alphaDitherWidget->configuration(*config, "alphaDither/");
+
+ return config;
+}
+
+KisConfigWidget* KisFilterPalettize::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const
+{
+ Q_UNUSED(dev)
+ Q_UNUSED(useForMasks)
+
+ return new KisPalettizeWidget(parent);
+}
+
+KisFilterConfigurationSP KisFilterPalettize::factoryConfiguration() const
+{
+ KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1);
+
+ config->setProperty("palette", "Default");
+ config->setProperty("colorspace", Colorspace::Lab);
+ config->setProperty("ditherEnabled", false);
+ KisDitherWidget::factoryConfiguration(*config, "dither/");
+ config->setProperty("dither/colorMode", ColorMode::PerChannelOffset);
+ config->setProperty("dither/offsetScale", 0.125);
+ config->setProperty("alphaEnabled", true);
+ config->setProperty("alphaMode", AlphaMode::Clip);
+ config->setProperty("alphaClip", 0.5);
+ config->setProperty("alphaIndex", 0);
+ KisDitherWidget::factoryConfiguration(*config, "alphaDither/");
+
+ return config;
+}
+
+void KisFilterPalettize::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const
+{
+ const KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette"));
+ const int searchColorspace = config->getInt("colorspace");
+ const bool ditherEnabled = config->getBool("ditherEnabled");
+ const int colorMode = config->getInt("dither/colorMode");
+ const double offsetScale = config->getDouble("dither/offsetScale");
+ const bool alphaEnabled = config->getBool("alphaEnabled");
+ const int alphaMode = config->getInt("alphaMode");
+ const double alphaClip = config->getDouble("alphaClip");
+ const int alphaIndex = config->getInt("alphaIndex");
+
+ const KoColorSpace* colorspace = device->colorSpace();
+ const KoColorSpace* workColorspace = (searchColorspace == Colorspace::Lab
+ ? KoColorSpaceRegistry::instance()->lab16()
+ : KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc"));
+
+ const quint8 colorCount = ditherEnabled && colorMode == ColorMode::NearestColors ? 2 : 1;
+
+ using SearchColor = boost::geometry::model::point<quint16, 3, boost::geometry::cs::cartesian>;
+ struct ColorCandidate {
+ KoColor color;
+ quint16 index;
+ double distance;
+ };
+ using SearchEntry = std::pair<SearchColor, ColorCandidate>;
+ boost::geometry::index::rtree<SearchEntry, boost::geometry::index::quadratic<16>> rtree;
+
+ if (palette) {
+ // Add palette colors to search tree
+ quint16 index = 0;
+ for (int row = 0; row < palette->rowCount(); ++row) {
+ for (int column = 0; column < palette->columnCount(); ++column) {
+ KisSwatch swatch = palette->getColorGlobal(column, row);
+ if (swatch.isValid()) {
+ KoColor color = swatch.color().convertedTo(colorspace);
+ KoColor workColor = swatch.color().convertedTo(workColorspace);
+ SearchColor searchColor;
+ memcpy(&searchColor, workColor.data(), sizeof(SearchColor));
+ // Don't add duplicates so won't dither between identical colors
+ std::vector<SearchEntry> result;
+ rtree.query(boost::geometry::index::contains(searchColor), std::back_inserter(result));
+ if (result.empty()) rtree.insert(SearchEntry(searchColor, {color, index, 0.0}));
+ }
+ ++index;
+ }
+ }
+
+ KisDitherUtil ditherUtil;
+ if (ditherEnabled) ditherUtil.setConfiguration(*config, "dither/");
+
+ KisDitherUtil alphaDitherUtil;
+ if (alphaMode == AlphaMode::Dither) alphaDitherUtil.setConfiguration(*config, "alphaDither/");
+
+ KisSequentialIteratorProgress pixel(device, applyRect, progressUpdater);
+ while (pixel.nextPixel()) {
+ KoColor workColor(pixel.oldRawData(), colorspace);
+ workColor.convertTo(workColorspace);
+
+ // Find dither threshold
+ double threshold = 0.5;
+ if (ditherEnabled) {
+ threshold = ditherUtil.threshold(QPoint(pixel.x(), pixel.y()));
+
+ // Traditional per-channel ordered dithering
+ if (colorMode == ColorMode::PerChannelOffset) {
+ QVector<float> normalized(int(workColorspace->channelCount()));
+ workColorspace->normalisedChannelsValue(workColor.data(), normalized);
+ for (int channel = 0; channel < int(workColorspace->channelCount()); ++channel) {
+ normalized[channel] += (threshold - 0.5) * offsetScale;
+ }
+ workColorspace->fromNormalisedChannelsValue(workColor.data(), normalized);
+ }
+ }
+
+ // Get candidate colors and their distances
+ SearchColor searchColor;
+ memcpy(reinterpret_cast<quint8 *>(&searchColor), workColor.data(), sizeof(SearchColor));
+ std::vector<ColorCandidate> candidateColors;
+ candidateColors.reserve(size_t(colorCount));
+ double distanceSum = 0.0;
+ for (auto it = rtree.qbegin(boost::geometry::index::nearest(searchColor, colorCount)); it != rtree.qend() && candidateColors.size() < colorCount; ++it) {
+ ColorCandidate candidate = it->second;
+ candidate.distance = boost::geometry::distance(searchColor, it->first);
+ candidateColors.push_back(candidate);
+ distanceSum += candidate.distance;
+ }
+
+ // Select color candidate
+ quint16 selected;
+ if (ditherEnabled && colorMode == ColorMode::NearestColors) {
+ // Sort candidates by palette order for stable dither color ordering
+ const bool swap = candidateColors[0].index > candidateColors[1].index;
+ selected = swap ^ (candidateColors[swap].distance / distanceSum > threshold);
+ }
+ else {
+ selected = 0;
+ }
+ ColorCandidate &candidate = candidateColors[selected];
+
+ // Set alpha
+ const double oldAlpha = colorspace->opacityF(pixel.oldRawData());
+ double newAlpha = oldAlpha;
+ if (alphaEnabled && !(!ditherEnabled && alphaMode == AlphaMode::Dither)) {
+ if (alphaMode == AlphaMode::Clip) {
+ newAlpha = oldAlpha < alphaClip? 0.0 : 1.0;
+ }
+ else if (alphaMode == AlphaMode::Index) {
+ newAlpha = (candidate.index == alphaIndex ? 0.0 : 1.0);
+ }
+ else if (alphaMode == AlphaMode::Dither) {
+ newAlpha = oldAlpha < alphaDitherUtil.threshold(QPoint(pixel.x(), pixel.y())) ? 0.0 : 1.0;
+ }
+ }
+ colorspace->setOpacity(candidate.color.data(), newAlpha, 1);
+
+ // Copy color to pixel
+ memcpy(pixel.rawData(), candidate.color.data(), colorspace->pixelSize());
+ }
+ }
+}
diff --git a/plugins/filters/palettize/palettize.h b/plugins/filters/palettize/palettize.h
new file mode 100644
index 0000000000..a9eade61cd
--- /dev/null
+++ b/plugins/filters/palettize/palettize.h
@@ -0,0 +1,85 @@
+/*
+ * This file is part of the KDE project
+ *
+ * Copyright (c) 2019 Carl Olsson <carl.olsson@gmail.com>
+ *
+ * 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 PALETTIZE_H
+#define PALETTIZE_H
+
+#include "ui_palettize.h"
+
+#include <kis_filter.h>
+#include <kis_config_widget.h>
+#include <kis_filter_configuration.h>
+#include <boost/geometry.hpp>
+#include <boost/geometry/index/rtree.hpp>
+#include <boost/geometry/geometries/point.hpp>
+#include <boost/geometry/geometries/register/point.hpp>
+
+class KoResourceItemChooser;
+
+class Palettize : public QObject
+{
+public:
+ Palettize(QObject *parent, const QVariantList &);
+};
+
+class KisPalettizeWidget : public KisConfigWidget, public Ui::Palettize
+{
+public:
+ KisPalettizeWidget(QWidget* parent = 0);
+ void setConfiguration(const KisPropertiesConfigurationSP) override;
+ KisPropertiesConfigurationSP configuration() const override;
+private:
+ KoResourceItemChooser* m_paletteWidget;
+ KoResourceItemChooser* m_ditherPatternWidget;
+};
+
+class KisFilterPalettize : public KisFilter
+{
+public:
+ enum Colorspace {
+ Lab,
+ RGB
+ };
+ enum AlphaMode {
+ Clip,
+ Index,
+ Dither
+ };
+ enum ThresholdMode {
+ Pattern,
+ Noise
+ };
+ enum PatternValueMode {
+ Auto,
+ Lightness,
+ Alpha
+ };
+ enum ColorMode {
+ PerChannelOffset,
+ NearestColors
+ };
+ KisFilterPalettize();
+ static inline KoID id() { return KoID("palettize", i18n("Palettize")); }
+ KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override;
+ KisFilterConfigurationSP factoryConfiguration() const override;
+ void processImpl(KisPaintDeviceSP device, const QRect &applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override;
+};
+
+#endif
diff --git a/plugins/filters/palettize/palettize.ui b/plugins/filters/palettize/palettize.ui
new file mode 100644
index 0000000000..e02d343f4f
--- /dev/null
+++ b/plugins/filters/palettize/palettize.ui
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Palettize</class>
+ <widget class="QWidget" name="Palettize">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>242</width>
+ <height>345</height>
+ </rect>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="paletteLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Palette</string>
+ </property>
+ <property name="buddy">
+ <cstring>paletteIconWidget</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="KisIconWidget" name="paletteIconWidget">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="colorspaceLabel">
+ <property name="text">
+ <string>Colorspace</string>
+ </property>
+ <property name="buddy">
+ <cstring>colorspaceComboBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="colorspaceComboBox">
+ <item>
+ <property name="text">
+ <string>Lab</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>RGB</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QGroupBox" name="ditherGroupBox">
+ <property name="title">
+ <string>Dither</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QFormLayout" name="formLayout_3">
+ <item row="1" column="1">
+ <widget class="QComboBox" name="colorModeComboBox">
+ <item>
+ <property name="text">
+ <string>Per Channel Offset</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Nearest Colors</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="colorModeLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Color Mode</string>
+ </property>
+ <property name="buddy">
+ <cstring>colorModeComboBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QStackedWidget" name="colorModeStackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="colorModePerChannelOffsetPage">
+ <layout class="QFormLayout" name="formLayout_8">
+ <item row="0" column="0" colspan="2">
+ <widget class="KisDoubleSliderSpinBox" name="offsetScaleSpinBox"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="colorModeNearestColorsPage">
+ <layout class="QFormLayout" name="formLayout_4"/>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="KisDitherWidget" name="ditherWidget" native="true">
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="2">
+ <widget class="QGroupBox" name="alphaGroupBox">
+ <property name="title">
+ <string>Alpha</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="alphaModeLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Alpha Mode</string>
+ </property>
+ <property name="buddy">
+ <cstring>alphaModeComboBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="alphaModeComboBox">
+ <item>
+ <property name="text">
+ <string>Clip</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Index</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dither</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QStackedWidget" name="alphaModeStackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="currentIndex">
+ <number>2</number>
+ </property>
+ <property name="prefix" stdset="0">
+ <string>Amount: </string>
+ </property>
+ <widget class="QWidget" name="clipPage">
+ <layout class="QFormLayout" name="formLayout_7">
+ <item row="0" column="0" colspan="2">
+ <widget class="KisDoubleSliderSpinBox" name="alphaClipSpinBox"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="indexPage">
+ <layout class="QFormLayout" name="formLayout_6">
+ <item row="0" column="0" colspan="2">
+ <widget class="KisSliderSpinBox" name="alphaIndexSpinBox"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="alphaDitherPage">
+ <layout class="QFormLayout" name="formLayout_5">
+ <item row="0" column="0" colspan="2">
+ <widget class="KisDitherWidget" name="alphaDitherWidget" native="true"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KisDitherWidget</class>
+ <extends>QWidget</extends>
+ <header location="global">KisDitherWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KisIconWidget</class>
+ <extends>QToolButton</extends>
+ <header location="global">kis_iconwidget.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KisDoubleSliderSpinBox</class>
+ <extends>QDoubleSpinBox</extends>
+ <header location="global">kis_slider_spin_box.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KisSliderSpinBox</class>
+ <extends>QSpinBox</extends>
+ <header location="global">kis_slider_spin_box.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>paletteIconWidget</tabstop>
+ <tabstop>colorspaceComboBox</tabstop>
+ <tabstop>ditherGroupBox</tabstop>
+ <tabstop>ditherWidget</tabstop>
+ <tabstop>colorModeComboBox</tabstop>
+ <tabstop>offsetScaleSpinBox</tabstop>
+ <tabstop>alphaGroupBox</tabstop>
+ <tabstop>alphaModeComboBox</tabstop>
+ <tabstop>alphaClipSpinBox</tabstop>
+ <tabstop>alphaIndexSpinBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>alphaModeComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>alphaModeStackedWidget</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>238</x>
+ <y>288</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>183</x>
+ <y>341</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>alphaModeStackedWidget</sender>
+ <signal>currentChanged(int)</signal>
+ <receiver>alphaModeComboBox</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>107</x>
+ <y>341</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>228</x>
+ <y>288</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorModeComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>colorModeStackedWidget</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>143</x>
+ <y>142</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>139</x>
+ <y>162</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorModeStackedWidget</sender>
+ <signal>currentChanged(int)</signal>
+ <receiver>colorModeComboBox</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>167</x>
+ <y>164</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>169</x>
+ <y>143</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/plugins/filters/posterize/posterize.cpp b/plugins/filters/posterize/posterize.cpp
index 93da26c2e0..776371e996 100644
--- a/plugins/filters/posterize/posterize.cpp
+++ b/plugins/filters/posterize/posterize.cpp
@@ -1,111 +1,128 @@
/*
* Copyright (c) 2014 Manuel Riecke <spell1337@gmail.com>
*
* 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 "posterize.h"
#include <stdlib.h>
#include <vector>
#include <QPoint>
#include <QTime>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <kis_processing_information.h>
#include <kis_types.h>
#include <kis_selection.h>
#include <kis_layer.h>
#include <filter/kis_filter_category_ids.h>
#include <filter/kis_filter_registry.h>
#include <kis_global.h>
#include <KoColorSpaceMaths.h>
#include <filter/kis_color_transformation_configuration.h>
#include <widgets/kis_multi_integer_filter_widget.h>
K_PLUGIN_FACTORY_WITH_JSON(PosterizeFactory, "kritaposterize.json", registerPlugin<Posterize>();)
Posterize::Posterize(QObject *parent, const QVariantList &)
: QObject(parent)
{
KisFilterRegistry::instance()->add(KisFilterSP(new KisFilterPosterize()));
}
Posterize::~Posterize()
{
}
KisFilterPosterize::KisFilterPosterize() : KisColorTransformationFilter(id(), FiltersCategoryArtisticId, i18n("&Posterize..."))
{
setColorSpaceIndependence(FULLY_INDEPENDENT);
setSupportsPainting(true);
setShowConfigurationWidget(true);
}
KoColorTransformation* KisFilterPosterize::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const
{
return new KisPosterizeColorTransformation(config->getInt("steps", 16), cs);
}
KisPosterizeColorTransformation::KisPosterizeColorTransformation(int steps, const KoColorSpace* cs) : m_colorSpace(cs), m_psize(cs->pixelSize())
{
m_step = KoColorSpaceMathsTraits<quint16>::max / steps;
m_halfStep = m_step / 2;
+ m_fromConversion = KoColorSpaceRegistry::instance()->createColorConverter(
+ m_colorSpace,
+ KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc"),
+ KoColorConversionTransformation::internalRenderingIntent(),
+ KoColorConversionTransformation::internalConversionFlags());
+ m_toConversion = KoColorSpaceRegistry::instance()->createColorConverter(
+ KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc"),
+ m_colorSpace,
+ KoColorConversionTransformation::internalRenderingIntent(),
+ KoColorConversionTransformation::internalConversionFlags());
+}
+
+KisPosterizeColorTransformation::~KisPosterizeColorTransformation()
+{
+ delete m_fromConversion;
+ delete m_toConversion;
}
KisConfigWidget* KisFilterPosterize::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const
{
Q_UNUSED(dev);
vKisIntegerWidgetParam param;
param.push_back(KisIntegerWidgetParam(2, 128, 16, i18n("Steps"), "steps"));
return new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param);
}
KisFilterConfigurationSP KisFilterPosterize::factoryConfiguration() const
{
KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 0);
config->setProperty("steps", 16);
return config;
}
void KisPosterizeColorTransformation::transform(const quint8* src, quint8* dst, qint32 nPixels) const
{
quint16 m_rgba[4];
quint16 m_mod[4];
+
while (nPixels--) {
- m_colorSpace->toRgbA16(src, reinterpret_cast<quint8 *>(m_rgba), 1);
+ m_fromConversion->transform(src, reinterpret_cast<quint8 *>(m_rgba), 1);
m_mod[0] = m_rgba[0] % m_step;
m_mod[1] = m_rgba[1] % m_step;
m_mod[2] = m_rgba[2] % m_step;
m_mod[3] = m_rgba[3] % m_step;
m_rgba[0] = m_rgba[0] + (m_mod[0] > m_halfStep ? m_step - m_mod[0] : -m_mod[0]);
m_rgba[1] = m_rgba[1] + (m_mod[1] > m_halfStep ? m_step - m_mod[1] : -m_mod[1]);
m_rgba[2] = m_rgba[2] + (m_mod[2] > m_halfStep ? m_step - m_mod[2] : -m_mod[2]);
m_rgba[3] = m_rgba[3] + (m_mod[3] > m_halfStep ? m_step - m_mod[3] : -m_mod[3]);
- m_colorSpace->fromRgbA16(reinterpret_cast<quint8 *>(m_rgba), dst, 1);
+ m_toConversion->transform(reinterpret_cast<quint8 *>(m_rgba), dst, 1);
src += m_psize;
dst += m_psize;
}
}
#include "posterize.moc"
diff --git a/plugins/filters/posterize/posterize.h b/plugins/filters/posterize/posterize.h
index 88f7b69922..2774a94d33 100644
--- a/plugins/filters/posterize/posterize.h
+++ b/plugins/filters/posterize/posterize.h
@@ -1,61 +1,64 @@
/*
* Copyright (c) 2014 Manuel Riecke <spell1337@gmail.com>
*
* 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 POSTERIZE_H
#define POSTERIZE_H
#include <QObject>
#include <QVariant>
#include "filter/kis_color_transformation_filter.h"
#include "kis_config_widget.h"
class Posterize : public QObject
{
Q_OBJECT
public:
Posterize(QObject *parent, const QVariantList &);
~Posterize() override;
};
class KisFilterPosterize : public KisColorTransformationFilter
{
public:
KisFilterPosterize();
public:
KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override;
KisConfigWidget* createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override;
static inline KoID id() {
return KoID("posterize", i18n("Posterize"));
}
protected:
KisFilterConfigurationSP factoryConfiguration() const override;
};
class KisPosterizeColorTransformation : public KoColorTransformation
{
public:
KisPosterizeColorTransformation(int steps, const KoColorSpace* cs);
+ ~KisPosterizeColorTransformation() override;
void transform(const quint8* src, quint8* dst, qint32 nPixels) const override;
private:
const KoColorSpace* m_colorSpace;
quint32 m_psize;
quint16 m_step;
quint16 m_halfStep;
+ KoColorConversionTransformation* m_fromConversion;
+ KoColorConversionTransformation* m_toConversion;
};
#endif
diff --git a/plugins/flake/textshape/TextShapeFactory.cpp b/plugins/flake/textshape/TextShapeFactory.cpp
index 915ee5e58b..5451d0a0f3 100644
--- a/plugins/flake/textshape/TextShapeFactory.cpp
+++ b/plugins/flake/textshape/TextShapeFactory.cpp
@@ -1,164 +1,164 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007,2009,2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License 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 "TextShapeFactory.h"
#include "TextShape.h"
#include <KoProperties.h>
#include <KoShape.h>
#include <KoTextDocument.h>
#include <KoTextShapeData.h>
#include <KoXmlNS.h>
#include <KoStyleManager.h>
#include <KoDocumentResourceManager.h>
#include <KoInlineTextObjectManager.h>
#include <KoTextRangeManager.h>
#include <changetracker/KoChangeTracker.h>
#include <KoImageCollection.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <kundo2stack.h>
#include <QTextCursor>
TextShapeFactory::TextShapeFactory()
: KoShapeFactoryBase(TextShape_SHAPEID, i18n("Text"))
{
setToolTip(i18n("A shape that shows text"));
QList<QPair<QString, QStringList> > odfElements;
odfElements.append(QPair<QString, QStringList>(KoXmlNS::draw, QStringList("text-box")));
odfElements.append(QPair<QString, QStringList>(KoXmlNS::table, QStringList("table")));
setXmlElements(odfElements);
setLoadingPriority(1);
KoShapeTemplate t;
t.name = i18n("Text");
t.iconName = koIconName("x-shape-text");
t.toolTip = i18n("Text Shape");
KoProperties *props = new KoProperties();
t.properties = props;
props->setProperty("demo", true);
addTemplate(t);
}
KoShape *TextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const
{
KoInlineTextObjectManager *manager = 0;
KoTextRangeManager *locationManager = 0;
if (documentResources && documentResources->hasResource(KoText::InlineTextObjectManager)) {
QVariant variant = documentResources->resource(KoText::InlineTextObjectManager);
if (variant.isValid()) {
manager = variant.value<KoInlineTextObjectManager *>();
}
}
if (documentResources && documentResources->hasResource(KoText::TextRangeManager)) {
QVariant variant = documentResources->resource(KoText::TextRangeManager);
if (variant.isValid()) {
locationManager = variant.value<KoTextRangeManager *>();
}
}
if (!manager) {
manager = new KoInlineTextObjectManager();
}
if (!locationManager) {
locationManager = new KoTextRangeManager();
}
TextShape *text = new TextShape(manager, locationManager);
if (documentResources) {
KoTextDocument document(text->textShapeData()->document());
if (documentResources->hasResource(KoText::StyleManager)) {
KoStyleManager *styleManager = documentResources->resource(KoText::StyleManager).value<KoStyleManager *>();
document.setStyleManager(styleManager);
}
// this is needed so the shape can reinitialize itself with the stylemanager
text->textShapeData()->setDocument(text->textShapeData()->document());
document.setUndoStack(documentResources->undoStack());
if (documentResources->hasResource(KoText::PageProvider)) {
KoPageProvider *pp = static_cast<KoPageProvider *>(documentResources->resource(KoText::PageProvider).value<void *>());
text->setPageProvider(pp);
}
if (documentResources->hasResource(KoText::ChangeTracker)) {
KoChangeTracker *changeTracker = documentResources->resource(KoText::ChangeTracker).value<KoChangeTracker *>();
document.setChangeTracker(changeTracker);
}
- document.setShapeController(documentResources->shapeController());
+ document.setShapeController(documentResources->globalShapeController());
//update the resources of the document
text->updateDocumentData();
text->setImageCollection(documentResources->imageCollection());
}
return text;
}
KoShape *TextShapeFactory::createShape(const KoProperties */*params*/, KoDocumentResourceManager *documentResources) const
{
TextShape *shape = static_cast<TextShape *>(createDefaultShape(documentResources));
shape->textShapeData()->document()->setUndoRedoEnabled(false);
shape->setSize(QSizeF(300, 200));
/*
QString text("text");
if (params->contains(text)) {
KoTextShapeData *shapeData = qobject_cast<KoTextShapeData*>(shape->userData());
}
*/
if (documentResources) {
shape->setImageCollection(documentResources->imageCollection());
}
shape->textShapeData()->document()->setUndoRedoEnabled(true);
return shape;
}
bool TextShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
return (e.localName() == "text-box" && e.namespaceURI() == KoXmlNS::draw) ||
(e.localName() == "table" && e.namespaceURI() == KoXmlNS::table);
}
void TextShapeFactory::newDocumentResourceManager(KoDocumentResourceManager *manager) const
{
QVariant variant;
variant.setValue<KoInlineTextObjectManager *>(new KoInlineTextObjectManager(manager));
manager->setResource(KoText::InlineTextObjectManager, variant);
variant.setValue<KoTextRangeManager *>(new KoTextRangeManager());
manager->setResource(KoText::TextRangeManager, variant);
if (!manager->hasResource(KoDocumentResourceManager::UndoStack)) {
// qWarning() << "No KUndo2Stack found in the document resource manager, creating a new one";
manager->setUndoStack(new KUndo2Stack(manager));
}
if (!manager->hasResource(KoText::StyleManager)) {
variant.setValue(new KoStyleManager(manager));
manager->setResource(KoText::StyleManager, variant);
}
if (!manager->imageCollection()) {
manager->setImageCollection(new KoImageCollection(manager));
}
}
diff --git a/plugins/impex/brush/CMakeLists.txt b/plugins/impex/brush/CMakeLists.txt
index ffce44e4d0..b0343eddd4 100644
--- a/plugins/impex/brush/CMakeLists.txt
+++ b/plugins/impex/brush/CMakeLists.txt
@@ -1,26 +1,29 @@
+add_subdirectory(tests)
+
set(kritabrushexport_PART_SRCS
kis_brush_export.cpp
+ KisWdgOptionsBrush.cpp
KisAnimatedBrushAnnotation.cpp
)
ki18n_wrap_ui(kritabrushexport_PART_SRCS wdg_export_gih.ui)
add_library(kritabrushexport MODULE ${kritabrushexport_PART_SRCS})
target_link_libraries(kritabrushexport kritalibbrush kritalibpaintop kritaui kritaimpex)
install(TARGETS kritabrushexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
set(kritabrushimport_PART_SRCS
kis_brush_import.cpp
KisAnimatedBrushAnnotation.cpp
)
ki18n_wrap_ui(kritabrushimport_PART_SRCS )
add_library(kritabrushimport MODULE ${kritabrushimport_PART_SRCS})
target_link_libraries(kritabrushimport kritalibbrush kritaui)
install(TARGETS kritabrushimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_brush.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/brush/KisWdgOptionsBrush.cpp b/plugins/impex/brush/KisWdgOptionsBrush.cpp
new file mode 100644
index 0000000000..4b0e8d75c7
--- /dev/null
+++ b/plugins/impex/brush/KisWdgOptionsBrush.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2019 Iván SantaMaría <ghevan@gmail.com>
+ *
+ * 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 "KisWdgOptionsBrush.h"
+
+#include <KisViewManager.h>
+#include <kis_image.h>
+#include <KoProperties.h>
+#include <KisDocument.h>
+
+KisWdgOptionsBrush::KisWdgOptionsBrush(QWidget *parent)
+ : KisConfigWidget(parent)
+ , m_currentDimensions(0)
+ , m_layersCount(0)
+ , m_view(0)
+{
+ setupUi(this);
+ connect(this->brushStyle, SIGNAL(currentIndexChanged(int)), SLOT(slotEnableSelectionMethod(int)));
+ connect(this->dimensionSpin, SIGNAL(valueChanged(int)), SLOT(slotActivateDimensionRanks()));
+
+ slotEnableSelectionMethod(brushStyle->currentIndex());
+
+ BrushPipeSelectionModeHelper *bp;
+ for (int i = 0; i < this->dimensionSpin->maximum(); i++) {
+ bp = new BrushPipeSelectionModeHelper(0, i);
+ connect(bp, SIGNAL(sigRankChanged(int)), SLOT(slotRecalculateRanks(int)));
+ dimRankLayout->addWidget(bp);
+ }
+
+ slotActivateDimensionRanks();
+}
+
+void KisWdgOptionsBrush::setConfiguration(const KisPropertiesConfigurationSP cfg)
+{
+ spacingWidget->setSpacing(false, cfg->getDouble("spacing"));
+ if (nameLineEdit->text().isEmpty()) {
+ nameLineEdit->setText(cfg->getString("name"));
+ }
+ colorAsMask->setChecked(cfg->getBool("mask"));
+ brushStyle->setCurrentIndex(cfg->getInt("brushStyle"));
+ dimensionSpin->setValue(cfg->getInt("dimensions"));
+
+ QLayoutItem *item;
+ BrushPipeSelectionModeHelper *bp;
+ for (int i = 0; i < dimensionSpin->maximum(); ++i) {
+ if ((item = dimRankLayout->itemAt(i)) != 0) {
+ bp = dynamic_cast<BrushPipeSelectionModeHelper*>(item->widget());
+ bp->cmbSelectionMode.setCurrentIndex(cfg->getInt("selectionMode" + QString::number(i)));
+ bp->rankSpinBox.setValue(cfg->getInt("rank" + QString::number(i)));
+ }
+ }
+}
+
+KisPropertiesConfigurationSP KisWdgOptionsBrush::configuration() const
+{
+ KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
+ cfg->setProperty("spacing", spacingWidget->spacing());
+ cfg->setProperty("name", nameLineEdit->text());
+ cfg->setProperty("mask", colorAsMask->isChecked());
+ cfg->setProperty("brushStyle", brushStyle->currentIndex());
+ cfg->setProperty("dimensions", dimensionSpin->value());
+
+ QLayoutItem *item;
+ BrushPipeSelectionModeHelper *bp;
+ for (int i = 0; i < dimensionSpin->maximum(); ++i) {
+ if ((item = dimRankLayout->itemAt(i)) != 0) {
+ bp = dynamic_cast<BrushPipeSelectionModeHelper*>(item->widget());
+ cfg->setProperty("selectionMode" + QString::number(i), bp->cmbSelectionMode.currentIndex());
+ cfg->setProperty("rank" + QString::number(i), bp->rankSpinBox.value());
+ }
+ }
+
+ return cfg;
+}
+
+void KisWdgOptionsBrush::setView(KisViewManager *view)
+{
+ if (view) {
+ m_view = view;
+ KoProperties properties;
+ properties.setProperty("visible", true);
+ m_layersCount = m_view->image()->root()->childNodes(QStringList("KisLayer"), properties).count();
+
+ slotRecalculateRanks();
+ }
+}
+
+void KisWdgOptionsBrush::slotEnableSelectionMethod(int value)
+{
+ if (value == 0) {
+ animStyleGroup->setEnabled(false);
+ } else {
+ animStyleGroup->setEnabled(true);
+ }
+}
+
+void KisWdgOptionsBrush::slotActivateDimensionRanks()
+{
+ QLayoutItem *item;
+ BrushPipeSelectionModeHelper *bp;
+ int dim = this->dimensionSpin->value();
+ if (dim >= m_currentDimensions) {
+ for (int i = m_currentDimensions; i < dim; ++i) {
+ if ((item = dimRankLayout->itemAt(i)) != 0) {
+ bp = dynamic_cast<BrushPipeSelectionModeHelper*>(item->widget());
+ bp->setEnabled(true);
+ bp->show();
+ }
+ }
+ }
+ else {
+ for (int i = m_currentDimensions -1; i >= dim; --i) {
+ if ((item = dimRankLayout->itemAt(i)) != 0) {
+ bp = dynamic_cast<BrushPipeSelectionModeHelper*>(item->widget());
+ bp->setEnabled(false);
+ bp->hide();
+ }
+ }
+ }
+ m_currentDimensions = dim;
+}
+
+void KisWdgOptionsBrush::slotRecalculateRanks(int rankDimension)
+{
+ int rankSum = 0;
+ int maxDim = this->dimensionSpin->maximum();
+
+ QVector<BrushPipeSelectionModeHelper *> bp;
+ QLayoutItem *item;
+
+ for (int i = 0; i < maxDim; ++i) {
+ if ((item = dimRankLayout->itemAt(i)) != 0) {
+ bp.push_back(dynamic_cast<BrushPipeSelectionModeHelper*>(item->widget()));
+ rankSum += bp.at(i)->rankSpinBox.value();
+ }
+ }
+
+ BrushPipeSelectionModeHelper *currentBrushHelper;
+ BrushPipeSelectionModeHelper *callerBrushHelper = bp.at(rankDimension);
+ QVectorIterator<BrushPipeSelectionModeHelper*> bpIterator(bp);
+
+ while (rankSum > m_layersCount && bpIterator.hasNext()) {
+ currentBrushHelper = bpIterator.next();
+
+ if (currentBrushHelper != callerBrushHelper) {
+ int currentValue = currentBrushHelper->rankSpinBox.value();
+ currentBrushHelper->rankSpinBox.setValue(currentValue -1);
+ rankSum -= currentValue;
+ }
+ }
+
+ if (rankSum > m_layersCount) {
+ callerBrushHelper->rankSpinBox.setValue(m_layersCount);
+ }
+
+ if (rankSum == 0) {
+ bp.at(0)->rankSpinBox.setValue(m_layersCount);
+ return;
+ }
+}
diff --git a/plugins/impex/brush/KisWdgOptionsBrush.h b/plugins/impex/brush/KisWdgOptionsBrush.h
new file mode 100644
index 0000000000..fbcf95eb60
--- /dev/null
+++ b/plugins/impex/brush/KisWdgOptionsBrush.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2019 Iván SantaMaría <ghevan@gmail.com>
+ *
+ * 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 KISWDGOPTIONSBRUSH_H
+#define KISWDGOPTIONSBRUSH_H
+
+#include <QVariant>
+#include <QSpinBox>
+#include <QPainter>
+
+#include <KisImportExportFilter.h>
+#include <ui_wdg_export_gih.h>
+#include <kis_config_widget.h>
+#include <kis_properties_configuration.h>
+
+class BrushPipeSelectionModeHelper : public QWidget
+{
+ Q_OBJECT
+
+public:
+ BrushPipeSelectionModeHelper(QWidget *parent, int dimension)
+ : QWidget(parent)
+ , cmbSelectionMode(this)
+ , rankSpinBox(this)
+ , rankLbl(this)
+ , horizLayout(this)
+ , dimension(dimension)
+ {
+ cmbSelectionMode.addItem(i18n("Constant"));
+ cmbSelectionMode.addItem(i18n("Random"));
+ cmbSelectionMode.addItem(i18n("Incremental"));
+ cmbSelectionMode.addItem(i18n("Pressure"));
+ cmbSelectionMode.addItem(i18n("Angular"));
+ cmbSelectionMode.addItem(i18n("Velocity"));
+
+ horizLayout.setSpacing(6);
+ horizLayout.setMargin(0);
+
+ QSizePolicy sizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
+ sizePolicy.setHorizontalStretch(1);
+ sizePolicy.setVerticalStretch(0);
+
+ this->setSizePolicy(sizePolicy);
+
+ cmbSelectionMode.setSizePolicy(sizePolicy);
+ cmbSelectionMode.setCurrentIndex(2);
+
+ rankSpinBox.setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred));
+ rankLbl.setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
+
+ rankLbl.setText(i18n("Rank"));
+ horizLayout.addWidget(&rankLbl);
+ horizLayout.addWidget(&rankSpinBox);
+ horizLayout.addWidget(&cmbSelectionMode);
+
+ connect(&rankSpinBox, SIGNAL(valueChanged(int)), this, SLOT(slotRankChanged()));
+
+ this->hide();
+ this->setEnabled(false);
+ }
+
+ QComboBox cmbSelectionMode;
+ QSpinBox rankSpinBox;
+ QLabel rankLbl;
+ QHBoxLayout horizLayout;
+
+ int dimension;
+
+
+Q_SIGNALS:
+ void sigRankChanged(int rankEmitter);
+
+public Q_SLOTS:
+ void slotRankChanged()
+ {
+ emit sigRankChanged(dimension);
+ }
+
+};
+
+class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih
+{
+ Q_OBJECT
+
+public:
+ KisWdgOptionsBrush(QWidget *parent);
+
+ void setConfiguration(const KisPropertiesConfigurationSP cfg) override;
+ KisPropertiesConfigurationSP configuration() const override;
+
+ void setView(KisViewManager *view) override;
+
+public Q_SLOTS:
+
+ void slotEnableSelectionMethod(int value);
+ void slotActivateDimensionRanks();
+ void slotRecalculateRanks(int rankDimension = 0);
+
+private:
+ int m_currentDimensions;
+ int m_layersCount;
+ KisViewManager *m_view;
+};
+
+#endif // KISWDGOPTIONSBRUSH_H
diff --git a/plugins/impex/brush/kis_brush_export.cpp b/plugins/impex/brush/kis_brush_export.cpp
index 74705909d2..981ac59956 100644
--- a/plugins/impex/brush/kis_brush_export.cpp
+++ b/plugins/impex/brush/kis_brush_export.cpp
@@ -1,228 +1,252 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_brush_export.h"
#include <QCheckBox>
#include <QSlider>
#include <QBuffer>
#include <KoProperties.h>
#include <KoDialog.h>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KisExportCheckRegistry.h>
#include <kis_paint_device.h>
+#include <KisViewManager.h>
+#include <kis_image.h>
+#include <KoProperties.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_spacing_selection_widget.h>
#include <kis_gbr_brush.h>
#include <kis_imagepipe_brush.h>
#include <kis_pipebrush_parasite.h>
#include <KisAnimatedBrushAnnotation.h>
+#include <KisWdgOptionsBrush.h>
#include <KisImportExportManager.h>
#include <kis_config.h>
struct KisBrushExportOptions {
qreal spacing;
bool mask;
int brushStyle;
- int selectionMode;
+ int dimensions;
+ qint32 ranks[KisPipeBrushParasite::MaxDim];
+ qint32 selectionModes[KisPipeBrushParasite::MaxDim];
QString name;
};
K_PLUGIN_FACTORY_WITH_JSON(KisBrushExportFactory, "krita_brush_export.json", registerPlugin<KisBrushExport>();)
KisBrushExport::KisBrushExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisBrushExport::~KisBrushExport()
{
}
-KisImportExportFilter::ConversionStatus KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
// XXX: Loading the parasite itself was commented out -- needs investigation
// KisAnnotationSP annotation = document->savingImage()->annotation("ImagePipe Parasite");
// KisPipeBrushParasite parasite;
// if (annotation) {
// QBuffer buf(const_cast<QByteArray*>(&annotation->annotation()));
// buf.open(QBuffer::ReadOnly);
// parasite.loadFromDevice(&buf);
// buf.close();
// }
KisBrushExportOptions exportOptions;
+
if (document->savingImage()->dynamicPropertyNames().contains("brushspacing")) {
exportOptions.spacing = document->savingImage()->property("brushspacing").toFloat();
}
else {
exportOptions.spacing = configuration->getInt("spacing");
}
if (!configuration->getString("name").isEmpty()) {
exportOptions.name = configuration->getString("name");
}
else {
exportOptions.name = document->savingImage()->objectName();
}
+
exportOptions.mask = configuration->getBool("mask");
- exportOptions.selectionMode = configuration->getInt("selectionMode");
exportOptions.brushStyle = configuration->getInt("brushStyle");
+ exportOptions.dimensions = configuration->getInt("dimensions");
+
+ for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) {
+ exportOptions.selectionModes[i] = configuration->getInt("selectionMode" + QString::number(i));
+ exportOptions.ranks[i] = configuration->getInt("rank" + QString::number(i));
+ }
KisGbrBrush *brush = 0;
if (mimeType() == "image/x-gimp-brush") {
brush = new KisGbrBrush(filename());
}
else if (mimeType() == "image/x-gimp-brush-animated") {
brush = new KisImagePipeBrush(filename());
}
else {
- return KisImportExportFilter::BadMimeType;
+ return ImportExportCodes::FileFormatIncorrect;
}
qApp->processEvents(); // For vector layers to be updated
QRect rc = document->savingImage()->bounds();
- brush->setName(exportOptions.name);
brush->setSpacing(exportOptions.spacing);
- brush->setUseColorAsMask(exportOptions.mask);
-
-
-
KisImagePipeBrush *pipeBrush = dynamic_cast<KisImagePipeBrush*>(brush);
if (pipeBrush) {
// Create parasite. XXX: share with KisCustomBrushWidget
QVector< QVector<KisPaintDevice*> > devices;
devices.push_back(QVector<KisPaintDevice*>());
KoProperties properties;
properties.setProperty("visible", true);
QList<KisNodeSP> layers = document->savingImage()->root()->childNodes(QStringList("KisLayer"), properties);
Q_FOREACH (KisNodeSP node, layers) {
- devices[0].push_back(node->projection().data());
+ // push_front to behave exactly as gimp for gih creation
+ devices[0].push_front(node->projection().data());
}
QVector<KisParasite::SelectionMode > modes;
- switch (exportOptions.selectionMode) {
- case 0: modes.push_back(KisParasite::Constant); break;
- case 1: modes.push_back(KisParasite::Random); break;
- case 2: modes.push_back(KisParasite::Incremental); break;
- case 3: modes.push_back(KisParasite::Pressure); break;
- case 4: modes.push_back(KisParasite::Angular); break;
- default: modes.push_back(KisParasite::Incremental);
+
+ for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) {
+ switch (exportOptions.selectionModes[i]) {
+ case 0: modes.push_back(KisParasite::Constant); break;
+ case 1: modes.push_back(KisParasite::Random); break;
+ case 2: modes.push_back(KisParasite::Incremental); break;
+ case 3: modes.push_back(KisParasite::Pressure); break;
+ case 4: modes.push_back(KisParasite::Angular); break;
+ case 5: modes.push_back(KisParasite::Velocity); break;
+ default: modes.push_back(KisParasite::Incremental);
+ }
}
KisPipeBrushParasite parasite;
- // XXX: share code with KisImagePipeBrush, when we figure out how to support more gih features
- parasite.dim = devices.count();
- // XXX Change for multidim! :
+ parasite.dim = exportOptions.dimensions;
parasite.ncells = devices.at(0).count();
- parasite.rank[0] = parasite.ncells; // ### This can mask some bugs, be careful here in the future
- parasite.selection[0] = modes.at(0);
+
+ int maxRanks = 0;
+ for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) {
+ // ### This can mask some bugs, be careful here in the future
+ parasite.rank[i] = exportOptions.ranks[i];
+ parasite.selection[i] = modes.at(i);
+ maxRanks += exportOptions.ranks[i];
+ }
+
+ if (maxRanks > layers.count()) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
// XXX needs movement!
parasite.setBrushesCount();
pipeBrush->setParasite(parasite);
pipeBrush->setDevices(devices, rc.width(), rc.height());
+
+ if (exportOptions.mask) {
+ QVector<KisGbrBrush*> brushes = pipeBrush->brushes();
+ Q_FOREACH(KisGbrBrush* brush, brushes) {
+ brush->setHasColor(false);
+ }
+ }
}
else {
- QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
- image.save("~/bla.png");
- brush->setImage(image);
- brush->setBrushTipImage(image);
+ if (exportOptions.mask) {
+ QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
+ brush->setImage(image);
+ brush->setBrushTipImage(image);
+ } else {
+ brush->initFromPaintDev(document->savingImage()->projection(),0,0,rc.width(), rc.height());
+ }
}
+ brush->setName(exportOptions.name);
+ // brushes are created after devices are loaded, call mask mode after that
+ brush->setUseColorAsMask(exportOptions.mask);
brush->setWidth(rc.width());
brush->setHeight(rc.height());
if (brush->saveToDevice(io)) {
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
else {
- return KisImportExportFilter::CreationError;
+ return ImportExportCodes::Failure;
}
}
KisPropertiesConfigurationSP KisBrushExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("spacing", 1.0);
cfg->setProperty("name", "");
cfg->setProperty("mask", true);
- cfg->setProperty("selectionMode", 0);
cfg->setProperty("brushStyle", 0);
+ cfg->setProperty("dimensions", 1);
+
+ for (int i = 0; i < KisPipeBrushParasite::MaxDim; ++i) {
+ cfg->setProperty("selectionMode" + QString::number(i), 2);
+ cfg->getInt("rank" + QString::number(i), 0);
+ }
return cfg;
}
KisConfigWidget *KisBrushExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &to) const
{
KisWdgOptionsBrush *wdg = new KisWdgOptionsBrush(parent);
if (to == "image/x-gimp-brush") {
wdg->groupBox->setVisible(false);
+ wdg->animStyleGroup->setVisible(false);
}
else if (to == "image/x-gimp-brush-animated") {
wdg->groupBox->setVisible(true);
+ wdg->animStyleGroup->setVisible(true);
}
+
+ // preload gih name with chosen filename
+ QFileInfo fileLocation(filename());
+ wdg->nameLineEdit->setText(fileLocation.baseName());
return wdg;
}
void KisBrushExport::initializeCapabilities()
{
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "Gimp Brushes");
if (mimeType() == "image/x-gimp-brush-animated") {
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
}
}
-void KisWdgOptionsBrush::setConfiguration(const KisPropertiesConfigurationSP cfg)
-{
- spacingWidget->setSpacing(false, cfg->getDouble("spacing"));
- nameLineEdit->setText(cfg->getString("name"));
- colorAsMask->setChecked(cfg->getBool("mask"));
- brushStyle->setCurrentIndex(cfg->getInt("brushStyle"));
- cmbSelectionMode->setCurrentIndex(cfg->getInt("selectionMode"));
-}
-
-KisPropertiesConfigurationSP KisWdgOptionsBrush::configuration() const
-{
- KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
- cfg->setProperty("spacing", spacingWidget->spacing());
- cfg->setProperty("name", nameLineEdit->text());
- cfg->setProperty("mask", colorAsMask->isChecked());
- cfg->setProperty("selectionMode", cmbSelectionMode->currentIndex());
- cfg->setProperty("brushStyle", brushStyle->currentIndex());
- return cfg;
-}
-
-
#include "kis_brush_export.moc"
diff --git a/plugins/impex/brush/kis_brush_export.h b/plugins/impex/brush/kis_brush_export.h
index 57c5f1449f..14eeed9d6a 100644
--- a/plugins/impex/brush/kis_brush_export.h
+++ b/plugins/impex/brush/kis_brush_export.h
@@ -1,68 +1,45 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_Brush_EXPORT_H_
#define _KIS_Brush_EXPORT_H_
#include <QVariant>
+#include <QSpinBox>
+#include <QPainter>
#include <KisImportExportFilter.h>
-#include <ui_wdg_export_gih.h>
#include <kis_config_widget.h>
#include <kis_properties_configuration.h>
-class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih
-{
- Q_OBJECT
-
-public:
- KisWdgOptionsBrush(QWidget *parent)
- : KisConfigWidget(parent)
- {
- setupUi(this);
- connect(this->brushStyle, SIGNAL(currentIndexChanged(int)), SLOT(enableSelectionMedthod(int)));
- }
-
- void setConfiguration(const KisPropertiesConfigurationSP cfg) override;
- KisPropertiesConfigurationSP configuration() const override;
-public Q_SLOTS:
- void enableSelectionMedthod(int value) {
- if (value == 0) {
- cmbSelectionMode->setEnabled(false);
- } else {
- cmbSelectionMode->setEnabled(true);
- }
- }
-};
-
class KisBrushExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisBrushExport(QObject *parent, const QVariantList &);
~KisBrushExport() override;
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/brush/kis_brush_import.cpp b/plugins/impex/brush/kis_brush_import.cpp
index fb681bf25e..56bc545c11 100644
--- a/plugins/impex/brush/kis_brush_import.cpp
+++ b/plugins/impex/brush/kis_brush_import.cpp
@@ -1,123 +1,123 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_brush_import.h"
#include <QCheckBox>
#include <QBuffer>
#include <QSlider>
#include <QApplication>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KisDocument.h>
#include <kis_transaction.h>
#include <kis_paint_device.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <kis_gbr_brush.h>
#include <kis_imagepipe_brush.h>
#include <KisAnimatedBrushAnnotation.h>
K_PLUGIN_FACTORY_WITH_JSON(KisBrushImportFactory, "krita_brush_import.json", registerPlugin<KisBrushImport>();)
KisBrushImport::KisBrushImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisBrushImport::~KisBrushImport()
{
}
-KisImportExportFilter::ConversionStatus KisBrushImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisBrushImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
KisBrush *brush;
if (mimeType() == "image/x-gimp-brush") {
brush = new KisGbrBrush(filename());
}
else if (mimeType() == "image/x-gimp-brush-animated") {
brush = new KisImagePipeBrush(filename());
}
else {
- return KisImportExportFilter::BadMimeType;
+ return ImportExportCodes::FileFormatIncorrect;
}
if (!brush->loadFromDevice(io)) {
delete brush;
- return KisImportExportFilter::InvalidFormat;
+ return ImportExportCodes::FileFormatIncorrect;
}
if (!brush->valid()) {
delete brush;
- return KisImportExportFilter::InvalidFormat;
+ return ImportExportCodes::FileFormatIncorrect;;
}
const KoColorSpace *colorSpace = 0;
if (brush->hasColor()) {
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
}
else {
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), "");
}
KisImageSP image = new KisImage(document->createUndoStore(), brush->width(), brush->height(), colorSpace, brush->name());
image->setProperty("brushspacing", brush->spacing());
KisImagePipeBrush *pipeBrush = dynamic_cast<KisImagePipeBrush*>(brush);
if (pipeBrush) {
QVector<KisGbrBrush*> brushes = pipeBrush->brushes();
for(int i = brushes.size(); i > 0; i--) {
KisGbrBrush *subbrush = brushes.at(i - 1);
const KoColorSpace *subColorSpace = 0;
if (brush->hasColor()) {
subColorSpace = KoColorSpaceRegistry::instance()->rgb8();
}
else {
subColorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), "");
}
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255, subColorSpace);
layer->paintDevice()->convertFromQImage(subbrush->brushTipImage(), 0, 0, 0);
image->addNode(layer, image->rootLayer());
}
KisAnnotationSP ann = new KisAnimatedBrushAnnotation(pipeBrush->parasite());
image->addAnnotation(ann);
}
else {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255, colorSpace);
layer->paintDevice()->convertFromQImage(brush->brushTipImage(), 0, 0, 0);
image->addNode(layer, image->rootLayer(), 0);
}
document->setCurrentImage(image);
delete brush;
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_brush_import.moc"
diff --git a/plugins/impex/brush/kis_brush_import.h b/plugins/impex/brush/kis_brush_import.h
index ee4f870acd..86b91a20a6 100644
--- a/plugins/impex/brush/kis_brush_import.h
+++ b/plugins/impex/brush/kis_brush_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_Brush_IMPORT_H_
#define _KIS_Brush_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisBrushImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisBrushImport(QObject *parent, const QVariantList &);
~KisBrushImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/brush/tests/CMakeLists.txt b/plugins/impex/brush/tests/CMakeLists.txt
new file mode 100644
index 0000000000..d150e0f759
--- /dev/null
+++ b/plugins/impex/brush/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(KisBrushTest.cpp
+ TEST_NAME KisBrushTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/brush/tests/KisBrushTest.cpp
similarity index 63%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/brush/tests/KisBrushTest.cpp
index 6ca66688f8..32877619f1 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/brush/tests/KisBrushTest.cpp
@@ -1,40 +1,57 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisBrushTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString BrushMimetype = "image/x-gimp-brush";
+
+
+
+void KisBrushTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), BrushMimetype);
+}
+
+
+void KisBrushTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), BrushMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisBrushTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), BrushMimetype);
+}
+
+
+
+KISTEST_MAIN(KisBrushTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/brush/tests/KisBrushTest.h
similarity index 73%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/brush/tests/KisBrushTest.h
index 4dad62dfca..0defe86df7 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/brush/tests/KisBrushTest.h
@@ -1,32 +1,35 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_BRUSH_TEST_H_
+#define _KIS_BRUSH_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisBrushTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_BRUSH_TEST_H_
+
diff --git a/plugins/impex/brush/tests/data/incorrectFormatFile.txt b/plugins/impex/brush/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/brush/tests/data/readonlyFile.txt b/plugins/impex/brush/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/brush/tests/data/writeonlyFile.txt b/plugins/impex/brush/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/brush/wdg_export_gih.ui b/plugins/impex/brush/wdg_export_gih.ui
index 32fad06607..1aeea206a1 100644
--- a/plugins/impex/brush/wdg_export_gih.ui
+++ b/plugins/impex/brush/wdg_export_gih.ui
@@ -1,202 +1,277 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgExportGih</class>
<widget class="QWidget" name="WdgExportGih">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>468</width>
- <height>318</height>
+ <height>429</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="nameLineEdit"/>
</item>
<item row="1" column="1">
<widget class="KisSpacingSelectionWidget" name="spacingWidget" native="true"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="spacingLbl">
<property name="text">
<string>Spacing:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="brushNameLbl">
<property name="text">
<string>Name:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="colorAsMask">
<property name="text">
<string>Create mask from color</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="minimumSize">
<size>
<width>0</width>
- <height>110</height>
+ <height>0</height>
</size>
</property>
<property name="title">
<string>Brush Style</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
- <layout class="QGridLayout" name="gridLayout_2">
- <property name="spacing">
+ <layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0">
+ <property name="horizontalSpacing">
+ <number>9</number>
+ </property>
+ <property name="verticalSpacing">
<number>3</number>
</property>
<item row="0" column="0">
- <widget class="QLabel" name="textLabel2">
+ <widget class="QLabel" name="styleLbl">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Style:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="brushStyle">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Regular</string>
</property>
</item>
<item>
<property name="text">
<string>Animated</string>
</property>
</item>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="textLabel3">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Selection mode:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="cmbSelectionMode">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="currentIndex">
- <number>2</number>
- </property>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="animStyleGroup">
+ <property name="title">
+ <string>Animated style options</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
+ <item row="2" column="0">
+ <layout class="QVBoxLayout" name="animStyleLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,1">
<item>
- <property name="text">
- <string>Constant</string>
- </property>
+ <widget class="QLabel" name="dimensionLbl">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Dimensions</string>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </widget>
</item>
<item>
- <property name="text">
- <string>Random</string>
- </property>
+ <widget class="QSpinBox" name="dimensionSpin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ </widget>
</item>
<item>
- <property name="text">
- <string>Incremental</string>
- </property>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
</item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
<item>
- <property name="text">
- <string>Pressure</string>
- </property>
+ <widget class="QLabel" name="selectionModeLbl">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Selection mode:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
</item>
<item>
- <property name="text">
- <string>Angular</string>
- </property>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
</item>
- </widget>
+ </layout>
</item>
</layout>
</item>
+ <item row="3" column="0">
+ <layout class="QVBoxLayout" name="dimRankLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ </layout>
+ </item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisSpacingSelectionWidget</class>
<extends>QWidget</extends>
<header>kis_spacing_selection_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp
index 6feedd40bc..7ebf6d03fb 100644
--- a/plugins/impex/csv/csv_loader.cpp
+++ b/plugins/impex/csv/csv_loader.cpp
@@ -1,489 +1,488 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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 "csv_loader.h"
#include <QDebug>
#include <QApplication>
#include <QFile>
#include <QVector>
#include <QIODevice>
#include <QStatusBar>
#include <QFileInfo>
#include <KisPart.h>
#include <KisView.h>
#include <KisDocument.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <kis_debug.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_raster_keyframe_channel.h>
#include <kis_image_animation_interface.h>
#include <kis_time_range.h>
#include "csv_read_line.h"
#include "csv_layer_record.h"
CSVLoader::CSVLoader(KisDocument *doc, bool batchMode)
: m_image(0)
, m_doc(doc)
, m_batchMode(batchMode)
, m_stop(false)
{
}
CSVLoader::~CSVLoader()
{
}
-KisImageBuilder_Result CSVLoader::decode(QIODevice *io, const QString &filename)
+KisImportExportErrorCode CSVLoader::decode(QIODevice *io, const QString &filename)
{
QString field;
int idx;
int frame = 0;
QString projName;
int width = 0;
int height = 0;
int frameCount = 1;
float framerate = 24.0;
float pixelRatio = 1.0;
int projNameIdx = -1;
int widthIdx = -1;
int heightIdx = -1;
int frameCountIdx = -1;
int framerateIdx = -1;
int pixelRatioIdx = -1;
QVector<CSVLayerRecord*> layers;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
idx = filename.lastIndexOf(QRegExp("[\\/]"));
QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator
QString path = filename;
if (path.right(4).toUpper() == ".CSV")
path = path.left(path.size() - 4);
//according to the QT docs, the slash is a universal directory separator
path.append(".frames/");
- KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK;
+ KisImportExportErrorCode retval = ImportExportCodes::OK;
dbgFile << "pos:" << io->pos();
CSVReadLine readLine;
QScopedPointer<KisDocument> importDoc(KisPart::instance()->createDocument());
importDoc->setInfiniteAutoSaveInterval();
importDoc->setFileBatchMode(true);
KisView *setView(0);
if (!m_batchMode) {
// TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater()
// //show the statusbar message even if no view
// Q_FOREACH (KisView* view, KisPart::instance()->views()) {
// if (view && view->document() == m_doc) {
// setView = view;
// break;
// }
// }
// if (!setView) {
// QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
// if (sb) {
// sb->showMessage(i18n("Loading CSV file..."));
// }
// } else {
// emit m_doc->statusBarMessage(i18n("Loading CSV file..."));
// }
// emit m_doc->sigProgress(0);
// connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
}
int step = 0;
do {
qApp->processEvents();
if (m_stop) {
- retval = KisImageBuilder_RESULT_CANCEL;
+ retval = ImportExportCodes::Cancelled;
break;
}
if ((idx = readLine.nextLine(io)) <= 0) {
if ((idx < 0) ||(step < 5))
- retval = KisImageBuilder_RESULT_FAILURE;
-
+ retval = ImportExportCodes::FileFormatIncorrect;
break;
}
field = readLine.nextField(); //first field of the line
if (field.isNull()) continue; //empty row
switch (step) {
case 0 : //skip first row
step = 1;
break;
case 1 : //scene header names
step = 2;
for (idx = 0; !field.isNull(); idx++) {
if (field == "Project Name") {
projNameIdx = idx;
} else if (field == "Width") {
widthIdx = idx;
} else if (field == "Height") {
heightIdx = idx;
} else if (field == "Frame Count") {
frameCountIdx = idx;
} else if (field == "Frame Rate") {
framerateIdx = idx;
} else if (field == "Pixel Aspect Ratio") {
pixelRatioIdx = idx;
}
field= readLine.nextField();
}
break;
case 2 : //scene header values
step= 3;
for (idx= 0; !field.isNull(); idx++) {
if (idx == projNameIdx) {
projName = field;
} else if (idx == widthIdx) {
width = field.toInt();
} else if (idx == heightIdx) {
height = field.toInt();
} else if (idx == frameCountIdx) {
frameCount = field.toInt();
if (frameCount < 1) frameCount= 1;
} else if (idx == framerateIdx) {
framerate = field.toFloat();
} else if (idx == pixelRatioIdx) {
pixelRatio = field.toFloat();
}
field= readLine.nextField();
}
if ((width < 1) || (height < 1)) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName);
break;
case 3 : //create level headers
if (field[0] != '#') break;
for (; !(field = readLine.nextField()).isNull(); ) {
CSVLayerRecord* layerRecord = new CSVLayerRecord();
layers.append(layerRecord);
}
readLine.rewind();
field = readLine.nextField();
step = 4;
Q_FALLTHROUGH();
case 4 : //level header
if (field == "#Layers") {
//layer name
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->name = field;
break;
}
if (field == "#Density") {
//layer opacity
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->density = field.toFloat();
break;
}
if (field == "#Blending") {
//layer blending mode
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->blending = field;
break;
}
if (field == "#Visible") {
//layer visibility
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->visible = field.toInt();
break;
}
if (field == "#Folder") {
//CSV 1.1 folder location
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->path = validPath(field, base);
break;
}
if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
step = 5;
Q_FALLTHROUGH();
case 5 : //frames
if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) {
CSVLayerRecord* layer = layers.at(idx);
if (layer->last != field) {
if (!m_batchMode) {
//emit m_doc->sigProgress((frame * layers.size() + idx) * 100 /
// (frameCount * layers.size()));
}
retval = setLayer(layer, importDoc.data(), path);
layer->last = field;
layer->frame = frame;
}
}
frame++;
break;
}
- } while (retval == KisImageBuilder_RESULT_OK);
+ } while (retval.isOk());
//finish the layers
- if (retval == KisImageBuilder_RESULT_OK) {
+ if (retval.isOk()) {
if (m_image) {
KisImageAnimationInterface *animation = m_image->animationInterface();
if (frame > frameCount)
frameCount = frame;
animation->setFullClipRange(KisTimeRange::fromTime(0,frameCount - 1));
animation->setFramerate((int)framerate);
}
for (idx = 0; idx < layers.size(); idx++) {
CSVLayerRecord* layer = layers.at(idx);
//empty layers without any pictures are dropped
if ((layer->frame > 0) || !layer->last.isEmpty()) {
retval = setLayer(layer, importDoc.data(), path);
- if (retval != KisImageBuilder_RESULT_OK)
+ if (!retval.isOk())
break;
}
}
}
if (m_image) {
//insert the existing layers by the right order
for (idx = layers.size() - 1; idx >= 0; idx--) {
CSVLayerRecord* layer = layers.at(idx);
if (layer->layer) {
m_image->addNode(layer->layer, m_image->root());
}
}
m_image->unlock();
}
qDeleteAll(layers);
io->close();
if (!m_batchMode) {
// disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
// emit m_doc->sigProgress(100);
if (!setView) {
QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
if (sb) {
sb->clearMessage();
}
} else {
emit m_doc->clearStatusBarMessage();
}
}
QApplication::restoreOverrideCursor();
return retval;
}
QString CSVLoader::convertBlending(const QString &blending)
{
if (blending == "Color") return COMPOSITE_OVER;
if (blending == "Behind") return COMPOSITE_BEHIND;
if (blending == "Erase") return COMPOSITE_ERASE;
// "Shade"
if (blending == "Light") return COMPOSITE_LINEAR_LIGHT;
if (blending == "Colorize") return COMPOSITE_COLORIZE;
if (blending == "Hue") return COMPOSITE_HUE;
if (blending == "Add") return COMPOSITE_ADD;
if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT;
if (blending == "Multiply") return COMPOSITE_MULT;
if (blending == "Screen") return COMPOSITE_SCREEN;
// "Replace"
// "Substitute"
if (blending == "Difference") return COMPOSITE_DIFF;
if (blending == "Divide") return COMPOSITE_DIVIDE;
if (blending == "Overlay") return COMPOSITE_OVERLAY;
if (blending == "Light2") return COMPOSITE_DODGE;
if (blending == "Shade2") return COMPOSITE_BURN;
if (blending == "HardLight") return COMPOSITE_HARD_LIGHT;
if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP;
if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT;
if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE;
if (blending == "Sub2") return COMPOSITE_SUBTRACT;
if (blending == "Darken") return COMPOSITE_DARKEN;
if (blending == "Lighten") return COMPOSITE_LIGHTEN;
if (blending == "Saturation") return COMPOSITE_SATURATION;
return COMPOSITE_OVER;
}
QString CSVLoader::validPath(const QString &path,const QString &base)
{
//replace Windows directory separators with the universal /
QString tryPath= QString(path).replace(QString("\\"), QString("/"));
int i = tryPath.lastIndexOf("/");
if (i == (tryPath.size() - 1))
tryPath= tryPath.left(i); //remove the ending separator if exists
if (QFileInfo(tryPath).isDir())
return tryPath.append("/");
QString scan(tryPath);
i = -1;
while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) {
//avoid testing if the next level will be the default xxxx.layers folder
if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue;
tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a /
if (QFileInfo(tryPath).isDir())
return tryPath.append("/");
}
return QString(); //NULL string
}
-KisImageBuilder_Result CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path)
+KisImportExportErrorCode CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path)
{
bool result = true;
if (layer->channel == 0) {
//create a new document layer
float opacity = layer->density;
if (opacity > 1.0)
opacity = 1.0;
else if (opacity < 0.0)
opacity = 0.0;
const KoColorSpace* cs = m_image->colorSpace();
const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name;
KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName,
(quint8)(opacity * OPACITY_OPAQUE_U8), cs);
paintLayer->setCompositeOpId(convertBlending(layer->blending));
paintLayer->setVisible(layer->visible);
paintLayer->enableAnimation();
layer->layer = paintLayer;
layer->channel = qobject_cast<KisRasterKeyframeChannel*>
(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true));
}
if (!layer->last.isEmpty()) {
//png image
QString filename = layer->path.isNull() ? path : layer->path;
filename.append(layer->last);
result = importDoc->openUrl(QUrl::fromLocalFile(filename),
KisDocument::DontAddToRecent);
if (result)
layer->channel->importFrame(layer->frame, importDoc->image()->projection(), 0);
} else {
//blank
layer->channel->addKeyframe(layer->frame);
}
- return (result) ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE;
+ return (result) ? ImportExportCodes::OK : ImportExportCodes::Failure;
}
-KisImageBuilder_Result CSVLoader::createNewImage(int width, int height, float ratio, const QString &name)
+KisImportExportErrorCode CSVLoader::createNewImage(int width, int height, float ratio, const QString &name)
{
//the CSV is RGBA 8bits, sRGB
if (!m_image) {
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0);
if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name);
- if (!m_image) return KisImageBuilder_RESULT_FAILURE;
+ if (!m_image) return ImportExportCodes::Failure;
m_image->setResolution(ratio, 1.0);
m_image->lock();
}
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
-KisImageBuilder_Result CSVLoader::buildAnimation(QIODevice *io, const QString &filename)
+KisImportExportErrorCode CSVLoader::buildAnimation(QIODevice *io, const QString &filename)
{
return decode(io, filename);
}
KisImageSP CSVLoader::image()
{
return m_image;
}
void CSVLoader::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/csv/csv_loader.h b/plugins/impex/csv/csv_loader.h
index 1b419aa031..8a1d1b5ef4 100644
--- a/plugins/impex/csv/csv_loader.h
+++ b/plugins/impex/csv/csv_loader.h
@@ -1,62 +1,62 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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 CSV_LOADER_H_
#define CSV_LOADER_H_
#include <QObject>
#include <QFileInfo>
#include "kis_image.h"
#include "kritaui_export.h"
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
class KisDocument;
#include "csv_layer_record.h"
class CSVLoader : public QObject {
Q_OBJECT
public:
CSVLoader(KisDocument* doc, bool batchMode);
~CSVLoader() override;
- KisImageBuilder_Result buildAnimation(QIODevice *io, const QString &filename);
+ KisImportExportErrorCode buildAnimation(QIODevice *io, const QString &filename);
KisImageSP image();
private:
- KisImageBuilder_Result decode(QIODevice *io, const QString &filename);
- KisImageBuilder_Result setLayer(CSVLayerRecord* , KisDocument* ,const QString &);
- KisImageBuilder_Result createNewImage(int, int, float, const QString &);
+ KisImportExportErrorCode decode(QIODevice *io, const QString &filename);
+ KisImportExportErrorCode setLayer(CSVLayerRecord* , KisDocument* ,const QString &);
+ KisImportExportErrorCode createNewImage(int, int, float, const QString &);
QString convertBlending(const QString &);
QString validPath(const QString &, const QString &);
private Q_SLOTS:
void cancel();
private:
KisImageSP m_image;
KisDocument* m_doc;
bool m_batchMode;
bool m_stop;
};
#endif
diff --git a/plugins/impex/csv/csv_saver.cpp b/plugins/impex/csv/csv_saver.cpp
index e76ee539b8..ea6e7a9ba1 100644
--- a/plugins/impex/csv/csv_saver.cpp
+++ b/plugins/impex/csv/csv_saver.cpp
@@ -1,478 +1,476 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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 "csv_saver.h"
#include <QDebug>
#include <QApplication>
#include <QFileInfo>
#include <QFile>
#include <QDir>
#include <QVector>
#include <QIODevice>
#include <QRect>
#include <KisMimeDatabase.h>
#include <KisPart.h>
#include <KisDocument.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <kis_annotation.h>
#include <kis_types.h>
#include <kis_debug.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <kis_raster_keyframe_channel.h>
#include <kis_image_animation_interface.h>
#include <kis_time_range.h>
#include <kis_iterator_ng.h>
#include "csv_layer_record.h"
CSVSaver::CSVSaver(KisDocument *doc, bool batchMode)
: m_image(doc->savingImage())
, m_doc(doc)
, m_batchMode(batchMode)
, m_stop(false)
{
}
CSVSaver::~CSVSaver()
{
}
KisImageSP CSVSaver::image()
{
return m_image;
}
-KisImageBuilder_Result CSVSaver::encode(QIODevice *io)
+KisImportExportErrorCode CSVSaver::encode(QIODevice *io)
{
int idx;
int start, end;
KisNodeSP node;
QByteArray ba;
KisKeyframeSP keyframe;
QVector<CSVLayerRecord*> layers;
KisImageAnimationInterface *animation = m_image->animationInterface();
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
// XXX: Stream was unused?
// //DataStream instead of TextStream for correct line endings
// QDataStream stream(&f);
//Using the original local path
QString path = m_doc->localFilePath();
if (path.right(4).toUpper() == ".CSV")
path = path.left(path.size() - 4);
else {
// something is wrong: the local file name is not .csv!
// trying the given (probably temporary) filename as well
KIS_SAFE_ASSERT_RECOVER(0 && "Wrong extension of the saved file!") {
path = path.left(path.size() - 4);
}
}
path.append(".frames");
//create directory
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
//according to the QT docs, the slash is a universal directory separator
path.append("/");
node = m_image->rootLayer()->firstChild();
//TODO: correct handling of the layer tree.
//for now, only top level paint layers are saved
idx = 0;
while (node) {
if (node->inherits("KisLayer")) {
KisLayer* paintLayer = qobject_cast<KisLayer*>(node.data());
CSVLayerRecord* layerRecord = new CSVLayerRecord();
layers.prepend(layerRecord); //reverse order!
layerRecord->name = paintLayer->name();
layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_");
if (layerRecord->name.isEmpty())
layerRecord->name= QString("Unnamed-%1").arg(idx);
layerRecord->visible = (paintLayer->visible()) ? 1 : 0;
layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8;
layerRecord->blending = convertToBlending(paintLayer->compositeOpId());
layerRecord->layer = paintLayer;
layerRecord->channel = paintLayer->original()->keyframeChannel();
layerRecord->last = "";
layerRecord->frame = 0;
idx++;
}
node = node->nextSibling();
}
KisTimeRange range = animation->fullClipRange();
start = (range.isValid()) ? range.start() : 0;
if (!range.isInfinite()) {
end = range.end();
if (end < start) end = start;
} else {
//undefined length, searching for the last keyframe
end = start;
for (idx = 0; idx < layers.size(); idx++) {
KisRasterKeyframeChannel *channel = layers.at(idx)->channel;
if (channel) {
keyframe = channel->lastKeyframe();
if ( (!keyframe.isNull()) && (keyframe->time() > end) )
end = keyframe->time();
}
}
}
//create temporary doc for exporting
QScopedPointer<KisDocument> exportDoc(KisPart::instance()->createDocument());
createTempImage(exportDoc.data());
- KisImageBuilder_Result retval= KisImageBuilder_RESULT_OK;
+ KisImportExportErrorCode retval= ImportExportCodes::OK;
if (!m_batchMode) {
// TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater()
//emit m_doc->statusBarMessage(i18n("Saving CSV file..."));
//emit m_doc->sigProgress(0);
//connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
}
int frame = start;
int step = 0;
do {
qApp->processEvents();
if (m_stop) {
- retval = KisImageBuilder_RESULT_CANCEL;
+ retval = ImportExportCodes::Cancelled;
break;
}
switch(step) {
case 0 : //first row
if (io->write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
}
break;
case 1 : //scene header names
if (io->write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
}
break;
case 2 : //scene header values
ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8();
if (io->write(ba.data()) < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8();
if (io->write(ba.data()) < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8();
if (io->write(ba.data()) < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
//the framerate is an integer here
ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8();
if (io->write(ba.data()) < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8();
if (io->write(ba.data()) < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
break;
case 3 : //layer header values
if (io->write("#Layers") < 0) { //Layers
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
break;
case 4 :
if (io->write("\r\n#Density") < 0) { //Density
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
break;
case 5 :
if (io->write("\r\n#Blending") < 0) { //Blending
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
break;
case 6 :
if (io->write("\r\n#Visible") < 0) { //Visible
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
if (idx < layers.size()) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
}
break;
default : //frames
if (frame > end) {
if (io->write("\r\n") < 0)
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
step = 8;
break;
}
ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8();
if (io->write(ba.data()) < 0) {
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
CSVLayerRecord *layer = layers.at(idx);
KisRasterKeyframeChannel *channel = layer->channel;
if (channel) {
if (frame == start) {
keyframe = channel->activeKeyframeAt(frame);
} else {
keyframe = channel->keyframeAt(frame);
}
} else {
keyframe.clear(); // without animation
}
if ( !keyframe.isNull() || (frame == start) ) {
if (!m_batchMode) {
//emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 /
// ((end - start) * layers.size()));
}
retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx);
- if (retval != KisImageBuilder_RESULT_OK)
+ if (!retval.isOk())
break;
}
ba = QString(", \"%1\"").arg(layer->last).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
if (idx < layers.size())
- retval = KisImageBuilder_RESULT_FAILURE;
+ retval = ImportExportCodes::Failure;
frame++;
step = 6; //keep step here
break;
}
step++;
- } while((retval == KisImageBuilder_RESULT_OK) && (step < 8));
+ } while((retval.isOk()) && (step < 8));
qDeleteAll(layers);
// io->close(); it seems this is not required anymore
if (!m_batchMode) {
//disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
//emit m_doc->sigProgress(100);
//emit m_doc->clearStatusBarMessage();
}
QApplication::restoreOverrideCursor();
return retval;
}
QString CSVSaver::convertToBlending(const QString &opid)
{
if (opid == COMPOSITE_OVER) return "Color";
if (opid == COMPOSITE_BEHIND) return "Behind";
if (opid == COMPOSITE_ERASE) return "Erase";
// "Shade"
if (opid == COMPOSITE_LINEAR_LIGHT) return "Light";
if (opid == COMPOSITE_COLORIZE) return "Colorize";
if (opid == COMPOSITE_HUE) return "Hue";
if ((opid == COMPOSITE_ADD) ||
(opid == COMPOSITE_LINEAR_DODGE)) return "Add";
if (opid == COMPOSITE_INVERSE_SUBTRACT) return "Sub";
if (opid == COMPOSITE_MULT) return "Multiply";
if (opid == COMPOSITE_SCREEN) return "Screen";
// "Replace"
// "Substitute"
if (opid == COMPOSITE_DIFF) return "Difference";
if (opid == COMPOSITE_DIVIDE) return "Divide";
if (opid == COMPOSITE_OVERLAY) return "Overlay";
if (opid == COMPOSITE_DODGE) return "Light2";
if (opid == COMPOSITE_BURN) return "Shade2";
if (opid == COMPOSITE_HARD_LIGHT) return "HardLight";
if ((opid == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) ||
(opid == COMPOSITE_SOFT_LIGHT_SVG)) return "SoftLight";
if (opid == COMPOSITE_GRAIN_EXTRACT) return "GrainExtract";
if (opid == COMPOSITE_GRAIN_MERGE) return "GrainMerge";
if (opid == COMPOSITE_SUBTRACT) return "Sub2";
if (opid == COMPOSITE_DARKEN) return "Darken";
if (opid == COMPOSITE_LIGHTEN) return "Lighten";
if (opid == COMPOSITE_SATURATION) return "Saturation";
return "Color";
}
-KisImageBuilder_Result CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx)
+KisImportExportErrorCode CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx)
{
//render to the temp layer
KisImageSP image = exportDoc->savingImage();
if (!image) image= exportDoc->image();
KisPaintDeviceSP device = image->rootLayer()->firstChild()->projection();
if (!keyframe.isNull()) {
layer->channel->fetchFrame(keyframe, device);
} else {
device->makeCloneFrom(layer->layer->projection(),image->bounds()); // without animation
}
QRect bounds = device->exactBounds();
if (bounds.isEmpty()) {
layer->last = ""; //empty frame
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
layer->last = QString("frame%1-%2.png").arg(idx + 1,5,10,QChar('0')).arg(frame,5,10,QChar('0'));
QString filename = path;
filename.append(layer->last);
//save to PNG
KisSequentialConstIterator it(device, image->bounds());
const KoColorSpace* cs = device->colorSpace();
bool isThereAlpha = false;
while (it.nextPixel()) {
if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) {
isThereAlpha = true;
break;
}
}
if (!KisPNGConverter::isColorSpaceSupported(cs)) {
device = new KisPaintDevice(*device.data());
device->convertTo(KoColorSpaceRegistry::instance()->rgb8());
}
KisPNGOptions options;
options.alpha = isThereAlpha;
options.interlace = false;
options.compression = 8;
options.tryToSaveAsIndexed = false;
options.transparencyFillColor = QColor(0,0,0);
options.saveSRGBProfile = true; //TVPaint can use only sRGB
options.forceSRGB = false;
KisPNGConverter kpc(exportDoc);
- KisImageBuilder_Result result = kpc.buildFile(filename, image->bounds(),
+ KisImportExportErrorCode result = kpc.buildFile(filename, image->bounds(),
image->xRes(), image->yRes(), device,
image->beginAnnotations(), image->endAnnotations(),
options, (KisMetaData::Store* )0 );
return result;
}
void CSVSaver::createTempImage(KisDocument* exportDoc)
{
exportDoc->setInfiniteAutoSaveInterval();
exportDoc->setFileBatchMode(true);
KisImageSP exportImage = new KisImage(exportDoc->createUndoStore(),
m_image->width(), m_image->height(), m_image->colorSpace(),
QString());
exportImage->setResolution(m_image->xRes(), m_image->yRes());
exportDoc->setCurrentImage(exportImage);
KisPaintLayer* paintLayer = new KisPaintLayer(exportImage, "paint device", OPACITY_OPAQUE_U8);
exportImage->addNode(paintLayer, exportImage->rootLayer(), KisLayerSP(0));
}
-KisImageBuilder_Result CSVSaver::buildAnimation(QIODevice *io)
+KisImportExportErrorCode CSVSaver::buildAnimation(QIODevice *io)
{
- if (!m_image) {
- return KisImageBuilder_RESULT_EMPTY;
- }
+ KIS_ASSERT_RECOVER_RETURN_VALUE(m_image, ImportExportCodes::InternalError);
return encode(io);
}
void CSVSaver::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/csv/csv_saver.h b/plugins/impex/csv/csv_saver.h
index 395f84ef23..d382d8a818 100644
--- a/plugins/impex/csv/csv_saver.h
+++ b/plugins/impex/csv/csv_saver.h
@@ -1,61 +1,61 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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 CSV_SAVER_H_
#define CSV_SAVER_H_
#include <QObject>
#include <QIODevice>
#include "kis_types.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_png_converter.h"
-/* The KisImageBuilder_Result definitions come from kis_png_converter.h here */
+/* The KisImportExportErrorCode definitions come from kis_png_converter.h here */
#include "csv_layer_record.h"
class KisDocument;
class CSVSaver : public QObject {
Q_OBJECT
public:
CSVSaver(KisDocument* doc, bool batchMode);
~CSVSaver() override;
- KisImageBuilder_Result buildAnimation(QIODevice *io);
+ KisImportExportErrorCode buildAnimation(QIODevice *io);
KisImageSP image();
private:
- KisImageBuilder_Result encode(QIODevice *io);
- KisImageBuilder_Result getLayer(CSVLayerRecord* , KisDocument* , KisKeyframeSP, const QString &, int, int);
+ KisImportExportErrorCode encode(QIODevice *io);
+ KisImportExportErrorCode getLayer(CSVLayerRecord* , KisDocument* , KisKeyframeSP, const QString &, int, int);
void createTempImage(KisDocument* );
QString convertToBlending(const QString &);
private Q_SLOTS:
void cancel();
private:
KisImageSP m_image;
KisDocument* m_doc;
bool m_batchMode;
bool m_stop;
};
#endif
diff --git a/plugins/impex/csv/kis_csv_export.cpp b/plugins/impex/csv/kis_csv_export.cpp
index 402dc70614..f16924aeab 100644
--- a/plugins/impex/csv/kis_csv_export.cpp
+++ b/plugins/impex/csv/kis_csv_export.cpp
@@ -1,80 +1,70 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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_csv_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QApplication>
#include <KisExportCheckRegistry.h>
#include <KisImportExportManager.h>
#include <KoColorSpaceConstants.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include "csv_saver.h"
K_PLUGIN_FACTORY_WITH_JSON(KisCSVExportFactory, "krita_csv_export.json", registerPlugin<KisCSVExport>();)
KisCSVExport::KisCSVExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisCSVExport::~KisCSVExport()
{
}
-KisImportExportFilter::ConversionStatus KisCSVExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisCSVExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
CSVSaver kpc(document, batchMode());
- KisImageBuilder_Result res;
-
- if ((res = kpc.buildAnimation(io)) == KisImageBuilder_RESULT_OK) {
- dbgFile <<"success!";
- return KisImportExportFilter::OK;
- }
- dbgFile <<" Result =" << res;
-
- if (res == KisImageBuilder_RESULT_CANCEL)
- return KisImportExportFilter::ProgressCancelled;
-
- return KisImportExportFilter::InternalError;
+ KisImportExportErrorCode res = kpc.buildAnimation(io);
+ return res;
}
void KisCSVExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("AnimationCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "CSV");
addCapability(KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED));
}
#include "kis_csv_export.moc"
diff --git a/plugins/impex/csv/kis_csv_export.h b/plugins/impex/csv/kis_csv_export.h
index f2063884fb..2ceaa38c4c 100644
--- a/plugins/impex/csv/kis_csv_export.h
+++ b/plugins/impex/csv/kis_csv_export.h
@@ -1,38 +1,38 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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_CSV_EXPORT_H_
#define _KIS_CSV_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisCSVExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisCSVExport(QObject *parent, const QVariantList &);
~KisCSVExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/csv/kis_csv_import.cpp b/plugins/impex/csv/kis_csv_import.cpp
index 4f39d5eb0d..fb9a555d7c 100644
--- a/plugins/impex/csv/kis_csv_import.cpp
+++ b/plugins/impex/csv/kis_csv_import.cpp
@@ -1,77 +1,55 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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_csv_import.h"
#include <kpluginfactory.h>
#include <QDebug>
#include <QFileInfo>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <kis_image.h>
#include "csv_loader.h"
K_PLUGIN_FACTORY_WITH_JSON(CSVImportFactory, "krita_csv_import.json", registerPlugin<KisCSVImport>();)
KisCSVImport::KisCSVImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisCSVImport::~KisCSVImport()
{
}
-KisImportExportFilter::ConversionStatus KisCSVImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisCSVImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
CSVLoader ib(document, batchMode());
-
- KisImageBuilder_Result result = ib.buildAnimation(io, filename());
-
- switch (result) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- return KisImportExportFilter::NotImplemented;
- case KisImageBuilder_RESULT_INVALID_ARG:
- return KisImportExportFilter::BadMimeType;
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_EXIST:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- qDebug() << "ib returned KisImageBuilder_RESULT_NOT_LOCAL";
- return KisImportExportFilter::FileNotFound;
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- return KisImportExportFilter::ParsingError;
- case KisImageBuilder_RESULT_FAILURE:
- return KisImportExportFilter::InternalError;
- case KisImageBuilder_RESULT_CANCEL:
- return KisImportExportFilter::ProgressCancelled;
- case KisImageBuilder_RESULT_OK:
- document -> setCurrentImage( ib.image());
- return KisImportExportFilter::OK;
- default:
- return KisImportExportFilter::StorageCreationError;
+ KisImportExportErrorCode result = ib.buildAnimation(io, filename());
+ if (result.isOk()) {
+ document->setCurrentImage(ib.image());
}
- return KisImportExportFilter::InternalError;
+ return result;
}
#include <kis_csv_import.moc>
diff --git a/plugins/impex/csv/kis_csv_import.h b/plugins/impex/csv/kis_csv_import.h
index dd342f05e4..edf85ef992 100644
--- a/plugins/impex/csv/kis_csv_import.h
+++ b/plugins/impex/csv/kis_csv_import.h
@@ -1,36 +1,36 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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_CSV_IMPORT_H_
#define _KIS_CSV_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisCSVImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisCSVImport(QObject *parent, const QVariantList &);
~KisCSVImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/csv/tests/data/incorrectFormatFile.txt b/plugins/impex/csv/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/csv/tests/data/readonlyFile.txt b/plugins/impex/csv/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/csv/tests/data/writeonlyFile.txt b/plugins/impex/csv/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/csv/tests/kis_csv_test.cpp b/plugins/impex/csv/tests/kis_csv_test.cpp
index f68aeff2dd..bd804f3aed 100644
--- a/plugins/impex/csv/tests/kis_csv_test.cpp
+++ b/plugins/impex/csv/tests/kis_csv_test.cpp
@@ -1,37 +1,58 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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_csv_test.h"
#include <QTest>
#include <QCoreApplication>
#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 CsvMimetype = "text/csv";
+
void KisCsvTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList());
}
+
+
+void KisCsvTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), CsvMimetype);
+}
+
+
+void KisCsvTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), CsvMimetype);
+}
+
+
+void KisCsvTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), CsvMimetype);
+}
+
QTEST_MAIN(KisCsvTest)
diff --git a/plugins/impex/csv/tests/kis_csv_test.h b/plugins/impex/csv/tests/kis_csv_test.h
index c1df27049b..ae4fb3c76f 100644
--- a/plugins/impex/csv/tests/kis_csv_test.h
+++ b/plugins/impex/csv/tests/kis_csv_test.h
@@ -1,31 +1,34 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* 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_CSV_TEST_H_
#define _KIS_CSV_TEST_H_
#include <QtTest>
class KisCsvTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/impex/exr/exr_converter.cc b/plugins/impex/exr/exr_converter.cc
index 7bcdd2cca7..ec3edd7176 100644
--- a/plugins/impex/exr/exr_converter.cc
+++ b/plugins/impex/exr/exr_converter.cc
@@ -1,1373 +1,1402 @@
/*
* Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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 "exr_converter.h"
#include <half.h>
#include <ImfAttribute.h>
#include <ImfChannelList.h>
#include <ImfInputFile.h>
#include <ImfOutputFile.h>
#include <ImfStringAttribute.h>
#include "exr_extra_tags.h"
#include <QApplication>
#include <QMessageBox>
#include <QDomDocument>
#include <QThread>
#include <QFileInfo>
#include <KoColorSpaceRegistry.h>
#include <KoCompositeOpRegistry.h>
#include <KoColorSpaceTraits.h>
#include <KoColorModelStandardIds.h>
#include <KoColor.h>
+#include <KoColorProfile.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_paint_layer.h>
#include <kis_transaction.h>
#include "kis_iterator_ng.h"
#include <kis_exr_layers_sorter.h>
#include <kis_meta_data_entry.h>
#include <kis_meta_data_schema.h>
#include <kis_meta_data_schema_registry.h>
#include <kis_meta_data_store.h>
#include <kis_meta_data_value.h>
#include "kis_kra_savexml_visitor.h"
+#include <KisImportExportAdditionalChecks.h>
+
// Do not translate!
#define HDR_LAYER "HDR Layer"
template<typename _T_>
struct Rgba {
_T_ r;
_T_ g;
_T_ b;
_T_ a;
};
struct ExrGroupLayerInfo;
struct ExrLayerInfoBase {
ExrLayerInfoBase() : colorSpace(0), parent(0) {
}
const KoColorSpace* colorSpace;
QString name;
const ExrGroupLayerInfo* parent;
};
struct ExrGroupLayerInfo : public ExrLayerInfoBase {
ExrGroupLayerInfo() : groupLayer(0) {}
KisGroupLayerSP groupLayer;
};
enum ImageType {
IT_UNKNOWN,
IT_FLOAT16,
IT_FLOAT32,
IT_UNSUPPORTED
};
struct ExrPaintLayerInfo : public ExrLayerInfoBase {
ExrPaintLayerInfo()
: imageType(IT_UNKNOWN)
{
}
ImageType imageType;
QMap< QString, QString> channelMap; ///< first is either R, G, B or A second is the EXR channel name
struct Remap {
Remap(const QString& _original, const QString& _current) : original(_original), current(_current) {
}
QString original;
QString current;
};
QList< Remap > remappedChannels; ///< this is used to store in the metadata the mapping between exr channel name, and channels used in Krita
void updateImageType(ImageType channelType);
};
void ExrPaintLayerInfo::updateImageType(ImageType channelType)
{
if (imageType == IT_UNKNOWN) {
imageType = channelType;
}
else if (imageType != channelType) {
imageType = IT_UNSUPPORTED;
}
}
struct ExrPaintLayerSaveInfo {
QString name; ///< name of the layer with a "." at the end (ie "group1.group2.layer1.")
KisPaintDeviceSP layerDevice;
KisPaintLayerSP layer;
QList<QString> channels;
Imf::PixelType pixelType;
};
struct EXRConverter::Private {
Private()
: doc(0)
, alphaWasModified(false)
, showNotifications(false)
{}
KisImageSP image;
KisDocument *doc;
bool alphaWasModified;
bool showNotifications;
QString errorMessage;
template <class WrapperType>
void unmultiplyAlpha(typename WrapperType::pixel_type *pixel);
template<typename _T_>
void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
template<typename _T_>
void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
QDomDocument loadExtraLayersInfo(const Imf::Header &header);
bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set<std::string> exrLayerNames);
void makeLayerNamesUnique(QList<ExrPaintLayerSaveInfo>& informationObjects);
void recBuildPaintLayerSaveInfo(QList<ExrPaintLayerSaveInfo>& informationObjects, const QString& name, KisGroupLayerSP parent);
void reportLayersNotSaved(const QSet<KisNodeSP> &layersNotSaved);
QString fetchExtraLayersInfo(QList<ExrPaintLayerSaveInfo>& informationObjects);
};
EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications)
: d(new Private)
{
d->doc = doc;
d->showNotifications = showNotifications;
// Set thread count for IlmImf library
Imf::setGlobalThreadCount(QThread::idealThreadCount());
dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount();
}
EXRConverter::~EXRConverter()
{
}
ImageType imfTypeToKisType(Imf::PixelType type)
{
switch (type) {
case Imf::UINT:
case Imf::NUM_PIXELTYPES:
return IT_UNSUPPORTED;
case Imf::HALF:
return IT_FLOAT16;
case Imf::FLOAT:
return IT_FLOAT32;
default:
qFatal("Out of bound enum");
return IT_UNKNOWN;
}
}
-const KoColorSpace* kisTypeToColorSpace(QString model, ImageType imageType)
+const KoColorSpace *kisTypeToColorSpace(QString colorModelID, ImageType imageType)
{
- const QString profileName = KisConfig(false).readEntry("ExrDefaultColorProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(model));
- switch (imageType) {
+ QString colorDepthID = "UNKNOWN";
+ switch(imageType) {
case IT_FLOAT16:
- return KoColorSpaceRegistry::instance()->colorSpace(model, Float16BitsColorDepthID.id(), profileName);
+ colorDepthID = Float16BitsColorDepthID.id();
+ break;
case IT_FLOAT32:
- return KoColorSpaceRegistry::instance()->colorSpace(model, Float32BitsColorDepthID.id(), profileName);
- case IT_UNKNOWN:
- case IT_UNSUPPORTED:
- return 0;
+ colorDepthID = Float32BitsColorDepthID.id();
+ break;
default:
- qFatal("Out of bound enum");
return 0;
- }
+ };
+
+ const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID);
+ const QString profileName = KisConfig(false).readEntry("ExrDefaultColorProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId));
+
+ return KoColorSpaceRegistry::instance()->colorSpace(colorModelID, colorDepthID, profileName);
+
}
template <typename T>
static inline T alphaEpsilon()
{
return static_cast<T>(HALF_EPSILON);
}
template <typename T>
static inline T alphaNoiseThreshold()
{
return static_cast<T>(0.01); // 1%
}
static inline bool qFuzzyCompare(half p1, half p2)
{
return std::abs(p1 - p2) < float(HALF_EPSILON);
}
static inline bool qFuzzyIsNull(half h)
{
return std::abs(h) < float(HALF_EPSILON);
}
template <typename T>
struct RgbPixelWrapper
{
typedef T channel_type;
typedef Rgba<T> pixel_type;
RgbPixelWrapper(Rgba<T> &_pixel) : pixel(_pixel) {}
inline T alpha() const {
return pixel.a;
}
inline bool checkMultipliedColorsConsistent() const {
return !(std::abs(pixel.a) < alphaEpsilon<T>() &&
(!qFuzzyIsNull(pixel.r) ||
!qFuzzyIsNull(pixel.g) ||
!qFuzzyIsNull(pixel.b)));
}
inline bool checkUnmultipliedColorsConsistent(const Rgba<T> &mult) const {
const T alpha = std::abs(pixel.a);
return alpha >= alphaNoiseThreshold<T>() ||
(qFuzzyCompare(T(pixel.r * alpha), mult.r) &&
qFuzzyCompare(T(pixel.g * alpha), mult.g) &&
qFuzzyCompare(T(pixel.b * alpha), mult.b));
}
inline void setUnmultiplied(const Rgba<T> &mult, T newAlpha) {
const T absoluteAlpha = std::abs(newAlpha);
pixel.r = mult.r / absoluteAlpha;
pixel.g = mult.g / absoluteAlpha;
pixel.b = mult.b / absoluteAlpha;
pixel.a = newAlpha;
}
Rgba<T> &pixel;
};
template <typename T>
struct GrayPixelWrapper
{
typedef T channel_type;
typedef typename KoGrayTraits<T>::Pixel pixel_type;
GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {}
inline T alpha() const {
return pixel.alpha;
}
inline bool checkMultipliedColorsConsistent() const {
return !(std::abs(pixel.alpha) < alphaEpsilon<T>() &&
!qFuzzyIsNull(pixel.gray));
}
inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const {
const T alpha = std::abs(pixel.alpha);
return alpha >= alphaNoiseThreshold<T>() ||
qFuzzyCompare(T(pixel.gray * alpha), mult.gray);
}
inline void setUnmultiplied(const pixel_type &mult, T newAlpha) {
const T absoluteAlpha = std::abs(newAlpha);
pixel.gray = mult.gray / absoluteAlpha;
pixel.alpha = newAlpha;
}
pixel_type &pixel;
};
template <class WrapperType>
void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel)
{
typedef typename WrapperType::pixel_type pixel_type;
typedef typename WrapperType::channel_type channel_type;
WrapperType srcPixel(*pixel);
if (!srcPixel.checkMultipliedColorsConsistent()) {
channel_type newAlpha = srcPixel.alpha();
pixel_type __dstPixelData;
WrapperType dstPixel(__dstPixelData);
/**
* Division by a tiny alpha may result in an overflow of half
* value. That is why we use safe iterational approach.
*/
while (1) {
dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha);
if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) {
break;
}
newAlpha += alphaEpsilon<channel_type>();
alphaWasModified = true;
}
*pixel = dstPixel.pixel;
} else if (srcPixel.alpha() > 0.0) {
srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha());
}
}
template <typename T, typename Pixel, int size, int alphaPos>
void multiplyAlpha(Pixel *pixel)
{
if (alphaPos >= 0) {
T alpha = pixel->data[alphaPos];
if (alpha > 0.0) {
for (int i = 0; i < size; ++i) {
if (i != alphaPos) {
pixel->data[i] *= alpha;
}
}
pixel->data[alphaPos] = alpha;
}
}
}
template<typename _T_>
void EXRConverter::Private::decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
{
typedef Rgba<_T_> Rgba;
QVector<Rgba> pixels(width * height);
bool hasAlpha = info.channelMap.contains("A");
Imf::FrameBuffer frameBuffer;
Rgba* frameBufferData = (pixels.data()) - xstart - ystart * width;
frameBuffer.insert(info.channelMap["R"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->r,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
frameBuffer.insert(info.channelMap["G"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->g,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
frameBuffer.insert(info.channelMap["B"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->b,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
if (hasAlpha) {
frameBuffer.insert(info.channelMap["A"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->a,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
}
file.setFrameBuffer(frameBuffer);
file.readPixels(ystart, height + ystart - 1);
Rgba *rgba = pixels.data();
QRect paintRegion(xstart, ystart, width, height);
KisSequentialIterator it(layer->paintDevice(), paintRegion);
while (it.nextPixel()) {
if (hasAlpha) {
unmultiplyAlpha<RgbPixelWrapper<_T_> >(rgba);
}
typename KoRgbTraits<_T_>::Pixel* dst = reinterpret_cast<typename KoRgbTraits<_T_>::Pixel*>(it.rawData());
dst->red = rgba->r;
dst->green = rgba->g;
dst->blue = rgba->b;
if (hasAlpha) {
dst->alpha = rgba->a;
} else {
dst->alpha = 1.0;
}
++rgba;
}
}
template<typename _T_>
void EXRConverter::Private::decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
{
typedef typename GrayPixelWrapper<_T_>::channel_type channel_type;
typedef typename GrayPixelWrapper<_T_>::pixel_type pixel_type;
KIS_ASSERT_RECOVER_RETURN(
layer->paintDevice()->colorSpace()->colorModelId() == GrayAColorModelID);
QVector<pixel_type> pixels(width * height);
Q_ASSERT(info.channelMap.contains("G"));
dbgFile << "G -> " << info.channelMap["G"];
bool hasAlpha = info.channelMap.contains("A");
dbgFile << "Has Alpha:" << hasAlpha;
Imf::FrameBuffer frameBuffer;
pixel_type* frameBufferData = (pixels.data()) - xstart - ystart * width;
frameBuffer.insert(info.channelMap["G"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->gray,
sizeof(pixel_type) * 1,
sizeof(pixel_type) * width));
if (hasAlpha) {
frameBuffer.insert(info.channelMap["A"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->alpha,
sizeof(pixel_type) * 1,
sizeof(pixel_type) * width));
}
file.setFrameBuffer(frameBuffer);
file.readPixels(ystart, height + ystart - 1);
pixel_type *srcPtr = pixels.data();
QRect paintRegion(xstart, ystart, width, height);
KisSequentialIterator it(layer->paintDevice(), paintRegion);
do {
if (hasAlpha) {
unmultiplyAlpha<GrayPixelWrapper<_T_> >(srcPtr);
}
pixel_type* dstPtr = reinterpret_cast<pixel_type*>(it.rawData());
dstPtr->gray = srcPtr->gray;
dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0);
++srcPtr;
} while (it.nextPixel());
}
bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2)
{
if (idx1 > idx2) return true;
if (group.name == list[idx2]) {
return recCheckGroup(*group.parent, list, idx1, idx2 - 1);
}
return false;
}
ExrGroupLayerInfo* searchGroup(QList<ExrGroupLayerInfo>* groups, QStringList list, int idx1, int idx2)
{
if (idx1 > idx2) {
return 0;
}
// Look for the group
for (int i = 0; i < groups->size(); ++i) {
if (recCheckGroup(groups->at(i), list, idx1, idx2)) {
return &(*groups)[i];
}
}
// Create the group
ExrGroupLayerInfo info;
info.name = list.at(idx2);
info.parent = searchGroup(groups, list, idx1, idx2 - 1);
groups->append(info);
return &groups->last();
}
QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header)
{
const Imf::StringAttribute *layersInfoAttribute =
header.findTypedAttribute<Imf::StringAttribute>(EXR_KRITA_LAYERS);
if (!layersInfoAttribute) return QDomDocument();
QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str());
QDomDocument doc;
doc.setContent(layersInfoString);
return doc;
}
bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set<std::string> exrLayerNames)
{
std::set<std::string> extraInfoLayers;
QDomElement root = doc.documentElement();
KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; };
QDomElement el = root.firstChildElement();
while(!el.isNull()) {
KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; };
QString layerName = el.attribute(EXR_NAME).toUtf8();
if (layerName != QString(HDR_LAYER)) {
extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData());
}
el = el.nextSiblingElement();
}
bool result = (extraInfoLayers == exrLayerNames);
if (!result) {
dbgKrita << "WARINING: Krita EXR extra layers info is inconsistent!";
dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size());
std::set<std::string>::const_iterator it1 = extraInfoLayers.begin();
std::set<std::string>::const_iterator it2 = exrLayerNames.begin();
std::set<std::string>::const_iterator end1 = extraInfoLayers.end();
for (; it1 != end1; ++it1, ++it2) {
dbgKrita << it1->c_str() << it2->c_str();
}
}
return result;
}
-KisImageBuilder_Result EXRConverter::decode(const QString &filename)
+KisImportExportErrorCode EXRConverter::decode(const QString &filename)
{
- Imf::InputFile file(QFile::encodeName(filename));
+ try {
+ Imf::InputFile file(QFile::encodeName(filename));
- Imath::Box2i dw = file.header().dataWindow();
- Imath::Box2i displayWindow = file.header().displayWindow();
+ Imath::Box2i dw = file.header().dataWindow();
+ Imath::Box2i displayWindow = file.header().displayWindow();
- int width = dw.max.x - dw.min.x + 1;
- int height = dw.max.y - dw.min.y + 1;
- int dx = dw.min.x;
- int dy = dw.min.y;
+ int width = dw.max.x - dw.min.x + 1;
+ int height = dw.max.y - dw.min.y + 1;
+ int dx = dw.min.x;
+ int dy = dw.min.y;
- // Display the attributes of a file
- for (Imf::Header::ConstIterator it = file.header().begin();
- it != file.header().end(); ++it) {
- dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName();
- }
+ // Display the attributes of a file
+ for (Imf::Header::ConstIterator it = file.header().begin();
+ it != file.header().end(); ++it) {
+ dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName();
+ }
- // fetch Krita's extra layer info, which might have been stored previously
- QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header());
+ // fetch Krita's extra layer info, which might have been stored previously
+ QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header());
- // Construct the list of LayerInfo
+ // Construct the list of LayerInfo
- QList<ExrPaintLayerInfo> informationObjects;
- QList<ExrGroupLayerInfo> groups;
+ QList<ExrPaintLayerInfo> informationObjects;
+ QList<ExrGroupLayerInfo> groups;
- ImageType imageType = IT_UNKNOWN;
+ ImageType imageType = IT_UNKNOWN;
- const Imf::ChannelList &channels = file.header().channels();
- std::set<std::string> layerNames;
- channels.layers(layerNames);
+ const Imf::ChannelList &channels = file.header().channels();
+ std::set<std::string> layerNames;
+ channels.layers(layerNames);
- if (!extraLayersInfo.isNull() &&
- !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) {
+ if (!extraLayersInfo.isNull() &&
+ !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) {
- // it is inconsistent anyway
- extraLayersInfo = QDomDocument();
- }
+ // it is inconsistent anyway
+ extraLayersInfo = QDomDocument();
+ }
- // Check if there are A, R, G, B channels
-
- dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:";
- ExrPaintLayerInfo info;
- bool topLevelRGBFound = false;
- info.name = HDR_LAYER;
-
- QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B"
- << ".A" << ".R" << ".G" << ".B"
- << "A." << "R." << "G." << "B."
- << "A." << "R." << "G." << "B."
- << ".alpha" << ".red" << ".green" << ".blue";
-
- for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
- const Imf::Channel &channel = i.channel();
- dbgFile << "Channel name = " << i.name() << " type = " << channel.type;
-
- QString qname = i.name();
- if (topLevelChannelNames.contains(qname)) {
- topLevelRGBFound = true;
- dbgFile << "Found top-level channel" << qname;
- info.channelMap[qname] = qname;
- info.updateImageType(imfTypeToKisType(channel.type));
+ // Check if there are A, R, G, B channels
+
+ dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:";
+ ExrPaintLayerInfo info;
+ bool topLevelRGBFound = false;
+ info.name = HDR_LAYER;
+
+ QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B"
+ << ".A" << ".R" << ".G" << ".B"
+ << "A." << "R." << "G." << "B."
+ << "A." << "R." << "G." << "B."
+ << ".alpha" << ".red" << ".green" << ".blue";
+
+ for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
+ const Imf::Channel &channel = i.channel();
+ dbgFile << "Channel name = " << i.name() << " type = " << channel.type;
+
+ QString qname = i.name();
+ if (topLevelChannelNames.contains(qname)) {
+ topLevelRGBFound = true;
+ dbgFile << "Found top-level channel" << qname;
+ info.channelMap[qname] = qname;
+ info.updateImageType(imfTypeToKisType(channel.type));
+ }
+ // Channel names that don't contain a "." or that contain a
+ // "." only at the beginning or at the end are not considered
+ // to be part of any layer.
+ else if (!qname.contains('.')
+ || !qname.mid(1).contains('.')
+ || !qname.left(qname.size() - 1).contains('.')) {
+ warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel.";
+ }
}
- // Channel names that don't contain a "." or that contain a
- // "." only at the beginning or at the end are not considered
- // to be part of any layer.
- else if (!qname.contains('.')
- || !qname.mid(1).contains('.')
- || !qname.left(qname.size() - 1).contains('.')) {
- warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel.";
+ if (topLevelRGBFound) {
+ dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType;
+ informationObjects.push_back(info);
+ imageType = info.imageType;
}
- }
- if (topLevelRGBFound) {
- dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType;
- informationObjects.push_back(info);
- imageType = info.imageType;
- }
- dbgFile << "Extra layers:" << layerNames.size();
+ dbgFile << "Extra layers:" << layerNames.size();
- for (std::set<std::string>::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) {
+ for (std::set<std::string>::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) {
- info = ExrPaintLayerInfo();
+ info = ExrPaintLayerInfo();
- dbgFile << "layer name = " << i->c_str();
- info.name = i->c_str();
- Imf::ChannelList::ConstIterator layerBegin, layerEnd;
- channels.channelsInLayer(*i, layerBegin, layerEnd);
- for (Imf::ChannelList::ConstIterator j = layerBegin;
- j != layerEnd; ++j) {
- const Imf::Channel &channel = j.channel();
+ dbgFile << "layer name = " << i->c_str();
+ info.name = i->c_str();
+ Imf::ChannelList::ConstIterator layerBegin, layerEnd;
+ channels.channelsInLayer(*i, layerBegin, layerEnd);
+ for (Imf::ChannelList::ConstIterator j = layerBegin;
+ j != layerEnd; ++j) {
+ const Imf::Channel &channel = j.channel();
- info.updateImageType(imfTypeToKisType(channel.type));
+ info.updateImageType(imfTypeToKisType(channel.type));
- QString qname = j.name();
- QStringList list = qname.split('.');
- QString layersuffix = list.last();
+ QString qname = j.name();
+ QStringList list = qname.split('.');
+ QString layersuffix = list.last();
- dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type;
+ dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type;
- // Nuke writes the channels for sublayers as .red instead of .R, so convert those.
- // See https://bugs.kde.org/show_bug.cgi?id=393771
- if (topLevelChannelNames.contains("." + layersuffix)) {
- layersuffix = layersuffix.at(0).toUpper();
- }
- dbgFile << "\t\tsuffix" << layersuffix;
+ // Nuke writes the channels for sublayers as .red instead of .R, so convert those.
+ // See https://bugs.kde.org/show_bug.cgi?id=393771
+ if (topLevelChannelNames.contains("." + layersuffix)) {
+ layersuffix = layersuffix.at(0).toUpper();
+ }
+ dbgFile << "\t\tsuffix" << layersuffix;
- if (list.size() > 1) {
- info.name = list[list.size()-2];
- info.parent = searchGroup(&groups, list, 0, list.size() - 3);
- }
+ if (list.size() > 1) {
+ info.name = list[list.size()-2];
+ info.parent = searchGroup(&groups, list, 0, list.size() - 3);
+ }
- info.channelMap[layersuffix] = qname;
- }
+ info.channelMap[layersuffix] = qname;
+ }
- if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) {
- informationObjects.push_back(info);
- if (imageType < info.imageType) {
- imageType = info.imageType;
+ if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) {
+ informationObjects.push_back(info);
+ if (imageType < info.imageType) {
+ imageType = info.imageType;
+ }
}
}
- }
- dbgFile << "File has" << informationObjects.size() << "layer(s)";
-
- // Set the colorspaces
- for (int i = 0; i < informationObjects.size(); ++i) {
- ExrPaintLayerInfo& info = informationObjects[i];
- QString modelId;
-
- if (info.channelMap.size() == 1) {
- modelId = GrayAColorModelID.id();
- QString key = info.channelMap.begin().key();
- if (key != "G") {
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G"));
- QString channel = info.channelMap.begin().value();
- info.channelMap.clear();
- info.channelMap["G"] = channel;
+ dbgFile << "File has" << informationObjects.size() << "layer(s)";
+
+ // Set the colorspaces
+ for (int i = 0; i < informationObjects.size(); ++i) {
+ ExrPaintLayerInfo& info = informationObjects[i];
+ QString modelId;
+
+ if (info.channelMap.size() == 1) {
+ modelId = GrayAColorModelID.id();
+ QString key = info.channelMap.begin().key();
+ if (key != "G") {
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G"));
+ QString channel = info.channelMap.begin().value();
+ info.channelMap.clear();
+ info.channelMap["G"] = channel;
+ }
}
- }
- else if (info.channelMap.size() == 2) {
- modelId = GrayAColorModelID.id();
+ else if (info.channelMap.size() == 2) {
+ modelId = GrayAColorModelID.id();
- QMap<QString,QString>::const_iterator it = info.channelMap.constBegin();
- QMap<QString,QString>::const_iterator end = info.channelMap.constEnd();
+ QMap<QString,QString>::const_iterator it = info.channelMap.constBegin();
+ QMap<QString,QString>::const_iterator end = info.channelMap.constEnd();
- QString failingChannelKey;
+ QString failingChannelKey;
- for (; it != end; ++it) {
- if (it.key() != "G" && it.key() != "A") {
- failingChannelKey = it.key();
- break;
+ for (; it != end; ++it) {
+ if (it.key() != "G" && it.key() != "A") {
+ failingChannelKey = it.key();
+ break;
+ }
}
- }
- info.remappedChannels.push_back(
- ExrPaintLayerInfo::Remap(failingChannelKey, "G"));
+ info.remappedChannels.push_back(
+ ExrPaintLayerInfo::Remap(failingChannelKey, "G"));
- QString failingChannelValue = info.channelMap[failingChannelKey];
- info.channelMap.remove(failingChannelKey);
- info.channelMap["G"] = failingChannelValue;
+ QString failingChannelValue = info.channelMap[failingChannelKey];
+ info.channelMap.remove(failingChannelKey);
+ info.channelMap["G"] = failingChannelValue;
- }
- else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) {
-
- if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) {
- modelId = RGBAColorModelID.id();
}
- else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) {
- modelId = XYZAColorModelID.id();
- QMap<QString, QString> newChannelMap;
- if (info.channelMap.contains("W")) {
- newChannelMap["A"] = info.channelMap["W"];
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A"));
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X"));
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y"));
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z"));
- } else if (info.channelMap.contains("A")) {
- newChannelMap["A"] = info.channelMap["A"];
+ else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) {
+
+ if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) {
+ modelId = RGBAColorModelID.id();
}
- // The decode function expect R, G, B in the channel map
- newChannelMap["B"] = info.channelMap["X"];
- newChannelMap["G"] = info.channelMap["Y"];
- newChannelMap["R"] = info.channelMap["Z"];
- info.channelMap = newChannelMap;
- }
- else {
- modelId = RGBAColorModelID.id();
- QMap<QString, QString> newChannelMap;
- QMap<QString, QString>::iterator it = info.channelMap.begin();
- newChannelMap["R"] = it.value();
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R"));
- ++it;
- newChannelMap["G"] = it.value();
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G"));
- ++it;
- newChannelMap["B"] = it.value();
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B"));
- if (info.channelMap.size() == 4) {
- ++it;
- newChannelMap["A"] = it.value();
- info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A"));
+ else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) {
+ modelId = XYZAColorModelID.id();
+ QMap<QString, QString> newChannelMap;
+ if (info.channelMap.contains("W")) {
+ newChannelMap["A"] = info.channelMap["W"];
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A"));
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X"));
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y"));
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z"));
+ } else if (info.channelMap.contains("A")) {
+ newChannelMap["A"] = info.channelMap["A"];
+ }
+ // The decode function expect R, G, B in the channel map
+ newChannelMap["B"] = info.channelMap["X"];
+ newChannelMap["G"] = info.channelMap["Y"];
+ newChannelMap["R"] = info.channelMap["Z"];
+ info.channelMap = newChannelMap;
}
+ else {
+ modelId = RGBAColorModelID.id();
+ QMap<QString, QString> newChannelMap;
+ QMap<QString, QString>::iterator it = info.channelMap.begin();
+ newChannelMap["R"] = it.value();
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R"));
+ ++it;
+ newChannelMap["G"] = it.value();
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G"));
+ ++it;
+ newChannelMap["B"] = it.value();
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B"));
+ if (info.channelMap.size() == 4) {
+ ++it;
+ newChannelMap["A"] = it.value();
+ info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A"));
+ }
- info.channelMap = newChannelMap;
+ info.channelMap = newChannelMap;
+ }
+ }
+ else {
+ dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do.";
+ }
+ if (!modelId.isEmpty()) {
+ info.colorSpace = kisTypeToColorSpace(modelId, info.imageType);
}
}
- else {
- dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do.";
- }
- if (!modelId.isEmpty()) {
- info.colorSpace = kisTypeToColorSpace(modelId, info.imageType);
- }
- }
- // Get colorspace
- dbgFile << "Image type = " << imageType;
- const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType);
+ // Get colorspace
+ dbgFile << "Image type = " << imageType;
+ const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType);
- if (!colorSpace) return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
- dbgFile << "Colorspace: " << colorSpace->name();
+ if (!colorSpace) return ImportExportCodes::FormatColorSpaceUnsupported;
+ dbgFile << "Colorspace: " << colorSpace->name();
- // Set the colorspace on all groups
- for (int i = 0; i < groups.size(); ++i) {
- ExrGroupLayerInfo& info = groups[i];
- info.colorSpace = colorSpace;
- }
+ // Set the colorspace on all groups
+ for (int i = 0; i < groups.size(); ++i) {
+ ExrGroupLayerInfo& info = groups[i];
+ info.colorSpace = colorSpace;
+ }
- // Create the image
- // Make sure the created image is the same size as the displayWindow since
- // the dataWindow can be cropped in some cases.
- int displayWidth = displayWindow.max.x - displayWindow.min.x + 1;
- int displayHeight = displayWindow.max.y - displayWindow.min.y + 1;
- d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, "");
+ // Create the image
+ // Make sure the created image is the same size as the displayWindow since
+ // the dataWindow can be cropped in some cases.
+ int displayWidth = displayWindow.max.x - displayWindow.min.x + 1;
+ int displayHeight = displayWindow.max.y - displayWindow.min.y + 1;
+ d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, "");
- if (!d->image) {
- return KisImageBuilder_RESULT_FAILURE;
- }
+ if (!d->image) {
+ return ImportExportCodes::Failure;
+ }
- /**
- * EXR semi-transparent images are expected to be rendered on
- * black to ensure correctness of the light model
- */
- d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace));
-
- // Create group layers
- for (int i = 0; i < groups.size(); ++i) {
- ExrGroupLayerInfo& info = groups[i];
- Q_ASSERT(info.parent == 0 || info.parent->groupLayer);
- KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
- info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8);
- d->image->addNode(info.groupLayer, groupLayerParent);
- }
+ /**
+ * EXR semi-transparent images are expected to be rendered on
+ * black to ensure correctness of the light model
+ */
+ d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace));
- // Load the layers
- for (int i = informationObjects.size() - 1; i >= 0; --i) {
- ExrPaintLayerInfo& info = informationObjects[i];
- if (info.colorSpace) {
- dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id();
- KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace);
+ // Create group layers
+ for (int i = 0; i < groups.size(); ++i) {
+ ExrGroupLayerInfo& info = groups[i];
+ Q_ASSERT(info.parent == 0 || info.parent->groupLayer);
+ KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
+ info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8);
+ d->image->addNode(info.groupLayer, groupLayerParent);
+ }
- layer->setCompositeOpId(COMPOSITE_OVER);
+ // Load the layers
+ for (int i = informationObjects.size() - 1; i >= 0; --i) {
+ ExrPaintLayerInfo& info = informationObjects[i];
+ if (info.colorSpace) {
+ dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id();
+ KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace);
- if (!layer) {
- return KisImageBuilder_RESULT_FAILURE;
- }
+ if (!layer) {
+ return ImportExportCodes::Failure;
+ }
- switch (info.channelMap.size()) {
- case 1:
- case 2:
- // Decode the data
- switch (info.imageType) {
- case IT_FLOAT16:
- d->decodeData1<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
+ layer->setCompositeOpId(COMPOSITE_OVER);
+
+ switch (info.channelMap.size()) {
+ case 1:
+ case 2:
+ // Decode the data
+ switch (info.imageType) {
+ case IT_FLOAT16:
+ d->decodeData1<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
+ break;
+ case IT_FLOAT32:
+ d->decodeData1<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
+ break;
+ case IT_UNKNOWN:
+ case IT_UNSUPPORTED:
+ qFatal("Impossible error");
+ }
break;
- case IT_FLOAT32:
- d->decodeData1<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
+ case 3:
+ case 4:
+ // Decode the data
+ switch (info.imageType) {
+ case IT_FLOAT16:
+ d->decodeData4<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
+ break;
+ case IT_FLOAT32:
+ d->decodeData4<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
+ break;
+ case IT_UNKNOWN:
+ case IT_UNSUPPORTED:
+ qFatal("Impossible error");
+ }
break;
- case IT_UNKNOWN:
- case IT_UNSUPPORTED:
- qFatal("Impossible error");
+ default:
+ qFatal("Invalid number of channels: %i", info.channelMap.size());
}
- break;
- case 3:
- case 4:
- // Decode the data
- switch (info.imageType) {
- case IT_FLOAT16:
- d->decodeData4<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
- break;
- case IT_FLOAT32:
- d->decodeData4<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
- break;
- case IT_UNKNOWN:
- case IT_UNSUPPORTED:
- qFatal("Impossible error");
+ // Check if should set the channels
+ if (!info.remappedChannels.isEmpty()) {
+ QList<KisMetaData::Value> values;
+ Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) {
+ QMap<QString, KisMetaData::Value> map;
+ map["original"] = KisMetaData::Value(remap.original);
+ map["current"] = KisMetaData::Value(remap.current);
+ values.append(map);
+ }
+ layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values));
}
- break;
- default:
- qFatal("Invalid number of channels: %i", info.channelMap.size());
+ // Add the layer
+ KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
+ d->image->addNode(layer, groupLayerParent);
+ } else {
+ dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space";
}
- // Check if should set the channels
- if (!info.remappedChannels.isEmpty()) {
- QList<KisMetaData::Value> values;
- Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) {
- QMap<QString, KisMetaData::Value> map;
- map["original"] = KisMetaData::Value(remap.original);
- map["current"] = KisMetaData::Value(remap.current);
- values.append(map);
- }
- layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values));
+ }
+ // Set projectionColor to opaque
+ d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace));
+
+ // After reading the image, notify the user about changed alpha.
+ if (d->alphaWasModified) {
+ QString msg =
+ i18nc("@info",
+ "The image contains pixels with zero alpha channel and non-zero "
+ "color channels. Krita has modified those pixels to have "
+ "at least some alpha. The initial values will <i>not</i> "
+ "be reverted on saving the image back."
+ "<br/><br/>"
+ "This will hardly make any visual difference just keep it in mind.");
+ if (d->showNotifications) {
+ QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg);
+ } else {
+ warnKrita << "WARNING:" << msg;
}
- // Add the layer
- KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
- d->image->addNode(layer, groupLayerParent);
- } else {
- dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space";
}
- }
- // Set projectionColor to opaque
- d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace));
-
- // After reading the image, notify the user about changed alpha.
- if (d->alphaWasModified) {
- QString msg =
- i18nc("@info",
- "The image contains pixels with zero alpha channel and non-zero "
- "color channels. Krita has modified those pixels to have "
- "at least some alpha. The initial values will <i>not</i> "
- "be reverted on saving the image back."
- "<br/><br/>"
- "This will hardly make any visual difference just keep it in mind.");
- if (d->showNotifications) {
- QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg);
- } else {
- warnKrita << "WARNING:" << msg;
+
+ if (!extraLayersInfo.isNull()) {
+ KisExrLayersSorter sorter(extraLayersInfo, d->image);
}
- }
- if (!extraLayersInfo.isNull()) {
- KisExrLayersSorter sorter(extraLayersInfo, d->image);
+ return ImportExportCodes::OK;
+
+ } catch (std::exception &e) {
+ dbgFile << "Error while reading from the exr file: " << e.what();
+
+ if (!KisImportExportAdditionalChecks::doesFileExist(filename)) {
+ return ImportExportCodes::FileNotExist;
+ } else if(!KisImportExportAdditionalChecks::isFileReadable(filename)) {
+ return ImportExportCodes::NoAccessToRead;
+ } else {
+ return ImportExportCodes::ErrorWhileReading;
+ }
}
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
-KisImageBuilder_Result EXRConverter::buildImage(const QString &filename)
+KisImportExportErrorCode EXRConverter::buildImage(const QString &filename)
{
return decode(filename);
}
KisImageSP EXRConverter::image()
{
return d->image;
}
QString EXRConverter::errorMessage() const
{
return d->errorMessage;
}
template<typename _T_, int size>
struct ExrPixel_ {
_T_ data[size];
};
class Encoder
{
public:
virtual ~Encoder() {}
virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0;
virtual void encodeData(int line) = 0;
};
template<typename _T_, int size, int alphaPos>
class EncoderImpl : public Encoder
{
public:
EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {}
~EncoderImpl() override {}
void prepareFrameBuffer(Imf::FrameBuffer*, int line) override;
void encodeData(int line) override;
private:
typedef ExrPixel_<_T_, size> ExrPixel;
Imf::OutputFile* file;
const ExrPaintLayerSaveInfo* info;
QVector<ExrPixel> pixels;
int m_width;
};
template<typename _T_, int size, int alphaPos>
void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line)
{
int xstart = 0;
int ystart = 0;
ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width;
for (int k = 0; k < size; ++k) {
frameBuffer->insert(info->channels[k].toUtf8(),
Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k],
sizeof(ExrPixel) * 1,
sizeof(ExrPixel) * m_width));
}
}
template<typename _T_, int size, int alphaPos>
void EncoderImpl<_T_, size, alphaPos>::encodeData(int line)
{
ExrPixel *rgba = pixels.data();
KisHLineConstIteratorSP it = info->layerDevice->createHLineConstIteratorNG(0, line, m_width);
do {
const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData());
for (int i = 0; i < size; ++i) {
rgba->data[i] = dst[i];
}
if (alphaPos != -1) {
multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba);
}
++rgba;
} while (it->nextPixel());
}
Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width)
{
dbgFile << "Create encoder for" << info.name << info.channels << info.layerDevice->colorSpace()->channelCount();
switch (info.layerDevice->colorSpace()->channelCount()) {
case 1: {
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::HALF);
return new EncoderImpl < half, 1, -1 > (&file, &info, width);
} else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::FLOAT);
return new EncoderImpl < float, 1, -1 > (&file, &info, width);
}
break;
}
case 2: {
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::HALF);
return new EncoderImpl<half, 2, 1>(&file, &info, width);
} else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::FLOAT);
return new EncoderImpl<float, 2, 1>(&file, &info, width);
}
break;
}
case 4: {
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::HALF);
return new EncoderImpl<half, 4, 3>(&file, &info, width);
} else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::FLOAT);
return new EncoderImpl<float, 4, 3>(&file, &info, width);
}
break;
}
default:
qFatal("Impossible error");
}
return 0;
}
void encodeData(Imf::OutputFile& file, const QList<ExrPaintLayerSaveInfo>& informationObjects, int width, int height)
{
QList<Encoder*> encoders;
Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
encoders.push_back(encoder(file, info, width));
}
for (int y = 0; y < height; ++y) {
Imf::FrameBuffer frameBuffer;
Q_FOREACH (Encoder* encoder, encoders) {
encoder->prepareFrameBuffer(&frameBuffer, y);
}
file.setFrameBuffer(frameBuffer);
Q_FOREACH (Encoder* encoder, encoders) {
encoder->encodeData(y);
}
file.writePixels(1);
}
qDeleteAll(encoders);
}
KisPaintDeviceSP wrapLayerDevice(KisPaintDeviceSP device)
{
const KoColorSpace *cs = device->colorSpace();
if (cs->colorDepthId() != Float16BitsColorDepthID && cs->colorDepthId() != Float32BitsColorDepthID) {
cs = KoColorSpaceRegistry::instance()->colorSpace(
cs->colorModelId() == GrayAColorModelID ?
GrayAColorModelID.id() : RGBAColorModelID.id(),
Float16BitsColorDepthID.id());
} else if (cs->colorModelId() != GrayColorModelID &&
cs->colorModelId() != RGBAColorModelID) {
cs = KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
cs->colorDepthId().id());
}
if (*cs != *device->colorSpace()) {
device = new KisPaintDevice(*device);
device->convertTo(cs);
}
return device;
}
-KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer)
+KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer)
{
- if (!layer)
- return KisImageBuilder_RESULT_INVALID_ARG;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
KisImageSP image = layer->image();
- if (!image)
- return KisImageBuilder_RESULT_EMPTY;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError);
+
// Make the header
qint32 height = image->height();
qint32 width = image->width();
Imf::Header header(width, height);
ExrPaintLayerSaveInfo info;
info.layer = layer;
info.layerDevice = wrapLayerDevice(layer->paintDevice());
-
Imf::PixelType pixelType = Imf::NUM_PIXELTYPES;
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
pixelType = Imf::HALF;
}
else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
pixelType = Imf::FLOAT;
}
-
header.channels().insert("R", Imf::Channel(pixelType));
header.channels().insert("G", Imf::Channel(pixelType));
header.channels().insert("B", Imf::Channel(pixelType));
header.channels().insert("A", Imf::Channel(pixelType));
info.channels.push_back("R");
info.channels.push_back("G");
info.channels.push_back("B");
info.channels.push_back("A");
info.pixelType = pixelType;
// Open file for writing
- Imf::OutputFile file(QFile::encodeName(filename), header);
+ try {
+ Imf::OutputFile file(QFile::encodeName(filename), header);
- QList<ExrPaintLayerSaveInfo> informationObjects;
- informationObjects.push_back(info);
+ QList<ExrPaintLayerSaveInfo> informationObjects;
+ informationObjects.push_back(info);
+ encodeData(file, informationObjects, width, height);
+ return ImportExportCodes::OK;
- encodeData(file, informationObjects, width, height);
+ } catch(std::exception &e) {
+ dbgFile << "Exception while writing to exr file: " << e.what();
+ if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) {
+ return ImportExportCodes::NoAccessToWrite;
+ }
+ return ImportExportCodes::Failure;
+ }
- return KisImageBuilder_RESULT_OK;
}
QString remap(const QMap<QString, QString>& current2original, const QString& current)
{
if (current2original.contains(current)) {
return current2original[current];
}
return current;
}
void EXRConverter::Private::makeLayerNamesUnique(QList<ExrPaintLayerSaveInfo>& informationObjects)
{
typedef QMultiMap<QString, QList<ExrPaintLayerSaveInfo>::iterator> NamesMap;
NamesMap namesMap;
{
QList<ExrPaintLayerSaveInfo>::iterator it = informationObjects.begin();
QList<ExrPaintLayerSaveInfo>::iterator end = informationObjects.end();
for (; it != end; ++it) {
namesMap.insert(it->name, it);
}
}
Q_FOREACH (const QString &key, namesMap.keys()) {
if (namesMap.count(key) > 1) {
KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; }
QString strippedName = key.left(key.size() - 1); // trim the ending dot
int nameCounter = 0;
NamesMap::iterator it = namesMap.find(key);
NamesMap::iterator end = namesMap.end();
for (; it != end; ++it) {
QString newName =
QString("%1_%2.")
.arg(strippedName)
.arg(nameCounter++);
it.value()->name = newName;
QList<QString>::iterator channelsIt = it.value()->channels.begin();
QList<QString>::iterator channelsEnd = it.value()->channels.end();
for (; channelsIt != channelsEnd; ++channelsIt) {
channelsIt->replace(key, newName);
}
}
}
}
}
void EXRConverter::Private::recBuildPaintLayerSaveInfo(QList<ExrPaintLayerSaveInfo>& informationObjects, const QString& name, KisGroupLayerSP parent)
{
QSet<KisNodeSP> layersNotSaved;
for (uint i = 0; i < parent->childCount(); ++i) {
KisNodeSP node = parent->at(i);
if (KisPaintLayerSP paintLayer = dynamic_cast<KisPaintLayer*>(node.data())) {
QMap<QString, QString> current2original;
if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) {
const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap");
QList< KisMetaData::Value> values = entry.value().asArray();
Q_FOREACH (const KisMetaData::Value& value, values) {
QMap<QString, KisMetaData::Value> map = value.asStructure();
if (map.contains("original") && map.contains("current")) {
current2original[map["current"].toString()] = map["original"].toString();
}
}
}
ExrPaintLayerSaveInfo info;
info.name = name + paintLayer->name() + '.';
info.layer = paintLayer;
info.layerDevice = wrapLayerDevice(paintLayer->paintDevice());
if (info.name == QString(HDR_LAYER) + ".") {
info.channels.push_back("R");
info.channels.push_back("G");
info.channels.push_back("B");
info.channels.push_back("A");
}
else {
if (paintLayer->colorSpace()->colorModelId() == RGBAColorModelID) {
info.channels.push_back(info.name + remap(current2original, "R"));
info.channels.push_back(info.name + remap(current2original, "G"));
info.channels.push_back(info.name + remap(current2original, "B"));
info.channels.push_back(info.name + remap(current2original, "A"));
}
else if (paintLayer->colorSpace()->colorModelId() == GrayAColorModelID) {
info.channels.push_back(info.name + remap(current2original, "G"));
info.channels.push_back(info.name + remap(current2original, "A"));
}
else if (paintLayer->colorSpace()->colorModelId() == GrayColorModelID) {
info.channels.push_back(info.name + remap(current2original, "G"));
}
else if (paintLayer->colorSpace()->colorModelId() == XYZAColorModelID) {
info.channels.push_back(info.name + remap(current2original, "X"));
info.channels.push_back(info.name + remap(current2original, "Y"));
info.channels.push_back(info.name + remap(current2original, "Z"));
info.channels.push_back(info.name + remap(current2original, "A"));
}
}
if (paintLayer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
info.pixelType = Imf::HALF;
}
else if (paintLayer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
info.pixelType = Imf::FLOAT;
}
else {
info.pixelType = Imf::NUM_PIXELTYPES;
}
if (info.pixelType < Imf::NUM_PIXELTYPES) {
dbgFile << "Going to save layer" << info.name;
informationObjects.push_back(info);
}
else {
warnFile << "Will not save layer" << info.name;
layersNotSaved << node;
}
}
else if (KisGroupLayerSP groupLayer = dynamic_cast<KisGroupLayer*>(node.data())) {
recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer);
}
else {
/**
* The EXR can store paint and group layers only. The rest will
* go to /dev/null :(
*/
layersNotSaved.insert(node);
}
}
if (!layersNotSaved.isEmpty()) {
reportLayersNotSaved(layersNotSaved);
}
}
void EXRConverter::Private::reportLayersNotSaved(const QSet<KisNodeSP> &layersNotSaved)
{
QString layersList;
QTextStream textStream(&layersList);
textStream.setCodec("UTF-8");
Q_FOREACH (KisNodeSP node, layersNotSaved) {
textStream << "<li>" << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "</li>";
}
QString msg =
i18nc("@info",
"<p>The following layers have a type that is not supported by EXR format:</p>"
"<r><ul>%1</ul></p>"
"<p><warning>these layers have <b>not</b> been saved to the final EXR file</warning></p>", layersList);
errorMessage = msg;
}
QString EXRConverter::Private::fetchExtraLayersInfo(QList<ExrPaintLayerSaveInfo>& informationObjects)
{
KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty());
if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") {
return QString();
}
QDomDocument doc("krita-extra-layers-info");
doc.appendChild(doc.createElement("root"));
QDomElement rootElement = doc.documentElement();
for (int i = 0; i < informationObjects.size(); i++) {
ExrPaintLayerSaveInfo &info = informationObjects[i];
quint32 unused;
KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false);
QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc);
// cut the ending '.'
QString strippedName = info.name.left(info.name.size() - 1);
el.setAttribute(EXR_NAME, strippedName);
rootElement.appendChild(el);
}
return doc.toString();
}
-KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten)
+KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten)
{
- if (!layer)
- return KisImageBuilder_RESULT_INVALID_ARG;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
KisImageSP image = layer->image();
- if (!image)
- return KisImageBuilder_RESULT_EMPTY;
-
+ KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError);
qint32 height = image->height();
qint32 width = image->width();
Imf::Header header(width, height);
if (flatten) {
KisPaintDeviceSP pd = new KisPaintDevice(*image->projection());
KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd);
return buildFile(filename, l);
}
else {
-
QList<ExrPaintLayerSaveInfo> informationObjects;
d->recBuildPaintLayerSaveInfo(informationObjects, "", layer);
if(informationObjects.isEmpty()) {
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
-
d->makeLayerNamesUnique(informationObjects);
QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8();
if (!extraLayersInfo.isNull()) {
header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData()));
}
dbgFile << informationObjects.size() << " layers to save";
-
Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
if (info.pixelType < Imf::NUM_PIXELTYPES) {
Q_FOREACH (const QString& channel, info.channels) {
dbgFile << channel << " " << info.pixelType;
header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType));
}
}
}
// Open file for writing
- Imf::OutputFile file(QFile::encodeName(filename), header);
+ try {
+ Imf::OutputFile file(QFile::encodeName(filename), header);
+ encodeData(file, informationObjects, width, height);
+ return ImportExportCodes::OK;
+ } catch(std::exception &e) {
+ dbgFile << "Exception while writing to exr file: " << e.what();
+ if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) {
+ return ImportExportCodes::NoAccessToWrite;
+ }
+ return ImportExportCodes::ErrorWhileWriting;
+ }
- encodeData(file, informationObjects, width, height);
- return KisImageBuilder_RESULT_OK;
}
}
void EXRConverter::cancel()
{
warnKrita << "WARNING: Cancelling of an EXR loading is not supported!";
}
diff --git a/plugins/impex/exr/exr_converter.h b/plugins/impex/exr/exr_converter.h
index 6db6795339..6a11120e02 100644
--- a/plugins/impex/exr/exr_converter.h
+++ b/plugins/impex/exr/exr_converter.h
@@ -1,56 +1,57 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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 _EXR_CONVERTER_H_
#define _EXR_CONVERTER_H_
#include <stdio.h>
#include <QObject>
#include "kis_types.h"
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
+
class KisDocument;
class EXRConverter : public QObject
{
Q_OBJECT
public:
EXRConverter(KisDocument *doc, bool showNotifications);
~EXRConverter() override;
public:
- KisImageBuilder_Result buildImage(const QString &filename);
- KisImageBuilder_Result buildFile(const QString &filename, KisPaintLayerSP layer);
- KisImageBuilder_Result buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten=false);
+ KisImportExportErrorCode buildImage(const QString &filename);
+ KisImportExportErrorCode buildFile(const QString &filename, KisPaintLayerSP layer);
+ KisImportExportErrorCode buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten=false);
/**
* Retrieve the constructed image
*/
KisImageSP image();
QString errorMessage() const;
private:
- KisImageBuilder_Result decode(const QString &filename);
+ KisImportExportErrorCode decode(const QString &filename);
public Q_SLOTS:
virtual void cancel();
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif
diff --git a/plugins/impex/exr/exr_export.cc b/plugins/impex/exr/exr_export.cc
index fd23fe0ad4..6c60865aec 100644
--- a/plugins/impex/exr/exr_export.cc
+++ b/plugins/impex/exr/exr_export.cc
@@ -1,159 +1,127 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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 "exr_export.h"
#include <QCheckBox>
#include <QSlider>
#include <QApplication>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceConstants.h>
#include <KisImportExportManager.h>
#include <KisExportCheckRegistry.h>
#include <kis_properties_configuration.h>
#include <kis_config.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_device.h>
#include <kis_paint_layer.h>
#include "exr_converter.h"
class KisExternalLayer;
K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_exr_export.json", registerPlugin<EXRExport>();)
EXRExport::EXRExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
EXRExport::~EXRExport()
{
}
KisPropertiesConfigurationSP EXRExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("flatten", false);
return cfg;
}
KisConfigWidget *EXRExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
return new KisWdgOptionsExr(parent);
}
-KisImportExportFilter::ConversionStatus EXRExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode EXRExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration)
{
Q_ASSERT(document);
Q_ASSERT(configuration);
KisImageSP image = document->savingImage();
Q_ASSERT(image);
EXRConverter exrConverter(document, !batchMode());
- KisImageBuilder_Result res;
+ KisImportExportErrorCode res;
if (configuration && configuration->getBool("flatten")) {
res = exrConverter.buildFile(filename(), image->rootLayer(), true);
}
else {
res = exrConverter.buildFile(filename(), image->rootLayer());
}
dbgFile << " Result =" << res;
- switch (res) {
- case KisImageBuilder_RESULT_INVALID_ARG:
- document->setErrorMessage(i18n("This layer cannot be saved to EXR."));
- return KisImportExportFilter::WrongFormat;
-
- case KisImageBuilder_RESULT_EMPTY:
- document->setErrorMessage(i18n("The layer does not have an image associated with it."));
- return KisImportExportFilter::WrongFormat;
-
- case KisImageBuilder_RESULT_NO_URI:
- document->setErrorMessage(i18n("The filename is empty."));
- return KisImportExportFilter::CreationError;
-
- case KisImageBuilder_RESULT_NOT_LOCAL:
- document->setErrorMessage(i18n("EXR images cannot be saved remotely."));
- return KisImportExportFilter::InternalError;
-
- case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE:
- document->setErrorMessage(i18n("Colorspace not supported: EXR images must be 16 or 32 bits floating point RGB."));
- return KisImportExportFilter::WrongFormat;
-
- case KisImageBuilder_RESULT_OK:
- if (!exrConverter.errorMessage().isNull()) {
- document->setErrorMessage(exrConverter.errorMessage());
- }
- return KisImportExportFilter::OK;
- default:
- break;
- }
-
- document->setErrorMessage(i18n("Internal Error"));
- return KisImportExportFilter::InternalError;
-
+ return res;
}
void EXRExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Float16BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Float32BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Float16BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Float32BitsColorDepthID)
<< QPair<KoID, KoID>(GrayColorModelID, Float16BitsColorDepthID)
<< QPair<KoID, KoID>(GrayColorModelID, Float32BitsColorDepthID)
<< QPair<KoID, KoID>(XYZAColorModelID, Float16BitsColorDepthID)
<< QPair<KoID, KoID>(XYZAColorModelID, Float32BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "EXR");
}
void KisWdgOptionsExr::setConfiguration(const KisPropertiesConfigurationSP cfg)
{
chkFlatten->setChecked(cfg->getBool("flatten", false));
}
KisPropertiesConfigurationSP KisWdgOptionsExr::configuration() const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("flatten", chkFlatten->isChecked());
return cfg;
}
#include <exr_export.moc>
diff --git a/plugins/impex/exr/exr_export.h b/plugins/impex/exr/exr_export.h
index 11a501ea19..a95034e9fb 100644
--- a/plugins/impex/exr/exr_export.h
+++ b/plugins/impex/exr/exr_export.h
@@ -1,59 +1,59 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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 _EXR_EXPORT_H_
#define _EXR_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
#include "ui_exr_export_widget.h"
class KisWdgOptionsExr : public KisConfigWidget, public Ui::ExrExportWidget
{
Q_OBJECT
public:
KisWdgOptionsExr(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
}
void setConfiguration(const KisPropertiesConfigurationSP cfg) override;
KisPropertiesConfigurationSP configuration() const override;
};
class EXRExport : public KisImportExportFilter
{
Q_OBJECT
public:
EXRExport(QObject *parent, const QVariantList &);
~EXRExport() override;
bool supportsIO() const override { return false; }
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/exr/exr_import.cc b/plugins/impex/exr/exr_import.cc
index baae10dbd5..a6926dd6bc 100644
--- a/plugins/impex/exr/exr_import.cc
+++ b/plugins/impex/exr/exr_import.cc
@@ -1,83 +1,53 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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 "exr_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <kis_image.h>
#include "exr_converter.h"
K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_exr_import.json", registerPlugin<exrImport>();)
exrImport::exrImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
exrImport::~exrImport()
{
}
-KisImportExportFilter::ConversionStatus exrImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode exrImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/)
{
EXRConverter ib(document, !batchMode());
-
- switch (ib.buildImage(filename())) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE:
- document->setErrorMessage(i18n("Krita does not support this type of EXR file."));
- return KisImportExportFilter::NotImplemented;
-
- case KisImageBuilder_RESULT_INVALID_ARG:
- document->setErrorMessage(i18n("This is not an EXR file."));
- return KisImportExportFilter::BadMimeType;
-
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- document->setErrorMessage(i18n("The EXR file does not exist."));
- return KisImportExportFilter::FileNotFound;
-
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- document->setErrorMessage(i18n("The EXR is corrupted."));
- return KisImportExportFilter::ParsingError;
-
- case KisImageBuilder_RESULT_FAILURE:
- document->setErrorMessage(i18n("Krita could not create a new image."));
- return KisImportExportFilter::InternalError;
-
- case KisImageBuilder_RESULT_OK:
- Q_ASSERT(ib.image());
- document -> setCurrentImage(ib.image());
- return KisImportExportFilter::OK;
-
- default:
- break;
+ KisImportExportErrorCode result = ib.buildImage(filename());
+ if (result.isOk()) {
+ document->setCurrentImage(ib.image());
}
-
- return KisImportExportFilter::StorageCreationError;
+ return result;
}
#include <exr_import.moc>
diff --git a/plugins/impex/exr/exr_import.h b/plugins/impex/exr/exr_import.h
index f009c2b341..5841f3ff32 100644
--- a/plugins/impex/exr/exr_import.h
+++ b/plugins/impex/exr/exr_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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 EXR_IMPORT_H_
#define EXR_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class exrImport : public KisImportExportFilter
{
Q_OBJECT
public:
exrImport(QObject *parent, const QVariantList &);
~exrImport() override;
bool supportsIO() const override { return false; }
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/exr/tests/data/incorrectFormatFile.txt b/plugins/impex/exr/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/exr/tests/data/readonlyFile.txt b/plugins/impex/exr/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/exr/tests/data/writeonlyFile.txt b/plugins/impex/exr/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/exr/tests/kis_exr_test.cpp b/plugins/impex/exr/tests/kis_exr_test.cpp
index f3e2f0fb55..0a649b6db7 100644
--- a/plugins/impex/exr/tests/kis_exr_test.cpp
+++ b/plugins/impex/exr/tests/kis_exr_test.cpp
@@ -1,94 +1,110 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_exr_test.h"
#include <QTest>
#include <QCoreApplication>
#include <sdk/tests/kistest.h>
#include <half.h>
#include <KisMimeDatabase.h>
#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 ExrMimetype = "application/x-extension-exr";
void KisExrTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 5);
}
+void KisExrTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), ExrMimetype);
+}
+
+void KisExrTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), ExrMimetype, true);
+}
+
+void KisExrTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), ExrMimetype);
+}
+
void KisExrTest::testRoundTrip()
{
QString inputFileName(TestUtil::fetchDataFileLazy("CandleGlass.exr"));
KisDocument *doc1 = KisPart::instance()->createDocument();
doc1->setFileBatchMode(true);
bool r = doc1->importDocument(QUrl::fromLocalFile(inputFileName));
QVERIFY(r);
QVERIFY(doc1->errorMessage().isEmpty());
QVERIFY(doc1->image());
QTemporaryFile savedFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".exr"));
savedFile.setAutoRemove(true);
savedFile.open();
QString savedFileName(savedFile.fileName());
QString typeName = KisMimeDatabase::mimeTypeForFile(savedFileName, false);
QByteArray mimeType(typeName.toLatin1());
r = doc1->exportDocumentSync(QUrl::fromLocalFile(savedFileName), mimeType);
QVERIFY(r);
QVERIFY(QFileInfo(savedFileName).exists());
{
KisDocument *doc2 = KisPart::instance()->createDocument();
doc2->setFileBatchMode(true);
r = doc2->importDocument(QUrl::fromLocalFile(savedFileName));
QVERIFY(r);
QVERIFY(doc2->errorMessage().isEmpty());
QVERIFY(doc2->image());
doc1->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("1.png");
doc2->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("2.png");
QVERIFY(TestUtil::comparePaintDevicesClever<half>(
doc1->image()->root()->firstChild()->paintDevice(),
doc2->image()->root()->firstChild()->paintDevice(),
0.01 /* meaningless alpha */));
delete doc2;
}
savedFile.close();
delete doc1;
}
KISTEST_MAIN(KisExrTest)
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/exr/tests/kis_exr_test.h
index 4dad62dfca..7119e96947 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/exr/tests/kis_exr_test.h
@@ -1,32 +1,35 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_EXR_TEST_H_
#define _KIS_EXR_TEST_H_
#include <QtTest>
class KisExrTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
void testRoundTrip();
};
#endif
diff --git a/plugins/impex/gif/CMakeLists.txt b/plugins/impex/gif/CMakeLists.txt
index 31ad0a488d..bb7f582b6c 100644
--- a/plugins/impex/gif/CMakeLists.txt
+++ b/plugins/impex/gif/CMakeLists.txt
@@ -1,26 +1,28 @@
+add_subdirectory(tests)
+
set(kritagifexport_SOURCES
kis_gif_export.cpp
qgiflibhandler.cpp
)
ki18n_wrap_ui(kritagifexport_SOURCES )
add_library(kritagifexport MODULE ${kritagifexport_SOURCES})
target_link_libraries(kritagifexport kritaui kritaimpex ${GIF_LIBRARY})
install(TARGETS kritagifexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
set(kritagifimport_SOURCES
kis_gif_import.cpp
qgiflibhandler.cpp
)
ki18n_wrap_ui(kritagifimport_SOURCES )
add_library(kritagifimport MODULE ${kritagifimport_SOURCES})
target_link_libraries(kritagifimport kritaui ${GIF_LIBRARY})
install(TARGETS kritagifimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_gif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/gif/kis_gif_export.cpp b/plugins/impex/gif/kis_gif_export.cpp
index f0b2471a64..c9332d3670 100644
--- a/plugins/impex/gif/kis_gif_export.cpp
+++ b/plugins/impex/gif/kis_gif_export.cpp
@@ -1,72 +1,76 @@
/*
* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_gif_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QApplication>
#include <KoColorModelStandardIds.h>
#include <KisExportCheckRegistry.h>
#include <KisImportExportManager.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include "qgiflibhandler.h"
K_PLUGIN_FACTORY_WITH_JSON(KisGIFExportFactory, "krita_gif_export.json", registerPlugin<KisGIFExport>();)
KisGIFExport::KisGIFExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisGIFExport::~KisGIFExport()
{
}
-KisImportExportFilter::ConversionStatus KisGIFExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisGIFExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
Q_UNUSED(configuration);
QRect rc = document->savingImage()->bounds();
QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
QGIFLibHandler handler;
handler.setDevice(io);
bool result = handler.write(image);
- return (result ? KisImportExportFilter::OK : KisImportExportFilter::InternalError);
+ if (!result) {
+ KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError);
+ return ImportExportCodes::InternalError;
+ }
+ return ImportExportCodes::OK;
}
void KisGIFExport::initializeCapabilities()
{
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "GIF");
}
#include "kis_gif_export.moc"
diff --git a/plugins/impex/gif/kis_gif_export.h b/plugins/impex/gif/kis_gif_export.h
index a0106d9ae8..b2d238ca44 100644
--- a/plugins/impex/gif/kis_gif_export.h
+++ b/plugins/impex/gif/kis_gif_export.h
@@ -1,38 +1,38 @@
/*
* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_GIF_EXPORT_H_
#define _KIS_GIF_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisGIFExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisGIFExport(QObject *parent, const QVariantList &);
~KisGIFExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/gif/kis_gif_import.cpp b/plugins/impex/gif/kis_gif_import.cpp
index 86d9a7bb21..11b8957a92 100644
--- a/plugins/impex/gif/kis_gif_import.cpp
+++ b/plugins/impex/gif/kis_gif_import.cpp
@@ -1,84 +1,94 @@
/*
* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_gif_import.h"
#include <QCheckBox>
#include <QBuffer>
#include <QSlider>
#include <QApplication>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <kis_transaction.h>
#include <kis_paint_device.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <KisDocument.h>
+#include <KisImportExportAdditionalChecks.h>
#include "qgiflibhandler.h"
K_PLUGIN_FACTORY_WITH_JSON(KisGIFImportFactory, "krita_gif_import.json", registerPlugin<KisGIFImport>();)
KisGIFImport::KisGIFImport(QObject *parent, const QVariantList &)
: KisImportExportFilter(parent)
{
}
KisGIFImport::~KisGIFImport()
{
}
-KisImportExportFilter::ConversionStatus KisGIFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisGIFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
Q_UNUSED(configuration);
QImage img;
bool result = false;
QGIFLibHandler handler;
+
+
handler.setDevice(io);
+ if (!io->isReadable()) {
+ return ImportExportCodes::NoAccessToRead;
+ }
+
if (handler.canRead()) {
result = handler.read(&img);
+ } else {
+ // handler.canRead() checks for the flag in the file; if it can't read it, maybe the format is incorrect
+ return ImportExportCodes::FileFormatIncorrect;
}
if (result == false) {
- return KisImportExportFilter::CreationError;
+ return ImportExportCodes::FileFormatIncorrect;
}
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, "imported from gif");
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255);
layer->paintDevice()->convertFromQImage(img, 0, 0, 0);
image->addNode(layer.data(), image->rootLayer().data());
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_gif_import.moc"
diff --git a/plugins/impex/gif/kis_gif_import.h b/plugins/impex/gif/kis_gif_import.h
index 7b9d9f90bf..8afa60ca9d 100644
--- a/plugins/impex/gif/kis_gif_import.h
+++ b/plugins/impex/gif/kis_gif_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2018 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_GIF_IMPORT_H_
#define _KIS_GIF_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisGIFImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisGIFImport(QObject *parent, const QVariantList &);
~KisGIFImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/gif/tests/CMakeLists.txt b/plugins/impex/gif/tests/CMakeLists.txt
new file mode 100644
index 0000000000..a58fb5b17f
--- /dev/null
+++ b/plugins/impex/gif/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(KisGifTest.cpp
+ TEST_NAME KisGifTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/gif/tests/KisGifTest.cpp
similarity index 64%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/gif/tests/KisGifTest.cpp
index 6ca66688f8..5c87288247 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/gif/tests/KisGifTest.cpp
@@ -1,40 +1,57 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisGifTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString GifMimetype = "image/gif";
+
+
+
+void KisGifTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), GifMimetype);
+}
+
+
+void KisGifTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), GifMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisGifTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), GifMimetype);
+}
+
+
+
+KISTEST_MAIN(KisGifTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/gif/tests/KisGifTest.h
similarity index 74%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/gif/tests/KisGifTest.h
index 4dad62dfca..a65c46bead 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/gif/tests/KisGifTest.h
@@ -1,32 +1,35 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_GIF_TEST_H_
+#define _KIS_GIF_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisGifTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_BRUSH_TEST_H_
+
diff --git a/plugins/impex/gif/tests/data/incorrectFormatFile.txt b/plugins/impex/gif/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/gif/tests/data/readonlyFile.txt b/plugins/impex/gif/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/gif/tests/data/writeonlyFile.txt b/plugins/impex/gif/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/heif/CMakeLists.txt b/plugins/impex/heif/CMakeLists.txt
index 725e7afed3..d25f3e1704 100644
--- a/plugins/impex/heif/CMakeLists.txt
+++ b/plugins/impex/heif/CMakeLists.txt
@@ -1,31 +1,33 @@
+add_subdirectory(tests)
+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${HEIF_CFLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${HEIF_CFLAGS}")
add_definitions(${HEIF_DEFINITIONS})
set(kritaheifimport_SOURCES
HeifImport.cpp
HeifError.cpp
)
add_library(kritaheifimport MODULE ${kritaheifimport_SOURCES})
target_link_libraries(kritaheifimport kritaui kritalibkra
${HEIF_LDFLAGS} ${HEIF_LIBRARIES}
)
install(TARGETS kritaheifimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
set(kritaheifexport_SOURCES
HeifExport.cpp
HeifError.cpp
)
ki18n_wrap_ui(kritaheifexport_SOURCES WdgHeifExport.ui )
add_library(kritaheifexport MODULE ${kritaheifexport_SOURCES})
target_link_libraries(kritaheifexport kritaui kritalibkra kritaimpex ${HEIF_LDFLAGS} ${HEIF_LIBRARIES} )
install(TARGETS kritaheifexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_heif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/heif/HeifError.cpp b/plugins/impex/heif/HeifError.cpp
index 41a324a1ce..e37c655936 100644
--- a/plugins/impex/heif/HeifError.cpp
+++ b/plugins/impex/heif/HeifError.cpp
@@ -1,64 +1,62 @@
/*
* Copyright (c) 2018 Dirk Farin <farin@struktur.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "HeifError.h"
-KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document,
+KisImportExportErrorCode setHeifError(KisDocument* document,
heif::Error error)
{
switch (error.get_code()) {
case heif_error_Ok:
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
case heif_error_Input_does_not_exist:
// this should never happen because we do not read from file names
- document->setErrorMessage(i18n("Internal error."));
- return KisImportExportFilter::InternalError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError);
+ return ImportExportCodes::InternalError;
case heif_error_Invalid_input:
case heif_error_Decoder_plugin_error:
- document->setErrorMessage(i18n("The HEIF file is corrupted."));
- return KisImportExportFilter::ParsingError;
+ return ImportExportCodes::FileFormatIncorrect;
case heif_error_Unsupported_filetype:
case heif_error_Unsupported_feature:
- document->setErrorMessage(i18n("Krita does not support this type of HEIF file."));
- return KisImportExportFilter::NotImplemented;
+ return ImportExportCodes::FormatFeaturesUnsupported;
case heif_error_Usage_error:
case heif_error_Encoder_plugin_error:
// this should never happen if we use libheif in the correct way
- document->setErrorMessage(i18n("Internal libheif API error."));
- return KisImportExportFilter::InternalError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError);
+ return ImportExportCodes::InternalError;
case heif_error_Memory_allocation_error:
document->setErrorMessage(i18n("Could not allocate memory."));
- return KisImportExportFilter::StorageCreationError;
+ return ImportExportCodes::InsufficientMemory;
case heif_error_Encoding_error:
document->setErrorMessage(i18n("Could not encode or write image."));
- return KisImportExportFilter::CreationError;
+ return ImportExportCodes::NoAccessToWrite;
default:
// we only get here when we forgot to handle an error ID
document->setErrorMessage(i18n("Unknown error."));
- return KisImportExportFilter::InternalError;
+ return ImportExportCodes::Failure;
}
}
diff --git a/plugins/impex/heif/HeifError.h b/plugins/impex/heif/HeifError.h
index 3fd38b018a..39ee3b2b7d 100644
--- a/plugins/impex/heif/HeifError.h
+++ b/plugins/impex/heif/HeifError.h
@@ -1,31 +1,32 @@
/*
* Copyright (c) 2018 Dirk Farin <farin@struktur.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef HEIF_ERROR_H_
#define HEIF_ERROR_H_
#include <KisDocument.h>
#include "libheif/heif_cxx.h"
+#include <KisImportExportErrorCode.h>
-KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document,
+KisImportExportErrorCode setHeifError(KisDocument* document,
heif::Error error);
#endif
diff --git a/plugins/impex/heif/HeifExport.cpp b/plugins/impex/heif/HeifExport.cpp
index 4ff1d388a5..ddf761e939 100644
--- a/plugins/impex/heif/HeifExport.cpp
+++ b/plugins/impex/heif/HeifExport.cpp
@@ -1,282 +1,282 @@
/*
* Copyright (c) 2018 Dirk Farin <farin@struktur.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "HeifExport.h"
#include "HeifError.h"
#include <QCheckBox>
#include <QSlider>
#include <QApplication>
#include <QScopedPointer>
#include <QBuffer>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceConstants.h>
#include <KoColorModelStandardIds.h>
#include <KisImportExportManager.h>
#include <KisExportCheckRegistry.h>
#include <kis_properties_configuration.h>
#include <kis_config.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_device.h>
#include <kis_paint_layer.h>
#include <kis_meta_data_store.h>
#include <kis_meta_data_entry.h>
#include <kis_meta_data_value.h>
#include <kis_meta_data_schema.h>
#include <kis_meta_data_schema_registry.h>
#include <kis_meta_data_filter_registry_model.h>
#include <kis_exif_info_visitor.h>
#include <kis_meta_data_io_backend.h>
#include "kis_iterator_ng.h"
#include "libheif/heif_cxx.h"
class KisExternalLayer;
K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_heif_export.json", registerPlugin<HeifExport>();)
HeifExport::HeifExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
HeifExport::~HeifExport()
{
}
KisPropertiesConfigurationSP HeifExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("quality", 50);
cfg->setProperty("lossless", true);
return cfg;
}
KisConfigWidget *HeifExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
return new KisWdgOptionsHeif(parent);
}
class Writer_QIODevice : public heif::Context::Writer
{
public:
Writer_QIODevice(QIODevice* io)
: m_io(io)
{
}
heif_error write(const void* data, size_t size) override {
qint64 n = m_io->write((const char*)data,size);
if (n != (qint64)size) {
QString error = m_io->errorString();
heif_error err = {
heif_error_Encoding_error,
heif_suberror_Cannot_write_output_data,
"Could not write output data" };
return err;
}
struct heif_error heif_error_ok = { heif_error_Ok, heif_suberror_Unspecified, "Success" };
return heif_error_ok;
}
private:
QIODevice* m_io;
};
-KisImportExportFilter::ConversionStatus HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
KisImageSP image = document->savingImage();
const KoColorSpace *cs = image->colorSpace();
// Convert to 8 bits rgba on saving
if (cs->colorModelId() != RGBAColorModelID || cs->colorDepthId() != Integer8BitsColorDepthID) {
cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id());
image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
int quality = configuration->getInt("quality", 50);
bool lossless = configuration->getBool("lossless", false);
bool has_alpha = configuration->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false);
// If we want to add information from the document to the metadata,
// we should do that here.
try {
// --- use standard HEVC encoder
heif::Encoder encoder(heif_compression_HEVC);
encoder.set_lossy_quality(quality);
encoder.set_lossless(lossless);
// --- convert KisImage to HEIF image ---
int width = image->width();
int height = image->height();
heif::Context ctx;
heif::Image img;
img.create(width,height, heif_colorspace_RGB, heif_chroma_444);
img.add_plane(heif_channel_R, width,height, 8);
img.add_plane(heif_channel_G, width,height, 8);
img.add_plane(heif_channel_B, width,height, 8);
uint8_t* ptrR {0};
uint8_t* ptrG {0};
uint8_t* ptrB {0};
uint8_t* ptrA {0};
int strideR,strideG,strideB,strideA;
ptrR = img.get_plane(heif_channel_R, &strideR);
ptrG = img.get_plane(heif_channel_G, &strideG);
ptrB = img.get_plane(heif_channel_B, &strideB);
if (has_alpha) {
img.add_plane(heif_channel_Alpha, width,height, 8);
ptrA = img.get_plane(heif_channel_Alpha, &strideA);
}
KisPaintDeviceSP pd = image->projection();
for (int y=0; y<height; y++) {
KisHLineIteratorSP it = pd->createHLineIteratorNG(0, y, width);
for (int x=0; x<width; x++) {
ptrR[y*strideR+x] = KoBgrTraits<quint8>::red(it->rawData());
ptrG[y*strideG+x] = KoBgrTraits<quint8>::green(it->rawData());
ptrB[y*strideB+x] = KoBgrTraits<quint8>::blue(it->rawData());
if (has_alpha) {
ptrA[y*strideA+x] = cs->opacityU8(it->rawData());
}
it->nextPixel();
}
}
// --- encode and write image
heif::ImageHandle handle = ctx.encode_image(img, encoder);
// --- add Exif / XMP metadata
KisExifInfoVisitor exivInfoVisitor;
exivInfoVisitor.visit(image->rootLayer().data());
QScopedPointer<KisMetaData::Store> metaDataStore;
if (exivInfoVisitor.metaDataCount() == 1) {
metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo()));
}
else {
metaDataStore.reset(new KisMetaData::Store());
}
if (!metaDataStore->empty()) {
{
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif");
QBuffer buffer;
exifIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else?
QByteArray data = buffer.data();
// Write the data to the file
ctx.add_exif_metadata(handle, data.constData(), data.size());
}
{
KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp");
QBuffer buffer;
xmpIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else?
QByteArray data = buffer.data();
// Write the data to the file
ctx.add_XMP_metadata(handle, data.constData(), data.size());
}
}
// --- write HEIF file
Writer_QIODevice writer(io);
ctx.write(writer);
}
catch (heif::Error err) {
return setHeifError(document, err);
}
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
void HeifExport::initializeCapabilities()
{
// This checks before saving for what the file format supports: anything that is supported needs to be mentioned here
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
/*<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID)*/
;
addSupportedColorModels(supportedColorModels, "HEIF");
}
void KisWdgOptionsHeif::setConfiguration(const KisPropertiesConfigurationSP cfg)
{
chkLossless->setChecked(cfg->getBool("lossless", true));
sliderQuality->setValue(qreal(cfg->getInt("quality", 50)));
m_hasAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false);
}
KisPropertiesConfigurationSP KisWdgOptionsHeif::configuration() const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("lossless", chkLossless->isChecked());
cfg->setProperty("quality", int(sliderQuality->value()));
cfg->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, m_hasAlpha);
return cfg;
}
void KisWdgOptionsHeif::toggleQualitySlider(bool toggle)
{
// Disable the quality slider if lossless is true
lossySettings->setEnabled(!toggle);
}
#include <HeifExport.moc>
diff --git a/plugins/impex/heif/HeifExport.h b/plugins/impex/heif/HeifExport.h
index 3eb7b3601e..3f15e4126d 100644
--- a/plugins/impex/heif/HeifExport.h
+++ b/plugins/impex/heif/HeifExport.h
@@ -1,72 +1,72 @@
/*
* Copyright (c) 2018 Dirk Farin <farin@struktur.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef HEIF_EXPORT_H_
#define HEIF_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
#include "ui_WdgHeifExport.h"
class KisWdgOptionsHeif : public KisConfigWidget, public Ui::WdgHeifExport
{
Q_OBJECT
public:
KisWdgOptionsHeif(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
connect(chkLossless, SIGNAL(toggled(bool)), SLOT(toggleQualitySlider(bool)));
sliderQuality->setRange(0, 100, 0);
}
void setConfiguration(const KisPropertiesConfigurationSP cfg) override;
KisPropertiesConfigurationSP configuration() const override;
private Q_SLOTS:
void toggleQualitySlider(bool toggle);
private:
bool m_hasAlpha {false};
};
class HeifExport : public KisImportExportFilter
{
Q_OBJECT
public:
HeifExport(QObject *parent, const QVariantList &);
~HeifExport() override;
// This should return true if the library can work with a QIODevice, and doesn't want to open the file by itself
bool supportsIO() const override { return true; }
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/heif/HeifImport.cpp b/plugins/impex/heif/HeifImport.cpp
index ec9675feba..e903f23483 100644
--- a/plugins/impex/heif/HeifImport.cpp
+++ b/plugins/impex/heif/HeifImport.cpp
@@ -1,189 +1,189 @@
/*
* Copyright (c) 2018 Dirk Farin <farin@struktur.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "HeifImport.h"
#include "HeifError.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QBuffer>
#include <KisImportExportManager.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <kis_transaction.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <kis_meta_data_entry.h>
#include <kis_meta_data_value.h>
#include <kis_meta_data_store.h>
#include <kis_meta_data_io_backend.h>
#include "kis_iterator_ng.h"
#include "libheif/heif_cxx.h"
K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin<HeifImport>();)
HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
HeifImport::~HeifImport()
{
}
class Reader_QIODevice : public heif::Context::Reader {
public:
Reader_QIODevice(QIODevice* device) : m_device(device) { m_total_length=m_device->bytesAvailable(); }
int64_t get_position() const { return m_device->pos(); }
int read(void* data, size_t size) { return m_device->read((char*)data,size) != size; }
int seek(int64_t position) { return !m_device->seek(position); }
heif_reader_grow_status wait_for_file_size(int64_t target_size) {
return (target_size > m_total_length) ? heif_reader_grow_status_size_beyond_eof : heif_reader_grow_status_size_reached;
}
private:
QIODevice* m_device;
int64_t m_total_length;
};
-KisImportExportFilter::ConversionStatus HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
// Wrap input stream into heif Reader object
Reader_QIODevice reader(io);
try {
heif::Context ctx;
ctx.read_from_reader(reader);
// decode primary image
heif::ImageHandle handle = ctx.get_primary_image_handle();
heif::Image heifimage = handle.decode_image(heif_colorspace_RGB, heif_chroma_444);
int width =handle.get_width();
int height=handle.get_height();
bool hasAlpha = handle.has_alpha_channel();
// convert HEIF image to Krita KisDocument
int strideR, strideG, strideB, strideA;
const uint8_t* imgR = heifimage.get_plane(heif_channel_R, &strideR);
const uint8_t* imgG = heifimage.get_plane(heif_channel_G, &strideG);
const uint8_t* imgB = heifimage.get_plane(heif_channel_B, &strideB);
const uint8_t* imgA = heifimage.get_plane(heif_channel_Alpha, &strideA);
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace,
"HEIF image");
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255);
for (int y=0;y<height;y++) {
KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, y, width);
for (int x=0;x<width;x++) {
KoBgrTraits<quint8>::setRed(it->rawData(), imgR[y*strideR+x]);
KoBgrTraits<quint8>::setGreen(it->rawData(), imgG[y*strideG+x]);
KoBgrTraits<quint8>::setBlue(it->rawData(), imgB[y*strideB+x]);
if (hasAlpha) {
colorSpace->setOpacity(it->rawData(), quint8(imgA[y*strideA+x]), 1);
}
else {
colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
}
it->nextPixel();
}
}
image->addNode(layer.data(), image->rootLayer().data());
// --- Iterate through all metadata blocks and extract Exif and XMP metadata ---
std::vector<heif_item_id> metadata_IDs = handle.get_list_of_metadata_block_IDs();
for (heif_item_id id : metadata_IDs) {
if (handle.get_metadata_type(id) == "Exif") {
// Read exif information
std::vector<uint8_t> exif_data = handle.get_metadata(id);
if (exif_data.size()>4) {
uint32_t skip = ((exif_data[0]<<24) | (exif_data[1]<<16) | (exif_data[2]<<8) | exif_data[3]) + 4;
if (exif_data.size()>skip) {
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif");
// Copy the exif data into the byte array
QByteArray ba;
ba.append((char*)(exif_data.data()+skip), exif_data.size()-skip);
QBuffer buf(&ba);
exifIO->loadFrom(layer->metaData(), &buf);
}
}
}
if (handle.get_metadata_type(id) == "mime" &&
handle.get_metadata_content_type(id) == "application/rdf+xml") {
// Read XMP information
std::vector<uint8_t> xmp_data = handle.get_metadata(id);
KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp");
// Copy the xmp data into the byte array
QByteArray ba;
ba.append((char*)(xmp_data.data()), xmp_data.size());
QBuffer buf(&ba);
xmpIO->loadFrom(layer->metaData(), &buf);
}
}
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
catch (heif::Error err) {
return setHeifError(document, err);
}
}
#include <HeifImport.moc>
diff --git a/plugins/impex/heif/HeifImport.h b/plugins/impex/heif/HeifImport.h
index 6f162eeba6..9c99161af5 100644
--- a/plugins/impex/heif/HeifImport.h
+++ b/plugins/impex/heif/HeifImport.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2018 Dirk Farin <farin@struktur.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef HEIF_IMPORT_H_
#define HEIF_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class HeifImport : public KisImportExportFilter
{
Q_OBJECT
public:
HeifImport(QObject *parent, const QVariantList &);
~HeifImport() override;
bool supportsIO() const override { return true; }
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/heif/tests/CMakeLists.txt b/plugins/impex/heif/tests/CMakeLists.txt
new file mode 100644
index 0000000000..0f9cfe6c94
--- /dev/null
+++ b/plugins/impex/heif/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(KisHeifTest.cpp
+ TEST_NAME KisHeifTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/heif/tests/KisHeifTest.cpp
similarity index 64%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/heif/tests/KisHeifTest.cpp
index 6ca66688f8..7b95b42ec7 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/heif/tests/KisHeifTest.cpp
@@ -1,40 +1,57 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisHeifTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString HeifMimetype = "image/heic";
+
+
+
+void KisHeifTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), HeifMimetype);
+}
+
+
+void KisHeifTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), HeifMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisHeifTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), HeifMimetype);
+}
+
+
+
+KISTEST_MAIN(KisHeifTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/heif/tests/KisHeifTest.h
similarity index 73%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/heif/tests/KisHeifTest.h
index 4dad62dfca..ea09c9a836 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/heif/tests/KisHeifTest.h
@@ -1,32 +1,35 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_HEIF_TEST_H_
+#define _KIS_HEIF_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisHeifTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_HEIF_TEST_H_
+
diff --git a/plugins/impex/heif/tests/data/incorrectFormatFile.txt b/plugins/impex/heif/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/heif/tests/data/readonlyFile.txt b/plugins/impex/heif/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/heif/tests/data/writeonlyFile.txt b/plugins/impex/heif/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/heightmap/kis_heightmap_export.cpp b/plugins/impex/heightmap/kis_heightmap_export.cpp
index 085dc7e4f3..cea427fac5 100644
--- a/plugins/impex/heightmap/kis_heightmap_export.cpp
+++ b/plugins/impex/heightmap/kis_heightmap_export.cpp
@@ -1,148 +1,150 @@
/*
* Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2017 Victor Wåhlström <victor.wahlstrom@initiali.se>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_heightmap_export.h"
#include <qendian.h>
#include <QDataStream>
#include <QApplication>
#include <kpluginfactory.h>
#include <KoColorSpace.h>
#include <KoColorSpaceConstants.h>
#include <KoColorSpaceTraits.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KisImportExportManager.h>
#include <KisExportCheckRegistry.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_properties_configuration.h>
#include <kis_config.h>
#include <kis_iterator_ng.h>
#include <kis_random_accessor_ng.h>
#include <kis_config_widget.h>
#include "kis_wdg_options_heightmap.h"
#include "kis_heightmap_utils.h"
K_PLUGIN_FACTORY_WITH_JSON(KisHeightMapExportFactory, "krita_heightmap_export.json", registerPlugin<KisHeightMapExport>();)
template<typename T>
static void writeData(KisPaintDeviceSP pd, const QRect &bounds, QDataStream &out_stream)
{
KIS_ASSERT_RECOVER_RETURN(pd);
KisSequentialConstIterator it(pd, bounds);
while (it.nextPixel()) {
out_stream << KoGrayTraits<T>::gray(const_cast<quint8*>(it.rawDataConst()));
}
}
KisHeightMapExport::KisHeightMapExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisHeightMapExport::~KisHeightMapExport()
{
}
KisPropertiesConfigurationSP KisHeightMapExport::defaultConfiguration(const QByteArray &from, const QByteArray &to) const
{
Q_UNUSED(from);
Q_UNUSED(to);
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("endianness", 0);
return cfg;
}
KisConfigWidget *KisHeightMapExport::createConfigurationWidget(QWidget *parent, const QByteArray &from, const QByteArray &to) const
{
Q_UNUSED(from);
Q_UNUSED(to);
bool export_mode = true;
KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(parent, export_mode);
return wdg;
}
void KisHeightMapExport::initializeCapabilities()
{
if (mimeType() == "image/x-r8") {
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "R8 Heightmap");
}
else if (mimeType() == "image/x-r16") {
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "R16 Heightmap");
}
else if (mimeType() == "image/x-r32") {
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(GrayAColorModelID, Float32BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "R32 Heightmap");
}
}
-KisImportExportFilter::ConversionStatus KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
- KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", KisImportExportFilter::WrongFormat);
+ KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", ImportExportCodes::FileFormatIncorrect);
KisImageSP image = document->savingImage();
QDataStream::ByteOrder bo = configuration->getInt("endianness", 1) == 0 ? QDataStream::BigEndian : QDataStream::LittleEndian;
KisPaintDeviceSP pd = new KisPaintDevice(*image->projection());
QDataStream s(io);
s.setByteOrder(bo);
// needed for 32bit float data
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
KoID target_co_model = GrayAColorModelID;
KoID target_co_depth = KisHeightmapUtils::mimeTypeToKoID(mimeType());
KIS_ASSERT(!target_co_depth.id().isNull());
if (pd->colorSpace()->colorModelId() != target_co_model || pd->colorSpace()->colorDepthId() != target_co_depth) {
pd = new KisPaintDevice(*pd.data());
pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(target_co_model.id(), target_co_depth.id()));
}
if (target_co_depth == Float32BitsColorDepthID) {
writeData<float>(pd, image->bounds(), s);
}
else if (target_co_depth == Integer16BitsColorDepthID) {
writeData<quint16>(pd, image->bounds(), s);
}
else if (target_co_depth == Integer8BitsColorDepthID) {
writeData<quint8>(pd, image->bounds(), s);
}
else {
- return KisImportExportFilter::InternalError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError);
+ return ImportExportCodes::InternalError;
}
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_heightmap_export.moc"
diff --git a/plugins/impex/heightmap/kis_heightmap_export.h b/plugins/impex/heightmap/kis_heightmap_export.h
index f686961e27..0ff29cb890 100644
--- a/plugins/impex/heightmap/kis_heightmap_export.h
+++ b/plugins/impex/heightmap/kis_heightmap_export.h
@@ -1,41 +1,42 @@
/*
* Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2017 Victor Wåhlström <victor.wahlstrom@initiali.se>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_HeightMap_EXPORT_H_
#define _KIS_HeightMap_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
class KisHeightMapExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisHeightMapExport(QObject *parent, const QVariantList &);
~KisHeightMapExport() override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/heightmap/kis_heightmap_import.cpp b/plugins/impex/heightmap/kis_heightmap_import.cpp
index 9f899267b5..6e53044f7f 100644
--- a/plugins/impex/heightmap/kis_heightmap_import.cpp
+++ b/plugins/impex/heightmap/kis_heightmap_import.cpp
@@ -1,198 +1,204 @@
/*
* Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2017 Victor Wåhlström <victor.wahlstrom@initiali.se>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_heightmap_import.h"
#include <ctype.h>
#include <QApplication>
#include <qendian.h>
#include <kpluginfactory.h>
#include <KoDialog.h>
#include <KisImportExportManager.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpace.h>
#include <KoColorSpaceTraits.h>
#include <kis_debug.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_iterator_ng.h>
#include <kis_random_accessor_ng.h>
#include <kis_config.h>
#include "kis_wdg_options_heightmap.h"
#include "kis_heightmap_utils.h"
K_PLUGIN_FACTORY_WITH_JSON(HeightMapImportFactory, "krita_heightmap_import.json", registerPlugin<KisHeightMapImport>();)
template<typename T>
void fillData(KisPaintDeviceSP pd, int w, int h, QDataStream &stream) {
KIS_ASSERT_RECOVER_RETURN(pd);
T pixel;
for (int i = 0; i < h; ++i) {
KisHLineIteratorSP it = pd->createHLineIteratorNG(0, i, w);
do {
stream >> pixel;
KoGrayTraits<T>::setGray(it->rawData(), pixel);
KoGrayTraits<T>::setOpacity(it->rawData(), OPACITY_OPAQUE_F, 1);
} while(it->nextPixel());
}
}
KisHeightMapImport::KisHeightMapImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisHeightMapImport::~KisHeightMapImport()
{
}
-KisImportExportFilter::ConversionStatus KisHeightMapImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisHeightMapImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
Q_UNUSED(configuration);
KoID depthId = KisHeightmapUtils::mimeTypeToKoID(mimeType());
if (depthId.id().isNull()) {
document->setErrorMessage(i18n("Unknown file type"));
- return KisImportExportFilter::WrongFormat;
+ return ImportExportCodes::FileFormatIncorrect;
}
int w = 0;
int h = 0;
KIS_ASSERT(io->isOpen());
const quint64 size = io->size();
+ if (size == 0) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
+
QDataStream::ByteOrder bo = QDataStream::LittleEndian;
if (!batchMode()) {
QApplication::restoreOverrideCursor();
KoDialog* kdb = new KoDialog(0);
kdb->setWindowTitle(i18n("Heightmap Import Options"));
kdb->setButtons(KoDialog::Ok | KoDialog::Cancel);
KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(kdb);
kdb->setMainWidget(wdg);
connect(wdg, SIGNAL(statusUpdated(bool)), kdb, SLOT(enableButtonOk(bool)));
KisConfig config(true);
QString filterConfig = config.importConfiguration(mimeType());
KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration);
cfg->fromXML(filterConfig);
int endianness = cfg->getInt("endianness", 1);
if (endianness == 0) {
wdg->radioBig->setChecked(true);
}
else {
wdg->radioLittle->setChecked(true);
}
wdg->fileSizeLabel->setText(QString::number(size));
if(depthId == Integer8BitsColorDepthID) {
wdg->bppLabel->setText(QString::number(8));
wdg->typeLabel->setText("Integer");
}
else if(depthId == Integer16BitsColorDepthID) {
wdg->bppLabel->setText(QString::number(16));
wdg->typeLabel->setText("Integer");
}
else if(depthId == Float32BitsColorDepthID) {
wdg->bppLabel->setText(QString::number(32));
wdg->typeLabel->setText("Float");
}
else {
- return KisImportExportFilter::InternalError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError);
+ return ImportExportCodes::InternalError;
}
if (kdb->exec() == QDialog::Rejected) {
- return KisImportExportFilter::UserCancelled;
+ return ImportExportCodes::Cancelled;
}
cfg->setProperty("endianness", wdg->radioBig->isChecked() ? 0 : 1);
config.setImportConfiguration(mimeType(), cfg);
w = wdg->widthInput->value();
h = wdg->heightInput->value();
bo = QDataStream::LittleEndian;
cfg->setProperty("endianness", 1);
if (wdg->radioBig->isChecked()) {
bo = QDataStream::BigEndian;
cfg->setProperty("endianness", 0);
}
KisConfig(true).setExportConfiguration(mimeType(), cfg);
} else {
const int pixelSize =
depthId == Float32BitsColorDepthID ? 4 :
depthId == Integer16BitsColorDepthID ? 2 : 1;
const int numPixels = size / pixelSize;
w = std::sqrt(numPixels);
h = numPixels / w;
bo = QDataStream::LittleEndian;
}
QDataStream s(io);
s.setByteOrder(bo);
// needed for 32bit float data
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), depthId.id(), "Gray-D50-elle-V2-srgbtrc.icc");
KisImageSP image = new KisImage(document->createUndoStore(), w, h, colorSpace, "imported heightmap");
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255);
if (depthId == Float32BitsColorDepthID) {
fillData<float>(layer->paintDevice(), w, h, s);
}
else if (depthId == Integer16BitsColorDepthID) {
fillData<quint16>(layer->paintDevice(), w, h, s);
}
else if (depthId == Integer8BitsColorDepthID) {
fillData<quint8>(layer->paintDevice(), w, h, s);
}
else {
- return KisImportExportFilter::InternalError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError);
+ return ImportExportCodes::InternalError;
}
image->addNode(layer.data(), image->rootLayer().data());
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_heightmap_import.moc"
diff --git a/plugins/impex/heightmap/kis_heightmap_import.h b/plugins/impex/heightmap/kis_heightmap_import.h
index 94f1df9449..1a89a8171d 100644
--- a/plugins/impex/heightmap/kis_heightmap_import.h
+++ b/plugins/impex/heightmap/kis_heightmap_import.h
@@ -1,38 +1,38 @@
/*
* Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2017 Victor Wåhlström <victor.wahlstrom@initiali.se>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_HeightMap_IMPORT_H_
#define _KIS_HeightMap_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisHeightMapImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisHeightMapImport(QObject *parent, const QVariantList &);
~KisHeightMapImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/heightmap/tests/data/incorrectFormatFile.txt b/plugins/impex/heightmap/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/heightmap/tests/data/readonlyFile.txt b/plugins/impex/heightmap/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/heightmap/tests/data/writeonlyFile.txt b/plugins/impex/heightmap/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp
index 13fc7f3026..82c5d7d3ad 100644
--- a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp
+++ b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp
@@ -1,38 +1,62 @@
/*
* Copyright (c) 2017 Victor Wåhlström <victor.wahlstrom@initiali.se>
*
* 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_heightmap_test.h"
#include <QTest>
#include <QCoreApplication>
#include <sdk/tests/kistest.h>
#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 HeightmapMimetype = "image/x-r8";
+
+
void KisHeightmapTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1);
}
+
+
+
+void KisHeightmapTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), HeightmapMimetype);
+}
+
+
+void KisHeightmapTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), HeightmapMimetype);
+}
+
+
+void KisHeightmapTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), HeightmapMimetype);
+}
+
+
KISTEST_MAIN(KisHeightmapTest)
diff --git a/plugins/impex/heightmap/tests/kis_heightmap_test.h b/plugins/impex/heightmap/tests/kis_heightmap_test.h
index b22fa1d2ba..05d960740e 100644
--- a/plugins/impex/heightmap/tests/kis_heightmap_test.h
+++ b/plugins/impex/heightmap/tests/kis_heightmap_test.h
@@ -1,31 +1,35 @@
/*
* Copyright (c) 2017 Victor Wåhlström <victor.wahlstrom@initiali.se>
*
* 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_HEIGHTMAP_TEST_H_
#define _KIS_HEIGHTMAP_TEST_H_
#include <QtTest>
class KisHeightmapTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
#endif // _KIS_HEIGHTMAP_TEST_H_
diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc
index eca195fde1..276b8af756 100644
--- a/plugins/impex/jpeg/kis_jpeg_converter.cc
+++ b/plugins/impex/jpeg/kis_jpeg_converter.cc
@@ -1,730 +1,728 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_jpeg_converter.h"
#include <stdio.h>
#include <stdint.h>
#include <KoConfig.h>
#ifdef HAVE_LCMS2
# include <lcms2.h>
#else
# include <lcms.h>
#endif
extern "C" {
#include <iccjpeg.h>
}
#include <exiv2/jpgimage.hpp>
#include <QFile>
#include <QBuffer>
#include <QApplication>
#include <klocalizedstring.h>
#include <QFileInfo>
#include <KoDocumentInfo.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoColor.h>
#include <KoUnit.h>
#include "KoColorModelStandardIds.h"
#include <kis_painter.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_transaction.h>
#include <kis_group_layer.h>
#include <kis_meta_data_entry.h>
#include <kis_meta_data_value.h>
#include <kis_meta_data_store.h>
#include <kis_meta_data_io_backend.h>
#include <kis_paint_device.h>
#include <kis_transform_worker.h>
#include <kis_jpeg_source.h>
#include <kis_jpeg_destination.h>
#include "kis_iterator_ng.h"
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
const char photoshopMarker[] = "Photoshop 3.0\0";
//const char photoshopBimId_[] = "8BIM";
const uint16_t photoshopIptc = 0x0404;
const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0";
const QByteArray photoshopIptc_((char*)&photoshopIptc, 2);
namespace
{
void jpegErrorExit ( j_common_ptr cinfo )
{
char jpegLastErrorMsg[JMSG_LENGTH_MAX];
/* Create the message */
( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg );
/* Jump to the setjmp point */
throw std::runtime_error( jpegLastErrorMsg ); // or your preferred exception ...
}
J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs)
{
if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") {
return JCS_GRAYSCALE;
}
if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) {
return JCS_RGB;
}
if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) {
return JCS_CMYK;
}
return JCS_UNKNOWN;
}
QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type)
{
dbgFile << "color_type =" << color_type;
if (color_type == JCS_GRAYSCALE) {
return GrayAColorModelID.id();
} else if (color_type == JCS_RGB) {
return RGBAColorModelID.id();
} else if (color_type == JCS_CMYK) {
return CMYKAColorModelID.id();
}
return "";
}
}
struct KisJPEGConverter::Private
{
Private(KisDocument *doc, bool batchMode)
: doc(doc),
stop(false),
batchMode(batchMode)
{}
KisImageSP image;
KisDocument *doc;
bool stop;
bool batchMode;
};
KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode)
: m_d(new Private(doc, batchMode))
{
}
KisJPEGConverter::~KisJPEGConverter()
{
}
-KisImageBuilder_Result KisJPEGConverter::decode(QIODevice *io)
+KisImportExportErrorCode KisJPEGConverter::decode(QIODevice *io)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = jpegErrorExit;
try {
jpeg_create_decompress(&cinfo);
KisJPEGSource::setSource(&cinfo, io);
jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
/* Save APP0..APP15 markers */
for (int m = 0; m < 16; m++)
jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF);
// setup_read_icc_profile(&cinfo);
// read header
jpeg_read_header(&cinfo, (boolean)true);
// start reading
jpeg_start_decompress(&cinfo);
// Get the colorspace
QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space);
if (modelId.isEmpty()) {
dbgFile << "unsupported colorspace :" << cinfo.out_color_space;
jpeg_destroy_decompress(&cinfo);
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
uchar* profile_data;
uint profile_len;
const KoColorProfile* profile = 0;
QByteArray profile_rawdata;
if (read_icc_profile(&cinfo, &profile_data, &profile_len)) {
profile_rawdata.resize(profile_len);
memcpy(profile_rawdata.data(), profile_data, profile_len);
cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len);
if (hProfile != (cmsHPROFILE) 0) {
profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata);
Q_CHECK_PTR(profile);
dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info();
if (!profile->isSuitableForOutput()) {
dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user
}
}
}
const QString colorSpaceId =
KoColorSpaceRegistry::instance()->colorSpaceId(modelId, Integer8BitsColorDepthID.id());
// Check that the profile is used by the color space
if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) {
warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId;
profile = 0;
}
// Retrieve a pointer to the colorspace
const KoColorSpace* cs;
if (profile && profile->isSuitableForOutput()) {
dbgFile << "image has embedded profile:" << profile -> name() << "";
cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile);
} else
cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), "");
if (cs == 0) {
dbgFile << "unknown colorspace";
jpeg_destroy_decompress(&cinfo);
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
// TODO fixit
// Create the cmsTransform if needed
KoColorTransformation* transform = 0;
if (profile && !profile->isSuitableForOutput()) {
transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
// Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451.
// After 2.3: warn the user!
if (transform && !transform->isValid()) {
delete transform;
transform = 0;
}
// Creating the KisImageSP
if (!m_d->image) {
m_d->image = new KisImage(m_d->doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image");
Q_CHECK_PTR(m_d->image);
}
// Set resolution
double xres = 72, yres = 72;
if (cinfo.density_unit == 1) {
xres = cinfo.X_density;
yres = cinfo.Y_density;
} else if (cinfo.density_unit == 2) {
xres = cinfo.X_density * 2.54;
yres = cinfo.Y_density * 2.54;
}
if (xres < 72) {
xres = 72;
}
if (yres < 72) {
yres = 72;
}
m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points
// Create layer
KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_d->image.data(), m_d->image -> nextLayerName(), quint8_MAX));
// Read data
JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components];
for (; cinfo.output_scanline < cinfo.image_height;) {
KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width);
jpeg_read_scanlines(&cinfo, &row_pointer, 1);
quint8 *src = row_pointer;
switch (cinfo.out_color_space) {
case JCS_GRAYSCALE:
do {
quint8 *d = it->rawData();
d[0] = *(src++);
if (transform) transform->transform(d, d, 1);
d[1] = quint8_MAX;
} while (it->nextPixel());
break;
case JCS_RGB:
do {
quint8 *d = it->rawData();
d[2] = *(src++);
d[1] = *(src++);
d[0] = *(src++);
if (transform) transform->transform(d, d, 1);
d[3] = quint8_MAX;
} while (it->nextPixel());
break;
case JCS_CMYK:
do {
quint8 *d = it->rawData();
d[0] = quint8_MAX - *(src++);
d[1] = quint8_MAX - *(src++);
d[2] = quint8_MAX - *(src++);
d[3] = quint8_MAX - *(src++);
if (transform) transform->transform(d, d, 1);
d[4] = quint8_MAX;
} while (it->nextPixel());
break;
default:
- return KisImageBuilder_RESULT_UNSUPPORTED;
+ return ImportExportCodes::FormatFeaturesUnsupported;
}
}
m_d->image->addNode(KisNodeSP(layer.data()), m_d->image->rootLayer().data());
// Read exif information
dbgFile << "Looking for exif information";
for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) {
dbgFile << "Marker is" << marker->marker;
if (marker->marker != (JOCTET)(JPEG_APP0 + 1)
|| marker->data_length < 14) {
continue; /* Exif data is in an APP1 marker of at least 14 octets */
}
if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 ||
GETJOCTET(marker->data[1]) != (JOCTET) 0x78 ||
GETJOCTET(marker->data[2]) != (JOCTET) 0x69 ||
GETJOCTET(marker->data[3]) != (JOCTET) 0x66 ||
GETJOCTET(marker->data[4]) != (JOCTET) 0x00 ||
GETJOCTET(marker->data[5]) != (JOCTET) 0x00)
continue; /* no Exif header */
dbgFile << "Found exif information of length :" << marker->data_length;
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif");
Q_ASSERT(exifIO);
QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6);
QBuffer buf(&byteArray);
exifIO->loadFrom(layer->metaData(), &buf);
// Interpret orientation tag
if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) {
KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation");
if (entry.value().type() == KisMetaData::Value::Variant) {
switch (entry.value().asVariant().toInt()) {
case 2:
KisTransformWorker::mirrorY(layer->paintDevice());
break;
case 3:
image()->rotateImage(M_PI);
break;
case 4:
KisTransformWorker::mirrorX(layer->paintDevice());
break;
case 5:
image()->rotateImage(M_PI / 2);
KisTransformWorker::mirrorY(layer->paintDevice());
break;
case 6:
image()->rotateImage(M_PI / 2);
break;
case 7:
image()->rotateImage(M_PI / 2);
KisTransformWorker::mirrorX(layer->paintDevice());
break;
case 8:
image()->rotateImage(-M_PI / 2 + M_PI*2);
break;
default:
break;
}
}
entry.value().setVariant(1);
}
break;
}
dbgFile << "Looking for IPTC information";
for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) {
dbgFile << "Marker is" << marker->marker;
if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) {
continue; /* IPTC data is in an APP13 marker of at least 16 octets */
}
if (memcmp(marker->data, photoshopMarker, 14) != 0) {
for (int i = 0; i < 14; i++) {
dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]);
}
dbgFile << "No photoshop marker";
continue; /* No IPTC Header */
}
dbgFile << "Found Photoshop information of length :" << marker->data_length;
KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc");
Q_ASSERT(iptcIO);
const Exiv2::byte *record = 0;
uint32_t sizeIptc = 0;
uint32_t sizeHdr = 0;
// Find actual Iptc data within the APP13 segment
if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14),
marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) {
if (sizeIptc) {
// Decode the IPTC data
QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc);
QBuffer buf(&byteArray);
iptcIO->loadFrom(layer->metaData(), &buf);
} else {
dbgFile << "IPTC Not found in Photoshop marker";
}
}
break;
}
dbgFile << "Looking for XMP information";
for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) {
dbgFile << "Marker is" << marker->marker;
if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) {
continue; /* XMP data is in an APP1 marker of at least 31 octets */
}
if (memcmp(marker->data, xmpMarker, 29) != 0) {
dbgFile << "Not XMP marker";
continue; /* No xmp Header */
}
dbgFile << "Found XMP Marker of length " << marker->data_length;
QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29);
KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp");
Q_ASSERT(xmpIO);
xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray));
break;
}
// Dump loaded metadata
layer->metaData()->debugDump();
// Check whether the metadata has resolution info, too...
if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) {
double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble();
double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble();
if (xres != 0 && yres != 0) {
m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points
}
}
// Finish decompression
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
delete [] row_pointer;
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
catch( std::runtime_error &e) {
jpeg_destroy_decompress(&cinfo);
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
}
-KisImageBuilder_Result KisJPEGConverter::buildImage(QIODevice *io)
+KisImportExportErrorCode KisJPEGConverter::buildImage(QIODevice *io)
{
return decode(io);
}
KisImageSP KisJPEGConverter::image()
{
return m_d->image;
}
-KisImageBuilder_Result KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData)
+KisImportExportErrorCode KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData)
{
- if (!layer)
- return KisImageBuilder_RESULT_INVALID_ARG;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
KisImageSP image = KisImageSP(layer->image());
- if (!image)
- return KisImageBuilder_RESULT_EMPTY;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
const KoColorSpace * cs = layer->colorSpace();
J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs);
if (color_type == JCS_UNKNOWN) {
layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
cs = KoColorSpaceRegistry::instance()->rgb8();
color_type = JCS_RGB;
}
if (options.forceSRGB) {
const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)");
layer->paintDevice()->convertTo(dst);
cs = dst;
color_type = JCS_RGB;
}
uint height = image->height();
uint width = image->width();
// Initialize structure
struct jpeg_compress_struct cinfo;
// Initialize error output
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
// Initialize output stream
KisJPEGDestination::setDestination(&cinfo, io);
cinfo.image_width = width; // image width and height, in pixels
cinfo.image_height = height;
cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */
cinfo.in_color_space = color_type; // colorspace of input image
// Set default compression parameters
jpeg_set_defaults(&cinfo);
// Customize them
jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG);
if (options.progressive) {
jpeg_simple_progression(&cinfo);
}
// Optimize ?
cinfo.optimize_coding = (boolean)options.optimize;
// Smoothing
cinfo.smoothing_factor = (boolean)options.smooth;
// Subsampling
switch (options.subsampling) {
default:
case 0: {
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 2;
cinfo.comp_info[1].h_samp_factor = 1;
cinfo.comp_info[1].v_samp_factor = 1;
cinfo.comp_info[2].h_samp_factor = 1;
cinfo.comp_info[2].v_samp_factor = 1;
}
break;
case 1: {
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 1;
cinfo.comp_info[1].h_samp_factor = 1;
cinfo.comp_info[1].v_samp_factor = 1;
cinfo.comp_info[2].h_samp_factor = 1;
cinfo.comp_info[2].v_samp_factor = 1;
}
break;
case 2: {
cinfo.comp_info[0].h_samp_factor = 1;
cinfo.comp_info[0].v_samp_factor = 2;
cinfo.comp_info[1].h_samp_factor = 1;
cinfo.comp_info[1].v_samp_factor = 1;
cinfo.comp_info[2].h_samp_factor = 1;
cinfo.comp_info[2].v_samp_factor = 1;
}
break;
case 3: {
cinfo.comp_info[0].h_samp_factor = 1;
cinfo.comp_info[0].v_samp_factor = 1;
cinfo.comp_info[1].h_samp_factor = 1;
cinfo.comp_info[1].v_samp_factor = 1;
cinfo.comp_info[2].h_samp_factor = 1;
cinfo.comp_info[2].v_samp_factor = 1;
}
break;
}
// Save resolution
cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points
cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points
cinfo.density_unit = 1;
cinfo.write_JFIF_header = (boolean)true;
// Start compression
jpeg_start_compress(&cinfo, (boolean)true);
// Save exif and iptc information if any available
if (metaData && !metaData->empty()) {
metaData->applyFilters(options.filters);
// Save EXIF
if (options.exif) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif");
Q_ASSERT(exifIO);
QBuffer buffer;
exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
dbgFile << "Exif information size is" << buffer.data().size();
QByteArray data = buffer.data();
if (data.size() < MAX_DATA_BYTES_IN_MARKER) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size());
} else {
dbgFile << "EXIF information could not be saved."; // TODO: warn the user ?
}
}
// Save IPTC
if (options.iptc) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc");
Q_ASSERT(iptcIO);
QBuffer buffer;
iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
dbgFile << "IPTC information size is" << buffer.data().size();
QByteArray data = buffer.data();
if (data.size() < MAX_DATA_BYTES_IN_MARKER) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size());
} else {
dbgFile << "IPTC information could not be saved."; // TODO: warn the user ?
}
}
// Save XMP
if (options.xmp) {
dbgFile << "Trying to save XMP information";
KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp");
Q_ASSERT(xmpIO);
QBuffer buffer;
xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
dbgFile << "XMP information size is" << buffer.data().size();
QByteArray data = buffer.data();
if (data.size() < MAX_DATA_BYTES_IN_MARKER) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size());
} else {
dbgFile << "XMP information could not be saved."; // TODO: warn the user ?
}
}
}
KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace());
KoColor c(options.transparencyFillColor, layer->colorSpace());
dev->fill(QRect(0, 0, width, height), c);
KisPainter gc(dev);
gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height));
gc.end();
if (options.saveProfile) {
const KoColorProfile* colorProfile = layer->colorSpace()->profile();
QByteArray colorProfileData = colorProfile->rawData();
write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size());
}
// Write data information
JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components];
int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount();
for (; cinfo.next_scanline < height;) {
KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width);
quint8 *dst = row_pointer;
switch (color_type) {
case JCS_GRAYSCALE:
if (color_nb_bits == 16) {
do {
//const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
const quint8 *d = it->oldRawData();
*(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX;
} while (it->nextPixel());
} else {
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[0];
} while (it->nextPixel());
}
break;
case JCS_RGB:
if (color_nb_bits == 16) {
do {
//const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
const quint8 *d = it->oldRawData();
*(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX;
*(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX;
*(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX;
} while (it->nextPixel());
} else {
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[2];
*(dst++) = d[1];
*(dst++) = d[0];
} while (it->nextPixel());
}
break;
case JCS_CMYK:
if (color_nb_bits == 16) {
do {
//const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
const quint8 *d = it->oldRawData();
*(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX;
*(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX;
*(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX;
*(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX;
} while (it->nextPixel());
} else {
do {
const quint8 *d = it->oldRawData();
*(dst++) = quint8_MAX - d[0];
*(dst++) = quint8_MAX - d[1];
*(dst++) = quint8_MAX - d[2];
*(dst++) = quint8_MAX - d[3];
} while (it->nextPixel());
}
break;
default:
delete [] row_pointer;
jpeg_destroy_compress(&cinfo);
- return KisImageBuilder_RESULT_UNSUPPORTED;
+ return ImportExportCodes::FormatFeaturesUnsupported;
}
jpeg_write_scanlines(&cinfo, &row_pointer, 1);
}
// Writing is over
jpeg_finish_compress(&cinfo);
delete [] row_pointer;
// Free memory
jpeg_destroy_compress(&cinfo);
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
void KisJPEGConverter::cancel()
{
m_d->stop = true;
}
diff --git a/plugins/impex/jpeg/kis_jpeg_converter.h b/plugins/impex/jpeg/kis_jpeg_converter.h
index 45a2c9c0ac..070c013ce3 100644
--- a/plugins/impex/jpeg/kis_jpeg_converter.h
+++ b/plugins/impex/jpeg/kis_jpeg_converter.h
@@ -1,86 +1,86 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* 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_JPEG_CONVERTER_H_
#define _KIS_JPEG_CONVERTER_H_
#include <stdio.h>
extern "C" {
#include <jpeglib.h>
}
#include <QColor>
#include <QVector>
#include "kis_types.h"
#include "kis_annotation.h"
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
class KisDocument;
namespace KisMetaData
{
class Filter;
}
struct KisJPEGOptions {
int quality;
bool progressive;
bool optimize;
int smooth;
bool baseLineJPEG;
int subsampling;
bool exif;
bool iptc;
bool xmp;
QList<const KisMetaData::Filter*> filters;
QColor transparencyFillColor;
bool forceSRGB;
bool saveProfile;
bool storeDocumentMetaData; //this is for getting the metadata from the document info.
bool storeAuthor; //this is for storing author data from the document info.
};
namespace KisMetaData
{
class Store;
}
class KisJPEGConverter : public QObject
{
Q_OBJECT
public:
KisJPEGConverter(KisDocument *doc, bool batchMode = false);
~KisJPEGConverter() override;
public:
- KisImageBuilder_Result buildImage(QIODevice *io);
- KisImageBuilder_Result buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData);
+ KisImportExportErrorCode buildImage(QIODevice *io);
+ KisImportExportErrorCode buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData);
/** Retrieve the constructed image
*/
KisImageSP image();
public Q_SLOTS:
virtual void cancel();
private:
- KisImageBuilder_Result decode(QIODevice *io);
+ KisImportExportErrorCode decode(QIODevice *io);
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif
diff --git a/plugins/impex/jpeg/kis_jpeg_export.cc b/plugins/impex/jpeg/kis_jpeg_export.cc
index eb2a7526cd..6bd8b31c72 100644
--- a/plugins/impex/jpeg/kis_jpeg_export.cc
+++ b/plugins/impex/jpeg/kis_jpeg_export.cc
@@ -1,296 +1,291 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_jpeg_export.h"
#include <QCheckBox>
#include <QSlider>
#include <QColor>
#include <QString>
#include <QStringList>
#include <QApplication>
#include <QFileInfo>
#include <QScopedPointer>
#include <kpluginfactory.h>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <KoColorSpaceConstants.h>
#include <KoColorSpaceRegistry.h>
#include <KisImportExportManager.h>
#include <kis_slider_spin_box.h>
#include <KisDocument.h>
#include <KoDocumentInfo.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <kis_properties_configuration.h>
#include <kis_config.h>
#include <kis_meta_data_store.h>
#include <kis_meta_data_entry.h>
#include <kis_meta_data_value.h>
#include <kis_meta_data_schema.h>
#include <kis_meta_data_schema_registry.h>
#include <kis_meta_data_filter_registry_model.h>
#include <kis_exif_info_visitor.h>
#include <generator/kis_generator_layer.h>
#include <KisExportCheckRegistry.h>
#include "kis_jpeg_converter.h"
class KisExternalLayer;
K_PLUGIN_FACTORY_WITH_JSON(KisJPEGExportFactory, "krita_jpeg_export.json", registerPlugin<KisJPEGExport>();)
KisJPEGExport::KisJPEGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisJPEGExport::~KisJPEGExport()
{
}
-KisImportExportFilter::ConversionStatus KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
KisImageSP image = document->savingImage();
Q_CHECK_PTR(image);
// An extra option to pass to the config widget to set the state correctly, this isn't saved
const KoColorSpace* cs = image->projection()->colorSpace();
bool sRGB = cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive);
configuration->setProperty("is_sRGB", sRGB);
KisJPEGOptions options;
options.progressive = configuration->getBool("progressive", false);
options.quality = configuration->getInt("quality", 80);
options.forceSRGB = configuration->getBool("forceSRGB", false);
options.saveProfile = configuration->getBool("saveProfile", true);
options.optimize = configuration->getBool("optimize", true);
options.smooth = configuration->getInt("smoothing", 0);
options.baseLineJPEG = configuration->getBool("baseline", true);
options.subsampling = configuration->getInt("subsampling", 0);
options.exif = configuration->getBool("exif", true);
options.iptc = configuration->getBool("iptc", true);
options.xmp = configuration->getBool("xmp", true);
KoColor c(KoColorSpaceRegistry::instance()->rgb8());
c.fromQColor(Qt::white);
options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor();
KisMetaData::FilterRegistryModel m;
m.setEnabledFilters(configuration->getString("filters").split(","));
options.filters = m.enabledFilters();
options.storeAuthor = configuration->getBool("storeAuthor", false);
options.storeDocumentMetaData = configuration->getBool("storeMetaData", false);
KisPaintDeviceSP pd = new KisPaintDevice(*image->projection());
KisJPEGConverter kpc(document, batchMode());
KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd);
KisExifInfoVisitor exivInfoVisitor;
exivInfoVisitor.visit(image->rootLayer().data());
QScopedPointer<KisMetaData::Store> metaDataStore;
if (exivInfoVisitor.metaDataCount() == 1) {
metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo()));
}
else {
metaDataStore.reset(new KisMetaData::Store());
}
//add extra meta-data here
const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri);
Q_ASSERT(dcSchema);
if (options.storeDocumentMetaData) {
QString title = document->documentInfo()->aboutInfo("title");
if (!title.isEmpty()) {
if (metaDataStore->containsEntry("title")) {
metaDataStore->removeEntry("title");
}
metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "title", KisMetaData::Value(QVariant(title))));
}
QString description = document->documentInfo()->aboutInfo("subject");
if (description.isEmpty()) {
description = document->documentInfo()->aboutInfo("abstract");
}
if (!description.isEmpty()) {
QString keywords = document->documentInfo()->aboutInfo("keyword");
if (!keywords.isEmpty()) {
description = description + " keywords: " + keywords;
}
if (metaDataStore->containsEntry("description")) {
metaDataStore->removeEntry("description");
}
metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "description", KisMetaData::Value(QVariant(description))));
}
QString license = document->documentInfo()->aboutInfo("license");
if (!license.isEmpty()) {
if (metaDataStore->containsEntry("rights")) {
metaDataStore->removeEntry("rights");
}
metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "rights", KisMetaData::Value(QVariant(license))));
}
QString date = document->documentInfo()->aboutInfo("date");
if (!date.isEmpty() && !metaDataStore->containsEntry("rights")) {
metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "date", KisMetaData::Value(QVariant(date))));
}
}
if (options.storeAuthor) {
QString author = document->documentInfo()->authorInfo("creator");
if (!author.isEmpty()) {
if (!document->documentInfo()->authorContactInfo().isEmpty()) {
QString contact = document->documentInfo()->authorContactInfo().at(0);
if (!contact.isEmpty()) {
author = author+"("+contact+")";
}
}
if (metaDataStore->containsEntry("creator")) {
metaDataStore->removeEntry("creator");
}
metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author))));
}
}
- KisImageBuilder_Result res = kpc.buildFile(io, l, options, metaDataStore.data());
-
- if (res == KisImageBuilder_RESULT_OK) {
- return KisImportExportFilter::OK;
- }
- dbgFile << " Result =" << res;
- return KisImportExportFilter::InternalError;
+ KisImportExportErrorCode res = kpc.buildFile(io, l, options, metaDataStore.data());
+ return res;
}
KisPropertiesConfigurationSP KisJPEGExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("progressive", false);
cfg->setProperty("quality", 80);
cfg->setProperty("forceSRGB", false);
cfg->setProperty("saveProfile", true);
cfg->setProperty("optimize", true);
cfg->setProperty("smoothing", 0);
cfg->setProperty("baseline", true);
cfg->setProperty("subsampling", 0);
cfg->setProperty("exif", true);
cfg->setProperty("iptc", true);
cfg->setProperty("xmp", true);
cfg->setProperty("storeAuthor", false);
cfg->setProperty("storeMetaData", false);
KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8());
fill_color = KoColor();
fill_color.fromQColor(Qt::white);
QVariant v;
v.setValue(fill_color);
cfg->setProperty("transparencyFillcolor", v);
cfg->setProperty("filters", "");
return cfg;
}
KisConfigWidget *KisJPEGExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
return new KisWdgOptionsJPEG(parent);
}
void KisJPEGExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("ExifCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(CMYKAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "JPEG");
}
KisWdgOptionsJPEG::KisWdgOptionsJPEG(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
metaDataFilters->setModel(&m_filterRegistryModel);
qualityLevel->setRange(0, 100, 0);
qualityLevel->setSuffix("%");
smoothLevel->setRange(0, 100, 0);
smoothLevel->setSuffix("%");
}
void KisWdgOptionsJPEG::setConfiguration(const KisPropertiesConfigurationSP cfg)
{
progressive->setChecked(cfg->getBool("progressive", false));
qualityLevel->setValue(cfg->getInt("quality", 80));
optimize->setChecked(cfg->getBool("optimize", true));
smoothLevel->setValue(cfg->getInt("smoothing", 0));
baseLineJPEG->setChecked(cfg->getBool("baseline", true));
subsampling->setCurrentIndex(cfg->getInt("subsampling", 0));
exif->setChecked(cfg->getBool("exif", true));
iptc->setChecked(cfg->getBool("iptc", true));
xmp->setChecked(cfg->getBool("xmp", true));
chkForceSRGB->setVisible(cfg->getBool("is_sRGB"));
chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false));
chkSaveProfile->setChecked(cfg->getBool("saveProfile", true));
KoColor background(KoColorSpaceRegistry::instance()->rgb8());
background.fromQColor(Qt::white);
bnTransparencyFillColor->setDefaultColor(background);
bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background));
chkAuthor->setChecked(cfg->getBool("storeAuthor", false));
chkMetaData->setChecked(cfg->getBool("storeMetaData", false));
m_filterRegistryModel.setEnabledFilters(cfg->getString("filters").split(','));
}
KisPropertiesConfigurationSP KisWdgOptionsJPEG::configuration() const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
QVariant transparencyFillcolor;
transparencyFillcolor.setValue(bnTransparencyFillColor->color());
cfg->setProperty("progressive", progressive->isChecked());
cfg->setProperty("quality", (int)qualityLevel->value());
cfg->setProperty("forceSRGB", chkForceSRGB->isChecked());
cfg->setProperty("saveProfile", chkSaveProfile->isChecked());
cfg->setProperty("optimize", optimize->isChecked());
cfg->setProperty("smoothing", (int)smoothLevel->value());
cfg->setProperty("baseline", baseLineJPEG->isChecked());
cfg->setProperty("subsampling", subsampling->currentIndex());
cfg->setProperty("exif", exif->isChecked());
cfg->setProperty("iptc", iptc->isChecked());
cfg->setProperty("xmp", xmp->isChecked());
cfg->setProperty("transparencyFillcolor", transparencyFillcolor);
cfg->setProperty("storeAuthor", chkAuthor->isChecked());
cfg->setProperty("storeMetaData", chkMetaData->isChecked());
QString enabledFilters;
Q_FOREACH (const KisMetaData::Filter* filter, m_filterRegistryModel.enabledFilters()) {
enabledFilters = enabledFilters + filter->id() + ',';
}
cfg->setProperty("filters", enabledFilters);
return cfg;
}
#include <kis_jpeg_export.moc>
diff --git a/plugins/impex/jpeg/kis_jpeg_export.h b/plugins/impex/jpeg/kis_jpeg_export.h
index 624f9cea68..65ca4a44e9 100644
--- a/plugins/impex/jpeg/kis_jpeg_export.h
+++ b/plugins/impex/jpeg/kis_jpeg_export.h
@@ -1,58 +1,58 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* 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_JPEG_EXPORT_H_
#define _KIS_JPEG_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
#include "ui_kis_wdg_options_jpeg.h"
#include <kis_meta_data_store.h>
#include <kis_meta_data_filter_registry_model.h>
class KisWdgOptionsJPEG : public KisConfigWidget, public Ui::WdgOptionsJPEG
{
Q_OBJECT
public:
KisWdgOptionsJPEG(QWidget *parent);
void setConfiguration(const KisPropertiesConfigurationSP cfg) override;
KisPropertiesConfigurationSP configuration() const override;
private:
KisMetaData::FilterRegistryModel m_filterRegistryModel;
};
class KisJPEGExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisJPEGExport(QObject *parent, const QVariantList &);
~KisJPEGExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/jpeg/kis_jpeg_import.cc b/plugins/impex/jpeg/kis_jpeg_import.cc
index 4a80e3dc53..911c3c453d 100644
--- a/plugins/impex/jpeg/kis_jpeg_import.cc
+++ b/plugins/impex/jpeg/kis_jpeg_import.cc
@@ -1,80 +1,54 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_jpeg_import.h"
#include <QFileInfo>
#include <kpluginfactory.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <KisViewManager.h>
#include <KisImportExportManager.h>
#include "kis_jpeg_converter.h"
K_PLUGIN_FACTORY_WITH_JSON(JPEGImportFactory, "krita_jpeg_import.json", registerPlugin<KisJPEGImport>();)
KisJPEGImport::KisJPEGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisJPEGImport::~KisJPEGImport()
{
}
-KisImportExportFilter::ConversionStatus KisJPEGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisJPEGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
-
KisJPEGConverter ib(document, batchMode());
-
- switch (ib.buildImage(io)) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE:
- return KisImportExportFilter::NotImplemented;
- break;
- case KisImageBuilder_RESULT_INVALID_ARG:
- return KisImportExportFilter::BadMimeType;
- break;
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- return KisImportExportFilter::FileNotFound;
- break;
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- return KisImportExportFilter::ParsingError;
- break;
- case KisImageBuilder_RESULT_FAILURE:
- return KisImportExportFilter::InternalError;
- break;
- case KisImageBuilder_RESULT_OK:
+ KisImportExportErrorCode result = ib.buildImage(io);
+ if (result.isOk()) {
document->setCurrentImage(ib.image());
- return KisImportExportFilter::OK;
- default:
- break;
-
}
-
- return KisImportExportFilter::InternalError;
-
+ return result;
}
#include <kis_jpeg_import.moc>
diff --git a/plugins/impex/jpeg/kis_jpeg_import.h b/plugins/impex/jpeg/kis_jpeg_import.h
index 598237b859..63555c740b 100644
--- a/plugins/impex/jpeg/kis_jpeg_import.h
+++ b/plugins/impex/jpeg/kis_jpeg_import.h
@@ -1,36 +1,36 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* 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_JPEG_IMPORT_H_
#define _KIS_JPEG_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisJPEGImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisJPEGImport(QObject *parent, const QVariantList &);
~KisJPEGImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt b/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/jpeg/tests/data/readonlyFile.txt b/plugins/impex/jpeg/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/jpeg/tests/data/writeonlyFile.txt b/plugins/impex/jpeg/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
index 59da037645..7d84ef5cdc 100644
--- a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
+++ b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
@@ -1,55 +1,74 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_jpeg_test.h"
#include <QTest>
#include <QCoreApplication>
#include "kisexiv2/kis_exiv2.h"
#include "filestest.h"
#include "jpeglib.h"
#include <sdk/tests/kistest.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
#ifndef JPEG_LIB_VERSION
#error "JPEG_LIB_VERSION not set. libjpeg should set it."
#endif
+const QString JpegMimetype = "image/jpeg";
+
void KisJpegTest::testFiles()
{
KisExiv2::initialize();
/**
* Different versions of JPEG library may produce a bit different
* result, so just compare in a weak way, i.e, only the size for real
*/
const int fuzziness = 1;
const int maxNumFailingPixels = 2592 * 1952; // All pixels can be different...
+ const bool showDebug = false; // No need to write down all pixels that are different since all of them can be.
if (JPEG_LIB_VERSION == 80){
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels);
+ TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels, showDebug);
}else {
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels);
+ TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels, showDebug);
}
+}
+
+void KisJpegTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), JpegMimetype);
+}
+
+void KisJpegTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), JpegMimetype);
+}
+
+
+void KisJpegTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), JpegMimetype);
}
KISTEST_MAIN(KisJpegTest)
diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/jpeg/tests/kis_jpeg_test.h
index 5c19b0cda6..dfc815be3c 100644
--- a/plugins/impex/jpeg/tests/kis_jpeg_test.h
+++ b/plugins/impex/jpeg/tests/kis_jpeg_test.h
@@ -1,31 +1,34 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_JPEG_TEST_H_
#define _KIS_JPEG_TEST_H_
#include <QtTest>
class KisJpegTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp
index 5bce748908..807bfbe6de 100644
--- a/plugins/impex/kra/kra_converter.cpp
+++ b/plugins/impex/kra/kra_converter.cpp
@@ -1,372 +1,377 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kra_converter.h"
#include <QApplication>
#include <QFileInfo>
#include <QScopedPointer>
#include <QUrl>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoColorSpaceRegistry.h>
#include <KoDocumentInfo.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KritaVersionWrapper.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_png_converter.h>
#include <KisDocument.h>
static const char CURRENT_DTD_VERSION[] = "2.0";
KraConverter::KraConverter(KisDocument *doc)
: m_doc(doc)
, m_image(doc->savingImage())
{
}
KraConverter::~KraConverter()
{
delete m_store;
delete m_kraSaver;
delete m_kraLoader;
}
-KisImageBuilder_Result KraConverter::buildImage(QIODevice *io)
+KisImportExportErrorCode KraConverter::buildImage(QIODevice *io)
{
m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip);
if (m_store->bad()) {
m_doc->setErrorMessage(i18n("Not a valid Krita file"));
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
bool success;
{
if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml)
KoXmlDocument doc;
- bool ok = oldLoadAndParse(m_store, "root", doc);
- if (ok)
- ok = loadXML(doc, m_store);
- if (!ok) {
- return KisImageBuilder_RESULT_FAILURE;
+ KisImportExportErrorCode res = oldLoadAndParse(m_store, "root", doc);
+ if (res.isOk())
+ res = loadXML(doc, m_store);
+ if (!res.isOk()) {
+ return res;
}
} else {
errUI << "ERROR: No maindoc.xml" << endl;
- m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'."));
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
if (m_store->hasFile("documentinfo.xml")) {
KoXmlDocument doc;
- if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) {
+ if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) {
m_doc->documentInfo()->load(doc);
}
}
success = completeLoading(m_store);
}
- return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE;
+ return success ? ImportExportCodes::OK : ImportExportCodes::Failure;
}
KisImageSP KraConverter::image()
{
return m_image;
}
vKisNodeSP KraConverter::activeNodes()
{
return m_activeNodes;
}
QList<KisPaintingAssistantSP> KraConverter::assistants()
{
return m_assistants;
}
-KisImageBuilder_Result KraConverter::buildFile(QIODevice *io, const QString &filename)
+KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename)
{
m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip);
if (m_store->bad()) {
m_doc->setErrorMessage(i18n("Could not create the file for saving"));
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::CannotCreateFile;
}
- bool result = false;
m_kraSaver = new KisKraSaver(m_doc, filename);
- result = saveRootDocuments(m_store);
+ KisImportExportErrorCode resultCode = saveRootDocuments(m_store);
- if (!result) {
- return KisImageBuilder_RESULT_FAILURE;
+ if (!resultCode.isOk()) {
+ return resultCode;
}
+ bool result;
+
result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true);
if (!result) {
qWarning() << "saving key frames failed";
}
result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving());
if (!result) {
qWarning() << "saving binary data failed";
}
result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile());
if (!result) {
qWarning() << "saving palettes data failed";
}
if (!m_store->finalize()) {
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::Failure;
}
if (!m_kraSaver->errorMessages().isEmpty()) {
m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n"));
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::Failure;
}
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
-bool KraConverter::saveRootDocuments(KoStore *store)
+KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store)
{
dbgFile << "Saving root";
if (store->open("root")) {
KoStoreDevice dev(store);
if (!saveToStream(&dev) || !store->close()) {
dbgUI << "saveToStream failed";
- return false;
+ return ImportExportCodes::NoAccessToWrite;
}
} else {
m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml")));
- return false;
+ return ImportExportCodes::ErrorWhileWriting;
}
- bool success = false;
+
if (store->open("documentinfo.xml")) {
QDomDocument doc = KisDocument::createDomDocument("document-info"
/*DTD name*/, "document-info" /*tag name*/, "1.1");
doc = m_doc->documentInfo()->save(doc);
KoStoreDevice dev(store);
QByteArray s = doc.toByteArray(); // this is already Utf8!
- success = dev.write(s.data(), s.size());
+ bool success = dev.write(s.data(), s.size());
+ if (!success) {
+ return ImportExportCodes::ErrorWhileWriting;
+ }
store->close();
+ } else {
+ return ImportExportCodes::Failure;
}
if (store->open("preview.png")) {
// ### TODO: missing error checking (The partition could be full!)
- savePreview(store);
+ KisImportExportErrorCode result = savePreview(store);
(void)store->close();
+ if (!result.isOk()) {
+ return result;
+ }
+ } else {
+ return ImportExportCodes::Failure;
}
-
dbgUI << "Saving done of url:" << m_doc->url().toLocalFile();
- // Success
- return success;
+ return ImportExportCodes::OK;
}
bool KraConverter::saveToStream(QIODevice *dev)
{
QDomDocument doc = createDomDocument();
// Save to buffer
QByteArray s = doc.toByteArray(); // utf8 already
dev->open(QIODevice::WriteOnly);
int nwritten = dev->write(s.data(), s.size());
if (nwritten != (int)s.size()) {
warnUI << "wrote " << nwritten << "- expected" << s.size();
}
return nwritten == (int)s.size();
}
QDomDocument KraConverter::createDomDocument()
{
QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION);
QDomElement root = doc.documentElement();
root.setAttribute("editor", "Krita");
root.setAttribute("syntaxVersion", "2");
root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false));
root.appendChild(m_kraSaver->saveXML(doc, m_image));
if (!m_kraSaver->errorMessages().isEmpty()) {
m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n"));
}
return doc;
}
-bool KraConverter::savePreview(KoStore *store)
+KisImportExportErrorCode KraConverter::savePreview(KoStore *store)
{
QPixmap pix = m_doc->generatePreview(QSize(256, 256));
QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly));
if (preview.size() == QSize(0,0)) {
QSize newSize = m_doc->savingImage()->bounds().size();
newSize.scale(QSize(256, 256), Qt::KeepAspectRatio);
preview = QImage(newSize, QImage::Format_ARGB32);
preview.fill(QColor(0, 0, 0, 0));
}
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly)) {
- return false;
+ return ImportExportCodes::NoAccessToWrite;
}
bool ret = preview.save(&io, "PNG");
io.close();
- return ret;
+ return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting;
}
-bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc)
+KisImportExportErrorCode KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc)
{
//dbgUI <<"Trying to open" << filename;
if (!store->open(filename)) {
warnUI << "Entry " << filename << " not found!";
- m_doc->setErrorMessage(i18n("Could not find %1", filename));
- return false;
+ return ImportExportCodes::FileNotExist;
}
// Error variables for QDomDocument::setContent
QString errorMsg;
int errorLine, errorColumn;
bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn);
store->close();
if (!ok) {
errUI << "Parsing error in " << filename << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
- m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4"
- , filename , errorLine, errorColumn ,
- QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0)));
- return false;
+ return ImportExportCodes::FileFormatIncorrect;
}
dbgUI << "File" << filename << " loaded and parsed";
- return true;
+ return ImportExportCodes::OK;
}
-bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store)
+KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store)
{
Q_UNUSED(store);
KoXmlElement root;
KoXmlNode node;
if (doc.doctype().name() != "DOC") {
- m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted"));
- return false;
+ errUI << "The format is not supported or the file is corrupted";
+ return ImportExportCodes::FileFormatIncorrect;
}
root = doc.documentElement();
int syntaxVersion = root.attribute("syntaxVersion", "3").toInt();
if (syntaxVersion > 2) {
- m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion));
- return false;
+ errUI << "The file is too new for this version of Krita: " + syntaxVersion;
+ return ImportExportCodes::FormatFeaturesUnsupported;
}
if (!root.hasChildNodes()) {
- m_doc->setErrorMessage(i18n("The file has no layers."));
- return false;
+ errUI << "The file has no layers.";
+ return ImportExportCodes::FileFormatIncorrect;
}
m_kraLoader = new KisKraLoader(m_doc, syntaxVersion);
// reset the old image before loading the next one
m_doc->setCurrentImage(0, false);
for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) {
if (node.isElement()) {
if (node.nodeName() == "IMAGE") {
KoXmlElement elem = node.toElement();
if (!(m_image = m_kraLoader->loadXML(elem))) {
+
if (m_kraLoader->errorMessages().isEmpty()) {
- m_doc->setErrorMessage(i18n("Unknown error."));
+ errUI << "Unknown error while opening the .kra file.";
}
else {
- m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n"));
+ errUI << m_kraLoader->errorMessages().join("\n");
}
- return false;
+ return ImportExportCodes::Failure;
}
// HACK ALERT!
m_doc->hackPreliminarySetImage(m_image);
- return true;
+ return ImportExportCodes::OK;
}
else {
if (m_kraLoader->errorMessages().isEmpty()) {
m_doc->setErrorMessage(i18n("The file does not contain an image."));
}
- return false;
+ return ImportExportCodes::FileFormatIncorrect;
}
}
}
- return false;
+ return ImportExportCodes::Failure;
}
bool KraConverter::completeLoading(KoStore* store)
{
if (!m_image) {
if (m_kraLoader->errorMessages().isEmpty()) {
m_doc->setErrorMessage(i18n("Unknown error."));
}
else {
m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n"));
}
return false;
}
m_image->blockUpdates();
QString layerPathName = m_kraLoader->imageName();
if (!m_store->hasDirectory(layerPathName)) {
// We might be hitting an encoding problem. Get the only folder in the toplevel
Q_FOREACH (const QString &entry, m_store->directoryList()) {
if (entry.contains("/layers/")) {
layerPathName = entry.split("/layers/").first();
m_store->setSubstitution(m_kraLoader->imageName(), layerPathName);
break;
}
}
}
m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true);
m_kraLoader->loadPalettes(store, m_doc);
m_image->unblockUpdates();
if (!m_kraLoader->warningMessages().isEmpty()) {
// warnings do not interrupt loading process, so we do not return here
m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n"));
}
m_activeNodes = m_kraLoader->selectedNodes();
m_assistants = m_kraLoader->assistants();
return true;
}
void KraConverter::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/kra/kra_converter.h b/plugins/impex/kra/kra_converter.h
index ff5b50a331..46a634e9d7 100644
--- a/plugins/impex/kra/kra_converter.h
+++ b/plugins/impex/kra/kra_converter.h
@@ -1,80 +1,80 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* 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 _KRA_CONVERTER_H_
#define _KRA_CONVERTER_H_
#include <stdio.h>
#include <QObject>
#include <QDomDocument>
#include <KoStore.h>
#include <kis_png_converter.h>
#include <kis_types.h>
#include <kis_kra_saver.h>
#include <kis_kra_loader.h>
class KisDocument;
class KraConverter : public QObject
{
Q_OBJECT
public:
KraConverter(KisDocument *doc);
~KraConverter() override;
- KisImageBuilder_Result buildImage(QIODevice *io);
- KisImageBuilder_Result buildFile(QIODevice *io, const QString &filename);
+ KisImportExportErrorCode buildImage(QIODevice *io);
+ KisImportExportErrorCode buildFile(QIODevice *io, const QString &filename);
/**
* Retrieve the constructed image
*/
KisImageSP image();
vKisNodeSP activeNodes();
QList<KisPaintingAssistantSP> assistants();
public Q_SLOTS:
virtual void cancel();
private:
- bool saveRootDocuments(KoStore *store);
+ KisImportExportErrorCode saveRootDocuments(KoStore *store);
bool saveToStream(QIODevice *dev);
QDomDocument createDomDocument();
- bool savePreview(KoStore *store);
- bool oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc);
- bool loadXML(const KoXmlDocument &doc, KoStore *store);
+ KisImportExportErrorCode savePreview(KoStore *store);
+ KisImportExportErrorCode oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc);
+ KisImportExportErrorCode loadXML(const KoXmlDocument &doc, KoStore *store);
bool completeLoading(KoStore *store);
KisDocument *m_doc {0};
KisImageSP m_image;
vKisNodeSP m_activeNodes;
QList<KisPaintingAssistantSP> m_assistants;
bool m_stop {false};
KoStore *m_store {0};
KisKraSaver *m_kraSaver {0};
KisKraLoader *m_kraLoader {0};
};
#endif
diff --git a/plugins/impex/kra/kra_export.cpp b/plugins/impex/kra/kra_export.cpp
index 69a1a6984b..5db4f6535d 100644
--- a/plugins/impex/kra/kra_export.cpp
+++ b/plugins/impex/kra/kra_export.cpp
@@ -1,83 +1,93 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kra_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QApplication>
#include <KisImportExportManager.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpace.h>
#include <KisExportCheckRegistry.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_shape_layer.h>
#include <KoProperties.h>
#include "kra_converter.h"
class KisExternalLayer;
K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_kra_export.json", registerPlugin<KraExport>();)
KraExport::KraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KraExport::~KraExport()
{
}
-KisImportExportFilter::ConversionStatus KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
KisImageSP image = document->savingImage();
- KIS_ASSERT_RECOVER_RETURN_VALUE(image, CreationError);
+ KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError);
KraConverter kraConverter(document);
- KisImageBuilder_Result res = kraConverter.buildFile(io, filename());
-
- if (res == KisImageBuilder_RESULT_OK) {
- dbgFile << "KraExport::convert success !";
- return KisImportExportFilter::OK;
- }
+ KisImportExportErrorCode res = kraConverter.buildFile(io, filename());
dbgFile << "KraExport::convert result =" << res;
- return KisImportExportFilter::InternalError;
+ return res;
}
void KraExport::initializeCapabilities()
{
// Kra supports everything, by definition
KisExportCheckFactory *factory = 0;
Q_FOREACH(const QString &id, KisExportCheckRegistry::instance()->keys()) {
factory = KisExportCheckRegistry::instance()->get(id);
addCapability(factory->create(KisExportCheckBase::SUPPORTED));
}
}
+QString KraExport::verify(const QString &fileName) const
+{
+ QString error = KisImportExportFilter::verify(fileName);
+ if (error.isEmpty()) {
+ return KisImportExportFilter::verifyZiPBasedFiles(fileName,
+ QStringList()
+ << "mimetype"
+ << "documentinfo.xml"
+ << "maindoc.xml"
+ << "preview.png");
+ }
+ return error;
+}
+
+
#include <kra_export.moc>
diff --git a/plugins/impex/kra/kra_export.h b/plugins/impex/kra/kra_export.h
index 2dc08f2cb7..77e5a7823f 100644
--- a/plugins/impex/kra/kra_export.h
+++ b/plugins/impex/kra/kra_export.h
@@ -1,39 +1,39 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* 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 _KRA_EXPORT_H_
#define _KRA_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KraExport : public KisImportExportFilter
{
Q_OBJECT
public:
KraExport(QObject *parent, const QVariantList &);
~KraExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
-
+ QString verify(const QString &fileName) const override;
};
#endif
diff --git a/plugins/impex/kra/kra_import.cpp b/plugins/impex/kra/kra_import.cpp
index bbc6bcaff2..207e225107 100644
--- a/plugins/impex/kra/kra_import.cpp
+++ b/plugins/impex/kra/kra_import.cpp
@@ -1,77 +1,57 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kra_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KisDocument.h>
#include <kis_image.h>
#include "kra_converter.h"
K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_kra_import.json", registerPlugin<KraImport>();)
KraImport::KraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KraImport::~KraImport()
{
}
-KisImportExportFilter::ConversionStatus KraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
KraConverter kraConverter(document);
- switch (kraConverter.buildImage(io)) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- return KisImportExportFilter::NotImplemented;
- break;
- case KisImageBuilder_RESULT_INVALID_ARG:
- return KisImportExportFilter::BadMimeType;
- break;
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- return KisImportExportFilter::FileNotFound;
- break;
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- return KisImportExportFilter::ParsingError;
- break;
- case KisImageBuilder_RESULT_FAILURE:
- return KisImportExportFilter::InvalidFormat;
- break;
- case KisImageBuilder_RESULT_OK:
+ KisImportExportErrorCode result = kraConverter.buildImage(io);
+ if (result.isOk()) {
document->setCurrentImage(kraConverter.image());
if (kraConverter.activeNodes().size() > 0) {
document->setPreActivatedNode(kraConverter.activeNodes()[0]);
}
if (kraConverter.assistants().size() > 0) {
document->setAssistants(kraConverter.assistants());
}
- return KisImportExportFilter::OK;
- default:
- break;
}
- return KisImportExportFilter::InternalError;
+ return result;
}
#include <kra_import.moc>
diff --git a/plugins/impex/kra/kra_import.h b/plugins/impex/kra/kra_import.h
index f5eb04a2db..e921cdd060 100644
--- a/plugins/impex/kra/kra_import.h
+++ b/plugins/impex/kra/kra_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* 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 KRA_IMPORT_H_
#define KRA_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KraImport : public KisImportExportFilter
{
Q_OBJECT
public:
KraImport(QObject *parent, const QVariantList &);
~KraImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/libkra/tests/data/incorrectFormatFile.txt b/plugins/impex/libkra/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/libkra/tests/data/readonlyFile.txt b/plugins/impex/libkra/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/libkra/tests/data/writeonlyFile.txt b/plugins/impex/libkra/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp
index 10350d829c..500295dcd7 100644
--- a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp
+++ b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp
@@ -1,173 +1,192 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_kra_loader_test.h"
#include <QTest>
#include <KisDocument.h>
#include <KoDocumentInfo.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include <KoColor.h>
#include "kis_image.h"
#include "testutil.h"
#include "KisPart.h"
#include <filter/kis_filter_registry.h>
#include <generator/kis_generator_registry.h>
#include "kis_image_animation_interface.h"
#include "kis_keyframe_channel.h"
#include "kis_time_range.h"
+#include <filestest.h>
+
#include <sdk/tests/kistest.h>
+
+const QString KraMimetype = "application/x-krita";
+
void KisKraLoaderTest::initTestCase()
{
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
}
void KisKraLoaderTest::testLoading()
{
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra");
KisImageSP image = doc->image();
image->lock();
QCOMPARE(image->nlayers(), 12);
QCOMPARE(doc->documentInfo()->aboutInfo("title"), QString("test image for loading"));
QCOMPARE(image->height(), 753);
QCOMPARE(image->width(), 1000);
QCOMPARE(image->colorSpace()->id(), KoColorSpaceRegistry::instance()->rgb8()->id());
KisNodeSP node = image->root()->firstChild();
QVERIFY(node);
QCOMPARE(node->name(), QString("Background"));
QVERIFY(node->inherits("KisPaintLayer"));
node = node->nextSibling();
QVERIFY(node);
QCOMPARE(node->name(), QString("Group 1"));
QVERIFY(node->inherits("KisGroupLayer"));
QCOMPARE((int) node->childCount(), 2);
}
void testObligeSingleChildImpl(bool transpDefaultPixel)
{
QString id = !transpDefaultPixel ?
"single_layer_no_channel_flags_nontransp_def_pixel.kra" :
"single_layer_no_channel_flags_transp_def_pixel.kra";
QString fileName = TestUtil::fetchDataFileLazy(id);
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
const bool result = doc->loadNativeFormat(fileName);
QVERIFY(result);
KisImageSP image = doc->image();
QVERIFY(image);
QCOMPARE(image->nlayers(), 2);
KisNodeSP root = image->root();
KisNodeSP child = root->firstChild();
QVERIFY(child);
QCOMPARE(root->original(), root->projection());
if (transpDefaultPixel) {
QCOMPARE(root->original(), child->projection());
} else {
QVERIFY(root->original() != child->projection());
}
}
void KisKraLoaderTest::testObligeSingleChild()
{
testObligeSingleChildImpl(true);
}
void KisKraLoaderTest::testObligeSingleChildNonTranspPixel()
{
testObligeSingleChildImpl(false);
}
void KisKraLoaderTest::testLoadAnimated()
{
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test_animation.kra");
KisImageSP image = doc->image();
KisNodeSP node1 = image->root()->firstChild();
KisNodeSP node2 = node1->nextSibling();
QVERIFY(node1->inherits("KisPaintLayer"));
QVERIFY(node2->inherits("KisPaintLayer"));
KisPaintLayerSP layer1 = qobject_cast<KisPaintLayer*>(node1.data());
KisPaintLayerSP layer2 = qobject_cast<KisPaintLayer*>(node2.data());
QVERIFY(layer1->isAnimated());
QVERIFY(!layer2->isAnimated());
KisKeyframeChannel *channel1 = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id());
QVERIFY(channel1);
QCOMPARE(channel1->keyframeCount(), 3);
QCOMPARE(image->animationInterface()->framerate(), 17);
QCOMPARE(image->animationInterface()->fullClipRange(), KisTimeRange::fromTime(15, 45));
QCOMPARE(image->animationInterface()->currentTime(), 19);
KisPaintDeviceSP dev = layer1->paintDevice();
const KoColorSpace *cs = dev->colorSpace();
KoColor transparent(Qt::transparent, cs);
KoColor white(Qt::white, cs);
KoColor red(Qt::red, cs);
image->animationInterface()->switchCurrentTimeAsync(0);
image->waitForDone();
QCOMPARE(dev->exactBounds(), QRect(506, 378, 198, 198));
QCOMPARE(dev->x(), -26);
QCOMPARE(dev->y(), -128);
QCOMPARE(dev->defaultPixel(), transparent);
image->animationInterface()->switchCurrentTimeAsync(20);
image->waitForDone();
QCOMPARE(dev->nonDefaultPixelArea(), QRect(615, 416, 129, 129));
QCOMPARE(dev->x(), 502);
QCOMPARE(dev->y(), 224);
QCOMPARE(dev->defaultPixel(), white);
image->animationInterface()->switchCurrentTimeAsync(30);
image->waitForDone();
QCOMPARE(dev->nonDefaultPixelArea(), QRect(729, 452, 45, 44));
QCOMPARE(dev->x(), 645);
QCOMPARE(dev->y(), -10);
QCOMPARE(dev->defaultPixel(), red);
}
+
+void KisKraLoaderTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), KraMimetype);
+}
+
+
+void KisKraLoaderTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), KraMimetype);
+}
+
+
+
KISTEST_MAIN(KisKraLoaderTest)
diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.h b/plugins/impex/libkra/tests/kis_kra_loader_test.h
index 88da2d5448..99a1caf0a4 100644
--- a/plugins/impex/libkra/tests/kis_kra_loader_test.h
+++ b/plugins/impex/libkra/tests/kis_kra_loader_test.h
@@ -1,37 +1,42 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_KRA_LOADER_TEST_H
#define KIS_KRA_LOADER_TEST_H
#include <QtTest>
class KisKraLoaderTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testLoading();
void testObligeSingleChild();
void testObligeSingleChildNonTranspPixel();
void testLoadAnimated();
+
+ void testImportFromWriteonly();
+ void testImportIncorrectFormat();
+
+
};
#endif
diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp
index bd7fbfb1ee..e9afb6f14c 100644
--- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp
+++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp
@@ -1,543 +1,552 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_kra_saver_test.h"
#include <QTest>
#include <QBitArray>
#include <KisDocument.h>
#include <KoDocumentInfo.h>
#include <KoShapeContainer.h>
#include <KoPathShape.h>
#include "filter/kis_filter_registry.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter.h"
#include "kis_image.h"
#include "kis_pixel_selection.h"
#include "kis_group_layer.h"
#include "kis_paint_layer.h"
#include "kis_clone_layer.h"
#include "kis_adjustment_layer.h"
#include "kis_shape_layer.h"
#include "kis_filter_mask.h"
#include "kis_transparency_mask.h"
#include "kis_selection_mask.h"
#include "kis_selection.h"
#include "kis_fill_painter.h"
#include "kis_shape_selection.h"
#include "util.h"
#include "testutil.h"
#include "kis_keyframe_channel.h"
#include "kis_image_animation_interface.h"
#include "kis_layer_properties_icons.h"
#include "kis_transform_mask_params_interface.h"
#include <generator/kis_generator_registry.h>
#include <KoResourcePaths.h>
#include <sdk/tests/kistest.h>
+#include <filestest.h>
+
+const QString KraMimetype = "application/x-krita";
void KisKraSaverTest::initTestCase()
{
KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns");
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
}
void KisKraSaverTest::testCrashyShapeLayer()
{
/**
* KisShapeLayer used to call setImage from its destructor and
* therefore causing an infinite recursion (when at least one transparency
* mask was preset. This testcase just checks that.
*/
//QScopedPointer<KisDocument> doc(createCompleteDocument(true));
//Q_UNUSED(doc);
}
void KisKraSaverTest::testRoundTrip()
{
KisDocument* doc = createCompleteDocument();
KoColor bgColor(Qt::red, doc->image()->colorSpace());
doc->image()->setDefaultProjectionColor(bgColor);
doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType());
QStringList list;
KisCountVisitor cv1(list, KoProperties());
doc->image()->rootLayer()->accept(cv1);
KisDocument *doc2 = KisPart::instance()->createDocument();
bool result = doc2->loadNativeFormat("roundtriptest.kra");
QVERIFY(result);
KisCountVisitor cv2(list, KoProperties());
doc2->image()->rootLayer()->accept(cv2);
QCOMPARE(cv1.count(), cv2.count());
// check whether the BG color is saved correctly
QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor);
// test round trip of a transform mask
KisNode* tnode =
TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data();
QVERIFY(tnode);
KisTransformMask *tmask = dynamic_cast<KisTransformMask*>(tnode);
QVERIFY(tmask);
KisDumbTransformMaskParams *params = dynamic_cast<KisDumbTransformMaskParams*>(tmask->transformParams().data());
QVERIFY(params);
QTransform t = params->testingGetTransform();
QCOMPARE(t, createTestingTransform());
delete doc2;
delete doc;
}
void KisKraSaverTest::testSaveEmpty()
{
KisDocument* doc = createEmptyDocument();
doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType());
QStringList list;
KisCountVisitor cv1(list, KoProperties());
doc->image()->rootLayer()->accept(cv1);
KisDocument *doc2 = KisPart::instance()->createDocument();
doc2->loadNativeFormat("emptytest.kra");
KisCountVisitor cv2(list, KoProperties());
doc2->image()->rootLayer()->accept(cv2);
QCOMPARE(cv1.count(), cv2.count());
delete doc2;
delete doc;
}
#include <generator/kis_generator.h>
void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config)
{
TestUtil::ReferenceImageChecker chk(testName, "fill_layer");
chk.setFuzzy(2);
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
// mask parent should be destructed before the document!
QRect refRect(0,0,512,512);
TestUtil::MaskParent p(refRect);
doc->setCurrentImage(p.image);
doc->documentInfo()->setAboutInfo("title", p.image->objectName());
KisSelectionSP selection;
KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection);
p.image->addNode(glayer, p.image->root(), KisNodeSP());
glayer->setDirty();
p.image->waitForDone();
chk.checkImage(p.image, "00_initial_layer_update");
doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType());
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_fill_layer_test.kra");
doc2->image()->waitForDone();
chk.checkImage(doc2->image(), "01_fill_layer_round_trip");
QVERIFY(chk.testPassed());
}
void KisKraSaverTest::testRoundTripFillLayerColor()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color");
Q_ASSERT(generator);
// warning: we pass null paint device to the default constructed value
KisFilterConfigurationSP config = generator->factoryConfiguration();
Q_ASSERT(config);
QVariant v;
v.setValue(KoColor(Qt::red, cs));
config->setProperty("color", v);
testRoundTripFillLayerImpl("fill_layer_color", config);
}
void KisKraSaverTest::testRoundTripFillLayerPattern()
{
KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern");
QVERIFY(generator);
// warning: we pass null paint device to the default constructed value
KisFilterConfigurationSP config = generator->factoryConfiguration();
QVERIFY(config);
QVariant v;
v.setValue(QString("11_drawed_furry.png"));
config->setProperty("pattern", v);
testRoundTripFillLayerImpl("fill_layer_pattern", config);
}
#include "kis_psd_layer_style.h"
void KisKraSaverTest::testRoundTripLayerStyles()
{
TestUtil::ReferenceImageChecker chk("kra_saver_test", "layer_styles");
QRect imageRect(0,0,512,512);
// the document should be created before the image!
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image");
KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8);
KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8);
KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8);
image->addNode(layer1);
image->addNode(layer2);
image->addNode(layer3);
doc->setCurrentImage(image);
doc->documentInfo()->setAboutInfo("title", image->objectName());
layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs));
layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs));
layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs));
KisPSDLayerStyleSP style(new KisPSDLayerStyle());
style->dropShadow()->setEffectEnabled(true);
style->dropShadow()->setAngle(-90);
style->dropShadow()->setUseGlobalLight(false);
layer1->setLayerStyle(style->clone());
style->dropShadow()->setAngle(180);
style->dropShadow()->setUseGlobalLight(true);
layer2->setLayerStyle(style->clone());
style->dropShadow()->setAngle(90);
style->dropShadow()->setUseGlobalLight(false);
layer3->setLayerStyle(style->clone());
image->initialRefreshGraph();
chk.checkImage(image, "00_initial_layers");
doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType());
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_layer_styles.kra");
doc2->image()->waitForDone();
chk.checkImage(doc2->image(), "00_initial_layers");
QVERIFY(chk.testPassed());
}
void KisKraSaverTest::testRoundTripAnimation()
{
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
QRect imageRect(0,0,512,512);
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image");
KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8);
image->addNode(layer1);
layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs));
layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs));
KUndo2Command parentCommand;
layer1->enableAnimation();
KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
QVERIFY(rasterChannel);
rasterChannel->addKeyframe(10, &parentCommand);
image->animationInterface()->switchCurrentTimeAsync(10);
image->waitForDone();
layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs));
layer1->paintDevice()->moveTo(25, 15);
layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs));
rasterChannel->addKeyframe(20, &parentCommand);
image->animationInterface()->switchCurrentTimeAsync(20);
image->waitForDone();
layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs));
layer1->paintDevice()->moveTo(100, 50);
layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs));
QVERIFY(!layer1->useInTimeline());
layer1->setUseInTimeline(true);
doc->setCurrentImage(image);
doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType());
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_animation.kra");
KisImageSP image2 = doc2->image();
KisNodeSP node = image2->root()->firstChild();
QVERIFY(node->inherits("KisPaintLayer"));
KisPaintLayerSP layer2 = qobject_cast<KisPaintLayer*>(node.data());
cs = layer2->paintDevice()->colorSpace();
QCOMPARE(image2->animationInterface()->currentTime(), 20);
KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id());
QVERIFY(channel);
QCOMPARE(channel->keyframeCount(), 3);
image2->animationInterface()->switchCurrentTimeAsync(0);
image2->waitForDone();
QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128));
QCOMPARE(layer2->paintDevice()->x(), 0);
QCOMPARE(layer2->paintDevice()->y(), 0);
QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs));
image2->animationInterface()->switchCurrentTimeAsync(10);
image2->waitForDone();
QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64));
QCOMPARE(layer2->paintDevice()->x(), 25);
QCOMPARE(layer2->paintDevice()->y(), 15);
QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs));
image2->animationInterface()->switchCurrentTimeAsync(20);
image2->waitForDone();
QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64));
QCOMPARE(layer2->paintDevice()->x(), 100);
QCOMPARE(layer2->paintDevice()->y(), 50);
QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs));
QVERIFY(layer2->useInTimeline());
}
#include "lazybrush/kis_lazy_fill_tools.h"
void KisKraSaverTest::testRoundTripColorizeMask()
{
QRect imageRect(0,0,512,512);
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16();
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image");
doc->setCurrentImage(image);
KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS);
image->addNode(layer1);
KisColorizeMaskSP mask = new KisColorizeMask();
image->addNode(mask, layer1);
mask->initializeCompositeOp();
delete mask->setColorSpace(layer1->colorSpace());
{
KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace()));
mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace()));
// KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd");
}
{
KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace()));
mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace()));
// KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd");
}
{
KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace()));
mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true);
// KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd");
}
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image);
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image);
doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType());
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_colorize.kra");
KisImageSP image2 = doc2->image();
KisNodeSP node = image2->root()->firstChild()->firstChild();
KisColorizeMaskSP mask2 = dynamic_cast<KisColorizeMask*>(node.data());
QVERIFY(mask2);
QCOMPARE(mask2->compositeOpId(), mask->compositeOpId());
QCOMPARE(mask2->colorSpace(), mask->colorSpace());
QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false);
QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false);
QList<KisLazyFillTools::KeyStroke> strokes = mask->fetchKeyStrokesDirect();
qDebug() << ppVar(strokes.size());
QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20));
QCOMPARE(strokes[0].isTransparent, false);
QCOMPARE(strokes[0].color.colorSpace(), weirdCS);
QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20));
QCOMPARE(strokes[1].isTransparent, false);
QCOMPARE(strokes[1].color.colorSpace(), weirdCS);
QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10));
QCOMPARE(strokes[2].isTransparent, true);
QCOMPARE(strokes[2].color.colorSpace(), weirdCS);
}
#include <KoColorBackground.h>
void KisKraSaverTest::testRoundTripShapeLayer()
{
TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_layer");
QRect refRect(0,0,512,512);
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
TestUtil::MaskParent p(refRect);
const qreal resolution = 144.0 / 72.0;
p.image->setResolution(resolution, resolution);
doc->setCurrentImage(p.image);
doc->documentInfo()->setAboutInfo("title", p.image->objectName());
KoPathShape* path = new KoPathShape();
path->setShapeId(KoPathShapeId);
path->moveTo(QPointF(10, 10));
path->lineTo(QPointF( 10, 110));
path->lineTo(QPointF(110, 110));
path->lineTo(QPointF(110, 10));
path->close();
path->normalize();
path->setBackground(toQShared(new KoColorBackground(Qt::red)));
path->setName("my_precious_shape");
KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75);
shapeLayer->addShape(path);
p.image->addNode(shapeLayer);
shapeLayer->setDirty();
qApp->processEvents();
p.image->waitForDone();
chk.checkImage(p.image, "00_initial_layer_update");
doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType());
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_shapelayer_test.kra");
qApp->processEvents();
doc2->image()->waitForDone();
QCOMPARE(doc2->image()->xRes(), resolution);
QCOMPARE(doc2->image()->yRes(), resolution);
chk.checkImage(doc2->image(), "01_shape_layer_round_trip");
QVERIFY(chk.testPassed());
}
void KisKraSaverTest::testRoundTripShapeSelection()
{
TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_selection");
QRect refRect(0,0,512,512);
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
TestUtil::MaskParent p(refRect);
doc->setCurrentImage(p.image);
const qreal resolution = 144.0 / 72.0;
p.image->setResolution(resolution, resolution);
doc->setCurrentImage(p.image);
doc->documentInfo()->setAboutInfo("title", p.image->objectName());
p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace()));
KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds());
KisShapeSelection *shapeSelection = new KisShapeSelection(doc->shapeController(), p.image, selection);
selection->setShapeSelection(shapeSelection);
KoPathShape* path = new KoPathShape();
path->setShapeId(KoPathShapeId);
path->moveTo(QPointF(10, 10));
path->lineTo(QPointF( 10, 110));
path->lineTo(QPointF(110, 110));
path->lineTo(QPointF(110, 10));
path->close();
path->normalize();
path->setBackground(toQShared(new KoColorBackground(Qt::red)));
path->setName("my_precious_shape");
shapeSelection->addShape(path);
KisTransparencyMaskSP tmask = new KisTransparencyMask();
tmask->setSelection(selection);
p.image->addNode(tmask, p.layer);
tmask->setDirty(p.image->bounds());
qApp->processEvents();
p.image->waitForDone();
chk.checkImage(p.image, "00_initial_shape_selection");
doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType());
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_shapeselection_test.kra");
qApp->processEvents();
doc2->image()->waitForDone();
QCOMPARE(doc2->image()->xRes(), resolution);
QCOMPARE(doc2->image()->yRes(), resolution);
chk.checkImage(doc2->image(), "00_initial_shape_selection");
KisNodeSP node = doc2->image()->root()->firstChild()->firstChild();
KisTransparencyMask *newMask = dynamic_cast<KisTransparencyMask*>(node.data());
QVERIFY(newMask);
QVERIFY(newMask->selection()->hasShapeSelection());
QVERIFY(chk.testPassed());
}
+
+void KisKraSaverTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), KraMimetype);
+}
+
KISTEST_MAIN(KisKraSaverTest)
diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.h b/plugins/impex/libkra/tests/kis_kra_saver_test.h
index 04ff99c8d5..4a6c1310bb 100644
--- a/plugins/impex/libkra/tests/kis_kra_saver_test.h
+++ b/plugins/impex/libkra/tests/kis_kra_saver_test.h
@@ -1,51 +1,53 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_KRA_SAVER_TEST_H
#define KIS_KRA_SAVER_TEST_H
#include <QtTest>
class KisKraSaverTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testCrashyShapeLayer();
// XXX: Also test roundtripping of metadata
void testRoundTrip();
void testSaveEmpty();
void testRoundTripFillLayerColor();
void testRoundTripFillLayerPattern();
void testRoundTripLayerStyles();
void testRoundTripAnimation();
void testRoundTripColorizeMask();
void testRoundTripShapeLayer();
void testRoundTripShapeSelection();
+ void testExportToReadonly();
+
};
#endif
diff --git a/plugins/impex/ora/CMakeLists.txt b/plugins/impex/ora/CMakeLists.txt
index 6cedf854e4..a476f422b4 100644
--- a/plugins/impex/ora/CMakeLists.txt
+++ b/plugins/impex/ora/CMakeLists.txt
@@ -1,32 +1,34 @@
+add_subdirectory(tests)
+
set(libkritaconverter_LIB_SRCS
ora_converter.cpp
kis_open_raster_load_context.cpp
kis_open_raster_save_context.cpp
kis_open_raster_stack_load_visitor.cpp
kis_open_raster_stack_save_visitor.cpp
)
set(kritaoraimport_SOURCES
ora_import.cc
${libkritaconverter_LIB_SRCS}
)
add_library(kritaoraimport MODULE ${kritaoraimport_SOURCES})
target_link_libraries(kritaoraimport kritaui )
install(TARGETS kritaoraimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
set(kritaoraexport_SOURCES
ora_export.cc
${libkritaconverter_LIB_SRCS}
)
add_library(kritaoraexport MODULE ${kritaoraexport_SOURCES})
target_link_libraries(kritaoraexport kritaui kritaimpex)
install(TARGETS kritaoraexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_ora.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/ora/kis_open_raster_load_context.cpp b/plugins/impex/ora/kis_open_raster_load_context.cpp
index 7a073b3426..4b06ac9d48 100644
--- a/plugins/impex/ora/kis_open_raster_load_context.cpp
+++ b/plugins/impex/ora/kis_open_raster_load_context.cpp
@@ -1,62 +1,63 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_open_raster_load_context.h"
#include <QDomDocument>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include "kis_png_converter.h"
KisOpenRasterLoadContext::KisOpenRasterLoadContext(KoStore* _store)
: m_store(_store)
{
}
KisImageSP KisOpenRasterLoadContext::loadDeviceData(const QString & filename)
{
if (m_store->open(filename)) {
KoStoreDevice io(m_store);
if (!io.open(QIODevice::ReadOnly)) {
dbgFile << "Could not open for reading:" << filename;
return 0;
}
KisPNGConverter pngConv(0);
pngConv.buildImage(&io);
io.close();
m_store->close();
return pngConv.image();
}
return 0;
}
QDomDocument KisOpenRasterLoadContext::loadStack()
{
m_store->open("stack.xml");
KoStoreDevice io(m_store);
QDomDocument doc;
doc.setContent(&io, false);
io.close();
m_store->close();
return doc;
}
diff --git a/plugins/impex/ora/kis_open_raster_save_context.cpp b/plugins/impex/ora/kis_open_raster_save_context.cpp
index 142a19607b..e9ff568a0d 100644
--- a/plugins/impex/ora/kis_open_raster_save_context.cpp
+++ b/plugins/impex/ora/kis_open_raster_save_context.cpp
@@ -1,60 +1,61 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_open_raster_save_context.h"
#include <QDomDocument>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoColorSpaceRegistry.h>
#include <kundo2command.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <kis_image.h>
#include <kis_meta_data_store.h>
#include "kis_png_converter.h"
KisOpenRasterSaveContext::KisOpenRasterSaveContext(KoStore* store)
: m_id(0)
, m_store(store)
{
}
QString KisOpenRasterSaveContext::saveDeviceData(KisPaintDeviceSP dev, KisMetaData::Store* metaData, const QRect &imageRect, const qreal xRes, const qreal yRes)
{
QString filename = QString("data/layer%1.png").arg(m_id++);
if (KisPNGConverter::saveDeviceToStore(filename, imageRect, xRes, yRes, dev, m_store, metaData)) {
return filename;
}
return "";
}
void KisOpenRasterSaveContext::saveStack(const QDomDocument& doc)
{
if (m_store->open("stack.xml")) {
KoStoreDevice io(m_store);
io.write(doc.toByteArray());
io.close();
m_store->close();
} else {
dbgFile << "Opening of the stack.xml file failed :";
}
}
diff --git a/plugins/impex/ora/ora_converter.cpp b/plugins/impex/ora/ora_converter.cpp
index 044b41fdf1..49667f9058 100644
--- a/plugins/impex/ora/ora_converter.cpp
+++ b/plugins/impex/ora/ora_converter.cpp
@@ -1,116 +1,118 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "ora_converter.h"
#include <QApplication>
#include <QFileInfo>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoColorSpaceRegistry.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_open_raster_stack_load_visitor.h>
#include <kis_open_raster_stack_save_visitor.h>
#include <kis_paint_layer.h>
#include "kis_png_converter.h"
#include "kis_open_raster_load_context.h"
#include "kis_open_raster_save_context.h"
OraConverter::OraConverter(KisDocument *doc)
: m_doc(doc)
, m_stop(false)
{
}
OraConverter::~OraConverter()
{
}
-KisImageBuilder_Result OraConverter::buildImage(QIODevice *io)
+KisImportExportErrorCode OraConverter::buildImage(QIODevice *io)
{
KoStore* store = KoStore::createStore(io, KoStore::Read, "image/openraster", KoStore::Zip);
if (!store) {
delete store;
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::Failure;
}
KisOpenRasterLoadContext olc(store);
KisOpenRasterStackLoadVisitor orslv(m_doc->createUndoStore(), &olc);
orslv.loadImage();
m_image = orslv.image();
m_activeNodes = orslv.activeNodes();
delete store;
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
KisImageSP OraConverter::image()
{
return m_image;
}
vKisNodeSP OraConverter::activeNodes()
{
return m_activeNodes;
}
-KisImageBuilder_Result OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes)
+KisImportExportErrorCode OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes)
{
// Open file for writing
KoStore* store = KoStore::createStore(io, KoStore::Write, "image/openraster", KoStore::Zip);
if (!store) {
- return KisImageBuilder_RESULT_FAILURE;
+ delete store;
+ return ImportExportCodes::Failure;
}
KisOpenRasterSaveContext osc(store);
KisOpenRasterStackSaveVisitor orssv(&osc, activeNodes);
image->rootLayer()->accept(orssv);
if (store->open("Thumbnails/thumbnail.png")) {
QSize previewSize = image->bounds().size();
previewSize.scale(QSize(256,256), Qt::KeepAspectRatio);
QImage preview = image->convertToQImage(previewSize, 0);
KoStoreDevice io(store);
if (io.open(QIODevice::WriteOnly)) {
preview.save(&io, "PNG");
}
io.close();
store->close();
}
KisPaintDeviceSP dev = image->projection();
KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store);
delete store;
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
void OraConverter::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/ora/ora_converter.h b/plugins/impex/ora/ora_converter.h
index 33f8c12d8c..b215e786e0 100644
--- a/plugins/impex/ora/ora_converter.h
+++ b/plugins/impex/ora/ora_converter.h
@@ -1,53 +1,54 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _ORA_CONVERTER_H_
#define _ORA_CONVERTER_H_
#include <stdio.h>
#include <QObject>
#include "kis_png_converter.h"
#include "kis_types.h"
class KisDocument;
class OraConverter : public QObject
{
Q_OBJECT
public:
OraConverter(KisDocument *doc);
~OraConverter() override;
public:
- KisImageBuilder_Result buildImage(QIODevice *io);
- KisImageBuilder_Result buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes);
+ KisImportExportErrorCode buildImage(QIODevice *io);
+ KisImportExportErrorCode buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes);
/**
* Retrieve the constructed image
*/
KisImageSP image();
vKisNodeSP activeNodes();
public Q_SLOTS:
virtual void cancel();
private:
KisImageSP m_image;
KisDocument *m_doc;
vKisNodeSP m_activeNodes;
bool m_stop;
};
#endif
diff --git a/plugins/impex/ora/ora_export.cc b/plugins/impex/ora/ora_export.cc
index ed8ec1328c..8d2d28f714 100644
--- a/plugins/impex/ora/ora_export.cc
+++ b/plugins/impex/ora/ora_export.cc
@@ -1,109 +1,118 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "ora_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QApplication>
-
+#include <KoStore.h>
#include <KisImportExportManager.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpace.h>
#include <KisExportCheckRegistry.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_shape_layer.h>
#include <KoProperties.h>
#include "ora_converter.h"
class KisExternalLayer;
K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_ora_export.json", registerPlugin<OraExport>();)
OraExport::OraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
OraExport::~OraExport()
{
}
bool hasShapeLayerChild(KisNodeSP node)
{
if (!node) return false;
Q_FOREACH (KisNodeSP child, node->childNodes(QStringList(), KoProperties())) {
if (child->inherits("KisShapeLayer")
|| child->inherits("KisGeneratorLayer")
|| child->inherits("KisCloneLayer")) {
return true;
}
else {
if (hasShapeLayerChild(child)) {
return true;
}
}
}
return false;
}
-KisImportExportFilter::ConversionStatus OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
KisImageSP image = document->savingImage();
Q_CHECK_PTR(image);
OraConverter oraConverter(document);
- KisImageBuilder_Result res;
-
- if ((res = oraConverter.buildFile(io, image, {document->preActivatedNode()})) == KisImageBuilder_RESULT_OK) {
- dbgFile << "success !";
- return KisImportExportFilter::OK;
- }
- dbgFile << " Result =" << res;
- return KisImportExportFilter::InternalError;
+ KisImportExportErrorCode res = oraConverter.buildFile(io, image, {document->preActivatedNode()});
+ return res;
}
void OraExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisAdjustmentLayer")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "OpenRaster");
}
+QString OraExport::verify(const QString &fileName) const
+{
+ QString error = KisImportExportFilter::verify(fileName);
+ if (error.isEmpty()) {
+ return KisImportExportFilter::verifyZiPBasedFiles(fileName,
+ QStringList()
+ << "mimetype"
+ << "stack.xml"
+ << "mergedimage.png");
+ }
+
+ return error;
+}
+
#include <ora_export.moc>
diff --git a/plugins/impex/ora/ora_export.h b/plugins/impex/ora/ora_export.h
index a3a614475c..896d8fbf1a 100644
--- a/plugins/impex/ora/ora_export.h
+++ b/plugins/impex/ora/ora_export.h
@@ -1,36 +1,38 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 _ORA_EXPORT_H_
#define _ORA_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class OraExport : public KisImportExportFilter
{
Q_OBJECT
public:
OraExport(QObject *parent, const QVariantList &);
~OraExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
+ QString verify(const QString &fileName) const override;
};
#endif
diff --git a/plugins/impex/ora/ora_import.cc b/plugins/impex/ora/ora_import.cc
index 635b88fe2d..d558a1d3de 100644
--- a/plugins/impex/ora/ora_import.cc
+++ b/plugins/impex/ora/ora_import.cc
@@ -1,76 +1,53 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 "ora_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KisDocument.h>
#include <kis_image.h>
#include "ora_converter.h"
K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_ora_import.json", registerPlugin<OraImport>();)
OraImport::OraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
OraImport::~OraImport()
{
}
-KisImportExportFilter::ConversionStatus OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
OraConverter oraConverter(document);
-
-
- switch (oraConverter.buildImage(io)) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- return KisImportExportFilter::NotImplemented;
- break;
- case KisImageBuilder_RESULT_INVALID_ARG:
- return KisImportExportFilter::BadMimeType;
- break;
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- return KisImportExportFilter::FileNotFound;
- break;
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- return KisImportExportFilter::ParsingError;
- break;
- case KisImageBuilder_RESULT_FAILURE:
- return KisImportExportFilter::InternalError;
- break;
- case KisImageBuilder_RESULT_OK:
+ KisImportExportErrorCode result = oraConverter.buildImage(io);
+ if (result.isOk()) {
document->setCurrentImage(oraConverter.image());
if (oraConverter.activeNodes().size() > 0) {
document->setPreActivatedNode(oraConverter.activeNodes()[0]);
}
- return KisImportExportFilter::OK;
- default:
- break;
}
-
-
- return KisImportExportFilter::InternalError;
+ return result;
}
#include <ora_import.moc>
diff --git a/plugins/impex/ora/ora_import.h b/plugins/impex/ora/ora_import.h
index bc40596deb..56f26ab538 100644
--- a/plugins/impex/ora/ora_import.h
+++ b/plugins/impex/ora/ora_import.h
@@ -1,35 +1,36 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 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 ORA_IMPORT_H_
#define ORA_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class OraImport : public KisImportExportFilter
{
Q_OBJECT
public:
OraImport(QObject *parent, const QVariantList &);
~OraImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/ora/tests/CMakeLists.txt b/plugins/impex/ora/tests/CMakeLists.txt
new file mode 100644
index 0000000000..40a1fd500f
--- /dev/null
+++ b/plugins/impex/ora/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(KisOraTest.cpp
+ TEST_NAME KisOraTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/ora/tests/KisOraTest.cpp
similarity index 64%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/ora/tests/KisOraTest.cpp
index 6ca66688f8..39931639a0 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/ora/tests/KisOraTest.cpp
@@ -1,40 +1,57 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisOraTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString OraMimetype = "image/openraster";
+
+
+
+void KisOraTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), OraMimetype);
+}
+
+
+void KisOraTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), OraMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisOraTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), OraMimetype);
+}
+
+
+
+KISTEST_MAIN(KisOraTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/ora/tests/KisOraTest.h
similarity index 74%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/ora/tests/KisOraTest.h
index 4dad62dfca..a044b20b25 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/ora/tests/KisOraTest.h
@@ -1,32 +1,35 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_ORA_TEST_H_
+#define _KIS_ORA_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisOraTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_ORA_TEST_H_
+
diff --git a/plugins/impex/ora/tests/data/incorrectFormatFile.txt b/plugins/impex/ora/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/ora/tests/data/readonlyFile.txt b/plugins/impex/ora/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/ora/tests/data/writeonlyFile.txt b/plugins/impex/ora/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/pdf/CMakeLists.txt b/plugins/impex/pdf/CMakeLists.txt
index d3ada35792..320da291a4 100644
--- a/plugins/impex/pdf/CMakeLists.txt
+++ b/plugins/impex/pdf/CMakeLists.txt
@@ -1,10 +1,12 @@
+add_subdirectory(tests)
+
set(kritapdfimport_SOURCES kis_pdf_import.cpp kis_pdf_import_widget.cpp )
ki18n_wrap_ui(kritapdfimport_SOURCES pdfimportwidgetbase.ui )
add_library(kritapdfimport MODULE ${kritapdfimport_SOURCES})
target_link_libraries(kritapdfimport kritaui Poppler::Qt5)
install(TARGETS kritapdfimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install(PROGRAMS krita_pdf.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/pdf/kis_pdf_import.cpp b/plugins/impex/pdf/kis_pdf_import.cpp
index 7854cc0627..a30e49cd21 100644
--- a/plugins/impex/pdf/kis_pdf_import.cpp
+++ b/plugins/impex/pdf/kis_pdf_import.cpp
@@ -1,135 +1,136 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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_pdf_import.h"
// poppler's headers
#include <poppler-qt5.h>
// Qt's headers
#include <QFile>
#include <QImage>
#include <QRadioButton>
#include <QApplication>
#include <QFileInfo>
// KDE's headers
#include <kis_debug.h>
#include <kis_paint_device.h>
#include <KoDialog.h>
#include <kpluginfactory.h>
#include <kpassworddialog.h>
// calligra's headers
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoProgressUpdater.h>
#include <KoUpdater.h>
// krita's headers
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_transaction.h>
// plugins's headers
#include "kis_pdf_import_widget.h"
+#include <KisImportExportErrorCode.h>
K_PLUGIN_FACTORY_WITH_JSON(PDFImportFactory, "krita_pdf_import.json",
registerPlugin<KisPDFImport>();)
KisPDFImport::KisPDFImport(QObject *parent, const QVariantList &)
: KisImportExportFilter(parent)
{
}
KisPDFImport::~KisPDFImport()
{
}
-KisPDFImport::ConversionStatus KisPDFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisPDFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
Poppler::Document* pdoc = Poppler::Document::loadFromData(io->readAll());
if (!pdoc) {
dbgFile << "Error when reading the PDF";
- return KisImportExportFilter::StorageCreationError;
+ return ImportExportCodes::ErrorWhileReading;
}
pdoc->setRenderHint(Poppler::Document::Antialiasing, true);
pdoc->setRenderHint(Poppler::Document::TextAntialiasing, true);
while (pdoc->isLocked()) {
KPasswordDialog dlg(0);
dlg.setPrompt(i18n("A password is required to read that pdf"));
dlg.setWindowTitle(i18n("A password is required to read that pdf"));
if (dlg.exec() != QDialog::Accepted) {
dbgFile << "Password canceled";
- return KisImportExportFilter::StorageCreationError;
+ return ImportExportCodes::Cancelled;
} else
pdoc->unlock(dlg.password().toLocal8Bit(), dlg.password().toLocal8Bit());
}
KoDialog* kdb = new KoDialog(0);
kdb->setCaption(i18n("PDF Import Options"));
kdb->setModal(false);
KisPDFImportWidget* wdg = new KisPDFImportWidget(pdoc, kdb);
kdb->setMainWidget(wdg);
QApplication::restoreOverrideCursor();
if (kdb->exec() == QDialog::Rejected) {
delete pdoc;
delete kdb;
- return KisImportExportFilter::StorageCreationError; // FIXME Cancel doesn't exist :(
+ return ImportExportCodes::Cancelled;
}
// Create the krita image
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
int width = wdg->intWidth->value();
int height = wdg->intHeight->value();
KisImageSP image = new KisImage(document->createUndoStore(), width, height, cs, "built image");
image->setResolution(wdg->intResolution->value() / 72.0, wdg->intResolution->value() / 72.0);
// create a layer
QList<int> pages = wdg->pages();
for (QList<int>::const_iterator it = pages.constBegin(); it != pages.constEnd(); ++it) {
KisPaintLayer* layer = new KisPaintLayer(image.data(),
i18n("Page %1", *it + 1),
quint8_MAX);
Poppler::Page* page = pdoc->page(*it);
QImage img = page->renderToImage(wdg->intResolution->value(), wdg->intResolution->value(), 0, 0, width, height);
layer->paintDevice()->convertFromQImage(img, 0, 0, 0);
delete page;
image->addNode(layer, image->rootLayer(), 0);
setProgress(qreal(*it + 1) * 100 / pages.count());
}
document->setCurrentImage(image);
delete pdoc;
delete kdb;
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_pdf_import.moc"
diff --git a/plugins/impex/pdf/kis_pdf_import.h b/plugins/impex/pdf/kis_pdf_import.h
index df387515ff..76593a58b7 100644
--- a/plugins/impex/pdf/kis_pdf_import.h
+++ b/plugins/impex/pdf/kis_pdf_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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_PDF_IMPORT_H
#define KIS_PDF_IMPORT_H
#include <QVariant>
#include <KisImportExportFilter.h>
class KisPDFImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisPDFImport(QObject *parent, const QVariantList &);
virtual ~KisPDFImport();
public:
- virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
+ virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
};
#endif
diff --git a/plugins/impex/pdf/tests/CMakeLists.txt b/plugins/impex/pdf/tests/CMakeLists.txt
new file mode 100644
index 0000000000..19cb770163
--- /dev/null
+++ b/plugins/impex/pdf/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(KisPdfTest.cpp
+ TEST_NAME KisPdfTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/pdf/tests/KisPdfTest.cpp
similarity index 64%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/pdf/tests/KisPdfTest.cpp
index 6ca66688f8..1f0dcec5ab 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/pdf/tests/KisPdfTest.cpp
@@ -1,40 +1,57 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisPdfTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString PdfMimetype = "image/x-gimp-brush";
+
+
+
+void KisPdfTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PdfMimetype);
+}
+
+
+void KisPdfTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PdfMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisPdfTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PdfMimetype);
+}
+
+
+
+KISTEST_MAIN(KisPdfTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/pdf/tests/KisPdfTest.h
similarity index 74%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/pdf/tests/KisPdfTest.h
index 4dad62dfca..a6d0203eed 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/pdf/tests/KisPdfTest.h
@@ -1,32 +1,35 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_PDF_TEST_H_
+#define _KIS_PDF_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisPdfTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_PDF_TEST_H_
+
diff --git a/plugins/impex/pdf/tests/data/incorrectFormatFile.txt b/plugins/impex/pdf/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/pdf/tests/data/readonlyFile.txt b/plugins/impex/pdf/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/pdf/tests/data/writeonlyFile.txt b/plugins/impex/pdf/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/png/kis_png_export.cc b/plugins/impex/png/kis_png_export.cc
index bd937658f2..d813603159 100644
--- a/plugins/impex/png/kis_png_export.cc
+++ b/plugins/impex/png/kis_png_export.cc
@@ -1,252 +1,247 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_png_export.h"
#include <QCheckBox>
#include <QSlider>
#include <QApplication>
#include <kpluginfactory.h>
#include <KoColorSpace.h>
#include <KisImportExportManager.h>
+#include <KisImportExportErrorCode.h>
#include <KoColorProfile.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpaceRegistry.h>
#include <KisExportCheckRegistry.h>
#include <kis_properties_configuration.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_config.h>
#include <kis_meta_data_store.h>
#include <kis_meta_data_filter_registry_model.h>
#include <kis_exif_info_visitor.h>
#include "kis_png_converter.h"
#include <kis_iterator_ng.h>
K_PLUGIN_FACTORY_WITH_JSON(KisPNGExportFactory, "krita_png_export.json", registerPlugin<KisPNGExport>();)
KisPNGExport::KisPNGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisPNGExport::~KisPNGExport()
{
}
-KisImportExportFilter::ConversionStatus KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
KisImageSP image = document->savingImage();
KisPNGOptions options;
options.alpha = configuration->getBool("alpha", true);
options.interlace = configuration->getBool("interlaced", false);
options.compression = configuration->getInt("compression", 3);
options.tryToSaveAsIndexed = configuration->getBool("indexed", false);
KoColor c(KoColorSpaceRegistry::instance()->rgb8());
c.fromQColor(Qt::white);
options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor();
options.saveSRGBProfile = configuration->getBool("saveSRGBProfile", false);
options.forceSRGB = configuration->getBool("forceSRGB", true);
options.storeAuthor = configuration->getBool("storeAuthor", false);
options.storeMetaData = configuration->getBool("storeMetaData", false);
options.saveAsHDR = configuration->getBool("saveAsHDR", false);
vKisAnnotationSP_it beginIt = image->beginAnnotations();
vKisAnnotationSP_it endIt = image->endAnnotations();
KisExifInfoVisitor eIV;
eIV.visit(image->rootLayer().data());
KisMetaData::Store *eI = 0;
if (eIV.metaDataCount() == 1) {
eI = eIV.exifInfo();
}
if (eI) {
KisMetaData::Store* copy = new KisMetaData::Store(*eI);
eI = copy;
}
KisPNGConverter pngConverter(document);
- KisImageBuilder_Result res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI);
-
- if (res == KisImageBuilder_RESULT_OK) {
- delete eI;
- return KisImportExportFilter::OK;
- }
-
+ KisImportExportErrorCode res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI);
delete eI;
dbgFile << " Result =" << res;
- return KisImportExportFilter::InternalError;
+ return res;
}
KisPropertiesConfigurationSP KisPNGExport::defaultConfiguration(const QByteArray &, const QByteArray &) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("alpha", true);
cfg->setProperty("indexed", false);
cfg->setProperty("compression", 3);
cfg->setProperty("interlaced", false);
KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8());
fill_color = KoColor();
fill_color.fromQColor(Qt::white);
QVariant v;
v.setValue(fill_color);
cfg->setProperty("transparencyFillcolor", v);
cfg->setProperty("saveSRGBProfile", false);
cfg->setProperty("forceSRGB", true);
cfg->setProperty("saveAsHDR", false);
cfg->setProperty("storeMetaData", false);
cfg->setProperty("storeAuthor", false);
return cfg;
}
KisConfigWidget *KisPNGExport::createConfigurationWidget(QWidget *parent, const QByteArray &, const QByteArray &) const
{
return new KisWdgOptionsPNG(parent);
}
void KisPNGExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "PNG");
}
KisWdgOptionsPNG::KisWdgOptionsPNG(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
connect(chkSaveAsHDR, SIGNAL(toggled(bool)), this, SLOT(slotUseHDRChanged(bool)));
}
void KisWdgOptionsPNG::setConfiguration(const KisPropertiesConfigurationSP cfg)
{
// the export manager should have prepared some info for us!
KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ImageContainsTransparencyTag));
KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ColorModelIDTag));
KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::sRGBTag));
const bool isThereAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag);
alpha->setChecked(cfg->getBool("alpha", isThereAlpha));
bnTransparencyFillColor->setEnabled(!alpha->isChecked());
if (cfg->getString(KisImportExportFilter::ColorModelIDTag) == RGBAColorModelID.id()) {
tryToSaveAsIndexed->setVisible(true);
if (alpha->isChecked()) {
tryToSaveAsIndexed->setChecked(false);
}
else {
tryToSaveAsIndexed->setChecked(cfg->getBool("indexed", false));
}
}
else {
tryToSaveAsIndexed->setVisible(false);
}
interlacing->setChecked(cfg->getBool("interlaced", false));
compressionLevel->setValue(cfg->getInt("compression", 3));
compressionLevel->setRange(1, 9, 0);
tryToSaveAsIndexed->setVisible(!isThereAlpha);
//const bool sRGB = cfg->getBool(KisImportExportFilter::sRGBTag, false);
//chkSRGB->setEnabled(sRGB);
chkSRGB->setChecked(cfg->getBool("saveSRGBProfile", true));
//chkForceSRGB->setEnabled(!sRGB);
chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false));
chkSaveAsHDR->setChecked(cfg->getBool("saveAsHDR", false));
slotUseHDRChanged(chkSaveAsHDR->isChecked());
chkAuthor->setChecked(cfg->getBool("storeAuthor", false));
chkMetaData->setChecked(cfg->getBool("storeMetaData", false));
KoColor background(KoColorSpaceRegistry::instance()->rgb8());
background.fromQColor(Qt::white);
bnTransparencyFillColor->setDefaultColor(background);
bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background));
}
KisPropertiesConfigurationSP KisWdgOptionsPNG::configuration() const
{
KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration());
bool alpha = this->alpha->isChecked();
bool interlace = interlacing->isChecked();
int compression = (int)compressionLevel->value();
bool saveAsHDR = chkSaveAsHDR->isChecked();
bool tryToSaveAsIndexed = !saveAsHDR && this->tryToSaveAsIndexed->isChecked();
bool saveSRGB = !saveAsHDR && chkSRGB->isChecked();
bool forceSRGB = !saveAsHDR && chkForceSRGB->isChecked();
bool storeAuthor = chkAuthor->isChecked();
bool storeMetaData = chkMetaData->isChecked();
QVariant transparencyFillcolor;
transparencyFillcolor.setValue(bnTransparencyFillColor->color());
cfg->setProperty("alpha", alpha);
cfg->setProperty("indexed", tryToSaveAsIndexed);
cfg->setProperty("compression", compression);
cfg->setProperty("interlaced", interlace);
cfg->setProperty("transparencyFillcolor", transparencyFillcolor);
cfg->setProperty("saveAsHDR", saveAsHDR);
cfg->setProperty("saveSRGBProfile", saveSRGB);
cfg->setProperty("forceSRGB", forceSRGB);
cfg->setProperty("storeAuthor", storeAuthor);
cfg->setProperty("storeMetaData", storeMetaData);
return cfg;
}
void KisWdgOptionsPNG::on_alpha_toggled(bool checked)
{
bnTransparencyFillColor->setEnabled(!checked);
}
void KisWdgOptionsPNG::slotUseHDRChanged(bool value)
{
tryToSaveAsIndexed->setDisabled(value);
chkForceSRGB->setDisabled(value);
chkSRGB->setDisabled(value);
}
#include "kis_png_export.moc"
diff --git a/plugins/impex/png/kis_png_export.h b/plugins/impex/png/kis_png_export.h
index 18c91e2c78..3be3e51478 100644
--- a/plugins/impex/png/kis_png_export.h
+++ b/plugins/impex/png/kis_png_export.h
@@ -1,59 +1,59 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* 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_PNG_EXPORT_H_
#define _KIS_PNG_EXPORT_H_
#include "ui_kis_wdg_options_png.h"
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
class KisWdgOptionsPNG : public KisConfigWidget, public Ui::KisWdgOptionsPNG
{
Q_OBJECT
public:
KisWdgOptionsPNG(QWidget *parent);
void setConfiguration(const KisPropertiesConfigurationSP config) override;
KisPropertiesConfigurationSP configuration() const override;
private Q_SLOTS:
void on_alpha_toggled(bool checked);
void slotUseHDRChanged(bool value);
};
class KisPNGExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisPNGExport(QObject *parent, const QVariantList &);
~KisPNGExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/png/kis_png_import.cc b/plugins/impex/png/kis_png_import.cc
index b45fcad441..29a82cea2d 100644
--- a/plugins/impex/png/kis_png_import.cc
+++ b/plugins/impex/png/kis_png_import.cc
@@ -1,79 +1,56 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_png_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <KisViewManager.h>
#include "kis_png_converter.h"
K_PLUGIN_FACTORY_WITH_JSON(PNGImportFactory, "krita_png_import.json", registerPlugin<KisPNGImport>();)
KisPNGImport::KisPNGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisPNGImport::~KisPNGImport()
{
}
-KisImportExportFilter::ConversionStatus KisPNGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisPNGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
KisPNGConverter ib(document, batchMode());
-
- switch (ib.buildImage(io)) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- return KisImportExportFilter::NotImplemented;
- break;
- case KisImageBuilder_RESULT_INVALID_ARG:
- return KisImportExportFilter::BadMimeType;
- break;
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_EXIST:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- return KisImportExportFilter::FileNotFound;
- break;
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- return KisImportExportFilter::ParsingError;
- break;
- case KisImageBuilder_RESULT_FAILURE:
- return KisImportExportFilter::InternalError;
- break;
- case KisImageBuilder_RESULT_OK:
- document -> setCurrentImage(ib.image());
- return KisImportExportFilter::OK;
- break;
- default:
- break;
+ KisImportExportErrorCode res = ib.buildImage(io);
+ if (res.isOk()){
+ document->setCurrentImage(ib.image());
}
- return KisImportExportFilter::InternalError;
+ return res;
}
#include <kis_png_import.moc>
diff --git a/plugins/impex/png/kis_png_import.h b/plugins/impex/png/kis_png_import.h
index 51d0ac7134..8b43397eb8 100644
--- a/plugins/impex/png/kis_png_import.h
+++ b/plugins/impex/png/kis_png_import.h
@@ -1,36 +1,36 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* 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_PNG_IMPORT_H_
#define _KIS_PNG_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisPNGImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisPNGImport(QObject *parent, const QVariantList &);
~KisPNGImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/png/tests/data/writeonlyFile.txt b/plugins/impex/png/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/png/tests/kis_png_test.cpp b/plugins/impex/png/tests/kis_png_test.cpp
index 583ba20488..2259cb7822 100644
--- a/plugins/impex/png/tests/kis_png_test.cpp
+++ b/plugins/impex/png/tests/kis_png_test.cpp
@@ -1,155 +1,162 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_png_test.h"
#include <QTest>
#include <QCoreApplication>
#include "filestest.h"
#include <sdk/tests/kistest.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 PngMimetype = "image/png";
+
void KisPngTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1);
}
+void KisPngTest::testWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PngMimetype);
+}
+
void roudTripHdrImage(const KoColorSpace *savingColorSpace)
{
qDebug() << "Test saving" << savingColorSpace->id() << savingColorSpace->profile()->name();
const KoColorSpace * scRGBF32 =
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Float32BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p709G10Profile());
KoColor fillColor(scRGBF32);
float *pixelPtr = reinterpret_cast<float*>(fillColor.data());
pixelPtr[0] = 2.7;
pixelPtr[1] = 1.6;
pixelPtr[2] = 0.8;
pixelPtr[3] = 0.9;
{
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
KisImageSP image = new KisImage(0, 3, 3, scRGBF32, "png test");
KisPaintLayerSP paintLayer0 = new KisPaintLayer(image, "paint0", OPACITY_OPAQUE_U8);
paintLayer0->paintDevice()->fill(image->bounds(), fillColor);
image->addNode(paintLayer0, image->root());
// convert image color space before saving
image->convertImageColorSpace(savingColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
image->waitForDone();
KisImportExportManager manager(doc.data());
doc->setFileBatchMode(true);
doc->setCurrentImage(image);
KisPropertiesConfigurationSP exportConfiguration = new KisPropertiesConfiguration();
exportConfiguration->setProperty("saveAsHDR", true);
exportConfiguration->setProperty("saveSRGBProfile", false);
exportConfiguration->setProperty("forceSRGB", false);
doc->exportDocumentSync(QUrl::fromLocalFile("test.png"), "image/png", exportConfiguration);
}
{
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
KisImportExportManager manager(doc.data());
doc->setFileBatchMode(true);
- KisImportExportFilter::ConversionStatus loadingStatus =
+ KisImportExportErrorCode loadingStatus =
manager.importDocument("test.png", QString());
- QCOMPARE(loadingStatus, KisImportExportFilter::OK);
+ QVERIFY(loadingStatus.isOk());
KisImageSP image = doc->image();
image->initialRefreshGraph();
KoColor resultColor;
// qDebug() << ppVar(image->colorSpace()) << image->colorSpace()->profile()->name();
// image->projection()->pixel(1, 1, &resultColor);
// qDebug() << ppVar(resultColor);
image->convertImageColorSpace(scRGBF32, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
image->waitForDone();
image->projection()->pixel(1, 1, &resultColor);
// qDebug() << ppVar(resultColor);
const float tolerance = savingColorSpace->colorDepthId() == Integer8BitsColorDepthID ? 0.02 : 0.01;
bool resultIsValid = true;
float *resultPtr = reinterpret_cast<float*>(resultColor.data());
for (int i = 0; i < 4; i++) {
resultIsValid &= qAbs(resultPtr[i] - pixelPtr[i]) < tolerance;
}
if (!resultIsValid) {
qDebug() << ppVar(fillColor) << ppVar(resultColor);
}
QVERIFY(resultIsValid);
}
}
void KisPngTest::testSaveHDR()
{
QVector<KoID> colorDepthIds;
colorDepthIds << Float16BitsColorDepthID;
colorDepthIds << Float32BitsColorDepthID;
QVector<const KoColorProfile*> profiles;
profiles << KoColorSpaceRegistry::instance()->p709G10Profile();
profiles << KoColorSpaceRegistry::instance()->p2020G10Profile();
profiles << KoColorSpaceRegistry::instance()->p2020PQProfile();
Q_FOREACH(const KoID &depth, colorDepthIds) {
Q_FOREACH(const KoColorProfile *profile, profiles) {
roudTripHdrImage(
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
depth.id(),
profile));
}
}
roudTripHdrImage(
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Integer16BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p2020PQProfile()));
roudTripHdrImage(
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Integer8BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p2020PQProfile()));
}
KISTEST_MAIN(KisPngTest)
diff --git a/plugins/impex/png/tests/kis_png_test.h b/plugins/impex/png/tests/kis_png_test.h
index e312f85756..0c2374b4c6 100644
--- a/plugins/impex/png/tests/kis_png_test.h
+++ b/plugins/impex/png/tests/kis_png_test.h
@@ -1,32 +1,33 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_PNG_TEST_H_
#define _KIS_PNG_TEST_H_
#include <QtTest>
class KisPngTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+ void testWriteonly();
void testSaveHDR();
};
#endif
diff --git a/plugins/impex/ppm/kis_ppm_export.cpp b/plugins/impex/ppm/kis_ppm_export.cpp
index b93eb05a09..e76bccd566 100644
--- a/plugins/impex/ppm/kis_ppm_export.cpp
+++ b/plugins/impex/ppm/kis_ppm_export.cpp
@@ -1,283 +1,317 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_ppm_export.h"
#include <kpluginfactory.h>
#include <KoColorSpace.h>
#include <KoColorSpaceConstants.h>
#include <KisImportExportManager.h>
#include <KisExportCheckRegistry.h>
#include <kis_debug.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_properties_configuration.h>
#include <kis_config.h>
#include <qendian.h>
#include <KoColorSpaceTraits.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include "kis_iterator_ng.h"
#include <QApplication>
K_PLUGIN_FACTORY_WITH_JSON(KisPPMExportFactory, "krita_ppm_export.json", registerPlugin<KisPPMExport>();)
KisPPMExport::KisPPMExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisPPMExport::~KisPPMExport()
{
}
class KisPPMFlow
{
public:
KisPPMFlow() {
}
virtual ~KisPPMFlow() {
}
virtual void writeBool(quint8 v) = 0;
virtual void writeBool(quint16 v) = 0;
virtual void writeNumber(quint8 v) = 0;
virtual void writeNumber(quint16 v) = 0;
virtual void flush() = 0;
private:
};
class KisPPMAsciiFlow : public KisPPMFlow
{
public:
KisPPMAsciiFlow(QIODevice* device) : m_device(device) {
}
~KisPPMAsciiFlow() override {
}
void writeBool(quint8 v) override {
if (v > 127) {
m_device->write("1 ");
} else {
m_device->write("0 ");
}
}
void writeBool(quint16 v) override {
writeBool(quint8(v >> 8));
}
void writeNumber(quint8 v) override {
m_device->write(QByteArray::number(v));
m_device->write(" ");
}
void writeNumber(quint16 v) override {
m_device->write(QByteArray::number(v));
m_device->write(" ");
}
void flush() override {
}
private:
QIODevice* m_device;
};
class KisPPMBinaryFlow : public KisPPMFlow
{
public:
KisPPMBinaryFlow(QIODevice* device) : m_device(device), m_pos(0), m_current(0) {
}
~KisPPMBinaryFlow() override {
}
void writeBool(quint8 v) override {
m_current = m_current << 1;
m_current |= (v > 127);
++m_pos;
if (m_pos >= 8) {
m_current = 0;
m_pos = 0;
flush();
}
}
void writeBool(quint16 v) override {
writeBool(quint8(v >> 8));
}
void writeNumber(quint8 v) override {
m_device->write((char*)&v, 1);
}
void writeNumber(quint16 v) override {
quint16 vo = qToBigEndian(v);
m_device->write((char*)&vo, 2);
}
void flush() override {
m_device->write((char*)&m_current, 1);
}
private:
QIODevice* m_device;
int m_pos;
quint8 m_current;
};
-KisImportExportFilter::ConversionStatus KisPPMExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisPPMExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
bool rgb = (mimeType() == "image/x-portable-pixmap");
bool binary = (configuration->getInt("type") == 0);
bool bitmap = (mimeType() == "image/x-portable-bitmap");
KisImageSP image = document->savingImage();
KisPaintDeviceSP pd = new KisPaintDevice(*image->projection());
// Test color space
if (((rgb && (pd->colorSpace()->id() != "RGBA" && pd->colorSpace()->id() != "RGBA16"))
|| (!rgb && (pd->colorSpace()->id() != "GRAYA" && pd->colorSpace()->id() != "GRAYA16" && pd->colorSpace()->id() != "GRAYAU16")))) {
if (rgb) {
pd->convertTo(KoColorSpaceRegistry::instance()->rgb8(0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
else {
pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
}
bool is16bit = pd->colorSpace()->id() == "RGBA16" || pd->colorSpace()->id() == "GRAYAU16";
// Write the magic
+ QString toWrite = "";
+ int written = 0;
if (rgb) {
- if (binary) io->write("P6");
- else io->write("P3");
+ if (binary) {
+ toWrite = "P6";
+ }
+ else {
+ toWrite = "P3";
+ }
} else if (bitmap) {
- if (binary) io->write("P4");
- else io->write("P1");
+ if (binary) {
+ toWrite = "P4";
+ }
+ else {
+ toWrite = "P1";
+ }
} else {
- if (binary) io->write("P5");
- else io->write("P2");
+ if (binary) {
+ toWrite = "P5";
+ }
+ else {
+ toWrite = "P2";
+ }
}
- io->write("\n");
+ written = io->write(toWrite.toUtf8());
+ if (written != toWrite.toUtf8().length()) {
+ return ImportExportCodes::ErrorWhileWriting;
+ }
+
+ written = io->write("\n");
// Write the header
- io->write(QByteArray::number(image->width()));
- io->write(" ");
- io->write(QByteArray::number(image->height()));
+ QByteArray width = QByteArray::number(image->width());
+ QByteArray height = QByteArray::number(image->height());
+
+ written += io->write(width);
+ written += io->write(" ");
+ written += io->write(height);
+ if (written != QString(" ").length() + QString("\n").length() + width.length() + height.length()) {
+ return ImportExportCodes::ErrorWhileWriting;
+ }
if (!bitmap) {
- if (is16bit) io->write(" 65535\n");
- else io->write(" 255\n");
+ if (is16bit) {
+ toWrite = " 65535\n";
+ }
+ else {
+ toWrite = " 255\n";
+ }
} else {
- io->write("\n");
+ toWrite = "\n";
+ }
+ written = io->write(toWrite.toUtf8());
+ if (written != toWrite.toUtf8().length()) {
+ return ImportExportCodes::ErrorWhileWriting;
}
// Write the data
KisPPMFlow* flow = 0;
if (binary) flow = new KisPPMBinaryFlow(io);
else flow = new KisPPMAsciiFlow(io);
for (int y = 0; y < image->height(); ++y) {
KisHLineIteratorSP it = pd->createHLineIteratorNG(0, y, image->width());
if (is16bit) {
if (rgb) {
do {
flow->writeNumber(KoBgrU16Traits::red(it->rawData()));
flow->writeNumber(KoBgrU16Traits::green(it->rawData()));
flow->writeNumber(KoBgrU16Traits::blue(it->rawData()));
} while (it->nextPixel());
} else if (bitmap) {
do {
flow->writeBool(*reinterpret_cast<quint16*>(it->rawData()));
} while (it->nextPixel());
} else {
do {
flow->writeNumber(*reinterpret_cast<quint16*>(it->rawData()));
} while (it->nextPixel());
}
} else {
if (rgb) {
do {
flow->writeNumber(KoBgrTraits<quint8>::red(it->rawData()));
flow->writeNumber(KoBgrTraits<quint8>::green(it->rawData()));
flow->writeNumber(KoBgrTraits<quint8>::blue(it->rawData()));
} while (it->nextPixel());
} else if (bitmap) {
do {
flow->writeBool(*reinterpret_cast<quint8*>(it->rawData()));
} while (it->nextPixel());
} else {
do {
flow->writeNumber(*reinterpret_cast<quint8*>(it->rawData()));
} while (it->nextPixel());
}
}
}
if (bitmap) {
flow->flush();
}
delete flow;
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
KisPropertiesConfigurationSP KisPPMExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("type", 0);
return cfg;
}
KisConfigWidget *KisPPMExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
return new KisWdgOptionsPPM(parent);
}
void KisWdgOptionsPPM::setConfiguration(const KisPropertiesConfigurationSP cfg)
{
cmbType->setCurrentIndex(cfg->getInt("type", 0));
}
KisPropertiesConfigurationSP KisWdgOptionsPPM::configuration() const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("type", cmbType->currentIndex());
return cfg;
}
void KisPPMExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "PPM");
}
#include "kis_ppm_export.moc"
diff --git a/plugins/impex/ppm/kis_ppm_export.h b/plugins/impex/ppm/kis_ppm_export.h
index 7689305acb..070db83b7a 100644
--- a/plugins/impex/ppm/kis_ppm_export.h
+++ b/plugins/impex/ppm/kis_ppm_export.h
@@ -1,58 +1,59 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_PPM_EXPORT_H_
#define _KIS_PPM_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
#include "ui_kis_wdg_options_ppm.h"
class KisWdgOptionsPPM : public KisConfigWidget, public Ui::WdgOptionsPPM
{
Q_OBJECT
public:
KisWdgOptionsPPM(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
}
void setConfiguration(const KisPropertiesConfigurationSP cfg) override;
KisPropertiesConfigurationSP configuration() const override;
};
class KisPPMExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisPPMExport(QObject *parent, const QVariantList &);
~KisPPMExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/ppm/kis_ppm_import.cpp b/plugins/impex/ppm/kis_ppm_import.cpp
index 32eae9206d..ee31b591d3 100644
--- a/plugins/impex/ppm/kis_ppm_import.cpp
+++ b/plugins/impex/ppm/kis_ppm_import.cpp
@@ -1,314 +1,320 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_ppm_import.h"
#include <ctype.h>
#include <QApplication>
#include <QFile>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpaceRegistry.h>
#include <kis_debug.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <KoColorSpaceTraits.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <KoColorSpace.h>
#include <qendian.h>
#include <KoColorModelStandardIds.h>
#include "kis_iterator_ng.h"
K_PLUGIN_FACTORY_WITH_JSON(PPMImportFactory, "krita_ppm_import.json", registerPlugin<KisPPMImport>();)
KisPPMImport::KisPPMImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisPPMImport::~KisPPMImport()
{
}
int readNumber(QIODevice* device)
{
char c;
int val = 0;
while (true) {
if (!device->getChar(&c)) break; // End of the file
if (isdigit(c)) {
val = 10 * val + c - '0';
} else if (c == '#') {
device->readLine();
break;
} else if (isspace((uchar) c)) {
break;
}
}
return val;
}
class KisPpmFlow
{
public:
KisPpmFlow() {
}
virtual ~KisPpmFlow() {
}
virtual void nextRow() = 0;
virtual bool valid() = 0;
virtual bool nextUint1() = 0;
virtual quint8 nextUint8() = 0;
virtual quint16 nextUint16() = 0;
};
class KisAsciiPpmFlow : public KisPpmFlow
{
public:
KisAsciiPpmFlow(QIODevice* device) : m_device(device) {
}
~KisAsciiPpmFlow() override {
}
void nextRow() override {
}
bool valid() override {
return !m_device->atEnd();
}
bool nextUint1() override {
return readNumber(m_device) == 1;
}
quint8 nextUint8() override {
return readNumber(m_device);
}
quint16 nextUint16() override {
return readNumber(m_device);
}
private:
QIODevice* m_device;
};
class KisBinaryPpmFlow : public KisPpmFlow
{
public:
KisBinaryPpmFlow(QIODevice* device, int lineWidth) : m_pos(0), m_device(device), m_lineWidth(lineWidth) {
}
~KisBinaryPpmFlow() override {
}
void nextRow() override {
m_array = m_device->read(m_lineWidth);
m_ptr = m_array.data();
}
bool valid() override {
return m_array.size() == m_lineWidth;
}
bool nextUint1() override {
if (m_pos == 0) {
m_current = nextUint8();
m_pos = 8;
}
bool v = (m_current & 1) == 1;
--m_pos;
m_current = m_current >> 1;
return v;
}
quint8 nextUint8() override {
quint8 v = *reinterpret_cast<quint8*>(m_ptr);
m_ptr += 1;
return v;
}
quint16 nextUint16() override {
quint16 v = *reinterpret_cast<quint16*>(m_ptr);
m_ptr += 2;
return qFromBigEndian(v);
}
private:
int m_pos;
quint8 m_current;
char* m_ptr;
QIODevice* m_device;
QByteArray m_array;
int m_lineWidth;
};
-KisImportExportFilter::ConversionStatus KisPPMImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisPPMImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
QByteArray array = io->read(2);
- if (array.size() < 2) return KisImportExportFilter::CreationError;
+ if (array.size() < 2) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
// Read the type of the ppm file
enum { Puk, P1, P2, P3, P4, P5, P6 } fileType = Puk; // Puk => unknown
int channels = -1;
bool isAscii = false;
if (array == "P1") {
fileType = P1;
isAscii = true;
channels = 0;
} else if (array == "P2") {
fileType = P2;
channels = 1;
isAscii = true;
} else if (array == "P3") {
fileType = P3;
channels = 3;
isAscii = true;
} else if (array == "P4") {
fileType = P4;
channels = 0;
} else if (array == "P5") { // PGM
fileType = P5;
channels = 1;
} else if (array == "P6") { // PPM
fileType = P6;
channels = 3;
}
Q_ASSERT(channels != -1);
char c; io->getChar(&c);
- if (!isspace(c)) return KisImportExportFilter::CreationError; // Invalid file, it should have a separator now
+ if (!isspace(c)) {
+ return ImportExportCodes::FileFormatIncorrect;; // Invalid file, it should have a separator now
+ }
while (io->peek(1) == "#") {
io->readLine();
}
// Read width
int width = readNumber(io);
int height = readNumber(io);
int maxval = 1;
if (fileType != P1 && fileType != P4) {
maxval = readNumber(io);
}
dbgFile << "Width = " << width << " height = " << height << "maxval = " << maxval;
// Select the colorspace depending on the maximum value
int pixelsize = -1;
const KoColorSpace* colorSpace = 0;
const KoColorProfile *profile = 0;
QString colorSpaceId;
QString bitDepthId;
if (maxval <= 255) {
bitDepthId = Integer8BitsColorDepthID.id();
if (channels == 1 || channels == 0) {
pixelsize = 1;
colorSpaceId = GrayAColorModelID.id();
} else {
pixelsize = 3;
colorSpaceId = RGBAColorModelID.id();
}
} else if (maxval <= 65535) {
bitDepthId = Integer16BitsColorDepthID.id();
if (channels == 1 || channels == 0) {
pixelsize = 2;
colorSpaceId = GrayAColorModelID.id();
} else {
pixelsize = 6;
colorSpaceId = RGBAColorModelID.id();
}
} else {
dbgFile << "Unknown colorspace";
- return KisImportExportFilter::CreationError;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
if (colorSpaceId == RGBAColorModelID.id()) {
profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc");
} else if (colorSpaceId == GrayAColorModelID.id()) {
profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc");
}
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId, bitDepthId, profile);
KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "built image");
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255);
QScopedPointer<KisPpmFlow> ppmFlow;
if (isAscii) {
ppmFlow.reset(new KisAsciiPpmFlow(io));
} else {
ppmFlow.reset(new KisBinaryPpmFlow(io, pixelsize * width));
}
for (int v = 0; v < height; ++v) {
KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, v, width);
ppmFlow->nextRow();
- if (!ppmFlow->valid()) return KisImportExportFilter::CreationError;
+ if (!ppmFlow->valid()) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
if (maxval <= 255) {
if (channels == 3) {
do {
KoBgrTraits<quint8>::setRed(it->rawData(), ppmFlow->nextUint8());
KoBgrTraits<quint8>::setGreen(it->rawData(), ppmFlow->nextUint8());
KoBgrTraits<quint8>::setBlue(it->rawData(), ppmFlow->nextUint8());
colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
} while (it->nextPixel());
} else if (channels == 1) {
do {
*reinterpret_cast<quint8*>(it->rawData()) = ppmFlow->nextUint8();
colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
} while (it->nextPixel());
} else if (channels == 0) {
do {
if (ppmFlow->nextUint1()) {
*reinterpret_cast<quint8*>(it->rawData()) = 255;
} else {
*reinterpret_cast<quint8*>(it->rawData()) = 0;
}
colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
} while (it->nextPixel());
}
} else {
if (channels == 3) {
do {
KoBgrU16Traits::setRed(it->rawData(), ppmFlow->nextUint16());
KoBgrU16Traits::setGreen(it->rawData(), ppmFlow->nextUint16());
KoBgrU16Traits::setBlue(it->rawData(), ppmFlow->nextUint16());
colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
} while (it->nextPixel());
} else if (channels == 1) {
do {
*reinterpret_cast<quint16*>(it->rawData()) = ppmFlow->nextUint16();
colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1);
} while (it->nextPixel());
}
}
}
image->addNode(layer.data(), image->rootLayer().data());
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_ppm_import.moc"
diff --git a/plugins/impex/ppm/kis_ppm_import.h b/plugins/impex/ppm/kis_ppm_import.h
index f3921fece6..07268bff36 100644
--- a/plugins/impex/ppm/kis_ppm_import.h
+++ b/plugins/impex/ppm/kis_ppm_import.h
@@ -1,40 +1,40 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_PPM_IMPORT_H_
#define _KIS_PPM_IMPORT_H_
#include <QVariant>
#include <QIODevice>
#include <KisImportExportFilter.h>
class KisDocument;
class KisPPMImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisPPMImport(QObject *parent, const QVariantList &);
~KisPPMImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/ppm/tests/data/incorrectFormatFile.txt b/plugins/impex/ppm/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/ppm/tests/data/readonlyFile.txt b/plugins/impex/ppm/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/ppm/tests/data/writeonlyFile.txt b/plugins/impex/ppm/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/ppm/tests/kis_ppm_test.cpp
index 6aa3859d0a..fce6ee9d5c 100644
--- a/plugins/impex/ppm/tests/kis_ppm_test.cpp
+++ b/plugins/impex/ppm/tests/kis_ppm_test.cpp
@@ -1,39 +1,61 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_ppm_test.h"
#include <QTest>
#include <QCoreApplication>
#include <sdk/tests/kistest.h>
#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 PPMMimetype = "image/x-portable-pixmap";
+
void KisPPMTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1);
}
+
+void KisPPMTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PPMMimetype);
+}
+
+
+void KisPPMTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PPMMimetype);
+}
+
+
+void KisPPMTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PPMMimetype);
+}
+
+
KISTEST_MAIN(KisPPMTest)
diff --git a/plugins/impex/ppm/tests/kis_ppm_test.h b/plugins/impex/ppm/tests/kis_ppm_test.h
index 36e9d19953..827856e793 100644
--- a/plugins/impex/ppm/tests/kis_ppm_test.h
+++ b/plugins/impex/ppm/tests/kis_ppm_test.h
@@ -1,31 +1,35 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_PPM_TEST_H_
#define _KIS_PPM_TEST_H_
#include <QtTest>
class KisPPMTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/impex/psd/psd_export.cc b/plugins/impex/psd/psd_export.cc
index 05df481795..ca04ebeb8c 100644
--- a/plugins/impex/psd/psd_export.cc
+++ b/plugins/impex/psd/psd_export.cc
@@ -1,102 +1,91 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "psd_export.h"
#include <QCheckBox>
#include <QSlider>
#include <QFileInfo>
#include <QApplication>
#include <kpluginfactory.h>
#include <KisExportCheckRegistry.h>
#include <KisImportExportManager.h>
#include <ImageSizeCheck.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceConstants.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include "psd_saver.h"
class KisExternalLayer;
K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_psd_export.json", registerPlugin<psdExport>();)
psdExport::psdExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
psdExport::~psdExport()
{
}
-KisImportExportFilter::ConversionStatus psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
PSDSaver psdSaver(document);
- KisImageBuilder_Result res;
-
- if ((res = psdSaver.buildFile(io)) == KisImageBuilder_RESULT_OK) {
- dbgFile <<"success !";
- return KisImportExportFilter::OK;
- }
- else if (res == KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE) {
- document->setErrorMessage(i18n("Could not convert this colorspace to something Krita can save."));
- return KisImportExportFilter::WrongFormat;
- }
- dbgFile <<" Result =" << res;
- return KisImportExportFilter::InternalError;
+ return psdSaver.buildFile(io);
}
void psdExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("PSDLayerStyleCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::UNSUPPORTED, i18nc("image conversion warning", "Your image contains one or more layers with a color model that is different from the image.")));
ImageSizeCheckFactory *factory = dynamic_cast<ImageSizeCheckFactory*>(KisExportCheckRegistry::instance()->get("ImageSizeCheck"));
if (factory) {
addCapability(factory->create(30000, 30000, KisExportCheckBase::SUPPORTED));
}
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
// << QPair<KoID, KoID>(RGBAColorModelID, Float16BitsColorDepthID)
// << QPair<KoID, KoID>(RGBAColorModelID, Float32BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(CMYKAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(CMYKAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(LABAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(LABAColorModelID, Integer16BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "PSD");
}
#include <psd_export.moc>
diff --git a/plugins/impex/psd/psd_export.h b/plugins/impex/psd/psd_export.h
index 0d66e19571..55d79b4d70 100644
--- a/plugins/impex/psd/psd_export.h
+++ b/plugins/impex/psd/psd_export.h
@@ -1,35 +1,35 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _PSD_EXPORT_H_
#define _PSD_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class psdExport : public KisImportExportFilter {
Q_OBJECT
public:
psdExport(QObject *parent, const QVariantList &);
~psdExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/psd/psd_import.cc b/plugins/impex/psd/psd_import.cc
index 0d8722f207..a73917e1e6 100644
--- a/plugins/impex/psd/psd_import.cc
+++ b/plugins/impex/psd/psd_import.cc
@@ -1,72 +1,49 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "psd_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KisDocument.h>
#include <kis_image.h>
#include "psd_loader.h"
K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_psd_import.json", registerPlugin<psdImport>();)
psdImport::psdImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
psdImport::~psdImport()
{
}
-KisImportExportFilter::ConversionStatus psdImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode psdImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
-
-
PSDLoader ib(document);
-
- KisImageBuilder_Result result = ib.buildImage(io);
-
- switch (result) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- return KisImportExportFilter::NotImplemented;
- case KisImageBuilder_RESULT_INVALID_ARG:
- return KisImportExportFilter::BadMimeType;
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_EXIST:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- qDebug() << "ib returned KisImageBuilder_RESULT_NOT_LOCAL";
- return KisImportExportFilter::FileNotFound;
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- return KisImportExportFilter::ParsingError;
- case KisImageBuilder_RESULT_FAILURE:
- return KisImportExportFilter::InternalError;
- case KisImageBuilder_RESULT_OK:
- document -> setCurrentImage( ib.image());
- return KisImportExportFilter::OK;
- default:
- return KisImportExportFilter::StorageCreationError;
- //dbgFile << "Result was: " << result;
+ KisImportExportErrorCode result = ib.buildImage(io);
+ if (result.isOk()) {
+ document->setCurrentImage(ib.image());
}
- return KisImportExportFilter::InternalError;
+ return result;
}
#include <psd_import.moc>
diff --git a/plugins/impex/psd/psd_import.h b/plugins/impex/psd/psd_import.h
index a522c7d5a0..606d7563ab 100644
--- a/plugins/impex/psd/psd_import.h
+++ b/plugins/impex/psd/psd_import.h
@@ -1,34 +1,34 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef PSD_IMPORT_H_
#define PSD_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class psdImport : public KisImportExportFilter {
Q_OBJECT
public:
psdImport(QObject *parent, const QVariantList &);
~psdImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp
index 0b99d9d1e6..f68e47a503 100644
--- a/plugins/impex/psd/psd_loader.cpp
+++ b/plugins/impex/psd/psd_loader.cpp
@@ -1,380 +1,380 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "psd_loader.h"
#include <QApplication>
#include <QFileInfo>
#include <QStack>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoColorProfile.h>
#include <KoCompositeOp.h>
#include <KoUnit.h>
#include <kis_annotation.h>
#include <kis_types.h>
#include <kis_paint_layer.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_transparency_mask.h>
#include <kis_asl_layer_style_serializer.h>
#include <kis_psd_layer_style_resource.h>
#include "KisResourceServerProvider.h"
#include "psd.h"
#include "psd_header.h"
#include "psd_colormode_block.h"
#include "psd_utils.h"
#include "psd_resource_section.h"
#include "psd_layer_section.h"
#include "psd_resource_block.h"
#include "psd_image_data.h"
PSDLoader::PSDLoader(KisDocument *doc)
: m_image(0)
, m_doc(doc)
, m_stop(false)
{
}
PSDLoader::~PSDLoader()
{
}
-KisImageBuilder_Result PSDLoader::decode(QIODevice *io)
+KisImportExportErrorCode PSDLoader::decode(QIODevice *io)
{
// open the file
dbgFile << "pos:" << io->pos();
PSDHeader header;
- if (!header.read( io)) {
+ if (!header.read(io)) {
dbgFile << "failed reading header: " << header.error;
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << header;
dbgFile << "Read header. pos:" << io->pos();
PSDColorModeBlock colorModeBlock(header.colormode);
if (!colorModeBlock.read(io)) {
dbgFile << "failed reading colormode block: " << colorModeBlock.error;
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << "Read color mode block. pos:" << io->pos();
PSDImageResourceSection resourceSection;
if (!resourceSection.read(io)) {
dbgFile << "failed image reading resource section: " << resourceSection.error;
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << "Read image resource section. pos:" << io->pos();
PSDLayerMaskSection layerSection(header);
if (!layerSection.read(io)) {
dbgFile << "failed reading layer/mask section: " << layerSection.error;
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos();
// Done reading, except possibly for the image data block, which is only relevant if there
// are no layers.
// Get the right colorspace
QPair<QString, QString> colorSpaceId = psd_colormode_to_colormodelid(header.colormode,
header.channelDepth);
if (colorSpaceId.first.isNull()) {
dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth;
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
// Get the icc profile from the image resource section
const KoColorProfile* profile = 0;
if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) {
ICC_PROFILE_1039 *iccProfileData = dynamic_cast<ICC_PROFILE_1039*>(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource);
if (iccProfileData ) {
profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first,
colorSpaceId.second,
iccProfileData->icc);
dbgFile << "Loaded ICC profile" << profile->name();
delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE);
}
}
// Create the colorspace
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile);
if (!cs) {
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
// Creating the KisImage
QFile *file = dynamic_cast<QFile*>(io);
QString name = file ? file->fileName() : "Imported";
m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name);
Q_CHECK_PTR(m_image);
m_image->lock();
// set the correct resolution
if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) {
RESN_INFO_1005 *resInfo = dynamic_cast<RESN_INFO_1005*>(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource);
if (resInfo) {
// check resolution size is not zero
if (resInfo->hRes * resInfo->vRes > 0)
m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes));
// let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it.
delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO);
}
}
// Preserve all the annotations
Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) {
m_image->addAnnotation(resourceBlock);
}
// Preserve the duotone colormode block for saving back to psd
if (header.colormode == DuoTone) {
KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock",
i18n("Duotone Colormode Block"),
colorModeBlock.data);
m_image->addAnnotation(annotation);
}
// Read the projection into our single layer. Since we only read the projection when
// we have just one layer, we don't need to later on apply the alpha channel of the
// first layer to the projection if the number of layers is negative/
// See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000.
if (layerSection.nLayers == 0) {
dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'";
KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8);
PSDImageData imageData(&header);
imageData.read(io, layer->paintDevice());
m_image->addNode(layer, m_image->rootLayer());
// Only one layer, the background layer, so we're done.
m_image->unlock();
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
// More than one layer, so now construct the Krita image from the info we read.
QStack<KisGroupLayerSP> groupStack;
groupStack.push(m_image->rootLayer());
/**
* PSD has a weird "optimization": if a group layer has only one
* child layer, it omits it's 'psd_bounding_divider' section. So
* fi you ever see an unbalanced layers group in PSD, most
* probably, it is just a single layered group.
*/
KisNodeSP lastAddedLayer;
typedef QPair<QDomDocument, KisLayerSP> LayerStyleMapping;
QVector<LayerStyleMapping> allStylesXml;
// read the channels for the various layers
for(int i = 0; i < layerSection.nLayers; ++i) {
PSDLayerRecord* layerRecord = layerSection.layers.at(i);
dbgFile << "Going to read channels for layer" << i << layerRecord->layerName;
KisLayerSP newLayer;
if (layerRecord->infoBlocks.keys.contains("lsct") &&
layerRecord->infoBlocks.sectionDividerType != psd_other) {
if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) {
KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8);
m_image->addNode(groupLayer, groupStack.top());
groupStack.push(groupLayer);
newLayer = groupLayer;
}
else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder ||
layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) &&
(groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) {
KisGroupLayerSP groupLayer;
if (groupStack.size() <= 1) {
groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8);
m_image->addNode(groupLayer, groupStack.top());
m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP());
} else {
groupLayer = groupStack.pop();
}
const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml;
if (!styleXml.isNull()) {
allStylesXml << LayerStyleMapping(styleXml, groupLayer);
}
groupLayer->setName(layerRecord->layerName);
groupLayer->setVisible(layerRecord->visible);
QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode);
// Krita doesn't support pass-through blend
// mode. Instead it is just a property of a group
// layer, so flip it
if (compositeOp == COMPOSITE_PASS_THROUGH) {
compositeOp = COMPOSITE_OVER;
groupLayer->setPassThroughMode(true);
}
groupLayer->setCompositeOpId(compositeOp);
newLayer = groupLayer;
} else {
/**
* In some files saved by PS CS6 the group layer sections seem
* to be unbalanced. I don't know why it happens because the
* reporter didn't provide us an example file. So here we just
* check if the new layer was created, and if not, skip the
* initialization of masks.
*
* See bug: 357559
*/
warnKrita << "WARNING: Provided PSD has unbalanced group "
<< "layer markers. Some masks and/or layers can "
<< "be lost while loading this file. Please "
<< "report a bug to Krita developers and attach "
<< "this file to the bugreport\n"
<< " " << ppVar(layerRecord->layerName) << "\n"
<< " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n"
<< " " << ppVar(groupStack.size());
continue;
}
}
else {
KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity);
layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey));
const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml;
if (!styleXml.isNull()) {
allStylesXml << LayerStyleMapping(styleXml, layer);
}
if (!layerRecord->readPixelData(io, layer->paintDevice())) {
dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error;
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::FileFormatIncorrect;
}
if (!groupStack.isEmpty()) {
m_image->addNode(layer, groupStack.top());
}
else {
m_image->addNode(layer, m_image->root());
}
layer->setVisible(layerRecord->visible);
newLayer = layer;
}
Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) {
if (channelInfo->channelId < -1) {
KisTransparencyMaskSP mask = new KisTransparencyMask();
mask->setName(i18n("Transparency Mask"));
mask->initSelection(newLayer);
if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) {
dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error;
}
m_image->addNode(mask, newLayer);
}
}
lastAddedLayer = newLayer;
}
const QVector<QDomDocument> &embeddedPatterns =
layerSection.globalInfoSection.embeddedPatterns;
KisAslLayerStyleSerializer serializer;
if (!embeddedPatterns.isEmpty()) {
Q_FOREACH (const QDomDocument &doc, embeddedPatterns) {
serializer.registerPSDPattern(doc);
}
}
QVector<KisPSDLayerStyleSP> allStylesForServer;
if (!allStylesXml.isEmpty()) {
Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) {
serializer.readFromPSDXML(mapping.first);
if (serializer.styles().size() == 1) {
KisPSDLayerStyleSP layerStyle = serializer.styles().first();
KisLayerSP layer = mapping.second;
layerStyle->setName(layer->name());
allStylesForServer << layerStyle;
layer->setLayerStyle(layerStyle->clone());
} else {
warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles());
}
}
}
if (!allStylesForServer.isEmpty()) {
KisPSDLayerStyleCollectionResource *collection =
new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl");
collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName()));
KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid());
collection->setLayerStyles(allStylesForServer);
KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid());
KoResourceServer<KisPSDLayerStyleCollectionResource> *server = KisResourceServerProvider::instance()->layerStyleCollectionServer();
server->addResource(collection, false);
}
m_image->unlock();
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
-KisImageBuilder_Result PSDLoader::buildImage(QIODevice *io)
+KisImportExportErrorCode PSDLoader::buildImage(QIODevice *io)
{
return decode(io);
}
KisImageSP PSDLoader::image()
{
return m_image;
}
void PSDLoader::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/psd/psd_loader.h b/plugins/impex/psd/psd_loader.h
index 0618f803f5..0f096b20e2 100644
--- a/plugins/impex/psd/psd_loader.h
+++ b/plugins/impex/psd/psd_loader.h
@@ -1,59 +1,59 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _PSD_LOADER_H_
#define _PSD_LOADER_H_
#include <stdio.h>
#include <QObject>
#include <QFileInfo>
#include "kis_types.h"
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
class KisDocument;
class PSDLoader : public QObject {
Q_OBJECT
public:
PSDLoader(KisDocument *doc);
~PSDLoader() override;
- KisImageBuilder_Result buildImage(QIODevice *io);
+ KisImportExportErrorCode buildImage(QIODevice *io);
KisImageSP image();
public Q_SLOTS:
virtual void cancel();
private:
- KisImageBuilder_Result decode(QIODevice *io);
+ KisImportExportErrorCode decode(QIODevice *io);
private:
KisImageSP m_image;
KisDocument *m_doc;
bool m_stop;
};
#endif
diff --git a/plugins/impex/psd/psd_saver.cpp b/plugins/impex/psd/psd_saver.cpp
index 3ed8d70b26..8ea25a8e84 100644
--- a/plugins/impex/psd/psd_saver.cpp
+++ b/plugins/impex/psd/psd_saver.cpp
@@ -1,250 +1,252 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "psd_saver.h"
#include <KoColorSpace.h>
#include <KoColorModelStandardIds.h>
#include <KoColorProfile.h>
#include <KoCompositeOp.h>
#include <KoUnit.h>
#include <QFileInfo>
#include <kis_annotation.h>
#include <kis_types.h>
#include <kis_paint_layer.h>
#include "kis_painter.h"
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_debug.h>
#include "psd.h"
#include "psd_header.h"
#include "psd_colormode_block.h"
#include "psd_utils.h"
#include "psd_resource_section.h"
#include "psd_layer_section.h"
#include "psd_resource_block.h"
#include "psd_image_data.h"
+const int MAX_PSD_SIZE = 30000;
+
+
QPair<psd_color_mode, quint16> colormodelid_to_psd_colormode(const QString &colorSpaceId, const QString &colorDepthId)
{
psd_color_mode colorMode = COLORMODE_UNKNOWN;
if (colorSpaceId == RGBAColorModelID.id()) {
colorMode = RGB;
}
else if (colorSpaceId == CMYKAColorModelID.id()) {
colorMode = CMYK;
}
else if (colorSpaceId == GrayAColorModelID.id()) {
colorMode = Grayscale;
}
else if (colorSpaceId == LABAColorModelID.id()) {
colorMode = Lab;
}
quint16 depth = 0;
if (colorDepthId == Integer8BitsColorDepthID.id()) {
depth = 8;
}
else if (colorDepthId == Integer16BitsColorDepthID.id()) {
depth = 16;
}
else if (colorDepthId == Float16BitsColorDepthID.id()) {
depth = 32;
}
else if (colorDepthId == Float32BitsColorDepthID.id()) {
depth = 32;
}
return QPair<psd_color_mode, quint16>(colorMode, depth);
}
PSDSaver::PSDSaver(KisDocument *doc)
: m_image(doc->savingImage())
, m_doc(doc)
, m_stop(false)
{
}
PSDSaver::~PSDSaver()
{
}
KisImageSP PSDSaver::image()
{
return m_image;
}
-KisImageBuilder_Result PSDSaver::buildFile(QIODevice *io)
+KisImportExportErrorCode PSDSaver::buildFile(QIODevice *io)
{
- if (!m_image) {
- return KisImageBuilder_RESULT_EMPTY;
- }
- if (m_image->width() > 30000 || m_image->height() > 30000) {
- return KisImageBuilder_RESULT_FAILURE;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(m_image, ImportExportCodes::InternalError);
+
+ if (m_image->width() > MAX_PSD_SIZE || m_image->height() > MAX_PSD_SIZE) {
+ return ImportExportCodes::Failure;
}
const bool haveLayers = m_image->rootLayer()->childCount() > 1 ||
KisPainter::checkDeviceHasTransparency(
m_image->rootLayer()->firstChild()->projection());
// HEADER
PSDHeader header;
header.signature = "8BPS";
header.version = 1;
header.nChannels = haveLayers ?
m_image->colorSpace()->channelCount() :
m_image->colorSpace()->colorChannelCount();
header.width = m_image->width();
header.height = m_image->height();
QPair<psd_color_mode, quint16> colordef = colormodelid_to_psd_colormode(m_image->colorSpace()->colorModelId().id(),
m_image->colorSpace()->colorDepthId().id());
if (colordef.first == COLORMODE_UNKNOWN || colordef.second == 0 || colordef.second == 32) {
m_image->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
colordef = colormodelid_to_psd_colormode(m_image->colorSpace()->colorModelId().id(), m_image->colorSpace()->colorDepthId().id());
}
header.colormode = colordef.first;
header.channelDepth = colordef.second;
dbgFile << "header" << header << io->pos();
if (!header.write(io)) {
dbgFile << "Failed to write header. Error:" << header.error << io->pos();
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::ErrorWhileWriting;
}
// COLORMODE BlOCK
PSDColorModeBlock colorModeBlock(header.colormode);
// XXX: check for annotations that contain the duotone spec
KisAnnotationSP annotation = m_image->annotation("DuotoneColormodeBlock");
if (annotation) {
colorModeBlock.duotoneSpecification = annotation->annotation();
}
dbgFile << "colormode block" << io->pos();
if (!colorModeBlock.write(io)) {
dbgFile << "Failed to write colormode block. Error:" << colorModeBlock.error << io->pos();
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::ErrorWhileWriting;
}
// IMAGE RESOURCES SECTION
PSDImageResourceSection resourceSection;
vKisAnnotationSP_it it = m_image->beginAnnotations();
vKisAnnotationSP_it endIt = m_image->endAnnotations();
while (it != endIt) {
KisAnnotationSP annotation = (*it);
if (!annotation || annotation->type().isEmpty()) {
dbgFile << "Warning: empty annotation";
it++;
continue;
}
dbgFile << "Annotation:" << annotation->type() << annotation->description();
if (annotation->type().startsWith(QString("PSD Resource Block:"))) { //
PSDResourceBlock *resourceBlock = dynamic_cast<PSDResourceBlock*>(annotation.data());
if (resourceBlock) {
dbgFile << "Adding PSD Resource Block" << resourceBlock->identifier;
resourceSection.resources[(PSDImageResourceSection::PSDResourceID)resourceBlock->identifier] = resourceBlock;
}
}
it++;
}
// Add resolution block
{
RESN_INFO_1005 *resInfo = new RESN_INFO_1005;
resInfo->hRes = INCH_TO_POINT(m_image->xRes());
resInfo->vRes = INCH_TO_POINT(m_image->yRes());
PSDResourceBlock *block = new PSDResourceBlock;
block->identifier = PSDImageResourceSection::RESN_INFO;
block->resource = resInfo;
resourceSection.resources[PSDImageResourceSection::RESN_INFO] = block;
}
// Add icc block
{
ICC_PROFILE_1039 *profileInfo = new ICC_PROFILE_1039;
profileInfo->icc = m_image->profile()->rawData();
PSDResourceBlock *block = new PSDResourceBlock;
block->identifier = PSDImageResourceSection::ICC_PROFILE;
block->resource = profileInfo;
resourceSection.resources[PSDImageResourceSection::ICC_PROFILE] = block;
}
dbgFile << "resource section" << io->pos();
if (!resourceSection.write(io)) {
dbgFile << "Failed to write resource section. Error:" << resourceSection.error << io->pos();
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::ErrorWhileWriting;
}
// LAYER AND MASK DATA
// Only save layers and masks if there is more than one layer
dbgFile << "m_image->rootLayer->childCount" << m_image->rootLayer()->childCount() << io->pos();
if (haveLayers) {
PSDLayerMaskSection layerSection(header);
layerSection.hasTransparency = true;
if (!layerSection.write(io, m_image->rootLayer())) {
dbgFile << "failed to write layer section. Error:" << layerSection.error << io->pos();
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::ErrorWhileWriting;
}
}
else {
// else write a zero length block
dbgFile << "No layers, saving empty layers/mask block" << io->pos();
psdwrite(io, (quint32)0);
}
// IMAGE DATA
dbgFile << "Saving composited image" << io->pos();
PSDImageData imagedata(&header);
if (!imagedata.write(io, m_image->projection(), haveLayers)) {
dbgFile << "Failed to write image data. Error:" << imagedata.error;
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::ErrorWhileWriting;
}
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
void PSDSaver::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/psd/psd_saver.h b/plugins/impex/psd/psd_saver.h
index bf142d5128..f0aaa45c17 100644
--- a/plugins/impex/psd/psd_saver.h
+++ b/plugins/impex/psd/psd_saver.h
@@ -1,57 +1,62 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _PSD_CONVERTER_H_
#define _PSD_CONVERTER_H_
#include <stdio.h>
#include <QObject>
#include <QFileInfo>
#include "kis_types.h"
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
+
+
+// max number of pixels in one dimension of psd file
+extern const int MAX_PSD_SIZE;
+
+
class KisDocument;
class PSDSaver : public QObject {
Q_OBJECT
public:
PSDSaver(KisDocument *doc);
~PSDSaver() override;
public:
- KisImageBuilder_Result buildFile(QIODevice *io);
+ KisImportExportErrorCode buildFile(QIODevice *io);
KisImageSP image();
public Q_SLOTS:
virtual void cancel();
private:
-
KisImageSP m_image;
KisDocument *m_doc;
bool m_stop;
};
#endif
diff --git a/plugins/impex/psd/tests/data/incorrectFormatFile.txt b/plugins/impex/psd/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/psd/tests/data/readonlyFile.txt b/plugins/impex/psd/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/psd/tests/data/writeonlyFile.txt b/plugins/impex/psd/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/psd/tests/kis_psd_test.cpp b/plugins/impex/psd/tests/kis_psd_test.cpp
index f309ef2125..89b1e7100b 100644
--- a/plugins/impex/psd/tests/kis_psd_test.cpp
+++ b/plugins/impex/psd/tests/kis_psd_test.cpp
@@ -1,351 +1,377 @@
/*
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_psd_test.h"
#include <QTest>
#include <QCoreApplication>
#include <sdk/tests/kistest.h>
#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
#include <resources/KoPattern.h>
#include "kis_group_layer.h"
#include "kis_psd_layer_style.h"
#include "kis_paint_device_debug_utils.h"
+#include <KisImportExportErrorCode.h>
+
+
+
+const QString PSDMimetype = "image/vnd.adobe.photoshop";
void KisPSDTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList());
}
void KisPSDTest::testOpening()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd");
QScopedPointer<KisDocument> doc(qobject_cast<KisDocument*>(KisPart::instance()->createDocument()));
KisImportExportManager manager(doc.data());
doc->setFileBatchMode(true);
- KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString());
- QCOMPARE(status, KisImportExportFilter::OK);
+ KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString());
+ QVERIFY(status.isOk());
Q_ASSERT(doc->image());
}
QSharedPointer<KisDocument> openPsdDocument(const QFileInfo &fileInfo)
{
QSharedPointer<KisDocument> doc(qobject_cast<KisDocument*>(KisPart::instance()->createDocument()));
KisImportExportManager manager(doc.data());
doc->setFileBatchMode(true);
- KisImportExportFilter::ConversionStatus status = manager.importDocument(fileInfo.absoluteFilePath(), QString());
+ KisImportExportErrorCode status = manager.importDocument(fileInfo.absoluteFilePath(), QString());
Q_UNUSED(status);
return doc;
}
void KisPSDTest::testTransparencyMask()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/masks.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1));
doc->setFileBatchMode(true);
doc->setMimeType("image/vnd.adobe.photoshop");
QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_tmask.psd");
bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop");
QVERIFY(retval);
{
QSharedPointer<KisDocument> doc = openPsdDocument(dstFileInfo);
QVERIFY(doc->image());
QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1));
QVERIFY(doc->image()->root()->lastChild());
QVERIFY(doc->image()->root()->lastChild()->firstChild());
QVERIFY(doc->image()->root()->lastChild()->firstChild()->inherits("KisTransparencyMask"));
}
}
void KisPSDTest::testOpenGrayscaleMultilayered()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/gray.psd");
//QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/100x100gray8.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
}
void KisPSDTest::testOpenGroupLayers()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "group_layers.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
KisNodeSP node = TestUtil::findNode(doc->image()->root(), "Group 1 PT");
KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(node.data());
QVERIFY(group);
QVERIFY(group->passThroughMode());
}
void KisPSDTest::testOpenLayerStyles()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
KisLayerSP layer = qobject_cast<KisLayer*>(doc->image()->root()->lastChild().data());
QVERIFY(layer->layerStyle());
QVERIFY(layer->layerStyle()->dropShadow());
QVERIFY(layer->layerStyle()->dropShadow()->effectEnabled());
}
void KisPSDTest::testOpenLayerStylesWithPattern()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
KisLayerSP layer = qobject_cast<KisLayer*>(doc->image()->root()->lastChild().data());
QVERIFY(layer->layerStyle());
QVERIFY(layer->layerStyle()->patternOverlay());
QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid());
}
void KisPSDTest::testOpenLayerStylesWithPatternMulti()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
KisLayerSP layer = qobject_cast<KisLayer*>(doc->image()->root()->lastChild().data());
QVERIFY(layer->layerStyle());
QVERIFY(layer->layerStyle()->patternOverlay());
QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid());
QVERIFY(layer->layerStyle()->stroke());
QVERIFY(layer->layerStyle()->stroke()->effectEnabled());
QVERIFY(layer->layerStyle()->stroke()->pattern());
QVERIFY(layer->layerStyle()->stroke()->pattern()->valid());
}
void KisPSDTest::testSaveLayerStylesWithPatternMulti()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
KisLayerSP layer = qobject_cast<KisLayer*>(doc->image()->root()->lastChild().data());
QVERIFY(layer->layerStyle());
QVERIFY(layer->layerStyle()->patternOverlay());
QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid());
QVERIFY(layer->layerStyle()->stroke());
QVERIFY(layer->layerStyle()->stroke()->effectEnabled());
QVERIFY(layer->layerStyle()->stroke()->pattern());
QVERIFY(layer->layerStyle()->stroke()->pattern()->valid());
doc->setFileBatchMode(true);
const QByteArray mimeType("image/vnd.adobe.photoshop");
QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_save_styles.psd");
bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), mimeType);
QVERIFY(retval);
{
QSharedPointer<KisDocument> doc = openPsdDocument(dstFileInfo);
QVERIFY(doc->image());
QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
//QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single"));
KisLayerSP layer = qobject_cast<KisLayer*>(doc->image()->root()->lastChild().data());
QVERIFY(layer->layerStyle());
QVERIFY(layer->layerStyle()->patternOverlay());
QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern());
QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid());
QVERIFY(layer->layerStyle()->stroke());
QVERIFY(layer->layerStyle()->stroke()->effectEnabled());
QVERIFY(layer->layerStyle()->stroke()->pattern());
QVERIFY(layer->layerStyle()->stroke()->pattern()->valid());
}
}
void KisPSDTest::testOpeningFromOpenCanvas()
{
QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_krita_psd_from_opencanvas.psd");
Q_ASSERT(sourceFileInfo.exists());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
QVERIFY(doc->image());
QVERIFY(doc->image()->root()->firstChild());
}
void KisPSDTest::testOpeningAllFormats()
{
QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files");
QDir dirSources(path);
bool shouldFailTheTest = false;
Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) {
Q_ASSERT(sourceFileInfo.exists());
if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) {
continue;
}
if (sourceFileInfo.fileName() != "ml_cmyk_16b.psd") {
//continue;
}
//dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
if (!doc->image()) {
/**
* 32bit images are expected to fail atm, their loading is not implemented
*/
if (!sourceFileInfo.fileName().contains("_32b")) {
shouldFailTheTest = true;
}
errKrita << "FAILED to open" << sourceFileInfo.fileName();
continue;
}
// just check visually if the file loads fine
KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), sourceFileInfo.fileName(), "dd");
}
QVERIFY(!shouldFailTheTest);
}
void KisPSDTest::testSavingAllFormats()
{
QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files");
QDir dirSources(path);
Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) {
Q_ASSERT(sourceFileInfo.exists());
if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) {
continue;
}
if (sourceFileInfo.fileName() != "sl_rgb_8b.psd") {
//continue;
}
dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName());
QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
if (!doc->image()) {
errKrita << "FAILED to open" << sourceFileInfo.fileName();
continue;
}
QString baseName = sourceFileInfo.fileName();
//QString originalName = QString("%1_0orig").arg(baseName);
//QString resultName = QString("%1_1result").arg(baseName);
QString tempPsdName = QString("%1_3interm.psd").arg(baseName);
QImage refImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100));
// uncomment to do a visual check
// KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), originalName, "dd");
doc->setFileBatchMode(true);
doc->setMimeType("image/vnd.adobe.photoshop");
QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + tempPsdName);
dbgKrita << "Saving" << ppVar(dstFileInfo.fileName());
bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop");
QVERIFY(retval);
{
QSharedPointer<KisDocument> doc = openPsdDocument(dstFileInfo);
QVERIFY(doc->image());
// uncomment to do a visual check
//KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), resultName, "dd");
QImage resultImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100));
QCOMPARE(resultImage, refImage);
}
}
}
+
+void KisPSDTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PSDMimetype);
+}
+
+
+void KisPSDTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PSDMimetype);
+}
+
+
+void KisPSDTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PSDMimetype);
+}
+
+
+
+
KISTEST_MAIN(KisPSDTest)
diff --git a/plugins/impex/psd/tests/kis_psd_test.h b/plugins/impex/psd/tests/kis_psd_test.h
index 6ed0b7c537..326368d64e 100644
--- a/plugins/impex/psd/tests/kis_psd_test.h
+++ b/plugins/impex/psd/tests/kis_psd_test.h
@@ -1,45 +1,50 @@
/*
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_PSD_TEST_H_
#define _KIS_PSD_TEST_H_
#include <QtTest>
class KisPSDTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
void testOpening();
void testTransparencyMask();
void testOpenGrayscaleMultilayered();
void testOpenGroupLayers();
void testOpenLayerStyles();
void testOpenLayerStylesWithPattern();
void testOpenLayerStylesWithPatternMulti();
void testSaveLayerStylesWithPatternMulti();
void testOpeningFromOpenCanvas();
void testOpeningAllFormats();
void testSavingAllFormats();
+
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/impex/qimageio/CMakeLists.txt b/plugins/impex/qimageio/CMakeLists.txt
index 1c4c9d8616..07221802c9 100644
--- a/plugins/impex/qimageio/CMakeLists.txt
+++ b/plugins/impex/qimageio/CMakeLists.txt
@@ -1,25 +1,27 @@
+add_subdirectory(tests)
+
set(kritaqimageioexport_SOURCES
kis_qimageio_export.cpp
)
ki18n_wrap_ui(kritaqimageioexport_SOURCES )
add_library(kritaqimageioexport MODULE ${kritaqimageioexport_SOURCES})
target_link_libraries(kritaqimageioexport kritaui kritaimpex)
install(TARGETS kritaqimageioexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
set(kritaqimageioimport_SOURCES
kis_qimageio_import.cpp
)
ki18n_wrap_ui(kritaqimageioimport_SOURCES )
add_library(kritaqimageioimport MODULE ${kritaqimageioimport_SOURCES})
target_link_libraries(kritaqimageioimport kritaui)
install(TARGETS kritaqimageioimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_qimageio.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/qimageio/kis_qimageio_export.cpp b/plugins/impex/qimageio/kis_qimageio_export.cpp
index ee1c2a9aac..55db76bcc3 100644
--- a/plugins/impex/qimageio/kis_qimageio_export.cpp
+++ b/plugins/impex/qimageio/kis_qimageio_export.cpp
@@ -1,69 +1,69 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_qimageio_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QApplication>
#include <KisMimeDatabase.h>
#include <KisExportCheckRegistry.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOExportFactory, "krita_qimageio_export.json", registerPlugin<KisQImageIOExport>();)
KisQImageIOExport::KisQImageIOExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisQImageIOExport::~KisQImageIOExport()
{
}
-KisImportExportFilter::ConversionStatus KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
QRect rc = document->savingImage()->bounds();
QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
- bool r = image.save(io, QFileInfo(filename()).suffix().toLatin1());
- if (r) {
- return KisImportExportFilter::OK;
+ bool result = image.save(io, QFileInfo(filename()).suffix().toLatin1());
+ if (result) {
+ return ImportExportCodes::OK;
}
else {
- return KisImportExportFilter::InvalidFormat;
+ return ImportExportCodes::FileFormatIncorrect;
}
}
void KisQImageIOExport::initializeCapabilities()
{
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, KisMimeDatabase::descriptionForMimeType(mimeType()));
addCapability(KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED));
}
#include "kis_qimageio_export.moc"
diff --git a/plugins/impex/qimageio/kis_qimageio_export.h b/plugins/impex/qimageio/kis_qimageio_export.h
index 6922fa357b..423f9876b5 100644
--- a/plugins/impex/qimageio/kis_qimageio_export.h
+++ b/plugins/impex/qimageio/kis_qimageio_export.h
@@ -1,38 +1,38 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_BMP_EXPORT_H_
#define _KIS_BMP_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisQImageIOExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisQImageIOExport(QObject *parent, const QVariantList &);
~KisQImageIOExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/qimageio/kis_qimageio_import.cpp b/plugins/impex/qimageio/kis_qimageio_import.cpp
index 73c2b78f0d..411162e8a4 100644
--- a/plugins/impex/qimageio/kis_qimageio_import.cpp
+++ b/plugins/impex/qimageio/kis_qimageio_import.cpp
@@ -1,73 +1,73 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_qimageio_import.h"
#include <QCheckBox>
#include <QSlider>
#include <QApplication>
#include <QFileInfo>
#include <QImageReader>
#include <kpluginfactory.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <kis_transaction.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_node.h>
#include <kis_group_layer.h>
K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOImportFactory, "krita_qimageio_import.json", registerPlugin<KisQImageIOImport>();)
KisQImageIOImport::KisQImageIOImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisQImageIOImport::~KisQImageIOImport()
{
}
-KisImportExportFilter::ConversionStatus KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
QFileInfo fi(filename());
QImage img;
if (!img.loadFromData(io->readAll(), fi.suffix().toLower().toLatin1())) {
- return KisImportExportFilter::InvalidFormat;
+ return ImportExportCodes::FileFormatIncorrect;
}
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, i18n("Imported Image"));
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255);
layer->paintDevice()->convertFromQImage(img, 0, 0, 0);
image->addNode(layer.data(), image->rootLayer().data());
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_qimageio_import.moc"
diff --git a/plugins/impex/qimageio/kis_qimageio_import.h b/plugins/impex/qimageio/kis_qimageio_import.h
index 4e93f74beb..a97eade902 100644
--- a/plugins/impex/qimageio/kis_qimageio_import.h
+++ b/plugins/impex/qimageio/kis_qimageio_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_BMP_IMPORT_H_
#define _KIS_BMP_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisQImageIOImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisQImageIOImport(QObject *parent, const QVariantList &);
~KisQImageIOImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/qimageio/tests/CMakeLists.txt b/plugins/impex/qimageio/tests/CMakeLists.txt
new file mode 100644
index 0000000000..bed630f553
--- /dev/null
+++ b/plugins/impex/qimageio/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(KisQImageIOTest.cpp
+ TEST_NAME KisQImageIOTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/qimageio/tests/KisQImageIOTest.cpp
similarity index 62%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/qimageio/tests/KisQImageIOTest.cpp
index 6ca66688f8..65d4cb9ce2 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/qimageio/tests/KisQImageIOTest.cpp
@@ -1,40 +1,57 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisQImageIOTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString QImageIOMimetype = "image/x-gimp-brush";
+
+
+
+void KisQImageIOTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), QImageIOMimetype);
+}
+
+
+void KisQImageIOTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), QImageIOMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisQImageIOTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), QImageIOMimetype);
+}
+
+
+
+KISTEST_MAIN(KisQImageIOTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/qimageio/tests/KisQImageIOTest.h
similarity index 72%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/qimageio/tests/KisQImageIOTest.h
index 4dad62dfca..ea154f3dbc 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/qimageio/tests/KisQImageIOTest.h
@@ -1,32 +1,35 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_QIMAGEIO_TEST_H_
+#define _KIS_QIMAGEIO_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisQImageIOTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_QIMAGEIO_TEST_H_
+
diff --git a/plugins/impex/qimageio/tests/data/incorrectFormatFile.txt b/plugins/impex/qimageio/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/qimageio/tests/data/readonlyFile.txt b/plugins/impex/qimageio/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/qimageio/tests/data/writeonlyFile.txt b/plugins/impex/qimageio/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/qml/CMakeLists.txt b/plugins/impex/qml/CMakeLists.txt
index 2f3826d5d1..e53156905a 100644
--- a/plugins/impex/qml/CMakeLists.txt
+++ b/plugins/impex/qml/CMakeLists.txt
@@ -1,10 +1,12 @@
+add_subdirectory(tests)
+
set(kritaqmlexport_SOURCES
qml_converter.cc
qml_export.cc
)
add_library(kritaqmlexport MODULE ${kritaqmlexport_SOURCES})
target_link_libraries(kritaqmlexport kritaui kritaimpex)
install(TARGETS kritaqmlexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/impex/qml/qml_converter.cc b/plugins/impex/qml/qml_converter.cc
index 8422672254..48b9440982 100644
--- a/plugins/impex/qml/qml_converter.cc
+++ b/plugins/impex/qml/qml_converter.cc
@@ -1,91 +1,97 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "qml_converter.h"
#include <QFileInfo>
#include <QDir>
#include <kis_image.h>
#include <kis_group_layer.h>
#define SPACE " "
QMLConverter::QMLConverter()
{
}
QMLConverter::~QMLConverter()
{
}
-KisImageBuilder_Result QMLConverter::buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image)
+KisImportExportErrorCode QMLConverter::buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image)
{
QTextStream out(io);
out.setCodec("UTF-8");
out << "import QtQuick 1.1" << "\n\n";
out << "Rectangle {\n";
writeInt(out, 1, "width", image->width());
writeInt(out, 1, "height", image->height());
out << "\n";
QFileInfo info(filename);
QFileInfo infoRealFile(realFilename);
KisNodeSP node = image->rootLayer()->firstChild();
QString imageDir = infoRealFile.baseName() + "_images";
QString imagePath = infoRealFile.absolutePath() + '/' + imageDir;
if (node) {
QDir dir;
- dir.mkpath(imagePath);
+ bool success = dir.mkpath(imagePath);
+ if (!success)
+ {
+ return ImportExportCodes::CannotCreateFile;
+ }
}
+
dbgFile << "Saving images to " << imagePath;
while(node) {
KisPaintDeviceSP projection = node->projection();
QRect rect = projection->exactBounds();
QImage qmlImage = projection->convertToQImage(0, rect.x(), rect.y(), rect.width(), rect.height());
QString name = node->name().replace(' ', '_').toLower();
QString fileName = name + ".png";
qmlImage.save(imagePath +'/'+ fileName);
out << SPACE << "Image {\n";
writeString(out, 2, "id", name);
writeInt(out, 2, "x", rect.x());
writeInt(out, 2, "y", rect.y());
writeInt(out, 2, "width", rect.width());
writeInt(out, 2, "height", rect.height());
writeString(out, 2, "source", "\"" + imageDir + '/' + fileName + "\"" );
writeString(out, 2, "opacity", QString().setNum(node->opacity()/255.0));
out << SPACE << "}\n";
node = node->nextSibling();
}
out << "}\n";
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
void QMLConverter::writeString(QTextStream& out, int spacing, const QString& setting, const QString& value) {
for (int space = 0; space < spacing; space++) {
out << SPACE;
}
out << setting << ": " << value << "\n";
}
void QMLConverter::writeInt(QTextStream& out, int spacing, const QString& setting, int value) {
writeString(out, spacing, setting, QString::number(value));
}
diff --git a/plugins/impex/qml/qml_converter.h b/plugins/impex/qml/qml_converter.h
index e70aa3e176..f0b1ccb122 100644
--- a/plugins/impex/qml/qml_converter.h
+++ b/plugins/impex/qml/qml_converter.h
@@ -1,44 +1,45 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _QML_CONVERTER_H_
#define _QML_CONVERTER_H_
#include <stdio.h>
#include <QObject>
#include <QFileInfo>
#include "kis_types.h"
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
class QMLConverter : public QObject
{
Q_OBJECT
public:
QMLConverter();
~QMLConverter() override;
public:
- KisImageBuilder_Result buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image);
+ KisImportExportErrorCode buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image);
private:
void writeString(QTextStream& out, int spacing, const QString& setting, const QString& value);
void writeInt(QTextStream& out, int spacing, const QString& setting, int value);
};
#endif
diff --git a/plugins/impex/qml/qml_export.cc b/plugins/impex/qml/qml_export.cc
index d70a3278bb..732b277aa7 100644
--- a/plugins/impex/qml/qml_export.cc
+++ b/plugins/impex/qml/qml_export.cc
@@ -1,73 +1,68 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 "qml_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QApplication>
#include <KisExportCheckRegistry.h>
#include <KisDocument.h>
#include <kis_image.h>
#include "qml_converter.h"
#include <KoColorModelStandardIds.h>
K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_qml_export.json", registerPlugin<QMLExport>();)
QMLExport::QMLExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
QMLExport::~QMLExport()
{
}
-KisImportExportFilter::ConversionStatus QMLExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode QMLExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
KisImageSP image = document->savingImage();
Q_CHECK_PTR(image);
QMLConverter converter;
- KisImageBuilder_Result result = converter.buildFile(filename(), realFilename(), io, image);
- if (result == KisImageBuilder_RESULT_OK) {
- dbgFile << "success !";
- return KisImportExportFilter::OK;
- }
- dbgFile << " Result =" << result;
- return KisImportExportFilter::InternalError;
+ return converter.buildFile(filename(), realFilename(), io, image);
}
void QMLExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "QML");
}
#include <qml_export.moc>
diff --git a/plugins/impex/qml/qml_export.h b/plugins/impex/qml/qml_export.h
index 1cf9b0038d..e951a92843 100644
--- a/plugins/impex/qml/qml_export.h
+++ b/plugins/impex/qml/qml_export.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 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 _QML_EXPORT_H_
#define _QML_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class QMLExport : public KisImportExportFilter
{
Q_OBJECT
public:
QMLExport(QObject *parent, const QVariantList &);
~QMLExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/qml/tests/CMakeLists.txt b/plugins/impex/qml/tests/CMakeLists.txt
new file mode 100644
index 0000000000..d197d45935
--- /dev/null
+++ b/plugins/impex/qml/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(KisQmlTest.cpp
+ TEST_NAME KisQmlTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/qml/tests/KisQmlTest.cpp
similarity index 78%
copy from plugins/impex/ppm/tests/kis_ppm_test.cpp
copy to plugins/impex/qml/tests/KisQmlTest.cpp
index 6aa3859d0a..c92533ae76 100644
--- a/plugins/impex/ppm/tests/kis_ppm_test.cpp
+++ b/plugins/impex/qml/tests/KisQmlTest.cpp
@@ -1,39 +1,43 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_ppm_test.h"
+#include "KisQmlTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisPPMTest::testFiles()
+
+const QString QmlMimetype = "text/x-qml";
+
+
+void KisQmlTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), QmlMimetype);
}
-KISTEST_MAIN(KisPPMTest)
+
+KISTEST_MAIN(KisQmlTest)
+
diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/qml/tests/KisQmlTest.h
similarity index 79%
copy from plugins/impex/jpeg/tests/kis_jpeg_test.h
copy to plugins/impex/qml/tests/KisQmlTest.h
index 5c19b0cda6..9944e9ca65 100644
--- a/plugins/impex/jpeg/tests/kis_jpeg_test.h
+++ b/plugins/impex/qml/tests/KisQmlTest.h
@@ -1,31 +1,33 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_JPEG_TEST_H_
-#define _KIS_JPEG_TEST_H_
+#ifndef _KIS_QML_TEST_H_
+#define _KIS_QML_TEST_H_
#include <QtTest>
-class KisJpegTest : public QObject
+class KisQmlTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
+
+ void testExportToReadonly();
};
-#endif
+#endif // _KIS_QML_TEST_H_
+
diff --git a/plugins/impex/qml/tests/data/readonlyFile.txt b/plugins/impex/qml/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/raw/CMakeLists.txt b/plugins/impex/raw/CMakeLists.txt
index f03ae6dd5c..f7b63c845c 100644
--- a/plugins/impex/raw/CMakeLists.txt
+++ b/plugins/impex/raw/CMakeLists.txt
@@ -1,33 +1,35 @@
+add_subdirectory(tests)
+
if(OPENEXR_FOUND)
include_directories(${OPENEXR_INCLUDE_DIR})
endif()
include_directories(${LibRaw_INCLUDE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libkdcraw/src)
set(krita_raw_import_SOURCES kis_raw_import.cpp
3rdparty/libkdcraw/src/dcrawinfocontainer.cpp
3rdparty/libkdcraw/src/dcrawsettingswidget.cpp
3rdparty/libkdcraw/src/kdcraw.cpp
3rdparty/libkdcraw/src/kdcraw_p.cpp
3rdparty/libkdcraw/src/libkdcraw_debug.cpp
3rdparty/libkdcraw/src/ractionjob.cpp
3rdparty/libkdcraw/src/ractionthreadbase.cpp
3rdparty/libkdcraw/src/rawdecodingsettings.cpp
3rdparty/libkdcraw/src/rcombobox.cpp
3rdparty/libkdcraw/src/rexpanderbox.cpp
3rdparty/libkdcraw/src/rnuminput.cpp
3rdparty/libkdcraw/src/rsliderspinbox.cpp
3rdparty/libkdcraw/src/rwidgetutils.cpp
3rdparty/libkdcraw/src/squeezedcombobox.cpp
)
ki18n_wrap_ui(krita_raw_import_SOURCES
wdgrawimport.ui
)
add_library(krita_raw_import MODULE ${krita_raw_import_SOURCES})
target_link_libraries(krita_raw_import kritaui ${LibRaw_LIBRARIES})
install(TARGETS krita_raw_import DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_raw.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/raw/kis_raw_import.cpp b/plugins/impex/raw/kis_raw_import.cpp
index 41cdd12c8c..29709cc1c9 100644
--- a/plugins/impex/raw/kis_raw_import.cpp
+++ b/plugins/impex/raw/kis_raw_import.cpp
@@ -1,192 +1,199 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* 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_raw_import.h"
#include <kpluginfactory.h>
#include <KoDialog.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
+#include <KisImportExportErrorCode.h>
#include <KoColorSpaceTraits.h>
#include "kis_debug.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_transaction.h"
#include "kis_group_layer.h"
#include "kis_paint_layer.h"
#include "kis_iterator_ng.h"
#include <kdcraw.h>
#include <libkdcraw_version.h>
using namespace KDcrawIface;
K_PLUGIN_FACTORY_WITH_JSON(KisRawImportFactory, "krita_raw_import.json",
registerPlugin<KisRawImport>();)
KisRawImport::KisRawImport(QObject *parent, const QVariantList &)
: KisImportExportFilter(parent)
{
m_dialog = new KoDialog();
m_dialog->enableButtonApply(false);
QWidget* widget = new QWidget;
m_rawWidget.setupUi(widget);
m_dialog->setMainWidget(widget);
connect(m_rawWidget.pushButtonUpdate, SIGNAL(clicked()), this, SLOT(slotUpdatePreview()));
}
KisRawImport::~KisRawImport()
{
delete m_dialog;
}
inline quint16 correctIndian(quint16 v)
{
#if KDCRAW_VERSION < 0x000400
return ((v & 0x00FF) << 8) | ((v & 0xFF00 >> 8));
#else
return v;
#endif
}
-KisImportExportFilter::ConversionStatus KisRawImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisRawImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/)
{
// Show dialog
m_dialog->setCursor(Qt::ArrowCursor);
QApplication::setOverrideCursor(Qt::ArrowCursor);
#if KDCRAW_VERSION < 0x010200
m_rawWidget.rawSettings->setDefaultSettings();
#else
m_rawWidget.rawSettings->resetToDefault();
#endif
slotUpdatePreview();
- if (m_dialog->exec() == QDialog::Accepted) {
+ int r = QDialog::Accepted;
+ if (!batchMode()) {
+ r = m_dialog->exec();
+ }
+
+ if (r == QDialog::Accepted) {
QApplication::setOverrideCursor(Qt::WaitCursor);
// Do the decoding
// TODO: it would probably be better done in a thread, while an other thread simulate that the application is still living (or even better if libkdcraw was giving us some progress report
QByteArray imageData;
RawDecodingSettings settings = rawDecodingSettings();
settings.sixteenBitsImage = true;
int width, height, rgbmax;
KDcraw dcraw;
- if (!dcraw.decodeRAWImage(filename(), settings, imageData, width, height, rgbmax)) return KisImportExportFilter::CreationError;
+ if (!dcraw.decodeRAWImage(filename(), settings, imageData, width, height, rgbmax))
+ return ImportExportCodes::FileFormatIncorrect;
QApplication::restoreOverrideCursor();
// Init the image
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb16();
KisImageSP image = new KisImage(document->createUndoStore(), width, height, cs, filename());
- if (image.isNull()) return KisImportExportFilter::CreationError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(!image.isNull(), ImportExportCodes::InternalError);
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), quint8_MAX);
image->addNode(layer, image->rootLayer());
- if (layer.isNull()) return KisImportExportFilter::CreationError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(!layer.isNull(), ImportExportCodes::InternalError);
KisPaintDeviceSP device = layer->paintDevice();
- if (device.isNull()) return KisImportExportFilter::CreationError;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(!device.isNull(), ImportExportCodes::InternalError);
// Copy the data
KisHLineIteratorSP it = device->createHLineIteratorNG(0, 0, width);
for (int y = 0; y < height; ++y) {
do {
KoBgrU16Traits::Pixel* pixel = reinterpret_cast<KoBgrU16Traits::Pixel*>(it->rawData());
quint16* ptr = ((quint16*)imageData.data()) + (y * width + it->x()) * 3;
#if KDCRAW_VERSION < 0x000400
pixel->red = correctIndian(ptr[2]);
pixel->green = correctIndian(ptr[1]);
pixel->blue = correctIndian(ptr[0]);
#else
pixel->red = correctIndian(ptr[0]);
pixel->green = correctIndian(ptr[1]);
pixel->blue = correctIndian(ptr[2]);
#endif
pixel->alpha = 0xFFFF;
} while (it->nextPixel());
it->nextRow();
}
QApplication::restoreOverrideCursor();
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
QApplication::restoreOverrideCursor();
- return KisImportExportFilter::UserCancelled;
+ return ImportExportCodes::Cancelled;
}
void KisRawImport::slotUpdatePreview()
{
QByteArray imageData;
RawDecodingSettings settings = rawDecodingSettings();
settings.sixteenBitsImage = false;
int width, height, rgbmax;
KDcraw dcraw;
if (dcraw.decodeHalfRAWImage(filename(), settings, imageData, width, height, rgbmax)) {
QImage image(width, height, QImage::Format_RGB32);
for (int y = 0; y < height; ++y) {
QRgb *pixel= reinterpret_cast<QRgb *>(image.scanLine(y));
for (int x = 0; x < width; ++x) {
quint8* ptr = ((quint8*)imageData.data()) + (y * width + x) * 3;
pixel[x] = qRgb(ptr[0], ptr[1], ptr[2]);
}
}
m_rawWidget.preview->setPixmap(QPixmap::fromImage(image));
}
}
RawDecodingSettings KisRawImport::rawDecodingSettings()
{
#if KDCRAW_VERSION < 0x010200
RawDecodingSettings settings;
settings.sixteenBitsImage = true;
settings.brightness = m_rawWidget.rawSettings->brightness();
settings.RAWQuality = m_rawWidget.rawSettings->quality();
settings.outputColorSpace = m_rawWidget.rawSettings->outputColorSpace();
settings.RGBInterpolate4Colors = m_rawWidget.rawSettings->useFourColor();
settings.DontStretchPixels = m_rawWidget.rawSettings->useDontStretchPixels();
settings.unclipColors = m_rawWidget.rawSettings->unclipColor();
settings.whiteBalance = m_rawWidget.rawSettings->whiteBalance();
settings.customWhiteBalance = m_rawWidget.rawSettings->customWhiteBalance();
settings.customWhiteBalanceGreen = m_rawWidget.rawSettings->customWhiteBalanceGreen();
settings.enableBlackPoint = m_rawWidget.rawSettings->useBlackPoint();
settings.blackPoint = m_rawWidget.rawSettings->blackPoint();
settings.enableNoiseReduction = m_rawWidget.rawSettings->useNoiseReduction();
settings.NRThreshold = m_rawWidget.rawSettings->NRThreshold();
settings.enableCACorrection = m_rawWidget.rawSettings->useCACorrection();
settings.caMultiplier[0] = m_rawWidget.rawSettings->caRedMultiplier();
settings.caMultiplier[1] = m_rawWidget.rawSettings->caBlueMultiplier();
return settings;
#else
return m_rawWidget.rawSettings->settings();
#endif
}
#include "kis_raw_import.moc"
diff --git a/plugins/impex/raw/kis_raw_import.h b/plugins/impex/raw/kis_raw_import.h
index 0e5843580e..8ae4e42af2 100644
--- a/plugins/impex/raw/kis_raw_import.h
+++ b/plugins/impex/raw/kis_raw_import.h
@@ -1,58 +1,58 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_RAW_IMPORT_H_
#define KIS_RAW_IMPORT_H_
#include <KisImportExportFilter.h>
#include "ui_wdgrawimport.h"
class KoDialog;
class WdgRawImport;
namespace KDcrawIface
{
class RawDecodingSettings;
}
class KisRawImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisRawImport(QObject *parent, const QVariantList &);
~KisRawImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
private Q_SLOTS:
void slotUpdatePreview();
private:
KDcrawIface::RawDecodingSettings rawDecodingSettings();
private:
Ui::WdgRawImport m_rawWidget;
KoDialog* m_dialog;
};
#endif // KIS_RAW_IMPORT_H_
diff --git a/plugins/impex/raw/tests/CMakeLists.txt b/plugins/impex/raw/tests/CMakeLists.txt
new file mode 100644
index 0000000000..9057477638
--- /dev/null
+++ b/plugins/impex/raw/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(KisRawTest.cpp
+ TEST_NAME KisRawTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/raw/tests/KisRawTest.cpp
similarity index 70%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/raw/tests/KisRawTest.cpp
index 6ca66688f8..ca954e6d6a 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/raw/tests/KisRawTest.cpp
@@ -1,40 +1,51 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisRawTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString RawMimetype = "image/x-krita-raw";
+
+
+
+void KisRawTest::testImportFromWriteonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), RawMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisRawTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), RawMimetype);
+}
+
+
+
+KISTEST_MAIN(KisRawTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/raw/tests/KisRawTest.h
similarity index 76%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/raw/tests/KisRawTest.h
index 4dad62dfca..ec170f963a 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/raw/tests/KisRawTest.h
@@ -1,32 +1,34 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_RAW_TEST_H_
+#define _KIS_RAW_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisRawTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_RAW_TEST_H_
+
diff --git a/plugins/impex/raw/tests/data/incorrectFormatFile.txt b/plugins/impex/raw/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/raw/tests/data/readonlyFile.txt b/plugins/impex/raw/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/raw/tests/data/writeonlyFile.txt b/plugins/impex/raw/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/spriter/kis_spriter_export.cpp b/plugins/impex/spriter/kis_spriter_export.cpp
index a55c47948a..c5cc2308ad 100644
--- a/plugins/impex/spriter/kis_spriter_export.cpp
+++ b/plugins/impex/spriter/kis_spriter_export.cpp
@@ -1,617 +1,651 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_spriter_export.h"
#include <QApplication>
#include <QCheckBox>
#include <QDomDocument>
#include <QFileInfo>
#include <QSlider>
#include <QDir>
#include <kpluginfactory.h>
#include <KoColorSpaceConstants.h>
#include <KoColorSpaceRegistry.h>
#include <KisExportCheckRegistry.h>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_node.h>
#include <kis_painter.h>
#include <kis_paint_layer.h>
#include <kis_shape_layer.h>
#include <kis_file_layer.h>
#include <kis_clone_layer.h>
#include <kis_generator_layer.h>
#include <kis_adjustment_layer.h>
#include <KisPart.h>
#include <kis_types.h>
#include <kis_png_converter.h>
#include <kis_global.h> // for KisDegreesToRadians
#include <kis_fast_math.h>
#include <math.h>
#include <kis_dom_utils.h>
K_PLUGIN_FACTORY_WITH_JSON(KisSpriterExportFactory, "krita_spriter_export.json", registerPlugin<KisSpriterExport>();)
KisSpriterExport::KisSpriterExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisSpriterExport::~KisSpriterExport()
{
}
-bool KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName)
+KisImportExportErrorCode KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName)
{
QFileInfo fi(fileName);
QDir d = fi.absoluteDir();
d.mkpath(d.path());
QRect rc = m_image->bounds().intersected(dev->exactBounds());
if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) {
dev = new KisPaintDevice(*dev.data());
dev->convertTo(KoColorSpaceRegistry::instance()->rgb8());
}
KisPNGOptions options;
options.forceSRGB = true;
vKisAnnotationSP_it beginIt = m_image->beginAnnotations();
vKisAnnotationSP_it endIt = m_image->endAnnotations();
KisPNGConverter converter(0);
- KisImageBuilder_Result res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0);
+ KisImportExportErrorCode res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0);
- return (res == KisImageBuilder_RESULT_OK);
+ return res;
}
-void KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId)
+KisImportExportErrorCode KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId)
{
// qDebug() << "parseFolder: parent" << parentGroup->name()
// << "folderName" << folderName
// << "basepath" << basePath;
int currentFolder=0;
if(folderId == 0)
{
folderId = &currentFolder;
}
QString pathName;
if (!folderName.isEmpty()) {
pathName = folderName + "/";
}
+
KisNodeSP child = parentGroup->lastChild();
while (child) {
if (child->visible() && child->inherits("KisGroupLayer")) {
- parseFolder(qobject_cast<KisGroupLayer*>(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId);
+ KisImportExportErrorCode res = parseFolder(qobject_cast<KisGroupLayer*>(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId);
+ if (!res.isOk()) {
+ return res;
+ }
}
child = child->prevSibling();
}
Folder folder;
folder.id = *folderId;
folder.name = folderName;
folder.groupName = parentGroup->name();
int fileId = 0;
child = parentGroup->lastChild();
+
while (child) {
if (child->visible() && !child->inherits("KisGroupLayer") && !child->inherits("KisMask")) {
QRectF rc = m_image->bounds().intersected(child->exactBounds());
QString layerBaseName = child->name().split(" ").first();
SpriterFile file;
file.id = fileId++;
file.pathName = pathName;
file.baseName = layerBaseName;
file.layerName = child->name();
file.name = folderName + "/" + layerBaseName + ".png";
qreal xmin = rc.left();
qreal ymin = rc.top();
qreal xmax = rc.right();
qreal ymax = rc.bottom();
file.width = xmax - xmin;
file.height = ymax - ymin;
file.x = xmin;
file.y = ymin;
//qDebug() << "Created file" << file.id << file.name << file.pathName << file.baseName << file.width << file.height << file.layerName;
-
- savePaintDevice(child->projection(), basePath + file.name);
- folder.files.append(file);
+ KisImportExportErrorCode result = savePaintDevice(child->projection(), basePath + file.name);
+ if (result.isOk()) {
+ folder.files.append(file);
+ } else {
+ return result;
+ }
}
child = child->prevSibling();
}
if (folder.files.size() > 0) {
//qDebug() << "Adding folder" << folder.id << folder.name << folder.groupName << folder.files.length();
m_folders.append(folder);
(*folderId)++;
}
+
+ return ImportExportCodes::OK;
}
Bone *KisSpriterExport::parseBone(const Bone *parent, KisGroupLayerSP groupLayer)
{
static int boneId = 0;
QString groupBaseName = groupLayer->name().split(" ").first();
Bone *bone = new Bone;
bone->id = boneId++;
bone->parentBone = parent;
bone->name = groupBaseName;
if (m_boneLayer) {
QRectF rc = m_image->bounds().intersected(m_boneLayer->exactBounds());
qreal xmin = rc.left();
qreal ymin = rc.top();
qreal xmax = rc.right();
qreal ymax = rc.bottom();
bone->x = (xmin + xmax) / 2;
bone->y = -(ymin + ymax) / 2;
bone->width = xmax - xmin;
bone->height = ymax - ymin;
}
else {
bone->x = 0.0;
bone->y = 0.0;
bone->width = 0.0;
bone->height = 0.0;
}
if (parent) {
bone->localX = bone->x - parent->x;
bone->localY = bone->y - parent->y;
}
else {
bone->localX = bone->x;
bone->localY = bone->y;
}
bone->localAngle = 0.0;
bone->localScaleX = 1.0;
bone->localScaleY = 1.0;
KisNodeSP child = groupLayer->lastChild();
while (child) {
if (child->visible() && child->inherits("KisGroupLayer")) {
bone->bones.append(parseBone(bone, qobject_cast<KisGroupLayer*>(child.data())));
}
child = child->prevSibling();
}
//qDebug() << "Created bone" << bone->id << "with" << bone->bones.size() << "bones";
return bone;
}
void copyBone(Bone *startBone)
{
startBone->fixLocalX = startBone->localX;
startBone->fixLocalY = startBone->localY;
startBone->fixLocalAngle = startBone->localAngle;
startBone->fixLocalScaleX= startBone->localScaleX;
startBone->fixLocalScaleY= startBone->localScaleY;
Q_FOREACH(Bone *child, startBone->bones) {
copyBone(child);
}
}
void KisSpriterExport::fixBone(Bone *bone)
{
qreal boneLocalAngle = 0;
qreal boneLocalScaleX = 1;
if (bone->bones.length() >= 1) {
// if a bone has one or more children, point at first child
Bone *childBone = bone->bones[0];
qreal dx = childBone->x - bone->x;
qreal dy = childBone->y - bone->y;
if (qAbs(dx) > 0 || qAbs(dy) > 0) {
boneLocalAngle = KisFastMath::atan2(dy, dx);
boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200;
}
}
else if (bone->parentBone) {
// else, if bone has parent, point away from parent
qreal dx = bone->x - bone->parentBone->x;
qreal dy = bone->y - bone->parentBone->y;
if (qAbs(dx) > 0 || qAbs(dy) > 0) {
boneLocalAngle = KisFastMath::atan2(dy, dx);
boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200;
}
}
// adjust bone angle
bone->fixLocalAngle += boneLocalAngle;
bone->fixLocalScaleX *= boneLocalScaleX;
// rotate all the child bones back to world position
for (int i = 0; i < bone->bones.length(); ++i) {
Bone *childBone = bone->bones[i];
qreal tx = childBone->fixLocalX;
qreal ty = childBone->fixLocalY;
childBone->fixLocalX = tx * cos(-boneLocalAngle) - ty * sin(-boneLocalAngle);
childBone->fixLocalY = tx * sin(-boneLocalAngle) + ty * cos(-boneLocalAngle);
childBone->fixLocalX /= boneLocalScaleX;
childBone->fixLocalAngle -= boneLocalAngle;
childBone->fixLocalScaleX /= boneLocalScaleX;
}
// rotate all the child objects back to world position
for (int i = 0; i < m_objects.length(); ++i) {
if (m_objects[i].bone == bone) {
m_objects[i].fixLocalAngle -= boneLocalAngle;
m_objects[i].fixLocalScaleX /= boneLocalScaleX;
}
}
// process all child bones
for (int i = 0; i < bone->bones.length(); ++i) {
fixBone(bone->bones[i]);
}
}
void KisSpriterExport::writeBoneRef(const Bone *bone, QDomElement &key, QDomDocument &scml)
{
if (!bone) return;
QDomElement boneRef = scml.createElement("bone_ref");
key.appendChild(boneRef);
boneRef.setAttribute("id", bone->id);
if (bone->parentBone) {
boneRef.setAttribute("parent", bone->parentBone->id);
}
boneRef.setAttribute("timeline", m_timelineid++);
boneRef.setAttribute("key", "0");
Q_FOREACH(const Bone *childBone, bone->bones) {
writeBoneRef(childBone, key, scml);
}
}
void KisSpriterExport::writeBone(const Bone *bone, QDomElement &animation, QDomDocument &scml)
{
if (!bone) return;
QDomElement timeline = scml.createElement("timeline");
animation.appendChild(timeline);
timeline.setAttribute("id", m_timelineid);
timeline.setAttribute("name", bone->name);
timeline.setAttribute("object_type", "bone");
QDomElement key = scml.createElement("key");
timeline.appendChild(key);
key.setAttribute("id", "0");
key.setAttribute("spin", 0);
QDomElement boneEl = scml.createElement("bone");
key.appendChild(boneEl);
boneEl.setAttribute("x", QString::number(bone->fixLocalX, 'f', 2));
boneEl.setAttribute("y", QString::number(bone->fixLocalY, 'f', 2));
boneEl.setAttribute("angle", QString::number(bone->fixLocalAngle, 'f', 2));
boneEl.setAttribute("scale_x", QString::number(bone->fixLocalScaleX, 'f', 2));
boneEl.setAttribute("scale_y", QString::number(bone->fixLocalScaleY, 'f', 2));
m_timelineid++;
Q_FOREACH(const Bone *childBone, bone->bones) {
writeBone(childBone, animation, scml);
}
}
void KisSpriterExport::fillScml(QDomDocument &scml, const QString &entityName)
{
//qDebug() << "Creating scml" << entityName;
QDomElement root = scml.createElement("spriter_data");
scml.appendChild(root);
root.setAttribute("scml_version", 1);
root.setAttribute("generator", "krita");
root.setAttribute("generator_version", qApp->applicationVersion());
Q_FOREACH(const Folder &folder, m_folders) {
QDomElement fe = scml.createElement("folder");
root.appendChild(fe);
fe.setAttribute("id", folder.id);
fe.setAttribute("name", folder.name);
Q_FOREACH(const SpriterFile &file, folder.files) {
QDomElement fileElement = scml.createElement("file");
fe.appendChild(fileElement);
fileElement.setAttribute("id", file.id);
fileElement.setAttribute("name", file.name);
fileElement.setAttribute("width", QString::number(file.width, 'f', 2));
fileElement.setAttribute("height", QString::number(file.height, 'f', 2));
// qreal pivotX=0;
// qreal pivotY=1;
// Q_FOREACH(const SpriterObject &object, m_objects) {
// if(file.id == object.fileId)
// {
// pivotX = (0.0 -(object.fixLocalX / file.width));
// pivotY = (1.0 -(object.fixLocalY / file.height));
// break;
// }
// }
// fileElement.setAttribute("pivot_x", QString::number(pivotX, 'f', 2));
// fileElement.setAttribute("pivot_y", QString::number(pivotY, 'f', 2));
}
}
// entity
QDomElement entity = scml.createElement("entity");
root.appendChild(entity);
entity.setAttribute("id", "0");
entity.setAttribute("name", entityName);
// entity/animation
QDomElement animation = scml.createElement("animation");
entity.appendChild(animation);
animation.setAttribute("id", "0");
animation.setAttribute("name", "default");
animation.setAttribute("length", "1000");
animation.setAttribute("looping", "false");
// entity/animation/mainline
QDomElement mainline = scml.createElement("mainline");
animation.appendChild(mainline);
QDomElement key = scml.createElement("key");
mainline.appendChild(key);
key.setAttribute("id", "0");
m_timelineid = 0;
writeBoneRef(m_rootBone, key, scml);
Q_FOREACH(const SpriterObject &object, m_objects) {
QDomElement oe = scml.createElement("object_ref");
key.appendChild(oe);
oe.setAttribute("id", object.id);
if (object.bone) {
oe.setAttribute("parent", object.bone->id);
}
oe.setAttribute("timeline", m_timelineid++);
oe.setAttribute("key", "0");
oe.setAttribute("z_index", object.id);
}
// entity/animation/timeline
m_timelineid = 0;
if (m_rootBone) {
writeBone(m_rootBone, animation, scml);
}
Q_FOREACH(const SpriterObject &object, m_objects) {
Folder folder;
Q_FOREACH(const Folder & f, m_folders) {
if (f.id == object.folderId) {
folder = f;
break;
}
}
SpriterFile file;
file.id = -1;
Q_FOREACH(const SpriterFile &f, folder.files) {
if (f.id == object.fileId) {
file = f;
break;
}
}
Q_ASSERT(file.id >= 0);
QString objectName = "object-" + file.baseName;
QDomElement timeline = scml.createElement("timeline");
animation.appendChild(timeline);
timeline.setAttribute("id", m_timelineid++);
timeline.setAttribute("name", objectName);
QDomElement key = scml.createElement("key");
timeline.appendChild(key);
key.setAttribute("id", "0");
key.setAttribute("spin", "0");
QDomElement objectEl = scml.createElement("object");
key.appendChild(objectEl);
objectEl.setAttribute("folder", object.folderId);
objectEl.setAttribute("file", object.fileId);
objectEl.setAttribute("x", object.fixLocalX);
objectEl.setAttribute("y", object.fixLocalY);
objectEl.setAttribute("angle", QString::number(kisRadiansToDegrees(object.fixLocalAngle), 'f', 2));
objectEl.setAttribute("scale_x", QString::number(object.fixLocalScaleX, 'f', 2));
objectEl.setAttribute("scale_y", QString::number(object.fixLocalScaleY, 'f', 2));
}
}
Bone *findBoneByName(Bone *startBone, const QString &name)
{
if (!startBone) return 0;
//qDebug() << "findBoneByName" << name << "starting with" << startBone->name;
if (startBone->name == name) {
return startBone;
}
Q_FOREACH(Bone *child, startBone->bones) {
//qDebug() << "looking for" << name << "found" << child->name;
if (child->name == name) {
return child;
}
Bone *grandChild = findBoneByName(child, name);
if (grandChild){
return grandChild;
}
}
return 0;
}
-KisImportExportFilter::ConversionStatus KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
QFileInfo fi(filename());
m_image = document->savingImage();
if (m_image->rootLayer()->childCount() == 0) {
- return KisImportExportFilter::UsageError;
+ return ImportExportCodes::Failure;
}
KisGroupLayerSP root = m_image->rootLayer();
m_boneLayer = qobject_cast<KisLayer*>(root->findChildByName("bone").data());
//qDebug() << "Found boneLayer" << m_boneLayer;
m_rootLayer= qobject_cast<KisGroupLayer*>(root->findChildByName("root").data());
//qDebug() << "Fond rootLayer" << m_rootLayer;
- parseFolder(m_image->rootLayer(), "", fi.absolutePath());
+ KisImportExportErrorCode result = parseFolder(m_image->rootLayer(), "", fi.absolutePath());
+ if (!result.isOk()) {
+ dbgFile << "There were errors encountered while using the spriter exporter.";
+ return result;
+ }
m_rootBone = 0;
if (m_rootLayer) {
m_rootBone = parseBone(0, m_rootLayer);
}
// Generate objects
int objectId = 0;
for (int folderIndex = 0, folderCount = m_folders.size(); folderIndex < folderCount; ++folderIndex) {
Folder folder = m_folders[folderCount - 1 - folderIndex];
for (int fileIndex = 0, fileCount = folder.files.size(); fileIndex < fileCount; ++ fileIndex) {
SpriterFile file = folder.files[fileCount - 1 - fileIndex];
SpriterObject spriterObject;
spriterObject.id = objectId++;
spriterObject.folderId = folder.id;
spriterObject.fileId = file.id;
spriterObject.x = file.x;
spriterObject.y = -file.y;
Bone *bone = 0;
//qDebug() << "file layername" << file.layerName;
// layer.name format: "base_name bone(bone_name) slot(slot_name)"
if (file.layerName.contains("bone(")) {
int start = file.layerName.indexOf("bone(") + 5;
int end = file.layerName.indexOf(')', start);
QString boneName = file.layerName.mid(start, end - start);
bone = findBoneByName(m_rootBone, boneName);
}
// layer.name format: "base_name"
if (!bone && m_rootBone) {
bone = findBoneByName(m_rootBone, file.layerName);
}
// group.name format: "base_name bone(bone_name)"
if (!bone && m_rootBone) {
if (folder.groupName.contains("bone(")) {
int start = folder.groupName.indexOf("bone(") + 5;
int end = folder.groupName.indexOf(')', start);
QString boneName = folder.groupName.mid(start, end - start);
bone = findBoneByName(m_rootBone, boneName);
}
// group.name format: "base_name"
if (!bone) {
bone = findBoneByName(m_rootBone, folder.groupName);
}
}
if (!bone) {
bone = m_rootBone;
}
if (bone) {
spriterObject.bone = bone;
spriterObject.localX = spriterObject.x - bone->x;
spriterObject.localY = spriterObject.y - bone->y;
}
else {
spriterObject.bone = 0;
spriterObject.localX = spriterObject.x;
spriterObject.localY = spriterObject.y;
}
spriterObject.localAngle = 0;
spriterObject.localScaleX = 1.0;
spriterObject.localScaleY = 1.0;
SpriterSlot *slot = 0;
// layer.name format: "base_name bone(bone_name) slot(slot_name)"
if (file.layerName.contains("slot(")) {
int start = file.layerName.indexOf("slot(") + 5;
int end = file.layerName.indexOf(')', start);
slot = new SpriterSlot();
slot->name = file.layerName.mid(start, end - start);
slot->defaultAttachmentFlag = file.layerName.contains("*");
}
spriterObject.slot = slot;
// qDebug() << "Created object" << spriterObject.id << spriterObject.folderId
// << spriterObject.fileId << spriterObject.x << spriterObject.y
// << spriterObject.localX << spriterObject.localY;
m_objects.append(spriterObject);
}
}
// Copy object transforms
for (int i = 0; i < m_objects.size(); ++i) {
m_objects[i].fixLocalX = m_objects[i].localX;
m_objects[i].fixLocalY = m_objects[i].localY;
m_objects[i].fixLocalAngle = m_objects[i].localAngle;
m_objects[i].fixLocalScaleX = m_objects[i].localScaleX;
m_objects[i].fixLocalScaleY = m_objects[i].localScaleY;
}
// Calculate bone angles
if (m_rootBone) {
copyBone(m_rootBone);
fixBone(m_rootBone);
}
// Generate scml
QDomDocument scml;
fillScml(scml, fi.baseName());
- io->write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- io->write(scml.toString(4).toUtf8());
+ bool openedHere = false;
+ if (!io->isOpen()) {
+ openedHere = io->open(QIODevice::WriteOnly);
+ if (!openedHere) {
+ // unsuccessful open
+ return ImportExportCodes::NoAccessToWrite;
+ }
+ }
+
+ QString towrite = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ if (io->write(towrite.toUtf8()) != towrite.length()) {
+ return ImportExportCodes::ErrorWhileWriting;
+ }
+ towrite = scml.toString(4).toUtf8();
+ if (io->write(towrite.toUtf8()) != towrite.length()) {
+ return ImportExportCodes::ErrorWhileWriting;
+ }
delete m_rootBone;
- return KisImportExportFilter::OK;
+ if (openedHere) {
+ // FIXME: casues crash...
+ //io->close();
+ }
+
+ return ImportExportCodes::OK;
}
void KisSpriterExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "Spriter");
}
#include "kis_spriter_export.moc"
diff --git a/plugins/impex/spriter/kis_spriter_export.h b/plugins/impex/spriter/kis_spriter_export.h
index 5d3c32c3b3..30e123ac76 100644
--- a/plugins/impex/spriter/kis_spriter_export.h
+++ b/plugins/impex/spriter/kis_spriter_export.h
@@ -1,135 +1,135 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_SPRITER_EXPORT_H_
#define _KIS_SPRITER_EXPORT_H_
#include <QVariant>
#include <QDomDocument>
#include <QList>
#include <KisImportExportFilter.h>
#include <kis_types.h>
struct SpriterFile {
qreal id;
QString name;
QString pathName;
QString baseName;
QString layerName;
qreal width;
qreal height;
qreal x;
qreal y;
};
struct Folder {
qreal id;
QString name;
QString pathName;
QString baseName;
QString groupName;
QList<SpriterFile> files;
};
struct Bone {
qreal id;
const Bone *parentBone;
QString name;
qreal x;
qreal y;
qreal width;
qreal height;
qreal localX;
qreal localY;
qreal localAngle;
qreal localScaleX;
qreal localScaleY;
qreal fixLocalX;
qreal fixLocalY;
qreal fixLocalAngle;
qreal fixLocalScaleX;
qreal fixLocalScaleY;
QList<Bone*> bones;
~Bone() {
qDeleteAll(bones);
bones.clear();
}
};
struct SpriterSlot {
QString name;
bool defaultAttachmentFlag = false;
};
struct SpriterObject {
qreal id;
qreal folderId;
qreal fileId;
Bone *bone;
SpriterSlot *slot;
qreal x;
qreal y;
qreal localX;
qreal localY;
qreal localAngle;
qreal localScaleX;
qreal localScaleY;
qreal fixLocalX;
qreal fixLocalY;
qreal fixLocalAngle;
qreal fixLocalScaleX;
qreal fixLocalScaleY;
~SpriterObject() {
delete slot;
}
};
class KisSpriterExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisSpriterExport(QObject *parent, const QVariantList &);
~KisSpriterExport() override;
bool supportsIO() const override { return false; }
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
private:
- bool savePaintDevice(KisPaintDeviceSP dev, const QString &fileName);
- void parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0);
+ KisImportExportErrorCode savePaintDevice(KisPaintDeviceSP dev, const QString &fileName);
+ KisImportExportErrorCode parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0);
Bone *parseBone(const Bone *parent, KisGroupLayerSP groupLayer);
void fixBone(Bone *bone);
void fillScml(QDomDocument &scml, const QString &entityName);
void writeBoneRef(const Bone *bone, QDomElement &mainline, QDomDocument &scml);
void writeBone(const Bone *bone, QDomElement &timeline, QDomDocument &scml);
KisImageSP m_image;
qreal m_timelineid;
QList<Folder> m_folders;
Bone *m_rootBone;
QList<SpriterObject> m_objects;
KisGroupLayerSP m_rootLayer; // Not the image's root later, but the one that is named "root"
KisLayerSP m_boneLayer;
};
#endif
diff --git a/plugins/impex/svg/kis_svg_import.cc b/plugins/impex/svg/kis_svg_import.cc
index 86c8ae1cca..bf41af28ca 100644
--- a/plugins/impex/svg/kis_svg_import.cc
+++ b/plugins/impex/svg/kis_svg_import.cc
@@ -1,103 +1,103 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_svg_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include "kis_config.h"
#include <QInputDialog>
#include <KisDocument.h>
#include <kis_image.h>
#include <SvgParser.h>
#include <KoColorSpaceRegistry.h>
#include "kis_shape_layer.h"
#include <KoShapeControllerBase.h>
K_PLUGIN_FACTORY_WITH_JSON(SVGImportFactory, "krita_svg_import.json", registerPlugin<KisSVGImport>();)
KisSVGImport::KisSVGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisSVGImport::~KisSVGImport()
{
}
-KisImportExportFilter::ConversionStatus KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
Q_UNUSED(configuration);
KisDocument * doc = document;
const QString baseXmlDir = QFileInfo(filename()).canonicalPath();
KisConfig cfg(false);
qreal resolutionPPI = cfg.preferredVectorImportResolutionPPI(true);
if (!batchMode()) {
bool okay = false;
const QString name = QFileInfo(filename()).fileName();
resolutionPPI = QInputDialog::getInt(0,
i18n("Import SVG"),
i18n("Enter preferred resolution (PPI) for \"%1\"", name),
cfg.preferredVectorImportResolutionPPI(),
0, 100000, 1, &okay);
if (!okay) {
- return KisImportExportFilter::UserCancelled;
+ return ImportExportCodes::Cancelled;
}
cfg.setPreferredVectorImportResolutionPPI(resolutionPPI);
}
const qreal resolution = resolutionPPI / 72.0;
QSizeF fragmentSize;
QList<KoShape*> shapes =
KisShapeLayer::createShapesFromSvg(io, baseXmlDir,
QRectF(0,0,1200,800), resolutionPPI,
doc->shapeController()->resourceManager(),
&fragmentSize);
QRectF rawImageRect(QPointF(), fragmentSize);
QRect imageRect(rawImageRect.toAlignedRect());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(doc->createUndoStore(), imageRect.width(), imageRect.height(), cs, "svg image");
image->setResolution(resolution, resolution);
doc->setCurrentImage(image);
KisShapeLayerSP shapeLayer =
new KisShapeLayer(doc->shapeController(), image,
i18n("Vector Layer"),
OPACITY_OPAQUE_U8);
Q_FOREACH (KoShape *shape, shapes) {
shapeLayer->addShape(shape);
}
image->addNode(shapeLayer);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include <kis_svg_import.moc>
diff --git a/plugins/impex/svg/kis_svg_import.h b/plugins/impex/svg/kis_svg_import.h
index a4d185566e..31e725a151 100644
--- a/plugins/impex/svg/kis_svg_import.h
+++ b/plugins/impex/svg/kis_svg_import.h
@@ -1,36 +1,36 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_SVG_IMPORT_H_
#define _KIS_SVG_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisSVGImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisSVGImport(QObject *parent, const QVariantList &);
~KisSVGImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) override;
};
#endif
diff --git a/plugins/impex/svg/tests/data/incorrectFormatFile.txt b/plugins/impex/svg/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/svg/tests/data/readonlyFile.txt b/plugins/impex/svg/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/svg/tests/data/writeonlyFile.txt b/plugins/impex/svg/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/svg/tests/kis_svg_test.cpp
index 6ca66688f8..b407b77a44 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/svg/tests/kis_svg_test.cpp
@@ -1,40 +1,57 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_svg_test.h"
#include <QTest>
#include <QCoreApplication>
#include <sdk/tests/kistest.h>
#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 SvgMimetype = "image/svg+xml";
+
+
void KisSvgTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisSvgTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), SvgMimetype);
+}
+
+
+
+void KisSvgTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), SvgMimetype);
+}
+
+
+KISTEST_MAIN(KisSvgTest)
diff --git a/plugins/impex/svg/tests/kis_svg_test.h b/plugins/impex/svg/tests/kis_svg_test.h
index 66cf670f4d..e8e481ded8 100644
--- a/plugins/impex/svg/tests/kis_svg_test.h
+++ b/plugins/impex/svg/tests/kis_svg_test.h
@@ -1,31 +1,34 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_SVG_TEST_H_
#define _KIS_SVG_TEST_H_
#include <QtTest>
class KisSvgTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+
+ void testImportFromWriteonly();
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/impex/tga/CMakeLists.txt b/plugins/impex/tga/CMakeLists.txt
index a9546c3326..b7fe804897 100644
--- a/plugins/impex/tga/CMakeLists.txt
+++ b/plugins/impex/tga/CMakeLists.txt
@@ -1,24 +1,26 @@
+add_subdirectory(tests)
+
set(kritatgaexport_SOURCES
kis_tga_export.cpp
)
ki18n_wrap_ui(kritatgaexport_SOURCES )
add_library(kritatgaexport MODULE ${kritatgaexport_SOURCES})
target_link_libraries(kritatgaexport kritaui kritaimpex)
install(TARGETS kritatgaexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
set(kritatgaimport_SOURCES
kis_tga_import.cpp
)
ki18n_wrap_ui(kritatgaimport_SOURCES )
add_library(kritatgaimport MODULE ${kritatgaimport_SOURCES})
target_link_libraries(kritatgaimport kritaui)
install(TARGETS kritatgaimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( PROGRAMS krita_tga.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/tga/kis_tga_export.cpp b/plugins/impex/tga/kis_tga_export.cpp
index ac79779c1f..3d8728e4f3 100644
--- a/plugins/impex/tga/kis_tga_export.cpp
+++ b/plugins/impex/tga/kis_tga_export.cpp
@@ -1,94 +1,94 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_tga_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <QApplication>
#include <KoColorModelStandardIds.h>
#include <KisExportCheckRegistry.h>
#include <KisImportExportManager.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include "tga.h"
K_PLUGIN_FACTORY_WITH_JSON(KisTGAExportFactory, "krita_tga_export.json", registerPlugin<KisTGAExport>();)
KisTGAExport::KisTGAExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisTGAExport::~KisTGAExport()
{
}
-KisImportExportFilter::ConversionStatus KisTGAExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisTGAExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
Q_UNUSED(configuration);
QRect rc = document->savingImage()->bounds();
QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
QDataStream s(io);
s.setByteOrder(QDataStream::LittleEndian);
const QImage& img = image;
const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
for (int i = 0; i < 12; i++)
s << targaMagic[i];
// write header
s << quint16(img.width()); // width
s << quint16(img.height()); // height
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4)
for (int y = 0; y < img.height(); y++) {
for (int x = 0; x < img.width(); x++) {
const QRgb color = img.pixel(x, y);
s << quint8(qBlue(color));
s << quint8(qGreen(color));
s << quint8(qRed(color));
if (hasAlpha)
s << quint8(qAlpha(color));
}
}
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
void KisTGAExport::initializeCapabilities()
{
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "TGA");
}
#include "kis_tga_export.moc"
diff --git a/plugins/impex/tga/kis_tga_export.h b/plugins/impex/tga/kis_tga_export.h
index a10414659e..8faa01d65e 100644
--- a/plugins/impex/tga/kis_tga_export.h
+++ b/plugins/impex/tga/kis_tga_export.h
@@ -1,38 +1,38 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_TGA_EXPORT_H_
#define _KIS_TGA_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisTGAExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTGAExport(QObject *parent, const QVariantList &);
~KisTGAExport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/tga/kis_tga_import.cpp b/plugins/impex/tga/kis_tga_import.cpp
index e78c4590b4..860f75237d 100644
--- a/plugins/impex/tga/kis_tga_import.cpp
+++ b/plugins/impex/tga/kis_tga_import.cpp
@@ -1,291 +1,291 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_tga_import.h"
#include <QCheckBox>
#include <QBuffer>
#include <QSlider>
#include <QApplication>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <kis_transaction.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <tga.h>
K_PLUGIN_FACTORY_WITH_JSON(KisTGAImportFactory, "krita_tga_import.json", registerPlugin<KisTGAImport>();)
KisTGAImport::KisTGAImport(QObject *parent, const QVariantList &)
: KisImportExportFilter(parent)
{
}
KisTGAImport::~KisTGAImport()
{
}
static QDataStream & operator>> (QDataStream & s, TgaHeader & head)
{
s >> head.id_length;
s >> head.colormap_type;
s >> head.image_type;
s >> head.colormap_index;
s >> head.colormap_length;
s >> head.colormap_size;
s >> head.x_origin;
s >> head.y_origin;
s >> head.width;
s >> head.height;
s >> head.pixel_size;
s >> head.flags;
/*dbgKrita << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type;
dbgKrita << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size;
dbgKrita << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: " << head.pixel_size << " - flags: " << head.flags;*/
return s;
}
static bool isSupported(const TgaHeader & head)
{
if (head.image_type != TGA_TYPE_INDEXED &&
head.image_type != TGA_TYPE_RGB &&
head.image_type != TGA_TYPE_GREY &&
head.image_type != TGA_TYPE_RLE_INDEXED &&
head.image_type != TGA_TYPE_RLE_RGB &&
head.image_type != TGA_TYPE_RLE_GREY) {
return false;
}
if (head.image_type == TGA_TYPE_INDEXED ||
head.image_type == TGA_TYPE_RLE_INDEXED) {
if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) {
return false;
}
}
if (head.image_type == TGA_TYPE_RGB ||
head.image_type == TGA_TYPE_GREY ||
head.image_type == TGA_TYPE_RLE_RGB ||
head.image_type == TGA_TYPE_RLE_GREY) {
if (head.colormap_type != 0) {
return false;
}
}
if (head.width == 0 || head.height == 0) {
return false;
}
if (head.pixel_size != 8 && head.pixel_size != 16 &&
head.pixel_size != 24 && head.pixel_size != 32) {
return false;
}
return true;
}
static bool loadTGA(QDataStream & s, const TgaHeader & tga, QImage &img)
{
// Create image.
img = QImage(tga.width, tga.height, QImage::Format_RGB32);
TgaHeaderInfo info(tga);
/**
* Theoretically, we should check alpha presence via the bits
* in flags, but there are a lot of files in the wild that
* have this flag unset. It contradicts TGA specification,
* but we cannot do anything about it.
*/
const bool hasAlpha = tga.flags & 0xf;
if (tga.pixel_size == 32 && !hasAlpha) {
qWarning() << "WARNING: TGA image with 32-bit pixel size reports absence of alpha channel. It is not possible, fixing...";
}
if (tga.pixel_size == 32 || tga.pixel_size == 16) {
img = QImage(tga.width, tga.height, QImage::Format_ARGB32);
}
uint pixel_size = (tga.pixel_size / 8);
uint size = tga.width * tga.height * pixel_size;
if (size < 1) {
dbgFile << "This TGA file is broken with size " << size;
return false;
}
// Read palette.
char palette[768];
if (info.pal) {
// @todo Support palettes in other formats!
s.readRawData(palette, 3 * tga.colormap_length);
}
// Allocate image.
uchar * const image = new uchar[size];
if (info.rle) {
// Decode image.
char * dst = (char *)image;
int num = size;
while (num > 0) {
// Get packet header.
uchar c;
s >> c;
uint count = (c & 0x7f) + 1;
num -= count * pixel_size;
if (c & 0x80) {
// RLE pixels.
Q_ASSERT(pixel_size <= 8);
char pixel[8];
s.readRawData(pixel, pixel_size);
do {
memcpy(dst, pixel, pixel_size);
dst += pixel_size;
} while (--count);
} else {
// Raw pixels.
count *= pixel_size;
s.readRawData(dst, count);
dst += count;
}
}
} else {
// Read raw image.
s.readRawData((char *)image, size);
}
// Convert image to internal format.
int y_start, y_step, y_end;
if (tga.flags & TGA_ORIGIN_UPPER) {
y_start = 0;
y_step = 1;
y_end = tga.height;
} else {
y_start = tga.height - 1;
y_step = -1;
y_end = -1;
}
uchar* src = image;
for (int y = y_start; y != y_end; y += y_step) {
QRgb * scanline = (QRgb *) img.scanLine(y);
if (info.pal) {
// Paletted.
for (int x = 0; x < tga.width; x++) {
uchar idx = *src++;
scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]);
}
} else if (info.grey) {
// Greyscale.
for (int x = 0; x < tga.width; x++) {
scanline[x] = qRgb(*src, *src, *src);
src++;
}
} else {
// True Color.
if (tga.pixel_size == 16) {
for (int x = 0; x < tga.width; x++) {
Color555 c = *reinterpret_cast<Color555 *>(src);
scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2));
src += 2;
}
} else if (tga.pixel_size == 24) {
for (int x = 0; x < tga.width; x++) {
scanline[x] = qRgb(src[2], src[1], src[0]);
src += 3;
}
} else if (tga.pixel_size == 32) {
for (int x = 0; x < tga.width; x++) {
const uchar alpha = src[3];
scanline[x] = qRgba(src[2], src[1], src[0], alpha);
src += 4;
}
}
}
}
// Free image.
delete []image;
return true;
}
-KisImportExportFilter::ConversionStatus KisTGAImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisTGAImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
Q_UNUSED(configuration);
QDataStream s(io);
s.setByteOrder(QDataStream::LittleEndian);
TgaHeader tga;
s >> tga;
s.device()->seek(TgaHeader::SIZE + tga.id_length);
// Check image file format.
if (s.atEnd()) {
- return KisImportExportFilter::InvalidFormat;
+ return ImportExportCodes::FileFormatIncorrect;
}
// Check supported file types.
if (!isSupported(tga)) {
- return KisImportExportFilter::InvalidFormat;
+ return ImportExportCodes::FileFormatIncorrect;
}
QImage img;
bool result = loadTGA(s, tga, img);
if (result == false) {
- return KisImportExportFilter::CreationError;
+ return ImportExportCodes::FileFormatIncorrect;
}
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, "imported from tga");
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255);
layer->paintDevice()->convertFromQImage(img, 0, 0, 0);
image->addNode(layer.data(), image->rootLayer().data());
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_tga_import.moc"
diff --git a/plugins/impex/tga/kis_tga_import.h b/plugins/impex/tga/kis_tga_import.h
index d8ea64383d..a3869bd16d 100644
--- a/plugins/impex/tga/kis_tga_import.h
+++ b/plugins/impex/tga/kis_tga_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_TGA_IMPORT_H_
#define _KIS_TGA_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisTGAImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTGAImport(QObject *parent, const QVariantList &);
~KisTGAImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/tga/tests/CMakeLists.txt b/plugins/impex/tga/tests/CMakeLists.txt
new file mode 100644
index 0000000000..7180181703
--- /dev/null
+++ b/plugins/impex/tga/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(KisTgaTest.cpp
+ TEST_NAME KisTgaTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/tga/tests/KisTgaTest.cpp
similarity index 64%
copy from plugins/impex/svg/tests/kis_svg_test.cpp
copy to plugins/impex/tga/tests/KisTgaTest.cpp
index 6ca66688f8..b3b83d1c05 100644
--- a/plugins/impex/svg/tests/kis_svg_test.cpp
+++ b/plugins/impex/tga/tests/KisTgaTest.cpp
@@ -1,40 +1,57 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_svg_test.h"
+#include "KisTgaTest.h"
#include <QTest>
#include <QCoreApplication>
-#include <sdk/tests/kistest.h>
-
#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
-void KisSvgTest::testFiles()
+const QString TgaMimetype = "image/x-gimp-brush";
+
+
+
+void KisTgaTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TgaMimetype);
+}
+
+
+void KisTgaTest::testExportToReadonly()
{
- TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50);
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TgaMimetype);
}
-KISTEST_MAIN(KisSvgTest)
+
+void KisTgaTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TgaMimetype);
+}
+
+
+
+KISTEST_MAIN(KisTgaTest)
+
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/tga/tests/KisTgaTest.h
similarity index 74%
copy from plugins/impex/exr/tests/kis_exr_test.h
copy to plugins/impex/tga/tests/KisTgaTest.h
index 4dad62dfca..172bf40374 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/tga/tests/KisTgaTest.h
@@ -1,32 +1,35 @@
/*
- * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ * Copyright (C) 2019 Agata Cacko <cacko.azh@gmail.com>
*
* 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_EXR_TEST_H_
-#define _KIS_EXR_TEST_H_
+#ifndef _KIS_TGA_TEST_H_
+#define _KIS_TGA_TEST_H_
#include <QtTest>
-class KisExrTest : public QObject
+class KisTgaTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
- void testFiles();
- void testRoundTrip();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
-#endif
+#endif // _KIS_TGA_TEST_H_
+
diff --git a/plugins/impex/tga/tests/data/incorrectFormatFile.txt b/plugins/impex/tga/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/tga/tests/data/readonlyFile.txt b/plugins/impex/tga/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/tga/tests/data/writeonlyFile.txt b/plugins/impex/tga/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/tiff/kis_tiff_converter.cc b/plugins/impex/tiff/kis_tiff_converter.cc
index dc873404ab..b68057ec87 100644
--- a/plugins/impex/tiff/kis_tiff_converter.cc
+++ b/plugins/impex/tiff/kis_tiff_converter.cc
@@ -1,747 +1,812 @@
/*
* Copyright (c) 2005-2006 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_tiff_converter.h"
#include <stdio.h>
#include <QFile>
#include <QApplication>
#include <QFileInfo>
#include <KoDocumentInfo.h>
#include <KoUnit.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include <KoColorModelStandardIds.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <KoColorProfile.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_transaction.h>
#include "kis_tiff_reader.h"
#include "kis_tiff_ycbcr_reader.h"
#include "kis_buffer_stream.h"
#include "kis_tiff_writer_visitor.h"
+#include <KisImportExportAdditionalChecks.h>
+
#if TIFFLIB_VERSION < 20111221
typedef size_t tmsize_t;
#endif
namespace
{
QPair<QString, QString> getColorSpaceForColorType(uint16 sampletype, uint16 color_type, uint16 color_nb_bits, TIFF *image, uint16 &nbchannels, uint16 &extrasamplescount, uint8 &destDepth)
{
+ const int bits32 = 32;
+ const int bits16 = 16;
+ const int bits8 = 8;
+
if (color_type == PHOTOMETRIC_MINISWHITE || color_type == PHOTOMETRIC_MINISBLACK) {
if (nbchannels == 0) nbchannels = 1;
extrasamplescount = nbchannels - 1; // FIX the extrasamples count in case of
if (sampletype == SAMPLEFORMAT_IEEEFP) {
if (color_nb_bits == 16) {
destDepth = 16;
return QPair<QString, QString>(GrayAColorModelID.id(), Float16BitsColorDepthID.id());
}
else if (color_nb_bits == 32) {
destDepth = 32;
return QPair<QString, QString>(GrayAColorModelID.id(), Float32BitsColorDepthID.id());
}
}
if (color_nb_bits <= 8) {
destDepth = 8;
return QPair<QString, QString>(GrayAColorModelID.id(), Integer8BitsColorDepthID.id());
}
- else {
+ else /* if (color_nb_bits == bits16) */ {
destDepth = 16;
return QPair<QString, QString>(GrayAColorModelID.id(), Integer16BitsColorDepthID.id());
}
} else if (color_type == PHOTOMETRIC_RGB /*|| color_type == */) {
if (nbchannels == 0) nbchannels = 3;
extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of
if (sampletype == SAMPLEFORMAT_IEEEFP) {
if (color_nb_bits == 16) {
destDepth = 16;
return QPair<QString, QString>(RGBAColorModelID.id(), Float16BitsColorDepthID.id());
}
else if (color_nb_bits == 32) {
destDepth = 32;
return QPair<QString, QString>(RGBAColorModelID.id(), Float32BitsColorDepthID.id());
}
- return QPair<QString, QString>();
+ return QPair<QString, QString>(); // sanity check; no support for float of higher or lower bit depth
}
else {
if (color_nb_bits <= 8) {
destDepth = 8;
return QPair<QString, QString>(RGBAColorModelID.id(), Integer8BitsColorDepthID.id());
- }
- else {
+ } else /* if (color_nb_bits == bits16) */ {
destDepth = 16;
return QPair<QString, QString>(RGBAColorModelID.id(), Integer16BitsColorDepthID.id());
}
}
} else if (color_type == PHOTOMETRIC_YCBCR) {
if (nbchannels == 0) nbchannels = 3;
extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of
+ if (sampletype == SAMPLEFORMAT_IEEEFP) {
+ return QPair<QString, QString>(); // sanity check; no support for float
+ }
if (color_nb_bits <= 8) {
destDepth = 8;
return QPair<QString, QString>(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id());
}
- else {
+ else if (color_nb_bits == bits16) {
destDepth = 16;
return QPair<QString, QString>(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id());
+ } else {
+ return QPair<QString, QString>(); // sanity check; no support integers of higher bit depth
}
}
else if (color_type == PHOTOMETRIC_SEPARATED) {
if (nbchannels == 0) nbchannels = 4;
// SEPARATED is in general CMYK but not always, so we check
uint16 inkset;
if ((TIFFGetField(image, TIFFTAG_INKSET, &inkset) == 0)) {
dbgFile << "Image does not define the inkset.";
inkset = 2;
}
if (inkset != INKSET_CMYK) {
dbgFile << "Unsupported inkset (right now, only CMYK is supported)";
char** ink_names;
uint16 numberofinks;
if (TIFFGetField(image, TIFFTAG_INKNAMES, &ink_names) == 1 && TIFFGetField(image, TIFFTAG_NUMBEROFINKS, &numberofinks) == 1) {
dbgFile << "Inks are :";
for (uint i = 0; i < numberofinks; i++) {
dbgFile << ink_names[i];
}
}
else {
dbgFile << "inknames are not defined !";
// To be able to read stupid adobe files, if there are no information about inks and four channels, then it's a CMYK file :
if (nbchannels - extrasamplescount != 4) {
return QPair<QString, QString>();
}
+ // else - assume it's CMYK and proceed
}
}
if (color_nb_bits <= 8) {
destDepth = 8;
return QPair<QString, QString>(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id());
- }
- else {
+ } else if (color_nb_bits == 16) {
destDepth = 16;
return QPair<QString, QString>(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id());
+ } else if (sampletype == SAMPLEFORMAT_IEEEFP) {
+ destDepth = bits32;
+ return QPair<QString, QString>(CMYKAColorModelID.id(), Float32BitsColorDepthID.id());
+ } else {
+ return QPair<QString, QString>(); // no support for other bit depths
}
}
else if (color_type == PHOTOMETRIC_CIELAB || color_type == PHOTOMETRIC_ICCLAB) {
- destDepth = 16;
if (nbchannels == 0) nbchannels = 3;
extrasamplescount = nbchannels - 3; // FIX the extrasamples count
- return QPair<QString, QString>(LABAColorModelID.id(), Integer16BitsColorDepthID.id());
+
+ switch(color_nb_bits) {
+ case bits32: {
+ destDepth = bits32;
+ return QPair<QString, QString>(LABAColorModelID.id(), Float32BitsColorDepthID.id());
+ }
+ case bits16: {
+ destDepth = bits16;
+ if (sampletype == SAMPLEFORMAT_IEEEFP) {
+ return QPair<QString, QString>(LABAColorModelID.id(), Float16BitsColorDepthID.id());
+ }
+ else {
+ return QPair<QString, QString>(LABAColorModelID.id(), Integer16BitsColorDepthID.id());
+ }
+ }
+ case bits8: {
+ destDepth = bits8;
+ return QPair<QString, QString>(LABAColorModelID.id(), Integer8BitsColorDepthID.id());
+ }
+ default: {
+ return QPair<QString, QString>();
+ }
+ }
}
else if (color_type == PHOTOMETRIC_PALETTE) {
destDepth = 16;
if (nbchannels == 0) nbchannels = 2;
extrasamplescount = nbchannels - 2; // FIX the extrasamples count
// <-- we will convert the index image to RGBA16 as the palette is always on 16bits colors
return QPair<QString, QString>(RGBAColorModelID.id(), Integer16BitsColorDepthID.id());
}
return QPair<QString, QString>();
}
}
KisPropertiesConfigurationSP KisTIFFOptions::toProperties() const
{
QHash<int, int> compToIndex;
compToIndex[COMPRESSION_NONE] = 0;
compToIndex[COMPRESSION_JPEG] = 1;
compToIndex[COMPRESSION_DEFLATE] = 2;
compToIndex[COMPRESSION_LZW] = 3;
compToIndex[COMPRESSION_PIXARLOG] = 8;
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("compressiontype", compToIndex.value(compressionType, 0));
cfg->setProperty("predictor", predictor - 1);
cfg->setProperty("alpha", alpha);
cfg->setProperty("flatten", flatten);
cfg->setProperty("quality", jpegQuality);
cfg->setProperty("deflate", deflateCompress);
cfg->setProperty("pixarlog", pixarLogCompress);
cfg->setProperty("saveProfile", saveProfile);
return cfg;
}
void KisTIFFOptions::fromProperties(KisPropertiesConfigurationSP cfg)
{
QHash<int, int> indexToComp;
indexToComp[0] = COMPRESSION_NONE;
indexToComp[1] = COMPRESSION_JPEG;
indexToComp[2] = COMPRESSION_DEFLATE;
indexToComp[3] = COMPRESSION_LZW;
indexToComp[4] = COMPRESSION_PIXARLOG;
// old value that might be still stored in a config (remove after Krita 5.0 :) )
indexToComp[8] = COMPRESSION_PIXARLOG;
compressionType =
indexToComp.value(
cfg->getInt("compressiontype", 0),
COMPRESSION_NONE);
predictor = cfg->getInt("predictor", 0) + 1;
alpha = cfg->getBool("alpha", true);
flatten = cfg->getBool("flatten", true);
jpegQuality = cfg->getInt("quality", 80);
deflateCompress = cfg->getInt("deflate", 6);
pixarLogCompress = cfg->getInt("pixarlog", 6);
saveProfile = cfg->getBool("saveProfile", true);
}
KisTIFFConverter::KisTIFFConverter(KisDocument *doc)
{
m_doc = doc;
m_stop = false;
TIFFSetWarningHandler(0);
TIFFSetErrorHandler(0);
}
KisTIFFConverter::~KisTIFFConverter()
{
}
-KisImageBuilder_Result KisTIFFConverter::decode(const QString &filename)
+KisImportExportErrorCode KisTIFFConverter::decode(const QString &filename)
{
dbgFile << "Start decoding TIFF File";
// Opent the TIFF file
TIFF *image = 0;
+
+ if (!KisImportExportAdditionalChecks::doesFileExist(filename)) {
+ return ImportExportCodes::FileNotExist;
+ }
+ if (!KisImportExportAdditionalChecks::isFileReadable(filename)) {
+ return ImportExportCodes::NoAccessToRead;
+ }
+
if ((image = TIFFOpen(QFile::encodeName(filename), "r")) == 0) {
dbgFile << "Could not open the file, either it does not exist, either it is not a TIFF :" << filename;
- return (KisImageBuilder_RESULT_BAD_FETCH);
+ return (ImportExportCodes::FileFormatIncorrect);
}
do {
dbgFile << "Read new sub-image";
- KisImageBuilder_Result result = readTIFFDirectory(image);
- if (result != KisImageBuilder_RESULT_OK) {
+ KisImportExportErrorCode result = readTIFFDirectory(image);
+ if (!result.isOk()) {
return result;
}
} while (TIFFReadDirectory(image));
// Freeing memory
TIFFClose(image);
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
-KisImageBuilder_Result KisTIFFConverter::readTIFFDirectory(TIFF* image)
+KisImportExportErrorCode KisTIFFConverter::readTIFFDirectory(TIFF* image)
{
// Read information about the tiff
uint32 width, height;
if (TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0) {
dbgFile << "Image does not define its width";
TIFFClose(image);
- return KisImageBuilder_RESULT_INVALID_ARG;
+ return ImportExportCodes::FileFormatIncorrect;
}
if (TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height) == 0) {
dbgFile << "Image does not define its height";
TIFFClose(image);
- return KisImageBuilder_RESULT_INVALID_ARG;
+ return ImportExportCodes::FileFormatIncorrect;
}
float xres;
if (TIFFGetField(image, TIFFTAG_XRESOLUTION, &xres) == 0) {
dbgFile << "Image does not define x resolution";
// but we don't stop
xres = 100;
}
float yres;
if (TIFFGetField(image, TIFFTAG_YRESOLUTION, &yres) == 0) {
dbgFile << "Image does not define y resolution";
// but we don't stop
yres = 100;
}
uint16 depth;
if ((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &depth) == 0)) {
dbgFile << "Image does not define its depth";
depth = 1;
}
uint16 sampletype;
if ((TIFFGetField(image, TIFFTAG_SAMPLEFORMAT, &sampletype) == 0)) {
dbgFile << "Image does not define its sample type";
sampletype = SAMPLEFORMAT_UINT;
}
// Determine the number of channels (useful to know if a file has an alpha or not
uint16 nbchannels;
if (TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &nbchannels) == 0) {
dbgFile << "Image has an undefined number of samples per pixel";
nbchannels = 0;
}
// Get the number of extrasamples and information about them
uint16 *sampleinfo = 0, extrasamplescount;
if (TIFFGetField(image, TIFFTAG_EXTRASAMPLES, &extrasamplescount, &sampleinfo) == 0) {
extrasamplescount = 0;
}
// Determine the colorspace
uint16 color_type;
if (TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &color_type) == 0) {
dbgFile << "Image has an undefined photometric interpretation";
color_type = PHOTOMETRIC_MINISWHITE;
}
uint8 dstDepth = 0;
QPair<QString, QString> colorSpaceIdTag = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth);
if (colorSpaceIdTag.first.isEmpty()) {
dbgFile << "Image has an unsupported colorspace :" << color_type << " for this depth :" << depth;
TIFFClose(image);
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
dbgFile << "Colorspace is :" << colorSpaceIdTag.first << colorSpaceIdTag.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels;
// Read image profile
dbgFile << "Reading profile";
const KoColorProfile* profile = 0;
quint32 EmbedLen;
quint8* EmbedBuffer;
if (TIFFGetField(image, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer) == 1) {
dbgFile << "Profile found";
QByteArray rawdata;
rawdata.resize(EmbedLen);
memcpy(rawdata.data(), EmbedBuffer, EmbedLen);
profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceIdTag.first, colorSpaceIdTag.second, rawdata);
}
const QString colorSpaceId =
KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceIdTag.first, colorSpaceIdTag.second);
// Check that the profile is used by the color space
if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) {
dbgFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceIdTag.first << " " << colorSpaceIdTag.second;
profile = 0;
}
// Do not use the linear gamma profile for 16 bits/channel by default, tiff files are usually created with
// gamma correction. XXX: Should we ask the user?
if (!profile) {
+ dbgFile << "No profile found; trying to assign a default one.";
if (colorSpaceIdTag.first == RGBAColorModelID.id()) {
profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc");
} else if (colorSpaceIdTag.first == GrayAColorModelID.id()) {
profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc");
+ } else if (colorSpaceIdTag.first == CMYKAColorModelID.id()) {
+ profile = KoColorSpaceRegistry::instance()->profileByName("Chemical proof");
+ } else if (colorSpaceIdTag.first == LABAColorModelID.id()) {
+ profile = KoColorSpaceRegistry::instance()->profileByName("Lab identity build-in");
+ }
+ if (!profile) {
+ dbgFile << "No suitable default profile found.";
}
}
// Retrieve a pointer to the colorspace
const KoColorSpace* cs = 0;
if (profile && profile->isSuitableForOutput()) {
dbgFile << "image has embedded profile:" << profile -> name() << "";
cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile);
}
else {
cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, 0);
}
if (cs == 0) {
dbgFile << "Colorspace" << colorSpaceIdTag.first << colorSpaceIdTag.second << " is not available, please check your installation.";
TIFFClose(image);
- return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
+ return ImportExportCodes::FormatColorSpaceUnsupported;
}
// Create the cmsTransform if needed
KoColorTransformation* transform = 0;
if (profile && !profile->isSuitableForOutput()) {
dbgFile << "The profile can't be used in krita, need conversion";
transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
// Check if there is an alpha channel
int8 alphapos = -1; // <- no alpha
// Check which extra is alpha if any
dbgFile << "There are" << nbchannels << " channels and" << extrasamplescount << " extra channels";
if (sampleinfo) { // index images don't have any sampleinfo, and therefore sampleinfo == 0
for (int i = 0; i < extrasamplescount; i ++) {
dbgFile << "sample" << i << "extra sample count" << extrasamplescount << "color channel count" << (cs->colorChannelCount()) << "Number of channels" << nbchannels << "sample info" << sampleinfo[i];
if (sampleinfo[i] == EXTRASAMPLE_UNSPECIFIED) {
qWarning() << "Extra sample type not defined for this file, assuming unassociated alpha.";
alphapos = i;
}
if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) {
// XXX: dangelo: the color values are already multiplied with
// the alpha value. This needs to be reversed later (postprocessor?)
qWarning() << "Associated alpha in this file: krita does not handle plremultiplied alpha.";
alphapos = i;
}
if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) {
// color values are not premultiplied with alpha, and can be used as they are.
alphapos = i;
}
}
}
dbgFile << "Alpha pos:" << alphapos;
// Read META Information
KoDocumentInfo * info = m_doc->documentInfo();
char* text;
if (TIFFGetField(image, TIFFTAG_ARTIST, &text) == 1) {
info->setAuthorInfo("creator", text);
}
if (TIFFGetField(image, TIFFTAG_DOCUMENTNAME, &text) == 1) {
info->setAboutInfo("title", text);
}
if (TIFFGetField(image, TIFFTAG_IMAGEDESCRIPTION, &text) == 1) {
info->setAboutInfo("description", text);
}
// Get the planar configuration
uint16 planarconfig;
if (TIFFGetField(image, TIFFTAG_PLANARCONFIG, &planarconfig) == 0) {
dbgFile << "Plannar configuration is not define";
TIFFClose(image);
- return KisImageBuilder_RESULT_INVALID_ARG;
+ return ImportExportCodes::FileFormatIncorrect;
}
// Creating the KisImageSP
if (! m_image) {
m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image");
m_image->setResolution( POINT_TO_INCH(xres), POINT_TO_INCH(yres )); // It is the "invert" macro because we convert from pointer-per-inchs to points
Q_CHECK_PTR(m_image);
}
else {
if (m_image->width() < (qint32)width || m_image->height() < (qint32)height) {
quint32 newwidth = (m_image->width() < (qint32)width) ? width : m_image->width();
quint32 newheight = (m_image->height() < (qint32)height) ? height : m_image->height();
m_image->resizeImage(QRect(0,0,newwidth, newheight));
}
}
KisPaintLayer* layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX);
tdata_t buf = 0;
tdata_t* ps_buf = 0; // used only for planar configuration separated
KisBufferStreamBase* tiffstream;
KisTIFFReaderBase* tiffReader = 0;
quint8 poses[5];
KisTIFFPostProcessor* postprocessor = 0;
// Configure poses
uint8 nbcolorsamples = nbchannels - extrasamplescount;
switch (color_type) {
case PHOTOMETRIC_MINISWHITE: {
poses[0] = 0; poses[1] = 1;
postprocessor = new KisTIFFPostProcessorInvert(nbcolorsamples);
}
break;
case PHOTOMETRIC_MINISBLACK: {
poses[0] = 0; poses[1] = 1;
postprocessor = new KisTIFFPostProcessor(nbcolorsamples);
}
break;
case PHOTOMETRIC_CIELAB: {
poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3;
postprocessor = new KisTIFFPostProcessorCIELABtoICCLAB(nbcolorsamples);
}
break;
case PHOTOMETRIC_ICCLAB: {
poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3;
postprocessor = new KisTIFFPostProcessor(nbcolorsamples);
}
break;
case PHOTOMETRIC_RGB: {
if (sampletype == SAMPLEFORMAT_IEEEFP)
{
poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3;
} else {
poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3;
}
postprocessor = new KisTIFFPostProcessor(nbcolorsamples);
}
break;
case PHOTOMETRIC_SEPARATED: {
poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; poses[4] = 4;
postprocessor = new KisTIFFPostProcessor(nbcolorsamples);
}
break;
default:
break;
}
// Initisalize tiffReader
uint16 * lineSizeCoeffs = new uint16[nbchannels];
uint16 vsubsampling = 1;
uint16 hsubsampling = 1;
for (uint i = 0; i < nbchannels; i++) {
lineSizeCoeffs[i] = 1;
}
if (color_type == PHOTOMETRIC_PALETTE) {
uint16 *red; // No need to free them they are free by libtiff
uint16 *green;
uint16 *blue;
if ((TIFFGetField(image, TIFFTAG_COLORMAP, &red, &green, &blue)) == 0) {
dbgFile << "Indexed image does not define a palette";
TIFFClose(image);
delete [] lineSizeCoeffs;
- return KisImageBuilder_RESULT_INVALID_ARG;
+ return ImportExportCodes::FileFormatIncorrect;
}
tiffReader = new KisTIFFReaderFromPalette(layer->paintDevice(), red, green, blue, poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor);
} else if (color_type == PHOTOMETRIC_YCBCR) {
TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRSUBSAMPLING, &hsubsampling, &vsubsampling);
lineSizeCoeffs[1] = hsubsampling;
lineSizeCoeffs[2] = hsubsampling;
uint16 position;
TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position);
if (dstDepth == 8) {
tiffReader = new KisTIFFYCbCrReaderTarget8Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling);
}
else if (dstDepth == 16) {
tiffReader = new KisTIFFYCbCrReaderTarget16Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling);
}
}
else if (dstDepth == 8) {
tiffReader = new KisTIFFReaderTarget8bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor);
}
else if (dstDepth == 16) {
uint16 alphaValue;
if (sampletype == SAMPLEFORMAT_IEEEFP)
{
alphaValue = 15360; // representation of 1.0 in half
} else {
alphaValue = quint16_MAX;
}
tiffReader = new KisTIFFReaderTarget16bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue);
}
else if (dstDepth == 32) {
union {
float f;
uint32 i;
} alphaValue;
if (sampletype == SAMPLEFORMAT_IEEEFP)
{
alphaValue.f = 1.0f;
} else {
alphaValue.i = quint32_MAX;
}
tiffReader = new KisTIFFReaderTarget32bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue.i);
}
if (!tiffReader) {
delete postprocessor;
delete[] lineSizeCoeffs;
TIFFClose(image);
dbgFile << "Image has an invalid/unsupported color type: " << color_type;
- return KisImageBuilder_RESULT_INVALID_ARG;
+ return ImportExportCodes::FileFormatIncorrect;
}
if (TIFFIsTiled(image)) {
dbgFile << "tiled image";
uint32 tileWidth, tileHeight;
uint32 x, y;
TIFFGetField(image, TIFFTAG_TILEWIDTH, &tileWidth);
TIFFGetField(image, TIFFTAG_TILELENGTH, &tileHeight);
uint32 linewidth = (tileWidth * depth * nbchannels) / 8;
if (planarconfig == PLANARCONFIG_CONTIG) {
buf = _TIFFmalloc(TIFFTileSize(image));
if (depth < 16) {
tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, linewidth);
}
else if (depth < 32) {
tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, linewidth);
}
else {
tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, linewidth);
}
}
else {
ps_buf = new tdata_t[nbchannels];
uint32 * lineSizes = new uint32[nbchannels];
tmsize_t baseSize = TIFFTileSize(image) / nbchannels;
for (uint i = 0; i < nbchannels; i++) {
ps_buf[i] = _TIFFmalloc(baseSize);
lineSizes[i] = tileWidth; // baseSize / lineSizeCoeffs[i];
}
tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes);
delete [] lineSizes;
}
dbgFile << linewidth << "" << nbchannels << "" << layer->paintDevice()->colorSpace()->colorChannelCount();
for (y = 0; y < height; y += tileHeight) {
for (x = 0; x < width; x += tileWidth) {
dbgFile << "Reading tile x =" << x << " y =" << y;
if (planarconfig == PLANARCONFIG_CONTIG) {
TIFFReadTile(image, buf, x, y, 0, (tsample_t) - 1);
}
else {
for (uint i = 0; i < nbchannels; i++) {
TIFFReadTile(image, ps_buf[i], x, y, 0, i);
}
}
uint32 realTileWidth = (x + tileWidth) < width ? tileWidth : width - x;
for (uint yintile = 0; y + yintile < height && yintile < tileHeight / vsubsampling;) {
tiffReader->copyDataToChannels(x, y + yintile , realTileWidth, tiffstream);
yintile += 1;
tiffstream->moveToLine(yintile);
}
tiffstream->restart();
}
}
}
else {
dbgFile << "striped image";
tsize_t stripsize = TIFFStripSize(image);
uint32 rowsPerStrip;
TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip);
dbgFile << rowsPerStrip << "" << height;
rowsPerStrip = qMin(rowsPerStrip, height); // when TIFFNumberOfStrips(image) == 1 it might happen that rowsPerStrip is incorrectly set
if (planarconfig == PLANARCONFIG_CONTIG) {
buf = _TIFFmalloc(stripsize);
if (depth < 16) {
tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, stripsize / rowsPerStrip);
}
else if (depth < 32) {
tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, stripsize / rowsPerStrip);
}
else {
tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, stripsize / rowsPerStrip);
}
}
else {
ps_buf = new tdata_t[nbchannels];
uint32 scanLineSize = stripsize / rowsPerStrip;
dbgFile << " scanLineSize for each plan =" << scanLineSize;
uint32 * lineSizes = new uint32[nbchannels];
for (uint i = 0; i < nbchannels; i++) {
ps_buf[i] = _TIFFmalloc(stripsize);
lineSizes[i] = scanLineSize / lineSizeCoeffs[i];
}
tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes);
delete [] lineSizes;
}
dbgFile << "Scanline size =" << TIFFRasterScanlineSize(image) << " / strip size =" << TIFFStripSize(image) << " / rowsPerStrip =" << rowsPerStrip << " stripsize/rowsPerStrip =" << stripsize / rowsPerStrip;
uint32 y = 0;
dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image) << " rowsPerStrip =" << rowsPerStrip << " stripsize =" << stripsize;
for (uint32 strip = 0; y < height; strip++) {
if (planarconfig == PLANARCONFIG_CONTIG) {
TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, 0) , buf, (tsize_t) - 1);
}
else {
for (uint i = 0; i < nbchannels; i++) {
TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, i), ps_buf[i], (tsize_t) - 1);
}
}
for (uint32 yinstrip = 0 ; yinstrip < rowsPerStrip && y < height ;) {
uint linesread = tiffReader->copyDataToChannels(0, y, width, tiffstream);
y += linesread;
yinstrip += linesread;
tiffstream->moveToLine(yinstrip);
}
tiffstream->restart();
}
}
tiffReader->finalize();
delete[] lineSizeCoeffs;
delete tiffReader;
delete tiffstream;
if (planarconfig == PLANARCONFIG_CONTIG) {
_TIFFfree(buf);
} else {
for (uint i = 0; i < nbchannels; i++) {
_TIFFfree(ps_buf[i]);
}
delete[] ps_buf;
}
m_image->addNode(KisNodeSP(layer), m_image->rootLayer().data());
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
-KisImageBuilder_Result KisTIFFConverter::buildImage(const QString &filename)
+KisImportExportErrorCode KisTIFFConverter::buildImage(const QString &filename)
{
return decode(filename);
}
KisImageSP KisTIFFConverter::image()
{
return m_image;
}
-KisImageBuilder_Result KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options)
+KisImportExportErrorCode KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options)
{
dbgFile << "Start writing TIFF File";
- if (!kisimage)
- return KisImageBuilder_RESULT_EMPTY;
+ KIS_ASSERT_RECOVER_RETURN_VALUE(kisimage, ImportExportCodes::InternalError);
// Open file for writing
TIFF *image;
if ((image = TIFFOpen(QFile::encodeName(filename), "w")) == 0) {
dbgFile << "Could not open the file for writing" << filename;
- TIFFClose(image);
- return (KisImageBuilder_RESULT_FAILURE);
+ return ImportExportCodes::NoAccessToWrite;
}
// Set the document information
KoDocumentInfo * info = m_doc->documentInfo();
QString title = info->aboutInfo("title");
if (!title.isEmpty()) {
- TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData());
+ if (!TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData())) {
+ TIFFClose(image);
+ return ImportExportCodes::ErrorWhileWriting;
+ }
}
QString abstract = info->aboutInfo("description");
if (!abstract.isEmpty()) {
- TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData());
+ if (!TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData())) {
+ TIFFClose(image);
+ return ImportExportCodes::ErrorWhileWriting;
+ }
}
QString author = info->authorInfo("creator");
if (!author.isEmpty()) {
- TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData());
+ if(!TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData())) {
+ TIFFClose(image);
+ return ImportExportCodes::ErrorWhileWriting;
+ }
}
dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes()) << " yres: " << INCH_TO_POINT(kisimage->yRes());
- TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes())); // It is the "invert" macro because we convert from pointer-per-inchs to points
- TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes()));
+ if (!TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes()))) { // It is the "invert" macro because we convert from pointer-per-inchs to points
+ TIFFClose(image);
+ return ImportExportCodes::ErrorWhileWriting;
+ }
+ if (!TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes()))) {
+ TIFFClose(image);
+ return ImportExportCodes::ErrorWhileWriting;
+ }
KisGroupLayer* root = dynamic_cast<KisGroupLayer*>(kisimage->rootLayer().data());
- if (root == 0) {
+ KIS_ASSERT_RECOVER(root) {
TIFFClose(image);
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::InternalError;
}
KisTIFFWriterVisitor* visitor = new KisTIFFWriterVisitor(image, &options);
- if (!visitor->visit(root)) {
+ if (!(visitor->visit(root))) {
TIFFClose(image);
- return KisImageBuilder_RESULT_FAILURE;
+ return ImportExportCodes::Failure;
}
TIFFClose(image);
- return KisImageBuilder_RESULT_OK;
+ return ImportExportCodes::OK;
}
void KisTIFFConverter::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/tiff/kis_tiff_converter.h b/plugins/impex/tiff/kis_tiff_converter.h
index 47298c85c3..af064a56bd 100644
--- a/plugins/impex/tiff/kis_tiff_converter.h
+++ b/plugins/impex/tiff/kis_tiff_converter.h
@@ -1,71 +1,71 @@
/*
* Copyright (c) 2005-2006 Cyrille Berger <cberger@cberger.net>
*
* 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_TIFF_CONVERTER_H_
#define _KIS_TIFF_CONVERTER_H_
#include <stdio.h>
#include <tiffio.h>
#include <QVector>
#include "kis_types.h"
#include "kis_global.h"
#include "kis_annotation.h"
-#include <KisImageBuilderResult.h>
+#include <KisImportExportErrorCode.h>
class KisDocument;
struct KisTIFFOptions {
quint16 compressionType = 0;
quint16 predictor = 1;
bool alpha = true;
bool flatten = true;
quint16 jpegQuality = 80;
quint16 deflateCompress = 6;
quint16 pixarLogCompress = 6;
bool saveProfile = true;
KisPropertiesConfigurationSP toProperties() const;
void fromProperties(KisPropertiesConfigurationSP cfg);
};
class KisTIFFConverter : public QObject
{
Q_OBJECT
public:
KisTIFFConverter(KisDocument *doc);
~KisTIFFConverter() override;
public:
- KisImageBuilder_Result buildImage(const QString &filename);
- KisImageBuilder_Result buildFile(const QString &filename, KisImageSP layer, KisTIFFOptions);
+ KisImportExportErrorCode buildImage(const QString &filename);
+ KisImportExportErrorCode buildFile(const QString &filename, KisImageSP layer, KisTIFFOptions);
/** Retrieve the constructed image
*/
KisImageSP image();
public Q_SLOTS:
virtual void cancel();
private:
- KisImageBuilder_Result decode(const QString &filename);
- KisImageBuilder_Result readTIFFDirectory(TIFF* image);
+ KisImportExportErrorCode decode(const QString &filename);
+ KisImportExportErrorCode readTIFFDirectory(TIFF* image);
private:
KisImageSP m_image;
KisDocument *m_doc;
bool m_stop;
};
#endif
diff --git a/plugins/impex/tiff/kis_tiff_export.cc b/plugins/impex/tiff/kis_tiff_export.cc
index 1bf81d4965..f045bb455f 100644
--- a/plugins/impex/tiff/kis_tiff_export.cc
+++ b/plugins/impex/tiff/kis_tiff_export.cc
@@ -1,144 +1,138 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_tiff_export.h"
#include <QCheckBox>
#include <QSlider>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include <KoChannelInfo.h>
#include <KoColorModelStandardIds.h>
#include <KisImportExportManager.h>
#include <KisExportCheckRegistry.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <kis_config.h>
#include "kis_layer_utils.h"
#include "kis_tiff_converter.h"
#include "kis_dlg_options_tiff.h"
#include "ui_kis_wdg_options_tiff.h"
K_PLUGIN_FACTORY_WITH_JSON(KisTIFFExportFactory, "krita_tiff_export.json", registerPlugin<KisTIFFExport>();)
KisTIFFExport::KisTIFFExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisTIFFExport::~KisTIFFExport()
{
}
-KisImportExportFilter::ConversionStatus KisTIFFExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration)
+KisImportExportErrorCode KisTIFFExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration)
{
// If a configuration object was passed to the convert method, we use that, otherwise we load from the settings
KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration());
if (configuration) {
cfg->fromXML(configuration->toXML());
}
else {
cfg = lastSavedConfiguration(KisDocument::nativeFormatMimeType(), "image/tiff");
}
const KoColorSpace* cs = document->savingImage()->colorSpace();
cfg->setProperty("type", (int)cs->channels()[0]->channelValueType());
cfg->setProperty("isCMYK", (cs->colorModelId() == CMYKAColorModelID));
KisTIFFOptions options;
options.fromProperties(configuration);
if (!options.flatten) {
const bool hasGroupLayers =
KisLayerUtils::recursiveFindNode(document->savingImage()->root(),
[] (KisNodeSP node) {
return node->parent() && node->inherits("KisGroupLayer");
});
options.flatten = hasGroupLayers;
}
if ((cs->channels()[0]->channelValueType() == KoChannelInfo::FLOAT16
|| cs->channels()[0]->channelValueType() == KoChannelInfo::FLOAT32) && options.predictor == 2) {
// FIXME THIS IS AN HACK FIX THAT IN 2.0 !! (62456a7b47636548c6507593df3e2bdf440f7544, BUG:135649)
options.predictor = 3;
}
KisImageSP image;
if (options.flatten) {
image = new KisImage(0, document->savingImage()->width(), document->savingImage()->height(), document->savingImage()->colorSpace(), "");
image->setResolution(document->savingImage()->xRes(), document->savingImage()->yRes());
KisPaintDeviceSP pd = KisPaintDeviceSP(new KisPaintDevice(*document->savingImage()->projection()));
KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), "projection", OPACITY_OPAQUE_U8, pd));
image->addNode(KisNodeSP(l.data()), image->rootLayer().data());
} else {
image = document->savingImage();
}
KisTIFFConverter tiffConverter(document);
- KisImageBuilder_Result res;
- if ((res = tiffConverter.buildFile(filename(), image, options)) == KisImageBuilder_RESULT_OK) {
- dbgFile << "success !";
- return KisImportExportFilter::OK;
- }
-
- dbgFile << " Result =" << res;
- return KisImportExportFilter::InternalError;
+ KisImportExportErrorCode res = tiffConverter.buildFile(filename(), image, options);
+ return res;
}
KisPropertiesConfigurationSP KisTIFFExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
KisTIFFOptions options;
return options.toProperties();
}
KisConfigWidget *KisTIFFExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const
{
return new KisTIFFOptionsWidget(parent);
}
void KisTIFFExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED));
addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Float16BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Float32BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(CMYKAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(CMYKAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(LABAColorModelID, Integer16BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "TIFF");
}
#include <kis_tiff_export.moc>
diff --git a/plugins/impex/tiff/kis_tiff_export.h b/plugins/impex/tiff/kis_tiff_export.h
index 7a3d024719..76afc237e2 100644
--- a/plugins/impex/tiff/kis_tiff_export.h
+++ b/plugins/impex/tiff/kis_tiff_export.h
@@ -1,41 +1,41 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* 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_TIFF_EXPORT_H_
#define _KIS_TIFF_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
class KisTIFFExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTIFFExport(QObject *parent, const QVariantList &);
~KisTIFFExport() override;
bool supportsIO() const override { return false; }
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override;
void initializeCapabilities() override;
};
#endif
diff --git a/plugins/impex/tiff/kis_tiff_import.cc b/plugins/impex/tiff/kis_tiff_import.cc
index c3d795dc07..eb947dc135 100644
--- a/plugins/impex/tiff/kis_tiff_import.cc
+++ b/plugins/impex/tiff/kis_tiff_import.cc
@@ -1,72 +1,54 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_tiff_import.h"
#include <QFileInfo>
#include <kpluginfactory.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <KisViewManager.h>
#include "kis_tiff_converter.h"
K_PLUGIN_FACTORY_WITH_JSON(TIFFImportFactory, "krita_tiff_import.json", registerPlugin<KisTIFFImport>();)
KisTIFFImport::KisTIFFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisTIFFImport::~KisTIFFImport()
{
}
-KisImportExportFilter::ConversionStatus KisTIFFImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisTIFFImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/)
{
KisTIFFConverter tiffConverter(document);
-
- switch (tiffConverter.buildImage(filename())) {
- case KisImageBuilder_RESULT_UNSUPPORTED:
- return KisImportExportFilter::NotImplemented;
- case KisImageBuilder_RESULT_INVALID_ARG:
- return KisImportExportFilter::BadMimeType;
- case KisImageBuilder_RESULT_NO_URI:
- case KisImageBuilder_RESULT_NOT_LOCAL:
- return KisImportExportFilter::FileNotFound;
- case KisImageBuilder_RESULT_BAD_FETCH:
- case KisImageBuilder_RESULT_EMPTY:
- return KisImportExportFilter::ParsingError;
- case KisImageBuilder_RESULT_FAILURE:
- return KisImportExportFilter::InternalError;
- case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE:
- return KisImportExportFilter::WrongFormat;
- case KisImageBuilder_RESULT_OK:
- document -> setCurrentImage(tiffConverter.image());
- return KisImportExportFilter::OK;
- default:
- break;
+ KisImportExportErrorCode result = tiffConverter.buildImage(filename());
+ if (result.isOk()) {
+ document->setCurrentImage(tiffConverter.image());
}
- return KisImportExportFilter::InternalError;
+ return result;
}
#include <kis_tiff_import.moc>
diff --git a/plugins/impex/tiff/kis_tiff_import.h b/plugins/impex/tiff/kis_tiff_import.h
index 03d54f6be8..ddfaf0bfd7 100644
--- a/plugins/impex/tiff/kis_tiff_import.h
+++ b/plugins/impex/tiff/kis_tiff_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
*
* 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_TIFF_IMPORT_H_
#define _KIS_TIFF_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisTIFFImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTIFFImport(QObject *parent, const QVariantList &);
~KisTIFFImport() override;
bool supportsIO() const override { return false; }
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/tiff/kis_tiff_writer_visitor.cpp b/plugins/impex/tiff/kis_tiff_writer_visitor.cpp
index a2db8503e0..dccf5530b0 100644
--- a/plugins/impex/tiff/kis_tiff_writer_visitor.cpp
+++ b/plugins/impex/tiff/kis_tiff_writer_visitor.cpp
@@ -1,224 +1,276 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_tiff_writer_visitor.h"
#include <KoColorProfile.h>
#include <KoColorSpace.h>
#include <KoID.h>
+#include <KoColorSpaceRegistry.h>
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
#endif
namespace
{
- bool writeColorSpaceInformation(TIFF* image, const KoColorSpace * cs, uint16& color_type, uint16& sample_format)
+ bool isBitDepthFloat(QString depth) {
+ return depth.contains("F");
+ }
+
+ bool writeColorSpaceInformation(TIFF* image, const KoColorSpace * cs, uint16& color_type, uint16& sample_format, const KoColorSpace* &destColorSpace)
{
dbgKrita << cs->id();
- if (cs->id() == "GRAYA" || cs->id() == "GRAYAU16") {
- color_type = PHOTOMETRIC_MINISBLACK;
- return true;
- }
- if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) {
+ QString id = cs->id();
+ QString depth = cs->colorDepthId().id();
+ // destColorSpace should be reassigned to a proper color space to convert to
+ // if the return value of this function is false
+ destColorSpace = 0;
+
+ // sample_format and color_type should be assigned to the destination color space,
+ // not /always/ the one we get here
+
+ if (id.contains("RGBA")) {
color_type = PHOTOMETRIC_RGB;
+ if (isBitDepthFloat(depth)) {
+ sample_format = SAMPLEFORMAT_IEEEFP;
+ }
return true;
- }
- if (KoID(cs->id()) == KoID("RGBAF16") || KoID(cs->id()) == KoID("RGBAF32")) {
- color_type = PHOTOMETRIC_RGB;
- sample_format = SAMPLEFORMAT_IEEEFP;
- return true;
- }
- if (cs->id() == "CMYK" || cs->id() == "CMYKAU16") {
+
+ } else if (id.contains("CMYK")) {
color_type = PHOTOMETRIC_SEPARATED;
TIFFSetField(image, TIFFTAG_INKSET, INKSET_CMYK);
+
+ if (depth == "F16") {
+ sample_format = SAMPLEFORMAT_IEEEFP;
+ destColorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), cs->profile());
+ return false;
+ }
+
+ if (isBitDepthFloat(depth)) {
+ sample_format = SAMPLEFORMAT_IEEEFP;
+ }
return true;
- }
- if (cs->id() == "LABA") {
+
+ } else if (id.contains("LABA")) {
color_type = PHOTOMETRIC_ICCLAB;
+
+ if (depth == "F16") {
+ sample_format = SAMPLEFORMAT_IEEEFP;
+ destColorSpace = KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), cs->profile());
+ return false;
+ }
+
+ if (isBitDepthFloat(depth)) {
+ sample_format = SAMPLEFORMAT_IEEEFP;
+ }
return true;
+
+ } else if (id.contains("GRAYA")) {
+ color_type = PHOTOMETRIC_MINISBLACK;
+ if (isBitDepthFloat(depth)) {
+ sample_format = SAMPLEFORMAT_IEEEFP;
+ }
+ return true;
+
+ } else {
+ color_type = PHOTOMETRIC_RGB;
+ const QString profile = "sRGB-elle-V2-srgbtrc";
+ destColorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), depth, profile);
+ if (isBitDepthFloat(depth)) {
+ sample_format = SAMPLEFORMAT_IEEEFP;
+ }
+ return false;
}
- return false;
}
}
KisTIFFWriterVisitor::KisTIFFWriterVisitor(TIFF*image, KisTIFFOptions* options)
: m_image(image)
, m_options(options)
{
}
KisTIFFWriterVisitor::~KisTIFFWriterVisitor()
{
}
bool KisTIFFWriterVisitor::copyDataToStrips(KisHLineConstIteratorSP it, tdata_t buff, uint8 depth, uint16 sample_format, uint8 nbcolorssamples, quint8* poses)
{
if (depth == 32) {
Q_ASSERT(sample_format == SAMPLEFORMAT_IEEEFP);
float *dst = reinterpret_cast<float *>(buff);
do {
const float *d = reinterpret_cast<const float *>(it->oldRawData());
int i;
for (i = 0; i < nbcolorssamples; i++) {
*(dst++) = d[poses[i]];
}
if (m_options->alpha) *(dst++) = d[poses[i]];
} while (it->nextPixel());
return true;
}
else if (depth == 16 ) {
if (sample_format == SAMPLEFORMAT_IEEEFP) {
#ifdef HAVE_OPENEXR
half *dst = reinterpret_cast<half *>(buff);
do {
const half *d = reinterpret_cast<const half *>(it->oldRawData());
int i;
for (i = 0; i < nbcolorssamples; i++) {
*(dst++) = d[poses[i]];
}
if (m_options->alpha) *(dst++) = d[poses[i]];
} while (it->nextPixel());
return true;
#endif
}
else {
quint16 *dst = reinterpret_cast<quint16 *>(buff);
do {
const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
int i;
for (i = 0; i < nbcolorssamples; i++) {
*(dst++) = d[poses[i]];
}
if (m_options->alpha) *(dst++) = d[poses[i]];
} while (it->nextPixel());
return true;
}
}
else if (depth == 8) {
quint8 *dst = reinterpret_cast<quint8 *>(buff);
do {
const quint8 *d = it->oldRawData();
int i;
for (i = 0; i < nbcolorssamples; i++) {
*(dst++) = d[poses[i]];
}
if (m_options->alpha) *(dst++) = d[poses[i]];
} while (it->nextPixel());
return true;
}
return false;
}
bool KisTIFFWriterVisitor::saveLayerProjection(KisLayer *layer)
{
dbgFile << "visiting on layer" << layer->name() << "";
KisPaintDeviceSP pd = layer->projection();
+
+ uint16 color_type;
+ uint16 sample_format = SAMPLEFORMAT_UINT;
+ const KoColorSpace* destColorSpace;
+ // Check colorspace
+ if (!writeColorSpaceInformation(image(), pd->colorSpace(), color_type, sample_format, destColorSpace)) { // unsupported colorspace
+ if (!destColorSpace) {
+ return false;
+ }
+ pd.attach(new KisPaintDevice(*pd));
+ pd->convertTo(destColorSpace);
+ }
+
// Save depth
int depth = 8 * pd->pixelSize() / pd->channelCount();
TIFFSetField(image(), TIFFTAG_BITSPERSAMPLE, depth);
// Save number of samples
if (m_options->alpha) {
TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount());
uint16 sampleinfo[1] = { EXTRASAMPLE_UNASSALPHA };
TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 1, sampleinfo);
} else {
TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount() - 1);
TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 0);
}
+
// Save colorspace information
- uint16 color_type;
- uint16 sample_format = SAMPLEFORMAT_UINT;
- if (!writeColorSpaceInformation(image(), pd->colorSpace(), color_type, sample_format)) { // unsupported colorspace
- return false;
- }
TIFFSetField(image(), TIFFTAG_PHOTOMETRIC, color_type);
TIFFSetField(image(), TIFFTAG_SAMPLEFORMAT, sample_format);
TIFFSetField(image(), TIFFTAG_IMAGEWIDTH, layer->image()->width());
TIFFSetField(image(), TIFFTAG_IMAGELENGTH, layer->image()->height());
// Set the compression options
TIFFSetField(image(), TIFFTAG_COMPRESSION, m_options->compressionType);
TIFFSetField(image(), TIFFTAG_JPEGQUALITY, m_options->jpegQuality);
TIFFSetField(image(), TIFFTAG_ZIPQUALITY, m_options->deflateCompress);
TIFFSetField(image(), TIFFTAG_PIXARLOGQUALITY, m_options->pixarLogCompress);
// Set the predictor
TIFFSetField(image(), TIFFTAG_PREDICTOR, m_options->predictor);
// Use contiguous configuration
TIFFSetField(image(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
// Use 8 rows per strip
TIFFSetField(image(), TIFFTAG_ROWSPERSTRIP, 8);
// Save profile
if (m_options->saveProfile) {
const KoColorProfile* profile = pd->colorSpace()->profile();
if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) {
QByteArray ba = profile->rawData();
TIFFSetField(image(), TIFFTAG_ICCPROFILE, ba.size(), ba.constData());
}
}
tsize_t stripsize = TIFFStripSize(image());
tdata_t buff = _TIFFmalloc(stripsize);
qint32 height = layer->image()->height();
qint32 width = layer->image()->width();
bool r = true;
for (int y = 0; y < height; y++) {
KisHLineConstIteratorSP it = pd->createHLineConstIteratorNG(0, y, width);
switch (color_type) {
case PHOTOMETRIC_MINISBLACK: {
quint8 poses[] = { 0, 1 };
r = copyDataToStrips(it, buff, depth, sample_format, 1, poses);
}
break;
case PHOTOMETRIC_RGB: {
quint8 poses[4];
if (sample_format == SAMPLEFORMAT_IEEEFP) {
poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3;
} else {
poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3;
}
r = copyDataToStrips(it, buff, depth, sample_format, 3, poses);
}
break;
case PHOTOMETRIC_SEPARATED: {
quint8 poses[] = { 0, 1, 2, 3, 4 };
r = copyDataToStrips(it, buff, depth, sample_format, 4, poses);
}
break;
case PHOTOMETRIC_ICCLAB: {
quint8 poses[] = { 0, 1, 2, 3 };
r = copyDataToStrips(it, buff, depth, sample_format, 3, poses);
}
break;
return false;
}
if (!r) return false;
TIFFWriteScanline(image(), buff, y, (tsample_t) - 1);
}
_TIFFfree(buff);
TIFFWriteDirectory(image());
return true;
}
diff --git a/plugins/impex/tiff/tests/data/incorrectFormatFile.txt b/plugins/impex/tiff/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/tiff/tests/data/readonlyFile.txt b/plugins/impex/tiff/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/tiff/tests/data/writeonlyFile.txt b/plugins/impex/tiff/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp
index 0ce5fcb26c..fd5d079426 100644
--- a/plugins/impex/tiff/tests/kis_tiff_test.cpp
+++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp
@@ -1,119 +1,201 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tiff_test.h"
#include <QTest>
#include <QCoreApplication>
#include "filestest.h"
#include <KoColorModelStandardIds.h>
#include <KoColor.h>
#include "kisexiv2/kis_exiv2.h"
#include <sdk/tests/kistest.h>
+#include <KoColorModelStandardIdsUtils.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 TiffMimetype = "image/tiff";
+
void KisTiffTest::testFiles()
{
// XXX: make the exiv io backends real plugins
KisExiv2::initialize();
QStringList excludes;
#ifndef CPU_32_BITS
excludes << "flower-minisblack-06.tif";
#endif
#ifdef HAVE_LCMS2
excludes << "flower-separated-contig-08.tif"
<< "flower-separated-contig-16.tif"
<< "flower-separated-planar-08.tif"
<< "flower-separated-planar-16.tif"
<< "flower-minisblack-02.tif"
<< "flower-minisblack-04.tif"
<< "flower-minisblack-08.tif"
<< "flower-minisblack-10.tif"
<< "flower-minisblack-12.tif"
<< "flower-minisblack-14.tif"
<< "flower-minisblack-16.tif"
<< "flower-minisblack-24.tif"
<< "flower-minisblack-32.tif"
<< "jim___dg.tif"
<< "jim___gg.tif"
<< "strike.tif";
#endif
excludes << "text.tif" << "ycbcr-cat.tif";
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes, QString(), 1);
}
void KisTiffTest::testRoundTripRGBF16()
{
// Disabled for now, it's broken because we assumed integers.
#if 0
QRect testRect(0,0,1000,1000);
QRect fillRect(100,100,100,100);
const KoColorSpace *csf16 = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), 0);
KisDocument *doc0 = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
doc0->newImage("test", testRect.width(), testRect.height(), csf16, KoColor(Qt::blue, csf16), QString(), 1.0);
QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".tiff"));
tmpFile.open();
doc0->setBackupFile(false);
doc0->setOutputMimeType("image/tiff");
doc0->setFileBatchMode(true);
doc0->saveAs(QUrl::fromLocalFile(tmpFile.fileName()));
KisNodeSP layer0 = doc0->image()->root()->firstChild();
Q_ASSERT(layer0);
layer0->paintDevice()->fill(fillRect, KoColor(Qt::red, csf16));
KisDocument *doc1 = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
KisImportExportManager manager(doc1);
doc1->setFileBatchMode(false);
- KisImportExportFilter::ConversionStatus status;
+ KisImportExportErrorCode status;
QString s = manager.importDocument(tmpFile.fileName(),
QString(),
status);
dbgKrita << s;
Q_ASSERT(doc1->image());
QImage ref0 = doc0->image()->projection()->convertToQImage(0, testRect);
QImage ref1 = doc1->image()->projection()->convertToQImage(0, testRect);
QCOMPARE(ref0, ref1);
#endif
}
+void KisTiffTest::testSaveTiffColorSpace(QString colorModel, QString colorDepth, QString colorProfile)
+{
+ KoColorSpaceRegistry registry;
+ const KoColorSpace *space = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile);
+ TestUtil::testExportToColorSpace(QString(FILES_DATA_DIR), TiffMimetype, space, ImportExportCodes::OK, true);
+}
+
+void KisTiffTest::testSaveTiffRgbaColorSpace()
+{
+ QString profile = "sRGB-elle-V2-srgbtrc";
+ testSaveTiffColorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile);
+ profile = "sRGB-elle-V2-g10";
+ testSaveTiffColorSpace(RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
+}
+
+void KisTiffTest::testSaveTiffGreyAColorSpace()
+{
+ QString profile = "Gray-D50-elle-V2-srgbtrc";
+ testSaveTiffColorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), profile);
+ profile = "Gray-D50-elle-V2-g10";
+ testSaveTiffColorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(GrayAColorModelID.id(), Float16BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
+
+}
+
+
+void KisTiffTest::testSaveTiffCmykColorSpace()
+{
+ QString profile = "Chemical proof";
+ testSaveTiffColorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
+}
+
+void KisTiffTest::testSaveTiffLabColorSpace()
+{
+ const QString profile = "Lab identity build-in";
+ testSaveTiffColorSpace(LABAColorModelID.id(), Integer8BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
+}
+
+
+void KisTiffTest::testSaveTiffYCrCbAColorSpace()
+{
+ /*
+ * There is no public/open profile for YCrCbA, so no way to test it...
+ *
+ const QString profile = "";
+ testSaveTiffColorSpace(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id(), profile);
+ testSaveTiffColorSpace(YCbCrAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
+ */
+}
+
+
+
+void KisTiffTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TiffMimetype);
+}
+
+
+void KisTiffTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TiffMimetype, true);
+}
+
+
+void KisTiffTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TiffMimetype);
+}
+
+
+
KISTEST_MAIN(KisTiffTest)
diff --git a/plugins/impex/tiff/tests/kis_tiff_test.h b/plugins/impex/tiff/tests/kis_tiff_test.h
index c86d906d4b..873a1caf89 100644
--- a/plugins/impex/tiff/tests/kis_tiff_test.h
+++ b/plugins/impex/tiff/tests/kis_tiff_test.h
@@ -1,32 +1,43 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_TIFF_TEST_H_
#define _KIS_TIFF_TEST_H_
#include <QtTest>
class KisTiffTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
void testRoundTripRGBF16();
+
+ void testSaveTiffColorSpace(QString colorModel, QString colorDepth, QString colorProfile);
+ void testSaveTiffRgbaColorSpace();
+ void testSaveTiffGreyAColorSpace();
+ void testSaveTiffCmykColorSpace();
+ void testSaveTiffLabColorSpace();
+ void testSaveTiffYCrCbAColorSpace();
+
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatspec.c b/plugins/impex/xcf/3rdparty/xcftools/flatspec.c
index ec12a67faf..54c74d90aa 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/flatspec.c
+++ b/plugins/impex/xcf/3rdparty/xcftools/flatspec.c
@@ -1,380 +1,397 @@
/* Flattening selections function for xcftools
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#include "xcftools.h"
#include "flatten.h"
#include <string.h>
#include <stdlib.h>
void
init_flatspec(struct FlattenSpec *spec)
{
spec->window_mode = USE_CANVAS ;
spec->default_pixel = PERHAPS_ALPHA_CHANNEL ;
spec->numLayers = 0 ;
spec->layers = NULL ;
spec->transmap_filename = NULL ;
spec->output_filename = "-" ;
spec->out_color_mode = COLOR_BY_CONTENTS ;
spec->partial_transparency_mode = ALLOW_PARTIAL_TRANSPARENCY ;
spec->process_in_memory = 0 ;
spec->gimpish_indexed = 1 ;
}
-void
+int
add_layer_request(struct FlattenSpec *spec, const char *layer)
{
spec->layers = realloc(spec->layers,
sizeof(struct xcfLayer) * (1+spec->numLayers));
- if( spec->layers == NULL )
+ if( spec->layers == NULL ) {
FatalUnexpected(_("Out of memory"));
+ return XCF_ERROR;
+ }
spec->layers[spec->numLayers].name = layer ;
spec->layers[spec->numLayers].mode = (GimpLayerModeEffects)-1 ;
spec->layers[spec->numLayers].opacity = 9999 ;
spec->layers[spec->numLayers].hasMask = -1 ;
spec->numLayers++ ;
+ return XCF_OK;
}
struct xcfLayer *
lastlayerspec(struct FlattenSpec *spec,const char *option)
{
- if( spec->numLayers == 0 )
+ if( spec->numLayers == 0 ) {
FatalGeneric(20,_("The %s option must follow a layer name on the "
"command line"),option);
+ return XCF_PTR_EMPTY;
+ }
return spec->layers + (spec->numLayers-1) ;
}
static int
typeHasTransparency(GimpImageType type)
{
switch( type ) {
case GIMP_RGB_IMAGE:
case GIMP_GRAY_IMAGE:
case GIMP_INDEXED_IMAGE:
return 0 ;
case GIMP_RGBA_IMAGE:
case GIMP_GRAYA_IMAGE:
case GIMP_INDEXEDA_IMAGE:
return 1 ;
}
return 1 ;
}
static enum out_color_mode
color_by_layers(struct FlattenSpec *spec)
{
int colormap_is_colored = 0 ;
enum out_color_mode grayish ;
int i ;
if( spec->default_pixel == CHECKERED_BACKGROUND )
grayish = COLOR_GRAY ;
else {
int degrayed = degrayPixel(spec->default_pixel);
if( degrayed < 0 ) {
return COLOR_RGB ;
} else if( spec->gimpish_indexed &&
(degrayed == 0 || degrayed == 255) ) {
grayish = COLOR_MONO ;
} else {
grayish = COLOR_GRAY ;
}
}
for( i=0; i<colormapLength; i++ ) {
if( colormap[i] == NEWALPHA(0,0) || colormap[i] == NEWALPHA(-1,0) )
continue ;
if( degrayPixel(colormap[i]) == -1 ) {
colormap_is_colored = 1 ;
break ;
} else {
grayish = COLOR_GRAY ;
}
}
for( i=0; i<spec->numLayers; i++ )
switch( spec->layers[i].type ) {
case GIMP_RGB_IMAGE:
case GIMP_RGBA_IMAGE:
return COLOR_RGB ;
case GIMP_GRAY_IMAGE:
case GIMP_GRAYA_IMAGE:
grayish = COLOR_GRAY ;
break ;
case GIMP_INDEXED_IMAGE:
case GIMP_INDEXEDA_IMAGE:
if( colormap_is_colored ) return COLOR_RGB ;
break ;
}
return grayish ;
}
-void
+int
complete_flatspec(struct FlattenSpec *spec, guesser guess_callback)
{
unsigned i ;
int anyPartial ;
/* Find the layers to convert.
*/
if( spec->numLayers == 0 ) {
spec->layers = XCF.layers ;
spec->numLayers = XCF.numLayers ;
} else {
for( i=0; i<spec->numLayers; i++ ) {
GimpLayerModeEffects mode ;
int opacity, hasMask ;
unsigned j ;
for( j=0; ; j++ ) {
- if( j == XCF.numLayers )
+ if( j == XCF.numLayers ) {
FatalGeneric(22,_("The image has no layer called '%s'"),
spec->layers[i].name);
+ return XCF_ERROR;
+ }
if( strcmp(spec->layers[i].name,XCF.layers[j].name) == 0 )
break ;
}
mode = spec->layers[i].mode == (GimpLayerModeEffects)-1 ?
XCF.layers[j].mode : spec->layers[i].mode ;
opacity = spec->layers[i].opacity == 9999 ?
XCF.layers[j].opacity : spec->layers[i].opacity ;
hasMask = spec->layers[i].hasMask == -1 ?
XCF.layers[j].hasMask : spec->layers[i].hasMask ;
if( hasMask && !XCF.layers[j].hasMask &&
- XCF.layers[j].mask.hierarchy == 0 )
+ XCF.layers[j].mask.hierarchy == 0 ) {
FatalGeneric(22,_("Layer '%s' has no layer mask to enable"),
spec->layers[i].name);
+ return XCF_ERROR;
+ }
spec->layers[i] = XCF.layers[j] ;
spec->layers[i].mode = mode ;
spec->layers[i].opacity = opacity ;
spec->layers[i].hasMask = hasMask ;
spec->layers[i].isVisible = 1 ;
}
}
/* Force the mode of the lowest visible layer to be Normal or Dissolve.
* That may not be logical, but the Gimp does it
*/
for( i=0; i < spec->numLayers; i++ ) {
if( spec->layers[i].isVisible ) {
if( spec->layers[i].mode != GIMP_DISSOLVE_MODE )
spec->layers[i].mode = GIMP_NORMAL_MODE ;
break ;
}
}
/* Mimic the Gimp's behavior on indexed layers */
if( XCF.type == GIMP_INDEXED && spec->gimpish_indexed ) {
for( i=0; i<spec->numLayers; i++ )
if( spec->layers[i].mode != GIMP_DISSOLVE_MODE )
spec->layers[i].mode = GIMP_NORMAL_NOPARTIAL_MODE ;
} else
spec->gimpish_indexed = 0 ;
/* compute dimensions of the window */
if( spec->window_mode == AUTOCROP ) {
int first = 1 ;
for( i=0; i<spec->numLayers; i++ )
if( spec->layers[i].isVisible ) {
computeDimensions(&spec->layers[i].dim) ;
if( first ) {
spec->dim = spec->layers[i].dim ;
first = 0 ;
} else {
if( spec->dim.c.l > spec->layers[i].dim.c.l )
spec->dim.c.l = spec->layers[i].dim.c.l ;
if( spec->dim.c.r < spec->layers[i].dim.c.r )
spec->dim.c.r = spec->layers[i].dim.c.r ;
if( spec->dim.c.t > spec->layers[i].dim.c.t )
spec->dim.c.t = spec->layers[i].dim.c.t ;
if( spec->dim.c.b < spec->layers[i].dim.c.b )
spec->dim.c.b = spec->layers[i].dim.c.b ;
}
}
if( first ) {
spec->window_mode = USE_CANVAS ;
} else {
spec->dim.width = spec->dim.c.r - spec->dim.c.l ;
spec->dim.height = spec->dim.c.b - spec->dim.c.t ;
}
}
if( spec->window_mode != AUTOCROP ) {
if( (spec->window_mode & MANUAL_OFFSET) == 0 )
spec->dim.c.t = spec->dim.c.l = 0 ;
if( (spec->window_mode & MANUAL_CROP) == 0 ) {
spec->dim.height = XCF.height ;
spec->dim.width = XCF.width ;
}
}
computeDimensions(&spec->dim);
/* Turn off layers that we don't hit at all */
for( i=0; i<spec->numLayers; i++ )
if( spec->layers[i].isVisible &&
disjointRects(spec->dim.c,spec->layers[i].dim.c) )
spec->layers[i].isVisible = 0 ;
/* See if there is a completely covering layer somewhere in the stack */
/* Also check if partial transparency is possible */
anyPartial = 0 ;
for( i=spec->numLayers; i-- ; ) {
if( !spec->layers[i].isVisible )
continue ;
if( typeHasTransparency(spec->layers[i].type) ) {
if( spec->layers[i].mode == GIMP_NORMAL_MODE )
anyPartial = 1;
} else if( isSubrect(spec->dim.c,spec->layers[i].dim.c) &&
!spec->layers[i].hasMask &&
(spec->layers[i].mode == GIMP_NORMAL_MODE ||
spec->layers[i].mode == GIMP_NORMAL_NOPARTIAL_MODE ||
spec->layers[i].mode == GIMP_DISSOLVE_MODE) ) {
/* This layer fills out the entire image.
* Turn off only lower layers, and note that we cannot have
* transparency at all.
*/
while(i) spec->layers[--i].isVisible = 0 ;
if( spec->default_pixel != FORCE_ALPHA_CHANNEL )
spec->default_pixel = NEWALPHA(colormap[0],255);
anyPartial = 0 ;
break ;
}
}
if( spec->partial_transparency_mode == ALLOW_PARTIAL_TRANSPARENCY &&
(!anyPartial || ALPHA(spec->default_pixel) >= 128) )
spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ;
/* Initialize layers and print overview if we're verbose */
for( i=spec->numLayers; i--; )
if( spec->layers[i].isVisible ) {
- initLayer(&spec->layers[i]) ;
+ if (initLayer(&spec->layers[i]) != XCF_OK) {
+ return XCF_ERROR;
+ }
if( verboseFlag ) {
fprintf(stderr,"%dx%d%+d%+d %s %s",
spec->layers[i].dim.width, spec->layers[i].dim.height,
spec->layers[i].dim.c.l - spec->dim.c.l,
spec->layers[i].dim.c.t - spec->dim.c.t,
_(showGimpImageType(spec->layers[i].type)),
_(showGimpLayerModeEffects(spec->layers[i].mode)));
if( spec->layers[i].opacity < 255 )
fprintf(stderr,"/%02d%%",spec->layers[i].opacity * 100 / 255);
if( XCF.layers[i].hasMask )
fprintf(stderr,_("/mask"));
fprintf(stderr," %s\n",spec->layers[i].name);
}
}
/* Resolve color mode unless we wait until we have the entire image */
if( spec->out_color_mode == COLOR_BY_CONTENTS &&
!spec->process_in_memory ) {
if( guess_callback )
spec->out_color_mode = guess_callback(spec,NULL);
if( spec->out_color_mode == COLOR_BY_CONTENTS )
spec->out_color_mode = color_by_layers(spec) ;
}
+ return XCF_OK;
}
-void
+int
analyse_colormode(struct FlattenSpec *spec,rgba **allPixels,
guesser guess_callback)
{
unsigned x,y ;
int status ;
/* 8 - looking for any transparency
* 4 - looking for partially transparent pixels
* 2 - looking for pixels other than black and white
* 1 - looking for colored pixels
*/
int known_absent = 0 ;
int assume_present = 0 ;
if( spec->out_color_mode == COLOR_BY_CONTENTS && guess_callback )
spec->out_color_mode = guess_callback(spec,allPixels) ;
if( spec->out_color_mode == COLOR_RGB ) assume_present |= 3 ;
if( spec->out_color_mode == COLOR_INDEXED ) assume_present |= 3 ;
if( spec->out_color_mode == COLOR_GRAY ) assume_present |= 2 ;
switch( color_by_layers(spec) ) {
case COLOR_GRAY: known_absent |= 1 ; break ;
case COLOR_MONO: known_absent |= 3 ; break ;
default: break ;
}
if( spec->partial_transparency_mode == DISSOLVE_PARTIAL_TRANSPARENCY ||
spec->partial_transparency_mode == PARTIAL_TRANSPARENCY_IMPOSSIBLE )
known_absent |= 4 ;
if( ALPHA(spec->default_pixel) >= 128 ) known_absent |= 12 ;
else if( spec->default_pixel == FORCE_ALPHA_CHANNEL ) assume_present |= 8 ;
status = 15 - (known_absent | assume_present) ;
for( y=0; status && y<spec->dim.height; y++ ) {
rgba *row = allPixels[y] ;
if( (status & 3) != 0 ) {
/* We're still interested in color */
for( x=0; status && x<spec->dim.width; x++ ) {
if( NULLALPHA(row[x]) )
status &= ~8 ;
else {
rgba full = row[x] | (255 << ALPHA_SHIFT) ;
if( !FULLALPHA(row[x]) ) status &= ~12 ;
if( full == NEWALPHA(0,255) || full == NEWALPHA(-1,255) )
/* Black or white */ ;
else if( degrayPixel(row[x]) != -1 )
status &= ~2 ; /* gray */
else
status &= ~3 ; /* color */
}
}
} else {
/* Not interested in color */
for( x=0; status && x<spec->dim.width; x++ ) {
if( NULLALPHA(row[x]) )
status &= ~8 ;
else if( !FULLALPHA(row[x]) )
status &= ~12 ;
}
}
}
status |= known_absent ;
switch( spec->out_color_mode ) {
case COLOR_INDEXED: /* The caller takes responsibility */
case COLOR_RGB: /* Everything is fine. */
break ;
case COLOR_GRAY:
- if( (status & 1) == 0 )
+ if( (status & 1) == 0 ) {
FatalGeneric(103,
_("Grayscale output selected, but colored pixel(s) found"));
+ return XCF_ERROR;
+ }
break ;
case COLOR_MONO:
- if( (status & 2) == 0 )
+ if( (status & 2) == 0 ) {
FatalGeneric(103,_("Monochrome output selected, but not all pixels "
"are black or white"));
+ return XCF_ERROR;
+ }
break ;
case COLOR_BY_FILENAME: /* Should not happen ... */
case COLOR_BY_CONTENTS:
if( (status & 1) == 0 )
spec->out_color_mode = COLOR_RGB ;
else if( (status & 2) == 0 )
spec->out_color_mode = COLOR_GRAY ;
else
spec->out_color_mode = COLOR_MONO ;
break ;
}
if( (status & 12) == 12 ) /* No transparency found */
spec->default_pixel = NEWALPHA(colormap[0],255);
else if( (status & 12) == 4 )
spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ;
+ return XCF_OK;
}
diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatten.c b/plugins/impex/xcf/3rdparty/xcftools/flatten.c
index c021bbe9e8..bfce8bf099 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/flatten.c
+++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.c
@@ -1,690 +1,725 @@
/* Flattning functions for xcftools
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#include "xcftools.h"
#include "flatten.h"
#include "pixels.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static rgba __ATTRIBUTE__((noinline,const))
composite_one(rgba bot,rgba top)
{
unsigned tfrac, alpha ;
tfrac = ALPHA(top) ;
alpha = 255 ;
if( !FULLALPHA(bot) ) {
alpha = 255 ^ scaletable[255-ALPHA(bot)][255-ALPHA(top)] ;
/* This peculiar combination of ^ and - makes the GCC code
* generator for i386 particularly happy.
*/
tfrac = (256*ALPHA(top) - 1) / alpha ;
/* Tfrac is the fraction of the coposited pixel's covered area
* that comes from the top pixel.
* For mathematical accuracy we ought to scale by 255 and
* subtract alpha/2, but this is faster, and never misses the
* true value by more than one 1/255. This effect is completely
* overshadowed by the linear interpolation in the first place.
* (I.e. gamma is ignored when combining intensities).
* [In any case, complete fairness is not possible: if the
* bottom pixel had alpha=170 and the top has alpha=102,
* each should contribute equally to the color of the
* resulting alpha=204 pixel, which is not possible in general]
* Subtracting one helps the topfrac never be 256, which would
* be bad.
* On the other hand it means that we would get tfrac=-1 if the
* top pixel is completely transparent, and we get a division
* by zero if _both_ pixels are fully transparent. These cases
* must be handled by all callers.
* More snooping in the Gimp sources reveal that it uses
* floating-point for its equivalent of tfrac when the
* bottom layer has an alpha channel. (alphify() macro
* in paint-funcs.c). What gives?
*/
}
return (alpha << ALPHA_SHIFT)
+ ((uint32_t)scaletable[ tfrac ][255&(top>>RED_SHIFT )] << RED_SHIFT )
+ ((uint32_t)scaletable[ tfrac ][255&(top>>GREEN_SHIFT)] << GREEN_SHIFT )
+ ((uint32_t)scaletable[ tfrac ][255&(top>>BLUE_SHIFT )] << BLUE_SHIFT )
+ ((uint32_t)scaletable[255^tfrac][255&(bot>>RED_SHIFT )] << RED_SHIFT )
+ ((uint32_t)scaletable[255^tfrac][255&(bot>>GREEN_SHIFT)] << GREEN_SHIFT )
+ ((uint32_t)scaletable[255^tfrac][255&(bot>>BLUE_SHIFT )] << BLUE_SHIFT )
;
}
/* merge_normal() takes ownership of bot.
* merge_normal() will share ownership of top.
* Return: may be shared.
*/
static struct Tile * __ATTRIBUTE__((noinline))
merge_normal(struct Tile *bot, struct Tile *top)
{
unsigned i ;
assertTileCompatibility(bot,top);
/* See if there is an easy winner */
if( (bot->summary & TILESUMMARY_ALLNULL) ||
(top->summary & TILESUMMARY_ALLFULL) ) {
freeTile(bot);
return top ;
}
if( top->summary & TILESUMMARY_ALLNULL ) {
freeTile(top);
return bot ;
}
/* Try hard to make top win */
for( i=0; ; i++ ) {
if( i == top->count ) {
freeTile(bot);
return top ;
}
if( !(NULLALPHA(bot->pixels[i]) || FULLALPHA(top->pixels[i])) )
break ;
}
INIT_SCALETABLE_IF( !(top->summary & TILESUMMARY_CRISP) );
/* Otherwise bot wins, but is forever changed ... */
if( (top->summary & TILESUMMARY_ALLNULL) == 0 ) {
unsigned i ;
invalidateSummary(bot,0);
for( i=0 ; i < top->count ; i++ ) {
if( !NULLALPHA(top->pixels[i]) ) {
if( FULLALPHA(top->pixels[i]) || NULLALPHA(bot->pixels[i]) )
bot->pixels[i] = top->pixels[i] ;
else
bot->pixels[i] = composite_one(bot->pixels[i],top->pixels[i]);
}
}
}
freeTile(top);
return bot ;
}
#define exotic_combinator static unsigned __ATTRIBUTE__((const))
exotic_combinator
ucombine_ADDITION(uint8_t bot,uint8_t top)
{
return bot+top > 255 ? 255 : bot+top ;
}
exotic_combinator
ucombine_SUBTRACT(uint8_t bot,uint8_t top)
{
return top>bot ? 0 : bot-top ;
}
exotic_combinator
ucombine_LIGHTEN_ONLY(uint8_t bot,uint8_t top)
{
return top > bot ? top : bot ;
}
exotic_combinator
ucombine_DARKEN_ONLY(uint8_t bot,uint8_t top)
{
return top < bot ? top : bot ;
}
exotic_combinator
ucombine_DIFFERENCE(uint8_t bot,uint8_t top)
{
return top > bot ? top-bot : bot-top ;
}
exotic_combinator
ucombine_MULTIPLY(uint8_t bot,uint8_t top)
{
return scaletable[bot][top] ;
}
exotic_combinator
ucombine_DIVIDE(uint8_t bot,uint8_t top)
{
int result = (int)bot*256 / (1+top) ;
return result >= 256 ? 255 : result ;
}
exotic_combinator
ucombine_SCREEN(uint8_t bot,uint8_t top)
{
/* An inverted version of "multiply" */
return 255 ^ scaletable[255-bot][255-top] ;
}
exotic_combinator
ucombine_OVERLAY(uint8_t bot,uint8_t top)
{
return scaletable[bot][bot] +
2*scaletable[top][scaletable[bot][255-bot]] ;
/* This strange formula is equivalent to
* (1-top)*(bot^2) + top*(1-(1-top)^2)
* that is, the top value is used to interpolate between
* the self-multiply and the self-screen of the bottom.
*/
/* Note: This is exactly what the "Soft light" effect also
* does, though with different code in the Gimp.
*/
}
exotic_combinator
ucombine_DODGE(uint8_t bot,uint8_t top)
{
return ucombine_DIVIDE(bot,255-top);
}
exotic_combinator
ucombine_BURN(uint8_t bot,uint8_t top)
{
return 255 - ucombine_DIVIDE(255-bot,top);
}
exotic_combinator
ucombine_HARDLIGHT(uint8_t bot,uint8_t top)
{
if( top >= 128 )
return 255 ^ scaletable[255-bot][2*(255-top)] ;
else
return scaletable[bot][2*top];
/* The code that implements "hardlight" in Gimp 2.2.10 has some
* rounding errors, but this is undoubtedly what is meant.
*/
}
exotic_combinator
ucombine_GRAIN_EXTRACT(uint8_t bot,uint8_t top)
{
int temp = (int)bot - (int)top + 128 ;
return temp < 0 ? 0 : temp >= 256 ? 255 : temp ;
}
exotic_combinator
ucombine_GRAIN_MERGE(uint8_t bot,uint8_t top)
{
int temp = (int)bot + (int)top - 128 ;
return temp < 0 ? 0 : temp >= 256 ? 255 : temp ;
}
struct HSV {
enum { HUE_RED_GREEN_BLUE,HUE_RED_BLUE_GREEN,HUE_BLUE_RED_GREEN,
HUE_BLUE_GREEN_RED,HUE_GREEN_BLUE_RED,HUE_GREEN_RED_BLUE } hue;
unsigned ch1, ch2, ch3 ;
};
static void
RGBtoHSV(rgba rgb,struct HSV *hsv)
{
unsigned RED = (uint8_t)(rgb >> RED_SHIFT);
unsigned GREEN = (uint8_t)(rgb >> GREEN_SHIFT);
unsigned BLUE = (uint8_t)(rgb >> BLUE_SHIFT) ;
#define HEXTANT(b,m,t) hsv->ch1 = b, hsv->ch2 = m, hsv->ch3 = t, \
hsv->hue = HUE_ ## b ## _ ## m ## _ ## t
if( GREEN <= RED )
if( BLUE <= RED )
if( GREEN <= BLUE )
HEXTANT(GREEN,BLUE,RED);
else
HEXTANT(BLUE,GREEN,RED);
else
HEXTANT(GREEN,RED,BLUE);
else if( BLUE <= RED )
HEXTANT(BLUE,RED,GREEN);
else if( BLUE <= GREEN )
HEXTANT(RED,BLUE,GREEN);
else
HEXTANT(RED,GREEN,BLUE);
#undef HEXTANT
}
/* merge_exotic() destructively updates bot.
* merge_exotic() reads but does not free top.
*/
-static void __ATTRIBUTE__((noinline))
+static int __ATTRIBUTE__((noinline))
merge_exotic(struct Tile *bot, const struct Tile *top,
GimpLayerModeEffects mode)
{
unsigned i ;
assertTileCompatibility(bot,top);
- if( (bot->summary & TILESUMMARY_ALLNULL) != 0 ) return ;
- if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return ;
+ if( (bot->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK;
+ if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK;
assert( bot->refcount == 1 );
/* The transparency status of bot never changes */
INIT_SCALETABLE_IF(1);
for( i=0; i < top->count ; i++ ) {
uint32_t RED, GREEN, BLUE ;
if( NULLALPHA(bot->pixels[i]) || NULLALPHA(top->pixels[i]) )
continue ;
#define UNIFORM(mode) case GIMP_ ## mode ## _MODE: \
RED = ucombine_ ## mode (bot->pixels[i]>>RED_SHIFT , \
top->pixels[i]>>RED_SHIFT ); \
GREEN = ucombine_ ## mode (bot->pixels[i]>>GREEN_SHIFT, \
top->pixels[i]>>GREEN_SHIFT); \
BLUE = ucombine_ ## mode (bot->pixels[i]>>BLUE_SHIFT , \
top->pixels[i]>>BLUE_SHIFT ); \
break ;
switch( mode ) {
case GIMP_NORMAL_MODE:
case GIMP_DISSOLVE_MODE:
- FatalUnexpected("Normal and Dissolve mode can't happen here!");
+ {
+ FatalUnexpected("Normal and Dissolve mode can't happen here!");
+ return XCF_ERROR;
+ }
UNIFORM(ADDITION);
UNIFORM(SUBTRACT);
UNIFORM(LIGHTEN_ONLY);
UNIFORM(DARKEN_ONLY);
UNIFORM(DIFFERENCE);
UNIFORM(MULTIPLY);
UNIFORM(DIVIDE);
UNIFORM(SCREEN);
case GIMP_SOFTLIGHT_MODE: /* A synonym for "overlay"! */
UNIFORM(OVERLAY);
UNIFORM(DODGE);
UNIFORM(BURN);
UNIFORM(HARDLIGHT);
UNIFORM(GRAIN_EXTRACT);
UNIFORM(GRAIN_MERGE);
case GIMP_HUE_MODE:
case GIMP_SATURATION_MODE:
case GIMP_VALUE_MODE:
case GIMP_COLOR_MODE:
{
static struct HSV hsvTop, hsvBot ;
RGBtoHSV(top->pixels[i],&hsvTop);
if( mode == GIMP_HUE_MODE && hsvTop.ch1 == hsvTop.ch3 )
continue ;
RGBtoHSV(bot->pixels[i],&hsvBot);
if( mode == GIMP_VALUE_MODE ) {
if( hsvBot.ch3 ) {
hsvBot.ch1 = (hsvBot.ch1*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3;
hsvBot.ch2 = (hsvBot.ch2*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3;
hsvBot.ch3 = hsvTop.ch3 ;
} else {
hsvBot.ch1 = hsvBot.ch2 = hsvBot.ch3 = hsvTop.ch3 ;
}
} else {
unsigned mfNum, mfDenom ;
if( mode == GIMP_HUE_MODE || mode == GIMP_COLOR_MODE ) {
mfNum = hsvTop.ch2-hsvTop.ch1 ;
mfDenom = hsvTop.ch3-hsvTop.ch1 ;
hsvBot.hue = hsvTop.hue ;
} else {
mfNum = hsvBot.ch2-hsvBot.ch1 ;
mfDenom = hsvBot.ch3-hsvBot.ch1 ;
}
if( mode == GIMP_SATURATION_MODE ) {
if( hsvTop.ch3 == 0 )
hsvBot.ch1 = hsvBot.ch3 ; /* Black has no saturation */
else
hsvBot.ch1 = (hsvTop.ch1*hsvBot.ch3 + hsvTop.ch3/2) / hsvTop.ch3;
} else if( mode == GIMP_COLOR_MODE ) {
/* GIMP_COLOR_MODE works in HSL space instead of HSV. We must
* transfer H and S, keeping the L = ch1+ch3 of the bottom pixel,
* but the S we transfer works differently from the S in HSV.
*/
unsigned L = hsvTop.ch1 + hsvTop.ch3 ;
unsigned sNum = hsvTop.ch3 - hsvTop.ch1 ;
unsigned sDenom = L < 256 ? L : 510-L ;
if( sDenom == 0 ) sDenom = 1 ; /* sNum will be 0 */
L = hsvBot.ch1 + hsvBot.ch3 ;
if( L < 256 ) {
/* Ideally we want to compute L/2 * (1-sNum/sDenom)
* But shuffle this a bit so we can use integer arithmetic.
* The "-1" in the rounding prevents us from ending up with
* ch1 > ch3.
*/
hsvBot.ch1 = (L*(sDenom-sNum)+sDenom-1)/(2*sDenom);
hsvBot.ch3 = L - hsvBot.ch1 ;
} else {
/* Here our goal is 255 - (510-L)/2 * (1-sNum/sDenom) */
hsvBot.ch3 = 255 - ((510-L)*(sDenom-sNum)+sDenom-1)/(2*sDenom);
hsvBot.ch1 = L - hsvBot.ch3 ;
}
assert(hsvBot.ch3 <= 255);
assert(hsvBot.ch3 >= hsvBot.ch1);
}
if( mfDenom == 0 )
hsvBot.ch2 = hsvBot.ch1 ;
else
hsvBot.ch2 = hsvBot.ch1 +
(mfNum*(hsvBot.ch3-hsvBot.ch1) + mfDenom/2) / mfDenom ;
}
switch( hsvBot.hue ) {
#define HEXTANT(b,m,t) case HUE_ ## b ## _ ## m ## _ ## t : \
b = hsvBot.ch1; m = hsvBot.ch2; t = hsvBot.ch3; break;
HEXTANT(RED,GREEN,BLUE);
HEXTANT(RED,BLUE,GREEN);
HEXTANT(BLUE,RED,GREEN);
HEXTANT(BLUE,GREEN,RED);
HEXTANT(GREEN,BLUE,RED);
HEXTANT(GREEN,RED,BLUE);
#undef HEXTANT
- default:
- FatalUnexpected("Hue hextant is %d", hsvBot.hue);
+ default: {
+
+ FatalUnexpected("Hue hextant is %d", hsvBot.hue);
+ return XCF_ERROR;
+ }
}
break ;
}
default:
- FatalUnsupportedXCF(_("'%s' layer mode"),
+ {
+ FatalUnsupportedXCF(_("'%s' layer mode"),
_(showGimpLayerModeEffects(mode)));
+ return XCF_ERROR;
+ }
}
if( FULLALPHA(bot->pixels[i] & top->pixels[i]) )
bot->pixels[i] = (bot->pixels[i] & (255 << ALPHA_SHIFT)) +
(RED << RED_SHIFT) +
(GREEN << GREEN_SHIFT) +
(BLUE << BLUE_SHIFT) ;
else {
rgba bp = bot->pixels[i] ;
/* In a sane world, the alpha of the top pixel would simply be
* used to interpolate linearly between the bottom pixel's base
* color and the effect-computed color.
* But no! What the Gimp actually does is empirically
* described by the following (which borrows code from
* composite_one() that makes no theoretical sense here):
*/
unsigned tfrac = ALPHA(top->pixels[i]) ;
if( !FULLALPHA(bp) ) {
unsigned pseudotop = (tfrac < ALPHA(bp) ? tfrac : ALPHA(bp));
unsigned alpha = 255 ^ scaletable[255-ALPHA(bp)][255-pseudotop] ;
tfrac = (256*pseudotop - 1) / alpha ;
}
bot->pixels[i] = (bp & (255 << ALPHA_SHIFT)) +
((rgba)scaletable[ tfrac ][ RED ] << RED_SHIFT ) +
((rgba)scaletable[ tfrac ][ GREEN ] << GREEN_SHIFT) +
((rgba)scaletable[ tfrac ][ BLUE ] << BLUE_SHIFT ) +
((rgba)scaletable[255^tfrac][255&(bp>>RED_SHIFT )] << RED_SHIFT ) +
((rgba)scaletable[255^tfrac][255&(bp>>GREEN_SHIFT)] << GREEN_SHIFT) +
((rgba)scaletable[255^tfrac][255&(bp>>BLUE_SHIFT )] << BLUE_SHIFT ) ;
}
}
- return ;
+ return XCF_OK;
}
static void
dissolveTile(struct Tile *tile)
{
unsigned i ;
summary_t summary ;
assert( tile->refcount == 1 );
if( (tile->summary & TILESUMMARY_CRISP) )
return ;
summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL
+ TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ;
for( i = 0 ; i < tile->count ; i++ ) {
if( FULLALPHA(tile->pixels[i]) )
summary &= ~TILESUMMARY_ALLNULL ;
else if ( NULLALPHA(tile->pixels[i]) )
summary &= ~TILESUMMARY_ALLFULL ;
else if( ALPHA(tile->pixels[i]) > rand() % 0xFF ) {
tile->pixels[i] |= 255 << ALPHA_SHIFT ;
summary &= ~TILESUMMARY_ALLNULL ;
} else {
tile->pixels[i] = 0 ;
summary &= ~TILESUMMARY_ALLFULL ;
}
}
tile->summary = summary ;
}
static void
roundAlpha(struct Tile *tile)
{
unsigned i ;
summary_t summary ;
assert( tile->refcount == 1 );
if( (tile->summary & TILESUMMARY_CRISP) )
return ;
summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL
+ TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ;
for( i = 0 ; i < tile->count ; i++ ) {
if( ALPHA(tile->pixels[i]) >= 128 ) {
tile->pixels[i] |= 255 << ALPHA_SHIFT ;
summary &= ~TILESUMMARY_ALLNULL ;
} else {
tile->pixels[i] = 0 ;
summary &= ~TILESUMMARY_ALLFULL ;
}
}
tile->summary = summary ;
}
/* flattenTopdown() shares ownership of top.
* The return value may be a shared tile.
*/
static struct Tile *
flattenTopdown(struct FlattenSpec *spec, struct Tile *top,
unsigned nlayers, const struct rect *where)
{
struct Tile *tile;
while( nlayers-- ) {
if( tileSummary(top) & TILESUMMARY_ALLFULL )
return top ;
if( !spec->layers[nlayers].isVisible )
continue ;
tile = getLayerTile(&spec->layers[nlayers],where);
+ if (tile == XCF_PTR_EMPTY) {
+ return XCF_PTR_EMPTY;
+ }
if( tile->summary & TILESUMMARY_ALLNULL )
continue ; /* Simulate a tail call */
switch( spec->layers[nlayers].mode ) {
case GIMP_NORMAL_NOPARTIAL_MODE:
roundAlpha(tile) ;
/* Falls through */
case GIMP_DISSOLVE_MODE:
dissolveTile(tile);
/* Falls through */
case GIMP_NORMAL_MODE:
top = merge_normal(tile,top);
break ;
default:
{
struct Tile *below, *above ;
unsigned i ;
if( !(top->summary & TILESUMMARY_ALLNULL) ) {
rgba tile_or = 0 ;
invalidateSummary(tile,0);
for( i=0; i<top->count; i++ )
if( FULLALPHA(top->pixels[i]) )
tile->pixels[i] = 0 ;
else
tile_or |= tile->pixels[i] ;
/* If the tile only has pixels that will be covered by 'top' anyway,
* forget it anyway.
*/
if( ALPHA(tile_or) == 0 ) {
freeTile(tile);
break ; /* from the switch, which will continue the while */
}
}
/* Create a dummy top for the layers below this */
if( top->summary & TILESUMMARY_CRISP ) {
above = forkTile(top);
+ if(above == XCF_PTR_EMPTY) {
+ return XCF_PTR_EMPTY;
+ }
} else {
summary_t summary = TILESUMMARY_ALLNULL ;
above = newTile(*where);
for( i=0; i<top->count; i++ )
if( FULLALPHA(top->pixels[i]) ) {
above->pixels[i] = -1 ;
summary = 0 ;
} else
above->pixels[i] = 0 ;
above->summary = TILESUMMARY_UPTODATE + TILESUMMARY_CRISP + summary;
}
below = flattenTopdown(spec, above, nlayers, where);
+ if (below == XCF_PTR_EMPTY) {
+ return XCF_PTR_EMPTY;
+ }
if( below->refcount > 1 ) {
- assert( below == top );
+ if (below != top) {
+ return XCF_PTR_EMPTY;
+ }
/* This can only happen if 'below' is a copy of 'top'
* THROUGH 'above', which in turn means that none of all
* this is visible after all. So just free it and return 'top'.
*/
freeTile(below);
return top ;
}
- merge_exotic(below,tile,spec->layers[nlayers].mode);
+ if (merge_exotic(below,tile,spec->layers[nlayers].mode) != XCF_OK) {
+ return XCF_PTR_EMPTY;
+ }
freeTile(tile);
top = merge_normal(below,top);
return top ;
}
}
}
return top ;
}
-static void
+static int
addBackground(struct FlattenSpec *spec, struct Tile *tile, unsigned ncols)
{
unsigned i ;
if( tileSummary(tile) & TILESUMMARY_ALLFULL )
- return ;
+ return XCF_OK;
switch( spec->partial_transparency_mode ) {
case FORBID_PARTIAL_TRANSPARENCY:
- if( !(tileSummary(tile) & TILESUMMARY_CRISP) )
+ if( !(tileSummary(tile) & TILESUMMARY_CRISP) ) {
FatalGeneric(102,_("Flattened image has partially transparent pixels"));
+ return XCF_ERROR;
+ }
break ;
case DISSOLVE_PARTIAL_TRANSPARENCY:
dissolveTile(tile);
break ;
case ALLOW_PARTIAL_TRANSPARENCY:
case PARTIAL_TRANSPARENCY_IMPOSSIBLE:
break ;
}
if( spec->default_pixel == CHECKERED_BACKGROUND ) {
INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP ) );
for( i=0; i<tile->count; i++ )
if( !FULLALPHA(tile->pixels[i]) ) {
rgba fillwith = ((i/ncols)^(i%ncols))&8 ? 0x66 : 0x99 ;
fillwith = graytable[fillwith] + (255 << ALPHA_SHIFT) ;
if( NULLALPHA(tile->pixels[i]) )
tile->pixels[i] = fillwith ;
else
tile->pixels[i] = composite_one(fillwith,tile->pixels[i]);
}
tile->summary = TILESUMMARY_UPTODATE +
TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ;
- return ;
+ return XCF_OK;
}
- if( !FULLALPHA(spec->default_pixel) ) return ;
+ if( !FULLALPHA(spec->default_pixel) ) return XCF_OK;
if( tileSummary(tile) & TILESUMMARY_ALLNULL ) {
fillTile(tile,spec->default_pixel);
} else {
INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP) );
for( i=0; i<tile->count; i++ )
if( NULLALPHA(tile->pixels[i]) )
tile->pixels[i] = spec->default_pixel ;
else if( FULLALPHA(tile->pixels[i]) )
;
else
tile->pixels[i] = composite_one(spec->default_pixel,tile->pixels[i]);
tile->summary = TILESUMMARY_UPTODATE +
TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ;
}
+ return XCF_OK;
}
-void
+int
flattenIncrementally(struct FlattenSpec *spec,lineCallback callback)
{
rgba *rows[TILE_HEIGHT] ;
unsigned i, y, nrows, ncols ;
struct rect where ;
struct Tile *tile ;
static struct Tile toptile ;
toptile.count = TILE_HEIGHT * TILE_WIDTH ;
fillTile(&toptile,0);
for( where.t = spec->dim.c.t; where.t < spec->dim.c.b; where.t=where.b ) {
where.b = TILE_TOP(where.t)+TILE_HEIGHT ;
if( where.b > spec->dim.c.b ) where.b = spec->dim.c.b ;
nrows = where.b - where.t ;
for( y = 0; y < nrows ; y++ )
rows[y] = xcfmalloc(4*(spec->dim.c.r-spec->dim.c.l));
for( where.l = spec->dim.c.l; where.l < spec->dim.c.r; where.l=where.r ) {
where.r = TILE_LEFT(where.l)+TILE_WIDTH ;
if( where.r > spec->dim.c.r ) where.r = spec->dim.c.r ;
ncols = where.r - where.l ;
toptile.count = ncols * nrows ;
toptile.refcount = 2 ; /* For bug checking */
assert( toptile.summary == TILESUMMARY_UPTODATE +
TILESUMMARY_ALLNULL + TILESUMMARY_CRISP );
tile = flattenTopdown(spec,&toptile,spec->numLayers,&where) ;
+ if (tile == XCF_PTR_EMPTY) {
+ return XCF_ERROR;
+ }
toptile.refcount-- ; /* addBackground may change destructively */
- addBackground(spec,tile,ncols);
+ if (addBackground(spec,tile,ncols) != XCF_OK) {
+ return XCF_ERROR;
+ }
for( i = 0 ; i < tile->count ; i++ )
if( NULLALPHA(tile->pixels[i]) )
tile->pixels[i] = 0 ;
for( y = 0 ; y < nrows ; y++ )
memcpy(rows[y] + (where.l - spec->dim.c.l),
tile->pixels + y * ncols, ncols*4);
if( tile == &toptile ) {
fillTile(&toptile,0);
} else {
freeTile(tile);
}
}
for( y = 0 ; y < nrows ; y++ )
callback(spec->dim.width,rows[y]);
}
+ return XCF_OK;
}
static rgba **collectPointer ;
static void
collector(unsigned num,rgba *row)
{
num += 0;
*collectPointer++ = row ;
}
rgba **
flattenAll(struct FlattenSpec *spec)
{
rgba **rows = xcfmalloc(spec->dim.height * sizeof(rgba*));
if( verboseFlag )
fprintf(stderr,_("Flattening image ..."));
collectPointer = rows ;
- flattenIncrementally(spec,collector);
+ if (flattenIncrementally(spec,collector) != XCF_OK) {
+ xcffree(rows);
+ collectPointer = XCF_PTR_EMPTY;
+ return XCF_PTR_EMPTY;
+ }
if( verboseFlag )
fprintf(stderr,"\n");
return rows ;
}
void
shipoutWithCallback(struct FlattenSpec *spec, rgba **pixels,
lineCallback callback)
{
unsigned i ;
for( i = 0; i < spec->dim.height; i++ ) {
callback(spec->dim.width,pixels[i]);
}
xcffree(pixels);
}
diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatten.h b/plugins/impex/xcf/3rdparty/xcftools/flatten.h
index 2047cbca6d..672afaa39b 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/flatten.h
+++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.h
@@ -1,77 +1,77 @@
/* Flattning functions for xcftools
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#ifndef FLATTEN_H
#define FLATTEN_H
#include "xcftools.h"
#include "pixels.h"
#define PERHAPS_ALPHA_CHANNEL (NEWALPHA(0,1))
#define FORCE_ALPHA_CHANNEL (NEWALPHA(0,2))
#define CHECKERED_BACKGROUND (NEWALPHA(0,200))
struct FlattenSpec {
struct tileDimensions dim ;
rgba default_pixel ;
int numLayers ;
struct xcfLayer *layers ;
const char * transmap_filename ;
const char * output_filename ;
enum out_color_mode {
COLOR_BY_FILENAME,
COLOR_BY_CONTENTS,
COLOR_INDEXED,
COLOR_RGB,
COLOR_GRAY,
COLOR_MONO
} out_color_mode ;
enum { ALLOW_PARTIAL_TRANSPARENCY,
DISSOLVE_PARTIAL_TRANSPARENCY,
FORBID_PARTIAL_TRANSPARENCY,
PARTIAL_TRANSPARENCY_IMPOSSIBLE
} partial_transparency_mode ;
enum { USE_CANVAS = 0,
MANUAL_OFFSET = 1,
MANUAL_CROP = 2,
AUTOCROP = 4 } window_mode ;
int process_in_memory ;
int gimpish_indexed ;
};
/* From flatspec.c */
void init_flatspec(struct FlattenSpec *);
-void add_layer_request(struct FlattenSpec *,const char *name);
+int add_layer_request(struct FlattenSpec *,const char *name);
struct xcfLayer *lastlayerspec(struct FlattenSpec *,const char *option);
typedef enum out_color_mode (*guesser) (struct FlattenSpec *,rgba **);
/* Call this after processing options, and after opening the XCF file */
-void complete_flatspec(struct FlattenSpec *,guesser);
-void analyse_colormode(struct FlattenSpec *,rgba **allPixels,guesser);
+int complete_flatspec(struct FlattenSpec *,guesser);
+int analyse_colormode(struct FlattenSpec *,rgba **allPixels,guesser);
/* From flatten.c */
typedef void (*lineCallback)(unsigned num,rgba *pixels);
-void flattenIncrementally(struct FlattenSpec *,lineCallback);
+int flattenIncrementally(struct FlattenSpec *,lineCallback);
rgba **flattenAll(struct FlattenSpec*);
void shipoutWithCallback(struct FlattenSpec *,rgba **pixels,lineCallback);
#endif /* FLATTEN_H */
diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.c b/plugins/impex/xcf/3rdparty/xcftools/pixels.c
index d7037937c9..51999520f2 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/pixels.c
+++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.c
@@ -1,491 +1,583 @@
/* Pixel and tile functions for xcftools
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#define DEBUG
#include "xcftools.h"
#include "pixels.h"
#include <assert.h>
#include <string.h>
rgba colormap[256] ;
unsigned colormapLength=0 ;
int
degrayPixel(rgba pixel)
{
if( ((pixel >> RED_SHIFT) & 255) == ((pixel >> GREEN_SHIFT) & 255) &&
((pixel >> RED_SHIFT) & 255) == ((pixel >> BLUE_SHIFT) & 255) )
return (pixel >> RED_SHIFT) & 255 ;
return -1 ;
}
/* ****************************************************************** */
typedef const struct _convertParams {
int bpp ;
int shift[4] ;
uint32_t base_pixel ;
const rgba *lookup ;
} convertParams ;
#define RGB_SHIFT RED_SHIFT, GREEN_SHIFT, BLUE_SHIFT
#define OPAQUE (255 << ALPHA_SHIFT)
static convertParams convertRGB = { 3, {RGB_SHIFT}, OPAQUE, 0 };
static convertParams convertRGBA = { 4, {RGB_SHIFT, ALPHA_SHIFT}, 0,0 };
static convertParams convertGRAY = { 1, {-1}, OPAQUE, graytable };
static convertParams convertGRAYA = { 2, {-1,ALPHA_SHIFT}, 0, graytable };
static convertParams convertINDEXED = { 1, {-1}, OPAQUE, colormap };
static convertParams convertINDEXEDA = { 2, {-1,ALPHA_SHIFT}, 0, colormap };
static convertParams convertColormap = { 3, {RGB_SHIFT}, 0, 0 };
static convertParams convertChannel = { 1, {ALPHA_SHIFT}, 0, 0 };
/* ****************************************************************** */
static int
-tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr)
+tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr, int* ptrOut)
{
- if( ptr == 0 )
- return 0 ;
+ if( ptr == 0 ) {
+ *ptrOut = 0;
+ return XCF_OK; /* allowed by xcf, apparently */
+ }
if( xcfL(ptr ) != dim->c.r - dim->c.l ||
- xcfL(ptr+4) != dim->c.b - dim->c.t )
+ xcfL(ptr+4) != dim->c.b - dim->c.t ) {
FatalBadXCF("Drawable size mismatch at %" PRIX32, ptr);
- return ptr += 8 ;
+ *ptrOut = XCF_PTR_EMPTY;
+ return XCF_ERROR;
+ }
+ *ptrOut = (ptr += 8) ;
+ return XCF_OK;
}
-static void
+static int
initTileDirectory(struct tileDimensions *dim,struct xcfTiles *tiles,
const char *type)
{
uint32_t ptr ;
uint32_t data ;
ptr = tiles->hierarchy ;
tiles->hierarchy = 0 ;
- if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ;
+ int ptrOut;
+ if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) {
+ return XCF_ERROR;
+ }
+ if (ptrOut == XCF_PTR_EMPTY) {
+ return XCF_OK;
+ }
+ ptr = ptrOut;
if( tiles->params == &convertChannel ) {
/* A layer mask is a channel.
* Skip a name and a property list.
*/
- xcfString(ptr,&ptr);
- while( xcfNextprop(&ptr,&data) != PROP_END )
- ;
- ptr = xcfOffset(ptr,4*4);
- if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ;
+ xcfString(ptr,&ptr);
+ PropType type;
+ int response;
+ while( (response = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) {
+
+ }
+ if (response != XCF_OK) {
+ return XCF_ERROR;
+ }
+ uint32_t ptrout;
+ if(xcfOffset(ptr,4*4, &ptrout) != XCF_OK) return XCF_ERROR;
+ ptr = ptrout;
+ if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) {
+ return XCF_ERROR;
+ }
+ if (ptrOut == XCF_PTR_EMPTY) {
+ return XCF_OK;
+ }
+ ptr = ptrOut;
}
/* The XCF format has a dummy "hierarchy" level which was
* once meant to mean something, but never happened. It contains
* the bpp value and a list of "level" pointers; but only the
* first level actually contains data.
*/
data = xcfL(ptr) ;
- if( xcfL(ptr) != tiles->params->bpp )
+ if( xcfL(ptr) != tiles->params->bpp ) {
FatalBadXCF("%"PRIu32" bytes per pixel for %s drawable",xcfL(ptr),type);
- ptr = xcfOffset(ptr+4,3*4) ;
- if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ;
+ return XCF_ERROR;
+ }
+ uint32_t ptrout;
+ if(xcfOffset(ptr+4,3*4, &ptrout) != XCF_OK) return XCF_ERROR;
+ ptr = ptrout;
+ if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) {
+ return XCF_ERROR;
+ }
+ if (ptrOut == XCF_PTR_EMPTY) {
+ return XCF_OK;
+ }
+ ptr = ptrOut;
- xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr);
+ if (xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr) != XCF_OK) {
+ return XCF_ERROR;
+ }
/* if( xcfL(ptr + dim->ntiles*4) != 0 )
FatalBadXCF("Wrong sized tile directory at %" PRIX32,ptr);*/
#define REUSE_RAW_DATA tiles->tileptrs = (uint32_t*)(xcf_file + ptr)
#if defined(WORDS_BIGENDIAN) && defined(CAN_DO_UNALIGNED_WORDS)
REUSE_RAW_DATA;
#else
# if defined(WORDS_BIGENDIAN)
if( (ptr&3) == 0 ) REUSE_RAW_DATA; else
# endif
{
unsigned i ;
tiles->tileptrs = xcfmalloc(dim->ntiles * sizeof(uint32_t)) ;
for( i = 0 ; i < dim->ntiles ; i++ )
tiles->tileptrs[i] = xcfL(ptr+i*4);
}
#endif
+ return XCF_OK;
}
-void
+int
initLayer(struct xcfLayer *layer) {
if( layer->dim.ntiles == 0 ||
(layer->pixels.hierarchy == 0 && layer->mask.hierarchy == 0) )
- return ;
+ return XCF_OK;
switch(layer->type) {
#define DEF(X) case GIMP_##X##_IMAGE: layer->pixels.params = &convert##X; break
DEF(RGB);
DEF(RGBA);
DEF(GRAY);
DEF(GRAYA);
DEF(INDEXED);
DEF(INDEXEDA);
default:
- FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type)));
+ {
+ FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type)));
+ return XCF_ERROR;
+ }
+
+ }
+ if (initTileDirectory(&layer->dim,&layer->pixels,
+ _(showGimpImageType(layer->type))) != XCF_OK) {
+ return XCF_ERROR;
}
- initTileDirectory(&layer->dim,&layer->pixels,
- _(showGimpImageType(layer->type)));
layer->mask.params = &convertChannel ;
- initTileDirectory(&layer->dim,&layer->mask,"layer mask");
+ if (initTileDirectory(&layer->dim,&layer->mask,"layer mask") != XCF_OK) {
+ return XCF_ERROR;
+ }
+ return XCF_OK;
}
-static void copyStraightPixels(rgba *dest,unsigned npixels,
+static int copyStraightPixels(rgba *dest,unsigned npixels,
uint32_t ptr,convertParams *params);
-void
+int
initColormap(void) {
uint32_t ncolors ;
if( XCF.colormapptr == 0 ) {
colormapLength = 0 ;
- return ;
+ return XCF_OK;
}
ncolors = xcfL(XCF.colormapptr) ;
- if( ncolors > 256 )
+ if( ncolors > 256 ) {
FatalUnsupportedXCF(_("Color map has more than 256 entries"));
- copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap);
+ return XCF_ERROR;
+ }
+ if(copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap) != XCF_OK) {
+ return XCF_ERROR;
+ }
colormapLength = ncolors ;
#ifdef xDEBUG
{
unsigned j ;
fprintf(stderr,"Colormap decoding OK\n");
for( j = 0 ; j < ncolors ; j++ ) {
if( j % 8 == 0 ) fprintf(stderr,"\n");
fprintf(stderr," %08x",colormap[j]);
}
fprintf(stderr,"\n");
}
#endif
+ return XCF_OK;
}
/* ****************************************************************** */
struct Tile *
newTile(struct rect r)
{
unsigned npixels = (unsigned)(r.b-r.t) * (unsigned)(r.r-r.l) ;
struct Tile *data
= xcfmalloc(sizeof(struct Tile) -
sizeof(rgba)*(TILE_HEIGHT*TILE_WIDTH - npixels)) ;
data->count = npixels ;
data->refcount = 1 ;
data->summary = 0 ;
return data ;
}
struct Tile *
forkTile(struct Tile* tile)
{
- if( ++tile->refcount <= 0 )
+ if( ++tile->refcount <= 0 ) {
FatalUnsupportedXCF(_("Unbelievably many layers?\n"
"More likely to be a bug in %s"),progname);
+ return XCF_PTR_EMPTY;
+ }
return tile ;
}
void
freeTile(struct Tile* tile)
{
if( --tile->refcount == 0 )
xcffree(tile) ;
}
summary_t
tileSummary(struct Tile *tile)
{
unsigned i ;
summary_t summary ;
if( (tile->summary & TILESUMMARY_UPTODATE) != 0 )
return tile->summary ;
summary = TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ;
for( i=0; summary && i<tile->count; i++ ) {
if( FULLALPHA(tile->pixels[i]) )
summary &= ~TILESUMMARY_ALLNULL ;
else if( NULLALPHA(tile->pixels[i]) )
summary &= ~TILESUMMARY_ALLFULL ;
else
summary = 0 ;
}
summary += TILESUMMARY_UPTODATE ;
tile->summary = summary ;
return summary ;
}
void
fillTile(struct Tile *tile,rgba data)
{
unsigned i ;
for( i = 0 ; i < tile->count ; i++ )
tile->pixels[i] = data ;
if( FULLALPHA(data) )
tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP;
else if (NULLALPHA(data) )
tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLNULL+TILESUMMARY_CRISP;
else
tile->summary = TILESUMMARY_UPTODATE ;
}
/* ****************************************************************** */
-static void
+static int
copyStraightPixels(rgba *dest,unsigned npixels,
uint32_t ptr,convertParams *params)
{
unsigned bpp = params->bpp;
const rgba *lookup = params->lookup;
rgba base_pixel = params->base_pixel ;
uint8_t *bp = xcf_file + ptr ;
- xcfCheckspace(ptr,bpp*npixels,
- "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr);
+ int response;
+ if ((response = xcfCheckspace(ptr,bpp*npixels,
+ "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr)) != XCF_OK) {
+ return XCF_ERROR;
+ }
while( npixels-- ) {
rgba pixel = base_pixel ;
unsigned i ;
for( i = 0 ; i < bpp ; i++ ) {
if( params->shift[i] < 0 ) {
pixel += lookup[*bp++] ;
} else {
pixel += *bp++ << params->shift[i] ;
}
}
*dest++ = pixel ;
}
+ return XCF_OK;
}
-static void
+static int
copyRLEpixels(rgba *dest,unsigned npixels,uint32_t ptr,convertParams *params)
{
unsigned i,j ;
rgba base_pixel = params->base_pixel ;
#ifdef xDEBUG
fprintf(stderr,"RLE stream at %x, want %u x %u pixels, base %x\n",
ptr,params->bpp,npixels,base_pixel);
#endif
-
/* This algorithm depends on the indexed byte always being the first one */
if( params->shift[0] < -1 )
base_pixel = 0 ;
for( j = npixels ; j-- ; )
dest[j] = base_pixel ;
for( i = 0 ; i < params->bpp ; i++ ) {
int shift = params->shift[i] ;
if( shift < 0 )
shift = 0 ;
for( j = 0 ; j < npixels ; ) {
int countspec ;
unsigned count ;
- xcfCheckspace(ptr,2,"RLE data stream");
+ if (xcfCheckspace(ptr,2,"RLE data stream") != XCF_OK) {
+ return XCF_ERROR;
+ }
countspec = (int8_t) xcf_file[ptr++] ;
count = countspec >= 0 ? countspec+1 : -countspec ;
if( count == 128 ) {
- xcfCheckspace(ptr,3,"RLE long count");
+ if (xcfCheckspace(ptr,3,"RLE long count") != XCF_OK) {
+ return XCF_ERROR;
+ }
count = xcf_file[ptr++] << 8 ;
count += xcf_file[ptr++] ;
}
- if( j + count > npixels )
+ if( j + count > npixels ) {
FatalBadXCF("Overlong RLE run at %"PRIX32" (plane %u, %u left)",
ptr,i,npixels-j);
+ return XCF_ERROR;
+ }
if( countspec >= 0 ) {
rgba data = (uint32_t) xcf_file[ptr++] << shift ;
while( count-- )
dest[j++] += data ;
} else {
while( count-- )
dest[j++] += (uint32_t) xcf_file[ptr++] << shift ;
}
}
if( i == 0 && params->shift[0] < 0 ) {
const rgba *lookup = params->lookup ;
base_pixel = params->base_pixel ;
for( j = npixels ; j-- ; ) {
dest[j] = lookup[dest[j]-base_pixel] + base_pixel ;
}
}
}
#ifdef xDEBUG
fprintf(stderr,"RLE decoding OK at %"PRIX32"\n",ptr);
/*
for( j = 0 ; j < npixels ; j++ ) {
if( j % 8 == 0 ) fprintf(stderr,"\n");
fprintf(stderr," %8x",dest[j]);
}
fprintf(stderr,"\n");
*/
#endif
+ return XCF_OK;
}
-static void
+static int
copyTilePixels(struct Tile *dest, uint32_t ptr,convertParams *params)
{
if( FULLALPHA(params->base_pixel) )
dest->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP;
else
dest->summary = 0 ;
switch( XCF.compression ) {
case COMPRESS_NONE:
- copyStraightPixels(dest->pixels,dest->count,ptr,params);
+ if (copyStraightPixels(dest->pixels,dest->count,ptr,params) != XCF_OK) {
+ return XCF_ERROR;
+ }
break ;
case COMPRESS_RLE:
- copyRLEpixels(dest->pixels,dest->count,ptr,params);
+ if (copyRLEpixels(dest->pixels,dest->count,ptr,params) != XCF_OK) {
+ return XCF_ERROR;
+ }
break ;
default:
- FatalUnsupportedXCF(_("%s compression"),
+ {
+ FatalUnsupportedXCF(_("%s compression"),
_(showXcfCompressionType(XCF.compression)));
+ return XCF_ERROR;
+ }
}
+ return XCF_OK;
}
struct Tile *
getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles,
struct rect want)
{
struct Tile *tile = newTile(want);
- assert( want.l < want.r && want.t < want.b );
+ if (want.l >= want.r || want.t >= want.b ) {
+ freeTile(tile);
+ return XCF_PTR_EMPTY;
+ }
+
+
if( tiles->tileptrs == 0 ) {
fillTile(tile,0);
return tile ;
}
#ifdef xDEBUG
fprintf(stderr,"getMaskOrLayer: (%d-%d),(%d-%d)\n",left,right,top,bottom);
#endif
if( isSubrect(want,dim->c) &&
(want.l - dim->c.l) % TILE_WIDTH == 0 &&
(want.t - dim->c.t) % TILE_HEIGHT == 0 ) {
int tx = TILE_NUM(want.l - dim->c.l);
int ty = TILE_NUM(want.t - dim->c.t);
if( want.r == TILEXn(*dim,tx+1) && want.b == TILEYn(*dim,ty+1) ) {
/* The common case? An entire single tile from the layer */
- copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params);
+ if (copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params) != XCF_OK) {
+ freeTile(tile);
+ return XCF_PTR_EMPTY;
+ }
return tile ;
}
}
/* OK, we must construct the wanted tile as a jigsaw */
{
unsigned width = want.r-want.l ;
rgba *pixvert = tile->pixels ;
rgba *pixhoriz ;
int y, ty, l0, l1 ;
int x, tx, c0, c1 ;
unsigned lstart, lnum ;
unsigned cstart, cnum ;
if( !isSubrect(want,dim->c) ) {
if( want.l < dim->c.l ) pixvert += (dim->c.l - want.l),
want.l = dim->c.l ;
if( want.r > dim->c.r ) want.r = dim->c.r ;
if( want.t < dim->c.t ) pixvert += (dim->c.t - want.t) * width,
want.t = dim->c.t ;
if( want.b > dim->c.b ) want.b = dim->c.b ;
fillTile(tile,0);
} else {
tile->summary = -1 ; /* I.e. whatever the jigsaw pieces say */
}
#ifdef xDEBUG
fprintf(stderr,"jig0 (%d-%d),(%d-%d)\n",left,right,top,bottom);
#endif
for( y=want.t, ty=TILE_NUM(want.t-dim->c.t), l0=TILEYn(*dim,ty);
y<want.b;
pixvert += lnum*width, ty++, y=l0=l1 ) {
l1 = TILEYn(*dim,ty+1) ;
lstart = y - l0 ;
lnum = (l1 > want.b ? want.b : l1) - y ;
pixhoriz = pixvert ;
for( x=want.l, tx=TILE_NUM(want.l-dim->c.l), c0=TILEXn(*dim,tx);
x<want.r;
pixhoriz += cnum, tx++, x=c0=c1 ) {
c1 = TILEXn(*dim,tx+1);
cstart = x - c0 ;
cnum = (c1 > want.r ? want.r : c1) - x ;
{
static struct Tile tmptile ;
unsigned dwidth = c1-c0 ;
unsigned i, j ;
tmptile.count = (c1-c0)*(l1-l0) ;
#ifdef xDEBUG
fprintf(stderr,"jig ty=%u(%u-%u-%u)(%u+%u) tx=%u(%u-%u-%u)(%u+%u)\n",
ty,l0,y,l1,lstart,lnum,
tx,c0,x,c1,cstart,cnum);
#endif
- copyTilePixels(&tmptile,
- tiles->tileptrs[tx+ty*dim->tilesx],tiles->params);
+ if (copyTilePixels(&tmptile,
+ tiles->tileptrs[tx+ty*dim->tilesx],tiles->params) != XCF_OK) {
+ freeTile(tile);
+ return XCF_PTR_EMPTY;
+ }
+
for(i=0; i<lnum; i++)
for(j=0; j<cnum; j++)
pixhoriz[i*width+j]
= tmptile.pixels[(i+lstart)*dwidth+(j+cstart)];
tile->summary &= tmptile.summary ;
}
}
}
}
return tile ;
}
void
applyMask(struct Tile *tile, struct Tile *mask)
{
unsigned i ;
assertTileCompatibility(tile,mask);
assert( tile->count == mask->count );
INIT_SCALETABLE_IF(1);
invalidateSummary(tile,0);
for( i=0; i < tile->count ;i++ )
tile->pixels[i] = NEWALPHA(tile->pixels[i],
scaletable[mask->pixels[i]>>ALPHA_SHIFT]
[ALPHA(tile->pixels[i])]);
freeTile(mask);
}
struct Tile *
getLayerTile(struct xcfLayer *layer,const struct rect *where)
{
struct Tile *data ;
#ifdef xDEBUG
fprintf(stderr,"getLayerTile(%s): (%d-%d),(%d-%d)\n",
layer->name,where->l,where->r,where->t,where->b);
#endif
if( disjointRects(*where,layer->dim.c) ||
layer->opacity == 0 ) {
data = newTile(*where);
fillTile(data,0);
return data ;
}
data = getMaskOrLayerTile(&layer->dim,&layer->pixels,*where);
+ if (data == XCF_PTR_EMPTY) {
+ return XCF_PTR_EMPTY;
+ }
if( (data->summary & TILESUMMARY_ALLNULL) != 0 )
return data ;
if( layer->hasMask ) {
struct Tile *mask = getMaskOrLayerTile(&layer->dim,&layer->mask,*where);
+ if (mask == XCF_PTR_EMPTY) { /* error */
+ return XCF_PTR_EMPTY;
+ }
applyMask(data,mask);
}
if( layer->opacity < 255 ) {
const uint8_t *ourtable ;
int i ;
invalidateSummary(data,~(TILESUMMARY_CRISP | TILESUMMARY_ALLFULL));
INIT_SCALETABLE_IF(1);
ourtable = scaletable[layer->opacity] ;
for( i=0; i < data->count; i++ )
data->pixels[i]
= NEWALPHA(data->pixels[i],ourtable[ALPHA(data->pixels[i])]) ;
}
return data ;
}
diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.h b/plugins/impex/xcf/3rdparty/xcftools/pixels.h
index 5342351367..6351784b3c 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/pixels.h
+++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.h
@@ -1,130 +1,130 @@
/* Pixel and tile functions for xcftools
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#ifndef PIXELS_H
#define PIXELS_H
#include "xcftools.h"
/* MACROS FOR INTERNAL PIXEL ORDERING HERE */
/*=========================================*/
/* In principle the internal representation of pixels may change.
* - this was supposed to allow an optimization where a layer could
* be represented as a pointer into the mmapped xcf file, if
* alignment, bpp, and endianness agreed (the point was that the
* pixel representation had to agree with the endianness).
*
* However, it turns out that the current Gimp _always_ saves images
* with RLE encoding of tiles, so such an effort would be in vain.
*
* Just for modularity, nevertheless try to isolate knowledge of
* the RGBA-to-machine-word packing in this section of the
* header file. Define new macros if necessary.
*
* Given that we don't have to agree with the uncompressed
* RLE format, we choose to have the alpha in the _least_
* significant byte on all archs - it is tested and used more
* often than the visible channels.
*/
typedef uint32_t rgba ;
#define ALPHA_SHIFT 0
#define RED_SHIFT 8
#define GREEN_SHIFT 16
#define BLUE_SHIFT 24
#define ALPHA(rgba) ((uint8_t)(rgba))
#define FULLALPHA(rgba) ((uint8_t)(rgba) == 255)
#define NULLALPHA(rgba) ((uint8_t)(rgba) == 0)
#define NEWALPHA(rgb,a) (((rgba)(rgb) & 0xFFFFFF00) + (a))
#ifdef PRECOMPUTED_SCALETABLE
extern const uint8_t scaletable[256][256] ;
#define INIT_SCALETABLE_IF(foo) ((void)0)
#else
extern uint8_t scaletable[256][256] ;
extern int ok_scaletable ;
void mk_scaletable(void);
#define INIT_SCALETABLE_IF(foo) \
(ok_scaletable || !(foo) || (mk_scaletable(),0) )
#endif
extern const rgba graytable[256] ;
extern rgba colormap[256] ;
extern unsigned colormapLength ;
-void initLayer(struct xcfLayer *);
-void initColormap();
+int initLayer(struct xcfLayer *);
+int initColormap();
int degrayPixel(rgba); /* returns -1 for non-gray pixels */
/* ******************************************************* */
#define TILEXn(dim,tx) \
((tx)==(dim).tilesx ? (dim).c.r : (dim).c.l + ((tx)*TILE_WIDTH))
#define TILEYn(dim,ty) \
((ty)==(dim).tilesy ? (dim).c.b : (dim).c.t + ((ty)*TILE_HEIGHT))
#if defined(__i386__)
/* This is probably the only common architecture where small constants
* are more efficient for byte operations.
*/
typedef int8_t summary_t ;
typedef short int refcount_t ;
#else
typedef int summary_t ;
typedef int refcount_t ;
#endif
#define TILESUMMARY_UPTODATE 8
#define TILESUMMARY_ALLNULL 4
#define TILESUMMARY_ALLFULL 2
#define TILESUMMARY_CRISP 1 /* everything either null or full */
struct Tile {
refcount_t refcount ;
summary_t summary ; /* a combination of TIMESUMMARY_FOO constatns */
unsigned count ;
rgba pixels[TILE_WIDTH * TILE_HEIGHT];
};
/* Actually, the Tile structures that get allocated many not have
* room for that many pixels. We subtract the space for those we don't
* use - which is Not Legal C, but ought to be portable.
* OTOH, one can also use a static struct Tile for temporary storage.
*/
#define assertTileCompatibility(t1,t2) assert((t1)->count==(t2)->count)
struct Tile *newTile(struct rect);
struct Tile *forkTile(struct Tile*);
void freeTile(struct Tile*);
#define invalidateSummary(tile,mask) \
do{ assert((tile)->refcount==1); (tile)->summary &= mask; } while(0)
summary_t __ATTRIBUTE__((pure)) tileSummary(struct Tile *);
void fillTile(struct Tile*,rgba);
/* applyMask() destructively changes tile,
* applyMask() gets ownership of mask
*/
void applyMask(struct Tile *tile, struct Tile *mask);
struct Tile *getLayerTile(struct xcfLayer *,const struct rect *);
struct Tile * getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles, struct rect want);
#endif /* FLATTEN_H */
diff --git a/plugins/impex/xcf/3rdparty/xcftools/utils.c b/plugins/impex/xcf/3rdparty/xcftools/utils.c
index 37b5aa8236..06958d0be6 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/utils.c
+++ b/plugins/impex/xcf/3rdparty/xcftools/utils.c
@@ -1,159 +1,168 @@
/* Generic support functions for Xcftools
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#include "xcftools.h"
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <errno.h>
const char *progname = "$0" ;
int verboseFlag = 0 ;
-static void __ATTRIBUTE__((noreturn))
+void
vFatalGeneric(int status,const char *format,va_list args)
{
if( format ) {
if( *format == '!' ) {
vfprintf(stderr,format+1,args);
fprintf(stderr,": %s\n",strerror(errno));
} else {
vfprintf(stderr,format,args);
fputc('\n',stderr);
}
}
- exit(status);
+ /* don't exit here - Krita can't handle errors otherwise */
+ /* exit(status); */
}
void
FatalGeneric(int status,const char* format,...)
{
va_list v; va_start(v,format);
if( format ) fprintf(stderr,"%s: ",progname);
vFatalGeneric(status,format,v);
}
void
FatalUnexpected(const char* format,...)
{
va_list v; va_start(v,format);
fprintf(stderr,"%s: ",progname);
vFatalGeneric(127,format,v) ;
}
void
FatalBadXCF(const char* format,...)
{
va_list v; va_start(v,format);
fprintf(stderr,"%s: %s:\n ",progname,_("Corrupted or malformed XCF file"));
vFatalGeneric(125,format,v) ;
}
-void
+int
xcfCheckspace(uint32_t addr,int spaceafter,const char *format,...)
{
if( xcf_length < spaceafter || addr > xcf_length - spaceafter ) {
va_list v; va_start(v,format);
fprintf(stderr,"%s: %s\n ",progname,_("Corrupted or truncated XCF file"));
fprintf(stderr,"(0x%" PRIXPTR " bytes): ",(uintptr_t)xcf_length);
vFatalGeneric(125,format,v) ;
+ return XCF_ERROR;
}
+ return XCF_OK;
}
void
FatalUnsupportedXCF(const char* format,...)
{
va_list v; va_start(v,format);
fprintf(stderr,"%s: %s\n ",progname,
_("The image contains features not understood by this program:"));
vFatalGeneric(123,format,v) ;
}
void
gpl_blurb(void)
{
fprintf(stderr,PACKAGE_STRING "\n");
fprintf(stderr,
_("Type \"%s -h\" to get an option summary.\n"),progname);
- exit(1) ;
+ /* don't exit here - Krita will close otherwise */
+ /* exit(1) ; */
}
/* ******************************************************* */
void *
xcfmalloc(size_t size)
{
void *ptr = malloc(size);
- if( !ptr )
+ if( !ptr ) {
FatalUnexpected(_("Out of memory"));
+ return XCF_PTR_EMPTY;
+ }
return ptr ;
}
void
xcffree(void *block)
{
if( xcf_file &&
(uint8_t*)block >= xcf_file &&
(uint8_t*)block < xcf_file + xcf_length )
;
else
free(block);
}
/* ******************************************************* */
FILE *
openout(const char *name)
{
FILE *newfile ;
if( strcmp(name,"-") == 0 )
return stdout ;
newfile = fopen(name,"wb") ;
- if( newfile == NULL )
+ if( newfile == NULL ) {
FatalUnexpected(_("!Cannot create file %s"),name);
+ return XCF_PTR_EMPTY;
+ }
return newfile ;
}
-void
+int
closeout(FILE *f,const char *name)
{
if( f == NULL )
- return ;
+ return XCF_OK;
if( fflush(f) == 0 ) {
errno = 0 ;
if( !ferror(f) ) {
if( fclose(f) == 0 )
- return ;
+ return XCF_OK;
} else if( errno == 0 ) {
/* Attempt to coax a valid errno out of the standard library,
* following an idea by Bruno Haible
* http://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html
*/
if( fputc('\0', f) != EOF &&
fflush(f) == 0 )
errno = EIO ; /* Argh, everything succedes. Just call it an I/O error */
}
}
FatalUnexpected(_("!Error writing file %s"),name);
+ return XCF_ERROR;
}
diff --git a/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c b/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c
index e5ae5a6049..c6966526ab 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c
+++ b/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c
@@ -1,305 +1,388 @@
/* Generic functions for reading XCF files
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#include "xcftools.h"
#include <string.h>
#include <errno.h>
#ifdef HAVE_ICONV
# include <iconv.h>
#elif !defined(ICONV_CONST)
# define ICONV_CONST const
#endif
uint8_t *xcf_file = 0 ;
size_t xcf_length ;
int use_utf8 = 0 ;
-uint32_t
-xcfOffset(uint32_t addr,int spaceafter)
+int
+xcfOffset(uint32_t addr,int spaceafter, uint32_t* apparent)
{
- uint32_t apparent ;
- xcfCheckspace(addr,4,"(xcfOffset)");
- apparent = xcfL(addr);
- xcfCheckspace(apparent,spaceafter,
+ if (!apparent) {
+ return XCF_ERROR;
+ }
+ if(xcfCheckspace(addr,4,"(xcfOffset)") != XCF_OK) {
+ return XCF_ERROR;
+ }
+ *apparent = xcfL(addr);
+ if (xcfCheckspace(*apparent,spaceafter,
"Too large offset (%" PRIX32 ") at position %" PRIX32,
- apparent,addr);
- return apparent ;
+ *apparent,addr) != XCF_OK) {
+ return XCF_ERROR;
+ }
+ return XCF_OK;
}
int
-xcfNextprop(uint32_t *master,uint32_t *body)
+xcfNextprop(uint32_t *master,uint32_t *body, PropType *typeOut)
{
+ int response;
+
+ if (typeOut == 0) {
+ return XCF_ERROR;
+ }
+
uint32_t ptr, length, total, minlength ;
PropType type ;
ptr = *master ;
- xcfCheckspace(ptr,8,"(property header)");
+ if ((response = xcfCheckspace(ptr,8,"(property header)")) != XCF_OK) {
+ return XCF_ERROR;
+ }
type = xcfL(ptr);
length = xcfL(ptr+4);
*body = ptr+8 ;
switch(type) {
case PROP_COLORMAP:
{
uint32_t ncolors ;
- xcfCheckspace(ptr+8,4,"(colormap length)");
+ if ((response = xcfCheckspace(ptr+8,4,"(colormap length)")) != XCF_OK) {
+ return XCF_ERROR;
+ }
ncolors = xcfL(ptr+8) ;
- if( ncolors > 256 )
+ if( ncolors > 256 ) {
FatalBadXCF("Colormap has %" PRIu32 " entries",ncolors);
+ return XCF_ERROR;
+ }
+
/* Surprise! Some older version of the Gimp computed the wrong length
* word, and the _reader_ always just reads three bytes per color
* and ignores the length tag! Duplicate this so we too can read
* the buggy XCF files.
*/
length = minlength = 4+3*ncolors;
break;
}
case PROP_COMPRESSION: minlength = 1; break;
case PROP_OPACITY: minlength = 4; break;
case PROP_APPLY_MASK: minlength = 4; break;
case PROP_OFFSETS: minlength = 8; break;
case PROP_MODE: minlength = 4; break;
default: minlength = 0; break;
}
- if( length < minlength )
+ if( length < minlength ) {
FatalBadXCF("Short %s property at %" PRIX32 " (%" PRIu32 "<%" PRIu32 ")",
showPropType(type),ptr,length,minlength);
+ return XCF_ERROR;
+ }
*master = ptr+8+length ;
total = 8 + length + (type != PROP_END ? 8 : 0) ;
- if( total < length ) /* Check overwrap */
+ if( total < length ) { /* Check overwrap */
FatalBadXCF("Overlong property at %" PRIX32, ptr);
- xcfCheckspace(ptr,total,"Overlong property at %" PRIX32,ptr) ;
- return type ;
+ return XCF_ERROR;
+ }
+ if((response = xcfCheckspace(ptr,total,"Overlong property at %" PRIX32,ptr)) != 0) {
+ return XCF_ERROR;
+ }
+ *typeOut = type;
+ return XCF_OK;
}
const char*
xcfString(uint32_t ptr,uint32_t *after)
{
uint32_t length ;
unsigned i ;
ICONV_CONST char *utf8master ;
- xcfCheckspace(ptr,4,"(string length)");
+ if (xcfCheckspace(ptr,4,"(string length)") != XCF_OK) {
+ return XCF_PTR_EMPTY;
+ }
length = xcfL(ptr) ;
ptr += 4 ;
- xcfCheckspace(ptr,length,"(string)");
+ if (xcfCheckspace(ptr,length,"(string)") != XCF_OK) {
+ return XCF_PTR_EMPTY;
+ }
utf8master = (ICONV_CONST char*)(xcf_file+ptr) ;
if( after ) *after = ptr + length ;
- if( length == 0 || utf8master[length-1] != 0 )
+ if( length == 0 || utf8master[length-1] != 0 ) {
FatalBadXCF("String at %" PRIX32 " not zero-terminated",ptr-4);
+ return XCF_PTR_EMPTY;
+ }
length-- ;
if( use_utf8 ) return utf8master ;
/* We assume that the local character set includes ASCII...
* Check if conversion is needed at all
*/
for( i=0 ; ; i++ ) {
if( i == length )
return utf8master ; /* Only ASCII after all */
- if( utf8master[i] == 0 )
+ if( utf8master[i] == 0 ) {
FatalBadXCF("String at %" PRIX32 " has embedded zeroes",ptr-4);
+ return XCF_PTR_EMPTY;
+ }
if( (int8_t) utf8master[i] < 0 )
break ;
}
#ifdef HAVE_ICONV
{
size_t targetsize = length+1 ;
int sloppy_translation = 0 ;
iconv_t cd = iconv_open("//TRANSLIT","UTF-8");
if( cd == (iconv_t) -1 ) {
cd = iconv_open("","UTF-8");
sloppy_translation = 1 ;
}
if( cd == (iconv_t) -1 )
iconv_close(cd) ; /* Give up; perhaps iconv doesn't know UTF-8 */
else
while(1) {
char *buffer = xcfmalloc(targetsize) ;
ICONV_CONST char *inbuf = utf8master ;
char *outbuf = buffer ;
size_t incount = length ;
size_t outcount = targetsize ;
while(1) { /* Loop for systems without //ICONV support */
size_t result = iconv(cd,&inbuf,&incount,&outbuf,&outcount) ;
if( result == (size_t)-1 && errno == EILSEQ &&
sloppy_translation && outcount > 0 ) {
*outbuf++ = '?' ;
outcount-- ;
while( (int8_t)*inbuf < 0 ) inbuf++, incount-- ;
continue ;
}
if( result != (size_t)-1 ) {
if( outcount == 0 )
errno = E2BIG ;
else {
*outbuf = 0 ;
iconv_close(cd) ;
return buffer ;
}
}
break ;
}
- if( errno == EILSEQ || errno == EINVAL )
+ if( errno == EILSEQ || errno == EINVAL ) {
FatalBadXCF("Bad UTF-8 encoding '%s' at %" PRIXPTR,
inbuf,(uintptr_t)((inbuf-utf8master)+ptr));
+ return XCF_PTR_EMPTY;
+ }
if( errno == E2BIG ) {
targetsize += 1+incount ;
xcffree(buffer) ;
continue ;
}
FatalUnexpected("!iconv on layer name at %"PRIX32,ptr);
+ return XCF_PTR_EMPTY:
}
}
#endif
{
static int warned = 0 ;
if( !warned ) {
fprintf(stderr,_("Warning: one or more layer names could not be\n"
" translated to the local character set.\n"));
warned = 1 ;
}
}
return utf8master ;
}
/* ****************************************************************** */
void
computeDimensions(struct tileDimensions *d)
{
d->c.r = d->c.l + d->width ;
d->c.b = d->c.t + d->height ;
d->tilesx = (d->width+TILE_WIDTH-1)/TILE_WIDTH ;
d->tilesy = (d->height+TILE_HEIGHT-1)/TILE_HEIGHT ;
d->ntiles = d->tilesx * d->tilesy ;
}
struct xcfImage XCF ;
-void
+int
getBasicXcfInfo(void)
{
+
uint32_t ptr, data, layerfile ;
PropType type ;
int i, j ;
+
+ int errorStatus;
+ uint32_t ptrout;
+
+ if (xcfCheckspace(0,14+7*4,"(very short)") != XCF_OK) {
+ return XCF_ERROR;
+ }
- xcfCheckspace(0,14+7*4,"(very short)");
if( strcmp((char*)xcf_file,"gimp xcf file") == 0 )
XCF.version = 0 ;
else if( xcf_file[13] == 0 &&
sscanf((char*)xcf_file,"gimp xcf v%d",&XCF.version) == 1 )
;
- else
+ else {
FatalBadXCF(_("Not an XCF file at all (magic not recognized)"));
+ return XCF_ERROR;
+ }
if (XCF.version < 0 || XCF.version > 3) {
- return;
+ return XCF_ERROR;
}
XCF.compression = COMPRESS_NONE ;
XCF.colormapptr = 0 ;
ptr = 14 ;
XCF.width = xcfL(ptr); ptr += 4 ;
XCF.height = xcfL(ptr); ptr += 4 ;
XCF.type = xcfL(ptr); ptr += 4 ;
- while( (type = xcfNextprop(&ptr,&data)) != PROP_END ) {
+ while( (errorStatus = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) {
+ if (errorStatus != XCF_OK) {
+ return XCF_ERROR;
+ }
+
switch(type) {
case PROP_COLORMAP:
XCF.colormapptr = data ;
break ;
case PROP_COMPRESSION:
XCF.compression = xcf_file[data] ;
break ;
default:
/* Ignore unknown properties */
break ;
}
}
layerfile = ptr ;
- for( XCF.numLayers = 0 ; xcfOffset(ptr,8*4) ; XCF.numLayers++, ptr+=4 )
- ;
+ XCF.numLayers = 0;
+ while (1) {
+ errorStatus = xcfOffset(ptr,8*4, &ptrout);
+ if (errorStatus != XCF_OK) {
+ return XCF_ERROR;
+ }
+ if (!ptrout) {
+ break;
+ }
+ XCF.numLayers++;
+ ptr+=4;
+ }
XCF.layers = xcfmalloc(XCF.numLayers * sizeof(struct xcfLayer)) ;
for( i = 0 ; i < XCF.numLayers ; i++ ) {
struct xcfLayer *L = XCF.layers + i ;
ptr = xcfL(layerfile+4*(XCF.numLayers-1-i)) ;
L->mode = GIMP_NORMAL_MODE ;
L->opacity = 255 ;
L->isVisible = 1 ;
L->hasMask = 0 ;
L->dim.width = xcfL(ptr); ptr+=4 ;
L->dim.height = xcfL(ptr); ptr+=4 ;
L->type = xcfL(ptr); ptr+=4 ;
L->name = xcfString(ptr,&ptr);
+ if (L->name == XCF_PTR_EMPTY) {
+ return XCF_ERROR;
+ }
L->propptr = ptr ;
L->isGroup = 0;
L->pathLength = 0;
L->path = NULL;
- while( (type = xcfNextprop(&ptr,&data)) != PROP_END ) {
+ while( (errorStatus = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) {
+ if (errorStatus != XCF_OK) {
+ xcffree(XCF.layers);
+ XCF.layers = XCF_PTR_EMPTY;
+ return XCF_ERROR;
+ }
switch(type) {
case PROP_OPACITY:
L->opacity = xcfL(data);
if( L->opacity > 255 )
L->opacity = 255 ;
break ;
case PROP_VISIBLE:
L->isVisible = xcfL(data) != 0 ;
break ;
case PROP_APPLY_MASK:
L->hasMask = xcfL(data) != 0 ;
break ;
case PROP_OFFSETS:
L->dim.c.l = (int32_t)(xcfL(data )) ;
L->dim.c.t = (int32_t)(xcfL(data+4)) ;
break ;
case PROP_MODE:
L->mode = xcfL(data);
break ;
case PROP_GROUP_ITEM:
L->isGroup = 1 ;
break;
case PROP_ITEM_PATH:
L->pathLength = (ptr - data - 2) / 4 ;
if ( L->pathLength != 0 ) {
L->path = xcfmalloc( L->pathLength * sizeof(unsigned) ) ;
for ( j = 0; j!=L->pathLength; j++ )
*(L->path + j) = (unsigned)xcfL(data + 4 * j);
}
break;
default:
/* Ignore unknown properties */
break ;
}
}
- xcfCheckspace(ptr,8,"(end of layer %s)",L->name);
+ if ((errorStatus = xcfCheckspace(ptr,8,"(end of layer %s)",L->name)) != XCF_OK) {
+ xcffree(XCF.layers);
+ XCF.layers = XCF_PTR_EMPTY;
+ return XCF_ERROR;
+ }
L->pixels.tileptrs = 0 ;
- L->pixels.hierarchy = xcfOffset(ptr ,4*4);
+
+ if (xcfOffset(ptr , 4*4, &(L->pixels.hierarchy)) != XCF_OK) {
+ xcffree(XCF.layers);
+ XCF.layers = XCF_PTR_EMPTY;
+ return XCF_ERROR;
+ }
L->mask.tileptrs = 0 ;
- L->mask.hierarchy = xcfOffset(ptr+4,4*4);
+ if (xcfOffset(ptr+4, 4*4, &(L->mask.hierarchy)) != XCF_OK) {
+ xcffree(XCF.layers);
+ XCF.layers = XCF_PTR_EMPTY;
+ return XCF_ERROR;
+ }
computeDimensions(&L->dim);
+
}
+ return XCF_OK;
}
diff --git a/plugins/impex/xcf/3rdparty/xcftools/xcftools.h b/plugins/impex/xcf/3rdparty/xcftools/xcftools.h
index 7b5eb5f00b..501067fbd9 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/xcftools.h
+++ b/plugins/impex/xcf/3rdparty/xcftools/xcftools.h
@@ -1,201 +1,207 @@
/* Generic functions and macros for reading XCF files
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#ifndef XCFTOOLS_H
#define XCFTOOLS_H
#include "config.h"
#include "enums.h"
#include <stddef.h>
#include <stdio.h>
#if defined(HAVE_GETTEXT) && defined(ENABLE_NLS)
#include <libintl.h>
#define _(s) gettext(s)
void nls_init(void);
#else
#define _(s) (s)
#define nls_init() (void)0
#endif
#define N_(s) (s)
#if HAVE_INTTYPES_H
# define __STDC_FORMAT_MACROS
# include <inttypes.h>
#else
/* These legacy fall-backs will probably work on every system
* that does not supply a inttypes.h ... */
typedef unsigned char uint8_t ;
typedef unsigned long int uint32_t;
typedef signed char int8_t ;
typedef signed long int int32_t ;
# define PRIX32 "lX"
# define PRIu32 "lu"
# define PRIXPTR "lX"
#endif
#if __GNUC__
# define __ATTRIBUTE__ __attribute__
#else
# define __ATTRIBUTE__(x)
#endif
#if HAVE_NETINET_IN_H
# include <netinet/in.h>
#elif HAVE_ARPA_INET_H
# include <arpa/inet.h>
#elif WORDS_BIGENDIAN
# define ntohl(x) (x)
#else
static inline uint32_t ntohl(uint32_t a) {
return (a << 24) + ((a & 0xFF00) << 8) + ((a >> 8) & 0xFF00) + (a >> 24) ;
}
#endif
#ifndef HAVE_STRCASECMP
#define strcasecmp strcmp
#endif
/* Read a single word value from the XCF file */
/* Use + instead of | because that allows LEA instructions */
#define xcfBE(a) ( ((uint32_t)xcf_file[(a) ] << 24) + \
((uint32_t)xcf_file[(a)+1] << 16) + \
((uint32_t)xcf_file[(a)+2] << 8 ) + \
((uint32_t)xcf_file[(a)+3] ) )
#define xcfLE(a) ( ((uint32_t)xcf_file[(a) ] ) + \
((uint32_t)xcf_file[(a)+1] << 8 ) + \
((uint32_t)xcf_file[(a)+2] << 16) + \
((uint32_t)xcf_file[(a)+3] << 24) )
#if defined(CAN_DO_UNALIGNED_WORDS)
# define xcfL(a) ntohl(*(uint32_t *)(xcf_file + (a)))
#else
# define xcfL(a) ((a) & 3 ? xcfBE(a) : ntohl(*(uint32_t *)(xcf_file + (a))))
#endif
/* ****************************************************************** */
/* The following are exported from am OS-specific source file;
* io-unix.c on unixish systems.
*/
void read_or_mmap_xcf(const char* filename, const char *unzipper);
void free_or_close_xcf(void);
/* ****************************************************************** */
/* utils.c */
+
+#define XCF_ERROR 1
+#define XCF_OK 0
+#define XCF_PTR_EMPTY 0
+
+
extern const char *progname ;
extern int verboseFlag ;
void *xcfmalloc(size_t size);
void xcffree(void*);
void FatalGeneric(int status,const char* format,...)
- __ATTRIBUTE__((format(printf,2,3),noreturn)) ;
+ __ATTRIBUTE__((format(printf,2,3))) ;
void FatalUnexpected(const char* format,...)
- __ATTRIBUTE__((format(printf,1,2),noreturn)) ;
+ __ATTRIBUTE__((format(printf,1,2))) ;
void FatalBadXCF(const char* format,...)
- __ATTRIBUTE__((format(printf,1,2),noreturn)) ;
+ __ATTRIBUTE__((format(printf,1,2))) ;
void FatalUnsupportedXCF(const char* format,...)
- __ATTRIBUTE__((format(printf,1,2),noreturn)) ;
+ __ATTRIBUTE__((format(printf,1,2))) ;
-void gpl_blurb(void) __ATTRIBUTE__((noreturn));
+void gpl_blurb(void);
FILE* openout(const char*);
-void closeout(FILE *,const char*);
+int closeout(FILE *,const char*);
struct rect {
int t, b, l, r ;
};
#define isSubrect(A,B) \
((A).l >= (B).l && (A).r <= (B).r && (A).t >= (B).t && (A).b <= (B).b)
#define disjointRects(A,B) \
((A).l >= (B).r || (A).r <= (B).l || (A).t >= (B).b || (A).b <= (B).t)
/* ****************************************************************** */
/* xcf-general.c */
extern uint8_t *xcf_file ;
extern size_t xcf_length ;
extern int use_utf8 ;
-void xcfCheckspace(uint32_t addr,int spaceafter, const char *format,...)
+int xcfCheckspace(uint32_t addr,int spaceafter, const char *format,...)
__ATTRIBUTE__((format(printf,3,4)));
-uint32_t xcfOffset(uint32_t addr,int spaceafter);
+int xcfOffset(uint32_t addr,int spaceafter, uint32_t* apparent);
-int xcfNextprop(uint32_t *master,uint32_t *body);
+int xcfNextprop(uint32_t *master,uint32_t *body, PropType* type);
const char* xcfString(uint32_t ptr,uint32_t *after);
/* These are hardcoded in the Gimp sources: */
#define TILE_SHIFT 6
#define TILE_WIDTH (1<<TILE_SHIFT)
#define TILE_HEIGHT (1<<TILE_SHIFT)
/* These definitions of TILE_LEFT and TILE_TOP work correctly for negative
* numbers, but on the other hand depend on TILE_WIDTH and TILE_HEIGHT
* being powers of 2. That's okay, because the tile size cannot change
* anyway.
*/
#define TILE_LEFT(x) ((x) & -TILE_WIDTH)
#define TILE_TOP(y) ((y) & -TILE_HEIGHT)
#define TILE_NUM(x) ((x) >> TILE_SHIFT)
struct tileDimensions {
struct rect c ;
unsigned width, height ;
unsigned tilesx, tilesy ;
unsigned ntiles ;
};
/* computeDimensions assumes that width, height, c.l, and c.t are set */
void computeDimensions(struct tileDimensions *);
struct xcfTiles {
const struct _convertParams *params ;
uint32_t *tileptrs ;
uint32_t hierarchy ;
};
struct xcfLayer {
struct tileDimensions dim ;
const char *name ;
GimpLayerModeEffects mode ;
GimpImageType type ;
unsigned int opacity ;
int isVisible, hasMask ;
uint32_t propptr ;
struct xcfTiles pixels ;
struct xcfTiles mask ;
int isGroup ;
unsigned pathLength ;
unsigned *path ;
};
extern struct xcfImage {
int version ;
unsigned width, height ;
GimpImageBaseType type ;
XcfCompressionType compression ;
int numLayers ;
struct xcfLayer *layers ;
uint32_t colormapptr ;
} XCF ;
-void getBasicXcfInfo(void);
+int getBasicXcfInfo(void);
#endif /* XCFTOOLS_H */
diff --git a/plugins/impex/xcf/kis_xcf_import.cpp b/plugins/impex/xcf/kis_xcf_import.cpp
index 138c82e4e9..41c14801f8 100644
--- a/plugins/impex/xcf/kis_xcf_import.cpp
+++ b/plugins/impex/xcf/kis_xcf_import.cpp
@@ -1,317 +1,330 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_xcf_import.h"
#include <ctype.h>
#include <QApplication>
#include <QFile>
#include <qendian.h>
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceTraits.h>
#include <KoCompositeOpRegistry.h>
#include <kis_debug.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_paint_layer.h>
#include <kis_transparency_mask.h>
#include "kis_iterator_ng.h"
#include "kis_types.h"
#include <KoColorModelStandardIds.h>
extern "C" {
#include "xcftools.h"
#include "pixels.h"
#define GET_RED(x) (x >> RED_SHIFT)
#define GET_GREEN(x) (x >> GREEN_SHIFT)
#define GET_BLUE(x) (x >> BLUE_SHIFT)
#define GET_ALPHA(x) (x >> ALPHA_SHIFT)
}
QString layerModeG2K(GimpLayerModeEffects mode)
{
switch (mode) {
case GIMP_NORMAL_MODE:
return COMPOSITE_OVER;
case GIMP_DISSOLVE_MODE:
return COMPOSITE_DISSOLVE;
case GIMP_MULTIPLY_MODE:
return COMPOSITE_MULT;
case GIMP_SCREEN_MODE:
return COMPOSITE_SCREEN;
case GIMP_OVERLAY_MODE:
case GIMP_SOFTLIGHT_MODE:
return COMPOSITE_OVERLAY;
case GIMP_DIFFERENCE_MODE:
return COMPOSITE_DIFF;
case GIMP_ADDITION_MODE:
return COMPOSITE_ADD;
case GIMP_SUBTRACT_MODE:
return COMPOSITE_SUBTRACT;
case GIMP_DARKEN_ONLY_MODE:
return COMPOSITE_DARKEN;
case GIMP_LIGHTEN_ONLY_MODE:
return COMPOSITE_LIGHTEN;
case GIMP_HUE_MODE:
return COMPOSITE_HUE_HSL;
case GIMP_SATURATION_MODE:
return COMPOSITE_SATURATION_HSV;
case GIMP_COLOR_MODE:
return COMPOSITE_COLOR_HSL;
case GIMP_VALUE_MODE:
return COMPOSITE_VALUE;
case GIMP_DIVIDE_MODE:
return COMPOSITE_DIVIDE;
case GIMP_DODGE_MODE:
return COMPOSITE_DODGE;
case GIMP_BURN_MODE:
return COMPOSITE_BURN;
case GIMP_ERASE_MODE:
return COMPOSITE_ERASE;
case GIMP_REPLACE_MODE:
return COMPOSITE_COPY;
case GIMP_HARDLIGHT_MODE:
return COMPOSITE_HARD_LIGHT;
case GIMP_COLOR_ERASE_MODE:
case GIMP_NORMAL_NOPARTIAL_MODE:
case GIMP_ANTI_ERASE_MODE:
case GIMP_GRAIN_EXTRACT_MODE:
return COMPOSITE_GRAIN_EXTRACT;
case GIMP_GRAIN_MERGE_MODE:
return COMPOSITE_GRAIN_MERGE;
case GIMP_BEHIND_MODE:
break;
}
dbgFile << "Unknown mode: " << mode;
return COMPOSITE_OVER;
}
struct Layer {
KisLayerSP layer;
int depth;
KisMaskSP mask;
};
KisGroupLayerSP findGroup(const QVector<Layer> &layers, const Layer& layer, int i)
{
for (; i < layers.size(); ++i) {
KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(const_cast<KisLayer*>(layers[i].layer.data()));
if (group && (layers[i].depth == layer.depth -1)) {
return group;
}
}
return 0;
}
void addLayers(const QVector<Layer> &layers, KisImageSP image, int depth)
{
for(int i = 0; i < layers.size(); i++) {
const Layer &layer = layers[i];
if (layer.depth == depth) {
KisGroupLayerSP group = (depth == 0 ? image->rootLayer() : findGroup(layers, layer, i));
image->addNode(layer.layer, group);
if (layer.mask) {
image->addNode(layer.mask, layer.layer);
}
}
}
}
K_PLUGIN_FACTORY_WITH_JSON(XCFImportFactory, "krita_xcf_import.json", registerPlugin<KisXCFImport>();)
KisXCFImport::KisXCFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisXCFImport::~KisXCFImport()
{
}
-KisImportExportFilter::ConversionStatus KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
+KisImportExportErrorCode KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
+ int errorStatus;
+
dbgFile << "Start decoding file";
QByteArray data = io->readAll();
xcf_file = (uint8_t*)data.data();
xcf_length = data.size();
io->close();
// Decode the data
- getBasicXcfInfo() ;
-
- if (XCF.version < 0 || XCF.version > 3) {
- document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer."));
- return KisImportExportFilter::UnsupportedVersion;
+ if (getBasicXcfInfo() != XCF_OK) {
+ if (XCF.version < 0 || XCF.version > 3) {
+ document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer."));
+ return ImportExportCodes::FormatFeaturesUnsupported;
+ }
+ return ImportExportCodes::FileFormatIncorrect;
}
- initColormap();
+ if(initColormap() != XCF_OK) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
dbgFile << XCF.version << "width = " << XCF.width << "height = " << XCF.height << "layers = " << XCF.numLayers;
// Create the image
KisImageSP image = new KisImage(document->createUndoStore(), XCF.width, XCF.height, KoColorSpaceRegistry::instance()->rgb8(), "built image");
QVector<Layer> layers;
uint maxDepth = 0;
// Read layers
for (int i = 0; i < XCF.numLayers; ++i) {
Layer layer;
xcfLayer& xcflayer = XCF.layers[i];
dbgFile << i << " name = " << xcflayer.name << " opacity = " << xcflayer.opacity << "group:" << xcflayer.isGroup << xcflayer.pathLength;
dbgFile << ppVar(xcflayer.dim.width) << ppVar(xcflayer.dim.height) << ppVar(xcflayer.dim.tilesx) << ppVar(xcflayer.dim.tilesy) << ppVar(xcflayer.dim.ntiles) << ppVar(xcflayer.dim.c.t) << ppVar(xcflayer.dim.c.l) << ppVar(xcflayer.dim.c.r) << ppVar(xcflayer.dim.c.b);
maxDepth = qMax(maxDepth, xcflayer.pathLength);
bool isRgbA = false;
// Select the color space
const KoColorSpace* colorSpace = 0;
switch (xcflayer.type) {
case GIMP_INDEXED_IMAGE:
case GIMP_INDEXEDA_IMAGE:
case GIMP_RGB_IMAGE:
case GIMP_RGBA_IMAGE:
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
isRgbA = true;
break;
case GIMP_GRAY_IMAGE:
case GIMP_GRAYA_IMAGE:
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), "");
isRgbA = false;
break;
}
// Create the layer
KisLayerSP kisLayer;
if (xcflayer.isGroup) {
kisLayer = new KisGroupLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity);
}
else {
kisLayer = new KisPaintLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity, colorSpace);
}
// Set some properties
kisLayer->setCompositeOpId(layerModeG2K(xcflayer.mode));
kisLayer->setVisible(xcflayer.isVisible);
kisLayer->disableAlphaChannel(xcflayer.mode != GIMP_NORMAL_MODE);
layer.layer = kisLayer;
layer.depth = xcflayer.pathLength;
// Copy the data in the image
- initLayer(&xcflayer);
+ if ((errorStatus = initLayer(&xcflayer)) != XCF_OK) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
int left = xcflayer.dim.c.l;
int top = xcflayer.dim.c.t;
if (!xcflayer.isGroup) {
// Copy the data;
for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) {
for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) {
rect want;
want.l = x + left;
want.t = y + top;
want.b = want.t + TILE_HEIGHT;
want.r = want.l + TILE_WIDTH;
Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.pixels, want);
+ if (tile == XCF_PTR_EMPTY) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
KisHLineIteratorSP it = kisLayer->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH);
rgba* data = tile->pixels;
for (int v = 0; v < TILE_HEIGHT; ++v) {
if (isRgbA) {
// RGB image
do {
KoBgrTraits<quint8>::setRed(it->rawData(), GET_RED(*data));
KoBgrTraits<quint8>::setGreen(it->rawData(), GET_GREEN(*data));
KoBgrTraits<quint8>::setBlue(it->rawData(), GET_BLUE(*data));
KoBgrTraits<quint8>::setOpacity(it->rawData(), quint8(GET_ALPHA(*data)), 1);
++data;
} while (it->nextPixel());
} else {
// Grayscale image
do {
it->rawData()[0] = GET_RED(*data);
it->rawData()[1] = GET_ALPHA(*data);
++data;
} while (it->nextPixel());
}
it->nextRow();
}
}
}
// Move the layer to its position
kisLayer->paintDevice()->setX(left);
kisLayer->paintDevice()->setY(top);
}
// Create the mask
if (xcflayer.hasMask) {
KisTransparencyMaskSP mask = new KisTransparencyMask();
layer.mask = mask;
mask->initSelection(kisLayer);
for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) {
for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) {
rect want;
want.l = x + left;
want.t = y + top;
want.b = want.t + TILE_HEIGHT;
want.r = want.l + TILE_WIDTH;
Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.mask, want);
+ if (tile == XCF_PTR_EMPTY) {
+ return ImportExportCodes::FileFormatIncorrect;
+ }
KisHLineIteratorSP it = mask->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH);
rgba* data = tile->pixels;
for (int v = 0; v < TILE_HEIGHT; ++v) {
do {
it->rawData()[0] = GET_ALPHA(*data);
++data;
} while (it->nextPixel());
it->nextRow();
}
}
}
mask->paintDevice()->setX(left);
mask->paintDevice()->setY(top);
}
dbgFile << xcflayer.pixels.tileptrs;
layers.append(layer);
}
for (uint i = 0; i <= maxDepth; ++i) {
addLayers(layers, image, i);
}
document->setCurrentImage(image);
- return KisImportExportFilter::OK;
+ return ImportExportCodes::OK;
}
#include "kis_xcf_import.moc"
diff --git a/plugins/impex/xcf/kis_xcf_import.h b/plugins/impex/xcf/kis_xcf_import.h
index cfbe32ebf6..67683cf51b 100644
--- a/plugins/impex/xcf/kis_xcf_import.h
+++ b/plugins/impex/xcf/kis_xcf_import.h
@@ -1,40 +1,40 @@
/*
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_XCF_IMPORT_H_
#define _KIS_XCF_IMPORT_H_
#include <QVariant>
#include <QIODevice>
#include <KisImportExportFilter.h>
class KisDocument;
class KisXCFImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisXCFImport(QObject *parent, const QVariantList &);
~KisXCFImport() override;
public:
- KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
+ KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override;
};
#endif
diff --git a/plugins/impex/xcf/tests/data/incorrectFormatFile.txt b/plugins/impex/xcf/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/xcf/tests/data/readonlyFile.txt b/plugins/impex/xcf/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/xcf/tests/data/writeonlyFile.txt b/plugins/impex/xcf/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/xcf/tests/kis_xcf_test.cpp b/plugins/impex/xcf/tests/kis_xcf_test.cpp
index 25c1c90c2a..51546a467a 100644
--- a/plugins/impex/xcf/tests/kis_xcf_test.cpp
+++ b/plugins/impex/xcf/tests/kis_xcf_test.cpp
@@ -1,38 +1,62 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_xcf_test.h"
#include <QTest>
#include <QCoreApplication>
#include <sdk/tests/kistest.h>
#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 XcfMimetype = "image/x-xcf";
+
+
void KisXCFTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1);
}
+
+
+void KisXCFTest::testImportFromWriteonly()
+{
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), XcfMimetype);
+}
+
+/*
+void KisXCFTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), XcfMimetype);
+}
+*/
+
+
+void KisXCFTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), XcfMimetype);
+}
+
+
KISTEST_MAIN(KisXCFTest)
diff --git a/plugins/impex/xcf/tests/kis_xcf_test.h b/plugins/impex/xcf/tests/kis_xcf_test.h
index 6ca18ec23d..11e3f37c72 100644
--- a/plugins/impex/xcf/tests/kis_xcf_test.h
+++ b/plugins/impex/xcf/tests/kis_xcf_test.h
@@ -1,31 +1,36 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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_XCF_TEST_H_
#define _KIS_XCF_TEST_H_
#include <QtTest>
class KisXCFTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
+
+ void testImportFromWriteonly();
+ // You can't export to xcf
+ /* void testExportToReadonly(); */
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/paintops/deform/deform_brush.h b/plugins/paintops/deform/deform_brush.h
index 6b8481d5fe..94eeb22b18 100644
--- a/plugins/paintops/deform/deform_brush.h
+++ b/plugins/paintops/deform/deform_brush.h
@@ -1,252 +1,253 @@
/*
* Copyright (c) 2008, 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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 _DEFORM_BRUSH_H_
#define _DEFORM_BRUSH_H_
#include <kis_paint_device.h>
#include <brushengine/kis_paint_information.h>
#include <kis_brush_size_option.h>
#include <kis_deform_option.h>
+#include "kis_algebra_2d.h"
#include <time.h>
#if defined(_WIN32) || defined(_WIN64)
#define srand48 srand
inline double drand48()
{
return double(rand()) / RAND_MAX;
}
#endif
enum DeformModes {GROW, SHRINK, SWIRL_CW, SWIRL_CCW, MOVE, LENS_IN, LENS_OUT, DEFORM_COLOR};
class DeformProperties
{
public:
int action;
qreal deformAmount;
bool useBilinear;
bool useCounter;
bool useOldData;
};
class DeformBase
{
public:
DeformBase() {}
virtual ~DeformBase() {}
virtual void transform(qreal * x, qreal * y, qreal distance) {
Q_UNUSED(x);
Q_UNUSED(y);
Q_UNUSED(distance);
}
};
/// Inverse weighted inverse scaling - grow&shrink
class DeformScale : public DeformBase
{
public:
void setFactor(qreal factor) {
m_factor = factor;
}
qreal factor() {
return m_factor;
}
void transform(qreal* x, qreal* y, qreal distance) override {
- qreal scaleFactor = (1.0 - distance) * m_factor + distance;
+ qreal scaleFactor = KisAlgebra2D::signPZ(m_factor) * (qAbs((1.0 - distance) * m_factor) + distance);
*x = *x / scaleFactor;
*y = *y / scaleFactor;
}
private:
qreal m_factor;
};
/// Inverse weighted rotation - swirlCW&&swirlCWW
class DeformRotation : public DeformBase
{
public:
void setAlpha(qreal alpha) {
m_alpha = alpha;
}
void transform(qreal* maskX, qreal* maskY, qreal distance) override {
distance = 1.0 - distance;
qreal rotX = cos(-m_alpha * distance) * (*maskX) - sin(-m_alpha * distance) * (*maskY);
qreal rotY = sin(-m_alpha * distance) * (*maskX) + cos(-m_alpha * distance) * (*maskY);
*maskX = rotX;
*maskY = rotY;
}
private:
qreal m_alpha;
};
/// Inverse move
class DeformMove : public DeformBase
{
public:
void setFactor(qreal factor) {
m_factor = factor;
}
void setDistance(qreal dx, qreal dy) {
m_dx = dx;
m_dy = dy;
}
void transform(qreal* maskX, qreal* maskY, qreal distance) override {
*maskX -= m_dx * m_factor * (1.0 - distance);
*maskY -= m_dy * m_factor * (1.0 - distance);
}
private:
qreal m_dx;
qreal m_dy;
qreal m_factor;
};
/// Inverse lens distortion
class DeformLens : public DeformBase
{
public:
void setLensFactor(qreal k1, qreal k2) {
m_k1 = k1;
m_k2 = k2;
}
void setMaxDistance(qreal maxX, qreal maxY) {
m_maxX = maxX;
m_maxY = maxY;
}
void setMode(bool out) {
m_out = out;
}
void transform(qreal* maskX, qreal* maskY, qreal distance) override {
Q_UNUSED(distance);
//normalize
qreal normX = *maskX / m_maxX;
qreal normY = *maskY / m_maxY;
qreal radius_2 = normX * normX + normY * normY;
qreal radius_4 = radius_2 * radius_2;
if (m_out) {
*maskX = normX * (1.0 + m_k1 * radius_2 + m_k2 * radius_4);
*maskY = normY * (1.0 + m_k1 * radius_2 + m_k2 * radius_4);
}
else {
*maskX = normX / (1.0 + m_k1 * radius_2 + m_k2 * radius_4);
*maskY = normY / (1.0 + m_k1 * radius_2 + m_k2 * radius_4);
}
*maskX = m_maxX * (*maskX);
*maskY = m_maxY * (*maskY);
}
private:
qreal m_k1, m_k2;
qreal m_maxX, m_maxY;
bool m_out;
};
/// Randomly disturb the pixels
class DeformColor : public DeformBase
{
public:
DeformColor() {
srand48(time(0));
}
void setFactor(qreal factor) {
m_factor = factor;
}
void transform(qreal* x, qreal* y, qreal distance) override {
Q_UNUSED(distance);
qreal randomX = m_factor * ((drand48() * 2.0) - 1.0);
qreal randomY = m_factor * ((drand48() * 2.0) - 1.0);
*x += randomX;
*y += randomY;
}
private:
qreal m_factor;
};
class DeformBrush
{
public:
DeformBrush();
~DeformBrush();
KisFixedPaintDeviceSP paintMask(KisFixedPaintDeviceSP dab, KisPaintDeviceSP layer,
qreal scale, qreal rotation, QPointF pos,
qreal subPixelX, qreal subPixelY, int dabX, int dabY);
void setSizeProperties(KisBrushSizeOptionProperties * properties) {
m_sizeProperties = properties;
}
void setProperties(DeformOption * properties) {
m_properties = properties;
}
void initDeformAction();
QPointF hotSpot(qreal scale, qreal rotation);
private:
// return true if can paint
bool setupAction(
DeformModes mode, const QPointF& pos, QTransform const& rotation);
void debugColor(const quint8* data, KoColorSpace * cs);
qreal maskWidth(qreal scale) {
return m_sizeProperties->brush_diameter * scale;
}
qreal maskHeight(qreal scale) {
return m_sizeProperties->brush_diameter * m_sizeProperties->brush_aspect * scale;
}
inline qreal norme(qreal x, qreal y) {
return x * x + y * y;
}
private:
KisRandomSubAccessorSP m_srcAcc;
bool m_firstPaint;
qreal m_prevX, m_prevY;
int m_counter;
QRectF m_maskRect;
DeformBase * m_deformAction;
DeformOption * m_properties;
KisBrushSizeOptionProperties * m_sizeProperties;
};
#endif
diff --git a/plugins/paintops/hairy/hairy_brush.cpp b/plugins/paintops/hairy/hairy_brush.cpp
index 5a59152cb5..4a71505402 100644
--- a/plugins/paintops/hairy/hairy_brush.cpp
+++ b/plugins/paintops/hairy/hairy_brush.cpp
@@ -1,444 +1,449 @@
/*
* Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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 "hairy_brush.h"
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorTransformation.h>
#include <KoCompositeOpRegistry.h>
#include <QVariant>
#include <QHash>
#include <QVector>
#include <kis_types.h>
#include <kis_random_accessor_ng.h>
#include <kis_cross_device_color_picker.h>
#include <kis_fixed_paint_device.h>
#include <cmath>
#include <ctime>
HairyBrush::HairyBrush()
{
m_counter = 0;
m_lastAngle = 0.0;
m_oldPressure = 1.0f;
m_saturationId = -1;
m_transfo = 0;
}
HairyBrush::~HairyBrush()
{
delete m_transfo;
qDeleteAll(m_bristles.begin(), m_bristles.end());
m_bristles.clear();
}
void HairyBrush::initAndCache()
{
m_compositeOp = m_dab->colorSpace()->compositeOp(COMPOSITE_OVER);
m_pixelSize = m_dab->colorSpace()->pixelSize();
if (m_properties->useSaturation) {
m_transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", m_params);
if (m_transfo) {
m_saturationId = m_transfo->parameterId("s");
}
}
}
void HairyBrush::fromDabWithDensity(KisFixedPaintDeviceSP dab, qreal density)
{
int width = dab->bounds().width();
int height = dab->bounds().height();
int centerX = width * 0.5;
int centerY = height * 0.5;
// make mask
Bristle * bristle = 0;
qreal alpha;
quint8 * dabPointer = dab->data();
quint8 pixelSize = dab->pixelSize();
const KoColorSpace * cs = dab->colorSpace();
KoColor bristleColor(cs);
KisRandomSource randomSource(0);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
alpha = cs->opacityF(dabPointer);
if (alpha != 0.0) {
if (density == 1.0 || randomSource.generateNormalized() <= density) {
memcpy(bristleColor.data(), dabPointer, pixelSize);
bristle = new Bristle(x - centerX, y - centerY, alpha); // using value from image as length of bristle
bristle->setColor(bristleColor);
m_bristles.append(bristle);
}
}
dabPointer += pixelSize;
}
}
}
void HairyBrush::paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation)
{
m_counter++;
qreal x1 = pi1.pos().x();
qreal y1 = pi1.pos().y();
qreal x2 = pi2.pos().x();
qreal y2 = pi2.pos().y();
qreal dx = x2 - x1;
qreal dy = y2 - y1;
// TODO:this angle is different from the drawing angle in sensor (info.angle()). The bug is caused probably due to
// not computing the drag vector properly in paintBezierLine when smoothing is used
//qreal angle = atan2(dy, dx);
qreal angle = rotation;
qreal mousePressure = 1.0;
if (m_properties->useMousePressure) { // want pressure from mouse movement
qreal distance = sqrt(dx * dx + dy * dy);
mousePressure = (1.0 - computeMousePressure(distance));
scale *= mousePressure;
}
// this pressure controls shear and ink depletion
qreal pressure = mousePressure * (pi2.pressure() * 2);
Bristle *bristle = 0;
KoColor bristleColor(dab->colorSpace());
m_dabAccessor = dab->createRandomAccessorNG((int)x1, (int)y1);
m_dab = dab;
// initialization block
if (firstStroke()) {
initAndCache();
}
/*If this is first time the brush touches the canvas and
we are using soak ink while ink depletion is enabled...*/
if (m_properties->inkDepletionEnabled &&
firstStroke() && m_properties->useSoakInk) {
if (layer) {
colorifyBristles(layer, pi1.pos());
}
else {
dbgKrita << "Can't soak the ink from the layer";
}
}
KisRandomSourceSP randomSource = pi2.randomSource();
qreal fx1, fy1, fx2, fy2;
qreal randomX, randomY;
qreal shear;
float inkDeplation = 0.0;
int inkDepletionSize = m_properties->inkDepletionCurve.size();
int bristleCount = m_bristles.size();
int bristlePathSize;
qreal threshold = 1.0 - pi2.pressure();
for (int i = 0; i < bristleCount; i++) {
if (!m_bristles.at(i)->enabled()) continue;
bristle = m_bristles[i];
randomX = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor;
randomY = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor;
shear = pressure * m_properties->shearFactor;
m_transform.reset();
m_transform.rotateRadians(-angle);
m_transform.scale(scale, scale);
m_transform.translate(randomX, randomY);
m_transform.shear(shear, shear);
if (firstStroke() || (!m_properties->connectedPath)) {
// transform start dab
m_transform.map(bristle->x(), bristle->y(), &fx1, &fy1);
// transform end dab
m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2);
}
else {
// continue the path of the bristle from the previous position
fx1 = bristle->prevX();
fy1 = bristle->prevY();
m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2);
}
// remember the end point
bristle->setPrevX(fx2);
bristle->setPrevY(fy2);
// all coords relative to device position
fx1 += x1;
fy1 += y1;
fx2 += x2;
fy2 += y2;
if (m_properties->threshold && (bristle->length() < threshold)) continue;
// paint between first and last dab
const QVector<QPointF> bristlePath = m_trajectory.getLinearTrajectory(QPointF(fx1, fy1), QPointF(fx2, fy2), 1.0);
bristlePathSize = m_trajectory.size();
+ // avoid overlapping bristle caps with antialias on
+ if (m_properties->antialias) {
+ bristlePathSize -= 1;
+ }
+
memcpy(bristleColor.data(), bristle->color().data() , m_pixelSize);
for (int i = 0; i < bristlePathSize ; i++) {
if (m_properties->inkDepletionEnabled) {
inkDeplation = fetchInkDepletion(bristle, inkDepletionSize);
if (m_properties->useSaturation && m_transfo != 0) {
saturationDepletion(bristle, bristleColor, pressure, inkDeplation);
}
if (m_properties->useOpacity) {
opacityDepletion(bristle, bristleColor, pressure, inkDeplation);
}
}
else {
if (bristleColor.opacityU8() != 0) {
bristleColor.setOpacity(bristle->length());
}
}
addBristleInk(bristle, bristlePath.at(i), bristleColor);
bristle->setInkAmount(1.0 - inkDeplation);
bristle->upIncrement();
}
}
m_dab = 0;
m_dabAccessor = 0;
}
inline qreal HairyBrush::fetchInkDepletion(Bristle* bristle, int inkDepletionSize)
{
if (bristle->counter() >= inkDepletionSize - 1) {
return m_properties->inkDepletionCurve[inkDepletionSize - 1];
} else {
return m_properties->inkDepletionCurve[bristle->counter()];
}
}
void HairyBrush::saturationDepletion(Bristle * bristle, KoColor &bristleColor, qreal pressure, qreal inkDeplation)
{
qreal saturation;
if (m_properties->useWeights) {
// new weighted way (experiment)
saturation = (
(pressure * m_properties->pressureWeight) +
(bristle->length() * m_properties->bristleLengthWeight) +
(bristle->inkAmount() * m_properties->bristleInkAmountWeight) +
((1.0 - inkDeplation) * m_properties->inkDepletionWeight)) - 1.0;
}
else {
// old way of computing saturation
saturation = (
pressure *
bristle->length() *
bristle->inkAmount() *
(1.0 - inkDeplation)) - 1.0;
}
m_transfo->setParameter(m_transfo->parameterId("h"), 0.0);
m_transfo->setParameter(m_transfo->parameterId("v"), 0.0);
m_transfo->setParameter(m_saturationId, saturation);
m_transfo->setParameter(3, 1);//sets the type to
m_transfo->setParameter(4, false);//sets the colorize to none.
m_transfo->transform(bristleColor.data(), bristleColor.data() , 1);
}
void HairyBrush::opacityDepletion(Bristle* bristle, KoColor& bristleColor, qreal pressure, qreal inkDeplation)
{
qreal opacity = OPACITY_OPAQUE_F;
if (m_properties->useWeights) {
opacity = pressure * m_properties->pressureWeight +
bristle->length() * m_properties->bristleLengthWeight +
bristle->inkAmount() * m_properties->bristleInkAmountWeight +
(1.0 - inkDeplation) * m_properties->inkDepletionWeight;
}
else {
opacity =
bristle->length() *
bristle->inkAmount();
}
opacity = qBound(0.0, opacity, 1.0);
bristleColor.setOpacity(opacity);
}
inline void HairyBrush::addBristleInk(Bristle *bristle,const QPointF &pos, const KoColor &color)
{
Q_UNUSED(bristle);
if (m_properties->antialias) {
if (m_properties->useCompositing) {
paintParticle(pos, color);
} else {
paintParticle(pos, color, 1.0);
}
}
else {
int ix = qRound(pos.x());
int iy = qRound(pos.y());
if (m_properties->useCompositing) {
plotPixel(ix, iy, color);
}
else {
darkenPixel(ix, iy, color);
}
}
}
void HairyBrush::paintParticle(QPointF pos, const KoColor& color, qreal weight)
{
// opacity top left, right, bottom left, right
quint8 opacity = color.opacityU8();
opacity *= weight;
int ipx = int (pos.x());
int ipy = int (pos.y());
- qreal fx = pos.x() - ipx;
- qreal fy = pos.y() - ipy;
+ qreal fx = qAbs(pos.x() - ipx);
+ qreal fy = qAbs(pos.y() - ipy);
quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity);
quint8 btr = qRound((fx) * (1.0 - fy) * opacity);
quint8 bbl = qRound((1.0 - fx) * (fy) * opacity);
quint8 bbr = qRound((fx) * (fy) * opacity);
const KoColorSpace * cs = m_dab->colorSpace();
m_dabAccessor->moveTo(ipx , ipy);
btl = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, btl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
cs->setOpacity(m_dabAccessor->rawData(), btl, 1);
m_dabAccessor->moveTo(ipx + 1, ipy);
btr = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, btr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
cs->setOpacity(m_dabAccessor->rawData(), btr, 1);
m_dabAccessor->moveTo(ipx, ipy + 1);
bbl = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, bbl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
cs->setOpacity(m_dabAccessor->rawData(), bbl, 1);
m_dabAccessor->moveTo(ipx + 1, ipy + 1);
bbr = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, bbr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
cs->setOpacity(m_dabAccessor->rawData(), bbr, 1);
}
void HairyBrush::paintParticle(QPointF pos, const KoColor& color)
{
// opacity top left, right, bottom left, right
memcpy(m_color.data(), color.data(), m_pixelSize);
quint8 opacity = color.opacityU8();
int ipx = int (pos.x());
int ipy = int (pos.y());
- qreal fx = pos.x() - ipx;
- qreal fy = pos.y() - ipy;
+ qreal fx = qAbs(pos.x() - ipx);
+ qreal fy = qAbs(pos.y() - ipy);
quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity);
quint8 btr = qRound((fx) * (1.0 - fy) * opacity);
quint8 bbl = qRound((1.0 - fx) * (fy) * opacity);
quint8 bbr = qRound((fx) * (fy) * opacity);
m_color.setOpacity(btl);
plotPixel(ipx , ipy, m_color);
m_color.setOpacity(btr);
plotPixel(ipx + 1 , ipy, m_color);
m_color.setOpacity(bbl);
plotPixel(ipx , ipy + 1, m_color);
m_color.setOpacity(bbr);
plotPixel(ipx + 1 , ipy + 1, m_color);
}
inline void HairyBrush::plotPixel(int wx, int wy, const KoColor &color)
{
m_dabAccessor->moveTo(wx, wy);
m_compositeOp->composite(m_dabAccessor->rawData(), m_pixelSize, color.data() , m_pixelSize, 0, 0, 1, 1, OPACITY_OPAQUE_U8);
}
inline void HairyBrush::darkenPixel(int wx, int wy, const KoColor &color)
{
m_dabAccessor->moveTo(wx, wy);
if (m_dab->colorSpace()->opacityU8(m_dabAccessor->rawData()) < color.opacityU8()) {
memcpy(m_dabAccessor->rawData(), color.data(), m_pixelSize);
}
}
double HairyBrush::computeMousePressure(double distance)
{
static const double scale = 20.0;
static const double minPressure = 0.02;
double oldPressure = m_oldPressure;
double factor = 1.0 - distance / scale;
if (factor < 0.0) factor = 0.0;
double result = ((4.0 * oldPressure) + minPressure + factor) / 5.0;
m_oldPressure = result;
return result;
}
void HairyBrush::colorifyBristles(KisPaintDeviceSP source, QPointF point)
{
KoColor bristleColor(m_dab->colorSpace());
KisCrossDeviceColorPickerInt colorPicker(source, bristleColor);
Bristle *b = 0;
int size = m_bristles.size();
for (int i = 0; i < size; i++) {
b = m_bristles[i];
int x = qRound(b->x() + point.x());
int y = qRound(b->y() + point.y());
colorPicker.pickOldColor(x, y, bristleColor.data());
b->setColor(bristleColor);
}
}
diff --git a/plugins/paintops/hairy/trajectory.cpp b/plugins/paintops/hairy/trajectory.cpp
index dc0518d7fa..2b83fac4b0 100644
--- a/plugins/paintops/hairy/trajectory.cpp
+++ b/plugins/paintops/hairy/trajectory.cpp
@@ -1,166 +1,167 @@
/*
* Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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 "trajectory.h"
#include <cmath>
#include <kis_debug.h>
Trajectory::Trajectory()
{
m_i = 0;
m_size = 0;
}
Trajectory::~Trajectory()
{
}
void Trajectory::addPoint(QPointF pos)
{
if (m_i >= m_path.size()) {
m_path.append(pos);
m_i++;
} else {
m_path[m_i] = pos;
m_i++;
}
m_size++;
}
void Trajectory::reset()
{
m_size = 0;
m_i = 0;
}
const QVector<QPointF> &Trajectory::getLinearTrajectory(const QPointF &start, const QPointF &end, double space)
{
Q_UNUSED(space);
reset();
// Width and height of the line
- float xd = (end.x() - start.x());
- float yd = (end.y() - start.y());
+ qreal xd = (end.x() - start.x());
+ qreal yd = (end.y() - start.y());
int x = (int)start.x();
int y = (int)start.y();
- float fx = start.x();
- float fy = start.y();
- float m = yd / xd;
+ qreal fx = start.x();
+ qreal fy = start.y();
+ qreal m = yd / xd;
int y2 = (int)end.y();
int x2 = (int)end.x();
- //m_path.append(start);
addPoint(start);
if (fabs(m) > 1) {
// y - directional axis
int incr;
if (yd > 0) {
m = 1.0f / m;
incr = 1;
} else {
m = -1.0f / m;
incr = -1;
}
while (y != y2) {
fx = fx + m;
- y = y + incr;
- x = (int)(fx + 0.5f);
- addPoint(QPointF(fx, y));
+ fy = fy + incr;
+ y += incr;
+// x = (int)(fx + 0.5f);
+ addPoint(QPointF(fx, fy));
}
} else {
// x - directional axis
int incr;
if (xd > 0) {
incr = 1;
} else {
incr = -1;
m = -m;
}
while (x != x2) {
fy = fy + m;
- x = x + incr;
- y = (int)(fy + 0.5f);
- addPoint(QPointF(x, fy));
+ fx = fx + incr;
+ x += incr;
+// y = (int)(fy + 0.5f);
+ addPoint(QPointF(fx, fy));
}
}
addPoint(end);
return m_path;
}
QVector<QPointF> Trajectory::getDDATrajectory(QPointF start, QPointF end, double space)
{
Q_UNUSED(space);
reset();
// Width and height of the line
int xd = (int)(end.x() - start.x());
int yd = (int)(end.y() - start.y());
int x = (int)start.x();
int y = (int)start.y();
float fx = start.x();
float fy = start.y();
float m = (float)yd / (float)xd;
int y2 = (int)end.y();
int x2 = (int)end.x();
if (fabs(m) > 1) {
int incr;
if (yd > 0) {
m = 1.0f / m;
incr = 1;
}
else {
m = -1.0f / m;
incr = -1;
}
while (y != y2) {
fx = fx + m;
y = y + incr;
x = (int)(fx + 0.5f);
addPoint(QPointF(x, y));
}
} else {
int incr;
if (xd > 0) {
incr = 1;
}
else {
incr = -1;
m = -m;
}
while (x != x2) {
fy = fy + m;
x = x + incr;
y = (int)(fy + 0.5f);
addPoint(QPointF(x, y));
}
}
return m_path;
}
diff --git a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp
index 8785178082..0ecdab3bf4 100644
--- a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp
+++ b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp
@@ -1,295 +1,295 @@
/*
* Copyright (c) 2004,2007,2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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 <compositeops/KoVcMultiArchBuildSupport.h> //MSVC requires that Vc come first
#include "kis_auto_brush_widget.h"
#include <kconfig.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <math.h>
#include <kis_debug.h>
#include <QSpinBox>
#include <QToolButton>
#include <QImage>
#include <QComboBox>
#include <QLabel>
#include <QPixmap>
#include <QResizeEvent>
#include <kis_fixed_paint_device.h>
#include <kis_mask_generator.h>
#include <kis_slider_spin_box.h>
#include "kis_signals_blocker.h"
#include "kis_signal_compressor.h"
#include "kis_aspect_ratio_locker.h"
#define showSlider(input, step) input->setRange(input->minimum(), input->maximum(), step)
#include <kis_cubic_curve.h>
KisAutoBrushWidget::KisAutoBrushWidget(QWidget *parent, const char* name)
: KisWdgAutoBrush(parent, name)
, m_autoBrush(0)
, m_updateCompressor(new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE))
, m_fadeAspectLocker(new KisAspectRatioLocker())
{
connect(m_updateCompressor.data(), SIGNAL(timeout()), SLOT(paramChanged()));
connect((QObject*)comboBoxShape, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start()));
- inputRadius->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2);
+ inputRadius->setRange(0.01, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2);
inputRadius->setExponentRatio(3.0);
inputRadius->setSingleStep(1);
inputRadius->setValue(5);
inputRadius->setSuffix(i18n(" px"));
inputRadius->setBlockUpdateSignalOnDrag(true);
connect(inputRadius, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start()));
inputRatio->setRange(0.0, 1.0, 2);
inputRatio->setSingleStep(0.1);
inputRatio->setValue(1.0);
inputRatio->setBlockUpdateSignalOnDrag(true);
connect(inputRatio, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start()));
inputHFade->setRange(0.0, 1.0, 2);
inputHFade->setSingleStep(0.1);
inputHFade->setValue(0.5);
inputVFade->setRange(0.0, 1.0, 2);
inputVFade->setSingleStep(0.1);
inputVFade->setValue(0.5);
aspectButton->setKeepAspectRatio(true);
m_fadeAspectLocker->connectSpinBoxes(inputHFade, inputVFade, aspectButton);
m_fadeAspectLocker->setBlockUpdateSignalOnDrag(true);
connect(m_fadeAspectLocker.data(), SIGNAL(sliderValueChanged()), m_updateCompressor.data(), SLOT(start()));
connect(m_fadeAspectLocker.data(), SIGNAL(aspectButtonChanged()), m_updateCompressor.data(), SLOT(start()));
inputSpikes->setRange(2, 20);
inputSpikes->setValue(2);
inputSpikes->setBlockUpdateSignalOnDrag(true);
connect(inputSpikes, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start()));
inputRandomness->setRange(0, 100);
inputRandomness->setValue(0);
inputRandomness->setBlockUpdateSignalOnDrag(true);
connect(inputRandomness, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start()));
inputAngle->setRange(0, 360);
inputAngle->setSuffix(QChar(Qt::Key_degree));
inputAngle->setValue(0);
inputAngle->setBlockUpdateSignalOnDrag(true);
connect(inputAngle, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start()));
connect(spacingWidget, SIGNAL(sigSpacingChanged()), m_updateCompressor.data(), SLOT(start()));
density->setRange(0, 100, 0);
density->setSingleStep(1);
density->setValue(100);
density->setSuffix("%");
density->setBlockUpdateSignalOnDrag(true);
connect(density, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start()));
KisCubicCurve topLeftBottomRightLinearCurve;
topLeftBottomRightLinearCurve.setPoint(0, QPointF(0.0, 1.0));
topLeftBottomRightLinearCurve.setPoint(1, QPointF(1.0, 0.0));
softnessCurve->setCurve(topLeftBottomRightLinearCurve);
connect(softnessCurve, SIGNAL(modified()), m_updateCompressor.data(), SLOT(start()));
m_brush = QImage(1, 1, QImage::Format_RGB32);
connect(brushPreview, SIGNAL(clicked()), m_updateCompressor.data(), SLOT(start()));
QList<KoID> ids = KisMaskGenerator::maskGeneratorIds();
for (int i = 0; i < ids.size(); i++) {
comboBoxMaskType->insertItem(i, ids[i].name());
}
connect(comboBoxMaskType, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start()));
connect(comboBoxMaskType, SIGNAL(currentIndexChanged(int)), SLOT(setStackedWidget(int)));
setStackedWidget(comboBoxMaskType->currentIndex());
brushPreview->setIconSize(QSize(100, 100));
connect(btnAntialiasing, SIGNAL(toggled(bool)), m_updateCompressor.data(), SLOT(start()));
m_updateCompressor->start();
}
KisAutoBrushWidget::~KisAutoBrushWidget()
{
}
void KisAutoBrushWidget::resizeEvent(QResizeEvent *)
{
brushPreview->setMinimumHeight(brushPreview->width()); // dirty hack !
brushPreview->setMaximumHeight(brushPreview->width()); // dirty hack !
}
void KisAutoBrushWidget::activate()
{
m_updateCompressor->start();
}
void KisAutoBrushWidget::paramChanged()
{
KisMaskGenerator* kas;
bool antialiasEdges = btnAntialiasing->isChecked();
if (comboBoxMaskType->currentIndex() == 2) { // gaussian brush
if (comboBoxShape->currentIndex() == 0) {
kas = new KisGaussCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges);
}
else {
kas = new KisGaussRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges);
}
}
else if (comboBoxMaskType->currentIndex() == 1) { // soft brush
if (comboBoxShape->currentIndex() == 0) {
kas = new KisCurveCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges);
}
else {
kas = new KisCurveRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges);
}
}
else {// default == 0 or any other
if (comboBoxShape->currentIndex() == 0) { // use index compare instead of comparing a translatable string
kas = new KisCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges);
}
else {
kas = new KisRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges);
}
}
Q_CHECK_PTR(kas);
m_autoBrush = new KisAutoBrush(kas, inputAngle->value() / 180.0 * M_PI, inputRandomness->value() / 100.0, density->value() / 100.0);
m_autoBrush->setSpacing(spacingWidget->spacing());
m_autoBrush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff());
m_brush = m_autoBrush->image();
drawBrushPreviewArea();
emit sigBrushChanged();
}
void KisAutoBrushWidget::drawBrushPreviewArea() {
QImage pi(m_brush);
double coeff = 1.0;
int bPw = brushPreview->width() - 3;
if (pi.width() > bPw) {
coeff = bPw / (double)pi.width();
}
int bPh = brushPreview->height() - 3;
if (pi.height() > coeff * bPh) {
coeff = bPh / (double)pi.height();
}
if (coeff < 1.0) {
pi = pi.scaled((int)(coeff * pi.width()) , (int)(coeff * pi.height()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QPixmap p = QPixmap::fromImage(pi);
brushPreview->setIcon(QIcon(p));
}
void KisAutoBrushWidget::setStackedWidget(int index)
{
if (index == 1) {
stackedWidget->setCurrentIndex(1);
}
else {
stackedWidget->setCurrentIndex(0);
}
}
KisBrushSP KisAutoBrushWidget::brush()
{
return m_autoBrush;
}
void KisAutoBrushWidget::setBrush(KisBrushSP brush)
{
m_autoBrush = brush;
m_brush = brush->image();
// XXX: lock, set and unlock the widgets.
KisAutoBrush* aBrush = dynamic_cast<KisAutoBrush*>(brush.data());
KisSignalsBlocker b1(comboBoxShape, comboBoxMaskType);
KisSignalsBlocker b2(inputRadius, inputRatio, inputHFade, inputVFade, inputAngle, inputSpikes);
KisSignalsBlocker b3(spacingWidget, inputRandomness, density, softnessCurve, btnAntialiasing);
if (aBrush->maskGenerator()->type() == KisMaskGenerator::CIRCLE) {
comboBoxShape->setCurrentIndex(0);
}
else if (aBrush->maskGenerator()->type() == KisMaskGenerator::RECTANGLE) {
comboBoxShape->setCurrentIndex(1);
}
else {
comboBoxShape->setCurrentIndex(2);
}
const int mastTypeIndex = comboBoxMaskType->findText(aBrush->maskGenerator()->name());
comboBoxMaskType->setCurrentIndex(mastTypeIndex);
setStackedWidget(mastTypeIndex); // adjusting manually because the signals are blocked
inputRadius->setValue(aBrush->maskGenerator()->diameter());
inputRatio->setValue(aBrush->maskGenerator()->ratio());
inputHFade->setValue(aBrush->maskGenerator()->horizontalFade());
inputVFade->setValue(aBrush->maskGenerator()->verticalFade());
inputAngle->setValue(aBrush->angle() * 180 / M_PI);
inputSpikes->setValue(aBrush->maskGenerator()->spikes());
spacingWidget->setSpacing(aBrush->autoSpacingActive(),
aBrush->autoSpacingActive() ?
aBrush->autoSpacingCoeff() : aBrush->spacing());
inputRandomness->setValue(aBrush->randomness() * 100);
density->setValue(aBrush->density() * 100);
if (!aBrush->maskGenerator()->curveString().isEmpty()) {
KisCubicCurve curve;
curve.fromString(aBrush->maskGenerator()->curveString());
softnessCurve->setCurve(curve);
}
btnAntialiasing->setChecked(aBrush->maskGenerator()->antialiasEdges());
drawBrushPreviewArea(); // sync up what the brush preview area looks like
}
void KisAutoBrushWidget::setBrushSize(qreal dxPixels, qreal dyPixels)
{
Q_UNUSED(dyPixels);
qreal newWidth = inputRadius->value() + dxPixels;
newWidth = qMax(newWidth, qreal(0.1));
inputRadius->setValue(newWidth);
}
QSizeF KisAutoBrushWidget::brushSize() const
{
return QSizeF(inputRadius->value(), inputRadius->value() * inputRatio->value());
}
#include "moc_kis_auto_brush_widget.cpp"
diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp
index a391a0441f..dc9662b966 100644
--- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp
+++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp
@@ -1,417 +1,421 @@
/*
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2009 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
*
* 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_chooser.h"
#include <QLabel>
#include <QLayout>
#include <QCheckBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QPainter>
#include <QAbstractItemDelegate>
#include <klocalizedstring.h>
#include <kconfig.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <KisKineticScroller.h>
#include <KoResourceItemView.h>
#include <KoResourceItemChooser.h>
#include <kis_icon.h>
#include "kis_brush_server.h"
#include "widgets/kis_slider_spin_box.h"
#include "widgets/kis_multipliers_double_slider_spinbox.h"
#include "kis_spacing_selection_widget.h"
#include "kis_signals_blocker.h"
#include "kis_imagepipe_brush.h"
#include "kis_custom_brush_widget.h"
#include "kis_clipboard_brush_widget.h"
#include <kis_config.h>
#include "kis_global.h"
#include "kis_gbr_brush.h"
#include "kis_debug.h"
#include "kis_image.h"
/// The resource item delegate for rendering the resource preview
class KisBrushDelegate : public QAbstractItemDelegate
{
public:
KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {}
~KisBrushDelegate() override {}
/// reimplemented
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
/// reimplemented
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override {
return option.decorationSize;
}
};
void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
if (! index.isValid())
return;
KisBrush *brush = static_cast<KisBrush*>(index.internalPointer());
QRect itemRect = option.rect;
QImage thumbnail = brush->image();
if (thumbnail.height() > itemRect.height() || thumbnail.width() > itemRect.width()) {
thumbnail = thumbnail.scaled(itemRect.size() , Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
painter->save();
int dx = (itemRect.width() - thumbnail.width()) / 2;
int dy = (itemRect.height() - thumbnail.height()) / 2;
painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail);
if (option.state & QStyle::State_Selected) {
painter->setPen(QPen(option.palette.highlight(), 2.0));
painter->drawRect(option.rect);
painter->setCompositionMode(QPainter::CompositionMode_HardLight);
painter->setOpacity(0.65);
painter->fillRect(option.rect, option.palette.highlight());
}
painter->restore();
}
KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char *name)
: QWidget(parent),
m_stampBrushWidget(0),
m_clipboardBrushWidget(0)
{
setObjectName(name);
setupUi(this);
brushSizeSpinBox->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2);
brushSizeSpinBox->setValue(5);
brushSizeSpinBox->setExponentRatio(3.0);
brushSizeSpinBox->setSuffix(i18n(" px"));
brushSizeSpinBox->setExponentRatio(3.0);
QObject::connect(brushSizeSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal)));
brushRotationSpinBox->setRange(0, 360, 0);
brushRotationSpinBox->setValue(0);
brushRotationSpinBox->setSuffix(QChar(Qt::Key_degree));
QObject::connect(brushRotationSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal)));
brushSpacingSelectionWidget->setSpacing(true, 1.0);
connect(brushSpacingSelectionWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged()));
QObject::connect(useColorAsMaskCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetItemUseColorAsMask(bool)));
KisBrushResourceServer* rServer = KisBrushServer::instance()->brushServer();
QSharedPointer<KisBrushResourceServerAdapter> adapter(new KisBrushResourceServerAdapter(rServer));
m_itemChooser = new KoResourceItemChooser(adapter, this);
m_itemChooser->setObjectName("brush_selector");
m_itemChooser->showTaggingBar(true);
m_itemChooser->setColumnCount(10);
m_itemChooser->setRowHeight(30);
m_itemChooser->setItemDelegate(new KisBrushDelegate(this));
m_itemChooser->setCurrentItem(0, 0);
m_itemChooser->setSynced(true);
m_itemChooser->setMinimumWidth(100);
m_itemChooser->setMinimumHeight(150);
m_itemChooser->showButtons(false); // turn the import and delete buttons since we want control over them
addPresetButton->setIcon(KisIconUtils::loadIcon("list-add"));
deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("trash-empty"));
connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource()));
connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource()));
presetsLayout->addWidget(m_itemChooser);
connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(updateBrushTip(KoResource*)));
stampButton->setIcon(KisIconUtils::loadIcon("list-add"));
stampButton->setToolTip(i18n("Creates a brush tip from the current image selection."
"\n If no selection is present the whole image will be used."));
clipboardButton->setIcon(KisIconUtils::loadIcon("list-add"));
clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard."));
connect(stampButton, SIGNAL(clicked()), this, SLOT(slotOpenStampBrush()));
connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush()));
QGridLayout *spacingLayout = new QGridLayout();
spacingLayout->setObjectName("spacing grid layout");
resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0"));
connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush()));
updateBrushTip(m_itemChooser->currentResource());
}
KisPredefinedBrushChooser::~KisPredefinedBrushChooser()
{
}
void KisPredefinedBrushChooser::setBrush(KisBrushSP brush)
{
/**
* Warning: since the brushes are always cloned after loading from XML or
* fetching from the server, we cannot just ask for that brush explicitly.
* Instead, we should search for the brush with the same filename and/or name
* and load it. Please take it into account that after selecting the brush
* explicitly in the chooser, m_itemChooser->currentResource() might be
* **not** the same as the value in m_brush.
*
* Ideally, if the resource is not found on the server, we should add it, but
* it might lead to a set of weird consequences. So for now we just
* select nothing.
*/
KisBrushResourceServer* server = KisBrushServer::instance()->brushServer();
KoResource *resource = server->resourceByFilename(brush->shortFilename()).data();
if (!resource) {
resource = server->resourceByName(brush->name()).data();
}
if (!resource) {
resource = brush.data();
}
m_itemChooser->setCurrentResource(resource);
updateBrushTip(brush.data(), true);
}
void KisPredefinedBrushChooser::slotResetBrush()
{
/**
* The slot also resets the brush on the server
*
* TODO: technically, after we refactored all the brushes to be forked,
* we can just re-update the brush from the server without reloading.
* But it needs testing.
*/
KisBrush *brush = dynamic_cast<KisBrush *>(m_itemChooser->currentResource());
if (brush) {
brush->load();
brush->setScale(1.0);
brush->setAngle(0.0);
updateBrushTip(brush);
emit sigBrushChanged();
}
}
void KisPredefinedBrushChooser::slotSetItemSize(qreal sizeValue)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush);
if (m_brush) {
int brushWidth = m_brush->width();
m_brush->setScale(sizeValue / qreal(brushWidth));
emit sigBrushChanged();
}
}
void KisPredefinedBrushChooser::slotSetItemRotation(qreal rotationValue)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush);
if (m_brush) {
m_brush->setAngle(rotationValue / 180.0 * M_PI);
emit sigBrushChanged();
}
}
void KisPredefinedBrushChooser::slotSpacingChanged()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush);
if (m_brush) {
m_brush->setSpacing(brushSpacingSelectionWidget->spacing());
m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff());
emit sigBrushChanged();
}
}
void KisPredefinedBrushChooser::slotSetItemUseColorAsMask(bool useColorAsMask)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush);
KisGbrBrush *brush = dynamic_cast<KisGbrBrush *>(m_brush.data());
if (brush) {
brush->setUseColorAsMask(useColorAsMask);
emit sigBrushChanged();
}
}
void KisPredefinedBrushChooser::slotOpenStampBrush()
{
if(!m_stampBrushWidget) {
m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image);
m_stampBrushWidget->setModal(false);
connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResource*)),
SLOT(slotNewPredefinedBrush(KoResource*)));
+ } else {
+ m_stampBrushWidget->setImage(m_image);
}
QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec();
if(result) {
updateBrushTip(m_itemChooser->currentResource());
}
}
void KisPredefinedBrushChooser::slotOpenClipboardBrush()
{
if(!m_clipboardBrushWidget) {
m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image);
m_clipboardBrushWidget->setModal(true);
connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResource*)),
SLOT(slotNewPredefinedBrush(KoResource*)));
}
QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec();
if(result) {
updateBrushTip(m_itemChooser->currentResource());
}
}
void KisPredefinedBrushChooser::updateBrushTip(KoResource * resource, bool isChangingBrushPresets)
{
QString animatedBrushTipSelectionMode; // incremental, random, etc
{
KisBrush* brush = dynamic_cast<KisBrush*>(resource);
m_brush = brush ? brush->clone() : 0;
}
if (m_brush) {
brushTipNameLabel->setText(i18n(m_brush->name().toUtf8().data()));
QString brushTypeString = "";
if (m_brush->brushType() == INVALID) {
brushTypeString = i18n("Invalid");
} else if (m_brush->brushType() == MASK) {
brushTypeString = i18n("Mask");
} else if (m_brush->brushType() == IMAGE) {
brushTypeString = i18n("GBR");
} else if (m_brush->brushType() == PIPE_MASK ) {
brushTypeString = i18n("Animated Mask"); // GIH brush
// cast to GIH brush and grab parasite name
//m_brush
KisImagePipeBrush* pipeBrush = dynamic_cast<KisImagePipeBrush*>(resource);
animatedBrushTipSelectionMode = pipeBrush->parasiteSelection();
} else if (m_brush->brushType() == PIPE_IMAGE ) {
brushTypeString = i18n("Animated Image");
}
QString brushDetailsText = QString("%1 (%2 x %3) %4")
.arg(brushTypeString)
.arg(m_brush->width())
.arg(m_brush->height())
.arg(animatedBrushTipSelectionMode);
brushDetailsLabel->setText(brushDetailsText);
// keep the current preset's tip settings if we are preserving it
// this will set the brush's model data to keep what it currently has for size, spacing, etc.
if (preserveBrushPresetSettings->isChecked() && !isChangingBrushPresets) {
m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff());
m_brush->setAngle(brushRotationSpinBox->value() * M_PI / 180);
m_brush->setSpacing(brushSpacingSelectionWidget->spacing());
m_brush->setUserEffectiveSize(brushSizeSpinBox->value());
}
brushSpacingSelectionWidget->setSpacing(m_brush->autoSpacingActive(),
m_brush->autoSpacingActive() ?
m_brush->autoSpacingCoeff() : m_brush->spacing());
brushRotationSpinBox->setValue(m_brush->angle() * 180 / M_PI);
brushSizeSpinBox->setValue(m_brush->width() * m_brush->scale());
// useColorAsMask support is only in gimp brush so far
+ bool prevColorAsMaskState = useColorAsMaskCheckbox->isChecked();
KisGbrBrush *gimpBrush = dynamic_cast<KisGbrBrush*>(m_brush.data());
if (gimpBrush) {
- useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask());
+ useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask() || prevColorAsMaskState);
+ gimpBrush->setUseColorAsMask(prevColorAsMaskState);
}
useColorAsMaskCheckbox->setEnabled(m_brush->hasColor() && gimpBrush);
emit sigBrushChanged();
}
}
void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResource *resource)
{
m_itemChooser->setCurrentResource(resource);
updateBrushTip(resource);
}
void KisPredefinedBrushChooser::setBrushSize(qreal xPixels, qreal yPixels)
{
Q_UNUSED(yPixels);
qreal oldWidth = m_brush->width() * m_brush->scale();
qreal newWidth = oldWidth + xPixels;
newWidth = qMax(newWidth, qreal(0.1));
brushSizeSpinBox->setValue(newWidth);
}
void KisPredefinedBrushChooser::setImage(KisImageWSP image)
{
m_image = image;
}
void KisPredefinedBrushChooser::slotImportNewBrushResource() {
m_itemChooser->slotButtonClicked(KoResourceItemChooser::Button_Import);
}
void KisPredefinedBrushChooser::slotDeleteBrushResource() {
m_itemChooser->slotButtonClicked(KoResourceItemChooser::Button_Remove);
}
#include "moc_kis_brush_chooser.cpp"
diff --git a/plugins/paintops/libpaintop/kis_color_source.cpp b/plugins/paintops/libpaintop/kis_color_source.cpp
index ae2bca7f10..5b7ea8ede6 100644
--- a/plugins/paintops/libpaintop/kis_color_source.cpp
+++ b/plugins/paintops/libpaintop/kis_color_source.cpp
@@ -1,296 +1,297 @@
/*
* Copyright (c) 2006-2007, 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_source.h"
#include <kis_paint_device.h>
#include <resources/KoAbstractGradient.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorTransformation.h>
#include <KoMixColorsOp.h>
#include <kis_datamanager.h>
#include <kis_fill_painter.h>
#include "kis_iterator_ng.h"
#include "kis_selection.h"
#include <brushengine/kis_random_source.h>
#include <brushengine/kis_paint_information.h>
#include <random>
KisColorSource::~KisColorSource() { }
const KoColor black;
const KoColor& KisColorSource::uniformColor() const
{
qFatal("Not an uniform color.");
return black;
}
KisUniformColorSource::KisUniformColorSource()
{
}
KisUniformColorSource::~KisUniformColorSource()
{
}
void KisUniformColorSource::rotate(double)
{}
void KisUniformColorSource::resize(double , double)
{
// Do nothing as plain color does not have size
}
void KisUniformColorSource::colorize(KisPaintDeviceSP dev, const QRect& size, const QPoint&) const
{
Q_UNUSED(size);
KoColor c(dev->colorSpace());
c.fromKoColor(m_color);
dev->dataManager()->setDefaultPixel(c.data());
dev->clear();
}
const KoColor& KisUniformColorSource::uniformColor() const
{
return m_color;
}
void KisUniformColorSource::applyColorTransformation(const KoColorTransformation* transfo)
{
transfo->transform(m_color.data(), m_color.data(), 1);
}
const KoColorSpace* KisUniformColorSource::colorSpace() const
{
return m_color.colorSpace();
}
bool KisUniformColorSource::isUniformColor() const
{
return true;
}
//-------------------------------------------------//
//---------------- KisPlainColorSource ---------------//
//-------------------------------------------------//
KisPlainColorSource::KisPlainColorSource(const KoColor& backGroundColor, const KoColor& foreGroundColor)
: m_backGroundColor(backGroundColor)
, m_cachedBackGroundColor(backGroundColor)
, m_foreGroundColor(foreGroundColor)
{
}
KisPlainColorSource::~KisPlainColorSource()
{
}
void KisPlainColorSource::selectColor(double mix, const KisPaintInformation &pi)
{
Q_UNUSED(pi);
if (m_color.colorSpace() != m_foreGroundColor.colorSpace()) {
m_color = KoColor(m_foreGroundColor.colorSpace());
m_cachedBackGroundColor = KoColor(m_foreGroundColor.colorSpace());
m_cachedBackGroundColor.fromKoColor(m_backGroundColor);
}
const quint8 *colors[2];
colors[0] = m_cachedBackGroundColor.data();
colors[1] = m_foreGroundColor.data();
// equally distribute mix factor over [0..255]
// mix * 256 ensures that, with exception of mix==1.0, which gets special handling
const int weight = (mix == 1.0) ? 255 : (int)(mix * 256);
const qint16 weights[2] = { (qint16)(255 - weight), (qint16)weight };
m_color.colorSpace()->mixColorsOp()->mixColors(colors, weights, 2, m_color.data());
}
//-------------------------------------------------//
//--------------- KisGradientColorSource -------------//
//-------------------------------------------------//
KisGradientColorSource::KisGradientColorSource(const KoAbstractGradient* gradient, const KoColorSpace* workingCS)
: m_gradient(gradient)
{
m_color = KoColor(workingCS);
Q_ASSERT(gradient);
}
KisGradientColorSource::~KisGradientColorSource()
{
}
void KisGradientColorSource::selectColor(double mix, const KisPaintInformation &pi)
{
Q_UNUSED(pi);
if (m_gradient) {
m_gradient->colorAt(m_color, mix);
}
}
//-------------------------------------------------//
//--------------- KisGradientColorSource -------------//
//-------------------------------------------------//
KisUniformRandomColorSource::KisUniformRandomColorSource()
{
}
KisUniformRandomColorSource::~KisUniformRandomColorSource()
{
}
void KisUniformRandomColorSource::selectColor(double mix, const KisPaintInformation &pi)
{
Q_UNUSED(pi);
Q_UNUSED(mix);
KisRandomSourceSP source = pi.randomSource();
m_color.fromQColor(QColor((int)source->generate(0, 255),
(int)source->generate(0, 255),
(int)source->generate(0, 255)));
}
//------------------------------------------------------//
//--------------- KisTotalRandomColorSource ---------------//
//------------------------------------------------------//
KisTotalRandomColorSource::KisTotalRandomColorSource() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8())
{
}
KisTotalRandomColorSource::~KisTotalRandomColorSource()
{
}
void KisTotalRandomColorSource::selectColor(double mix, const KisPaintInformation &pi)
{
Q_UNUSED(mix);
Q_UNUSED(pi);
}
void KisTotalRandomColorSource::applyColorTransformation(const KoColorTransformation*) {}
const KoColorSpace* KisTotalRandomColorSource::colorSpace() const
{
return m_colorSpace;
}
void KisTotalRandomColorSource::colorize(KisPaintDeviceSP dev, const QRect& rect, const QPoint&) const
{
KoColor kc(dev->colorSpace());
QColor qc;
std::random_device rand_dev;
std::default_random_engine rand_engine{rand_dev()};
std::uniform_int_distribution<> rand_distr(0, 255);
int pixelSize = dev->colorSpace()->pixelSize();
KisHLineIteratorSP it = dev->createHLineIteratorNG(rect.x(), rect.y(), rect.width());
for (int y = 0; y < rect.height(); y++) {
do {
qc.setRgb(rand_distr(rand_engine), rand_distr(rand_engine), rand_distr(rand_engine));
kc.fromQColor(qc);
memcpy(it->rawData(), kc.data(), pixelSize);
} while (it->nextPixel());
it->nextRow();
}
}
bool KisTotalRandomColorSource::isUniformColor() const
{
return false;
}
void KisTotalRandomColorSource::rotate(double) {}
void KisTotalRandomColorSource::resize(double , double) {}
KoPatternColorSource::KoPatternColorSource(KisPaintDeviceSP _pattern, int _width, int _height, bool _locked)
: m_device(_pattern)
, m_bounds(QRect(0, 0, _width, _height))
, m_locked(_locked)
{
}
KoPatternColorSource::~KoPatternColorSource()
{
}
void KoPatternColorSource::selectColor(double mix, const KisPaintInformation &pi)
{
Q_UNUSED(mix);
Q_UNUSED(pi);
}
void KoPatternColorSource::applyColorTransformation(const KoColorTransformation* transfo)
{
Q_UNUSED(transfo);
}
const KoColorSpace* KoPatternColorSource::colorSpace() const
{
return m_device->colorSpace();
}
void KoPatternColorSource::colorize(KisPaintDeviceSP device, const QRect& rect, const QPoint& offset) const
{
KisFillPainter painter(device);
if (m_locked) {
painter.fillRect(rect.x(), rect.y(), rect.width(), rect.height(), m_device, m_bounds);
}
else {
int x = offset.x() % m_bounds.width();
int y = offset.y() % m_bounds.height();
// Change the position, because the pattern is always applied starting
// from (0,0) in the paint device reference
device->setX(x);
device->setY(y);
painter.fillRect(rect.x() + x, rect.y() + y, rect.width(), rect.height(), m_device, m_bounds);
device->setX(0);
device->setY(0);
}
}
void KoPatternColorSource::rotate(double r)
{
Q_UNUSED(r);
}
void KoPatternColorSource::resize(double xs, double ys)
{
Q_UNUSED(xs);
Q_UNUSED(ys);
}
bool KoPatternColorSource::isUniformColor() const
{
return false;
}
diff --git a/plugins/paintops/libpaintop/kis_color_source.h b/plugins/paintops/libpaintop/kis_color_source.h
index 2801522860..ece32ca36e 100644
--- a/plugins/paintops/libpaintop/kis_color_source.h
+++ b/plugins/paintops/libpaintop/kis_color_source.h
@@ -1,152 +1,153 @@
/*
* Copyright (c) 2006-2007, 2009 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_COLORING_H_
#define _KIS_DYNAMIC_COLORING_H_
#include "kis_paintop_option.h"
#include <QRect>
#include <KoColor.h>
#include <kis_types.h>
#include <kritapaintop_export.h>
class KoAbstractGradient;
class KoColorTransformation;
class KisPaintInformation;
/**
* A color source allow to abstract how a brush is colorized,
* and to apply transformation.
*
* The first function to call is @ref selectColor , then any of the transformation.
*/
class PAINTOP_EXPORT KisColorSource
{
public:
virtual ~KisColorSource();
public:
/**
* This is function is called to initialize the color that will be used for the dab.
* @param mix is a parameter between 0.0 and 1.0
* @param pi paint information
*/
virtual void selectColor(double mix, const KisPaintInformation &pi) = 0;
/**
* Apply a color transformation on the selected color
*/
virtual void applyColorTransformation(const KoColorTransformation* transfo) = 0;
virtual const KoColorSpace* colorSpace() const = 0;
/**
* Apply the color on a paint device
*/
virtual void colorize(KisPaintDeviceSP, const QRect& rect, const QPoint& _offset) const = 0;
/**
* @return true if the color is an uniform color
*/
virtual bool isUniformColor() const = 0;
/**
* @return the color if the color is uniformed
*/
virtual const KoColor& uniformColor() const;
};
class PAINTOP_EXPORT KisUniformColorSource : public KisColorSource
{
public:
KisUniformColorSource();
~KisUniformColorSource() override;
virtual void rotate(double);
virtual void resize(double , double);
void applyColorTransformation(const KoColorTransformation* transfo) override;
const KoColorSpace* colorSpace() const override;
void colorize(KisPaintDeviceSP, const QRect& rect, const QPoint& offset) const override;
bool isUniformColor() const override;
const KoColor& uniformColor() const override;
protected:
KoColor m_color;
};
class PAINTOP_EXPORT KisPlainColorSource : public KisUniformColorSource
{
public:
KisPlainColorSource(const KoColor& backGroundColor, const KoColor& foreGroundColor);
~KisPlainColorSource() override;
void selectColor(double mix, const KisPaintInformation &pi) override;
private:
KoColor m_backGroundColor;
KoColor m_cachedBackGroundColor;
KoColor m_foreGroundColor;
};
class PAINTOP_EXPORT KisGradientColorSource : public KisUniformColorSource
{
public:
KisGradientColorSource(const KoAbstractGradient* gradient, const KoColorSpace* workingCS);
~KisGradientColorSource() override;
void selectColor(double mix, const KisPaintInformation &pi) override;
private:
const KoAbstractGradient* m_gradient;
};
class PAINTOP_EXPORT KisUniformRandomColorSource : public KisUniformColorSource
{
public:
KisUniformRandomColorSource();
~KisUniformRandomColorSource() override;
void selectColor(double mix, const KisPaintInformation &pi) override;
};
class PAINTOP_EXPORT KisTotalRandomColorSource : public KisColorSource
{
public:
KisTotalRandomColorSource();
~KisTotalRandomColorSource() override;
public:
void selectColor(double mix, const KisPaintInformation &pi) override;
void applyColorTransformation(const KoColorTransformation* transfo) override;
const KoColorSpace* colorSpace() const override;
void colorize(KisPaintDeviceSP, const QRect& rect, const QPoint& offset) const override;
virtual void rotate(double r);
virtual void resize(double xs, double ys);
bool isUniformColor() const override;
private:
const KoColorSpace* m_colorSpace;
};
class PAINTOP_EXPORT KoPatternColorSource : public KisColorSource
{
public:
KoPatternColorSource(KisPaintDeviceSP _pattern, int _width, int _height, bool _locked);
~KoPatternColorSource() override;
public:
void selectColor(double mix, const KisPaintInformation &pi) override;
void applyColorTransformation(const KoColorTransformation* transfo) override;
const KoColorSpace* colorSpace() const override;
void colorize(KisPaintDeviceSP, const QRect& rect, const QPoint& _offset) const override;
virtual void rotate(double r);
virtual void resize(double xs, double ys);
bool isUniformColor() const override;
private:
const KisPaintDeviceSP m_device;
QRect m_bounds;
bool m_locked;
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_curve_label.h b/plugins/paintops/libpaintop/kis_curve_label.h
index 97168acbb8..be52b620fe 100644
--- a/plugins/paintops/libpaintop/kis_curve_label.h
+++ b/plugins/paintops/libpaintop/kis_curve_label.h
@@ -1,44 +1,45 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_CURVE_LABEL_H_
#define _KIS_CURVE_LABEL_H_
#include <kritapaintop_export.h>
class QString;
class QImage;
struct PAINTOP_EXPORT KisCurveLabel
{
public:
KisCurveLabel();
KisCurveLabel(const QString&);
KisCurveLabel(const QImage&);
KisCurveLabel(const KisCurveLabel&);
KisCurveLabel& operator=(const KisCurveLabel&);
~KisCurveLabel();
QString name() const;
QImage icon() const;
private:
struct Private;
Private* const d;
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp b/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp
index 630064e59d..4617f04824 100644
--- a/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp
+++ b/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp
@@ -1,261 +1,267 @@
/*
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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_custom_brush_widget.h"
#include <kis_debug.h>
#include <QLabel>
#include <QImage>
#include <QPushButton>
#include <QComboBox>
#include <QCheckBox>
#include <QDateTime>
#include <QPixmap>
#include <QShowEvent>
#include <KoResourcePaths.h>
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_gbr_brush.h"
#include "kis_imagepipe_brush.h"
#include <kis_fixed_paint_device.h>
#include "kis_brush_server.h"
#include "kis_paint_layer.h"
#include "kis_group_layer.h"
#include <kis_selection.h>
#include <KoProperties.h>
#include "kis_iterator_ng.h"
KisCustomBrushWidget::KisCustomBrushWidget(QWidget *parent, const QString& caption, KisImageWSP image)
: KisWdgCustomBrush(parent)
, m_image(image)
{
setWindowTitle(caption);
preview->setScaledContents(false);
preview->setFixedSize(preview->size());
preview->setStyleSheet("border: 2px solid #222; border-radius: 4px; padding: 5px; font: normal 10px;");
KisBrushResourceServer* rServer = KisBrushServer::instance()->brushServer();
m_rServerAdapter = QSharedPointer<KisBrushResourceServerAdapter>(new KisBrushResourceServerAdapter(rServer));
m_brush = 0;
connect(this, SIGNAL(accepted()), SLOT(slotAddPredefined()));
connect(brushStyle, SIGNAL(activated(int)), this, SLOT(slotUpdateCurrentBrush(int)));
connect(colorAsMask, SIGNAL(toggled(bool)), this, SLOT(slotUpdateUseColorAsMask(bool)));
connect(comboBox2, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCurrentBrush(int)));
colorAsMask->setChecked(true); // use color as mask by default. This is by far the most common way to make tip.
spacingWidget->setSpacing(true, 1.0);
connect(spacingWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged()));
}
KisCustomBrushWidget::~KisCustomBrushWidget()
{
}
KisBrushSP KisCustomBrushWidget::brush()
{
return m_brush;
}
+void KisCustomBrushWidget::setImage(KisImageWSP image){
+ m_image = image;
+ createBrush();
+ updatePreviewImage();
+}
+
void KisCustomBrushWidget::showEvent(QShowEvent *)
{
slotUpdateCurrentBrush(0);
}
void KisCustomBrushWidget::updatePreviewImage()
{
QImage brushImage = m_brush ? m_brush->brushTipImage() : QImage();
if (!brushImage.isNull()) {
brushImage = brushImage.scaled(preview->size(), Qt::KeepAspectRatio);
}
preview->setPixmap(QPixmap::fromImage(brushImage));
}
void KisCustomBrushWidget::slotUpdateCurrentBrush(int)
{
if (brushStyle->currentIndex() == 0) {
comboBox2->setEnabled(false);
} else {
comboBox2->setEnabled(true);
}
if (m_image) {
createBrush();
updatePreviewImage();
}
}
void KisCustomBrushWidget::slotSpacingChanged()
{
if (m_brush) {
m_brush->setSpacing(spacingWidget->spacing());
m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff());
}
}
void KisCustomBrushWidget::slotUpdateUseColorAsMask(bool useColorAsMask)
{
if (m_brush) {
static_cast<KisGbrBrush*>(m_brush.data())->setUseColorAsMask(useColorAsMask);
updatePreviewImage();
}
}
void KisCustomBrushWidget::slotAddPredefined()
{
QString dir = KoResourcePaths::saveLocation("data", "brushes");
QString extension;
if (brushStyle->currentIndex() == 0) {
extension = ".gbr";
}
else {
extension = ".gih";
}
QString name = nameLineEdit->text();
QString tempFileName;
{
QFileInfo fileInfo;
fileInfo.setFile(dir + name + extension);
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(dir + name + QString("%1").arg(i) + extension);
i++;
}
tempFileName = fileInfo.filePath();
}
// Add it to the brush server, so that it automatically gets to the mediators, and
// so to the other brush choosers can pick it up, if they want to
if (m_rServerAdapter && m_brush) {
qDebug() << "m_brush" << m_brush;
KisGbrBrush *resource = dynamic_cast<KisGbrBrush*>(m_brush->clone());
resource->setFilename(tempFileName);
if (nameLineEdit->text().isEmpty()) {
resource->setName(QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm"));
}
else {
resource->setName(name);
}
if (colorAsMask->isChecked()) {
resource->makeMaskImage();
}
m_rServerAdapter->addResource(resource);
emit sigNewPredefinedBrush(resource);
}
close();
}
void KisCustomBrushWidget::createBrush()
{
if (!m_image)
return;
if (brushStyle->currentIndex() == 0) {
KisSelectionSP selection = m_image->globalSelection();
// create copy of the data
m_image->lock();
KisPaintDeviceSP dev = new KisPaintDevice(*m_image->projection());
m_image->unlock();
if (!selection) {
m_brush = new KisGbrBrush(dev, 0, 0, m_image->width(), m_image->height());
}
else {
// apply selection mask
QRect r = selection->selectedExactRect();
dev->crop(r);
KisHLineIteratorSP pixelIt = dev->createHLineIteratorNG(r.x(), r.top(), r.width());
KisHLineConstIteratorSP maskIt = selection->projection()->createHLineIteratorNG(r.x(), r.top(), r.width());
for (qint32 y = r.top(); y <= r.bottom(); ++y) {
do {
dev->colorSpace()->applyAlphaU8Mask(pixelIt->rawData(), maskIt->oldRawData(), 1);
} while (pixelIt->nextPixel() && maskIt->nextPixel());
pixelIt->nextRow();
maskIt->nextRow();
}
QRect rc = dev->exactBounds();
m_brush = new KisGbrBrush(dev, rc.x(), rc.y(), rc.width(), rc.height());
}
}
else {
// For each layer in the current image, create a new image, and add it to the list
QVector< QVector<KisPaintDevice*> > devices;
devices.push_back(QVector<KisPaintDevice*>());
int w = m_image->width();
int h = m_image->height();
m_image->lock();
// We only loop over the rootLayer. Since we actually should have a layer selection
// list, no need to elaborate on that here and now
KoProperties properties;
properties.setProperty("visible", true);
QList<KisNodeSP> layers = m_image->root()->childNodes(QStringList("KisLayer"), properties);
KisNodeSP node;
Q_FOREACH (KisNodeSP node, layers) {
devices[0].push_back(node->projection().data());
}
QVector<KisParasite::SelectionMode> modes;
switch (comboBox2->currentIndex()) {
case 0: modes.push_back(KisParasite::Constant); break;
case 1: modes.push_back(KisParasite::Random); break;
case 2: modes.push_back(KisParasite::Incremental); break;
case 3: modes.push_back(KisParasite::Pressure); break;
case 4: modes.push_back(KisParasite::Angular); break;
default: modes.push_back(KisParasite::Incremental);
}
m_brush = new KisImagePipeBrush(m_image->objectName(), w, h, devices, modes);
m_image->unlock();
}
static_cast<KisGbrBrush*>(m_brush.data())->setUseColorAsMask(colorAsMask->isChecked());
m_brush->setSpacing(spacingWidget->spacing());
m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff());
m_brush->setFilename(TEMPORARY_FILENAME);
m_brush->setName(TEMPORARY_BRUSH_NAME);
m_brush->setValid(true);
}
diff --git a/plugins/paintops/libpaintop/kis_custom_brush_widget.h b/plugins/paintops/libpaintop/kis_custom_brush_widget.h
index b4deb71bc8..ffeebf0207 100644
--- a/plugins/paintops/libpaintop/kis_custom_brush_widget.h
+++ b/plugins/paintops/libpaintop/kis_custom_brush_widget.h
@@ -1,79 +1,81 @@
/*
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
*
* 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_CUSTOM_BRUSH_H_
#define KIS_CUSTOM_BRUSH_H_
#include <QObject>
#include <QShowEvent>
#include <KoResourceServerAdapter.h>
#include "ui_wdgcustombrush.h"
#include <kis_types.h>
#include <kis_brush.h>
const QString TEMPORARY_FILENAME = "/tmp/temporaryKritaBrush.gbr";
const QString TEMPORARY_BRUSH_NAME = "Temporary custom brush";
const double DEFAULT_SPACING = 0.25;
class KoResource;
class KisWdgCustomBrush : public QDialog, public Ui::KisWdgCustomBrush
{
Q_OBJECT
public:
KisWdgCustomBrush(QWidget *parent) : QDialog(parent) {
setupUi(this);
}
};
class KisCustomBrushWidget : public KisWdgCustomBrush
{
Q_OBJECT
public:
KisCustomBrushWidget(QWidget *parent, const QString& caption, KisImageWSP image);
virtual ~KisCustomBrushWidget();
KisBrushSP brush();
+ void setImage(KisImageWSP image);
+
protected:
virtual void showEvent(QShowEvent *);
private Q_SLOTS:
void slotAddPredefined();
void slotUpdateCurrentBrush(int i = 0); // To connect with activated(int)
void slotSpacingChanged();
void slotUpdateUseColorAsMask(bool useColorAsMask);
Q_SIGNALS:
void sigNewPredefinedBrush(KoResource *);
private:
void createBrush();
void updatePreviewImage();
KisImageWSP m_image;
KisBrushSP m_brush;
QSharedPointer<KoAbstractResourceServerAdapter> m_rServerAdapter;
};
#endif // KIS_CUSTOM_BRUSH_H_
diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc
index 8f31aa9a1a..0c56495f77 100644
--- a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc
+++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc
@@ -1,575 +1,576 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor.h"
#include <QDomElement>
#include "kis_algebra_2d.h"
#include "sensors/kis_dynamic_sensors.h"
#include "sensors/kis_dynamic_sensor_distance.h"
#include "sensors/kis_dynamic_sensor_drawing_angle.h"
#include "sensors/kis_dynamic_sensor_time.h"
#include "sensors/kis_dynamic_sensor_fade.h"
#include "sensors/kis_dynamic_sensor_fuzzy.h"
KisDynamicSensor::KisDynamicSensor(DynamicSensorType type)
: m_length(-1)
, m_type(type)
, m_customCurve(false)
, m_active(false)
{
}
KisDynamicSensor::~KisDynamicSensor()
{
}
QWidget* KisDynamicSensor::createConfigurationWidget(QWidget* parent, QWidget*)
{
Q_UNUSED(parent);
return 0;
}
void KisDynamicSensor::reset()
{
}
KisDynamicSensorSP KisDynamicSensor::id2Sensor(const KoID& id, const QString &parentOptionName)
{
if (id.id() == PressureId.id()) {
return new KisDynamicSensorPressure();
}
else if (id.id() == PressureInId.id()) {
return new KisDynamicSensorPressureIn();
}
else if (id.id() == XTiltId.id()) {
return new KisDynamicSensorXTilt();
}
else if (id.id() == YTiltId.id()) {
return new KisDynamicSensorYTilt();
}
else if (id.id() == TiltDirectionId.id()) {
return new KisDynamicSensorTiltDirection();
}
else if (id.id() == TiltElevationId.id()) {
return new KisDynamicSensorTiltElevation();
}
else if (id.id() == SpeedId.id()) {
return new KisDynamicSensorSpeed();
}
else if (id.id() == DrawingAngleId.id()) {
return new KisDynamicSensorDrawingAngle();
}
else if (id.id() == RotationId.id()) {
return new KisDynamicSensorRotation();
}
else if (id.id() == DistanceId.id()) {
return new KisDynamicSensorDistance();
}
else if (id.id() == TimeId.id()) {
return new KisDynamicSensorTime();
}
else if (id.id() == FuzzyPerDabId.id()) {
return new KisDynamicSensorFuzzy(false, parentOptionName);
}
else if (id.id() == FuzzyPerStrokeId.id()) {
return new KisDynamicSensorFuzzy(true, parentOptionName);
}
else if (id.id() == FadeId.id()) {
return new KisDynamicSensorFade();
}
else if (id.id() == PerspectiveId.id()) {
return new KisDynamicSensorPerspective();
}
else if (id.id() == TangentialPressureId.id()) {
return new KisDynamicSensorTangentialPressure();
}
dbgPlugins << "Unknown transform parameter :" << id.id();
return 0;
}
DynamicSensorType KisDynamicSensor::id2Type(const KoID &id)
{
if (id.id() == PressureId.id()) {
return PRESSURE;
}
else if (id.id() == PressureInId.id()) {
return PRESSURE_IN;
}
else if (id.id() == XTiltId.id()) {
return XTILT;
}
else if (id.id() == YTiltId.id()) {
return YTILT;
}
else if (id.id() == TiltDirectionId.id()) {
return TILT_DIRECTION;
}
else if (id.id() == TiltElevationId.id()) {
return TILT_ELEVATATION;
}
else if (id.id() == SpeedId.id()) {
return SPEED;
}
else if (id.id() == DrawingAngleId.id()) {
return ANGLE;
}
else if (id.id() == RotationId.id()) {
return ROTATION;
}
else if (id.id() == DistanceId.id()) {
return DISTANCE;
}
else if (id.id() == TimeId.id()) {
return TIME;
}
else if (id.id() == FuzzyPerDabId.id()) {
return FUZZY_PER_DAB;
}
else if (id.id() == FuzzyPerStrokeId.id()) {
return FUZZY_PER_STROKE;
}
else if (id.id() == FadeId.id()) {
return FADE;
}
else if (id.id() == PerspectiveId.id()) {
return PERSPECTIVE;
}
else if (id.id() == TangentialPressureId.id()) {
return TANGENTIAL_PRESSURE;
}
return UNKNOWN;
}
KisDynamicSensorSP KisDynamicSensor::type2Sensor(DynamicSensorType sensorType, const QString &parentOptionName)
{
switch (sensorType) {
case FUZZY_PER_DAB:
return new KisDynamicSensorFuzzy(false, parentOptionName);
case FUZZY_PER_STROKE:
return new KisDynamicSensorFuzzy(true, parentOptionName);
case SPEED:
return new KisDynamicSensorSpeed();
case FADE:
return new KisDynamicSensorFade();
case DISTANCE:
return new KisDynamicSensorDistance();
case TIME:
return new KisDynamicSensorTime();
case ANGLE:
return new KisDynamicSensorDrawingAngle();
case ROTATION:
return new KisDynamicSensorRotation();
case PRESSURE:
return new KisDynamicSensorPressure();
case XTILT:
return new KisDynamicSensorXTilt();
case YTILT:
return new KisDynamicSensorYTilt();
case TILT_DIRECTION:
return new KisDynamicSensorTiltDirection();
case TILT_ELEVATATION:
return new KisDynamicSensorTiltElevation();
case PERSPECTIVE:
return new KisDynamicSensorPerspective();
case TANGENTIAL_PRESSURE:
return new KisDynamicSensorTangentialPressure();
case PRESSURE_IN:
return new KisDynamicSensorPressureIn();
default:
return 0;
}
}
QString KisDynamicSensor::minimumLabel(DynamicSensorType sensorType)
{
switch (sensorType) {
case FUZZY_PER_DAB:
case FUZZY_PER_STROKE:
return QString();
case FADE:
return i18n("0");
case DISTANCE:
return i18n("0 px");
case TIME:
return i18n("0 s");
case ANGLE:
return i18n("0°");
case SPEED:
return i18n("Slow");
case ROTATION:
return i18n("0°");
case PRESSURE:
return i18n("Low");
case XTILT:
return i18n("-30°");
case YTILT:
return i18n("-30°");
case TILT_DIRECTION:
return i18n("0°");
case TILT_ELEVATATION:
return i18n("90°");
case PERSPECTIVE:
return i18n("Far");
case TANGENTIAL_PRESSURE:
case PRESSURE_IN:
return i18n("Low");
default:
return i18n("0.0");
}
}
QString KisDynamicSensor::maximumLabel(DynamicSensorType sensorType, int max)
{
switch (sensorType) {
case FUZZY_PER_DAB:
case FUZZY_PER_STROKE:
return QString();
case FADE:
if (max < 0)
return i18n("1000");
else
return i18n("%1", max);
case DISTANCE:
if (max < 0)
return i18n("30 px");
else
return i18n("%1 px", max);
case TIME:
if (max < 0)
return i18n("3 s");
else
return i18n("%1 s", max / 1000);
case ANGLE:
return i18n("360°");
case SPEED:
return i18n("Fast");
case ROTATION:
return i18n("360°");
case PRESSURE:
return i18n("High");
case XTILT:
return i18n("30°");
case YTILT:
return i18n("30°");
case TILT_DIRECTION:
return i18n("360°");
case TILT_ELEVATATION:
return i18n("0°");
case PERSPECTIVE:
return i18n("Near");
case TANGENTIAL_PRESSURE:
case PRESSURE_IN:
return i18n("High");
default:
return i18n("1.0");
};
}
int KisDynamicSensor::minimumValue(DynamicSensorType sensorType)
{
switch (sensorType) {
case FUZZY_PER_DAB:
case FUZZY_PER_STROKE:
case FADE:
case DISTANCE:
case TIME:
case ANGLE:
case SPEED:
case ROTATION:
case PRESSURE:
case TILT_DIRECTION:
case PERSPECTIVE:
case PRESSURE_IN:
return 0;
case XTILT:
case YTILT:
return -30;
case TILT_ELEVATATION:
return 90;
case TANGENTIAL_PRESSURE:
default:
return 0;
}
}
int KisDynamicSensor::maximumValue(DynamicSensorType sensorType, int max)
{
switch (sensorType) {
case FUZZY_PER_DAB:
case FUZZY_PER_STROKE:
case SPEED:
case PERSPECTIVE:
case TANGENTIAL_PRESSURE:
case PRESSURE_IN:
case PRESSURE:
return 100;
case FADE:
if (max < 0) {
return 1000;
} else {
return max;
}
case DISTANCE:
if (max < 0) {
return 30;
} else {
return max;
}
case TIME:
if (max < 0) {
return 3000;
} else {
return max;
}
case ANGLE:
case ROTATION:
case TILT_DIRECTION:
return 360;
case XTILT:
case YTILT:
return 30;
case TILT_ELEVATATION:
return 0;
default:
return 100;
};
}
QString KisDynamicSensor::valueSuffix(DynamicSensorType sensorType)
{
switch (sensorType) {
case FUZZY_PER_DAB:
case FUZZY_PER_STROKE:
case SPEED:
case PRESSURE:
case PERSPECTIVE:
case TANGENTIAL_PRESSURE:
case PRESSURE_IN:
return i18n("%");
case FADE:
return QString();
case DISTANCE:
return i18n(" px");
case TIME:
return i18n(" ms");
case ANGLE:
case ROTATION:
case XTILT:
case YTILT:
case TILT_DIRECTION:
case TILT_ELEVATATION:
return i18n("°");
default:
return i18n("%");
};
}
KisDynamicSensorSP KisDynamicSensor::createFromXML(const QString& s, const QString &parentOptionName)
{
QDomDocument doc;
doc.setContent(s);
QDomElement e = doc.documentElement();
return createFromXML(e, parentOptionName);
}
KisDynamicSensorSP KisDynamicSensor::createFromXML(const QDomElement& e, const QString &parentOptionName)
{
QString id = e.attribute("id", "");
KisDynamicSensorSP sensor = id2Sensor(id, parentOptionName);
if (sensor) {
sensor->fromXML(e);
}
return sensor;
}
QList<KoID> KisDynamicSensor::sensorsIds()
{
QList<KoID> ids;
ids << PressureId
<< PressureInId
<< XTiltId
<< YTiltId
<< TiltDirectionId
<< TiltElevationId
<< SpeedId
<< DrawingAngleId
<< RotationId
<< DistanceId
<< TimeId
<< FuzzyPerDabId
<< FuzzyPerStrokeId
<< FadeId
<< PerspectiveId
<< TangentialPressureId;
return ids;
}
QList<DynamicSensorType> KisDynamicSensor::sensorsTypes()
{
QList<DynamicSensorType> sensorTypes;
sensorTypes
<< PRESSURE
<< PRESSURE_IN
<< XTILT
<< YTILT
<< TILT_DIRECTION
<< TILT_ELEVATATION
<< SPEED
<< ANGLE
<< ROTATION
<< DISTANCE
<< TIME
<< FUZZY_PER_DAB
<< FUZZY_PER_STROKE
<< FADE
<< PERSPECTIVE
<< TANGENTIAL_PRESSURE;
return sensorTypes;
}
QString KisDynamicSensor::id(DynamicSensorType sensorType)
{
switch (sensorType) {
case FUZZY_PER_DAB:
return "fuzzy";
case FUZZY_PER_STROKE:
return "fuzzystroke";
case FADE:
return "fade";
case DISTANCE:
return "distance";
case TIME:
return "time";
case ANGLE:
return "drawingangle";
case SPEED:
return "speed";
case ROTATION:
return "rotation";
case PRESSURE:
return "pressure";
case XTILT:
return "xtilt";
case YTILT:
return "ytilt";
case TILT_DIRECTION:
return "ascension";
case TILT_ELEVATATION:
return "declination";
case PERSPECTIVE:
return "perspective";
case TANGENTIAL_PRESSURE:
return "tangentialpressure";
case PRESSURE_IN:
return "pressurein";
case SENSORS_LIST:
return "sensorslist";
default:
return QString();
};
}
void KisDynamicSensor::toXML(QDomDocument& doc, QDomElement& elt) const
{
elt.setAttribute("id", id(sensorType()));
if (m_customCurve) {
QDomElement curve_elt = doc.createElement("curve");
QDomText text = doc.createTextNode(m_curve.toString());
curve_elt.appendChild(text);
elt.appendChild(curve_elt);
}
}
void KisDynamicSensor::fromXML(const QDomElement& e)
{
Q_ASSERT(e.attribute("id", "") == id(sensorType()));
m_customCurve = false;
QDomElement curve_elt = e.firstChildElement("curve");
if (!curve_elt.isNull()) {
m_customCurve = true;
m_curve.fromString(curve_elt.text());
}
}
qreal KisDynamicSensor::parameter(const KisPaintInformation& info)
{
const qreal val = value(info);
if (m_customCurve) {
qreal scaledVal = isAdditive() ? additiveToScaling(val) : val;
const QVector<qreal> transfer = m_curve.floatTransfer(256);
scaledVal = KisCubicCurve::interpolateLinear(scaledVal, transfer);
return isAdditive() ? scalingToAdditive(scaledVal) : scaledVal;
}
else {
return val;
}
}
void KisDynamicSensor::setCurve(const KisCubicCurve& curve)
{
m_customCurve = true;
m_curve = curve;
}
const KisCubicCurve& KisDynamicSensor::curve() const
{
return m_curve;
}
void KisDynamicSensor::removeCurve()
{
m_customCurve = false;
}
bool KisDynamicSensor::hasCustomCurve() const
{
return m_customCurve;
}
bool KisDynamicSensor::dependsOnCanvasRotation() const
{
return true;
}
bool KisDynamicSensor::isAdditive() const
{
return false;
}
bool KisDynamicSensor::isAbsoluteRotation() const
{
return false;
}
void KisDynamicSensor::setActive(bool active)
{
m_active = active;
}
bool KisDynamicSensor::isActive() const
{
return m_active;
}
diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.h b/plugins/paintops/libpaintop/kis_dynamic_sensor.h
index a4b843c36f..b349822eda 100644
--- a/plugins/paintops/libpaintop/kis_dynamic_sensor.h
+++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.h
@@ -1,221 +1,222 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_H_
#define _KIS_DYNAMIC_SENSOR_H_
#include <kritapaintop_export.h>
#include <QObject>
#include <KoID.h>
#include <klocalizedstring.h>
#include "kis_serializable_configuration.h"
#include "kis_curve_label.h"
#include <kis_cubic_curve.h>
#include <kis_shared_ptr.h>
#include <kis_shared.h>
class QWidget;
class KisPaintInformation;
const KoID FuzzyPerDabId("fuzzy", ki18n("Fuzzy Dab")); ///< generate a random number
const KoID FuzzyPerStrokeId("fuzzystroke", ki18n("Fuzzy Stroke")); ///< generate a random number
const KoID SpeedId("speed", ki18n("Speed")); ///< generate a number depending on the speed of the cursor
const KoID FadeId("fade", ki18n("Fade")); ///< generate a number that increase every time you call it (e.g. per dab)
const KoID DistanceId("distance", ki18n("Distance")); ///< generate a number that increase with distance
const KoID TimeId("time", ki18n("Time")); ///< generate a number that increase with time
const KoID DrawingAngleId("drawingangle", ki18n("Drawing angle")); ///< number depending on the angle
const KoID RotationId("rotation", ki18n("Rotation")); ///< rotation coming from the device
const KoID PressureId("pressure", ki18n("Pressure")); ///< number depending on the pressure
const KoID PressureInId("pressurein", ki18n("PressureIn")); ///< number depending on the pressure
const KoID XTiltId("xtilt", ki18n("X-Tilt")); ///< number depending on X-tilt
const KoID YTiltId("ytilt", ki18n("Y-Tilt")); ///< number depending on Y-tilt
/**
* "TiltDirection" and "TiltElevation" parameters are written to
* preset files as "ascension" and "declination" to keep backward
* compatibility with older presets from the days when they were called
* differently.
*/
const KoID TiltDirectionId("ascension", ki18n("Tilt direction")); /// < number depending on the X and Y tilt, tilt direction is 0 when stylus nib points to you and changes clockwise from -180 to +180.
const KoID TiltElevationId("declination", ki18n("Tilt elevation")); /// < tilt elevation is 90 when stylus is perpendicular to tablet and 0 when it's parallel to tablet
const KoID PerspectiveId("perspective", ki18n("Perspective")); ///< number depending on the distance on the perspective grid
const KoID TangentialPressureId("tangentialpressure", ki18n("Tangential pressure")); ///< the wheel on an airbrush device
const KoID SensorsListId("sensorslist", "SHOULD NOT APPEAR IN THE UI !"); ///< this a non user-visible sensor that can store a list of other sensors, and multiply their output
class KisDynamicSensor;
typedef KisSharedPtr<KisDynamicSensor> KisDynamicSensorSP;
enum DynamicSensorType {
FUZZY_PER_DAB,
FUZZY_PER_STROKE,
SPEED,
FADE,
DISTANCE,
TIME,
ANGLE,
ROTATION,
PRESSURE,
XTILT,
YTILT,
TILT_DIRECTION,
TILT_ELEVATATION,
PERSPECTIVE,
TANGENTIAL_PRESSURE,
SENSORS_LIST,
PRESSURE_IN,
UNKNOWN = 255
};
/**
* Sensors are used to extract from KisPaintInformation a single
* double value which can be used to control the parameters of
* a brush.
*/
class PAINTOP_EXPORT KisDynamicSensor : public KisSerializableConfiguration
{
public:
enum ParameterSign {
NegativeParameter = -1,
UnSignedParameter = 0,
PositiveParameter = 1
};
protected:
KisDynamicSensor(DynamicSensorType type);
public:
~KisDynamicSensor() override;
/**
* @return the value of this sensor for the given KisPaintInformation
*/
qreal parameter(const KisPaintInformation& info);
/**
* This function is call before beginning a stroke to reset the sensor.
* Default implementation does nothing.
*/
virtual void reset();
/**
* @param parent the parent QWidget
* @param selector is a \ref QWidget that contains a signal called "parametersChanged()"
*/
virtual QWidget* createConfigurationWidget(QWidget* parent, QWidget* selector);
/**
* Creates a sensor from its identifier.
*/
static KisDynamicSensorSP id2Sensor(const KoID& id, const QString &parentOptionName);
static KisDynamicSensorSP id2Sensor(const QString& s, const QString &parentOptionName) {
return id2Sensor(KoID(s), parentOptionName);
}
static DynamicSensorType id2Type(const KoID& id);
static DynamicSensorType id2Type(const QString& s) {
return id2Type(KoID(s));
}
/**
* type2Sensor creates a new sensor for the give type
*/
static KisDynamicSensorSP type2Sensor(DynamicSensorType sensorType, const QString &parentOptionName);
static QString minimumLabel(DynamicSensorType sensorType);
static QString maximumLabel(DynamicSensorType sensorType, int max = -1);
static int minimumValue(DynamicSensorType sensorType);
static int maximumValue(DynamicSensorType sensorType, int max = -1);
static QString valueSuffix(DynamicSensorType sensorType);
static KisDynamicSensorSP createFromXML(const QString&, const QString &parentOptionName);
static KisDynamicSensorSP createFromXML(const QDomElement&, const QString &parentOptionName);
/**
* @return the list of sensors
*/
static QList<KoID> sensorsIds();
static QList<DynamicSensorType> sensorsTypes();
/**
* @return the identifier of this sensor
*/
static QString id(DynamicSensorType sensorType);
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
void setCurve(const KisCubicCurve& curve);
const KisCubicCurve& curve() const;
void removeCurve();
bool hasCustomCurve() const;
void setActive(bool active);
bool isActive() const;
virtual bool dependsOnCanvasRotation() const;
virtual bool isAdditive() const;
virtual bool isAbsoluteRotation() const;
inline DynamicSensorType sensorType() const { return m_type; }
/**
* @return the currently set length or -1 if not relevant
*/
int length() { return m_length; }
public:
static inline qreal scalingToAdditive(qreal x) {
return -1.0 + 2.0 * x;
}
static inline qreal additiveToScaling(qreal x) {
return 0.5 * (1.0 + x);
}
protected:
virtual qreal value(const KisPaintInformation& info) = 0;
int m_length;
private:
Q_DISABLE_COPY(KisDynamicSensor)
DynamicSensorType m_type;
bool m_customCurve;
KisCubicCurve m_curve;
bool m_active;
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensors.h b/plugins/paintops/libpaintop/kis_dynamic_sensors.h
index 34dc12a7f0..2bd15cc820 100644
--- a/plugins/paintops/libpaintop/kis_dynamic_sensors.h
+++ b/plugins/paintops/libpaintop/kis_dynamic_sensors.h
@@ -1,140 +1,141 @@
/*
* Copyright (c) 2006-2007,2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSORS_H_
#define _KIS_DYNAMIC_SENSORS_H_
#include "../kis_dynamic_sensor.h"
#include "kis_paint_information.h"
class KisDynamicSensorSpeed : public KisDynamicSensor
{
public:
KisDynamicSensorSpeed();
virtual ~KisDynamicSensorSpeed() { }
virtual qreal value(const KisPaintInformation& info);
void reset() {
m_speed = -1.0;
}
private:
double m_speed;
};
class KisDynamicSensorRotation : public KisDynamicSensor
{
public:
KisDynamicSensorRotation();
virtual ~KisDynamicSensorRotation() { }
virtual qreal value(const KisPaintInformation& info) {
return info.rotation() / 360.0;
}
};
class KisDynamicSensorPressure : public KisDynamicSensor
{
public:
KisDynamicSensorPressure();
virtual ~KisDynamicSensorPressure() { }
virtual qreal value(const KisPaintInformation& info) {
return info.pressure();
}
};
class KisDynamicSensorPressureIn : public KisDynamicSensor
{
public:
KisDynamicSensorPressureIn();
virtual ~KisDynamicSensorPressureIn() { }
virtual qreal value(const KisPaintInformation& info) {
if(!info.isHoveringMode()) {
if(info.pressure() > lastPressure) {
lastPressure = info.pressure();
}
return lastPressure;
}
lastPressure = 0.0;
return 0.0;
}
private:
qreal lastPressure;
};
class KisDynamicSensorXTilt : public KisDynamicSensor
{
public:
KisDynamicSensorXTilt();
virtual ~KisDynamicSensorXTilt() { }
virtual qreal value(const KisPaintInformation& info) {
return 1.0 - fabs(info.xTilt()) / 60.0;
}
};
class KisDynamicSensorYTilt : public KisDynamicSensor
{
public:
KisDynamicSensorYTilt();
virtual ~KisDynamicSensorYTilt() { }
virtual qreal value(const KisPaintInformation& info) {
return 1.0 - fabs(info.yTilt()) / 60.0;
}
};
class KisDynamicSensorTiltDirection : public KisDynamicSensor
{
public:
KisDynamicSensorTiltDirection();
virtual ~KisDynamicSensorTiltDirection() {}
virtual qreal value(const KisPaintInformation& info) {
return KisPaintInformation::tiltDirection(info, true);
}
};
class KisDynamicSensorTiltElevation : public KisDynamicSensor
{
public:
KisDynamicSensorTiltElevation();
virtual ~KisDynamicSensorTiltElevation() {}
virtual qreal value(const KisPaintInformation& info) {
return KisPaintInformation::tiltElevation(info, 60.0, 60.0, true);
}
};
class KisDynamicSensorPerspective : public KisDynamicSensor
{
public:
KisDynamicSensorPerspective();
virtual ~KisDynamicSensorPerspective() { }
virtual qreal value(const KisPaintInformation& info) {
return info.perspective();
}
};
class KisDynamicSensorTangentialPressure : public KisDynamicSensor
{
public:
KisDynamicSensorTangentialPressure();
virtual ~KisDynamicSensorTangentialPressure() { }
virtual qreal value(const KisPaintInformation& info) {
return info.tangentialPressure();
}
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_precision_option.cpp b/plugins/paintops/libpaintop/kis_precision_option.cpp
index 425d5733f3..5fb152aa24 100644
--- a/plugins/paintops/libpaintop/kis_precision_option.cpp
+++ b/plugins/paintops/libpaintop/kis_precision_option.cpp
@@ -1,101 +1,101 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
*
* 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
{
return m_precisionLevel;
}
void KisPrecisionOption::setPrecisionLevel(int precisionLevel)
{
m_precisionLevel = precisionLevel;
}
void KisPrecisionOption::setAutoPrecisionEnabled(int value)
{
m_autoPrecisionEnabled = value;
}
void KisPrecisionOption::setDeltaValue(double value)
{
m_deltaValue = value;
}
void KisPrecisionOption::setSizeToStartFrom(double value)
{
m_sizeToStartFrom = value;
}
bool KisPrecisionOption::autoPrecisionEnabled()
{
return m_autoPrecisionEnabled;
}
double KisPrecisionOption::deltaValue()
{
return m_deltaValue;
}
double KisPrecisionOption::sizeToStartFrom()
{
return m_sizeToStartFrom;
}
void KisPrecisionOption::setAutoPrecision(double brushSize)
{
double deltaValue = this->deltaValue();
double sizeToStartFrom = this ->sizeToStartFrom();
- if (brushSize <= sizeToStartFrom + deltaValue)
+ if (brushSize < sizeToStartFrom + deltaValue)
{
this->setPrecisionLevel(5);
}
- else if (brushSize > sizeToStartFrom + deltaValue && brushSize <= sizeToStartFrom + deltaValue*3)
+ else if (brushSize >= sizeToStartFrom + deltaValue && brushSize < sizeToStartFrom + deltaValue*2)
{
this->setPrecisionLevel(4);
}
- else if (brushSize > sizeToStartFrom + deltaValue*2 && brushSize <= sizeToStartFrom + deltaValue*4)
+ else if (brushSize >= sizeToStartFrom + deltaValue*2 && brushSize < sizeToStartFrom + deltaValue*3)
{
this->setPrecisionLevel(3);
}
- else if (brushSize > sizeToStartFrom + deltaValue*3 && brushSize <= sizeToStartFrom + deltaValue*5)
+ else if (brushSize >= sizeToStartFrom + deltaValue*3 && brushSize < sizeToStartFrom + deltaValue*4)
{
this->setPrecisionLevel(2);
}
- else if (brushSize > sizeToStartFrom + deltaValue*4)
+ else if (brushSize >= sizeToStartFrom + deltaValue*4)
{
this->setPrecisionLevel(1);
}
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc
index 02347e68dc..25af0e2aa0 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc
@@ -1,84 +1,85 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_distance.h"
#include <kis_debug.h>
#include <QDomElement>
#include "ui_SensorDistanceConfiguration.h"
#include <brushengine/kis_paint_information.h>
KisDynamicSensorDistance::KisDynamicSensorDistance()
: KisDynamicSensor(DISTANCE)
, m_periodic(true)
{
setLength(30);
}
qreal KisDynamicSensorDistance::value(const KisPaintInformation& pi)
{
if (pi.isHoveringMode()) return 1.0;
const qreal distance =
m_periodic ?
fmod(pi.totalStrokeLength(), m_length) :
qMin(pi.totalStrokeLength(), (qreal)m_length);
return distance / m_length;
}
void KisDynamicSensorDistance::setPeriodic(bool periodic)
{
m_periodic = periodic;
}
void KisDynamicSensorDistance::setLength(int length)
{
m_length = length;
}
QWidget* KisDynamicSensorDistance::createConfigurationWidget(QWidget* parent, QWidget* ss)
{
QWidget* wdg = new QWidget(parent);
Ui_SensorDistanceConfiguration stc;
stc.setupUi(wdg);
stc.checkBoxRepeat->setChecked(m_periodic);
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool)));
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged()));
stc.spinBoxLength->setValue(m_length);
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), SLOT(setLength(int)));
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged()));
return wdg;
}
void KisDynamicSensorDistance::toXML(QDomDocument& doc, QDomElement& e) const
{
KisDynamicSensor::toXML(doc, e);
e.setAttribute("periodic", m_periodic);
e.setAttribute("length", m_length);
}
void KisDynamicSensorDistance::fromXML(const QDomElement& e)
{
KisDynamicSensor::fromXML(e);
m_periodic = e.attribute("periodic", "0").toInt();
m_length = e.attribute("length", "30").toInt();
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h
index 7cc1b7093a..b63c0c9f9a 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h
@@ -1,45 +1,46 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_DISTANCE_H_
#define _KIS_DYNAMIC_SENSOR_DISTANCE_H_
#include "kis_dynamic_sensor.h"
//
class KisDynamicSensorDistance : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
KisDynamicSensorDistance();
~KisDynamicSensorDistance() override { }
qreal value(const KisPaintInformation&) override;
QWidget* createConfigurationWidget(QWidget* parent, QWidget*) override;
public Q_SLOTS:
virtual void setPeriodic(bool periodic);
virtual void setLength(int length);
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
private:
bool m_periodic;
};
#endif
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp
index 8eeff9ae21..b3eb6f119b 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp
@@ -1,89 +1,90 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_fade.h"
#include <kis_debug.h>
#include <QDomElement>
#include "ui_SensorFadeConfiguration.h"
#include <brushengine/kis_paint_information.h>
static const int DEFAULT_LENGTH = 1000;
KisDynamicSensorFade::KisDynamicSensorFade()
: KisDynamicSensor(FADE)
, m_periodic(false)
{
setLength(DEFAULT_LENGTH);
}
qreal KisDynamicSensorFade::value(const KisPaintInformation& pi)
{
if (pi.isHoveringMode()) return 1.0;
const int currentValue =
m_periodic ?
pi.currentDabSeqNo() % m_length :
qMin(pi.currentDabSeqNo(), m_length);
return qreal(currentValue) / m_length;
}
void KisDynamicSensorFade::setPeriodic(bool periodic)
{
m_periodic = periodic;
}
void KisDynamicSensorFade::setLength(int length)
{
m_length = length;
}
QWidget* KisDynamicSensorFade::createConfigurationWidget(QWidget* parent, QWidget* ss)
{
QWidget* wdg = new QWidget(parent);
Ui_SensorFadeConfiguration stc;
stc.setupUi(wdg);
stc.checkBoxRepeat->setChecked(m_periodic);
stc.spinBoxLength->setSuffix(i18n(" px"));
stc.spinBoxLength->setExponentRatio(3.0);
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool)));
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged()));
stc.spinBoxLength->setValue(m_length);
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), SLOT(setLength(int)));
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged()));
return wdg;
}
void KisDynamicSensorFade::toXML(QDomDocument& doc, QDomElement& e) const
{
KisDynamicSensor::toXML(doc, e);
e.setAttribute("periodic", m_periodic);
e.setAttribute("length", m_length);
}
void KisDynamicSensorFade::fromXML(const QDomElement& e)
{
KisDynamicSensor::fromXML(e);
m_periodic = e.attribute("periodic", "0").toInt();
m_length = e.attribute("length", QString::number(DEFAULT_LENGTH)).toInt();
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h
index 4384b93d89..3c5d5af6e2 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h
@@ -1,46 +1,47 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_FADE_H_
#define _KIS_DYNAMIC_SENSOR_FADE_H_
#include "kis_dynamic_sensor.h"
class KisDynamicSensorFade : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
KisDynamicSensorFade();
~KisDynamicSensorFade() override { }
qreal value(const KisPaintInformation&) override;
QWidget* createConfigurationWidget(QWidget* parent, QWidget*) override;
public Q_SLOTS:
virtual void setPeriodic(bool periodic);
virtual void setLength(int length);
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
private:
bool m_periodic;
};
#endif
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc
index 9c9a4d9fad..ec1385b7da 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc
@@ -1,94 +1,95 @@
/*
* Copyright (c) 2007,2010 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_time.h"
#include <kis_debug.h>
#include <QDomElement>
#include "ui_SensorTimeConfiguration.h"
#include <brushengine/kis_paint_information.h>
KisDynamicSensorTime::KisDynamicSensorTime()
: KisDynamicSensor(TIME)
, m_periodic(true)
{
setLength(3);
}
qreal KisDynamicSensorTime::value(const KisPaintInformation& pi)
{
if (pi.isHoveringMode()) return 1.0;
const qreal currentTime =
m_periodic ?
std::fmod(pi.currentTime(), m_length) :
qMin(pi.currentTime(), qreal(m_length));
return currentTime / qreal(m_length);
}
void KisDynamicSensorTime::reset()
{
}
void KisDynamicSensorTime::setPeriodic(bool periodic)
{
m_periodic = periodic;
}
void KisDynamicSensorTime::setLength(qreal length)
{
m_length = (int)(length * 1000); // convert to milliseconds
}
QWidget* KisDynamicSensorTime::createConfigurationWidget(QWidget* parent, QWidget* ss)
{
QWidget* wdg = new QWidget(parent);
Ui_SensorTimeConfiguration stc;
stc.setupUi(wdg);
stc.checkBoxRepeat->setChecked(m_periodic);
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool)));
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged()));
stc.spinBoxDuration->setRange(0.02, 10.0, 2);
stc.spinBoxDuration->setSuffix(i18n(" s"));
stc.spinBoxDuration->setValue(m_length / 1000);
connect(stc.spinBoxDuration, SIGNAL(valueChanged(qreal)), SLOT(setLength(qreal)));
connect(stc.spinBoxDuration, SIGNAL(valueChanged(qreal)), ss, SIGNAL(parametersChanged()));
return wdg;
}
void KisDynamicSensorTime::toXML(QDomDocument& doc, QDomElement& e) const
{
KisDynamicSensor::toXML(doc, e);
e.setAttribute("periodic", m_periodic);
e.setAttribute("duration", m_length);
}
void KisDynamicSensorTime::fromXML(const QDomElement& e)
{
KisDynamicSensor::fromXML(e);
m_periodic = e.attribute("periodic", "0").toInt();
m_length = e.attribute("duration", "30").toInt();
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h
index 82e458cdc0..5a982dc532 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h
@@ -1,47 +1,48 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_TIME_H_
#define _KIS_DYNAMIC_SENSOR_TIME_H_
#include "kis_dynamic_sensor.h"
//
class KisDynamicSensorTime : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
KisDynamicSensorTime();
~KisDynamicSensorTime() override { }
qreal value(const KisPaintInformation&) override;
void reset() override;
QWidget* createConfigurationWidget(QWidget* parent, QWidget*) override;
public Q_SLOTS:
virtual void setPeriodic(bool periodic);
virtual void setLength(qreal length);
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
private:
bool m_periodic;
};
#endif
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.cc b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.cc
index f29e6c09b4..c871947bc4 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.cc
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.cc
@@ -1,96 +1,97 @@
/*
* Copyright (c) 2007,2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensors.h"
KisDynamicSensorSpeed::KisDynamicSensorSpeed()
: KisDynamicSensor(SPEED)
{
}
qreal KisDynamicSensorSpeed::value(const KisPaintInformation& info)
{
/**
* The value of maximum speed was measured empirically. This is
* the speed that is quite easy to get with an A6 tablet and quite
* a big image. If you need smaller speeds, just change the curve.
*/
const qreal maxSpeed = 30.0; // px / ms
const qreal blendExponent = 0.05;
qreal currentSpeed = info.drawingSpeed() / maxSpeed;
if (m_speed >= 0.0) {
m_speed = qMin(1.0, (m_speed * (1 - blendExponent) +
currentSpeed * blendExponent));
}
else {
m_speed = currentSpeed;
}
return m_speed;
}
KisDynamicSensorRotation::KisDynamicSensorRotation()
: KisDynamicSensor(ROTATION)
{
}
KisDynamicSensorPressure::KisDynamicSensorPressure()
: KisDynamicSensor(PRESSURE)
{
}
KisDynamicSensorPressureIn::KisDynamicSensorPressureIn()
: KisDynamicSensor(PRESSURE_IN)
, lastPressure(0.0)
{
}
KisDynamicSensorXTilt::KisDynamicSensorXTilt()
: KisDynamicSensor(XTILT)
{
}
KisDynamicSensorYTilt::KisDynamicSensorYTilt()
: KisDynamicSensor(YTILT)
{
}
KisDynamicSensorTiltDirection::KisDynamicSensorTiltDirection() :
KisDynamicSensor(TILT_DIRECTION)
{
}
KisDynamicSensorTiltElevation::KisDynamicSensorTiltElevation()
: KisDynamicSensor(TILT_ELEVATATION)
{
}
KisDynamicSensorPerspective::KisDynamicSensorPerspective()
: KisDynamicSensor(PERSPECTIVE)
{
}
KisDynamicSensorTangentialPressure::KisDynamicSensorTangentialPressure()
: KisDynamicSensor(TANGENTIAL_PRESSURE)
{
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.h
index 1d21d68fb0..e2c05c7e94 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.h
@@ -1,140 +1,141 @@
/*
* Copyright (c) 2006-2007,2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* 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.
+ * the Free Software Foundation; 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSORS_H_
#define _KIS_DYNAMIC_SENSORS_H_
#include "../kis_dynamic_sensor.h"
#include <brushengine/kis_paint_information.h>
class KisDynamicSensorSpeed : public KisDynamicSensor
{
public:
KisDynamicSensorSpeed();
~KisDynamicSensorSpeed() override { }
qreal value(const KisPaintInformation& info) override;
void reset() override {
m_speed = -1.0;
}
private:
double m_speed;
};
class KisDynamicSensorRotation : public KisDynamicSensor
{
public:
KisDynamicSensorRotation();
~KisDynamicSensorRotation() override { }
qreal value(const KisPaintInformation& info) override {
return info.rotation() / 360.0;
}
};
class KisDynamicSensorPressure : public KisDynamicSensor
{
public:
KisDynamicSensorPressure();
~KisDynamicSensorPressure() override { }
qreal value(const KisPaintInformation& info) override {
return info.pressure();
}
};
class KisDynamicSensorPressureIn : public KisDynamicSensor
{
public:
KisDynamicSensorPressureIn();
~KisDynamicSensorPressureIn() override { }
qreal value(const KisPaintInformation& info) override {
if(!info.isHoveringMode()) {
if(info.pressure() > lastPressure) {
lastPressure = info.pressure();
}
return lastPressure;
}
lastPressure = 0.0;
return 0.0;
}
private:
qreal lastPressure;
};
class KisDynamicSensorXTilt : public KisDynamicSensor
{
public:
KisDynamicSensorXTilt();
~KisDynamicSensorXTilt() override { }
qreal value(const KisPaintInformation& info) override {
return 1.0 - fabs(info.xTilt()) / 60.0;
}
};
class KisDynamicSensorYTilt : public KisDynamicSensor
{
public:
KisDynamicSensorYTilt();
~KisDynamicSensorYTilt() override { }
qreal value(const KisPaintInformation& info) override {
return 1.0 - fabs(info.yTilt()) / 60.0;
}
};
class KisDynamicSensorTiltDirection : public KisDynamicSensor
{
public:
KisDynamicSensorTiltDirection();
~KisDynamicSensorTiltDirection() override {}
qreal value(const KisPaintInformation& info) override {
return KisPaintInformation::tiltDirection(info, true);
}
};
class KisDynamicSensorTiltElevation : public KisDynamicSensor
{
public:
KisDynamicSensorTiltElevation();
~KisDynamicSensorTiltElevation() override {}
qreal value(const KisPaintInformation& info) override {
return KisPaintInformation::tiltElevation(info, 60.0, 60.0, true);
}
};
class KisDynamicSensorPerspective : public KisDynamicSensor
{
public:
KisDynamicSensorPerspective();
~KisDynamicSensorPerspective() override { }
qreal value(const KisPaintInformation& info) override {
return info.perspective();
}
};
class KisDynamicSensorTangentialPressure : public KisDynamicSensor
{
public:
KisDynamicSensorTangentialPressure();
~KisDynamicSensorTangentialPressure() override { }
qreal value(const KisPaintInformation& info) override {
return info.tangentialPressure();
}
};
#endif
diff --git a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
index fb4c42a72d..e4fe9fb501 100644
--- a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
+++ b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
@@ -1,56 +1,56 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=comics_project_management_tools
X-Krita-Manual=README.html
X-Python-2-Compatible=false
Name=Comics Project Management Tools
Name[ar]=أدوات إدارة المشاريع الهزليّة
Name[ca]=Eines per a la gestió dels projectes de còmics
Name[ca@valencia]=Eines per a la gestió dels projectes de còmics
Name[cs]=Nástroje pro správu projektů komixů
Name[el]=Εργαλεία διαχείρισης έργων ιστοριών σε εικόνες
Name[en_GB]=Comics Project Management Tools
Name[es]=Herramientas de gestión de proyectos de cómics
Name[eu]=Komikien proiektuak kudeatzeko tresnak
Name[fi]=Sarjakuvaprojektien hallintatyökalut
Name[fr]=Outils de gestion d'un projet de bande dessinée
Name[gl]=Ferramentas de xestión de proxectos de cómics
Name[is]=Verkefnisstjórn teiknimyndasögu
Name[it]=Strumenti per la gestione dei progetti di fumetti
Name[ko]=만화 프로젝트 관리 도구
Name[nl]=Hulpmiddelen voor projectbeheer van strips
Name[nn]=Prosjekthandsamingsverktøy for teikneseriar
Name[pl]=Narzędzia do zarządzania projektami komiksów
Name[pt]=Ferramentas de Gestão de Projectos de Banda Desenhada
Name[sv]=Projekthanteringsverktyg för tecknade serier
Name[tr]=Çizgi Roman Projesi Yönetimi Araçları
-Name[uk]=Інструменти для керування проектами коміксів
+Name[uk]=Інструменти для керування проєктами коміксів
Name[x-test]=xxComics Project Management Toolsxx
Name[zh_CN]=漫画项目管理工具
Name[zh_TW]=漫畫專案管理工具
Comment=Tools for managing comics.
Comment[ar]=أدوات لإدارة الهزليّات.
Comment[ca]=Eines per a gestionar els còmics.
Comment[ca@valencia]=Eines per a gestionar els còmics.
Comment[cs]=Nástroje pro správu komixů.
Comment[el]=Εργαλεία για τη διαχείριση ιστοριών σε εικόνες.
Comment[en_GB]=Tools for managing comics.
Comment[es]=Herramientas para gestionar cómics.
Comment[eu]=Komikiak kudeatzeko tresnak.
Comment[fi]=Sarjakuvien hallintatyökalut.
Comment[fr]=Outils pour gérer les bandes dessinées.
Comment[gl]=Ferramentas para xestionar cómics.
Comment[is]=Verkfæri til að stýra gerð teiknimyndasögu.
Comment[it]=Strumenti per la gestione dei fumetti.
Comment[ko]=만화 관리 도구입니다.
Comment[nl]=Hulpmiddelen voor beheer van strips.
Comment[nn]=Verktøy for handsaming av teikneseriar.
Comment[pl]=Narzędzie do zarządzania komiksami.
Comment[pt]=Ferramentas para gerir bandas desenhadas.
Comment[sv]=Verktyg för att hantera tecknade serier.
Comment[tr]=Çizgi romanları yönetmek için araçlar.
Comment[uk]=Інструменти для керування коміксами
Comment[x-test]=xxTools for managing comics.xx
Comment[zh_CN]=用于管理漫画的工具。
Comment[zh_TW]=管理漫畫的工具。
diff --git a/plugins/python/documenttools/tools/scaletool/scaletool.py b/plugins/python/documenttools/tools/scaletool/scaletool.py
index ec764e2643..5d3268f130 100644
--- a/plugins/python/documenttools/tools/scaletool/scaletool.py
+++ b/plugins/python/documenttools/tools/scaletool/scaletool.py
@@ -1,63 +1,63 @@
-/# This script is licensed CC 0 1.0, so that you can learn from it.
+# This script is licensed CC 0 1.0, so that you can learn from it.
# ------ CC 0 1.0 ---------------
# The person who associated a work with this deed has dedicated the
# work to the public domain by waiving all of his or her rights to the
# work worldwide under copyright law, including all related and
# neighboring rights, to the extent allowed by law.
# You can copy, modify, distribute and perform the work, even for
# commercial purposes, all without asking permission.
# https://creativecommons.org/publicdomain/zero/1.0/legalcode
from PyQt5.QtWidgets import (QWidget, QSpinBox,
QVBoxLayout, QFormLayout, QComboBox)
class ScaleTool(QWidget):
def __init__(self, mainDialog, parent=None):
super(ScaleTool, self).__init__(parent)
self.setObjectName(i18n("Scale"))
self.layout = QFormLayout()
self.resolutionLayout = QVBoxLayout()
self.widthSpinBox = QSpinBox()
self.heightSpinBox = QSpinBox()
self.xResSpinBox = QSpinBox()
self.yResSpinBox = QSpinBox()
self.strategyComboBox = QComboBox()
self.strategyComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.setLayout(self.layout)
self.initialize()
def initialize(self):
self.widthSpinBox.setRange(1, 10000)
self.heightSpinBox.setRange(1, 10000)
self.xResSpinBox.setRange(1, 10000)
self.yResSpinBox.setRange(1, 10000)
strategies = ['Hermite', 'Bicubic', 'Box',
'Bilinear', 'Bell', 'BSpline',
'Kanczos3', 'Mitchell']
self.strategyComboBox.addItems(strategies)
self.resolutionLayout.addWidget(self.xResSpinBox)
self.resolutionLayout.addWidget(self.yResSpinBox)
self.layout.addRow(i18n("Width:"), self.widthSpinBox)
self.layout.addRow(i18n("Height:"), self.heightSpinBox)
self.layout.addRow(i18n("Resolution:"), self.resolutionLayout)
self.layout.addRow(i18n("Filter:"), self.strategyComboBox)
def adjust(self, documents):
for document in documents:
document.scaleImage(self.widthSpinBox.value(),
self.heightSpinBox.value(),
self.xResSpinBox.value(),
self.yResSpinBox.value(),
self.strategyComboBox.currentText())
diff --git a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
index 12ae3f41b0..5b54ae897f 100644
--- a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
+++ b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
@@ -1,52 +1,52 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=lastdocumentsdocker
X-Python-2-Compatible=false
X-Krita-Manual=Manual.html
Name=Last Documents Docker
Name[ar]=رصيف بآخر المستندات
-Name[ca]=Acoblador dels darrers documents
-Name[ca@valencia]=Acoblador dels darrers documents
+Name[ca]=Acoblador Darrers documents
+Name[ca@valencia]=Acoblador Darrers documents
Name[el]=Προσάρτηση τελευταίων εγγράφοων
Name[en_GB]=Last Documents Docker
Name[es]=Panel de últimos documentos
Name[eu]=Azken dokumentuen panela
Name[fi]=Viimeisimpien tiedostojen telakka
Name[fr]=Récemment ouverts
Name[gl]=Doca dos últimos documentos
Name[it]=Area di aggancio Ultimi documenti
Name[ko]=마지막 문서 도킹 패널
Name[nl]=Laatste documenten verankering
Name[nn]=Dokk for nyleg brukte dokument
Name[pl]=Dok ostatnich dokumentów
Name[pt]=Área dos Últimos Documentos
Name[sv]=Dockningsfönster för senaste dokument
Name[tr]=Son Belgeler Doku
Name[uk]=Бічна панель останніх документів
Name[x-test]=xxLast Documents Dockerxx
Name[zh_CN]=最近文档工具面板
Name[zh_TW]=「最後檔案」面板
Comment=A Python-based docker for show thumbnails to last ten documents
Comment[ar]=رصيف بِ‍«پيثون» لعرض مصغّرات آخر ١٠ مستندات مفتوحة
Comment[ca]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents
Comment[ca@valencia]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την εμφάνιση επισκοπήσεων των δέκα τελευταίων εγγράφων
Comment[en_GB]=A Python-based docker for show thumbnails to last ten documents
Comment[es]=Un panel basado en Python para mostrar miniaturas de los últimos diez documentos
Comment[eu]=Azken hamar dokumentuen koadro-txikiak erakusteko Python-oinarridun panel bat
Comment[fi]=Python-pohjainen telakka viimeisimpien kymmenen tiedoston pienoiskuvien näyttämiseen
Comment[fr]=Panneau en Python pour afficher les vignettes des dix derniers documents
Comment[gl]=Unha doca baseada en Python para mostrar as miniaturas dos últimos dez documentos.
Comment[it]=Un'area di aggancio basata su Python per mostrare miniature degli ultimi dieci documenti.
Comment[ko]=10개의 문서를 표시할 수 있는 Python 기반 도킹 패널
Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten.
Comment[nn]=Python-basert dokk for vising av miniatyrbilete av dei siste ti dokumenta
Comment[pl]=Dok oparty na pythonie do wyświetlania miniatur ostatnich dziesięciu dokumentów
Comment[pt]=Uma área acoplável, feita em Python, para mostrar as miniaturas dos últimos dez documentos
Comment[sv]=Ett Python-baserat dockningsfönster för att visa miniatyrbilder för de tio senaste dokumenten
Comment[tr]=Son on belgenin küçük resmini göstermek için Python-tabanlı bir dok
Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів
Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx
Comment[zh_CN]=基于 Python 的工具面板,显示最近十个文档的缩略图
Comment[zh_TW]=基於 Python 的面板,用於顯示最後 10 個檔案縮圖
diff --git a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop
index 6b63d91bb4..1e1ccfb596 100644
--- a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop
+++ b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop
@@ -1,55 +1,55 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=palette_docker
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Palette docker
Name[ar]=رصيف اللوحات
-Name[ca]=Acoblador de paletes
-Name[ca@valencia]=Acoblador de paletes
+Name[ca]=Acoblador Paleta
+Name[ca@valencia]=Acoblador Paleta
Name[cs]=Dok palet
Name[de]=Paletten-Docker
Name[el]=Προσάρτηση παλέτας
Name[en_GB]=Palette docker
Name[es]=Panel de paleta
Name[eu]=Paleta-panela
Name[fi]=Palettitelakka
Name[fr]=Panneau de palette
Name[gl]=Doca de paleta
Name[is]=Tengikví fyrir litaspjald
Name[it]=Area di aggancio della tavolozza
Name[ko]=팔레트 도킹 패널
Name[nl]=Vastzetter van palet
Name[nn]=Palettdokk
Name[pl]=Dok palety
Name[pt]=Área acoplável da paleta
Name[sv]=Dockningsfönster för palett
Name[tr]=Palet doku
Name[uk]=Панель палітри
Name[x-test]=xxPalette dockerxx
Name[zh_CN]=调色板工具面板
Name[zh_TW]=「調色盤」面板
Comment=A Python-based docker to edit color palettes.
Comment[ar]=رصيف بِ‍«پيثون» لتحرير لوحات الألوان.
Comment[ca]=Un acoblador basat en Python per editar paletes de colors.
Comment[ca@valencia]=Un acoblador basat en Python per editar paletes de colors.
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την επεξεργασία παλετών χρώματος.
Comment[en_GB]=A Python-based docker to edit colour palettes.
Comment[es]=Un panel basado en Python para editar paletas de colores.
Comment[eu]=Kolore-paletak editatzeko Python-oinarridun paleta bat.
Comment[fi]=Python-pohjainen telakka väripalettien muokkaamiseen.
Comment[fr]=Panneau en Python pour éditer les palettes de couleurs.
Comment[gl]=Unha doca baseada en Python para editar paletas de cores.
Comment[it]=Un'area di aggancio per modificare le tavolozze di colori basata su Python.
Comment[ko]=색상 팔레트를 편집할 수 있는 Python 기반 도킹 패널입니다.
Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken.
Comment[nn]=Python-basert dokk for redigering av fargepalettar.
Comment[pl]=Dok oparty na pythonie do edytowania palet barw.
Comment[pt]=Uma área acoplável, feita em Python, para editar paletas de cores.
Comment[sv]=Ett Python-baserat dockningsfönster för att redigera färgpaletter.
Comment[tr]=Renk paletlerini düzenlemek için Python-tabanlı bir dok.
Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python.
Comment[x-test]=xxA Python-based docker to edit color palettes.xx
Comment[zh_CN]=基于 Python 的调色板编辑器工具面板
Comment[zh_TW]=基於 Python 的面板,用於編輯調色盤。
diff --git a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
index 822d624782..dd4d9681a0 100644
--- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
+++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
@@ -1,53 +1,53 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=quick_settings_docker
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Quick Settings Docker
Name[ar]=رصيف بإعدادات سريعة
-Name[ca]=Acoblador d'Arranjament ràpid
-Name[ca@valencia]=Acoblador d'Arranjament ràpid
+Name[ca]=Acoblador Arranjament ràpid
+Name[ca@valencia]=Acoblador Arranjament ràpid
Name[cs]=Dok pro rychlé nastavení
Name[el]=Προσάρτηση γρήγορων ρυθμίσεων
Name[en_GB]=Quick Settings Docker
Name[es]=Panel de ajustes rápidos
Name[eu]=Ezarpen azkarren panela
Name[fi]=Pika-asetustelakka
Name[fr]=Réglages rapides
Name[gl]=Doca de configuración rápida
Name[it]=Area di aggancio delle impostazioni rapide
Name[ko]=빠른 설정 도킹 패널
Name[nl]=Verankering voor snelle instellingen
Name[nn]=Snøgginnstillingar-dokk
Name[pl]=Dok szybkich ustawień
Name[pt]=Área de Configuração Rápida
Name[sv]=Dockningspanel med snabbinställningar
Name[tr]=Hızlı Ayarlar Doku
Name[uk]=Панель швидких параметрів
Name[x-test]=xxQuick Settings Dockerxx
Name[zh_CN]=快速设置工具面板
Name[zh_TW]=「快速設定」面板
Comment=A Python-based docker for quickly changing brush size and opacity.
Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة.
Comment[ca]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell.
Comment[ca@valencia]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell.
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου.
Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity.
Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel.
Comment[eu]=Isipu-neurria eta -opakotasuna aldatzeko Python-oinarridun panel bat.
Comment[fi]=Python-pohjainen telakka siveltimen koon ja läpikuultavuuden nopean muuttamiseen.
Comment[fr]=Panneau en python pour modifier rapidement la taille et l'opacité des brosses.
Comment[gl]=Unha doca baseada en Python para cambiar rapidamente a opacidade e o tamaño dos pinceis.
Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità.
Comment[ko]=브러시 크기와 불투명도를 빠르게 변경할 수 있는 Python 기반 도킹 패널입니다.
Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking.
Comment[nn]=Python-basert dokk for kjapt endring av penselstorleik/-tettleik.
Comment[pl]=Dok oparty na Pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla.
Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel.
Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet.
Comment[tr]=Fırça boyutunu ve matlığını hızlıca değiştirmek için Python-tabanlı bir dok.
Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля.
Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx
Comment[zh_CN]=基于 Python 的用于快速更改笔刷尺寸和透明度的工具面板。
Comment[zh_TW]=基於 Python 的面板,用於快速變更筆刷尺寸和不透明度。
diff --git a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop
index 25cf7669c7..b98a814a39 100644
--- a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop
+++ b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop
@@ -1,50 +1,50 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=scriptdocker
X-Python-2-Compatible=false
Name=Script Docker
Name[ar]=رصيف سكربتات
-Name[ca]=Acoblador de scripts
-Name[ca@valencia]=Acoblador de scripts
+Name[ca]=Acoblador Script
+Name[ca@valencia]=Acoblador Script
Name[cs]=Dok skriptu
Name[el]=Προσάρτηση σεναρίων
Name[en_GB]=Script Docker
Name[es]=Panel de guiones
Name[eu]=Script-panela
Name[fi]=Skriptitelakka
Name[fr]=Panneau de script
Name[gl]=Doca de scripts
Name[it]=Area di aggancio degli script
Name[ko]=스크립트 도킹 패널
Name[nl]=Verankering van scripts
Name[nn]=Skriptdokk
Name[pl]=Dok skryptów
Name[pt]=Área de Programas
Name[sv]=Dockningsfönster för skript
Name[tr]=Betik Doku
Name[uk]=Бічна панель скриптів
Name[x-test]=xxScript Dockerxx
Name[zh_CN]=脚本工具面板
Name[zh_TW]=「指令稿」面板
Comment=A Python-based docker for create actions and point to Python scripts
Comment[ar]=رصيف بِ‍«پيثون» لإنشاء الإجراءات والإشارة إلى سكربتات «پيثون»
Comment[ca]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python
Comment[ca@valencia]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για τη δημιουργία ενεργειών και τη δεικτοδότηση σεναρίων σε Python
Comment[en_GB]=A Python-based docker for create actions and point to Python scripts
Comment[es]=Un panel basado en Python para crear y apuntar a guiones de Python
Comment[eu]=Python-oinarridun panel bat.
Comment[gl]=Unha doca escrita en Python para crear accións e apuntar a scripts escritos en Python.
Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere script Python
Comment[ko]=작업 생성 및 Python 스크립트를 가리키는 Python 기반 도킹 패널
Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts
Comment[nn]=Python-basert dokk for å laga handlingar og peika til Python-skript
Comment[pl]=Dok oparty na pythonie do tworzenia działań i punktów dla skryptów pythona
Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python
Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript
Comment[tr]=Eylemler oluşturmak ve Python betiklerine yönlendirmek için Python-tabanlı bir dok
Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python.
Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx
Comment[zh_CN]=基于 Python 的用于创建动作并指向 Python 脚本的工具面板
Comment[zh_TW]=基於 Python 的面板,用於建立動作並指向 Python 指令稿
diff --git a/plugins/python/scripter/uicontroller.py b/plugins/python/scripter/uicontroller.py
index 50123fac77..087d8b1408 100644
--- a/plugins/python/scripter/uicontroller.py
+++ b/plugins/python/scripter/uicontroller.py
@@ -1,265 +1,265 @@
"""
Copyright (c) 2017 Eliakin Costa <eliakim170@gmail.com>
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.
"""
from PyQt5.QtCore import Qt, QObject, QFileInfo, QRect
from PyQt5.QtGui import QTextCursor, QPalette
from PyQt5.QtWidgets import (QToolBar, QMenuBar, QTabWidget,
QLabel, QVBoxLayout, QMessageBox,
QSplitter, QSizePolicy)
from .ui_scripter.syntax import syntax, syntaxstyles
from .ui_scripter.editor import pythoneditor
from . import scripterdialog
import importlib
import krita
KEY_GEOMETRY = "geometry"
DEFAULT_GEOMETRY = QRect(600, 200, 400, 500)
# essentially randomly placed
class Elided_Text_Label(QLabel):
mainText = str()
def __init__(self, parent=None):
super(QLabel, self).__init__(parent)
self.setMinimumWidth(self.fontMetrics().width("..."))
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
def setMainText(self, text=str()):
self.mainText = text
self.elideText()
def elideText(self):
self.setText(self.fontMetrics().elidedText(self.mainText, Qt.ElideRight, self.width()))
def resizeEvent(self, event):
self.elideText()
class UIController(object):
documentModifiedText = ""
documentStatusBarText = "untitled"
def __init__(self):
self.mainWidget = scripterdialog.ScripterDialog(self)
self.actionToolbar = QToolBar('toolBar', self.mainWidget)
self.menu_bar = QMenuBar(self.mainWidget)
self.actionToolbar.setObjectName('toolBar')
self.menu_bar.setObjectName('menuBar')
self.actions = []
self.mainWidget.setWindowModality(Qt.NonModal)
def initialize(self, scripter):
self.editor = pythoneditor.CodeEditor(scripter)
self.tabWidget = QTabWidget()
self.statusBar = Elided_Text_Label()
self.statusBar.setMainText('untitled')
self.splitter = QSplitter()
self.splitter.setOrientation(Qt.Vertical)
self.highlight = syntax.PythonHighlighter(self.editor.document(), syntaxstyles.DefaultSyntaxStyle())
p = self.editor.palette()
p.setColor(QPalette.Base, syntaxstyles.DefaultSyntaxStyle()['background'].foreground().color())
p.setColor(QPalette.Text, syntaxstyles.DefaultSyntaxStyle()['foreground'].foreground().color())
self.editor.setPalette(p)
self.scripter = scripter
self.loadMenus()
self.loadWidgets()
self.loadActions()
self._readSettings() # sets window size
vbox = QVBoxLayout(self.mainWidget)
vbox.addWidget(self.menu_bar)
vbox.addWidget(self.actionToolbar)
self.splitter.addWidget(self.editor)
self.splitter.addWidget(self.tabWidget)
vbox.addWidget(self.splitter)
vbox.addWidget(self.statusBar)
self.mainWidget.setWindowTitle(i18n("Scripter"))
self.mainWidget.setSizeGripEnabled(True)
self.mainWidget.show()
self.mainWidget.activateWindow()
self.editor.undoAvailable.connect(self.setStatusModified)
def loadMenus(self):
self.addMenu(i18n('File'), i18n('File'))
def addMenu(self, menuName, parentName):
parent = self.menu_bar.findChild(QObject, parentName)
self.newMenu = None
if parent:
self.newMenu = parent.addMenu(menuName)
else:
self.newMenu = self.menu_bar.addMenu(menuName)
self.newMenu.setObjectName(menuName)
return self.newMenu
def loadActions(self):
module_path = 'scripter.ui_scripter.actions'
actions_module = importlib.import_module(module_path)
modules = []
for class_path in actions_module.action_classes:
_module = class_path[:class_path.rfind(".")]
_klass = class_path[class_path.rfind(".") + 1:]
modules.append(dict(module='{0}.{1}'.format(module_path, _module),
klass=_klass))
for module in modules:
m = importlib.import_module(module['module'])
action_class = getattr(m, module['klass'])
obj = action_class(self.scripter)
- parent = self.mainWidget.findChild(QObject, obj.parent)
+ parent = self.mainWidget.findChild(QObject, i18n(obj.parent))
self.actions.append(dict(action=obj, parent=parent))
for action in self.actions:
action['parent'].addAction(action['action'])
def loadWidgets(self):
modulePath = 'scripter.ui_scripter.tabwidgets'
widgetsModule = importlib.import_module(modulePath)
modules = []
for class_path in widgetsModule.widgetClasses:
_module = class_path[:class_path.rfind(".")]
_klass = class_path[class_path.rfind(".") + 1:]
modules.append(dict(module='{0}.{1}'.format(modulePath, _module),
klass=_klass))
for module in modules:
m = importlib.import_module(module['module'])
widgetClass = getattr(m, module['klass'])
obj = widgetClass(self.scripter)
self.tabWidget.addTab(obj, obj.objectName())
def invokeAction(self, actionName):
for action in self.actions:
if action['action'].objectName() == actionName:
method = getattr(action['action'], actionName)
if method:
return method()
def findTabWidget(self, widgetName, childName=''):
for index in range(self.tabWidget.count()):
widget = self.tabWidget.widget(index)
if widget.objectName() == widgetName:
if childName:
widget = widget.findChild(QObject, childName)
return widget
def showException(self, exception):
QMessageBox.critical(self.editor, i18n("Error Running Script"), str(exception))
def setDocumentEditor(self, document):
self.editor.clear()
self.editor.moveCursor(QTextCursor.Start)
self.editor._documentModified = False
self.editor.setPlainText(document.data)
self.editor.moveCursor(QTextCursor.End)
def setStatusBar(self, value='untitled'):
self.documentStatusBarText = value
self.statusBar.setMainText(self.documentStatusBarText + self.documentModifiedText)
def setStatusModified(self):
self.documentModifiedText = ""
if (self.editor._documentModified is True):
self.documentModifiedText = " [Modified]"
self.statusBar.setMainText(self.documentStatusBarText + self.documentModifiedText)
def setActiveWidget(self, widgetName):
widget = self.findTabWidget(widgetName)
if widget:
self.tabWidget.setCurrentWidget(widget)
def setStepped(self, status):
self.editor.setStepped(status)
def clearEditor(self):
self.editor.clear()
def repaintDebugArea(self):
self.editor.repaintDebugArea()
def closeScripter(self):
self.mainWidget.close()
def _writeSettings(self):
""" _writeSettings is a method invoked when the scripter starts, making
control inversion. Actions can implement a writeSettings method to
save your own settings without this method to know about it. """
self.scripter.settings.beginGroup('scripter')
document = self.scripter.documentcontroller.activeDocument
if document:
self.scripter.settings.setValue('activeDocumentPath', document.filePath)
else:
self.scripter.settings.setValue('activeDocumentPath', '')
self.scripter.settings.setValue('editorFontSize', self.editor.fontInfo().pointSize())
for action in self.actions:
writeSettings = getattr(action['action'], "writeSettings", None)
if callable(writeSettings):
writeSettings()
# Window Geometry
rect = self.mainWidget.geometry()
self.scripter.settings.setValue(KEY_GEOMETRY, rect)
self.scripter.settings.endGroup()
def _readSettings(self):
""" It's similar to _writeSettings, but reading the settings when the ScripterDialog is closed. """
self.scripter.settings.beginGroup('scripter')
activeDocumentPath = self.scripter.settings.value('activeDocumentPath', '')
if activeDocumentPath:
if QFileInfo(activeDocumentPath).exists():
document = self.scripter.documentcontroller.openDocument(activeDocumentPath)
self.setStatusBar(document.filePath)
self.setDocumentEditor(document)
for action in self.actions:
readSettings = getattr(action['action'], "readSettings", None)
if callable(readSettings):
readSettings()
pointSize = self.scripter.settings.value('editorFontSize', str(self.editor.fontInfo().pointSize()))
self.editor.setFontSize(int(pointSize))
# Window Geometry
rect = self.scripter.settings.value(KEY_GEOMETRY, DEFAULT_GEOMETRY)
self.mainWidget.setGeometry(rect)
self.scripter.settings.endGroup()
def _saveSettings(self):
self.scripter.settings.sync()
diff --git a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
index 07b5c70c8e..1b463b43a6 100644
--- a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
+++ b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
@@ -1,50 +1,50 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=selectionsbagdocker
X-Python-2-Compatible=false
Name=Selections Bag Docker
-Name[ca]=Acoblador de bossa de seleccions
-Name[ca@valencia]=Acoblador de bossa de seleccions
+Name[ca]=Acoblador Bossa de seleccions
+Name[ca@valencia]=Acoblador Bossa de seleccions
Name[el]=Προσάρτηση σάκου επιλογών
Name[en_GB]=Selections Bag Docker
Name[es]=Panel de selecciones
Name[eu]=Hautapen-zakua panela
Name[fr]=Outils de sélection
Name[gl]=Doca de bolsa das seleccións
Name[it]=Area di raccolta selezioni
Name[ko]=선택 가방 도킹 패널
Name[nl]=Docker van zak met selecties
Name[nn]=Utvalssamlingsdokk
Name[pl]=Dok worka zaznaczeń
Name[pt]=Área de Selecções
Name[sv]=Dockningspanel med markeringspåse
Name[tr]=Seçim Çantası Doku
Name[uk]=Бічна панель позначеного
Name[x-test]=xxSelections Bag Dockerxx
Name[zh_CN]=选区列表工具面板
Name[zh_TW]=「選取範圍收藏」面板
Comment=A docker that allow to store a list of selections
Comment[ar]=رصيف يتيح تخزين قائمة تحديدات
Comment[ca]=Un acoblador que permet emmagatzemar una llista de seleccions
Comment[ca@valencia]=Un acoblador que permet emmagatzemar una llista de seleccions
Comment[cs]=Dok umožňující uložit seznam výběrů
Comment[el]=Ένα εργαλείο προσάρτησης που επιτρέπει την αποθήκευση μιας λίστας επιλογών
Comment[en_GB]=A docker that allow to store a list of selections
Comment[es]=Un panel que permite guardar una lista de selecciones
Comment[eu]=Hautapen zerrenda bat biltegiratzen uzten duen panel bat
Comment[fi]=Telakka, joka sallii tallentaa valintaluettelon
Comment[fr]=Panneau permettant de conserver une liste de sélections
Comment[gl]=Unha doca que permite almacenar unha lista de seleccións.
Comment[it]=Un'area di aggancio che consente di memorizzare un elenco di selezioni
Comment[ko]=선택 목록을 저장할 수 있는 도킹 패널
Comment[nl]=Een docker die een lijst met selecties kan opslaan
Comment[nn]=Dokk for lagring av ei liste med utval
Comment[pl]=Dok, który umożliwia przechowywanie listy zaznaczeń
Comment[pt]=Uma área acoplável que permite guardar uma lista de selecções
Comment[sv]=En dockningspanel som gör det möjligt att lagra en lista över markeringar
Comment[tr]=Seçimlerin bir listesini saklamayı sağlayan bir dok
Comment[uk]=Бічна панель, на якій можна зберігати список позначеного
Comment[x-test]=xxA docker that allow to store a list of selectionsxx
Comment[zh_CN]=用于保存一组选区的工具面板
Comment[zh_TW]=允許儲存選取範圍列表的面板
diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc
index 449ef8f7d6..b8222a8cb0 100644
--- a/plugins/tools/basictools/kis_tool_move.cc
+++ b/plugins/tools/basictools/kis_tool_move.cc
@@ -1,674 +1,683 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* 1999 Michael Koch <koch@kde.org>
* 2002 Patrick Julien <freak@codepimps.org>
* 2004 Boudewijn Rempt <boud@valdyas.org>
* 2016 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_move.h"
#include <QPoint>
#include "kis_cursor.h"
#include "kis_selection.h"
#include "kis_canvas2.h"
#include "kis_image.h"
#include "kis_tool_utils.h"
#include "kis_paint_layer.h"
#include "strokes/move_stroke_strategy.h"
#include "kis_tool_movetooloptionswidget.h"
#include "strokes/move_selection_stroke_strategy.h"
#include "kis_resources_snapshot.h"
#include "kis_action_registry.h"
#include "krita_utils.h"
#include <KisViewManager.h>
#include <KisDocument.h>
#include "kis_node_manager.h"
#include "kis_signals_blocker.h"
#include <boost/operators.hpp>
struct KisToolMoveState : KisToolChangesTrackerData, boost::equality_comparable<KisToolMoveState>
{
KisToolMoveState(QPoint _accumulatedOffset) : accumulatedOffset(_accumulatedOffset) {}
KisToolChangesTrackerData* clone() const { return new KisToolMoveState(*this); }
bool operator ==(const KisToolMoveState &rhs) {
return accumulatedOffset == rhs.accumulatedOffset;
}
QPoint accumulatedOffset;
};
KisToolMove::KisToolMove(KoCanvasBase *canvas)
: KisTool(canvas, KisCursor::moveCursor())
, m_updateCursorCompressor(100, KisSignalCompressor::FIRST_ACTIVE)
{
setObjectName("tool_move");
m_showCoordinatesAction = action("movetool-show-coordinates");
m_showCoordinatesAction = action("movetool-show-coordinates");
connect(&m_updateCursorCompressor, SIGNAL(timeout()), this, SLOT(resetCursorStyle()));
m_optionsWidget = new MoveToolOptionsWidget(0, currentImage()->xRes(), toolId());
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height());
m_showCoordinatesAction->setChecked(m_optionsWidget->showCoordinates());
m_optionsWidget->slotSetTranslate(m_handlesRect.topLeft() + currentOffset());
connect(m_optionsWidget, SIGNAL(sigSetTranslateX(int)), SLOT(moveBySpinX(int)), Qt::UniqueConnection);
connect(m_optionsWidget, SIGNAL(sigSetTranslateY(int)), SLOT(moveBySpinY(int)), Qt::UniqueConnection);
connect(m_optionsWidget, SIGNAL(sigRequestCommitOffsetChanges()), this, SLOT(commitChanges()), Qt::UniqueConnection);
connect(this, SIGNAL(moveInNewPosition(QPoint)), m_optionsWidget, SLOT(slotSetTranslate(QPoint)), Qt::UniqueConnection);
connect(qobject_cast<KisCanvas2*>(canvas)->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)), this, SLOT(slotNodeChanged(KisNodeList)), Qt::UniqueConnection);
}
KisToolMove::~KisToolMove()
{
endStroke();
}
void KisToolMove::resetCursorStyle()
{
KisTool::resetCursorStyle();
if (!isActive()) return;
KisImageSP image = this->image();
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager());
KisSelectionSP selection = resources->activeSelection();
KisNodeList nodes = fetchSelectedNodes(moveToolMode(), &m_lastCursorPos, selection);
if (nodes.isEmpty()) {
canvas()->setCursor(Qt::ForbiddenCursor);
}
}
KisNodeList KisToolMove::fetchSelectedNodes(MoveToolMode mode, const QPoint *pixelPoint, KisSelectionSP selection)
{
KisNodeList nodes;
KisImageSP image = this->image();
if (mode != MoveSelectedLayer && pixelPoint) {
const bool wholeGroup = !selection && mode == MoveGroup;
KisNodeSP node = KisToolUtils::findNode(image->root(), *pixelPoint, wholeGroup);
if (node) {
nodes = {node};
}
}
if (nodes.isEmpty()) {
nodes = this->selectedNodes();
KritaUtils::filterContainer<KisNodeList>(nodes,
[](KisNodeSP node) {
return node->isEditable();
});
}
return nodes;
}
bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos)
{
KisNodeSP node;
KisImageSP image = this->image();
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager());
KisSelectionSP selection = resources->activeSelection();
KisNodeList nodes = fetchSelectedNodes(mode, pos, selection);
if (nodes.size() == 1) {
node = nodes.first();
}
if (nodes.isEmpty()) {
return false;
}
/**
* If the target node has changed, the stroke should be
* restarted. Otherwise just continue processing current node.
*/
if (m_strokeId && !tryEndPreviousStroke(nodes)) {
return true;
}
/**
* FIXME: The move tool is not completely asynchronous, it
* needs the content of the layer and/or selection to calculate
* the bounding rectange. Technically, we can move its calculation
* into the stroke itself and pass it back to the tool via a signal.
*
* But currently, we will just disable starting a new stroke
* asynchronously.
*/
- if (image->tryBarrierLock()) {
- image->unlock();
- } else {
+ if (!blockUntilOperationsFinished()) {
return false;
}
-
initHandles(nodes);
KisStrokeStrategy *strategy;
KisPaintLayerSP paintLayer = node ?
dynamic_cast<KisPaintLayer*>(node.data()) : 0;
if (paintLayer && selection &&
(!selection->selectedRect().isEmpty() &&
!selection->selectedExactRect().isEmpty())) {
strategy =
new MoveSelectionStrokeStrategy(paintLayer,
selection,
image.data(),
image.data());
} else {
strategy =
new MoveStrokeStrategy(nodes, image.data(), image.data());
}
m_strokeId = image->startStroke(strategy);
m_currentlyProcessingNodes = nodes;
m_accumulatedOffset = QPoint();
KIS_SAFE_ASSERT_RECOVER(m_changesTracker.isEmpty()) {
m_changesTracker.reset();
}
commitChanges();
return true;
}
QPoint KisToolMove::currentOffset() const
{
return m_accumulatedOffset + m_dragPos - m_dragStart;
}
void KisToolMove::notifyGuiAfterMove(bool showFloatingMessage)
{
if (!m_optionsWidget) return;
const QPoint currentTopLeft = m_handlesRect.topLeft() + currentOffset();
KisSignalsBlocker b(m_optionsWidget);
emit moveInNewPosition(currentTopLeft);
// TODO: fetch this info not from options widget, but from config
const bool showCoordinates = m_optionsWidget->showCoordinates();
if (showCoordinates && showFloatingMessage) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in move tool",
"X: %1 px, Y: %2 px",
currentTopLeft.x(),
currentTopLeft.y()),
QIcon(), 1000, KisFloatingMessage::High);
}
}
bool KisToolMove::tryEndPreviousStroke(KisNodeList nodes)
{
if (!m_strokeId) return false;
bool strokeEnded = false;
if (!KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) {
endStroke();
strokeEnded = true;
}
return strokeEnded;
}
void KisToolMove::commitChanges()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId);
QSharedPointer<KisToolMoveState> newState(new KisToolMoveState(m_accumulatedOffset));
KisToolMoveState *lastState = dynamic_cast<KisToolMoveState*>(m_changesTracker.lastState().data());
if (lastState && *lastState == *newState) return;
m_changesTracker.commitConfig(newState);
}
void KisToolMove::moveDiscrete(MoveDirection direction, bool big)
{
if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging
if (!currentNode()) return;
if (!image()) return;
if (!currentNode()->isEditable()) return; // Don't move invisible nodes
if (startStrokeImpl(MoveSelectedLayer, 0)) {
setMode(KisTool::PAINT_MODE);
}
// Larger movement if "shift" key is pressed.
qreal scale = big ? m_optionsWidget->moveScale() : 1.0;
qreal moveStep = m_optionsWidget->moveStep() * scale;
const QPoint offset =
direction == Up ? QPoint( 0, -moveStep) :
direction == Down ? QPoint( 0, moveStep) :
direction == Left ? QPoint(-moveStep, 0) :
QPoint( moveStep, 0) ;
m_accumulatedOffset += offset;
image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset));
notifyGuiAfterMove();
commitChanges();
setMode(KisTool::HOVER_MODE);
}
void KisToolMove::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisTool::activate(toolActivation, shapes);
- QAction *a = action("movetool-move-up");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Up, false);});
-
- a = action("movetool-move-down");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Down, false);});
-
- a = action("movetool-move-left");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Left, false);});
-
- a = action("movetool-move-right");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Right, false);});
-
- a = action("movetool-move-up-more");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Up, true);});
-
- a = action("movetool-move-down-more");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Down, true);});
-
- a = action("movetool-move-left-more");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Left, true);});
-
- a = action("movetool-move-right-more");
- connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Right, true);});
+ m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteUp()));
+ m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteDown()));
+ m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteLeft()));
+ m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteRight()));
+
+ m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteUpMore()));
+ m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteDownMore()));
+ m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteLeftMore()));
+ m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)),
+ this, SLOT(slotMoveDiscreteRightMore()));
connect(m_showCoordinatesAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(setShowCoordinates(bool)), Qt::UniqueConnection);
connect(m_optionsWidget, SIGNAL(showCoordinatesChanged(bool)), m_showCoordinatesAction, SLOT(setChecked(bool)), Qt::UniqueConnection);
connect(&m_changesTracker,
SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)),
SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP)));
slotNodeChanged(this->selectedNodes());
}
void KisToolMove::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if (m_strokeId) {
QPainterPath handles;
handles.addRect(m_handlesRect.translated(currentOffset()));
QPainterPath path = pixelToView(handles);
paintToolOutline(&gc, path);
}
}
void KisToolMove::initHandles(const KisNodeList &nodes)
{
/**
* The handles should be initialized only once, **before** the start of
* the stroke. If the nodes change, we should restart the stroke.
*/
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_strokeId);
m_handlesRect = QRect();
for (KisNodeSP node : nodes) {
node->exactBounds();
m_handlesRect |= node->exactBounds();
}
if (image()->globalSelection()) {
m_handlesRect &= image()->globalSelection()->selectedExactRect();
}
}
void KisToolMove::deactivate()
{
- QAction *a = action("movetool-move-up");
- disconnect(a, 0, this, 0);
-
- a = action("movetool-move-down");
- disconnect(a, 0, this, 0);
-
- a = action("movetool-move-left");
- disconnect(a, 0, this, 0);
-
- a = action("movetool-move-right");
- disconnect(a, 0, this, 0);
-
- a = action("movetool-move-up-more");
- disconnect(a, 0, this, 0);
-
- a = action("movetool-move-down-more");
- disconnect(a, 0, this, 0);
-
- a = action("movetool-move-left-more");
- disconnect(a, 0, this, 0);
-
- a = action("movetool-move-right-more");
- disconnect(a, 0, this, 0);
+ m_actionConnections.clear();
disconnect(m_showCoordinatesAction, 0, this, 0);
disconnect(m_optionsWidget, 0, this, 0);
endStroke();
KisTool::deactivate();
}
void KisToolMove::requestStrokeEnd()
{
endStroke();
}
void KisToolMove::requestStrokeCancellation()
{
cancelStroke();
}
void KisToolMove::requestUndoDuringStroke()
{
if (!m_strokeId) return;
if (m_changesTracker.isEmpty()) {
cancelStroke();
} else {
m_changesTracker.requestUndo();
}
}
void KisToolMove::beginPrimaryAction(KoPointerEvent *event)
{
startAction(event, moveToolMode());
}
void KisToolMove::continuePrimaryAction(KoPointerEvent *event)
{
continueAction(event);
}
void KisToolMove::endPrimaryAction(KoPointerEvent *event)
{
endAction(event);
}
void KisToolMove::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
// Ctrl+Right click toggles between moving current layer and moving layer w/ content
if (action == PickFgNode || action == PickBgImage) {
MoveToolMode mode = moveToolMode();
if (mode == MoveSelectedLayer) {
mode = MoveFirstLayer;
} else if (mode == MoveFirstLayer) {
mode = MoveSelectedLayer;
}
startAction(event, mode);
} else {
startAction(event, MoveGroup);
}
}
void KisToolMove::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
Q_UNUSED(action)
continueAction(event);
}
void KisToolMove::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
Q_UNUSED(action)
endAction(event);
}
void KisToolMove::mouseMoveEvent(KoPointerEvent *event)
{
m_lastCursorPos = convertToPixelCoord(event).toPoint();
KisTool::mouseMoveEvent(event);
if (moveToolMode() == MoveFirstLayer) {
m_updateCursorCompressor.start();
}
}
void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode)
{
QPoint pos = convertToPixelCoordAndSnap(event).toPoint();
m_dragStart = pos;
m_dragPos = pos;
if (startStrokeImpl(mode, &pos)) {
setMode(KisTool::PAINT_MODE);
} else {
event->ignore();
m_dragPos = QPoint();
m_dragStart = QPoint();
}
qobject_cast<KisCanvas2*>(canvas())->updateCanvas();
}
void KisToolMove::continueAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
if (!m_strokeId) return;
QPoint pos = convertToPixelCoordAndSnap(event).toPoint();
pos = applyModifiers(event->modifiers(), pos);
m_dragPos = pos;
drag(pos);
notifyGuiAfterMove();
qobject_cast<KisCanvas2*>(canvas())->updateCanvas();
}
void KisToolMove::endAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
setMode(KisTool::HOVER_MODE);
if (!m_strokeId) return;
QPoint pos = convertToPixelCoordAndSnap(event).toPoint();
pos = applyModifiers(event->modifiers(), pos);
drag(pos);
m_accumulatedOffset += pos - m_dragStart;
m_dragStart = QPoint();
m_dragPos = QPoint();
commitChanges();
notifyGuiAfterMove();
qobject_cast<KisCanvas2*>(canvas())->updateCanvas();
}
void KisToolMove::drag(const QPoint& newPos)
{
KisImageWSP image = currentImage();
QPoint offset = m_accumulatedOffset + newPos - m_dragStart;
image->addJob(m_strokeId,
new MoveStrokeStrategy::Data(offset));
}
void KisToolMove::endStroke()
{
if (!m_strokeId) return;
KisImageSP image = currentImage();
image->endStroke(m_strokeId);
m_strokeId.clear();
m_changesTracker.reset();
m_currentlyProcessingNodes.clear();
m_accumulatedOffset = QPoint();
qobject_cast<KisCanvas2*>(canvas())->updateCanvas();
}
void KisToolMove::slotTrackerChangedConfig(KisToolChangesTrackerDataSP state)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId);
KisToolMoveState *newState = dynamic_cast<KisToolMoveState*>(state.data());
KIS_SAFE_ASSERT_RECOVER_RETURN(newState);
if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging
m_accumulatedOffset = newState->accumulatedOffset;
image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset));
notifyGuiAfterMove();
}
+void KisToolMove::slotMoveDiscreteLeft()
+{
+ moveDiscrete(MoveDirection::Left, false);
+}
+
+void KisToolMove::slotMoveDiscreteRight()
+{
+ moveDiscrete(MoveDirection::Right, false);
+}
+
+void KisToolMove::slotMoveDiscreteUp()
+{
+ moveDiscrete(MoveDirection::Up, false);
+}
+
+void KisToolMove::slotMoveDiscreteDown()
+{
+ moveDiscrete(MoveDirection::Down, false);
+}
+
+void KisToolMove::slotMoveDiscreteLeftMore()
+{
+ moveDiscrete(MoveDirection::Left, true);
+}
+
+void KisToolMove::slotMoveDiscreteRightMore()
+{
+ moveDiscrete(MoveDirection::Right, true);
+}
+
+void KisToolMove::slotMoveDiscreteUpMore()
+{
+ moveDiscrete(MoveDirection::Up, true);
+}
+
+void KisToolMove::slotMoveDiscreteDownMore()
+{
+ moveDiscrete(MoveDirection::Down, true);
+}
+
void KisToolMove::cancelStroke()
{
if (!m_strokeId) return;
KisImageSP image = currentImage();
image->cancelStroke(m_strokeId);
m_strokeId.clear();
m_changesTracker.reset();
m_currentlyProcessingNodes.clear();
m_accumulatedOffset = QPoint();
notifyGuiAfterMove();
qobject_cast<KisCanvas2*>(canvas())->updateCanvas();
}
QWidget* KisToolMove::createOptionWidget()
{
return m_optionsWidget;
}
KisToolMove::MoveToolMode KisToolMove::moveToolMode() const
{
if (m_optionsWidget)
return m_optionsWidget->mode();
return MoveSelectedLayer;
}
QPoint KisToolMove::applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos)
{
QPoint move = pos - m_dragStart;
// Snap to axis
if (modifiers & Qt::ShiftModifier) {
move = snapToClosestAxis(move);
}
// "Precision mode" - scale down movement by 1/5
if (modifiers & Qt::AltModifier) {
const qreal SCALE_FACTOR = .2;
move = SCALE_FACTOR * move;
}
return m_dragStart + move;
}
void KisToolMove::moveBySpinX(int newX)
{
if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging
if (!currentNode()->isEditable()) return; // Don't move invisible nodes
if (startStrokeImpl(MoveSelectedLayer, 0)) {
setMode(KisTool::PAINT_MODE);
}
m_accumulatedOffset.rx() = newX - m_handlesRect.x();
image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset));
notifyGuiAfterMove(false);
setMode(KisTool::HOVER_MODE);
}
void KisToolMove::moveBySpinY(int newY)
{
if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging
if (!currentNode()->isEditable()) return; // Don't move invisible nodes
if (startStrokeImpl(MoveSelectedLayer, 0)) {
setMode(KisTool::PAINT_MODE);
}
m_accumulatedOffset.ry() = newY - m_handlesRect.y();
image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset));
notifyGuiAfterMove(false);
setMode(KisTool::HOVER_MODE);
}
void KisToolMove::slotNodeChanged(KisNodeList nodes)
{
if (m_strokeId && !tryEndPreviousStroke(nodes)) {
return;
}
initHandles(nodes);
notifyGuiAfterMove(false);
}
QList<QAction *> KisToolMoveFactory::createActionsImpl()
{
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
QList<QAction *> actions = KisToolPaintFactoryBase::createActionsImpl();
actions << actionRegistry->makeQAction("movetool-move-up");
actions << actionRegistry->makeQAction("movetool-move-down");
actions << actionRegistry->makeQAction("movetool-move-left");
actions << actionRegistry->makeQAction("movetool-move-right");
actions << actionRegistry->makeQAction("movetool-move-up-more");
actions << actionRegistry->makeQAction("movetool-move-down-more");
actions << actionRegistry->makeQAction("movetool-move-left-more");
actions << actionRegistry->makeQAction("movetool-move-right-more");
actions << actionRegistry->makeQAction("movetool-show-coordinates");
return actions;
}
diff --git a/plugins/tools/basictools/kis_tool_move.h b/plugins/tools/basictools/kis_tool_move.h
index 920b229acc..db9e39ab77 100644
--- a/plugins/tools/basictools/kis_tool_move.h
+++ b/plugins/tools/basictools/kis_tool_move.h
@@ -1,191 +1,202 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* 1999 Michael Koch <koch@kde.org>
* 2003 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_MOVE_H_
#define KIS_TOOL_MOVE_H_
#include <KisToolPaintFactoryBase.h>
#include <kis_types.h>
#include <kis_tool.h>
#include <flake/kis_node_shape.h>
#include <kis_icon.h>
#include <QKeySequence>
#include <QWidget>
#include <QGroupBox>
#include <QRadioButton>
#include "KisToolChangesTracker.h"
#include "kis_signal_compressor.h"
+#include "kis_signal_auto_connection.h"
#include "kis_canvas2.h"
class KoCanvasBase;
class MoveToolOptionsWidget;
class KisDocument;
class KisToolMove : public KisTool
{
Q_OBJECT
Q_ENUMS(MoveToolMode);
public:
KisToolMove(KoCanvasBase * canvas);
~KisToolMove() override;
/**
* @brief wantsAutoScroll
* reimplemented from KoToolBase
* there's an issue where autoscrolling with this tool never makes the
* stroke end, so we return false here so that users don't get stuck with
* the tool. See bug 362659
* @return false
*/
bool wantsAutoScroll() const override {
return false;
}
public Q_SLOTS:
void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) override;
void deactivate() override;
public Q_SLOTS:
void requestStrokeEnd() override;
void requestStrokeCancellation() override;
void requestUndoDuringStroke() override;
protected Q_SLOTS:
void resetCursorStyle() override;
public:
enum MoveToolMode {
MoveSelectedLayer,
MoveFirstLayer,
MoveGroup
};
enum MoveDirection {
Up,
Down,
Left,
Right
};
void beginPrimaryAction(KoPointerEvent *event) override;
void continuePrimaryAction(KoPointerEvent *event) override;
void endPrimaryAction(KoPointerEvent *event) override;
void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override;
void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override;
void endAlternateAction(KoPointerEvent *event, AlternateAction action) override;
void mouseMoveEvent(KoPointerEvent *event) override;
void startAction(KoPointerEvent *event, MoveToolMode mode);
void continueAction(KoPointerEvent *event);
void endAction(KoPointerEvent *event);
void paint(QPainter& gc, const KoViewConverter &converter) override;
void initHandles(const KisNodeList &nodes);
QWidget *createOptionWidget() override;
void updateUIUnit(int newUnit);
MoveToolMode moveToolMode() const;
void setShowCoordinates(bool value);
public Q_SLOTS:
void moveDiscrete(MoveDirection direction, bool big);
void moveBySpinX(int newX);
void moveBySpinY(int newY);
void slotNodeChanged(KisNodeList nodes);
void commitChanges();
Q_SIGNALS:
void moveToolModeChanged();
void moveInNewPosition(QPoint);
private:
void drag(const QPoint& newPos);
void cancelStroke();
QPoint applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos);
bool startStrokeImpl(MoveToolMode mode, const QPoint *pos);
QPoint currentOffset() const;
void notifyGuiAfterMove(bool showFloatingMessage = true);
bool tryEndPreviousStroke(KisNodeList nodes);
KisNodeList fetchSelectedNodes(MoveToolMode mode, const QPoint *pixelPoint, KisSelectionSP selection);
private Q_SLOTS:
void endStroke();
void slotTrackerChangedConfig(KisToolChangesTrackerDataSP state);
+ void slotMoveDiscreteLeft();
+ void slotMoveDiscreteRight();
+ void slotMoveDiscreteUp();
+ void slotMoveDiscreteDown();
+ void slotMoveDiscreteLeftMore();
+ void slotMoveDiscreteRightMore();
+ void slotMoveDiscreteUpMore();
+ void slotMoveDiscreteDownMore();
+
private:
MoveToolOptionsWidget* m_optionsWidget {0};
QPoint m_dragStart; ///< Point where current cursor dragging began
QPoint m_accumulatedOffset; ///< Total offset including multiple clicks, up/down/left/right keys, etc. added together
KisStrokeId m_strokeId;
KisNodeList m_currentlyProcessingNodes;
int m_resolution;
QAction *m_showCoordinatesAction {0};
QPoint m_dragPos;
QRect m_handlesRect;
KisToolChangesTracker m_changesTracker;
QPoint m_lastCursorPos;
KisSignalCompressor m_updateCursorCompressor;
+ KisSignalAutoConnectionsStore m_actionConnections;
};
class KisToolMoveFactory : public KisToolPaintFactoryBase
{
public:
KisToolMoveFactory()
: KisToolPaintFactoryBase("KritaTransform/KisToolMove") {
setToolTip(i18n("Move Tool"));
setSection(TOOL_TYPE_TRANSFORM);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
setPriority(3);
setIconName(koIconNameCStr("krita_tool_move"));
setShortcut(QKeySequence(Qt::Key_T));
}
~KisToolMoveFactory() override {}
KoToolBase * createTool(KoCanvasBase *canvas) override {
return new KisToolMove(canvas);
}
QList<QAction *> createActionsImpl() override;
};
#endif // KIS_TOOL_MOVE_H_
diff --git a/plugins/tools/basictools/kis_tool_pan.h b/plugins/tools/basictools/kis_tool_pan.h
index 4f93cdf0ef..83018a31c3 100644
--- a/plugins/tools/basictools/kis_tool_pan.h
+++ b/plugins/tools/basictools/kis_tool_pan.h
@@ -1,56 +1,56 @@
/*
* Copyright (c) 2017 Victor Wåhlström <victor.wahlstrom@initiali.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_PAN_H_
#define KIS_TOOL_PAN_H_
#include <kis_tool.h>
#include <KoToolFactoryBase.h>
class KisToolPan : public KisTool
{
Q_OBJECT
public:
KisToolPan(KoCanvasBase *canvas);
~KisToolPan() override;
void beginPrimaryAction(KoPointerEvent *event) override;
void continuePrimaryAction(KoPointerEvent *event) override;
void endPrimaryAction(KoPointerEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void paint(QPainter &painter, const KoViewConverter &converter) override;
- bool wantsAutoScroll() const;
+ bool wantsAutoScroll() const override;
private:
QPoint m_lastPosition;
};
class KisToolPanFactory : public KoToolFactoryBase
{
public:
KisToolPanFactory();
~KisToolPanFactory() override;
KoToolBase *createTool(KoCanvasBase *canvas) override;
};
#endif // KIS_TOOL_PAN_H_
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
index 3b95393eda..afaae866e1 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
@@ -1,1729 +1,1735 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
Copyright (C) 2008-2009 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2008 C. Boemann <cbo@boemann.dk>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "DefaultTool.h"
#include "DefaultToolGeometryWidget.h"
#include "DefaultToolTabbedWidget.h"
#include "SelectionDecorator.h"
#include "ShapeMoveStrategy.h"
#include "ShapeRotateStrategy.h"
#include "ShapeShearStrategy.h"
#include "ShapeResizeStrategy.h"
#include <KoPointerEvent.h>
#include <KoToolSelection.h>
#include <KoToolManager.h>
#include <KoSelection.h>
#include <KoShapeController.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoShapeGroup.h>
#include <KoShapeLayer.h>
#include <KoShapeOdfSaveHelper.h>
#include <KoPathShape.h>
#include <KoDrag.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoShapeRubberSelectStrategy.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeTransformCommand.h>
#include <commands/KoShapeDeleteCommand.h>
#include <commands/KoShapeCreateCommand.h>
#include <commands/KoShapeGroupCommand.h>
#include <commands/KoShapeUngroupCommand.h>
#include <commands/KoShapeDistributeCommand.h>
#include <commands/KoKeepShapesSelectedCommand.h>
#include <KoSnapGuide.h>
#include <KoStrokeConfigWidget.h>
#include "kis_action_registry.h"
#include "kis_node.h"
#include "kis_node_manager.h"
#include "KisViewManager.h"
#include "kis_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include <KoInteractionStrategyFactory.h>
#include "kis_document_aware_spin_box_unit_manager.h"
#include <KoIcon.h>
#include <QPointer>
#include <QAction>
#include <QKeyEvent>
#include <QSignalMapper>
#include <KoResourcePaths.h>
#include <KoCanvasController.h>
#include <kactioncollection.h>
#include <QMenu>
#include <math.h>
#include "kis_assert.h"
#include "kis_global.h"
#include "kis_debug.h"
#include <QVector2D>
#define HANDLE_DISTANCE 10
#define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE)
#define INNER_HANDLE_DISTANCE_SQ 16
namespace {
static const QString EditFillGradientFactoryId = "edit_fill_gradient";
static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient";
enum TransformActionType {
TransformRotate90CW,
TransformRotate90CCW,
TransformRotate180,
TransformMirrorX,
TransformMirrorY,
TransformReset
};
enum BooleanOp {
BooleanUnion,
BooleanIntersection,
BooleanSubtraction
};
}
class NopInteractionStrategy : public KoInteractionStrategy
{
public:
explicit NopInteractionStrategy(KoToolBase *parent)
: KoInteractionStrategy(parent)
{
}
KUndo2Command *createCommand() override
{
return 0;
}
void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {}
void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {}
void paint(QPainter &painter, const KoViewConverter &converter) override {
Q_UNUSED(painter);
Q_UNUSED(converter);
}
};
class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy
{
public:
explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid)
: KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid)
{
}
void paint(QPainter &painter, const KoViewConverter &converter) override {
KoShapeRubberSelectStrategy::paint(painter, converter);
}
void finishInteraction(Qt::KeyboardModifiers modifiers = 0) override
{
Q_UNUSED(modifiers);
DefaultTool *defaultTool = dynamic_cast<DefaultTool*>(tool());
KIS_SAFE_ASSERT_RECOVER_RETURN(defaultTool);
KoSelection * selection = defaultTool->koSelection();
const bool useContainedMode = currentMode() == CoveringSelection;
QList<KoShape *> shapes =
defaultTool->shapeManager()->
shapesAt(selectedRectangle(), true, useContainedMode);
Q_FOREACH (KoShape * shape, shapes) {
if (!shape->isSelectable()) continue;
selection->select(shape);
}
defaultTool->repaintDecorations();
defaultTool->canvas()->updateCanvas(selectedRectangle());
}
};
#include <KoGradientBackground.h>
#include "KoShapeGradientHandles.h"
#include "ShapeGradientEditStrategy.h"
class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory
{
public:
MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,
int priority, const QString &id, DefaultTool *_q)
: KoInteractionStrategyFactory(priority, id),
q(_q),
m_fillVariant(fillVariant)
{
}
KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override
{
m_currentHandle = handleAt(ev->point);
if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
KoShape *shape = onlyEditableShape();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point);
}
return 0;
}
bool hoverEvent(KoPointerEvent *ev) override
{
m_currentHandle = handleAt(ev->point);
return false;
}
bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
{
Q_UNUSED(painter);
Q_UNUSED(converter);
return false;
}
bool tryUseCustomCursor() override {
if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
q->useCursor(Qt::OpenHandCursor);
}
return m_currentHandle.type != KoShapeGradientHandles::Handle::None;
}
private:
KoShape* onlyEditableShape() const {
KoSelection *selection = q->koSelection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
KoShape *shape = 0;
if (shapes.size() == 1) {
shape = shapes.first();
}
return shape;
}
KoShapeGradientHandles::Handle handleAt(const QPointF &pos) {
KoShapeGradientHandles::Handle result;
KoShape *shape = onlyEditableShape();
if (shape) {
KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
const qreal distanceThresholdSq =
globalHandle == KoFlake::NoHandle ?
HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ;
const KoViewConverter *converter = q->canvas()->viewConverter();
const QPointF viewPoint = converter->documentToView(pos);
qreal minDistanceSq = std::numeric_limits<qreal>::max();
KoShapeGradientHandles sh(m_fillVariant, shape);
Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) {
const QPointF handlePoint = converter->documentToView(handle.pos);
const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
result = handle;
minDistanceSq = distanceSq;
}
}
}
return result;
}
private:
DefaultTool *q;
KoFlake::FillVariant m_fillVariant;
KoShapeGradientHandles::Handle m_currentHandle;
};
class SelectionHandler : public KoToolSelection
{
public:
SelectionHandler(DefaultTool *parent)
: KoToolSelection(parent)
, m_selection(parent->koSelection())
{
}
bool hasSelection() override
{
if (m_selection) {
return m_selection->count();
}
return false;
}
private:
QPointer<KoSelection> m_selection;
};
DefaultTool::DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy)
: KoInteractionTool(canvas)
, m_lastHandle(KoFlake::NoHandle)
, m_hotPosition(KoFlake::TopLeft)
, m_mouseWasInsideHandles(false)
, m_decorator(0)
, m_selectionHandler(new SelectionHandler(this))
, m_tabbedOptionWidget(0)
{
setupActions();
QPixmap rotatePixmap, shearPixmap;
rotatePixmap.load(":/cursor_rotate.png");
Q_ASSERT(!rotatePixmap.isNull());
shearPixmap.load(":/cursor_shear.png");
Q_ASSERT(!shearPixmap.isNull());
m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45)));
m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90)));
m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135)));
m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180)));
m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225)));
m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270)));
m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315)));
m_rotateCursors[7] = QCursor(rotatePixmap);
/*
m_rotateCursors[0] = QCursor(Qt::RotateNCursor);
m_rotateCursors[1] = QCursor(Qt::RotateNECursor);
m_rotateCursors[2] = QCursor(Qt::RotateECursor);
m_rotateCursors[3] = QCursor(Qt::RotateSECursor);
m_rotateCursors[4] = QCursor(Qt::RotateSCursor);
m_rotateCursors[5] = QCursor(Qt::RotateSWCursor);
m_rotateCursors[6] = QCursor(Qt::RotateWCursor);
m_rotateCursors[7] = QCursor(Qt::RotateNWCursor);
*/
m_shearCursors[0] = QCursor(shearPixmap);
m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45)));
m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90)));
m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135)));
m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180)));
m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225)));
m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270)));
m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315)));
m_sizeCursors[0] = Qt::SizeVerCursor;
m_sizeCursors[1] = Qt::SizeBDiagCursor;
m_sizeCursors[2] = Qt::SizeHorCursor;
m_sizeCursors[3] = Qt::SizeFDiagCursor;
m_sizeCursors[4] = Qt::SizeVerCursor;
m_sizeCursors[5] = Qt::SizeBDiagCursor;
m_sizeCursors[6] = Qt::SizeHorCursor;
m_sizeCursors[7] = Qt::SizeFDiagCursor;
if (connectToSelectedShapesProxy) {
connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions()));
}
}
DefaultTool::~DefaultTool()
{
}
void DefaultTool::slotActivateEditFillGradient(bool value)
{
if (value) {
addInteractionFactory(
new MoveGradientHandleInteractionFactory(KoFlake::Fill,
1, EditFillGradientFactoryId, this));
} else {
removeInteractionFactory(EditFillGradientFactoryId);
}
repaintDecorations();
}
void DefaultTool::slotActivateEditStrokeGradient(bool value)
{
if (value) {
addInteractionFactory(
new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill,
0, EditStrokeGradientFactoryId, this));
} else {
removeInteractionFactory(EditStrokeGradientFactoryId);
}
repaintDecorations();
}
bool DefaultTool::wantsAutoScroll() const
{
return true;
}
void DefaultTool::addMappedAction(QSignalMapper *mapper, const QString &actionId, int commandType)
{
QAction *a =action(actionId);
connect(a, SIGNAL(triggered()), mapper, SLOT(map()));
mapper->setMapping(a, commandType);
}
void DefaultTool::setupActions()
{
m_alignSignalsMapper = new QSignalMapper(this);
addMappedAction(m_alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment);
m_distributeSignalsMapper = new QSignalMapper(this);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution);
m_transformSignalsMapper = new QSignalMapper(this);
addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW);
addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW);
addMappedAction(m_transformSignalsMapper, "object_transform_rotate_180", TransformRotate180);
addMappedAction(m_transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX);
addMappedAction(m_transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY);
addMappedAction(m_transformSignalsMapper, "object_transform_reset", TransformReset);
m_booleanSignalsMapper = new QSignalMapper(this);
addMappedAction(m_booleanSignalsMapper, "object_unite", BooleanUnion);
addMappedAction(m_booleanSignalsMapper, "object_intersect", BooleanIntersection);
addMappedAction(m_booleanSignalsMapper, "object_subtract", BooleanSubtraction);
m_contextMenu.reset(new QMenu());
}
qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation)
{
QPointF selectionCenter = koSelection()->absolutePosition();
QPointF direction;
switch (handle) {
case KoFlake::TopMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::TopRight)
- koSelection()->absolutePosition(KoFlake::TopLeft);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::TopRightHandle:
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF();
break;
case KoFlake::RightMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::BottomRight)
- koSelection()->absolutePosition(KoFlake::TopRight);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::BottomRightHandle:
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF();
break;
case KoFlake::BottomMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::BottomLeft)
- koSelection()->absolutePosition(KoFlake::BottomRight);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::BottomLeftHandle:
direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter;
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF();
break;
case KoFlake::LeftMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::TopLeft)
- koSelection()->absolutePosition(KoFlake::BottomLeft);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::TopLeftHandle:
direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter;
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF();
break;
case KoFlake::NoHandle:
return 0.0;
break;
}
qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI;
switch (handle) {
case KoFlake::TopMiddleHandle:
if (useEdgeRotation) {
rotation -= 0.0;
} else {
rotation -= 270.0;
}
break;
case KoFlake::TopRightHandle:
rotation -= 315.0;
break;
case KoFlake::RightMiddleHandle:
if (useEdgeRotation) {
rotation -= 90.0;
} else {
rotation -= 0.0;
}
break;
case KoFlake::BottomRightHandle:
rotation -= 45.0;
break;
case KoFlake::BottomMiddleHandle:
if (useEdgeRotation) {
rotation -= 180.0;
} else {
rotation -= 90.0;
}
break;
case KoFlake::BottomLeftHandle:
rotation -= 135.0;
break;
case KoFlake::LeftMiddleHandle:
if (useEdgeRotation) {
rotation -= 270.0;
} else {
rotation -= 180.0;
}
break;
case KoFlake::TopLeftHandle:
rotation -= 225.0;
break;
case KoFlake::NoHandle:
break;
}
if (rotation < 0.0) {
rotation += 360.0;
}
return rotation;
}
void DefaultTool::updateCursor()
{
if (tryUseCustomCursor()) return;
QCursor cursor = Qt::ArrowCursor;
QString statusText;
KoSelection *selection = koSelection();
if (selection && selection->count() > 0) { // has a selection
bool editable = !selection->selectedEditableShapes().isEmpty();
if (!m_mouseWasInsideHandles) {
m_angle = rotationOfHandle(m_lastHandle, true);
int rotOctant = 8 + int(8.5 + m_angle / 45);
bool rotateHandle = false;
bool shearHandle = false;
switch (m_lastHandle) {
case KoFlake::TopMiddleHandle:
cursor = m_shearCursors[(0 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::TopRightHandle:
cursor = m_rotateCursors[(1 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::RightMiddleHandle:
cursor = m_shearCursors[(2 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::BottomRightHandle:
cursor = m_rotateCursors[(3 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::BottomMiddleHandle:
cursor = m_shearCursors[(4 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::BottomLeftHandle:
cursor = m_rotateCursors[(5 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::LeftMiddleHandle:
cursor = m_shearCursors[(6 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::TopLeftHandle:
cursor = m_rotateCursors[(7 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::NoHandle:
cursor = Qt::ArrowCursor;
break;
}
if (rotateHandle) {
statusText = i18n("Left click rotates around center, right click around highlighted position.");
}
if (shearHandle) {
statusText = i18n("Click and drag to shear selection.");
}
} else {
statusText = i18n("Click and drag to resize selection.");
m_angle = rotationOfHandle(m_lastHandle, false);
int rotOctant = 8 + int(8.5 + m_angle / 45);
bool cornerHandle = false;
switch (m_lastHandle) {
case KoFlake::TopMiddleHandle:
cursor = m_sizeCursors[(0 + rotOctant) % 8];
break;
case KoFlake::TopRightHandle:
cursor = m_sizeCursors[(1 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::RightMiddleHandle:
cursor = m_sizeCursors[(2 + rotOctant) % 8];
break;
case KoFlake::BottomRightHandle:
cursor = m_sizeCursors[(3 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::BottomMiddleHandle:
cursor = m_sizeCursors[(4 + rotOctant) % 8];
break;
case KoFlake::BottomLeftHandle:
cursor = m_sizeCursors[(5 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::LeftMiddleHandle:
cursor = m_sizeCursors[(6 + rotOctant) % 8];
break;
case KoFlake::TopLeftHandle:
cursor = m_sizeCursors[(7 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::NoHandle:
cursor = Qt::SizeAllCursor;
statusText = i18n("Click and drag to move selection.");
break;
}
if (cornerHandle) {
statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position.");
}
}
if (!editable) {
cursor = Qt::ArrowCursor;
}
} else {
// there used to be guides... :'''(
}
useCursor(cursor);
if (currentStrategy() == 0) {
emit statusTextChanged(statusText);
}
}
void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter)
{
KoSelection *selection = koSelection();
if (selection) {
this->m_decorator = new SelectionDecorator(canvas()->resourceManager());
{
/**
* Selection masks don't render the outline of the shapes, so we should
* do that explicitly when rendering them via selection
*/
KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode();
const bool isSelectionMask = node && node->inherits("KisSelectionMask");
m_decorator->setForceShapeOutlines(isSelectionMask);
}
m_decorator->setSelection(selection);
m_decorator->setHandleRadius(handleRadius());
m_decorator->setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId));
m_decorator->setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId));
m_decorator->paint(painter, converter);
}
KoInteractionTool::paint(painter, converter);
painter.save();
KoShape::applyConversion(painter, converter);
canvas()->snapGuide()->paint(painter, converter);
painter.restore();
}
bool DefaultTool::isValidForCurrentLayer() const
{
// if the currently active node has a shape manager, then it is
// probably our client :)
KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
return bool(kisCanvas->localShapeManager());
}
KoShapeManager *DefaultTool::shapeManager() const {
return canvas()->shapeManager();
}
void DefaultTool::mousePressEvent(KoPointerEvent *event)
{
// this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it
if (!isValidForCurrentLayer()) {
KisCanvas2 *kiscanvas = static_cast<KisCanvas2 *>(canvas());
kiscanvas->viewManager()->showFloatingMessage(
i18n("This tool only works on vector layers. You probably want the move tool."),
QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter);
return;
}
KoInteractionTool::mousePressEvent(event);
updateCursor();
}
void DefaultTool::mouseMoveEvent(KoPointerEvent *event)
{
KoInteractionTool::mouseMoveEvent(event);
if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) {
QRectF bound = handlesSize();
if (bound.contains(event->point)) {
bool inside;
KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside);
if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) {
m_lastHandle = newDirection;
m_mouseWasInsideHandles = inside;
//repaintDecorations();
}
} else {
/*if (m_lastHandle != KoFlake::NoHandle)
repaintDecorations(); */
m_lastHandle = KoFlake::NoHandle;
m_mouseWasInsideHandles = false;
// there used to be guides... :'''(
}
} else {
// there used to be guides... :'''(
}
updateCursor();
}
QRectF DefaultTool::handlesSize()
{
KoSelection *selection = koSelection();
if (!selection || !selection->count()) return QRectF();
recalcSelectionBox(selection);
QRectF bound = m_selectionOutline.boundingRect();
// expansion Border
if (!canvas() || !canvas()->viewConverter()) {
return bound;
}
QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE));
bound.adjust(-border.x(), -border.y(), border.x(), border.y());
return bound;
}
void DefaultTool::mouseReleaseEvent(KoPointerEvent *event)
{
KoInteractionTool::mouseReleaseEvent(event);
updateCursor();
// This makes sure the decorations that are shown are refreshed. especally the "T" icon
canvas()->updateCanvas(QRectF(0,0,canvas()->canvasWidget()->width(), canvas()->canvasWidget()->height()));
}
void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
KoSelection *selection = koSelection();
KoShape *shape = shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop);
if (shape && selection && !selection->isSelected(shape)) {
if (!(event->modifiers() & Qt::ShiftModifier)) {
selection->deselectAll();
}
selection->select(shape);
}
explicitUserStrokeEndRequest();
}
bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
{
bool result = false;
qreal x = 0.0, y = 0.0;
if (direction == Qt::Key_Left) {
x = -5;
} else if (direction == Qt::Key_Right) {
x = 5;
} else if (direction == Qt::Key_Up) {
y = -5;
} else if (direction == Qt::Key_Down) {
y = 5;
}
if (x != 0.0 || y != 0.0) { // actually move
if ((modifiers & Qt::ShiftModifier) != 0) {
x *= 10;
y *= 10;
} else if ((modifiers & Qt::AltModifier) != 0) { // more precise
x /= 5;
y /= 5;
}
QList<KoShape *> shapes = koSelection()->selectedEditableShapes();
if (!shapes.isEmpty()) {
canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y)));
result = true;
}
}
return result;
}
void DefaultTool::keyPressEvent(QKeyEvent *event)
{
KoInteractionTool::keyPressEvent(event);
if (currentStrategy() == 0) {
switch (event->key()) {
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
if (moveSelection(event->key(), event->modifiers())) {
event->accept();
}
break;
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1);
event->accept();
break;
default:
return;
}
}
}
void DefaultTool::repaintDecorations()
{
if (koSelection() && koSelection()->count() > 0) {
canvas()->updateCanvas(handlesSize());
}
}
void DefaultTool::copy() const
{
// all the selected shapes, not only editable!
QList<KoShape *> shapes = koSelection()->selectedShapes();
if (!shapes.isEmpty()) {
KoDrag drag;
drag.setSvg(shapes);
drag.addToClipboard();
}
}
void DefaultTool::deleteSelection()
{
QList<KoShape *> shapes;
foreach (KoShape *s, koSelection()->selectedShapes()) {
if (s->isGeometryProtected()) {
continue;
}
shapes << s;
}
if (!shapes.empty()) {
canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes));
}
}
bool DefaultTool::paste()
{
// we no longer have to do anything as tool Proxy will do it for us
return false;
}
KoSelection *DefaultTool::koSelection() const
{
Q_ASSERT(canvas());
Q_ASSERT(canvas()->selectedShapesProxy());
return canvas()->selectedShapesProxy()->selection();
}
KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning)
{
// check for handles in this order; meaning that when handles overlap the one on top is chosen
static const KoFlake::SelectionHandle handleOrder[] = {
KoFlake::BottomRightHandle,
KoFlake::TopLeftHandle,
KoFlake::BottomLeftHandle,
KoFlake::TopRightHandle,
KoFlake::BottomMiddleHandle,
KoFlake::RightMiddleHandle,
KoFlake::LeftMiddleHandle,
KoFlake::TopMiddleHandle,
KoFlake::NoHandle
};
const KoViewConverter *converter = canvas()->viewConverter();
KoSelection *selection = koSelection();
if (!selection || !selection->count() || !converter) {
return KoFlake::NoHandle;
}
recalcSelectionBox(selection);
if (innerHandleMeaning) {
QPainterPath path;
path.addPolygon(m_selectionOutline);
*innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point));
}
const QPointF viewPoint = converter->documentToView(point);
for (int i = 0; i < KoFlake::NoHandle; ++i) {
KoFlake::SelectionHandle handle = handleOrder[i];
const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]);
const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
// if just inside the outline
if (distanceSq < HANDLE_DISTANCE_SQ) {
if (innerHandleMeaning) {
if (distanceSq < INNER_HANDLE_DISTANCE_SQ) {
*innerHandleMeaning = true;
}
}
return handle;
}
}
return KoFlake::NoHandle;
}
void DefaultTool::recalcSelectionBox(KoSelection *selection)
{
KIS_ASSERT_RECOVER_RETURN(selection->count());
QTransform matrix = selection->absoluteTransformation(0);
m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect()));
m_angle = 0.0;
QPolygonF outline = m_selectionOutline; //shorter name in the following :)
m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2;
m_selectionBox[KoFlake::TopRightHandle] = outline.value(1);
m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2;
m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2);
m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2;
m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3);
m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2;
m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0);
if (selection->count() == 1) {
#if 0 // TODO detect mirroring
KoShape *s = koSelection()->firstSelectedShape();
if (s->scaleX() < 0) { // vertically mirrored: swap left / right
std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]);
std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]);
std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]);
}
if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom
std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]);
std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]);
std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]);
}
#endif
}
}
void DefaultTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
QAction *actionBringToFront = action("object_order_front");
connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()), Qt::UniqueConnection);
QAction *actionRaise = action("object_order_raise");
connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()), Qt::UniqueConnection);
QAction *actionLower = action("object_order_lower");
connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown()));
QAction *actionSendToBack = action("object_order_back");
connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()), Qt::UniqueConnection);
QAction *actionGroupBottom = action("object_group");
connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()), Qt::UniqueConnection);
QAction *actionUngroupBottom = action("object_ungroup");
connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()), Qt::UniqueConnection);
QAction *actionSplit = action("object_split");
connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes()), Qt::UniqueConnection);
connect(m_alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int)));
connect(m_distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int)));
connect(m_transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int)));
connect(m_booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int)));
m_mouseWasInsideHandles = false;
m_lastHandle = KoFlake::NoHandle;
useCursor(Qt::ArrowCursor);
repaintDecorations();
updateActions();
if (m_tabbedOptionWidget) {
m_tabbedOptionWidget->activate();
}
}
void DefaultTool::deactivate()
{
KoToolBase::deactivate();
QAction *actionBringToFront = action("object_order_front");
disconnect(actionBringToFront, 0, this, 0);
QAction *actionRaise = action("object_order_raise");
disconnect(actionRaise, 0, this, 0);
QAction *actionLower = action("object_order_lower");
disconnect(actionLower, 0, this, 0);
QAction *actionSendToBack = action("object_order_back");
disconnect(actionSendToBack, 0, this, 0);
QAction *actionGroupBottom = action("object_group");
disconnect(actionGroupBottom, 0, this, 0);
QAction *actionUngroupBottom = action("object_ungroup");
disconnect(actionUngroupBottom, 0, this, 0);
QAction *actionSplit = action("object_split");
disconnect(actionSplit, 0, this, 0);
disconnect(m_alignSignalsMapper, 0, this, 0);
disconnect(m_distributeSignalsMapper, 0, this, 0);
disconnect(m_transformSignalsMapper, 0, this, 0);
disconnect(m_booleanSignalsMapper, 0, this, 0);
if (m_tabbedOptionWidget) {
m_tabbedOptionWidget->deactivate();
}
}
void DefaultTool::selectionGroup()
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
if (selectedShapes.isEmpty()) return;
const int groupZIndex = selectedShapes.last()->zIndex();
KoShapeGroup *group = new KoShapeGroup();
group->setZIndex(groupZIndex);
// TODO what if only one shape is left?
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
canvas()->shapeController()->addShapeDirect(group, 0, cmd);
new KoShapeGroupCommand(group, selectedShapes, true, cmd);
new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
// update selection so we can ungroup immediately again
selection->deselectAll();
selection->select(group);
}
void DefaultTool::selectionUngroup()
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
KUndo2Command *cmd = 0;
QList<KoShape*> newShapes;
// add a ungroup command for each found shape container to the macro command
Q_FOREACH (KoShape *shape, selectedShapes) {
KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape);
if (group) {
if (!cmd) {
cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes"));
new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
}
newShapes << group->shapes();
new KoShapeUngroupCommand(group, group->shapes(),
group->parent() ? QList<KoShape *>() : shapeManager()->topLevelShapes(),
cmd);
canvas()->shapeController()->removeShape(group, cmd);
}
}
if (cmd) {
new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
}
}
void DefaultTool::selectionTransform(int transformAction)
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
QTransform applyTransform;
bool shouldReset = false;
KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action");
switch (TransformActionType(transformAction)) {
case TransformRotate90CW:
applyTransform.rotate(90.0);
actionName = kundo2_i18n("Rotate Object 90° CW");
break;
case TransformRotate90CCW:
applyTransform.rotate(-90.0);
actionName = kundo2_i18n("Rotate Object 90° CCW");
break;
case TransformRotate180:
applyTransform.rotate(180.0);
actionName = kundo2_i18n("Rotate Object 180°");
break;
case TransformMirrorX:
applyTransform.scale(-1.0, 1.0);
actionName = kundo2_i18n("Mirror Object Horizontally");
break;
case TransformMirrorY:
applyTransform.scale(1.0, -1.0);
actionName = kundo2_i18n("Mirror Object Vertically");
break;
case TransformReset:
shouldReset = true;
actionName = kundo2_i18n("Reset Object Transformations");
break;
}
if (!shouldReset && applyTransform.isIdentity()) return;
QList<QTransform> oldTransforms;
QList<QTransform> newTransforms;
const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes);
const QPointF centerPoint = outlineRect.center();
const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y());
const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y());
// we also add selection to the list of transformed shapes, so that its outline is updated correctly
QList<KoShape*> transformedShapes = editableShapes;
transformedShapes << selection;
Q_FOREACH (KoShape *shape, transformedShapes) {
oldTransforms.append(shape->transformation());
QTransform t;
if (!shouldReset) {
const QTransform world = shape->absoluteTransformation(0);
t = world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation();
} else {
const QPointF center = shape->outlineRect().center();
const QPointF offset = shape->transformation().map(center) - center;
t = QTransform::fromTranslate(offset.x(), offset.y());
}
newTransforms.append(t);
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms);
cmd->setText(actionName);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionBooleanOp(int booleanOp)
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
QVector<QPainterPath> srcOutlines;
QPainterPath dstOutline;
KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name");
// TODO: implement a reference shape selection dialog!
const int referenceShapeIndex = 0;
KoShape *referenceShape = editableShapes[referenceShapeIndex];
Q_FOREACH (KoShape *shape, editableShapes) {
srcOutlines << shape->absoluteTransformation(0).map(shape->outline());
}
if (booleanOp == BooleanUnion) {
Q_FOREACH (const QPainterPath &path, srcOutlines) {
dstOutline |= path;
}
actionName = kundo2_i18n("Unite Shapes");
} else if (booleanOp == BooleanIntersection) {
for (int i = 0; i < srcOutlines.size(); i++) {
if (i == 0) {
dstOutline = srcOutlines[i];
} else {
dstOutline &= srcOutlines[i];
}
}
// there is a bug in Qt, sometimes it leaves the resulting
// outline open, so just close it explicitly.
dstOutline.closeSubpath();
actionName = kundo2_i18n("Intersect Shapes");
} else if (booleanOp == BooleanSubtraction) {
for (int i = 0; i < srcOutlines.size(); i++) {
dstOutline = srcOutlines[referenceShapeIndex];
if (i != referenceShapeIndex) {
dstOutline -= srcOutlines[i];
}
}
actionName = kundo2_i18n("Subtract Shapes");
}
KoShape *newShape = 0;
if (!dstOutline.isEmpty()) {
newShape = KoPathShape::createShapeFromPainterPath(dstOutline);
}
KUndo2Command *cmd = new KUndo2Command(actionName);
new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
QList<KoShape*> newSelectedShapes;
if (newShape) {
newShape->setBackground(referenceShape->background());
newShape->setStroke(referenceShape->stroke());
newShape->setZIndex(referenceShape->zIndex());
KoShapeContainer *parent = referenceShape->parent();
canvas()->shapeController()->addShapeDirect(newShape, parent, cmd);
newSelectedShapes << newShape;
}
canvas()->shapeController()->removeShapes(editableShapes, cmd);
new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionSplitShapes()
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes"));
new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
QList<KoShape*> newShapes;
Q_FOREACH (KoShape *shape, editableShapes) {
KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
if (!pathShape) return;
QList<KoPathShape*> splitShapes;
if (pathShape->separate(splitShapes)) {
QList<KoShape*> normalShapes = implicitCastList<KoShape*>(splitShapes);
KoShapeContainer *parent = shape->parent();
canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd);
canvas()->shapeController()->removeShape(shape, cmd);
newShapes << normalShapes;
}
}
new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionAlign(int _align)
{
KoShapeAlignCommand::Align align =
static_cast<KoShapeAlignCommand::Align>(_align);
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
// TODO add an option to the widget so that one can align to the page
// with multiple selected shapes too
QRectF bb;
// single selected shape is automatically aligned to document rect
if (editableShapes.count() == 1) {
if (!canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize)) {
return;
}
bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceProvider::PageSize));
} else {
bb = KoShape::absoluteOutlineRect(editableShapes);
}
KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionDistribute(int _distribute)
{
KoShapeDistributeCommand::Distribute distribute =
static_cast<KoShapeDistributeCommand::Distribute>(_distribute);
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.size() < 3) {
return;
}
QRectF bb = KoShape::absoluteOutlineRect(editableShapes);
KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionBringToFront()
{
selectionReorder(KoShapeReorderCommand::BringToFront);
}
void DefaultTool::selectionMoveUp()
{
selectionReorder(KoShapeReorderCommand::RaiseShape);
}
void DefaultTool::selectionMoveDown()
{
selectionReorder(KoShapeReorderCommand::LowerShape);
}
void DefaultTool::selectionSendToBack()
{
selectionReorder(KoShapeReorderCommand::SendToBack);
}
void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order)
{
KoSelection *selection = koSelection();
if (!selection) {
return;
}
QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
if (selectedShapes.isEmpty()) {
return;
}
KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order);
if (cmd) {
canvas()->addCommand(cmd);
}
}
QList<QPointer<QWidget> > DefaultTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
m_tabbedOptionWidget = new DefaultToolTabbedWidget(this);
if (isActivated()) {
m_tabbedOptionWidget->activate();
}
widgets.append(m_tabbedOptionWidget);
connect(m_tabbedOptionWidget,
SIGNAL(sigSwitchModeEditFillGradient(bool)),
SLOT(slotActivateEditFillGradient(bool)));
connect(m_tabbedOptionWidget,
SIGNAL(sigSwitchModeEditStrokeGradient(bool)),
SLOT(slotActivateEditStrokeGradient(bool)));
return widgets;
}
void DefaultTool::canvasResourceChanged(int key, const QVariant &res)
{
if (key == HotPosition) {
m_hotPosition = KoFlake::AnchorPosition(res.toInt());
repaintDecorations();
}
}
KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event)
{
KoSelection *selection = koSelection();
if (!selection) return nullptr;
bool insideSelection = false;
KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection);
bool editableShape = !selection->selectedEditableShapes().isEmpty();
const bool selectMultiple = event->modifiers() & Qt::ShiftModifier;
const bool selectNextInStack = event->modifiers() & Qt::ControlModifier;
const bool avoidSelection = event->modifiers() & Qt::AltModifier;
if (selectNextInStack) {
// change the hot selection position when middle clicking on a handle
KoFlake::AnchorPosition newHotPosition = m_hotPosition;
switch (handle) {
case KoFlake::TopMiddleHandle:
newHotPosition = KoFlake::Top;
break;
case KoFlake::TopRightHandle:
newHotPosition = KoFlake::TopRight;
break;
case KoFlake::RightMiddleHandle:
newHotPosition = KoFlake::Right;
break;
case KoFlake::BottomRightHandle:
newHotPosition = KoFlake::BottomRight;
break;
case KoFlake::BottomMiddleHandle:
newHotPosition = KoFlake::Bottom;
break;
case KoFlake::BottomLeftHandle:
newHotPosition = KoFlake::BottomLeft;
break;
case KoFlake::LeftMiddleHandle:
newHotPosition = KoFlake::Left;
break;
case KoFlake::TopLeftHandle:
newHotPosition = KoFlake::TopLeft;
break;
case KoFlake::NoHandle:
default:
// check if we had hit the center point
const KoViewConverter *converter = canvas()->viewConverter();
QPointF pt = converter->documentToView(event->point);
// TODO: use calculated values instead!
QPointF centerPt = converter->documentToView(selection->absolutePosition());
if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) {
newHotPosition = KoFlake::Center;
}
break;
}
if (m_hotPosition != newHotPosition) {
canvas()->resourceManager()->setResource(HotPosition, newHotPosition);
return new NopInteractionStrategy(this);
}
}
if (!avoidSelection && editableShape) {
// manipulation of selected shapes goes first
if (handle != KoFlake::NoHandle) {
// resizing or shearing only with left mouse button
if (insideSelection) {
bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling();
return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling);
}
if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) {
return new ShapeShearStrategy(this, selection, event->point, handle);
}
// rotating is allowed for right mouse button too
if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle ||
handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) {
return new ShapeRotateStrategy(this, selection, event->point, event->buttons());
}
}
if (!selectMultiple && !selectNextInStack) {
if (insideSelection) {
return new ShapeMoveStrategy(this, selection, event->point);
}
}
}
KoShape *shape = shapeManager()->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop);
if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) {
if (!selectMultiple) {
repaintDecorations();
selection->deselectAll();
}
return new SelectionInteractionStrategy(this, event->point, false);
}
if (selection->isSelected(shape)) {
if (selectMultiple) {
repaintDecorations();
selection->deselect(shape);
}
} else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected
repaintDecorations();
if (!selectMultiple) {
selection->deselectAll();
}
selection->select(shape);
repaintDecorations();
// tablet selection isn't precise and may lead to a move, preventing that
if (event->isTabletEvent()) {
return new NopInteractionStrategy(this);
}
return new ShapeMoveStrategy(this, selection, event->point);
}
return 0;
}
void DefaultTool::updateActions()
{
QList<KoShape*> editableShapes;
if (koSelection()) {
editableShapes = koSelection()->selectedEditableShapes();
}
const bool hasEditableShapes = !editableShapes.isEmpty();
action("object_order_front")->setEnabled(hasEditableShapes);
action("object_order_raise")->setEnabled(hasEditableShapes);
action("object_order_lower")->setEnabled(hasEditableShapes);
action("object_order_back")->setEnabled(hasEditableShapes);
action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes);
action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes);
action("object_transform_rotate_180")->setEnabled(hasEditableShapes);
action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes);
action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes);
action("object_transform_reset")->setEnabled(hasEditableShapes);
const bool multipleSelected = editableShapes.size() > 1;
const bool alignmentEnabled =
multipleSelected ||
(!editableShapes.isEmpty() &&
canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize));
action("object_align_horizontal_left")->setEnabled(alignmentEnabled);
action("object_align_horizontal_center")->setEnabled(alignmentEnabled);
action("object_align_horizontal_right")->setEnabled(alignmentEnabled);
action("object_align_vertical_top")->setEnabled(alignmentEnabled);
action("object_align_vertical_center")->setEnabled(alignmentEnabled);
action("object_align_vertical_bottom")->setEnabled(alignmentEnabled);
const bool distributionEnabled = editableShapes.size() > 2;
action("object_distribute_horizontal_left")->setEnabled(distributionEnabled);
action("object_distribute_horizontal_center")->setEnabled(distributionEnabled);
action("object_distribute_horizontal_right")->setEnabled(distributionEnabled);
action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled);
action("object_distribute_vertical_top")->setEnabled(distributionEnabled);
action("object_distribute_vertical_center")->setEnabled(distributionEnabled);
action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled);
action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled);
updateDistinctiveActions(editableShapes);
emit selectionChanged(editableShapes.size());
}
void DefaultTool::updateDistinctiveActions(const QList<KoShape*> &editableShapes) {
const bool multipleSelected = editableShapes.size() > 1;
action("object_group")->setEnabled(multipleSelected);
action("object_unite")->setEnabled(multipleSelected);
action("object_intersect")->setEnabled(multipleSelected);
action("object_subtract")->setEnabled(multipleSelected);
bool hasShapesWithMultipleSegments = false;
Q_FOREACH (KoShape *shape, editableShapes) {
KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
if (pathShape && pathShape->subpathCount() > 1) {
hasShapesWithMultipleSegments = true;
break;
}
}
action("object_split")->setEnabled(hasShapesWithMultipleSegments);
bool hasGroupShape = false;
foreach (KoShape *shape, editableShapes) {
if (dynamic_cast<KoShapeGroup *>(shape)) {
hasGroupShape = true;
break;
}
}
action("object_ungroup")->setEnabled(hasGroupShape);
}
KoToolSelection *DefaultTool::selection()
{
return m_selectionHandler;
}
QMenu* DefaultTool::popupActionsMenu()
{
if (m_contextMenu) {
m_contextMenu->clear();
- m_contextMenu->addAction(action("edit_cut"));
- m_contextMenu->addAction(action("edit_copy"));
- m_contextMenu->addAction(action("edit_paste"));
-
- m_contextMenu->addSeparator();
-
- m_contextMenu->addAction(action("object_order_front"));
- m_contextMenu->addAction(action("object_order_raise"));
- m_contextMenu->addAction(action("object_order_lower"));
- m_contextMenu->addAction(action("object_order_back"));
-
- if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) {
- m_contextMenu->addSeparator();
- m_contextMenu->addAction(action("object_group"));
- m_contextMenu->addAction(action("object_ungroup"));
- }
-
+ m_contextMenu->addSection(i18n("Vector Shape Actions"));
m_contextMenu->addSeparator();
QMenu *transform = m_contextMenu->addMenu(i18n("Transform"));
+
transform->addAction(action("object_transform_rotate_90_cw"));
transform->addAction(action("object_transform_rotate_90_ccw"));
transform->addAction(action("object_transform_rotate_180"));
transform->addSeparator();
transform->addAction(action("object_transform_mirror_horizontally"));
transform->addAction(action("object_transform_mirror_vertically"));
transform->addSeparator();
transform->addAction(action("object_transform_reset"));
if (action("object_unite")->isEnabled() ||
action("object_intersect")->isEnabled() ||
action("object_subtract")->isEnabled() ||
action("object_split")->isEnabled()) {
QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations"));
transform->addAction(action("object_unite"));
transform->addAction(action("object_intersect"));
transform->addAction(action("object_subtract"));
transform->addAction(action("object_split"));
}
+
+ m_contextMenu->addSeparator();
+
+ m_contextMenu->addAction(action("edit_cut"));
+ m_contextMenu->addAction(action("edit_copy"));
+ m_contextMenu->addAction(action("edit_paste"));
+
+ m_contextMenu->addSeparator();
+
+ m_contextMenu->addAction(action("object_order_front"));
+ m_contextMenu->addAction(action("object_order_raise"));
+ m_contextMenu->addAction(action("object_order_lower"));
+ m_contextMenu->addAction(action("object_order_back"));
+
+ if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) {
+ m_contextMenu->addSeparator();
+ m_contextMenu->addAction(action("object_group"));
+ m_contextMenu->addAction(action("object_ungroup"));
+ }
+
+
}
return m_contextMenu.data();
}
void DefaultTool::addTransformActions(QMenu *menu) const {
menu->addAction(action("object_transform_rotate_90_cw"));
menu->addAction(action("object_transform_rotate_90_ccw"));
menu->addAction(action("object_transform_rotate_180"));
menu->addSeparator();
menu->addAction(action("object_transform_mirror_horizontally"));
menu->addAction(action("object_transform_mirror_vertically"));
menu->addSeparator();
menu->addAction(action("object_transform_reset"));
}
void DefaultTool::explicitUserStrokeEndRequest()
{
QList<KoShape *> shapes = koSelection()->selectedEditableShapesAndDelegates();
emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes));
}
diff --git a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp
index afe9d5a844..9bea3bdc80 100644
--- a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp
+++ b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.cpp
@@ -1,289 +1,307 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 "ToolReferenceImages.h"
#include <QDesktopServices>
#include <QFile>
#include <QLayout>
#include <QMenu>
#include <QMessageBox>
#include <QVector>
+#include <QAction>
#include <KoSelection.h>
#include <KoShapeRegistry.h>
#include <KoShapeManager.h>
#include <KoShapeController.h>
#include <KoFileDialog.h>
#include <kis_action_registry.h>
#include <kis_canvas2.h>
#include <kis_canvas_resource_provider.h>
#include <KisViewManager.h>
#include <KisDocument.h>
#include <KisReferenceImagesLayer.h>
#include <kis_image.h>
#include "ToolReferenceImagesWidget.h"
#include "KisReferenceImageCollection.h"
ToolReferenceImages::ToolReferenceImages(KoCanvasBase * canvas)
: DefaultTool(canvas, false)
{
setObjectName("ToolReferenceImages");
}
ToolReferenceImages::~ToolReferenceImages()
{
}
void ToolReferenceImages::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
DefaultTool::activate(toolActivation, shapes);
auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
connect(kisCanvas->image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), this, SLOT(slotNodeAdded(KisNodeSP)));
+ connect(kisCanvas->imageView()->document(), &KisDocument::sigReferenceImagesLayerChanged, this, &ToolReferenceImages::slotNodeAdded);
auto referenceImageLayer = document()->referenceImagesLayer();
if (referenceImageLayer) {
setReferenceImageLayer(referenceImageLayer);
}
}
void ToolReferenceImages::deactivate()
{
DefaultTool::deactivate();
}
void ToolReferenceImages::slotNodeAdded(KisNodeSP node)
{
auto *referenceImagesLayer = dynamic_cast<KisReferenceImagesLayer*>(node.data());
if (referenceImagesLayer) {
setReferenceImageLayer(referenceImagesLayer);
}
}
void ToolReferenceImages::setReferenceImageLayer(KisSharedPtr<KisReferenceImagesLayer> layer)
{
m_layer = layer;
connect(layer.data(), SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
}
void ToolReferenceImages::addReferenceImage()
{
auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas)
- KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImage");
+ KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImage");
dialog.setCaption(i18n("Select a Reference Image"));
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
if (!locations.isEmpty()) {
dialog.setDefaultDir(locations.first());
}
QString filename = dialog.filename();
if (filename.isEmpty()) return;
if (!QFileInfo(filename).exists()) return;
auto *reference = KisReferenceImage::fromFile(filename, *kisCanvas->coordinatesConverter(), canvas()->canvasWidget());
if (reference) {
KisDocument *doc = document();
doc->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, {reference}));
}
}
+void ToolReferenceImages::pasteReferenceImage()
+{
+ KisCanvas2* kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
+ KIS_ASSERT_RECOVER_RETURN(kisCanvas)
+
+ KisReferenceImage* reference = KisReferenceImage::fromClipboard(*kisCanvas->coordinatesConverter());
+ if(reference) {
+ KisDocument *doc = document();
+ doc->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, {reference}));
+ }
+}
+
+
+
void ToolReferenceImages::removeAllReferenceImages()
{
auto layer = m_layer.toStrongRef();
if (!layer) return;
canvas()->addCommand(layer->removeReferenceImages(document(), layer->shapes()));
}
void ToolReferenceImages::loadReferenceImages()
{
auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas)
- KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImageCollection");
+ KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenReferenceImageCollection");
dialog.setMimeTypeFilters(QStringList() << "application/x-krita-reference-images");
dialog.setCaption(i18n("Load Reference Images"));
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
if (!locations.isEmpty()) {
dialog.setDefaultDir(locations.first());
}
QString filename = dialog.filename();
if (filename.isEmpty()) return;
if (!QFileInfo(filename).exists()) return;
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Could not open '%1'.", filename));
return;
}
KisReferenceImageCollection collection;
if (collection.load(&file)) {
QList<KoShape*> shapes;
Q_FOREACH(auto *reference, collection.referenceImages()) {
shapes.append(reference);
}
KisDocument *doc = document();
doc->addCommand(KisReferenceImagesLayer::addReferenceImages(doc, shapes));
} else {
QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Could not load reference images from '%1'.", filename));
}
file.close();
}
void ToolReferenceImages::saveReferenceImages()
{
auto layer = m_layer.toStrongRef();
if (!layer || layer->shapeCount() == 0) return;
auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas)
- KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "SaveReferenceImageCollection");
+ KoFileDialog dialog(kisCanvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "SaveReferenceImageCollection");
dialog.setMimeTypeFilters(QStringList() << "application/x-krita-reference-images");
dialog.setCaption(i18n("Save Reference Images"));
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
if (!locations.isEmpty()) {
dialog.setDefaultDir(locations.first());
}
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Could not open '%1' for saving.", filename));
return;
}
KisReferenceImageCollection collection(layer->referenceImages());
bool ok = collection.save(&file);
file.close();
if (!ok) {
QMessageBox::critical(nullptr, i18nc("@title:window", "Krita"), i18n("Failed to save reference images."));
}
}
void ToolReferenceImages::slotSelectionChanged()
{
auto layer = m_layer.toStrongRef();
if (!layer) return;
m_optionsWidget->selectionChanged(layer->shapeManager()->selection());
updateActions();
}
QList<QPointer<QWidget>> ToolReferenceImages::createOptionWidgets()
{
// Instead of inheriting DefaultTool's multi-tab implementation, inherit straight from KoToolBase
return KoToolBase::createOptionWidgets();
}
QWidget *ToolReferenceImages::createOptionWidget()
{
if (!m_optionsWidget) {
m_optionsWidget = new ToolReferenceImagesWidget(this);
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
}
return m_optionsWidget;
- }
+}
bool ToolReferenceImages::isValidForCurrentLayer() const
{
return true;
}
KoShapeManager *ToolReferenceImages::shapeManager() const
{
auto layer = m_layer.toStrongRef();
return layer ? layer->shapeManager() : nullptr;
}
KoSelection *ToolReferenceImages::koSelection() const
{
auto manager = shapeManager();
return manager ? manager->selection() : nullptr;
}
void ToolReferenceImages::updateDistinctiveActions(const QList<KoShape*> &)
{
action("object_group")->setEnabled(false);
action("object_unite")->setEnabled(false);
action("object_intersect")->setEnabled(false);
action("object_subtract")->setEnabled(false);
action("object_split")->setEnabled(false);
action("object_ungroup")->setEnabled(false);
}
void ToolReferenceImages::deleteSelection()
{
auto layer = m_layer.toStrongRef();
if (!layer) return;
QList<KoShape *> shapes = koSelection()->selectedShapes();
if (!shapes.empty()) {
canvas()->addCommand(layer->removeReferenceImages(document(), shapes));
}
}
KisDocument *ToolReferenceImages::document() const
{
auto kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
return kisCanvas->imageView()->document();
}
QList<QAction *> ToolReferenceImagesFactory::createActionsImpl()
{
- KisActionRegistry *actionRegistry = KisActionRegistry::instance();
- QList<QAction *> actions = DefaultToolFactory::createActionsImpl();
-
- actions << actionRegistry->makeQAction("object_order_front");
- actions << actionRegistry->makeQAction("object_order_raise");
- actions << actionRegistry->makeQAction("object_order_lower");
- actions << actionRegistry->makeQAction("object_order_back");
- actions << actionRegistry->makeQAction("object_group");
- actions << actionRegistry->makeQAction("object_ungroup");
- actions << actionRegistry->makeQAction("object_transform_rotate_90_cw");
- actions << actionRegistry->makeQAction("object_transform_rotate_90_ccw");
- actions << actionRegistry->makeQAction("object_transform_rotate_180");
- actions << actionRegistry->makeQAction("object_transform_mirror_horizontally");
- actions << actionRegistry->makeQAction("object_transform_mirror_vertically");
- actions << actionRegistry->makeQAction("object_transform_reset");
- actions << actionRegistry->makeQAction("object_unite");
- actions << actionRegistry->makeQAction("object_intersect");
- actions << actionRegistry->makeQAction("object_subtract");
- actions << actionRegistry->makeQAction("object_split");
+ QList<QAction *> defaultActions = DefaultToolFactory::createActionsImpl();
+ QList<QAction *> actions;
+
+ QStringList actionNames;
+ actionNames << "object_order_front"
+ << "object_order_raise"
+ << "object_order_lower"
+ << "object_order_back"
+ << "object_transform_rotate_90_cw"
+ << "object_transform_rotate_90_ccw"
+ << "object_transform_rotate_180"
+ << "object_transform_mirror_horizontally"
+ << "object_transform_mirror_vertically"
+ << "object_transform_reset";
+
+ Q_FOREACH(QAction *action, defaultActions) {
+ if (actionNames.contains(action->objectName())) {
+ actions << action;
+ }
+ }
return actions;
}
diff --git a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.h b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.h
index 33adb33ece..9a430a6c56 100644
--- a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.h
+++ b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImages.h
@@ -1,110 +1,112 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
+ * the Free Software Foundation; 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 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 TOOL_REFERENCE_IMAGES_H
#define TOOL_REFERENCE_IMAGES_H
#include <QPointer>
#include <KoToolFactoryBase.h>
#include <KoIcon.h>
#include <kis_tool.h>
#include "kis_painting_assistant.h"
#include <kis_icon.h>
#include <kis_canvas2.h>
#include <defaulttool/DefaultTool.h>
#include <defaulttool/DefaultToolFactory.h>
class ToolReferenceImagesWidget;
class KisReferenceImagesLayer;
class ToolReferenceImages : public DefaultTool
{
Q_OBJECT
public:
ToolReferenceImages(KoCanvasBase * canvas);
~ToolReferenceImages() override;
virtual quint32 priority() {
return 3;
}
void mouseDoubleClickEvent(KoPointerEvent */*event*/) override {}
void deleteSelection() override;
protected:
QList<QPointer<QWidget>> createOptionWidgets() override;
QWidget *createOptionWidget() override;
bool isValidForCurrentLayer() const override;
KoShapeManager *shapeManager() const override;
KoSelection *koSelection() const override;
void updateDistinctiveActions(const QList<KoShape*> &editableShapes) override;
public Q_SLOTS:
void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) override;
void deactivate() override;
void addReferenceImage();
+ void pasteReferenceImage();
void removeAllReferenceImages();
void saveReferenceImages();
void loadReferenceImages();
void slotNodeAdded(KisNodeSP node);
void slotSelectionChanged();
private:
friend class ToolReferenceImagesWidget;
ToolReferenceImagesWidget *m_optionsWidget = nullptr;
KisWeakSharedPtr<KisReferenceImagesLayer> m_layer;
KisDocument *document() const;
void setReferenceImageLayer(KisSharedPtr<KisReferenceImagesLayer> layer);
};
class ToolReferenceImagesFactory : public DefaultToolFactory
{
public:
ToolReferenceImagesFactory()
: DefaultToolFactory("ToolReferenceImages") {
setToolTip(i18n("Reference Images Tool"));
setSection(TOOL_TYPE_VIEW);
setIconName(koIconNameCStr("krita_tool_reference_images"));
setPriority(2);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
};
~ToolReferenceImagesFactory() override {}
KoToolBase * createTool(KoCanvasBase * canvas) override {
return new ToolReferenceImages(canvas);
}
QList<QAction *> createActionsImpl() override;
};
#endif
diff --git a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.cpp b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.cpp
index efa2de987c..1550b0d057 100644
--- a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.cpp
+++ b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.cpp
@@ -1,213 +1,246 @@
/*
* Copyright (c) 2017 Eugene Ingerman
*
* 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 "ToolReferenceImagesWidget.h"
#include "ui_WdgToolOptions.h"
#include <KoSelection.h>
#include <KoShapeTransparencyCommand.h>
#include <KoShapeKeepAspectRatioCommand.h>
#include <kis_config.h>
#include <kis_signals_blocker.h>
#include <KisReferenceImage.h>
+#include <QClipboard>
+#include <QApplication>
+#include <QStandardItemModel>
+
#include "ToolReferenceImages.h"
struct ToolReferenceImagesWidget::Private {
Private(ToolReferenceImages *tool)
: tool(tool)
{
}
Ui_WdgToolOptions *ui;
ToolReferenceImages *tool;
};
ToolReferenceImagesWidget::ToolReferenceImagesWidget(ToolReferenceImages *tool, KisCanvasResourceProvider */*provider*/, QWidget *parent)
: QWidget(parent),
d(new Private(tool))
{
d->ui = new Ui_WdgToolOptions();
d->ui->setupUi(this);
d->ui->opacitySlider->setRange(0, 100);
d->ui->opacitySlider->setPrefixes(i18n("Opacity: "), i18n("Opacity [*varies*]: "));
d->ui->opacitySlider->setSuffix(i18n(" %"));
d->ui->opacitySlider->setValueGetter(
[](KoShape *s){ return 100.0 * (1.0 - s->transparency()); }
);
d->ui->saturationSlider->setRange(0, 100);
d->ui->saturationSlider->setPrefixes(i18n("Saturation: "), i18n("Saturation [*varies*]: "));
d->ui->saturationSlider->setSuffix(i18n(" %"));
d->ui->saturationSlider->setValueGetter(
[](KoShape *s){
auto *r = dynamic_cast<KisReferenceImage*>(s);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(r, 0.0);
return 100.0 * r->saturation();
}
);
d->ui->bnAddReferenceImage->setToolTip(i18n("Add Reference Image"));
d->ui->bnAddReferenceImage->setIcon(KisIconUtils::loadIcon("addlayer"));
d->ui->bnDelete->setToolTip(i18n("Delete all Reference Images"));
d->ui->bnDelete->setIcon(KisIconUtils::loadIcon("edit-clear"));
d->ui->bnLoad->setToolTip(i18n("Load Reference Images Set"));
d->ui->bnLoad->setIcon(KisIconUtils::loadIcon("document-open"));
d->ui->bnSave->setToolTip(i18n("Export Reference Images Set"));
d->ui->bnSave->setIcon(KisIconUtils::loadIcon("document-save"));
-
+ d->ui->bnPasteReferenceImage->setToolTip(i18n("Paste Reference Image From System Clipboard"));
+ d->ui->bnPasteReferenceImage->setIcon(KisIconUtils::loadIcon("edit-paste"));
connect(d->ui->bnAddReferenceImage, SIGNAL(clicked()), tool, SLOT(addReferenceImage()));
+ connect(d->ui->bnPasteReferenceImage, SIGNAL(clicked()), tool, SLOT(pasteReferenceImage()));
+
connect(d->ui->bnDelete, SIGNAL(clicked()), tool, SLOT(removeAllReferenceImages()));
connect(d->ui->bnSave, SIGNAL(clicked()), tool, SLOT(saveReferenceImages()));
connect(d->ui->bnLoad, SIGNAL(clicked()), tool, SLOT(loadReferenceImages()));
connect(d->ui->chkKeepAspectRatio, SIGNAL(stateChanged(int)), this, SLOT(slotKeepAspectChanged()));
connect(d->ui->opacitySlider, SIGNAL(valueChanged(qreal)), this, SLOT(slotOpacitySliderChanged(qreal)));
connect(d->ui->saturationSlider, SIGNAL(valueChanged(qreal)), this, SLOT(slotSaturationSliderChanged(qreal)));
d->ui->referenceImageLocationCombobox->addItem(i18n("Embed to .KRA"));
d->ui->referenceImageLocationCombobox->addItem(i18n("Link to Image"));
connect(d->ui->referenceImageLocationCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSaveLocationChanged(int)));
updateVisibility(false); // no selection when we start
}
ToolReferenceImagesWidget::~ToolReferenceImagesWidget()
{
}
void ToolReferenceImagesWidget::selectionChanged(KoSelection *selection)
{
QList<KoShape*> shapes = selection->selectedEditableShapes();
d->ui->opacitySlider->setSelection(shapes);
d->ui->saturationSlider->setSelection(shapes);
bool anyKeepingAspectRatio = false;
bool anyNotKeepingAspectRatio = false;
bool anyEmbedded = false;
bool anyLinked = false;
bool anyNonLinkable = false;
bool anySelected = selection->count() > 0;
Q_FOREACH(KoShape *shape, shapes) {
KisReferenceImage *reference = dynamic_cast<KisReferenceImage*>(shape);
anyKeepingAspectRatio |= shape->keepAspectRatio();
anyNotKeepingAspectRatio |= !shape->keepAspectRatio();
if (reference) {
anyEmbedded |= reference->embed();
anyLinked |= !reference->embed();
anyNonLinkable |= !reference->hasLocalFile();
}
}
KisSignalsBlocker blocker(
d->ui->chkKeepAspectRatio,
d->ui->referenceImageLocationCombobox
);
d->ui->chkKeepAspectRatio->setCheckState(
(anyKeepingAspectRatio && anyNotKeepingAspectRatio) ? Qt::PartiallyChecked :
anyKeepingAspectRatio ? Qt::Checked : Qt::Unchecked);
// set save location combobox
bool imagesEmbedded = anyEmbedded && !anyLinked;
int comboBoxIndex = imagesEmbedded ? 0 : 1; // maps state to combobox index
d->ui->referenceImageLocationCombobox->setCurrentIndex(comboBoxIndex);
updateVisibility(anySelected);
}
void ToolReferenceImagesWidget::slotKeepAspectChanged()
{
KoSelection *selection = d->tool->koSelection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
KUndo2Command *cmd =
new KoShapeKeepAspectRatioCommand(shapes, d->ui->chkKeepAspectRatio->isChecked());
d->tool->canvas()->addCommand(cmd);
}
void ToolReferenceImagesWidget::slotOpacitySliderChanged(qreal newOpacity)
{
QList<KoShape*> shapes = d->ui->opacitySlider->selection();
if (shapes.isEmpty()) return;
KUndo2Command *cmd =
new KoShapeTransparencyCommand(shapes, 1.0 - newOpacity / 100.0);
d->tool->canvas()->addCommand(cmd);
}
void ToolReferenceImagesWidget::slotSaturationSliderChanged(qreal newSaturation)
{
QList<KoShape*> shapes = d->ui->saturationSlider->selection();
if (shapes.isEmpty()) return;
KUndo2Command *cmd =
new KisReferenceImage::SetSaturationCommand(shapes, newSaturation / 100.0);
d->tool->canvas()->addCommand(cmd);
}
void ToolReferenceImagesWidget::slotSaveLocationChanged(int index)
{
KoSelection *selection = d->tool->koSelection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
Q_FOREACH(KoShape *shape, shapes) {
KisReferenceImage *reference = dynamic_cast<KisReferenceImage*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN(reference);
if (index == 0) { // embed to KRA
reference->setEmbed(true);
} else { // link to file
- reference->setEmbed(false);
+ if (reference->hasLocalFile()) {
+ reference->setEmbed(false);
+ } else {
+ //In the case no local file is found, switch back to embed file data.
+ d->ui->referenceImageLocationCombobox->setCurrentIndex(0);
+ }
}
}
}
void ToolReferenceImagesWidget::updateVisibility(bool hasSelection)
{
// hide UI elements if nothing is selected.
d->ui->referenceImageLocationCombobox->setVisible(hasSelection);
d->ui->chkKeepAspectRatio->setVisible(hasSelection);
d->ui->saveLocationLabel->setVisible(hasSelection);
d->ui->opacitySlider->setVisible(hasSelection);
d->ui->saturationSlider->setVisible(hasSelection);
// show a label indicating that a selection is required to show options
d->ui->referenceImageOptionsLabel->setVisible(!hasSelection);
+ if (hasSelection) {
+ KoSelection* selection = d->tool->koSelection();
+ QList<KoShape*> shapes = selection->selectedEditableShapes();
+ bool usesLocalFile = true;
+
+ Q_FOREACH(KoShape *shape, shapes) {
+ KisReferenceImage *reference = dynamic_cast<KisReferenceImage*>(shape);
+
+ if (reference) {
+ usesLocalFile &= reference->hasLocalFile();
+ }
+ }
+
+ QStandardItemModel* model = dynamic_cast<QStandardItemModel*>(d->ui->referenceImageLocationCombobox->model());
+
+ if (model) {
+ QStandardItem* item = model->item(1);
+ item->setFlags(usesLocalFile ? item->flags() | Qt::ItemIsEnabled :
+ item->flags() & ~Qt::ItemIsEnabled);
+ }
+ }
}
diff --git a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.h b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.h
index a5cb030541..a84e3902db 100644
--- a/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.h
+++ b/plugins/tools/defaulttool/referenceimagestool/ToolReferenceImagesWidget.h
@@ -1,56 +1,55 @@
/*
* Copyright (c) 2017 Eugene Ingerman
*
* 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 __TOOL_REFERENCE_IMAGES_WIDGET_H
#define __TOOL_REFERENCE_IMAGES_WIDGET_H
#include <QScopedPointer>
#include <QWidget>
#include <QModelIndex>
#include "kis_types.h"
class KoColor;
class KoSelection;
class KisCanvasResourceProvider;
class ToolReferenceImages;
class ToolReferenceImagesWidget : public QWidget
{
Q_OBJECT
public:
ToolReferenceImagesWidget(ToolReferenceImages *tool, KisCanvasResourceProvider *provider = 0, QWidget *parent = 0);
~ToolReferenceImagesWidget() override;
void selectionChanged(KoSelection *selection);
+
private Q_SLOTS:
void slotOpacitySliderChanged(qreal);
void slotSaturationSliderChanged(qreal);
void slotKeepAspectChanged();
void slotSaveLocationChanged(int index);
-
-
private:
struct Private;
const QScopedPointer<Private> d;
void updateVisibility(bool hasSelection);
};
#endif
diff --git a/plugins/tools/defaulttool/referenceimagestool/WdgToolOptions.ui b/plugins/tools/defaulttool/referenceimagestool/WdgToolOptions.ui
index f7bd0dbf77..cb50eaa97f 100644
--- a/plugins/tools/defaulttool/referenceimagestool/WdgToolOptions.ui
+++ b/plugins/tools/defaulttool/referenceimagestool/WdgToolOptions.ui
@@ -1,208 +1,230 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgToolOptions</class>
<widget class="QWidget" name="WdgToolOptions">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>279</width>
+ <width>288</width>
<height>352</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="KisShapePropertySlider" name="opacitySlider" native="true"/>
</item>
<item>
<widget class="KisShapePropertySlider" name="saturationSlider" native="true"/>
</item>
<item>
<widget class="QCheckBox" name="chkKeepAspectRatio">
<property name="text">
<string>Keep aspect ratio</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="saveLocationLabel">
<property name="text">
<string>Save Location:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="referenceImageLocationCombobox"/>
</item>
<item>
<widget class="QLabel" name="referenceImageOptionsLabel">
<property name="text">
<string>Add/Select an image to show options</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="bnAddReferenceImage">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
+ <item>
+ <widget class="QPushButton" name="bnPasteReferenceImage">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>18</width>
+ <height>18</height>
+ </size>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="bnLoad">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bnSave">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bnDelete">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>All</string>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisShapePropertySlider</class>
<extends>QWidget</extends>
<header>KisSelectionPropertySlider.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
index af512b2a32..f9040ecb54 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
@@ -1,520 +1,524 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Fela Winkelmolen <fela.kde@gmail.com>
*
* 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 "KarbonCalligraphyTool.h"
#include "KarbonCalligraphicShape.h"
#include "KarbonCalligraphyOptionWidget.h"
#include <KoPathShape.h>
#include <KoShapeGroup.h>
#include <KoPointerEvent.h>
#include <KoPathPoint.h>
#include <KoCanvasBase.h>
#include <KoShapeController.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoSelection.h>
#include <KoCurveFit.h>
#include <KoColorBackground.h>
#include <KoCanvasResourceProvider.h>
#include <KoColor.h>
#include <KoShapePaintingContext.h>
#include <KoViewConverter.h>
#include <QAction>
#include <QDebug>
#include <klocalizedstring.h>
#include <QPainter>
#include <cmath>
#undef M_PI
const qreal M_PI = 3.1415927;
using std::pow;
using std::sqrt;
KarbonCalligraphyTool::KarbonCalligraphyTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_shape(0)
, m_angle(0)
, m_selectedPath(0)
, m_isDrawing(false)
, m_speed(0, 0)
, m_lastShape(0)
{
connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), SLOT(updateSelectedPath()));
updateSelectedPath();
}
KarbonCalligraphyTool::~KarbonCalligraphyTool()
{
}
void KarbonCalligraphyTool::paint(QPainter &painter, const KoViewConverter &converter)
{
if (m_selectedPath) {
painter.save();
painter.setRenderHints(QPainter::Antialiasing, false);
painter.setPen(Qt::red); // TODO make configurable
QRectF rect = m_selectedPath->boundingRect();
QPointF p1 = converter.documentToView(rect.topLeft());
QPointF p2 = converter.documentToView(rect.bottomRight());
painter.drawRect(QRectF(p1, p2));
painter.restore();
}
if (!m_shape) {
return;
}
painter.save();
painter.setTransform(m_shape->absoluteTransformation(&converter) *
painter.transform());
KoShapePaintingContext paintContext; //FIXME
m_shape->paint(painter, converter, paintContext);
painter.restore();
}
void KarbonCalligraphyTool::mousePressEvent(KoPointerEvent *event)
{
if (m_isDrawing) {
return;
}
m_lastPoint = event->point;
m_speed = QPointF(0, 0);
m_isDrawing = true;
m_pointCount = 0;
m_shape = new KarbonCalligraphicShape(m_caps);
m_shape->setBackground(QSharedPointer<KoShapeBackground>(new KoColorBackground(canvas()->resourceManager()->foregroundColor().toQColor())));
//addPoint( event );
}
void KarbonCalligraphyTool::mouseMoveEvent(KoPointerEvent *event)
{
if (!m_isDrawing) {
return;
}
addPoint(event);
}
void KarbonCalligraphyTool::mouseReleaseEvent(KoPointerEvent *event)
{
if (!m_isDrawing) {
return;
}
if (m_pointCount == 0) {
// handle click: select shape (if any)
if (event->point == m_lastPoint) {
KoShapeManager *shapeManager = canvas()->shapeManager();
KoShape *selectedShape = shapeManager->shapeAt(event->point);
if (selectedShape != 0) {
shapeManager->selection()->deselectAll();
shapeManager->selection()->select(selectedShape);
}
}
delete m_shape;
m_shape = 0;
m_isDrawing = false;
return;
} else {
m_endOfPath = false; // allow last point being added
addPoint(event); // add last point
m_isDrawing = false;
}
m_shape->simplifyGuidePath();
KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape, 0);
if (cmd) {
m_lastShape = m_shape;
canvas()->addCommand(cmd);
canvas()->updateCanvas(m_shape->boundingRect());
} else {
// don't leak shape when command could not be created
delete m_shape;
}
m_shape = 0;
}
void KarbonCalligraphyTool::addPoint(KoPointerEvent *event)
{
if (m_pointCount == 0) {
if (m_usePath && m_selectedPath) {
m_selectedPathOutline = m_selectedPath->outline();
}
m_pointCount = 1;
m_endOfPath = false;
m_followPathPosition = 0;
m_lastMousePos = event->point;
m_lastPoint = calculateNewPoint(event->point, &m_speed);
m_deviceSupportsTilt = (event->xTilt() != 0 || event->yTilt() != 0);
return;
}
if (m_endOfPath) {
return;
}
++m_pointCount;
setAngle(event);
QPointF newSpeed;
QPointF newPoint = calculateNewPoint(event->point, &newSpeed);
qreal width = calculateWidth(event->pressure());
qreal angle = calculateAngle(m_speed, newSpeed);
// add the previous point
m_shape->appendPoint(m_lastPoint, angle, width);
m_speed = newSpeed;
m_lastPoint = newPoint;
canvas()->updateCanvas(m_shape->lastPieceBoundingRect());
if (m_usePath && m_selectedPath) {
m_speed = QPointF(0, 0); // following path
}
}
void KarbonCalligraphyTool::setAngle(KoPointerEvent *event)
{
if (!m_useAngle) {
m_angle = (360 - m_customAngle + 90) / 180.0 * M_PI;
return;
}
// setting m_angle to the angle of the device
if (event->xTilt() != 0 || event->yTilt() != 0) {
m_deviceSupportsTilt = false;
}
if (m_deviceSupportsTilt) {
if (event->xTilt() == 0 && event->yTilt() == 0) {
return; // leave as is
}
qDebug() << "using tilt" << m_angle;
if (event->x() == 0) {
m_angle = M_PI / 2;
return;
}
// y is inverted in qt painting
m_angle = std::atan(static_cast<double>(-event->yTilt() / event->xTilt())) + M_PI / 2;
} else {
m_angle = event->rotation() + M_PI / 2;
qDebug() << "using rotation" << m_angle;
}
}
QPointF KarbonCalligraphyTool::calculateNewPoint(const QPointF &mousePos, QPointF *speed)
{
if (!m_usePath || !m_selectedPath) { // don't follow path
QPointF force = mousePos - m_lastPoint;
QPointF dSpeed = force / m_mass;
*speed = m_speed * (1.0 - m_drag) + dSpeed;
return m_lastPoint + *speed;
}
QPointF sp = mousePos - m_lastMousePos;
m_lastMousePos = mousePos;
// follow selected path
qreal step = QLineF(QPointF(0, 0), sp).length();
m_followPathPosition += step;
qreal t;
if (m_followPathPosition >= m_selectedPathOutline.length()) {
t = 1.0;
m_endOfPath = true;
} else {
t = m_selectedPathOutline.percentAtLength(m_followPathPosition);
}
QPointF res = m_selectedPathOutline.pointAtPercent(t)
+ m_selectedPath->position();
*speed = res - m_lastPoint;
return res;
}
qreal KarbonCalligraphyTool::calculateWidth(qreal pressure)
{
// calculate the modulo of the speed
qreal speed = std::sqrt(pow(m_speed.x(), 2) + pow(m_speed.y(), 2));
qreal thinning = m_thinning * (speed + 1) / 10.0; // can be negative
if (thinning > 1) {
thinning = 1;
}
if (!m_usePressure) {
pressure = 1.0;
}
qreal strokeWidth = m_strokeWidth * pressure * (1 - thinning);
const qreal MINIMUM_STROKE_WIDTH = 1.0;
if (strokeWidth < MINIMUM_STROKE_WIDTH) {
strokeWidth = MINIMUM_STROKE_WIDTH;
}
return strokeWidth;
}
qreal KarbonCalligraphyTool::calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed)
{
// calculate the average of the speed (sum of the normalized values)
qreal oldLength = QLineF(QPointF(0, 0), oldSpeed).length();
qreal newLength = QLineF(QPointF(0, 0), newSpeed).length();
QPointF oldSpeedNorm = !qFuzzyCompare(oldLength + 1, 1) ?
oldSpeed / oldLength : QPointF(0, 0);
QPointF newSpeedNorm = !qFuzzyCompare(newLength + 1, 1) ?
newSpeed / newLength : QPointF(0, 0);
QPointF speed = oldSpeedNorm + newSpeedNorm;
// angle solely based on the speed
qreal speedAngle = 0;
if (speed.x() != 0) { // avoid division by zero
speedAngle = std::atan(speed.y() / speed.x());
} else if (speed.y() > 0) {
// x == 0 && y != 0
speedAngle = M_PI / 2;
} else if (speed.y() < 0) {
// x == 0 && y != 0
speedAngle = -M_PI / 2;
}
if (speed.x() < 0) {
speedAngle += M_PI;
}
// move 90 degrees
speedAngle += M_PI / 2;
qreal fixedAngle = m_angle;
// check if the fixed angle needs to be flipped
qreal diff = fixedAngle - speedAngle;
while (diff >= M_PI) { // normalize diff between -180 and 180
diff -= 2 * M_PI;
}
while (diff < -M_PI) {
diff += 2 * M_PI;
}
if (std::abs(diff) > M_PI / 2) { // if absolute value < 90
fixedAngle += M_PI; // += 180
}
qreal dAngle = speedAngle - fixedAngle;
// normalize dAngle between -90 and +90
while (dAngle >= M_PI / 2) {
dAngle -= M_PI;
}
while (dAngle < -M_PI / 2) {
dAngle += M_PI;
}
qreal angle = fixedAngle + dAngle * (1.0 - m_fixation);
return angle;
}
void KarbonCalligraphyTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KoToolBase::activate(activation, shapes);
+ if (!m_widget) {
+ createOptionWidgets();
+ }
+
QAction *a = action("calligraphy_increase_width");
connect(a, SIGNAL(triggered()), m_widget, SLOT(increaseWidth()), Qt::UniqueConnection);
a = action("calligraphy_decrease_width");
connect(a, SIGNAL(triggered()), m_widget, SLOT(decreaseWidth()), Qt::UniqueConnection);
a = action("calligraphy_increase_angle");
connect(a, SIGNAL(triggered()), m_widget, SLOT(increaseAngle()), Qt::UniqueConnection);
a = action("calligraphy_decrease_angle");
connect(a, SIGNAL(triggered()), m_widget, SLOT(decreaseAngle()), Qt::UniqueConnection);
useCursor(Qt::CrossCursor);
m_lastShape = 0;
}
void KarbonCalligraphyTool::deactivate()
{
QAction *a = action("calligraphy_increase_width");
disconnect(a, 0, this, 0);
a = action("calligraphy_decrease_width");
disconnect(a, 0, this, 0);
a = action("calligraphy_increase_angle");
disconnect(a, 0, this, 0);
a = action("calligraphy_decrease_angle");
disconnect(a, 0, this, 0);
if (m_lastShape && canvas()->shapeManager()->shapes().contains(m_lastShape)) {
KoSelection *selection = canvas()->shapeManager()->selection();
selection->deselectAll();
selection->select(m_lastShape);
}
KoToolBase::deactivate();
}
QList<QPointer<QWidget> > KarbonCalligraphyTool::createOptionWidgets()
{
// if the widget don't exists yet create it
QList<QPointer<QWidget> > widgets;
//KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0);
//fillWidget->setWindowTitle(i18n("Fill"));
//widgets.append(fillWidget);
m_widget = new KarbonCalligraphyOptionWidget();
connect(m_widget, SIGNAL(usePathChanged(bool)),
this, SLOT(setUsePath(bool)));
connect(m_widget, SIGNAL(usePressureChanged(bool)),
this, SLOT(setUsePressure(bool)));
connect(m_widget, SIGNAL(useAngleChanged(bool)),
this, SLOT(setUseAngle(bool)));
connect(m_widget, SIGNAL(widthChanged(double)),
this, SLOT(setStrokeWidth(double)));
connect(m_widget, SIGNAL(thinningChanged(double)),
this, SLOT(setThinning(double)));
connect(m_widget, SIGNAL(angleChanged(int)),
this, SLOT(setAngle(int)));
connect(m_widget, SIGNAL(fixationChanged(double)),
this, SLOT(setFixation(double)));
connect(m_widget, SIGNAL(capsChanged(double)),
this, SLOT(setCaps(double)));
connect(m_widget, SIGNAL(massChanged(double)),
this, SLOT(setMass(double)));
connect(m_widget, SIGNAL(dragChanged(double)),
this, SLOT(setDrag(double)));
connect(this, SIGNAL(pathSelectedChanged(bool)),
m_widget, SLOT(setUsePathEnabled(bool)));
// sync all parameters with the loaded profile
m_widget->emitAll();
m_widget->setObjectName(i18n("Calligraphy"));
m_widget->setWindowTitle(i18n("Calligraphy"));
widgets.append(m_widget);
return widgets;
}
void KarbonCalligraphyTool::setStrokeWidth(double width)
{
m_strokeWidth = width;
}
void KarbonCalligraphyTool::setThinning(double thinning)
{
m_thinning = thinning;
}
void KarbonCalligraphyTool::setAngle(int angle)
{
m_customAngle = angle;
}
void KarbonCalligraphyTool::setFixation(double fixation)
{
m_fixation = fixation;
}
void KarbonCalligraphyTool::setMass(double mass)
{
m_mass = mass * mass + 1;
}
void KarbonCalligraphyTool::setDrag(double drag)
{
m_drag = drag;
}
void KarbonCalligraphyTool::setUsePath(bool usePath)
{
m_usePath = usePath;
}
void KarbonCalligraphyTool::setUsePressure(bool usePressure)
{
m_usePressure = usePressure;
}
void KarbonCalligraphyTool::setUseAngle(bool useAngle)
{
m_useAngle = useAngle;
}
void KarbonCalligraphyTool::setCaps(double caps)
{
m_caps = caps;
}
void KarbonCalligraphyTool::updateSelectedPath()
{
KoPathShape *oldSelectedPath = m_selectedPath; // save old value
KoSelection *selection = canvas()->shapeManager()->selection();
if (selection) {
// null pointer if it the selection isn't a KoPathShape
// or if the selection is empty
m_selectedPath =
dynamic_cast<KoPathShape *>(selection->firstSelectedShape());
// or if it's a KoPathShape but with no or more than one subpaths
if (m_selectedPath && m_selectedPath->subpathCount() != 1) {
m_selectedPath = 0;
}
// or if there ora none or more than 1 shapes selected
if (selection->count() != 1) {
m_selectedPath = 0;
}
// emit signal it there wasn't a selected path and now there is
// or the other way around
if ((m_selectedPath != 0) != (oldSelectedPath != 0)) {
emit pathSelectedChanged(m_selectedPath != 0);
}
}
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc
index 59d7c81a89..34acfb5865 100644
--- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc
@@ -1,251 +1,250 @@
/*
* kis_tool_select_contiguous - part of Krayon^WKrita
*
* Copyright (c) 1999 Michael Koch <koch@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2012 José Luis Vergara <pentalis@gmail.com>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_select_contiguous.h"
#include <QPainter>
#include <QLayout>
#include <QLabel>
#include <QApplication>
#include <QCheckBox>
#include <QVBoxLayout>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include "KoPointerEvent.h"
#include "KoViewConverter.h"
#include "kis_cursor.h"
#include "kis_selection_manager.h"
#include "kis_image.h"
#include "canvas/kis_canvas2.h"
#include "kis_layer.h"
#include "kis_selection_options.h"
#include "kis_paint_device.h"
#include "kis_fill_painter.h"
#include "kis_pixel_selection.h"
#include "kis_selection_tool_helper.h"
#include "kis_slider_spin_box.h"
#include "tiles3/kis_hline_iterator.h"
KisToolSelectContiguous::KisToolSelectContiguous(KoCanvasBase *canvas)
: KisToolSelect(canvas,
KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6),
i18n("Contiguous Area Selection")),
m_fuzziness(20),
m_sizemod(0),
m_feather(0),
m_limitToCurrentLayer(false)
{
setObjectName("tool_select_contiguous");
}
KisToolSelectContiguous::~KisToolSelectContiguous()
{
}
void KisToolSelectContiguous::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisToolSelect::activate(toolActivation, shapes);
m_configGroup = KSharedConfig::openConfig()->group(toolId());
}
void KisToolSelectContiguous::beginPrimaryAction(KoPointerEvent *event)
{
KisToolSelectBase::beginPrimaryAction(event);
KisPaintDeviceSP dev;
if (!currentNode() ||
!(dev = currentNode()->projection()) ||
!currentNode()->visible() ||
!selectionEditable()) {
event->ignore();
return;
}
if (KisToolSelect::selectionDidMove()) {
return;
}
QApplication::setOverrideCursor(KisCursor::waitCursor());
QPoint pos = convertToImagePixelCoordFloored(event);
QRect rc = currentImage()->bounds();
KisFillPainter fillpainter(dev);
fillpainter.setHeight(rc.height());
fillpainter.setWidth(rc.width());
fillpainter.setFillThreshold(m_fuzziness);
fillpainter.setFeather(m_feather);
fillpainter.setSizemod(m_sizemod);
KisImageWSP image = currentImage();
KisPaintDeviceSP sourceDevice = m_limitToCurrentLayer ? dev : image->projection();
image->lock();
KisSelectionSP selection = fillpainter.createFloodSelection(pos.x(), pos.y(), sourceDevice);
image->unlock();
// If we're not antialiasing, threshold the entire selection
if (!antiAliasSelection()) {
QRect r = selection->selectedExactRect();
if (r.isValid()) {
KisHLineIteratorSP selectionIt = selection->pixelSelection()->createHLineIteratorNG(r.x(), r.y(), r.width());
for (qint32 y = 0; y < r.height(); y++) {
do {
if (selectionIt->rawData()[0] > 0) {
selection->pixelSelection()->colorSpace()->setOpacity(selectionIt->rawData(), OPACITY_OPAQUE_U8, 1);
}
} while (selectionIt->nextPixel());
selectionIt->nextRow();
}
}
}
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
if (!kisCanvas || !selection->pixelSelection()) {
QApplication::restoreOverrideCursor();
return;
}
selection->pixelSelection()->invalidateOutlineCache();
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area"));
helper.selectPixelSelection(selection->pixelSelection(), selectionAction());
QApplication::restoreOverrideCursor();
}
void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
void KisToolSelectContiguous::slotSetFuzziness(int fuzziness)
{
m_fuzziness = fuzziness;
m_configGroup.writeEntry("fuzziness", fuzziness);
}
void KisToolSelectContiguous::slotSetSizemod(int sizemod)
{
m_sizemod = sizemod;
m_configGroup.writeEntry("sizemod", sizemod);
}
void KisToolSelectContiguous::slotSetFeather(int feather)
{
m_feather = feather;
m_configGroup.writeEntry("feather", feather);
}
QWidget* KisToolSelectContiguous::createOptionWidget()
{
KisToolSelectBase::createOptionWidget();
KisSelectionOptions *selectionWidget = selectionOptionWidget();
- selectionWidget->disableSelectionModeOption();
QVBoxLayout * l = dynamic_cast<QVBoxLayout*>(selectionWidget->layout());
Q_ASSERT(l);
if (l) {
QGridLayout * gridLayout = new QGridLayout();
l->insertLayout(1, gridLayout);
QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget);
gridLayout->addWidget(lbl, 0, 0, 1, 1);
KisSliderSpinBox *input = new KisSliderSpinBox(selectionWidget);
Q_CHECK_PTR(input);
input->setObjectName("fuzziness");
input->setRange(1, 100);
input->setSingleStep(1);
input->setExponentRatio(2);
gridLayout->addWidget(input, 0, 1, 1, 1);
lbl = new QLabel(i18n("Grow/shrink selection: "), selectionWidget);
gridLayout->addWidget(lbl, 1, 0, 1, 1);
KisSliderSpinBox *sizemod = new KisSliderSpinBox(selectionWidget);
Q_CHECK_PTR(sizemod);
sizemod->setObjectName("sizemod"); //grow/shrink selection
sizemod->setRange(-40, 40);
sizemod->setSingleStep(1);
gridLayout->addWidget(sizemod, 1, 1, 1, 1);
lbl = new QLabel(i18n("Feathering radius: "), selectionWidget);
gridLayout->addWidget(lbl, 2, 0, 1, 1);
KisSliderSpinBox *feather = new KisSliderSpinBox(selectionWidget);
Q_CHECK_PTR(feather);
feather->setObjectName("feathering");
feather->setRange(0, 40);
feather->setSingleStep(1);
gridLayout->addWidget(feather, 2, 1, 1, 1);
connect (input , SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int)));
connect (sizemod, SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int)));
connect (feather, SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int)));
QCheckBox* limitToCurrentLayer = new QCheckBox(i18n("Limit to current layer"), selectionWidget);
l->insertWidget(4, limitToCurrentLayer);
connect (limitToCurrentLayer, SIGNAL(stateChanged(int)), this, SLOT(slotLimitToCurrentLayer(int)));
// load configuration settings into tool options
input->setValue(m_configGroup.readEntry("fuzziness", 20)); // fuzziness
sizemod->setValue( m_configGroup.readEntry("sizemod", 0)); //grow/shrink
sizemod->setSuffix(i18n(" px"));
feather->setValue(m_configGroup.readEntry("feather", 0));
feather->setSuffix(i18n(" px"));
limitToCurrentLayer->setChecked(m_configGroup.readEntry("limitToCurrentLayer", false));
}
return selectionWidget;
}
void KisToolSelectContiguous::slotLimitToCurrentLayer(int state)
{
if (state == Qt::PartiallyChecked)
return;
m_limitToCurrentLayer = (state == Qt::Checked);
m_configGroup.writeEntry("limitToCurrentLayer", state);
}
void KisToolSelectContiguous::resetCursorStyle()
{
if (selectionAction() == SELECTION_ADD) {
useCursor(KisCursor::load("tool_contiguous_selection_cursor_add.png", 6, 6));
} else if (selectionAction() == SELECTION_SUBTRACT) {
useCursor(KisCursor::load("tool_contiguous_selection_cursor_sub.png", 6, 6));
} else {
KisToolSelect::resetCursorStyle();
}
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.h b/plugins/tools/selectiontools/kis_tool_select_contiguous.h
index 3bea53806c..68da15d936 100644
--- a/plugins/tools/selectiontools/kis_tool_select_contiguous.h
+++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.h
@@ -1,95 +1,97 @@
/*
* kis_tool_select_contiguous.h - part of KImageShop^WKrayon^Krita
*
* Copyright (c) 1999 Michael Koch <koch@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_TOOL_SELECT_CONTIGUOUS_H__
#define __KIS_TOOL_SELECT_CONTIGUOUS_H__
#include "KisSelectionToolFactoryBase.h"
#include "kis_tool_select_base.h"
#include <kis_icon.h>
#include <kconfig.h>
#include <kconfiggroup.h>
/**
* The 'magic wand' selection tool -- in fact just
* a floodfill that only creates a selection.
*/
class KisToolSelectContiguous : public KisToolSelect
{
Q_OBJECT
public:
KisToolSelectContiguous(KoCanvasBase *canvas);
~KisToolSelectContiguous() override;
QWidget* createOptionWidget() override;
void paint(QPainter &painter, const KoViewConverter &converter) override;
void beginPrimaryAction(KoPointerEvent *event) override;
void resetCursorStyle() override;
protected:
bool wantsAutoScroll() const override { return false; }
+ bool isPixelOnly() const override { return true; }
+
public Q_SLOTS:
void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) override;
virtual void slotSetFuzziness(int);
virtual void slotSetSizemod(int);
virtual void slotSetFeather(int);
virtual void slotLimitToCurrentLayer(int);
//virtual bool antiAliasSelection();
protected:
using KisToolSelectBase::m_widgetHelper;
private:
int m_fuzziness;
int m_sizemod;
int m_feather;
bool m_limitToCurrentLayer;
KConfigGroup m_configGroup;
};
class KisToolSelectContiguousFactory : public KisSelectionToolFactoryBase
{
public:
KisToolSelectContiguousFactory()
: KisSelectionToolFactoryBase("KisToolSelectContiguous")
{
setToolTip(i18n("Contiguous Selection Tool"));
setSection(TOOL_TYPE_SELECTION);
setIconName(koIconNameCStr("tool_contiguous_selection"));
setPriority(4);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
}
~KisToolSelectContiguousFactory() override {}
KoToolBase * createTool(KoCanvasBase *canvas) override {
return new KisToolSelectContiguous(canvas);
}
};
#endif //__KIS_TOOL_SELECT_CONTIGUOUS_H__
diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.cc b/plugins/tools/selectiontools/kis_tool_select_outline.cc
index 7b48eb24fc..43495b988d 100644
--- a/plugins/tools/selectiontools/kis_tool_select_outline.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_outline.cc
@@ -1,282 +1,283 @@
/*
* kis_tool_select_freehand.h - part of Krayon^WKrita
*
* Copyright (c) 2000 John Califf <jcaliff@compuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_select_outline.h"
#include <QApplication>
#include <QPainter>
#include <QWidget>
#include <QPainterPath>
#include <QLayout>
#include <QVBoxLayout>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoPointerEvent.h>
#include <KoShapeController.h>
#include <KoPathShape.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
#include <KoViewConverter.h>
#include <kis_layer.h>
#include <kis_selection_options.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include "kis_painter.h"
#include <brushengine/kis_paintop_registry.h>
#include "canvas/kis_canvas2.h"
#include "kis_pixel_selection.h"
#include "kis_selection_tool_helper.h"
#include "kis_algebra_2d.h"
#define FEEDBACK_LINE_WIDTH 2
KisToolSelectOutline::KisToolSelectOutline(KoCanvasBase * canvas)
: KisToolSelect(canvas,
KisCursor::load("tool_outline_selection_cursor.png", 5, 5),
i18n("Outline Selection")),
m_continuedMode(false)
{
}
KisToolSelectOutline::~KisToolSelectOutline()
{
}
void KisToolSelectOutline::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Control) {
m_continuedMode = true;
}
KisToolSelect::keyPressEvent(event);
}
void KisToolSelectOutline::keyReleaseEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Control ||
!(event->modifiers() & Qt::ControlModifier)) {
m_continuedMode = false;
if (mode() != PAINT_MODE && !m_points.isEmpty()) {
finishSelectionAction();
}
}
KisToolSelect::keyReleaseEvent(event);
}
void KisToolSelectOutline::mouseMoveEvent(KoPointerEvent *event)
{
KisToolSelect::mouseMoveEvent(event);
if (selectionDragInProgress()) return;
m_lastCursorPos = convertToPixelCoord(event);
if (m_continuedMode && mode() != PAINT_MODE) {
updateContinuedMode();
}
}
void KisToolSelectOutline::beginPrimaryAction(KoPointerEvent *event)
{
KisToolSelectBase::beginPrimaryAction(event);
if (selectionDragInProgress()) return;
if (!selectionEditable()) {
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
if (m_continuedMode && !m_points.isEmpty()) {
m_paintPath.lineTo(pixelToView(convertToPixelCoord(event)));
} else {
m_paintPath.moveTo(pixelToView(convertToPixelCoord(event)));
}
m_points.append(convertToPixelCoord(event));
}
void KisToolSelectOutline::continuePrimaryAction(KoPointerEvent *event)
{
KisToolSelectBase::continuePrimaryAction(event);
if (selectionDragInProgress()) return;
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
QPointF point = convertToPixelCoord(event);
m_paintPath.lineTo(pixelToView(point));
m_points.append(point);
updateFeedback();
}
void KisToolSelectOutline::endPrimaryAction(KoPointerEvent *event)
{
const bool hadMoveInProgress = selectionDragInProgress();
KisToolSelectBase::endPrimaryAction(event);
if (hadMoveInProgress) return;
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
setMode(KisTool::HOVER_MODE);
if (!m_continuedMode) {
finishSelectionAction();
+ m_points.clear(); // ensure points are always cleared
}
}
void KisToolSelectOutline::finishSelectionAction()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
kisCanvas->updateCanvas();
const QRectF boundingRect = KisAlgebra2D::accumulateBounds(m_points);
const QRectF boundingViewRect = pixelToView(boundingRect);
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Outline"));
if (helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) {
return;
}
if (m_points.count() > 2) {
QApplication::setOverrideCursor(KisCursor::waitCursor());
const SelectionMode mode =
helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(),
selectionMode(),
selectionAction());
if (mode == PIXEL_SELECTION) {
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
KisPainter painter(tmpSel);
painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace()));
painter.setAntiAliasPolygonFill(antiAliasSelection());
painter.setFillStyle(KisPainter::FillStyleForegroundColor);
painter.setStrokeStyle(KisPainter::StrokeStyleNone);
painter.paintPolygon(m_points);
QPainterPath cache;
cache.addPolygon(m_points);
cache.closeSubpath();
tmpSel->setOutlineCache(cache);
helper.selectPixelSelection(tmpSel, selectionAction());
} else {
KoPathShape* path = new KoPathShape();
path->setShapeId(KoPathShapeId);
QTransform resolutionMatrix;
resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
path->moveTo(resolutionMatrix.map(m_points[0]));
for (int i = 1; i < m_points.count(); i++)
path->lineTo(resolutionMatrix.map(m_points[i]));
path->close();
path->normalize();
helper.addSelectionShape(path, selectionAction());
}
QApplication::restoreOverrideCursor();
}
m_points.clear();
m_paintPath = QPainterPath();
}
void KisToolSelectOutline::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if ((mode() == KisTool::PAINT_MODE || m_continuedMode) &&
!m_points.isEmpty()) {
QPainterPath outline = m_paintPath;
if (m_continuedMode && mode() != KisTool::PAINT_MODE) {
outline.lineTo(pixelToView(m_lastCursorPos));
}
paintToolOutline(&gc, outline);
}
}
void KisToolSelectOutline::updateFeedback()
{
if (m_points.count() > 1) {
qint32 lastPointIndex = m_points.count() - 1;
QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized();
updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
updateCanvasPixelRect(updateRect);
}
}
void KisToolSelectOutline::updateContinuedMode()
{
if (!m_points.isEmpty()) {
qint32 lastPointIndex = m_points.count() - 1;
QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized();
updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
updateCanvasPixelRect(updateRect);
}
}
void KisToolSelectOutline::deactivate()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
kisCanvas->updateCanvas();
m_continuedMode = false;
KisTool::deactivate();
}
void KisToolSelectOutline::resetCursorStyle()
{
if (selectionAction() == SELECTION_ADD) {
useCursor(KisCursor::load("tool_outline_selection_cursor_add.png", 6, 6));
} else if (selectionAction() == SELECTION_SUBTRACT) {
useCursor(KisCursor::load("tool_outline_selection_cursor_sub.png", 6, 6));
} else {
KisToolSelect::resetCursorStyle();
}
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_similar.cc b/plugins/tools/selectiontools/kis_tool_select_similar.cc
index 67d3ebe748..106d7e30e8 100644
--- a/plugins/tools/selectiontools/kis_tool_select_similar.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_similar.cc
@@ -1,188 +1,194 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_select_similar.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <ksharedconfig.h>
#include <KoColorSpace.h>
#include <kis_cursor.h>
#include <KoPointerEvent.h>
#include <kis_selection_options.h>
#include <kis_paint_device.h>
#include "kis_canvas2.h"
#include <kis_pixel_selection.h>
#include "kis_selection_tool_helper.h"
#include "kis_slider_spin_box.h"
#include "kis_iterator_ng.h"
#include "kis_image.h"
void selectByColor(KisPaintDeviceSP dev, KisPixelSelectionSP selection, const quint8 *c, int fuzziness, const QRect & rc)
{
if (rc.isEmpty()) {
return;
}
// XXX: Multithread this!
qint32 x, y, w, h;
x = rc.x();
y = rc.y();
w = rc.width();
h = rc.height();
const KoColorSpace * cs = dev->colorSpace();
KisHLineConstIteratorSP hiter = dev->createHLineConstIteratorNG(x, y, w);
KisHLineIteratorSP selIter = selection->createHLineIteratorNG(x, y, w);
for (int row = y; row < y + h; ++row) {
do {
//if (dev->colorSpace()->hasAlpha())
// opacity = dev->colorSpace()->alpha(hiter->rawData());
if (fuzziness == 1) {
if (memcmp(c, hiter->oldRawData(), cs->pixelSize()) == 0) {
*(selIter->rawData()) = MAX_SELECTED;
}
}
else {
quint8 match = cs->difference(c, hiter->oldRawData());
if (match <= fuzziness) {
*(selIter->rawData()) = MAX_SELECTED;
}
}
}
while (hiter->nextPixel() && selIter->nextPixel());
hiter->nextRow();
selIter->nextRow();
}
}
KisToolSelectSimilar::KisToolSelectSimilar(KoCanvasBase * canvas)
: KisToolSelect(canvas,
KisCursor::load("tool_similar_selection_cursor.png", 6, 6),
i18n("Similar Color Selection")),
m_fuzziness(20)
{
}
void KisToolSelectSimilar::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisToolSelect::activate(toolActivation, shapes);
+ if (selectionOptionWidget()) {
+ // similar color selection tool doesn't use antialiasing option for now
+ // hence explicit disabling it
+ selectionOptionWidget()->disableAntiAliasSelectionOption();
+ }
m_configGroup = KSharedConfig::openConfig()->group(toolId());
}
void KisToolSelectSimilar::beginPrimaryAction(KoPointerEvent *event)
{
KisToolSelectBase::beginPrimaryAction(event);
KisPaintDeviceSP dev;
if (!currentNode() ||
!(dev = currentNode()->projection()) ||
!currentNode()->visible() ||
!selectionEditable()) {
event->ignore();
return;
}
if (KisToolSelect::selectionDidMove()) {
return;
}
QPointF pos = convertToPixelCoord(event);
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
QApplication::setOverrideCursor(KisCursor::waitCursor());
KoColor c;
dev->pixel(pos.x(), pos.y(), &c);
// XXX we should make this configurable: "allow to select transparent"
// if (opacity > OPACITY_TRANSPARENT)
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
QRect rc;
if (dev->colorSpace()->difference(c.data(), dev->defaultPixel().data()) <= m_fuzziness) {
rc = image()->bounds();
} else {
rc = dev->exactBounds();
}
selectByColor(dev, tmpSel, c.data(), m_fuzziness, rc);
tmpSel->invalidateOutlineCache();
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Similar Color"));
helper.selectPixelSelection(tmpSel, selectionAction());
QApplication::restoreOverrideCursor();
}
void KisToolSelectSimilar::slotSetFuzziness(int fuzziness)
{
m_fuzziness = fuzziness;
m_configGroup.writeEntry("fuzziness", fuzziness);
}
QWidget* KisToolSelectSimilar::createOptionWidget()
{
KisToolSelectBase::createOptionWidget();
KisSelectionOptions *selectionWidget = selectionOptionWidget();
+ // similar color selection tool doesn't use antialiasing option for now
+ // hence explicit disabling it
selectionWidget->disableAntiAliasSelectionOption();
- selectionWidget->disableSelectionModeOption();
QHBoxLayout* fl = new QHBoxLayout();
QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget);
fl->addWidget(lbl);
KisSliderSpinBox* input = new KisSliderSpinBox(selectionWidget);
input->setObjectName("fuzziness");
- input->setRange(0, 200);
+ input->setRange(1, 200);
input->setSingleStep(10);
fl->addWidget(input);
connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int)));
QVBoxLayout* l = dynamic_cast<QVBoxLayout*>(selectionWidget->layout());
Q_ASSERT(l);
l->insertLayout(1, fl);
// load setting from config
input->setValue(m_configGroup.readEntry("fuzziness", 20));
return selectionWidget;
}
void KisToolSelectSimilar::resetCursorStyle()
{
if (selectionAction() == SELECTION_ADD) {
useCursor(KisCursor::load("tool_similar_selection_cursor_add.png", 6, 6));
} else if (selectionAction() == SELECTION_SUBTRACT) {
useCursor(KisCursor::load("tool_similar_selection_cursor_sub.png", 6, 6));
} else {
KisToolSelect::resetCursorStyle();
}
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_similar.h b/plugins/tools/selectiontools/kis_tool_select_similar.h
index 06207ffdb3..c02fa766b7 100644
--- a/plugins/tools/selectiontools/kis_tool_select_similar.h
+++ b/plugins/tools/selectiontools/kis_tool_select_similar.h
@@ -1,75 +1,76 @@
/*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_SELECT_SIMILAR_H_
#define KIS_TOOL_SELECT_SIMILAR_H_
#include <KisSelectionToolFactoryBase.h>
#include <kis_icon.h>
#include <kconfig.h>
#include "kis_tool_select_base.h"
#include <kconfiggroup.h>
/*
* Tool to select colors by pointing at a color on the image.
*/
class KisToolSelectSimilar: public KisToolSelect
{
Q_OBJECT
public:
KisToolSelectSimilar(KoCanvasBase * canvas);
void beginPrimaryAction(KoPointerEvent *event) override;
void paint(QPainter&, const KoViewConverter &) override {}
QWidget* createOptionWidget() override;
void resetCursorStyle() override;
public Q_SLOTS:
void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) override;
void slotSetFuzziness(int);
protected:
using KisToolSelectBase::m_widgetHelper;
+ bool isPixelOnly() const override { return true; }
private:
int m_fuzziness;
KConfigGroup m_configGroup;
};
class KisToolSelectSimilarFactory : public KisSelectionToolFactoryBase
{
public:
KisToolSelectSimilarFactory()
: KisSelectionToolFactoryBase("KisToolSelectSimilar")
{
setToolTip(i18n("Similar Color Selection Tool"));
setSection(TOOL_TYPE_SELECTION);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
setIconName(koIconNameCStr("tool_similar_selection"));
setPriority(5);
}
~KisToolSelectSimilarFactory() override {}
KoToolBase * createTool(KoCanvasBase *canvas) override {
return new KisToolSelectSimilar(canvas);
}
};
#endif // KIS_TOOL_SELECT_SIMILAR_H_
diff --git a/plugins/tools/svgtexttool/SvgRichTextCtrl.cpp b/plugins/tools/svgtexttool/SvgRichTextCtrl.cpp
index 5d36ba38a3..bebcbc0f24 100644
--- a/plugins/tools/svgtexttool/SvgRichTextCtrl.cpp
+++ b/plugins/tools/svgtexttool/SvgRichTextCtrl.cpp
@@ -1,38 +1,37 @@
/* This file is part of the KDE project
*
* Copyright 2018 Mehmet Salih Çalışkan <msalihcaliskan@gmail.com>
*
* 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 "SvgRichTextCtrl.h"
#include <QMimeData>
SvgRichTextCtrl::SvgRichTextCtrl(QWidget* parent /*= nullptr*/)
: QTextEdit(parent)
{
- setStyleSheet("background-color:white");
}
void SvgRichTextCtrl::insertFromMimeData(const QMimeData *source)
{
if (!source->hasHtml() && source->hasText()) {
QTextCursor cursor = textCursor();
cursor.insertText(source->text());
} else {
QTextEdit::insertFromMimeData(source);
}
}
diff --git a/plugins/tools/svgtexttool/SvgRichTextCtrl.h b/plugins/tools/svgtexttool/SvgRichTextCtrl.h
index 9409120d9b..e391ff3aa7 100644
--- a/plugins/tools/svgtexttool/SvgRichTextCtrl.h
+++ b/plugins/tools/svgtexttool/SvgRichTextCtrl.h
@@ -1,36 +1,35 @@
/* This file is part of the KDE project
*
* Copyright 2018 Mehmet Salih Çalışkan <msalihcaliskan@gmail.com>
*
* 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 SVGRICHTEXTCTRL_H
#define SVGRICHTEXTCTRL_H
#include <QTextEdit>
class SvgRichTextCtrl : public QTextEdit
{
public:
SvgRichTextCtrl(QWidget* parent = nullptr);
-
protected:
void insertFromMimeData(const QMimeData* source) override;
};
#endif // SVGRICHTEXTCTRL_H
diff --git a/plugins/tools/svgtexttool/SvgTextEditor.cpp b/plugins/tools/svgtexttool/SvgTextEditor.cpp
index 680df71bac..75e4da1ef8 100644
--- a/plugins/tools/svgtexttool/SvgTextEditor.cpp
+++ b/plugins/tools/svgtexttool/SvgTextEditor.cpp
@@ -1,1149 +1,1195 @@
/* This file is part of the KDE project
*
* Copyright 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "SvgTextEditor.h"
#include <QAction>
#include <QApplication>
#include <QBuffer>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFontComboBox>
#include <QFontDatabase>
#include <QFormLayout>
#include <QLineEdit>
#include <QListView>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QPalette>
#include <QPushButton>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QSvgGenerator>
#include <QTabWidget>
#include <QTextEdit>
#include <QUrl>
#include <QVBoxLayout>
#include <QWidgetAction>
#include <kcharselect.h>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <kactioncollection.h>
#include <kxmlguifactory.h>
#include <ktoolbar.h>
#include <ktoggleaction.h>
#include <kguiitem.h>
#include <KoDialog.h>
#include <KoResourcePaths.h>
#include <KoSvgTextShape.h>
#include <KoSvgTextShapeMarkupConverter.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorPopupAction.h>
#include <svg/SvgUtil.h>
#include <KisScreenColorPicker.h>
#include <kis_icon.h>
#include <kis_config.h>
#include <kis_file_name_requester.h>
#include <kis_action_registry.h>
#include "kis_font_family_combo_box.h"
#include "FontSizeAction.h"
#include "kis_signals_blocker.h"
SvgTextEditor::SvgTextEditor(QWidget *parent, Qt::WindowFlags flags)
: KXmlGuiWindow(parent, flags)
, m_page(new QWidget(this))
#ifndef Q_OS_WIN
, m_charSelectDialog(new KoDialog(this))
#endif
{
m_textEditorWidget.setupUi(m_page);
setCentralWidget(m_page);
m_textEditorWidget.chkVertical->setVisible(false);
#ifndef Q_OS_WIN
KCharSelect *charSelector = new KCharSelect(m_charSelectDialog, 0, KCharSelect::AllGuiElements);
m_charSelectDialog->setMainWidget(charSelector);
connect(charSelector, SIGNAL(currentCharChanged(QChar)), SLOT(insertCharacter(QChar)));
m_charSelectDialog->hide();
m_charSelectDialog->setButtons(KoDialog::Close);
#endif
connect(m_textEditorWidget.buttons, SIGNAL(accepted()), this, SLOT(save()));
connect(m_textEditorWidget.buttons, SIGNAL(rejected()), this, SLOT(slotCloseEditor()));
connect(m_textEditorWidget.buttons, SIGNAL(clicked(QAbstractButton*)), this, SLOT(dialogButtonClicked(QAbstractButton*)));
KConfigGroup cg(KSharedConfig::openConfig(), "SvgTextTool");
actionCollection()->setConfigGroup("SvgTextTool");
actionCollection()->setComponentName("svgtexttool");
actionCollection()->setComponentDisplayName(i18n("Text Tool"));
QByteArray state;
if (cg.hasKey("WindowState")) {
state = cg.readEntry("State", state);
state = QByteArray::fromBase64(state);
// One day will need to load the version number, but for now, assume 0
restoreState(state);
}
setAcceptDrops(true);
//setStandardToolBarMenuEnabled(true);
#ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true);
#endif
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
m_syntaxHighlighter = new BasicXMLSyntaxHighlighter(m_textEditorWidget.svgTextEdit);
m_textEditorWidget.svgTextEdit->setFont(QFontDatabase().systemFont(QFontDatabase::FixedFont));
createActions();
// If we have customized the toolbars, load that first
setLocalXMLFile(KoResourcePaths::locateLocal("data", "svgtexttool.xmlgui"));
setXMLFile(":/kxmlgui5/svgtexttool.xmlgui");
guiFactory()->addClient(this);
// Create and plug toolbar list for Settings menu
QList<QAction *> toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast<KToolBar *>(it);
if (toolBar) {
toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
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);
}
}
plugActionList("toolbarlist", toolbarList);
connect(m_textEditorWidget.textTab, SIGNAL(currentChanged(int)), this, SLOT(switchTextEditorTab()));
switchTextEditorTab();
m_textEditorWidget.richTextEdit->document()->setDefaultStyleSheet("p {margin:0px;}");
applySettings();
}
SvgTextEditor::~SvgTextEditor()
{
KConfigGroup g(KSharedConfig::openConfig(), "SvgTextTool");
QByteArray ba = saveState();
g.writeEntry("windowState", ba.toBase64());
}
void SvgTextEditor::setShape(KoSvgTextShape *shape)
{
m_shape = shape;
if (m_shape) {
KoSvgTextShapeMarkupConverter converter(m_shape);
QString svg;
QString styles;
QTextDocument *doc = m_textEditorWidget.richTextEdit->document();
if (converter.convertToSvg(&svg, &styles)) {
m_textEditorWidget.svgTextEdit->setPlainText(svg);
m_textEditorWidget.svgStylesEdit->setPlainText(styles);
m_textEditorWidget.svgTextEdit->document()->setModified(false);
if (shape->isRichTextPreferred() &&
converter.convertSvgToDocument(svg, doc)) {
m_textEditorWidget.richTextEdit->setDocument(doc);
KisSignalsBlocker b(m_textEditorWidget.textTab);
m_textEditorWidget.textTab->setCurrentIndex(Richtext);
doc->clearUndoRedoStacks();
switchTextEditorTab(false);
} else {
KisSignalsBlocker b(m_textEditorWidget.textTab);
m_textEditorWidget.textTab->setCurrentIndex(SvgSource);
switchTextEditorTab(false);
}
}
else {
QMessageBox::warning(this, i18n("Conversion failed"), "Could not get svg text from the shape:\n" + converter.errors().join('\n') + "\n" + converter.warnings().join('\n'));
}
}
}
void SvgTextEditor::save()
{
if (m_shape) {
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QString svg;
QString styles = m_textEditorWidget.svgStylesEdit->document()->toPlainText();
KoSvgTextShapeMarkupConverter converter(m_shape);
if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) {
qWarning()<<"new converter doesn't work!";
}
m_textEditorWidget.richTextEdit->document()->setModified(false);
emit textUpdated(m_shape, svg, styles, true);
}
else {
emit textUpdated(m_shape, m_textEditorWidget.svgTextEdit->document()->toPlainText(), m_textEditorWidget.svgStylesEdit->document()->toPlainText(), false);
m_textEditorWidget.svgTextEdit->document()->setModified(false);
}
}
}
void SvgTextEditor::switchTextEditorTab(bool convertData)
{
KoSvgTextShape shape;
KoSvgTextShapeMarkupConverter converter(&shape);
if (m_currentEditor) {
disconnect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setModified(bool)));
}
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
//first, make buttons checkable
enableRichTextActions(true);
enableSvgTextActions(false);
//then connect the cursor change to the checkformat();
connect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat()));
if (m_shape && convertData) {
QTextDocument *doc = m_textEditorWidget.richTextEdit->document();
if (!converter.convertSvgToDocument(m_textEditorWidget.svgTextEdit->document()->toPlainText(), doc)) {
qWarning()<<"new converter svgToDoc doesn't work!";
}
m_textEditorWidget.richTextEdit->setDocument(doc);
doc->clearUndoRedoStacks();
}
m_currentEditor = m_textEditorWidget.richTextEdit;
}
else {
//first, make buttons uncheckable
enableRichTextActions(false);
enableSvgTextActions(true);
disconnect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat()));
// Convert the rich text to svg and styles strings
if (m_shape && convertData) {
QString svg;
QString styles;
if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) {
qWarning()<<"new converter docToSVG doesn't work!";
}
m_textEditorWidget.svgTextEdit->setPlainText(svg);
}
m_currentEditor = m_textEditorWidget.svgTextEdit;
}
connect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), SLOT(setModified(bool)));
}
void SvgTextEditor::checkFormat()
{
QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat();
QTextBlockFormat blockFormat = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
+ /**
+ * Make sure that when we remove the very last symbol, the last used font will not change
+ */
+ m_textEditorWidget.richTextEdit->document()->setDefaultFont(format.font());
+
// checkboxes do not emit signals on manual switching, so we
// can avoid blocking them
if (format.fontWeight() > QFont::Normal) {
actionCollection()->action("svg_weight_bold")->setChecked(true);
} else {
actionCollection()->action("svg_weight_bold")->setChecked(false);
}
actionCollection()->action("svg_format_italic")->setChecked(format.fontItalic());
actionCollection()->action("svg_format_underline")->setChecked(format.fontUnderline());
actionCollection()->action("svg_format_strike_through")->setChecked(format.fontStrikeOut());
{
FontSizeAction *fontSizeAction = qobject_cast<FontSizeAction*>(actionCollection()->action("svg_font_size"));
KisSignalsBlocker b(fontSizeAction);
fontSizeAction->setFontSize(format.font().pointSize());
}
{
KoColor fg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8());
KoColorPopupAction *fgColorPopup = qobject_cast<KoColorPopupAction*>(actionCollection()->action("svg_format_textcolor"));
KisSignalsBlocker b(fgColorPopup);
fgColorPopup->setCurrentColor(fg);
}
{
KoColor bg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8());
KoColorPopupAction *bgColorPopup = qobject_cast<KoColorPopupAction*>(actionCollection()->action("svg_background_color"));
KisSignalsBlocker b(bgColorPopup);
bgColorPopup->setCurrentColor(bg);
}
{
KisFontComboBoxes* fontComboBox = qobject_cast<KisFontComboBoxes*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_font"))->defaultWidget());
KisSignalsBlocker b(fontComboBox);
fontComboBox->setCurrentFont(format.font());
}
{
QDoubleSpinBox *spnLineHeight = qobject_cast<QDoubleSpinBox*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_line_height"))->defaultWidget());
KisSignalsBlocker b(spnLineHeight);
if (blockFormat.lineHeightType() == QTextBlockFormat::SingleHeight) {
spnLineHeight->setValue(100.0);
} else if(blockFormat.lineHeightType() == QTextBlockFormat::ProportionalHeight) {
spnLineHeight->setValue(double(blockFormat.lineHeight()));
}
}
+
+ {
+ QDoubleSpinBox* spnLetterSpacing = qobject_cast<QDoubleSpinBox*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_letter_spacing"))->defaultWidget());
+ KisSignalsBlocker b(spnLetterSpacing);
+ spnLetterSpacing->setValue(format.fontLetterSpacing());
+ }
}
void SvgTextEditor::undo()
{
m_currentEditor->undo();
}
void SvgTextEditor::redo()
{
m_currentEditor->redo();
}
void SvgTextEditor::cut()
{
m_currentEditor->cut();
}
void SvgTextEditor::copy()
{
m_currentEditor->copy();
}
void SvgTextEditor::paste()
{
m_currentEditor->paste();
}
void SvgTextEditor::selectAll()
{
m_currentEditor->selectAll();
}
void SvgTextEditor::deselect()
{
QTextCursor cursor(m_currentEditor->textCursor());
cursor.clearSelection();
m_currentEditor->setTextCursor(cursor);
}
void SvgTextEditor::find()
{
QDialog *findDialog = new QDialog(this);
findDialog->setWindowTitle(i18n("Find Text"));
QFormLayout *layout = new QFormLayout();
findDialog->setLayout(layout);
QLineEdit *lnSearchKey = new QLineEdit();
layout->addRow(i18n("Find:"), lnSearchKey);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
findDialog->layout()->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject()));
if (findDialog->exec()==QDialog::Accepted) {
m_searchKey = lnSearchKey->text();
m_currentEditor->find(m_searchKey);
}
}
void SvgTextEditor::findNext()
{
if (!m_currentEditor->find(m_searchKey)) {
QTextCursor cursor(m_currentEditor->textCursor());
cursor.movePosition(QTextCursor::Start);
m_currentEditor->setTextCursor(cursor);
m_currentEditor->find(m_searchKey);
}
}
void SvgTextEditor::findPrev()
{
if (!m_currentEditor->find(m_searchKey,QTextDocument::FindBackward)) {
QTextCursor cursor(m_currentEditor->textCursor());
cursor.movePosition(QTextCursor::End);
m_currentEditor->setTextCursor(cursor);
m_currentEditor->find(m_searchKey,QTextDocument::FindBackward);
}
}
void SvgTextEditor::replace()
{
QDialog *findDialog = new QDialog(this);
findDialog->setWindowTitle(i18n("Find and Replace all"));
QFormLayout *layout = new QFormLayout();
findDialog->setLayout(layout);
QLineEdit *lnSearchKey = new QLineEdit();
QLineEdit *lnReplaceKey = new QLineEdit();
layout->addRow(i18n("Find:"), lnSearchKey);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
layout->addRow(i18n("Replace:"), lnReplaceKey);
findDialog->layout()->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject()));
if (findDialog->exec()==QDialog::Accepted) {
QString search = lnSearchKey->text();
QString replace = lnReplaceKey->text();
QTextCursor cursor(m_currentEditor->textCursor());
cursor.movePosition(QTextCursor::Start);
m_currentEditor->setTextCursor(cursor);
while(m_currentEditor->find(search)) {
m_currentEditor->textCursor().removeSelectedText();
m_currentEditor->textCursor().insertText(replace);
}
}
}
void SvgTextEditor::zoomOut()
{
m_currentEditor->zoomOut();
}
void SvgTextEditor::zoomIn()
{
m_currentEditor->zoomIn();
}
#ifndef Q_OS_WIN
void SvgTextEditor::showInsertSpecialCharacterDialog()
{
m_charSelectDialog->setVisible(!m_charSelectDialog->isVisible());
}
void SvgTextEditor::insertCharacter(const QChar &c)
{
m_currentEditor->textCursor().insertText(QString(c));
}
#endif
void SvgTextEditor::setTextBold(QFont::Weight weight)
{
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCharFormat format;
QTextCursor oldCursor = setTextSelection();
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() > QFont::Normal && weight==QFont::Bold) {
format.setFontWeight(QFont::Normal);
} else {
format.setFontWeight(weight);
}
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
} else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-weight:700;\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setTextWeightLight()
{
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() < QFont::Normal) {
setTextBold(QFont::Normal);
} else {
setTextBold(QFont::Light);
}
}
void SvgTextEditor::setTextWeightNormal()
{
setTextBold(QFont::Normal);
}
void SvgTextEditor::setTextWeightDemi()
{
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() != QFont::Normal) {
setTextBold(QFont::Normal);
} else {
setTextBold(QFont::DemiBold);
}
}
void SvgTextEditor::setTextWeightBlack()
{
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight()>QFont::Normal) {
setTextBold(QFont::Normal);
} else {
setTextBold(QFont::Black);
}
}
void SvgTextEditor::setTextItalic(QFont::Style style)
{
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
QString fontStyle = "inherit";
if (style == QFont::StyleItalic) {
fontStyle = "italic";
} else if(style == QFont::StyleOblique) {
fontStyle = "oblique";
}
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCharFormat format;
QTextCursor origCursor = setTextSelection();
format.setFontItalic(!m_textEditorWidget.richTextEdit->textCursor().charFormat().fontItalic());
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(origCursor);
}
else {
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-style:"+fontStyle+";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setTextDecoration(KoSvgText::TextDecoration decor)
{
QTextCursor cursor = setTextSelection();
QTextCharFormat currentFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat();
QTextCharFormat format;
QString textDecoration = "inherit";
if (decor == KoSvgText::DecorationUnderline) {
textDecoration = "underline";
if (currentFormat.fontUnderline()) {
format.setFontUnderline(false);
}
else {
format.setFontUnderline(true);
}
format.setFontOverline(false);
format.setFontStrikeOut(false);
}
else if (decor == KoSvgText::DecorationLineThrough) {
textDecoration = "line-through";
format.setFontUnderline(false);
format.setFontOverline(false);
if (currentFormat.fontStrikeOut()) {
format.setFontStrikeOut(false);
}
else {
format.setFontStrikeOut(true);
}
}
else if (decor == KoSvgText::DecorationOverline) {
textDecoration = "overline";
format.setFontUnderline(false);
if (currentFormat.fontOverline()) {
format.setFontOverline(false);
}
else {
format.setFontOverline(true);
}
format.setFontStrikeOut(false);
}
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
}
else {
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"text-decoration:" + textDecoration + ";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
m_textEditorWidget.richTextEdit->setTextCursor(cursor);
}
void SvgTextEditor::setTextUnderline()
{
setTextDecoration(KoSvgText::DecorationUnderline);
}
void SvgTextEditor::setTextOverline()
{
setTextDecoration(KoSvgText::DecorationOverline);
}
void SvgTextEditor::setTextStrikethrough()
{
setTextDecoration(KoSvgText::DecorationLineThrough);
}
void SvgTextEditor::setTextSubscript()
{
QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat();
if (format.verticalAlignment()==QTextCharFormat::AlignSubScript) {
format.setVerticalAlignment(QTextCharFormat::AlignNormal);
} else {
format.setVerticalAlignment(QTextCharFormat::AlignSubScript);
}
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
}
void SvgTextEditor::setTextSuperScript()
{
QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat();
if (format.verticalAlignment()==QTextCharFormat::AlignSuperScript) {
format.setVerticalAlignment(QTextCharFormat::AlignNormal);
} else {
format.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
}
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
}
void SvgTextEditor::increaseTextSize()
{
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize();
if (pointSize<0) {
pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize();
}
format.setFontPointSize(pointSize+1.0);
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::decreaseTextSize()
{
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize();
if (pointSize<1) {
pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize();
}
format.setFontPointSize(qMax(pointSize-1.0, 1.0));
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::setLineHeight(double lineHeightPercentage)
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setLineHeight(lineHeightPercentage, QTextBlockFormat::ProportionalHeight);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
+void SvgTextEditor::setLetterSpacing(double letterSpacing)
+{
+ QTextCursor cursor = setTextSelection();
+ if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
+ QTextCharFormat format;
+ format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
+ format.setFontLetterSpacing(letterSpacing);
+ m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
+ m_textEditorWidget.richTextEdit->setTextCursor(cursor);
+ }
+ else {
+ if (cursor.hasSelection()) {
+ QString selectionModified = "<tspan style=\"letter-spacing:" + QString::number(letterSpacing) + "\">" + cursor.selectedText() + "</tspan>";
+ cursor.removeSelectedText();
+ cursor.insertText(selectionModified);
+ }
+ }
+}
void SvgTextEditor::alignLeft()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignLeft);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::alignRight()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignRight);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::alignCenter()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignCenter);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::alignJustified()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignJustify);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::setSettings()
{
KoDialog settingsDialog(this);
Ui_WdgSvgTextSettings textSettings;
QWidget *settingsPage = new QWidget(&settingsDialog, 0);
settingsDialog.setMainWidget(settingsPage);
textSettings.setupUi(settingsPage);
// get the settings and initialize the dialog
KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool");
QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(",");
QList<QFontDatabase::WritingSystem> scripts = QFontDatabase().writingSystems();
QStandardItemModel *writingSystemsModel = new QStandardItemModel(&settingsDialog);
for (int s = 0; s < scripts.size(); s ++) {
QString writingSystem = QFontDatabase().writingSystemName(scripts.at(s));
QStandardItem *script = new QStandardItem(writingSystem);
script->setCheckable(true);
script->setCheckState(selectedWritingSystems.contains(QString::number(scripts.at(s))) ? Qt::Checked : Qt::Unchecked);
script->setData((int)scripts.at(s));
writingSystemsModel->appendRow(script);
}
textSettings.lwScripts->setModel(writingSystemsModel);
EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both);
switch(mode) {
case(RichText):
textSettings.radioRichText->setChecked(true);
break;
case(SvgSource):
textSettings.radioSvgSource->setChecked(true);
break;
case(Both):
textSettings.radioBoth->setChecked(true);
}
QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().background().color());
textSettings.colorEditorBackground->setColor(background);
textSettings.colorEditorForeground->setColor(cfg.readEntry("colorEditorForeground", qApp->palette().text().color()));
textSettings.colorKeyword->setColor(cfg.readEntry("colorKeyword", QColor(background.value() < 100 ? Qt::cyan : Qt::blue)));
textSettings.chkBoldKeyword->setChecked(cfg.readEntry("BoldKeyword", true));
textSettings.chkItalicKeyword->setChecked(cfg.readEntry("ItalicKeyword", false));
textSettings.colorElement->setColor(cfg.readEntry("colorElement", QColor(background.value() < 100 ? Qt::magenta : Qt::darkMagenta)));
textSettings.chkBoldElement->setChecked(cfg.readEntry("BoldElement", true));
textSettings.chkItalicElement->setChecked(cfg.readEntry("ItalicElement", false));
textSettings.colorAttribute->setColor(cfg.readEntry("colorAttribute", QColor(background.value() < 100 ? Qt::green : Qt::darkGreen)));
textSettings.chkBoldAttribute->setChecked(cfg.readEntry("BoldAttribute", true));
textSettings.chkItalicAttribute->setChecked(cfg.readEntry("ItalicAttribute", true));
textSettings.colorValue->setColor(cfg.readEntry("colorValue", QColor(background.value() < 100 ? Qt::red: Qt::darkRed)));
textSettings.chkBoldValue->setChecked(cfg.readEntry("BoldValue", true));
textSettings.chkItalicValue->setChecked(cfg.readEntry("ItalicValue", false));
textSettings.colorComment->setColor(cfg.readEntry("colorComment", QColor(background.value() < 100 ? Qt::lightGray : Qt::gray)));
textSettings.chkBoldComment->setChecked(cfg.readEntry("BoldComment", false));
textSettings.chkItalicComment->setChecked(cfg.readEntry("ItalicComment", false));
settingsDialog.setButtons(KoDialog::Ok | KoDialog::Cancel);
if (settingsDialog.exec() == QDialog::Accepted) {
// save and set the settings
QStringList writingSystems;
for (int i = 0; i < writingSystemsModel->rowCount(); i++) {
QStandardItem *item = writingSystemsModel->item(i);
if (item->checkState() == Qt::Checked) {
writingSystems.append(QString::number(item->data().toInt()));
}
}
cfg.writeEntry("selectedWritingSystems", writingSystems.join(','));
if (textSettings.radioRichText->isChecked()) {
cfg.writeEntry("EditorMode", (int)Richtext);
}
else if (textSettings.radioSvgSource->isChecked()) {
cfg.writeEntry("EditorMode", (int)SvgSource);
}
else if (textSettings.radioBoth->isChecked()) {
cfg.writeEntry("EditorMode", (int)Both);
}
cfg.writeEntry("colorEditorBackground", textSettings.colorEditorBackground->color());
cfg.writeEntry("colorEditorForeground", textSettings.colorEditorForeground->color());
cfg.writeEntry("colorKeyword", textSettings.colorKeyword->color());
cfg.writeEntry("BoldKeyword", textSettings.chkBoldKeyword->isChecked());
cfg.writeEntry("ItalicKeyWord", textSettings.chkItalicKeyword->isChecked());
cfg.writeEntry("colorElement", textSettings.colorElement->color());
cfg.writeEntry("BoldElement", textSettings.chkBoldElement->isChecked());
cfg.writeEntry("ItalicElement", textSettings.chkItalicElement->isChecked());
cfg.writeEntry("colorAttribute", textSettings.colorAttribute->color());
cfg.writeEntry("BoldAttribute", textSettings.chkBoldAttribute->isChecked());
cfg.writeEntry("ItalicAttribute", textSettings.chkItalicAttribute->isChecked());
cfg.writeEntry("colorValue", textSettings.colorValue->color());
cfg.writeEntry("BoldValue", textSettings.chkBoldValue->isChecked());
cfg.writeEntry("ItalicValue", textSettings.chkItalicValue->isChecked());
cfg.writeEntry("colorComment", textSettings.colorComment->color());
cfg.writeEntry("BoldComment", textSettings.chkBoldComment->isChecked());
cfg.writeEntry("ItalicComment", textSettings.chkItalicComment->isChecked());
applySettings();
}
}
void SvgTextEditor::slotToolbarToggled(bool)
{
}
void SvgTextEditor::setFontColor(const KoColor &c)
{
QColor color = c.toQColor();
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
format.setForeground(QBrush(color));
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan fill=\""+color.name()+"\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setBackgroundColor(const KoColor &c)
{
QColor color = c.toQColor();
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan stroke=\""+color.name()+"\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
void SvgTextEditor::setModified(bool modified)
{
if (modified) {
m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Discard);
}
else {
m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close);
}
}
void SvgTextEditor::dialogButtonClicked(QAbstractButton *button)
{
if (m_textEditorWidget.buttons->standardButton(button) == QDialogButtonBox::Discard) {
if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("You have modified the text. Discard changes?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
close();
}
}
}
void SvgTextEditor::setFont(const QString &fontName)
{
QFont font;
font.fromString(fontName);
QTextCharFormat curFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat();
font.setPointSize(curFormat.font().pointSize());
QTextCharFormat format;
//This disables the style being set from the font-comboboxes too, so we need to rethink how we use that.
format.setFontFamily(font.family());
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCursor oldCursor = setTextSelection();
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
} else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-family:"+font.family()+" "+font.styleName()+";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setFontSize(qreal fontSize)
{
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
format.setFontPointSize(fontSize);
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
} else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-size:" + QString::number(fontSize) + ";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setBaseline(KoSvgText::BaselineShiftMode)
{
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-size:50%;baseline-shift:super;\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
void SvgTextEditor::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
int numDegrees = event->delta() / 8;
int numSteps = numDegrees / 7;
m_textEditorWidget.svgTextEdit->zoomOut(numSteps);
event->accept();
}
}
QTextCursor SvgTextEditor::setTextSelection()
{
QTextCursor orignalCursor(m_textEditorWidget.richTextEdit->textCursor());
if (!orignalCursor.hasSelection()){
m_textEditorWidget.richTextEdit->selectAll();
}
return orignalCursor;
}
void SvgTextEditor::applySettings()
{
KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool");
EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both);
QWidget *richTab = m_textEditorWidget.richTab;
QWidget *svgTab = m_textEditorWidget.svgTab;
m_page->setUpdatesEnabled(false);
m_textEditorWidget.textTab->clear();
switch(mode) {
case(RichText):
m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text"));
break;
case(SvgSource):
m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source"));
break;
case(Both):
m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text"));
m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source"));
}
m_syntaxHighlighter->setFormats();
QPalette palette = m_textEditorWidget.svgTextEdit->palette();
QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().background().color());
palette.setBrush(QPalette::Active, QPalette::Background, QBrush(background));
+ m_textEditorWidget.richTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name()));
+ m_textEditorWidget.svgStylesEdit->setStyleSheet(QString("background-color:%1").arg(background.name()));
+ m_textEditorWidget.svgTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name()));
+
QColor foreground = cfg.readEntry("colorEditorForeground", qApp->palette().text().color());
palette.setBrush(QPalette::Active, QPalette::Text, QBrush(foreground));
QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(",");
QVector<QFontDatabase::WritingSystem> writingSystems;
for (int i=0; i<selectedWritingSystems.size(); i++) {
writingSystems.append((QFontDatabase::WritingSystem)QString(selectedWritingSystems.at(i)).toInt());
}
qobject_cast<KisFontComboBoxes*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_font"))->defaultWidget())->refillComboBox(writingSystems);
+
+
m_page->setUpdatesEnabled(true);
}
QAction *SvgTextEditor::createAction(const QString &name, const char *member)
{
QAction *action = new QAction(this);
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
actionRegistry->propertizeAction(name, action);
actionCollection()->addAction(name, action);
QObject::connect(action, SIGNAL(triggered(bool)), this, member);
return action;
}
void SvgTextEditor::createActions()
{
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
// File: new, open, save, save as, close
KStandardAction::save(this, SLOT(save()), actionCollection());
KStandardAction::close(this, SLOT(slotCloseEditor()), actionCollection());
// Edit
KStandardAction::undo(this, SLOT(undo()), actionCollection());
KStandardAction::redo(this, SLOT(redo()), actionCollection());
KStandardAction::cut(this, SLOT(cut()), actionCollection());
KStandardAction::copy(this, SLOT(copy()), actionCollection());
KStandardAction::paste(this, SLOT(paste()), actionCollection());
KStandardAction::selectAll(this, SLOT(selectAll()), actionCollection());
KStandardAction::deselect(this, SLOT(deselect()), actionCollection());
KStandardAction::find(this, SLOT(find()), actionCollection());
KStandardAction::findNext(this, SLOT(findNext()), actionCollection());
KStandardAction::findPrev(this, SLOT(findPrev()), actionCollection());
KStandardAction::replace(this, SLOT(replace()), actionCollection());
// View
// WISH: we cannot zoom-in/out in rech-text mode
m_svgTextActions << KStandardAction::zoomOut(this, SLOT(zoomOut()), actionCollection());
m_svgTextActions << KStandardAction::zoomIn(this, SLOT(zoomIn()), actionCollection());
#ifndef Q_OS_WIN
// Insert:
QAction * insertAction = createAction("svg_insert_special_character",
SLOT(showInsertSpecialCharacterDialog()));
insertAction->setCheckable(true);
insertAction->setChecked(false);
#endif
// Format:
m_richTextActions << createAction("svg_weight_bold",
SLOT(setTextBold()));
m_richTextActions << createAction("svg_format_italic",
SLOT(setTextItalic()));
m_richTextActions << createAction("svg_format_underline",
SLOT(setTextUnderline()));
m_richTextActions << createAction("svg_format_strike_through",
SLOT(setTextStrikethrough()));
m_richTextActions << createAction("svg_format_superscript",
SLOT(setTextSuperScript()));
m_richTextActions << createAction("svg_format_subscript",
SLOT(setTextSubscript()));
m_richTextActions << createAction("svg_weight_light",
SLOT(setTextWeightLight()));
m_richTextActions << createAction("svg_weight_normal",
SLOT(setTextWeightNormal()));
m_richTextActions << createAction("svg_weight_demi",
SLOT(setTextWeightDemi()));
m_richTextActions << createAction("svg_weight_black",
SLOT(setTextWeightBlack()));
m_richTextActions << createAction("svg_increase_font_size",
SLOT(increaseTextSize()));
m_richTextActions << createAction("svg_decrease_font_size",
SLOT(decreaseTextSize()));
m_richTextActions << createAction("svg_align_left",
SLOT(alignLeft()));
m_richTextActions << createAction("svg_align_right",
SLOT(alignRight()));
m_richTextActions << createAction("svg_align_center",
SLOT(alignCenter()));
// m_richTextActions << createAction("svg_align_justified",
// SLOT(alignJustified()));
// Settings
m_richTextActions << createAction("svg_settings",
SLOT(setSettings()));
QWidgetAction *fontComboAction = new QWidgetAction(this);
fontComboAction->setToolTip(i18n("Font"));
KisFontComboBoxes *fontCombo = new KisFontComboBoxes();
connect(fontCombo, SIGNAL(fontChanged(QString)), SLOT(setFont(QString)));
fontComboAction->setDefaultWidget(fontCombo);
actionCollection()->addAction("svg_font", fontComboAction);
m_richTextActions << fontComboAction;
actionRegistry->propertizeAction("svg_font", fontComboAction);
QWidgetAction *fontSizeAction = new FontSizeAction(this);
fontSizeAction->setToolTip(i18n("Size"));
connect(fontSizeAction, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal)));
actionCollection()->addAction("svg_font_size", fontSizeAction);
m_richTextActions << fontSizeAction;
actionRegistry->propertizeAction("svg_font_size", fontSizeAction);
KoColorPopupAction *fgColor = new KoColorPopupAction(this);
fgColor->setCurrentColor(QColor(Qt::black));
fgColor->setToolTip(i18n("Text Color"));
connect(fgColor, SIGNAL(colorChanged(KoColor)), SLOT(setFontColor(KoColor)));
actionCollection()->addAction("svg_format_textcolor", fgColor);
m_richTextActions << fgColor;
actionRegistry->propertizeAction("svg_format_textcolor", fgColor);
KoColorPopupAction *bgColor = new KoColorPopupAction(this);
bgColor->setCurrentColor(QColor(Qt::white));
bgColor->setToolTip(i18n("Background Color"));
connect(bgColor, SIGNAL(colorChanged(KoColor)), SLOT(setBackgroundColor(KoColor)));
actionCollection()->addAction("svg_background_color", bgColor);
actionRegistry->propertizeAction("svg_background_color", bgColor);
m_richTextActions << bgColor;
QWidgetAction *colorPickerAction = new QWidgetAction(this);
colorPickerAction->setToolTip(i18n("Pick a Color"));
KisScreenColorPicker *colorPicker = new KisScreenColorPicker(false);
connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), fgColor, SLOT(setCurrentColor(KoColor)));
connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), SLOT(setFontColor(KoColor)));
colorPickerAction->setDefaultWidget(colorPicker);
actionCollection()->addAction("svg_pick_color", colorPickerAction);
m_richTextActions << colorPickerAction;
actionRegistry->propertizeAction("svg_pick_color", colorPickerAction);
QWidgetAction *lineHeight = new QWidgetAction(this);
QDoubleSpinBox *spnLineHeight = new QDoubleSpinBox();
spnLineHeight->setToolTip(i18n("Line height"));
spnLineHeight->setRange(0.0, 1000.0);
spnLineHeight->setSingleStep(10.0);
spnLineHeight->setSuffix("%");
connect(spnLineHeight, SIGNAL(valueChanged(double)), SLOT(setLineHeight(double)));
lineHeight->setDefaultWidget(spnLineHeight);
actionCollection()->addAction("svg_line_height", lineHeight);
m_richTextActions << lineHeight;
actionRegistry->propertizeAction("svg_line_height", lineHeight);
+
+ QWidgetAction *letterSpacing = new QWidgetAction(this);
+ QDoubleSpinBox *spnletterSpacing = new QDoubleSpinBox();
+ spnletterSpacing->setToolTip(i18n("Letter Spacing"));
+ spnletterSpacing->setRange(-20.0, 20.0);
+ spnletterSpacing->setSingleStep(0.5);
+ connect(spnletterSpacing, SIGNAL(valueChanged(double)), SLOT(setLetterSpacing(double)));
+ letterSpacing->setDefaultWidget(spnletterSpacing);
+ actionCollection()->addAction("svg_letter_spacing", letterSpacing);
+ m_richTextActions << letterSpacing;
+ actionRegistry->propertizeAction("svg_letter_spacing", letterSpacing);
}
void SvgTextEditor::enableRichTextActions(bool enable)
{
Q_FOREACH(QAction *action, m_richTextActions) {
action->setEnabled(enable);
}
}
void SvgTextEditor::enableSvgTextActions(bool enable)
{
Q_FOREACH(QAction *action, m_svgTextActions) {
action->setEnabled(enable);
}
}
void SvgTextEditor::slotCloseEditor()
{
close();
emit textEditorClosed();
}
diff --git a/plugins/tools/svgtexttool/SvgTextEditor.h b/plugins/tools/svgtexttool/SvgTextEditor.h
index 064156b25c..1e2a277340 100644
--- a/plugins/tools/svgtexttool/SvgTextEditor.h
+++ b/plugins/tools/svgtexttool/SvgTextEditor.h
@@ -1,168 +1,169 @@
/* This file is part of the KDE project
*
* Copyright 2017 Boudewijn Rempt <boud@valdyas.org>
*
* 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 TEXTNGSHAPECONFIGWIDGET_H
#define TEXTNGSHAPECONFIGWIDGET_H
#include <QWidget>
#include <QTextEdit>
#include <kxmlguiwindow.h>
#include <KoColor.h>
#include <KoSvgText.h>//for the enums
#include <BasicXMLSyntaxHighlighter.h>
#include "ui_WdgSvgTextEditor.h"
#include "ui_WdgSvgTextSettings.h"
class KoSvgTextShape;
class KoDialog;
class SvgTextEditor : public KXmlGuiWindow
{
Q_OBJECT
public:
SvgTextEditor(QWidget *parent = 0, Qt::WindowFlags flags = 0);
~SvgTextEditor();
//tiny enum to keep track of the tab on which editor something happens while keeping the code readable.
enum Editor {
Richtext, // 0
SVGsource // 1
};
// enum to store which tabs are visible in the configuration
enum EditorMode {
RichText,
SvgSource,
Both
};
void setShape(KoSvgTextShape *shape);
private Q_SLOTS:
/**
* switch the text editor tab.
*/
void switchTextEditorTab(bool convertData = true);
void slotCloseEditor();
/**
* in rich text, check the current format, and toggle the given buttons.
*/
void checkFormat();
void save();
void undo();
void redo();
void cut();
void copy();
void paste();
void selectAll();
void deselect();
void find();
void findNext();
void findPrev();
void replace();
void zoomOut();
void zoomIn();
#ifndef Q_OS_WIN
void showInsertSpecialCharacterDialog();
void insertCharacter(const QChar &c);
#endif
void setTextBold(QFont::Weight weight = QFont::Bold);
void setTextWeightLight();
void setTextWeightNormal();
void setTextWeightDemi();
void setTextWeightBlack();
void setTextItalic(QFont::Style style = QFont::StyleOblique);
void setTextDecoration(KoSvgText::TextDecoration decor);
void setTextUnderline();
void setTextOverline();
void setTextStrikethrough();
void setTextSubscript();
void setTextSuperScript();
void increaseTextSize();
void decreaseTextSize();
void setLineHeight(double lineHeightPercentage);
+ void setLetterSpacing(double letterSpacing);
void alignLeft();
void alignRight();
void alignCenter();
void alignJustified();
void setFont(const QString &fontName);
void setFontSize(qreal size);
void setBaseline(KoSvgText::BaselineShiftMode baseline);
void setSettings();
void slotToolbarToggled(bool);
void setFontColor(const KoColor &c);
void setBackgroundColor(const KoColor &c);
void setModified(bool modified);
void dialogButtonClicked(QAbstractButton *button);
Q_SIGNALS:
void textUpdated(KoSvgTextShape *shape, const QString &svg, const QString &defs, bool richTextPreferred);
void textEditorClosed();
protected:
void wheelEvent(QWheelEvent *event) override;
/**
* Selects all if there is no selection
* @returns a copy of the previous cursor.
*/
QTextCursor setTextSelection();
private:
void applySettings();
QAction *createAction(const QString &name,
const char *member);
void createActions();
void enableRichTextActions(bool enable);
void enableSvgTextActions(bool enable);
Ui_WdgSvgTextEditor m_textEditorWidget;
QTextEdit *m_currentEditor {0};
QWidget *m_page {0};
QList<QAction*> m_richTextActions;
QList<QAction*> m_svgTextActions;
KoSvgTextShape *m_shape {0};
#ifndef Q_OS_WIN
KoDialog *m_charSelectDialog {0};
#endif
BasicXMLSyntaxHighlighter *m_syntaxHighlighter;
QString m_searchKey;
};
#endif //TEXTNGSHAPECONFIGWIDGET_H
diff --git a/plugins/tools/svgtexttool/SvgTextTool.cpp b/plugins/tools/svgtexttool/SvgTextTool.cpp
index 5cf75edf5c..4548adca0d 100644
--- a/plugins/tools/svgtexttool/SvgTextTool.cpp
+++ b/plugins/tools/svgtexttool/SvgTextTool.cpp
@@ -1,429 +1,430 @@
/* This file is part of the KDE project
Copyright 2017 Boudewijn Rempt <boud@valdyas.org>
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 "SvgTextTool.h"
#include "KoSvgTextShape.h"
#include "SvgTextChangeCommand.h"
#include <QLabel>
#include <QToolButton>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QDesktopServices>
#include <QApplication>
#include <QGroupBox>
#include <QFontDatabase>
#include <QButtonGroup>
#include <klocalizedstring.h>
#include <KisPart.h>
#include <kis_canvas2.h>
#include <KSharedConfig>
#include "kis_assert.h"
#include <KoFileDialog.h>
#include <KoIcon.h>
#include <KoCanvasBase.h>
#include <KoImageCollection.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoShapeController.h>
#include <KoShapeRegistry.h>
#include <KoShapeFactoryBase.h>
#include <KoPointerEvent.h>
#include <KoProperties.h>
#include <KoSelectedShapesProxy.h>
#include "KoToolManager.h"
#include "KoCanvasResourceProvider.h"
#include "SvgTextEditor.h"
#include "KisHandlePainterHelper.h"
#include <commands/KoKeepShapesSelectedCommand.h>
SvgTextTool::SvgTextTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_editor(0)
, m_dragStart( 0, 0)
, m_dragEnd( 0, 0)
, m_dragging(false)
{
}
SvgTextTool::~SvgTextTool()
{
if(m_editor) {
m_editor->close();
}
}
void SvgTextTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
useCursor(Qt::ArrowCursor);
if (shapes.size() == 1) {
KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(*shapes.constBegin());
if (!textShape) {
koSelection()->deselectAll();
} else {
// if we are a text shape...and the proxy tells us we want to edit the shape. open the text editor
if (canvas()->selectedShapesProxy()->isRequestingToBeEdited()) {
showEditor();
}
}
} else if (shapes.size() > 1) {
KoSvgTextShape *foundTextShape = 0;
Q_FOREACH (KoShape *shape, shapes) {
KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
if (textShape) {
foundTextShape = textShape;
break;
}
}
koSelection()->deselectAll();
if (foundTextShape) {
koSelection()->select(foundTextShape);
}
}
}
void SvgTextTool::deactivate()
{
KoToolBase::deactivate();
QRectF updateRect = m_hoveredShapeHighlightRect;
KoSvgTextShape *shape = selectedShape();
if (shape) {
updateRect |= shape->boundingRect();
}
m_hoveredShapeHighlightRect = QRectF();
canvas()->updateCanvas(updateRect);
}
QWidget *SvgTextTool::createOptionWidget()
{
QWidget *optionWidget = new QWidget();
QGridLayout *layout = new QGridLayout(optionWidget);
m_configGroup = KSharedConfig::openConfig()->group(toolId());
QGroupBox *defsOptions = new QGroupBox(i18n("Create new texts with..."));
QVBoxLayout *defOptionsLayout = new QVBoxLayout();
defsOptions->setLayout(defOptionsLayout);
m_defFont = new QFontComboBox();
QString storedFont = m_configGroup.readEntry<QString>("defaultFont", QApplication::font().family());
m_defFont->setCurrentFont(QFont(storedFont));
defsOptions->layout()->addWidget(m_defFont);
m_defPointSize = new QComboBox();
Q_FOREACH (int size, QFontDatabase::standardSizes()) {
m_defPointSize->addItem(QString::number(size)+" pt");
}
int storedSize = m_configGroup.readEntry<int>("defaultSize", QApplication::font().pointSize());
m_defPointSize->setCurrentIndex(QFontDatabase::standardSizes().indexOf(storedSize));
int checkedAlignment = m_configGroup.readEntry<int>("defaultAlignment", 0);
m_defAlignment = new QButtonGroup();
QHBoxLayout *alignButtons = new QHBoxLayout();
alignButtons->addWidget(m_defPointSize);
QToolButton *alignLeft = new QToolButton();
alignLeft->setIcon(KisIconUtils::loadIcon("format-justify-left"));
alignLeft->setCheckable(true);
alignLeft->setToolTip(i18n("Anchor text to the left."));
m_defAlignment->addButton(alignLeft, 0);
alignButtons->addWidget(alignLeft);
QToolButton *alignCenter = new QToolButton();
alignCenter->setIcon(KisIconUtils::loadIcon("format-justify-center"));
alignCenter->setCheckable(true);
m_defAlignment->addButton(alignCenter, 1);
alignCenter->setToolTip(i18n("Anchor text to the middle."));
alignButtons->addWidget(alignCenter);
QToolButton *alignRight = new QToolButton();
alignRight->setIcon(KisIconUtils::loadIcon("format-justify-right"));
alignRight->setCheckable(true);
m_defAlignment->addButton(alignRight, 2);
alignRight->setToolTip(i18n("Anchor text to the right."));
alignButtons->addWidget(alignRight);
m_defAlignment->setExclusive(true);
if (checkedAlignment<1) {
alignLeft->setChecked(true);
} else if (checkedAlignment==1) {
alignCenter->setChecked(true);
} else if (checkedAlignment==2) {
alignRight->setChecked(true);
} else {
alignLeft->setChecked(true);
}
defOptionsLayout->addLayout(alignButtons);
layout->addWidget(defsOptions);
connect(m_defAlignment, SIGNAL(buttonClicked(int)), this, SLOT(storeDefaults()));
connect(m_defFont, SIGNAL(currentFontChanged(QFont)), this, SLOT(storeDefaults()));
connect(m_defPointSize, SIGNAL(currentIndexChanged(int)), this, SLOT(storeDefaults()));
m_edit = new QPushButton(optionWidget);
m_edit->setText(i18n("Edit Text"));
connect(m_edit, SIGNAL(clicked(bool)), SLOT(showEditor()));
layout->addWidget(m_edit);
return optionWidget;
}
KoSelection *SvgTextTool::koSelection() const
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0);
return canvas()->selectedShapesProxy()->selection();
}
KoSvgTextShape *SvgTextTool::selectedShape() const
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0);
QList<KoShape*> shapes = koSelection()->selectedEditableShapes();
if (shapes.isEmpty()) return 0;
KIS_SAFE_ASSERT_RECOVER_NOOP(shapes.size() == 1);
KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shapes.first());
return textShape;
}
void SvgTextTool::showEditor()
{
KoSvgTextShape *shape = selectedShape();
if (!shape) return;
if (!m_editor) {
m_editor = new SvgTextEditor(QApplication::activeWindow());
- m_editor->setWindowModality(Qt::NonModal);
+ m_editor->setWindowTitle(i18nc("@title:window", "Krita - Edit Text"));
+ m_editor->setWindowModality(Qt::ApplicationModal);
m_editor->setAttribute( Qt::WA_QuitOnClose, false );
connect(m_editor, SIGNAL(textUpdated(KoSvgTextShape*,QString,QString,bool)), SLOT(textUpdated(KoSvgTextShape*,QString,QString,bool)));
connect(m_editor, SIGNAL(textEditorClosed()), SLOT(slotTextEditorClosed()));
m_editor->activateWindow(); // raise on creation only
}
m_editor->setShape(shape);
m_editor->show();
}
void SvgTextTool::textUpdated(KoSvgTextShape *shape, const QString &svg, const QString &defs, bool richTextUpdated)
{
SvgTextChangeCommand *cmd = new SvgTextChangeCommand(shape, svg, defs, richTextUpdated);
canvas()->addCommand(cmd);
}
void SvgTextTool::slotTextEditorClosed()
{
// change tools to the shape selection tool when we close the text editor to allow moving and further editing of the object.
// most of the time when we edit text, the shape selection tool is where we left off anyway
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
QString SvgTextTool::generateDefs()
{
QString font = m_defFont->currentFont().family();
QString size = QString::number(QFontDatabase::standardSizes().at(m_defPointSize->currentIndex()));
QString textAnchor = "middle";
if (m_defAlignment->button(0)->isChecked()) {
textAnchor = "start";
}
if (m_defAlignment->button(2)->isChecked()) {
textAnchor = "end";
}
QString fontColor = canvas()->resourceManager()->foregroundColor().toQColor().name();
return QString("<defs>\n <style>\n text {\n font-family:'%1';\n font-size:%2 ; fill:%3 ; text-anchor:%4;\n }\n </style>\n</defs>").arg(font, size, fontColor, textAnchor);
}
void SvgTextTool::storeDefaults()
{
m_configGroup = KSharedConfig::openConfig()->group(toolId());
m_configGroup.writeEntry("defaultFont", m_defFont->currentFont().family());
m_configGroup.writeEntry("defaultSize", QFontDatabase::standardSizes().at(m_defPointSize->currentIndex()));
m_configGroup.writeEntry("defaultAlignment", m_defAlignment->checkedId());
}
void SvgTextTool::paint(QPainter &gc, const KoViewConverter &converter)
{
if (!isActivated()) return;
KoShape::applyConversion(gc, converter);
KisHandlePainterHelper handlePainter(&gc);
if (m_dragging) {
QPolygonF poly(QRectF(m_dragStart, m_dragEnd));
handlePainter.setHandleStyle(KisHandleStyle::primarySelection());
handlePainter.drawRubberLine(poly);
}
KoSvgTextShape *shape = selectedShape();
if (shape) {
handlePainter.setHandleStyle(KisHandleStyle::primarySelection());
QPainterPath path;
path.addRect(shape->boundingRect());
handlePainter.drawPath(path);
}
if (!m_hoveredShapeHighlightRect.isEmpty()) {
handlePainter.setHandleStyle(KisHandleStyle::highlightedPrimaryHandlesWithSolidOutline());
QPainterPath path;
path.addRect(m_hoveredShapeHighlightRect);
handlePainter.drawPath(path);
}
}
void SvgTextTool::mousePressEvent(KoPointerEvent *event)
{
KoSvgTextShape *selectedShape = this->selectedShape();
KoSvgTextShape *hoveredShape = dynamic_cast<KoSvgTextShape *>(canvas()->shapeManager()->shapeAt(event->point));
if (!selectedShape || hoveredShape != selectedShape) {
canvas()->shapeManager()->selection()->deselectAll();
if (hoveredShape) {
canvas()->shapeManager()->selection()->select(hoveredShape);
} else {
m_dragStart = m_dragEnd = event->point;
m_dragging = true;
event->accept();
}
}
}
void SvgTextTool::mouseMoveEvent(KoPointerEvent *event)
{
QRectF updateRect = m_hoveredShapeHighlightRect;
if (m_dragging) {
m_dragEnd = event->point;
m_hoveredShapeHighlightRect = QRectF();
updateRect |= QRectF(m_dragStart, m_dragEnd).normalized().toAlignedRect();
event->accept();
} else {
KoSvgTextShape *hoveredShape = dynamic_cast<KoSvgTextShape *>(canvas()->shapeManager()->shapeAt(event->point));
if (hoveredShape) {
m_hoveredShapeHighlightRect = hoveredShape->boundingRect();
updateRect |= m_hoveredShapeHighlightRect;
} else {
m_hoveredShapeHighlightRect = QRect();
}
event->ignore();
}
if (!updateRect.isEmpty()) {
canvas()->updateCanvas(kisGrowRect(updateRect, 100));
}
}
void SvgTextTool::mouseReleaseEvent(KoPointerEvent *event)
{
if (m_dragging) {
QRectF rectangle = QRectF(m_dragStart, m_dragEnd).normalized();
if (rectangle.width() < 4 && rectangle.height() < 4) {
m_dragging = false;
event->accept();
return;
}
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID");
KoProperties *params = new KoProperties();//Fill these with "svgText", "defs" and "shapeRect"
params->setProperty("defs", QVariant(generateDefs()));
if (m_dragging) {
m_dragEnd = event->point;
m_dragging = false;
//The following show only happen when we're creating preformatted text. If we're making
//Word-wrapped text, it should take the rectangle unmodified.
int size = QFontDatabase::standardSizes().at(m_defPointSize->currentIndex());
QFont font = m_defFont->currentFont();
font.setPointSize(size);
rectangle.setTop(rectangle.top()+QFontMetrics(font).lineSpacing());
if (m_defAlignment->button(1)->isChecked()) {
rectangle.setLeft(rectangle.center().x());
} else if (m_defAlignment->button(2)->isChecked()) {
qreal right = rectangle.right();
rectangle.setRight(right+10);
rectangle.setLeft(right);
}
params->setProperty("shapeRect", QVariant(rectangle));
}
KoShape *textShape = factory->createShape( params, canvas()->shapeController()->resourceManager());
KUndo2Command *parentCommand = new KUndo2Command();
new KoKeepShapesSelectedCommand(koSelection()->selectedShapes(), {}, canvas()->selectedShapesProxy(), false, parentCommand);
KUndo2Command *cmd = canvas()->shapeController()->addShape(textShape, 0, parentCommand);
parentCommand->setText(cmd->text());
new KoKeepShapesSelectedCommand({}, {textShape}, canvas()->selectedShapesProxy(), true, parentCommand);
canvas()->addCommand(parentCommand);
showEditor();
event->accept();
} else if (m_editor) {
showEditor();
event->accept();
}
}
void SvgTextTool::keyPressEvent(QKeyEvent *event)
{
if (event->key()==Qt::Key_Enter || event->key()==Qt::Key_Return) {
showEditor();
event->accept();
} else {
event->ignore();
}
}
void SvgTextTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
if (canvas()->shapeManager()->shapeAt(event->point) != selectedShape()) {
event->ignore(); // allow the event to be used by another
return;
}
showEditor();
if(m_editor) {
m_editor->raise();
m_editor->activateWindow();
}
event->accept();
}
diff --git a/plugins/tools/svgtexttool/WdgSvgTextEditor.ui b/plugins/tools/svgtexttool/WdgSvgTextEditor.ui
index 124a10005a..7e25fcbd73 100644
--- a/plugins/tools/svgtexttool/WdgSvgTextEditor.ui
+++ b/plugins/tools/svgtexttool/WdgSvgTextEditor.ui
@@ -1,101 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgSvgTextEditor</class>
<widget class="QWidget" name="WdgSvgTextEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>530</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="textTab">
<property name="currentIndex">
<number>0</number>
</property>
<property name="tabBarAutoHide">
<bool>true</bool>
</property>
<widget class="QWidget" name="richTab">
<attribute name="title">
<string>Rich text</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="SvgRichTextCtrl" name="richTextEdit"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="svgTab">
<attribute name="title">
<string>SVG source</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
- <number>1</number>
+ <number>0</number>
</property>
<widget class="QWidget" name="tabSvgTextEdit">
<attribute name="title">
<string>SVG</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTextEdit" name="svgTextEdit">
<property name="acceptRichText">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabSvgStylesEdit">
<attribute name="title">
<string>Styles</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QTextEdit" name="svgStylesEdit"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="chkVertical">
<property name="text">
<string>Vertical Text Layout</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttons">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SvgRichTextCtrl</class>
<extends>QTextEdit</extends>
<header>SvgRichTextCtrl.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/tools/svgtexttool/svgtexttool.xmlgui b/plugins/tools/svgtexttool/svgtexttool.xmlgui
index 026b8d0982..16fe4f94e0 100644
--- a/plugins/tools/svgtexttool/svgtexttool.xmlgui
+++ b/plugins/tools/svgtexttool/svgtexttool.xmlgui
@@ -1,100 +1,101 @@
<?xml version="1.0"?>
<kpartgui xmlns="http://www.kde.org/standards/kxmlgui/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="svg_SvgTextTool" version="1" xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0 http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd">
<MenuBar>
<Menu name="file">
<text>&amp;File</text>
<Action name="file_save"/>
<Separator/>
<Action name="file_close"/>
</Menu>
<Menu name="edit">
<text>&amp;Edit</text>
<Action name="edit_undo"/>
<Action name="edit_redo"/>
<Separator/>
<Action name="edit_cut"/>
<Action name="edit_copy"/>
<Action name="edit_paste"/>
<Separator/>
<Action name="edit_select_all"/>
<Action name="edit_deselect"/>
<Separator/>
<Action name="edit_find"/>
<Action name="edit_find_next"/>
<Action name="edit_find_prev"/>
<Action name="edit_replace"/>
</Menu>
<Menu name="view">
<text>&amp;View</text>
<Action name="view_zoom_out"/>
<Action name="view_zoom_in"/>
</Menu>
<Menu name="insert">
<text>&amp;Insert</text>
<Action name="svg_insert_special_character"/>
</Menu>
<Menu name="format">
<text>&amp;Format</text>
<Action name="svg_weight_bold"/>
<Action name="svg_format_italic"/>
<Action name="svg_format_underline"/>
<Action name="svg_format_strike_through"/>
<Action name="svg_format_superscript"/>
<Action name="svg_format_subscript"/>
<Menu name="weight">
<text>&amp;Weight</text>
<Action name="svg_weight_light"/>
<Action name="svg_weight_normal"/>
<Action name="svg_weight_bold"/>
<Action name="svg_weight_demi_bold"/>
<Action name="svg_weight_black"/>
</Menu>
<Separator/>
<Action name="svg_increase_text_size"/>
<Action name="svg_decrease_text_size"/>
<Separator/>
<Action name="svg_align_left"/>
<Action name="svg_align_center"/>
<Action name="svg_align_right"/>
<!--Action name="svg_align_justified"/-->
</Menu>
<Menu name="settings">
<text>Setti&amp;ngs</text>
<Action name="svg_settings"/>
<Merge name="StandardToolBarMenuHandler"/>
<Separator/>
</Menu>
</MenuBar>
<ToolBar name="mainToolBar" fullWidth="false" noMerge="1">
<Text>File</Text>
<Action name="edit_undo"/>
<Action name="edit_redo"/>
<Separator/>
<Action name="edit_cut"/>
<Action name="edit_copy"/>
<Action name="edit_paste"/>
</ToolBar>
<ToolBar name="fontSettings" newline="true" >
<Text>Font Settings</Text>
<Action name="svg_font"/>
<Action name="svg_font_size"/>
<Action name="svg_font_color"/>
<Action name="svg_format_textcolor"/>
<Action name="svg_pick_color"/>
</ToolBar>
<ToolBar name="formatting" newline="true" >
<Text>Formatting</Text>
<Action name="svg_weight_bold"/>
<Action name="svg_format_italic"/>
<Action name="svg_format_underline"/>
<Action name="svg_align_left"/>
<Action name="svg_align_center"/>
<Action name="svg_align_right"/>
<Action name="svg_line_height">
</Action>
+ <Action name="svg_letter_spacing"></Action>
</ToolBar>
</kpartgui>
diff --git a/plugins/tools/tool_crop/kis_tool_crop.cc b/plugins/tools/tool_crop/kis_tool_crop.cc
index a35bd33e74..42d928d102 100644
--- a/plugins/tools/tool_crop/kis_tool_crop.cc
+++ b/plugins/tools/tool_crop/kis_tool_crop.cc
@@ -1,914 +1,917 @@
/*
* kis_tool_crop.cc -- part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
* Copyright (C) 2007 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_crop.h"
#include <QCheckBox>
#include <QComboBox>
#include <QObject>
#include <QPainter>
#include <QPen>
#include <QPushButton>
#include <QRect>
#include <QVector>
#include <QMenu>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <KoCanvasBase.h>
#include <kis_global.h>
#include <kis_painter.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include <kis_undo_adapter.h>
#include <KoPointerEvent.h>
#include <kis_selection.h>
#include <kis_layer.h>
#include <kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_floating_message.h>
#include <kis_group_layer.h>
#include <kis_resources_snapshot.h>
#include <kundo2command.h>
#include <kis_crop_saved_extra_data.h>
struct DecorationLine
{
QPointF start;
QPointF end;
enum Relation
{
Width,
Height,
Smallest,
Largest
};
Relation startXRelation;
Relation startYRelation;
Relation endXRelation;
Relation endYRelation;
};
DecorationLine decors[20] =
{
//thirds
{QPointF(0.0, 0.3333),QPointF(1.0, 0.3333), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.0, 0.6666),QPointF(1.0, 0.6666), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.3333, 0.0),QPointF(0.3333, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.6666, 0.0),QPointF(0.6666, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
//fifths
{QPointF(0.0, 0.2),QPointF(1.0, 0.2), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.0, 0.4),QPointF(1.0, 0.4), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.0, 0.6),QPointF(1.0, 0.6), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.0, 0.8),QPointF(1.0, 0.8), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.2, 0.0),QPointF(0.2, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.4, 0.0),QPointF(0.4, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.6, 0.0),QPointF(0.6, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.8, 0.0),QPointF(0.8, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
// Passport photo
{QPointF(0.0, 0.45/0.35),QPointF(1.0, 0.45/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width},
{QPointF(0.2, 0.05/0.35),QPointF(0.8, 0.05/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width},
{QPointF(0.2, 0.40/0.35),QPointF(0.8, 0.40/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width},
{QPointF(0.25, 0.07/0.35),QPointF(0.75, 0.07/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width},
{QPointF(0.25, 0.38/0.35),QPointF(0.75, 0.38/0.35), DecorationLine::Width, DecorationLine::Width, DecorationLine::Width, DecorationLine::Width},
{QPointF(0.35/0.45, 0.0),QPointF(0.35/0.45, 1.0), DecorationLine::Height, DecorationLine::Height, DecorationLine::Height, DecorationLine::Height},
//Crosshair
{QPointF(0.0, 0.5),QPointF(1.0, 0.5), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height},
{QPointF(0.5, 0.0),QPointF(0.5, 1.0), DecorationLine::Width, DecorationLine::Height, DecorationLine::Width, DecorationLine::Height}
};
#define DECORATION_COUNT 5
const int decorsIndex[DECORATION_COUNT] = {0,4,12,18,20};
KisToolCrop::KisToolCrop(KoCanvasBase * canvas)
: KisTool(canvas, KisCursor::load("tool_crop_cursor.png", 6, 6))
{
setObjectName("tool_crop");
m_handleSize = 13;
m_haveCropSelection = false;
m_cropTypeSelectable = false;
m_cropType = ImageCropType;
m_decoration = 1;
connect(&m_finalRect, SIGNAL(sigValuesChanged()), SLOT(slotRectChanged()));
connect(&m_finalRect, SIGNAL(sigLockValuesChanged()), SLOT(slotRectChanged()));
// context menu options (mirrors tool options)
m_contextMenu.reset(new QMenu());
applyCrop = new KisAction(i18n("Crop"));
growToggleOption = new KisAction(i18n("Grow"));
growToggleOption->setCheckable(true);
centerToggleOption = new KisAction(i18n("Center"));
centerToggleOption->setCheckable(true);
}
KisToolCrop::~KisToolCrop()
{
}
void KisToolCrop::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisTool::activate(toolActivation, shapes);
configGroup = KSharedConfig::openConfig()->group(toolId()); // save settings to kritarc
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
// load settings from configuration
setGrowCenter(configGroup.readEntry("growCenter", false));
setAllowGrow(configGroup.readEntry("allowGrow", false));
// Default: thirds decoration
setDecoration(configGroup.readEntry("decoration", 1));
// Default: crop the entire image
setCropType(configGroup.readEntry("cropType", 1) == 0 ? LayerCropType : ImageCropType);
m_finalRect.setCropRect(image()->bounds());
KisSelectionSP sel = resources->activeSelection();
if (sel) {
m_haveCropSelection = true;
m_finalRect.setRectInitial(sel->selectedExactRect());
}
useCursor(cursor());
//pixel layer
if(resources->currentNode() && resources->currentNode()->paintDevice()) {
setCropTypeSelectable(true);
}
//vector layer
else {
setCropTypeSelectable(false);
}
}
void KisToolCrop::cancelStroke()
{
m_haveCropSelection = false;
doCanvasUpdate(image()->bounds());
}
void KisToolCrop::deactivate()
{
cancelStroke();
KisTool::deactivate();
}
void KisToolCrop::requestStrokeEnd()
{
if (m_haveCropSelection) crop();
}
void KisToolCrop::requestStrokeCancellation()
{
cancelStroke();
}
void KisToolCrop::canvasResourceChanged(int key, const QVariant &res)
{
KisTool::canvasResourceChanged(key, res);
//pixel layer
if(currentNode() && currentNode()->paintDevice()) {
setCropTypeSelectable(true);
}
//vector layer
else {
setCropType(ImageCropType);
setCropTypeSelectable(false);
}
}
void KisToolCrop::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(converter);
paintOutlineWithHandles(painter);
}
QMenu *KisToolCrop::popupActionsMenu()
{
if (m_contextMenu) {
m_contextMenu->clear();
+ m_contextMenu->addSection(i18n("Crop Tool Actions"));
+ m_contextMenu->addSeparator();
+
// keeps in sync with tool options
growToggleOption->setChecked(allowGrow());
centerToggleOption->setChecked(growCenter());
if (m_haveCropSelection) { // can't crop if there is no selection
m_contextMenu->addAction(applyCrop);
m_contextMenu->addSeparator();
}
m_contextMenu->addAction(growToggleOption);
m_contextMenu->addAction(centerToggleOption);
}
return m_contextMenu.data();
}
void KisToolCrop::beginPrimaryAction(KoPointerEvent *event)
{
m_finalRect.setCropRect(image()->bounds());
setMode(KisTool::PAINT_MODE);
const QPointF imagePoint = convertToPixelCoord(event);
m_mouseOnHandleType = mouseOnHandle(pixelToView(imagePoint));
if (m_mouseOnHandleType != KisConstrainedRect::None) {
QPointF snapPoint = m_finalRect.handleSnapPoint(KisConstrainedRect::HandleType(m_mouseOnHandleType), imagePoint);
QPointF snapDocPoint = image()->pixelToDocument(snapPoint);
m_dragOffsetDoc = snapDocPoint - event->point;
} else {
m_dragOffsetDoc = QPointF();
}
QPointF snappedPoint = convertToPixelCoordAndSnap(event, m_dragOffsetDoc);
m_dragStart = snappedPoint.toPoint();
m_resettingStroke = false;
if (!m_haveCropSelection || m_mouseOnHandleType == None) {
m_lastCanvasUpdateRect = image()->bounds();
const int initialWidth = m_finalRect.widthLocked() ? m_finalRect.rect().width() : 1;
const int initialHeight = m_finalRect.heightLocked() ? m_finalRect.rect().height() : 1;
const QRect initialRect = QRect(m_dragStart, QSize(initialWidth, initialHeight));
m_finalRect.setRectInitial(initialRect);
m_initialDragRect = initialRect;
m_mouseOnHandleType = KisConstrainedRect::Creation;
m_resettingStroke = true;
} else {
m_initialDragRect = m_finalRect.rect();
}
}
void KisToolCrop::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
const QPointF pos = convertToPixelCoordAndSnap(event, m_dragOffsetDoc);
const QPoint drag = pos.toPoint() - m_dragStart;
m_finalRect.moveHandle(KisConstrainedRect::HandleType(m_mouseOnHandleType), drag, m_initialDragRect);
}
bool KisToolCrop::tryContinueLastCropAction()
{
bool result = false;
const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand();
const KisCropSavedExtraData *data = 0;
if ((lastCommand = image()->undoAdapter()->presentCommand()) &&
(data = dynamic_cast<const KisCropSavedExtraData*>(lastCommand->extraData()))) {
bool cropImageConsistent =
m_cropType == ImageCropType &&
(data->type() == KisCropSavedExtraData::CROP_IMAGE ||
data->type() == KisCropSavedExtraData::RESIZE_IMAGE);
bool cropLayerConsistent =
m_cropType == LayerCropType &&
data->type() == KisCropSavedExtraData::CROP_LAYER &&
currentNode() == data->cropNode();
if (cropImageConsistent || cropLayerConsistent) {
image()->undoAdapter()->undoLastCommand();
image()->waitForDone();
m_finalRect.setRectInitial(data->cropRect());
m_haveCropSelection = true;
result = true;
}
}
return result;
}
void KisToolCrop::endPrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
setMode(KisTool::HOVER_MODE);
QRectF viewCropRect = pixelToView(m_finalRect.rect());
const bool haveValidRect =
viewCropRect.width() > m_handleSize &&
viewCropRect.height() > m_handleSize;
if (!m_haveCropSelection && !haveValidRect) {
if (!tryContinueLastCropAction()) {
m_finalRect.setRectInitial(image()->bounds());
m_haveCropSelection = true;
}
} else if (m_resettingStroke && !haveValidRect) {
m_lastCanvasUpdateRect = image()->bounds();
m_haveCropSelection = false;
} else {
m_haveCropSelection = true;
}
m_finalRect.normalize();
qint32 type = mouseOnHandle(pixelToView(convertToPixelCoordAndSnap(event, m_dragOffsetDoc)));
setMoveResizeCursor(type);
}
void KisToolCrop::mouseMoveEvent(KoPointerEvent *event)
{
QPointF pos = convertToPixelCoordAndSnap(event);
if (m_haveCropSelection) { //if the crop selection is set
//set resize cursor if we are on one of the handles
if(mode() == KisTool::PAINT_MODE) {
//keep the same cursor as the one we clicked with
setMoveResizeCursor(m_mouseOnHandleType);
}else{
//hovering
qint32 type = mouseOnHandle(pixelToView(pos));
setMoveResizeCursor(type);
}
}
}
void KisToolCrop::beginPrimaryDoubleClickAction(KoPointerEvent *event)
{
if (m_haveCropSelection) crop();
// this action will have no continuation
event->ignore();
}
#define BORDER_LINE_WIDTH 0
#define HALF_BORDER_LINE_WIDTH 0
#define HANDLE_BORDER_LINE_WIDTH 1
QRectF KisToolCrop::borderLineRect()
{
QRectF borderRect = pixelToView(m_finalRect.rect());
// Draw the border line right next to the crop rectangle perimeter.
borderRect.adjust(-HALF_BORDER_LINE_WIDTH, -HALF_BORDER_LINE_WIDTH, HALF_BORDER_LINE_WIDTH, HALF_BORDER_LINE_WIDTH);
return borderRect;
}
#define OUTSIDE_CROP_ALPHA 200
void KisToolCrop::paintOutlineWithHandles(QPainter& gc)
{
if (canvas() && (mode() == KisTool::PAINT_MODE || m_haveCropSelection)) {
gc.save();
QRectF wholeImageRect = pixelToView(image()->bounds());
QRectF borderRect = borderLineRect();
QPainterPath path;
path.addRect(wholeImageRect);
path.addRect(borderRect);
gc.setPen(Qt::NoPen);
gc.setBrush(QColor(0, 0, 0, OUTSIDE_CROP_ALPHA));
gc.drawPath(path);
// Handles
QPen pen(Qt::SolidLine);
pen.setWidth(HANDLE_BORDER_LINE_WIDTH);
pen.setColor(Qt::black);
gc.setPen(pen);
gc.setBrush(QColor(200, 200, 200, OUTSIDE_CROP_ALPHA));
gc.drawPath(handlesPath());
gc.setClipRect(borderRect, Qt::IntersectClip);
if (m_decoration > 0) {
for (int i = decorsIndex[m_decoration-1]; i<decorsIndex[m_decoration]; i++) {
drawDecorationLine(&gc, &(decors[i]), borderRect);
}
}
gc.restore();
}
}
void KisToolCrop::crop()
{
KIS_ASSERT_RECOVER_RETURN(currentImage());
if (m_finalRect.rect().isEmpty()) return;
if (m_cropType == LayerCropType) {
//Cropping layer
if (!nodeEditable()) {
return;
}
}
m_haveCropSelection = false;
useCursor(cursor());
QRect cropRect = m_finalRect.rect();
// The visitor adds the undo steps to the macro
if (m_cropType == LayerCropType && currentNode()->paintDevice()) {
currentImage()->cropNode(currentNode(), cropRect);
} else {
currentImage()->cropImage(cropRect);
}
}
void KisToolCrop::setCropTypeLegacy(int cropType)
{
setCropType(cropType == 0 ? LayerCropType : ImageCropType);
}
void KisToolCrop::setCropType(KisToolCrop::CropToolType cropType)
{
if(m_cropType == cropType)
return;
m_cropType = cropType;
// can't save LayerCropType, so have to convert it to int for saving
configGroup.writeEntry("cropType", cropType == LayerCropType ? 0 : 1);
emit cropTypeChanged(m_cropType);
}
KisToolCrop::CropToolType KisToolCrop::cropType() const
{
return m_cropType;
}
void KisToolCrop::setCropTypeSelectable(bool selectable)
{
if(selectable == m_cropTypeSelectable)
return;
m_cropTypeSelectable = selectable;
emit cropTypeSelectableChanged();
}
bool KisToolCrop::cropTypeSelectable() const
{
return m_cropTypeSelectable;
}
int KisToolCrop::decoration() const
{
return m_decoration;
}
void KisToolCrop::setDecoration(int i)
{
// This shouldn't happen, but safety first
if(i < 0 || i > DECORATION_COUNT)
return;
m_decoration = i;
emit decorationChanged(decoration());
updateCanvasViewRect(boundingRect());
configGroup.writeEntry("decoration", i);
}
void KisToolCrop::doCanvasUpdate(const QRect &updateRect)
{
updateCanvasViewRect(updateRect | m_lastCanvasUpdateRect);
m_lastCanvasUpdateRect = updateRect;
}
void KisToolCrop::slotRectChanged()
{
emit cropHeightChanged(cropHeight());
emit cropWidthChanged(cropWidth());
emit cropXChanged(cropX());
emit cropYChanged(cropY());
emit ratioChanged(ratio());
emit forceHeightChanged(forceHeight());
emit forceWidthChanged(forceWidth());
emit forceRatioChanged(forceRatio());
emit canGrowChanged(allowGrow());
emit isCenteredChanged(growCenter());
doCanvasUpdate(boundingRect().toAlignedRect());
}
void KisToolCrop::setCropX(int x)
{
if(x == m_finalRect.rect().x())
return;
if (!m_haveCropSelection) {
m_haveCropSelection = true;
m_finalRect.setRectInitial(image()->bounds());
}
QPoint offset = m_finalRect.rect().topLeft();
offset.setX(x);
m_finalRect.setOffset(offset);
}
int KisToolCrop::cropX() const
{
return m_finalRect.rect().x();
}
void KisToolCrop::setCropY(int y)
{
if(y == m_finalRect.rect().y())
return;
if (!m_haveCropSelection) {
m_haveCropSelection = true;
m_finalRect.setRectInitial(image()->bounds());
}
QPoint offset = m_finalRect.rect().topLeft();
offset.setY(y);
m_finalRect.setOffset(offset);
}
int KisToolCrop::cropY() const
{
return m_finalRect.rect().y();
}
void KisToolCrop::setCropWidth(int w)
{
if(w == m_finalRect.rect().width())
return;
if (!m_haveCropSelection) {
m_haveCropSelection = true;
m_finalRect.setRectInitial(image()->bounds());
}
m_finalRect.setWidth(w);
}
int KisToolCrop::cropWidth() const
{
return m_finalRect.rect().width();
}
void KisToolCrop::setForceWidth(bool force)
{
m_finalRect.setWidthLocked(force);
}
bool KisToolCrop::forceWidth() const
{
return m_finalRect.widthLocked();
}
void KisToolCrop::setCropHeight(int h)
{
if(h == m_finalRect.rect().height())
return;
if (!m_haveCropSelection) {
m_haveCropSelection = true;
m_finalRect.setRectInitial(image()->bounds());
}
m_finalRect.setHeight(h);
}
int KisToolCrop::cropHeight() const
{
return m_finalRect.rect().height();
}
void KisToolCrop::setForceHeight(bool force)
{
m_finalRect.setHeightLocked(force);
}
bool KisToolCrop::forceHeight() const
{
return m_finalRect.heightLocked();
}
void KisToolCrop::setAllowGrow(bool g)
{
m_finalRect.setCanGrow(g);
m_finalRect.setCropRect(image()->bounds());
configGroup.writeEntry("allowGrow", g);
emit canGrowChanged(g);
}
bool KisToolCrop::allowGrow() const
{
return m_finalRect.canGrow();
}
void KisToolCrop::setGrowCenter(bool value)
{
m_finalRect.setCentered(value);
configGroup.writeEntry("growCenter", value);
emit isCenteredChanged(value);
}
bool KisToolCrop::growCenter() const
{
return m_finalRect.centered();
}
void KisToolCrop::setRatio(double ratio)
{
if(ratio == m_finalRect.ratio())
return;
if (!m_haveCropSelection) {
m_haveCropSelection = true;
m_finalRect.setRectInitial(image()->bounds());
}
m_finalRect.setRatio(ratio);
}
double KisToolCrop::ratio() const
{
return m_finalRect.ratio();
}
void KisToolCrop::setForceRatio(bool force)
{
m_finalRect.setRatioLocked(force);
}
bool KisToolCrop::forceRatio() const
{
return m_finalRect.ratioLocked();
}
QWidget* KisToolCrop::createOptionWidget()
{
optionsWidget = new KisToolCropConfigWidget(0, this);
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
optionsWidget->layout()->addWidget(specialSpacer);
Q_CHECK_PTR(optionsWidget);
optionsWidget->setObjectName(toolId() + " option widget");
connect(optionsWidget->bnCrop, SIGNAL(clicked()), this, SLOT(crop()));
connect(optionsWidget, SIGNAL(cropTypeChanged(int)), this, SLOT(setCropTypeLegacy(int)));
connect(optionsWidget, SIGNAL(cropXChanged(int)), this, SLOT(setCropX(int)));
connect(optionsWidget, SIGNAL(cropYChanged(int)), this, SLOT(setCropY(int)));
connect(optionsWidget, SIGNAL(cropHeightChanged(int)), this, SLOT(setCropHeight(int)));
connect(optionsWidget, SIGNAL(forceHeightChanged(bool)), this, SLOT(setForceHeight(bool)));
connect(optionsWidget, SIGNAL(cropWidthChanged(int)), this, SLOT(setCropWidth(int)));
connect(optionsWidget, SIGNAL(forceWidthChanged(bool)), this, SLOT(setForceWidth(bool)));
connect(optionsWidget, SIGNAL(ratioChanged(double)), this, SLOT(setRatio(double)));
connect(optionsWidget, SIGNAL(forceRatioChanged(bool)), this, SLOT(setForceRatio(bool)));
connect(optionsWidget, SIGNAL(decorationChanged(int)), this, SLOT(setDecoration(int)));
connect(optionsWidget, SIGNAL(allowGrowChanged(bool)), this, SLOT(setAllowGrow(bool)));
connect(optionsWidget, SIGNAL(growCenterChanged(bool)), this, SLOT(setGrowCenter(bool)));
optionsWidget->setFixedHeight(optionsWidget->sizeHint().height());
connect(applyCrop, SIGNAL(triggered(bool)), this, SLOT(crop()));
connect(growToggleOption, SIGNAL(triggered(bool)), this, SLOT(setAllowGrow(bool)));
connect(centerToggleOption, SIGNAL(triggered(bool)), this, SLOT(setGrowCenter(bool)));
return optionsWidget;
}
QRectF KisToolCrop::lowerRightHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.right() - m_handleSize / 2.0, cropBorderRect.bottom() - m_handleSize / 2.0, m_handleSize, m_handleSize);
}
QRectF KisToolCrop::upperRightHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.right() - m_handleSize / 2.0 , cropBorderRect.top() - m_handleSize / 2.0, m_handleSize, m_handleSize);
}
QRectF KisToolCrop::lowerLeftHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.left() - m_handleSize / 2.0 , cropBorderRect.bottom() - m_handleSize / 2.0, m_handleSize, m_handleSize);
}
QRectF KisToolCrop::upperLeftHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.left() - m_handleSize / 2.0, cropBorderRect.top() - m_handleSize / 2.0, m_handleSize, m_handleSize);
}
QRectF KisToolCrop::lowerHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.left() + (cropBorderRect.width() - m_handleSize) / 2.0 , cropBorderRect.bottom() - m_handleSize / 2.0, m_handleSize, m_handleSize);
}
QRectF KisToolCrop::rightHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.right() - m_handleSize / 2.0 , cropBorderRect.top() + (cropBorderRect.height() - m_handleSize) / 2.0, m_handleSize, m_handleSize);
}
QRectF KisToolCrop::upperHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.left() + (cropBorderRect.width() - m_handleSize) / 2.0 , cropBorderRect.top() - m_handleSize / 2.0, m_handleSize, m_handleSize);
}
QRectF KisToolCrop::leftHandleRect(QRectF cropBorderRect)
{
return QRectF(cropBorderRect.left() - m_handleSize / 2.0, cropBorderRect.top() + (cropBorderRect.height() - m_handleSize) / 2.0, m_handleSize, m_handleSize);
}
QPainterPath KisToolCrop::handlesPath()
{
QRectF cropBorderRect = borderLineRect();
QPainterPath path;
path.addRect(upperLeftHandleRect(cropBorderRect));
path.addRect(upperRightHandleRect(cropBorderRect));
path.addRect(lowerLeftHandleRect(cropBorderRect));
path.addRect(lowerRightHandleRect(cropBorderRect));
path.addRect(upperHandleRect(cropBorderRect));
path.addRect(lowerHandleRect(cropBorderRect));
path.addRect(leftHandleRect(cropBorderRect));
path.addRect(rightHandleRect(cropBorderRect));
return path;
}
qint32 KisToolCrop::mouseOnHandle(QPointF currentViewPoint)
{
QRectF borderRect = borderLineRect();
qint32 handleType = None;
if (!m_haveCropSelection) {
return None;
}
if (upperLeftHandleRect(borderRect).contains(currentViewPoint)) {
handleType = UpperLeft;
} else if (lowerLeftHandleRect(borderRect).contains(currentViewPoint)) {
handleType = LowerLeft;
} else if (upperRightHandleRect(borderRect).contains(currentViewPoint)) {
handleType = UpperRight;
} else if (lowerRightHandleRect(borderRect).contains(currentViewPoint)) {
handleType = LowerRight;
} else if (upperHandleRect(borderRect).contains(currentViewPoint)) {
handleType = Upper;
} else if (lowerHandleRect(borderRect).contains(currentViewPoint)) {
handleType = Lower;
} else if (leftHandleRect(borderRect).contains(currentViewPoint)) {
handleType = Left;
} else if (rightHandleRect(borderRect).contains(currentViewPoint)) {
handleType = Right;
} else if (borderRect.contains(currentViewPoint)) {
handleType = Inside;
}
return handleType;
}
void KisToolCrop::setMoveResizeCursor(qint32 handle)
{
QCursor cursor;
switch (handle) {
case(UpperLeft):
case(LowerRight):
cursor = KisCursor::sizeFDiagCursor();
break;
case(LowerLeft):
case(UpperRight):
cursor = KisCursor::sizeBDiagCursor();
break;
case(Upper):
case(Lower):
cursor = KisCursor::sizeVerCursor();
break;
case(Left):
case(Right):
cursor = KisCursor::sizeHorCursor();
break;
case(Inside):
cursor = KisCursor::sizeAllCursor();
break;
default:
cursor = KisCursor::arrowCursor();
break;
}
useCursor(cursor);
}
QRectF KisToolCrop::boundingRect()
{
QRectF rect = handlesPath().boundingRect();
rect.adjust(-HANDLE_BORDER_LINE_WIDTH, -HANDLE_BORDER_LINE_WIDTH, HANDLE_BORDER_LINE_WIDTH, HANDLE_BORDER_LINE_WIDTH);
return rect;
}
void KisToolCrop::drawDecorationLine(QPainter *p, DecorationLine *decorLine, const QRectF rect)
{
QPointF start = rect.topLeft();
QPointF end = rect.topLeft();
qreal small = qMin(rect.width(), rect.height());
qreal large = qMax(rect.width(), rect.height());
switch (decorLine->startXRelation) {
case DecorationLine::Width:
start.setX(start.x() + decorLine->start.x() * rect.width());
break;
case DecorationLine::Height:
start.setX(start.x() + decorLine->start.x() * rect.height());
break;
case DecorationLine::Smallest:
start.setX(start.x() + decorLine->start.x() * small);
break;
case DecorationLine::Largest:
start.setX(start.x() + decorLine->start.x() * large);
break;
}
switch (decorLine->startYRelation) {
case DecorationLine::Width:
start.setY(start.y() + decorLine->start.y() * rect.width());
break;
case DecorationLine::Height:
start.setY(start.y() + decorLine->start.y() * rect.height());
break;
case DecorationLine::Smallest:
start.setY(start.y() + decorLine->start.y() * small);
break;
case DecorationLine::Largest:
start.setY(start.y() + decorLine->start.y() * large);
break;
}
switch (decorLine->endXRelation) {
case DecorationLine::Width:
end.setX(end.x() + decorLine->end.x() * rect.width());
break;
case DecorationLine::Height:
end.setX(end.x() + decorLine->end.x() * rect.height());
break;
case DecorationLine::Smallest:
end.setX(end.x() + decorLine->end.x() * small);
break;
case DecorationLine::Largest:
end.setX(end.x() + decorLine->end.x() * large);
break;
}
switch (decorLine->endYRelation) {
case DecorationLine::Width:
end.setY(end.y() + decorLine->end.y() * rect.width());
break;
case DecorationLine::Height:
end.setY(end.y() + decorLine->end.y() * rect.height());
break;
case DecorationLine::Smallest:
end.setY(end.y() + decorLine->end.y() * small);
break;
case DecorationLine::Largest:
end.setY(end.y() + decorLine->end.y() * large);
break;
}
p->drawLine(start, end);
}
diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc
index d5d651f232..5545dc4950 100644
--- a/plugins/tools/tool_transform2/kis_tool_transform.cc
+++ b/plugins/tools/tool_transform2/kis_tool_transform.cc
@@ -1,1320 +1,1322 @@
/*
* kis_tool_transform.cc -- part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2010 Marc Pegon <pe.marc@free.fr>
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_transform.h"
#include <math.h>
#include <limits>
#include <QPainter>
#include <QPen>
#include <QPushButton>
#include <QObject>
#include <QLabel>
#include <QComboBox>
#include <QApplication>
#include <QMatrix4x4>
#include <QMenu>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoPointerEvent.h>
#include <KoID.h>
#include <KoCanvasBase.h>
#include <KoViewConverter.h>
#include <KoSelection.h>
#include <KoCompositeOp.h>
#include <kis_global.h>
#include <canvas/kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_painter.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include <kis_undo_adapter.h>
#include <kis_transaction.h>
#include <kis_selection.h>
#include <kis_filter_strategy.h>
#include <widgets/kis_cmb_idlist.h>
#include <kis_statusbar.h>
#include <kis_transform_worker.h>
#include <kis_perspectivetransform_worker.h>
#include <kis_warptransform_worker.h>
#include <kis_pixel_selection.h>
#include <kis_shape_selection.h>
#include <kis_selection_manager.h>
#include <krita_utils.h>
#include <kis_resources_snapshot.h>
#include <KoShapeTransformCommand.h>
#include "widgets/kis_progress_widget.h"
#include "kis_transform_utils.h"
#include "kis_warp_transform_strategy.h"
#include "kis_cage_transform_strategy.h"
#include "kis_liquify_transform_strategy.h"
#include "kis_free_transform_strategy.h"
#include "kis_perspective_transform_strategy.h"
#include "kis_transform_mask.h"
#include "kis_transform_mask_adapter.h"
#include "krita_container_utils.h"
#include "kis_layer_utils.h"
#include <KisDelayedUpdateNodeInterface.h>
#include "strokes/transform_stroke_strategy.h"
KisToolTransform::KisToolTransform(KoCanvasBase * canvas)
: KisTool(canvas, KisCursor::rotateCursor())
, m_workRecursively(true)
, m_warpStrategy(
new KisWarpTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
m_currentArgs, m_transaction))
, m_cageStrategy(
new KisCageTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
m_currentArgs, m_transaction))
, m_liquifyStrategy(
new KisLiquifyTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
m_currentArgs, m_transaction, canvas->resourceManager()))
, m_freeStrategy(
new KisFreeTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
m_currentArgs, m_transaction))
, m_perspectiveStrategy(
new KisPerspectiveTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
m_currentArgs, m_transaction))
{
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
Q_ASSERT(m_canvas);
setObjectName("tool_transform");
useCursor(KisCursor::selectCursor());
m_optionsWidget = 0;
warpAction = new KisAction(i18n("Warp"));
liquifyAction = new KisAction(i18n("Liquify"));
cageAction = new KisAction(i18n("Cage"));
freeTransformAction = new KisAction(i18n("Free"));
perspectiveAction = new KisAction(i18n("Perspective"));
// extra actions for free transform that are in the tool options
mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal"));
mirrorVericalAction = new KisAction(i18n("Mirror Vertical"));
rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise"));
rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise"));
applyTransformation = new KisAction(i18n("Apply"));
resetTransformation = new KisAction(i18n("Reset"));
m_contextMenu.reset(new QMenu());
connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF)));
connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget()));
connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested()));
connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)),
this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP)));
}
KisToolTransform::~KisToolTransform()
{
cancelStroke();
}
void KisToolTransform::outlineChanged()
{
emit freeTransformChanged();
m_canvas->updateCanvas();
}
void KisToolTransform::canvasUpdateRequested()
{
m_canvas->updateCanvas();
}
void KisToolTransform::resetCursorStyle()
{
setFunctionalCursor();
}
void KisToolTransform::resetRotationCenterButtonsRequested()
{
if (!m_optionsWidget) return;
m_optionsWidget->resetRotationCenterButtons();
}
void KisToolTransform::imageTooBigRequested(bool value)
{
if (!m_optionsWidget) return;
m_optionsWidget->setTooBigLabelVisible(value);
}
KisTransformStrategyBase* KisToolTransform::currentStrategy() const
{
if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) {
return m_freeStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::WARP) {
return m_warpStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) {
return m_cageStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
return m_liquifyStrategy.data();
} else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ {
return m_perspectiveStrategy.data();
}
}
void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if (!m_strokeData.strokeId()) return;
QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0));
if (m_refRect != newRefRect) {
m_refRect = newRefRect;
currentStrategy()->externalConfigChanged();
}
gc.save();
if (m_optionsWidget && m_optionsWidget->showDecorations()) {
gc.setOpacity(0.3);
gc.fillPath(m_selectionPath, Qt::black);
}
gc.restore();
currentStrategy()->paint(gc);
if (!m_cursorOutline.isEmpty()) {
QPainterPath mappedOutline =
KisTransformUtils::imageToFlakeTransform(
m_canvas->coordinatesConverter()).map(m_cursorOutline);
paintToolOutline(&gc, mappedOutline);
}
}
void KisToolTransform::setFunctionalCursor()
{
if (overrideCursorIfNotEditable()) {
return;
}
if (!m_strokeData.strokeId()) {
useCursor(KisCursor::pointingHandCursor());
} else {
useCursor(currentStrategy()->getCurrentCursor());
}
}
void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos)
{
QRect canvasUpdateRect;
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect = m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
m_cursorOutline = currentStrategy()->
getCursorOutline().translated(imagePos);
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect |=
m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
if (!canvasUpdateRect.isEmpty()) {
// grow rect a bit to follow interpolation fuzziness
canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
m_canvas->updateCanvas(canvasUpdateRect);
}
}
void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (!nodeEditable()) {
event->ignore();
return;
}
if (!m_strokeData.strokeId()) {
startStroke(m_currentArgs.mode(), false);
} else {
bool result = false;
if (usePrimaryAction) {
result = currentStrategy()->beginPrimaryAction(event);
} else {
result = currentStrategy()->beginAlternateAction(event, action);
}
if (result) {
setMode(KisTool::PAINT_MODE);
}
}
m_actuallyMoveWhileSelected = false;
outlineChanged();
}
void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (mode() != KisTool::PAINT_MODE) return;
m_actuallyMoveWhileSelected = true;
if (usePrimaryAction) {
currentStrategy()->continuePrimaryAction(event);
} else {
currentStrategy()->continueAlternateAction(event, action);
}
updateOptionWidget();
outlineChanged();
}
void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (mode() != KisTool::PAINT_MODE) return;
setMode(KisTool::HOVER_MODE);
if (m_actuallyMoveWhileSelected ||
currentStrategy()->acceptsClicks()) {
bool result = false;
if (usePrimaryAction) {
result = currentStrategy()->endPrimaryAction(event);
} else {
result = currentStrategy()->endAlternateAction(event, action);
}
if (result) {
commitChanges();
}
outlineChanged();
}
updateOptionWidget();
updateApplyResetAvailability();
}
QMenu* KisToolTransform::popupActionsMenu()
{
if (m_contextMenu) {
m_contextMenu->clear();
+ m_contextMenu->addSection(i18n("Transform Tool Actions"));
+ m_contextMenu->addSeparator();
// add a quick switch to different transform types
m_contextMenu->addAction(freeTransformAction);
m_contextMenu->addAction(perspectiveAction);
m_contextMenu->addAction(warpAction);
m_contextMenu->addAction(cageAction);
m_contextMenu->addAction(liquifyAction);
// extra options if free transform is selected
if (transformMode() == FreeTransformMode) {
m_contextMenu->addSeparator();
m_contextMenu->addAction(mirrorHorizontalAction);
m_contextMenu->addAction(mirrorVericalAction);
m_contextMenu->addAction(rotateNinteyCWAction);
m_contextMenu->addAction(rotateNinteyCCWAction);
}
m_contextMenu->addSeparator();
m_contextMenu->addAction(applyTransformation);
m_contextMenu->addAction(resetTransformation);
}
return m_contextMenu.data();
}
void KisToolTransform::beginPrimaryAction(KoPointerEvent *event)
{
beginActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::continuePrimaryAction(KoPointerEvent *event)
{
continueActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::endPrimaryAction(KoPointerEvent *event)
{
endActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::activatePrimaryAction()
{
currentStrategy()->activatePrimaryAction();
setFunctionalCursor();
}
void KisToolTransform::deactivatePrimaryAction()
{
currentStrategy()->deactivatePrimaryAction();
}
void KisToolTransform::activateAlternateAction(AlternateAction action)
{
currentStrategy()->activateAlternateAction(action);
setFunctionalCursor();
}
void KisToolTransform::deactivateAlternateAction(AlternateAction action)
{
currentStrategy()->deactivateAlternateAction(action);
}
void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
beginActionImpl(event, false, action);
}
void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
continueActionImpl(event, false, action);
}
void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
endActionImpl(event, false, action);
}
void KisToolTransform::mousePressEvent(KoPointerEvent *event)
{
KisTool::mousePressEvent(event);
}
void KisToolTransform::mouseMoveEvent(KoPointerEvent *event)
{
QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
cursorOutlineUpdateRequested(mousePos);
if (this->mode() != KisTool::PAINT_MODE) {
currentStrategy()->hoverActionCommon(event);
setFunctionalCursor();
KisTool::mouseMoveEvent(event);
return;
}
}
void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event)
{
KisTool::mouseReleaseEvent(event);
}
void KisToolTransform::applyTransform()
{
slotApplyTransform();
}
KisToolTransform::TransformToolMode KisToolTransform::transformMode() const
{
TransformToolMode mode = FreeTransformMode;
switch (m_currentArgs.mode())
{
case ToolTransformArgs::FREE_TRANSFORM:
mode = FreeTransformMode;
break;
case ToolTransformArgs::WARP:
mode = WarpTransformMode;
break;
case ToolTransformArgs::CAGE:
mode = CageTransformMode;
break;
case ToolTransformArgs::LIQUIFY:
mode = LiquifyTransformMode;
break;
case ToolTransformArgs::PERSPECTIVE_4POINT:
mode = PerspectiveTransformMode;
break;
default:
KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
}
return mode;
}
double KisToolTransform::translateX() const
{
return m_currentArgs.transformedCenter().x();
}
double KisToolTransform::translateY() const
{
return m_currentArgs.transformedCenter().y();
}
double KisToolTransform::rotateX() const
{
return m_currentArgs.aX();
}
double KisToolTransform::rotateY() const
{
return m_currentArgs.aY();
}
double KisToolTransform::rotateZ() const
{
return m_currentArgs.aZ();
}
double KisToolTransform::scaleX() const
{
return m_currentArgs.scaleX();
}
double KisToolTransform::scaleY() const
{
return m_currentArgs.scaleY();
}
double KisToolTransform::shearX() const
{
return m_currentArgs.shearX();
}
double KisToolTransform::shearY() const
{
return m_currentArgs.shearY();
}
KisToolTransform::WarpType KisToolTransform::warpType() const
{
switch(m_currentArgs.warpType()) {
case KisWarpTransformWorker::AFFINE_TRANSFORM:
return AffineWarpType;
case KisWarpTransformWorker::RIGID_TRANSFORM:
return RigidWarpType;
case KisWarpTransformWorker::SIMILITUDE_TRANSFORM:
return SimilitudeWarpType;
default:
return RigidWarpType;
}
}
double KisToolTransform::warpFlexibility() const
{
return m_currentArgs.alpha();
}
int KisToolTransform::warpPointDensity() const
{
return m_currentArgs.numPoints();
}
void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode)
{
ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM;
switch (newMode) {
case FreeTransformMode:
mode = ToolTransformArgs::FREE_TRANSFORM;
break;
case WarpTransformMode:
mode = ToolTransformArgs::WARP;
break;
case CageTransformMode:
mode = ToolTransformArgs::CAGE;
break;
case LiquifyTransformMode:
mode = ToolTransformArgs::LIQUIFY;
break;
case PerspectiveTransformMode:
mode = ToolTransformArgs::PERSPECTIVE_4POINT;
break;
default:
KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
}
if( mode != m_currentArgs.mode() ) {
if( newMode == FreeTransformMode ) {
m_optionsWidget->slotSetFreeTransformModeButtonClicked( true );
} else if( newMode == WarpTransformMode ) {
m_optionsWidget->slotSetWarpModeButtonClicked( true );
} else if( newMode == CageTransformMode ) {
m_optionsWidget->slotSetCageModeButtonClicked( true );
} else if( newMode == LiquifyTransformMode ) {
m_optionsWidget->slotSetLiquifyModeButtonClicked( true );
} else if( newMode == PerspectiveTransformMode ) {
m_optionsWidget->slotSetPerspectiveModeButtonClicked( true );
}
emit transformModeChanged();
}
}
void KisToolTransform::setRotateX( double rotation )
{
m_currentArgs.setAX( normalizeAngle(rotation) );
}
void KisToolTransform::setRotateY( double rotation )
{
m_currentArgs.setAY( normalizeAngle(rotation) );
}
void KisToolTransform::setRotateZ( double rotation )
{
m_currentArgs.setAZ( normalizeAngle(rotation) );
}
void KisToolTransform::setWarpType( KisToolTransform::WarpType type )
{
switch( type ) {
case RigidWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM);
break;
case AffineWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM);
break;
case SimilitudeWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM);
break;
default:
break;
}
}
void KisToolTransform::setWarpFlexibility( double flexibility )
{
m_currentArgs.setAlpha( flexibility );
}
void KisToolTransform::setWarpPointDensity( int density )
{
m_optionsWidget->slotSetWarpDensity(density);
}
bool KisToolTransform::tryInitArgsFromNode(KisNodeSP node)
{
bool result = false;
if (KisTransformMaskSP mask =
dynamic_cast<KisTransformMask*>(node.data())) {
KisTransformMaskParamsInterfaceSP savedParams =
mask->transformParams();
KisTransformMaskAdapter *adapter =
dynamic_cast<KisTransformMaskAdapter*>(savedParams.data());
if (adapter) {
m_currentArgs = adapter->transformArgs();
result = true;
}
}
return result;
}
bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode)
{
bool result = false;
const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand();
KisNodeSP oldRootNode;
KisNodeList oldTransformedNodes;
if (lastCommand &&
TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode, &oldTransformedNodes) &&
args->mode() == mode &&
oldRootNode == currentNode) {
KisNodeList perspectiveTransformedNodes = fetchNodesList(mode, currentNode, m_workRecursively);
if (KritaUtils::compareListsUnordered(oldTransformedNodes, perspectiveTransformedNodes)) {
args->saveContinuedState();
image()->undoAdapter()->undoLastCommand();
// FIXME: can we make it async?
image()->waitForDone();
forceRepaintDelayedLayers(oldRootNode);
result = true;
}
}
return result;
}
void KisToolTransform::resetArgsForMode(ToolTransformArgs::TransformMode mode)
{
// NOTE: we are requesting an old value of m_currentArgs variable
// here, which is global, don't forget about this on higher
// levels.
QString filterId = m_currentArgs.filterId();
m_currentArgs = ToolTransformArgs();
m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric());
m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric());
if (mode == ToolTransformArgs::FREE_TRANSFORM) {
m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM);
} else if (mode == ToolTransformArgs::WARP) {
m_currentArgs.setMode(ToolTransformArgs::WARP);
m_optionsWidget->setDefaultWarpPoints();
m_currentArgs.setEditingTransformPoints(false);
} else if (mode == ToolTransformArgs::CAGE) {
m_currentArgs.setMode(ToolTransformArgs::CAGE);
m_currentArgs.setEditingTransformPoints(true);
} else if (mode == ToolTransformArgs::LIQUIFY) {
m_currentArgs.setMode(ToolTransformArgs::LIQUIFY);
const QRect srcRect = m_transaction.originalRect().toAlignedRect();
if (!srcRect.isEmpty()) {
m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect());
}
} else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) {
m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT);
}
}
void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode)
{
resetArgsForMode(mode);
initGuiAfterTransformMode();
}
void KisToolTransform::initGuiAfterTransformMode()
{
currentStrategy()->externalConfigChanged();
outlineChanged();
updateOptionWidget();
updateApplyResetAvailability();
}
void KisToolTransform::updateSelectionPath()
{
m_selectionPath = QPainterPath();
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
QPainterPath selectionOutline;
KisSelectionSP selection = resources->activeSelection();
if (selection && selection->outlineCacheValid()) {
selectionOutline = selection->outlineCache();
} else if (m_selectedPortionCache) {
selectionOutline.addRect(m_selectedPortionCache->exactBounds());
}
const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
m_selectionPath = i2f.map(selectionOutline);
}
void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice)
{
QImage origImg;
m_selectedPortionCache = previewDevice;
QTransform thumbToImageTransform;
const int maxSize = 2000;
QRect srcRect(m_transaction.originalRect().toAlignedRect());
int x, y, w, h;
srcRect.getRect(&x, &y, &w, &h);
if (m_selectedPortionCache) {
if (w > maxSize || h > maxSize) {
qreal scale = qreal(maxSize) / (w > h ? w : h);
QTransform scaleTransform = QTransform::fromScale(scale, scale);
QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
origImg = m_selectedPortionCache->
createThumbnail(thumbRect.width(),
thumbRect.height(),
srcRect, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
thumbToImageTransform = scaleTransform.inverted();
} else {
origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
thumbToImageTransform = QTransform();
}
}
// init both strokes since the thumbnail is initialized only once
// during the stroke
m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform);
}
void KisToolTransform::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisTool::activate(toolActivation, shapes);
if (currentNode()) {
m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
}
startStroke(ToolTransformArgs::FREE_TRANSFORM, false);
}
void KisToolTransform::deactivate()
{
endStroke();
m_canvas->updateCanvas();
KisTool::deactivate();
}
void KisToolTransform::requestUndoDuringStroke()
{
if (!m_strokeData.strokeId()) return;
if (m_changesTracker.isEmpty()) {
cancelStroke();
} else {
m_changesTracker.requestUndo();
}
}
void KisToolTransform::requestStrokeEnd()
{
endStroke();
}
void KisToolTransform::requestStrokeCancellation()
{
if (m_currentArgs.isIdentity()) {
cancelStroke();
} else {
slotResetTransform();
}
}
void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset)
{
Q_ASSERT(!m_strokeData.strokeId());
/**
* FIXME: The transform tool is not completely asynchronous, it
* needs the content of the layer for creation of the stroke
* strategy. It means that we cannot start a new stroke until the
* previous one is finished. Ideally, we should create the
* m_selectedPortionCache and m_selectionPath somewhere in the
* stroke and pass it to the tool somehow. But currently, we will
* just disable starting a new stroke asynchronously
*/
-
- if (image()->tryBarrierLock()) {
- image()->unlock();
- } else {
+ if (!blockUntilOperationsFinished()) {
return;
}
-
// set up and null checks before we do anything
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
KisNodeSP currentNode = resources->currentNode();
if (!currentNode || !currentNode->isEditable()) return;
// some layer types cannot be transformed. Give a message and return if a user tries it
if (currentNode->inherits("KisColorizeMask") ||
currentNode->inherits("KisFileLayer") ||
currentNode->inherits("KisCloneLayer")) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Layer type cannot use the transform tool"),
koIcon("object-locked"), 4000, KisFloatingMessage::High);
// force-reset transform mode to default
initTransformMode(mode);
return;
}
// this goes after the floating message check since the fetchNodesList()
// says a file layer is "empty" and messes up the floating message check above
QList<KisNodeSP> nodesList = fetchNodesList(mode, currentNode, m_workRecursively);
if (nodesList.isEmpty()) return;
/**
* We must ensure that the currently selected subtree
* has finished all its updates.
*/
forceRepaintDelayedLayers(currentNode);
ToolTransformArgs fetchedArgs;
bool fetchedFromCommand = false;
if (!forceReset) {
fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode);
}
if (m_optionsWidget) {
m_workRecursively = m_optionsWidget->workRecursively() ||
!currentNode->paintDevice();
}
KisSelectionSP selection = resources->activeSelection();
/**
* When working with transform mask, selections are not
* taken into account.
*/
if (selection && dynamic_cast<KisTransformMask*>(currentNode.data())) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Selections are not used when editing transform masks "),
QIcon(), 4000, KisFloatingMessage::Low);
selection = 0;
}
TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, nodesList, selection, image().data());
connect(strategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP)));
QRect srcRect;
if (selection) {
srcRect = selection->selectedExactRect();
} else {
srcRect = QRect();
Q_FOREACH (KisNodeSP node, nodesList) {
// group layers may have a projection of layers
// that are locked and will not be transformed
if (node->inherits("KisGroupLayer")) continue;
- srcRect |= node->exactBounds();
+ if (const KisTransformMask *mask = dynamic_cast<const KisTransformMask*>(node.data())) {
+ srcRect |= mask->sourceDataBounds();
+ } else {
+ srcRect |= node->exactBounds();
+ }
}
}
m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList);
if (!forceReset && fetchedFromCommand) {
m_currentArgs = fetchedArgs;
} else if (forceReset || !tryInitArgsFromNode(currentNode)) {
resetArgsForMode(mode);
}
m_strokeData = StrokeData(image()->startStroke(strategy));
image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::PreparePreviewData());
bool haveInvisibleNodes = clearDevices(nodesList);
if (haveInvisibleNodes) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "),
QIcon(), 4000, KisFloatingMessage::Low);
}
Q_ASSERT(m_changesTracker.isEmpty());
commitChanges();
slotPreviewDeviceGenerated(0);
}
void KisToolTransform::endStroke()
{
if (!m_strokeData.strokeId()) return;
if (!m_currentArgs.isIdentity()) {
transformClearedDevices();
image()->addJob(m_strokeData.strokeId(),
new TransformStrokeStrategy::TransformData(
TransformStrokeStrategy::TransformData::SELECTION,
m_currentArgs,
m_transaction.rootNode())); // root node is used for progress only
image()->endStroke(m_strokeData.strokeId());
} else {
image()->cancelStroke(m_strokeData.strokeId());
}
m_strokeData.clear();
m_changesTracker.reset();
m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
outlineChanged();
}
void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device)
{
if (device && device->exactBounds().isEmpty()) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Cannot transform empty layer "),
QIcon(), 1000, KisFloatingMessage::Medium);
cancelStroke();
} else {
initThumbnailImage(device);
updateSelectionPath();
initGuiAfterTransformMode();
}
}
void KisToolTransform::cancelStroke()
{
if (!m_strokeData.strokeId()) return;
if (m_currentArgs.continuedTransform()) {
m_currentArgs.restoreContinuedState();
endStroke();
} else {
image()->cancelStroke(m_strokeData.strokeId());
m_strokeData.clear();
m_changesTracker.reset();
m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
outlineChanged();
}
}
void KisToolTransform::commitChanges()
{
if (!m_strokeData.strokeId()) return;
m_changesTracker.commitConfig(toQShared(m_currentArgs.clone()));
}
void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status)
{
const ToolTransformArgs *newArgs = dynamic_cast<const ToolTransformArgs*>(status.data());
KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs);
*m_transaction.currentConfig() = *newArgs;
slotUiChangedConfig();
updateOptionWidget();
}
QList<KisNodeSP> KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive)
{
QList<KisNodeSP> result;
auto fetchFunc =
[&result, mode, root] (KisNodeSP node) {
if (node->isEditable(node == root) &&
(!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) &&
!node->inherits("KisFileLayer") &&
(!node->inherits("KisTransformMask") || node == root)) {
result << node;
}
};
if (recursive) {
KisLayerUtils::recursiveApplyNodes(root, fetchFunc);
} else {
fetchFunc(root);
}
return result;
}
bool KisToolTransform::clearDevices(const QList<KisNodeSP> &nodes)
{
bool haveInvisibleNodes = false;
Q_FOREACH (KisNodeSP node, nodes) {
haveInvisibleNodes |= !node->visible(false);
image()->addJob(m_strokeData.strokeId(),
new TransformStrokeStrategy::ClearSelectionData(node));
/**
* It might happen that the editablity state of the node would
* change during the stroke, so we need to save the set of
* applicable nodes right in the beginning of the processing
*/
m_strokeData.addClearedNode(node);
}
return haveInvisibleNodes;
}
void KisToolTransform::transformClearedDevices()
{
Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) {
KIS_ASSERT_RECOVER_RETURN(node);
image()->addJob(m_strokeData.strokeId(),
new TransformStrokeStrategy::TransformData(
TransformStrokeStrategy::TransformData::PAINT_DEVICE,
m_currentArgs,
node));
}
}
QWidget* KisToolTransform::createOptionWidget() {
m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0);
Q_CHECK_PTR(m_optionsWidget);
m_optionsWidget->setObjectName(toolId() + " option widget");
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
connect(m_optionsWidget, SIGNAL(sigConfigChanged()),
this, SLOT(slotUiChangedConfig()));
connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
this, SLOT(slotApplyTransform()));
connect(m_optionsWidget, SIGNAL(sigResetTransform()),
this, SLOT(slotResetTransform()));
connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
this, SLOT(slotRestartTransform()));
connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
this, SLOT(slotEditingFinished()));
connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX()));
connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY()));
connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW()));
connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW()));
connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType()));
connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType()));
connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType()));
connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType()));
connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType()));
connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform()));
connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform()));
updateOptionWidget();
return m_optionsWidget;
}
void KisToolTransform::updateOptionWidget()
{
if (!m_optionsWidget) return;
if (!currentNode()) {
m_optionsWidget->setEnabled(false);
return;
}
else {
m_optionsWidget->setEnabled(true);
m_optionsWidget->updateConfig(m_currentArgs);
}
}
void KisToolTransform::updateApplyResetAvailability()
{
if (m_optionsWidget) {
m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity());
}
}
void KisToolTransform::slotUiChangedConfig()
{
if (mode() == KisTool::PAINT_MODE) return;
currentStrategy()->externalConfigChanged();
if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
m_currentArgs.saveLiquifyTransformMode();
}
outlineChanged();
updateApplyResetAvailability();
}
void KisToolTransform::slotApplyTransform()
{
QApplication::setOverrideCursor(KisCursor::waitCursor());
endStroke();
QApplication::restoreOverrideCursor();
}
void KisToolTransform::slotResetTransform()
{
if (m_currentArgs.continuedTransform()) {
ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode();
/**
* Our reset transform button can be used for two purposes:
*
* 1) Reset current transform to the initial one, which was
* loaded from the previous user action.
*
* 2) Reset transform frame to infinity when the frame is unchanged
*/
const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
if (transformDiffers &&
m_currentArgs.continuedTransform()->mode() == savedMode) {
m_currentArgs.restoreContinuedState();
initGuiAfterTransformMode();
slotEditingFinished();
} else {
KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root();
cancelStroke();
image()->waitForDone();
forceRepaintDelayedLayers(root);
startStroke(savedMode, true);
KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform());
}
} else {
initTransformMode(m_currentArgs.mode());
slotEditingFinished();
}
}
void KisToolTransform::slotRestartTransform()
{
if (!m_strokeData.strokeId()) return;
KisNodeSP root = m_transaction.rootNode();
KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
ToolTransformArgs savedArgs(m_currentArgs);
cancelStroke();
image()->waitForDone();
forceRepaintDelayedLayers(root);
startStroke(savedArgs.mode(), true);
}
void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(root);
KisLayerUtils::forceAllDelayedNodesUpdate(root);
image()->waitForDone();
}
void KisToolTransform::slotEditingFinished()
{
commitChanges();
}
void KisToolTransform::slotUpdateToWarpType()
{
setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode);
}
void KisToolTransform::slotUpdateToPerspectiveType()
{
setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode);
}
void KisToolTransform::slotUpdateToFreeTransformType()
{
setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode);
}
void KisToolTransform::slotUpdateToLiquifyType()
{
setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode);
}
void KisToolTransform::slotUpdateToCageType()
{
setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode);
}
void KisToolTransform::setShearY(double shear)
{
m_optionsWidget->slotSetShearY(shear);
}
void KisToolTransform::setShearX(double shear)
{
m_optionsWidget->slotSetShearX(shear);
}
void KisToolTransform::setScaleY(double scale)
{
m_optionsWidget->slotSetScaleY(scale);
}
void KisToolTransform::setScaleX(double scale)
{
m_optionsWidget->slotSetScaleX(scale);
}
void KisToolTransform::setTranslateY(double translation)
{
m_optionsWidget->slotSetTranslateY(translation);
}
void KisToolTransform::setTranslateX(double translation)
{
m_optionsWidget->slotSetTranslateX(translation);
}
diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h
index 014cff7a6d..e7cb9165c0 100644
--- a/sdk/tests/filestest.h
+++ b/sdk/tests/filestest.h
@@ -1,118 +1,377 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* 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 FILESTEST
#define FILESTEST
#include "testutil.h"
#include <QDir>
#include <kaboutdata.h>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <KisPart.h>
#include <kis_image.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <QTemporaryFile>
#include <QFileInfo>
#include <QApplication>
+#include <QFile>
+#include <QFileDevice>
+#include <QIODevice>
namespace TestUtil
{
-void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0)
+void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0, bool showDebug = true)
{
QDir dirSources(_dirname);
QStringList failuresFileInfo;
QStringList failuresDocImage;
QStringList failuresCompare;
Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) {
qDebug() << sourceFileInfo.fileName();
if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) {
continue;
}
if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) {
QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png");
if (!resultFileInfo.exists()) {
failuresFileInfo << resultFileInfo.fileName();
continue;
}
KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
- KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString());
+ KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString());
Q_UNUSED(status);
if (!doc->image()) {
failuresDocImage << sourceFileInfo.fileName();
continue;
}
QString id = doc->image()->colorSpace()->id();
if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") {
dbgKrita << "Images need conversion";
doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(),
KoColorConversionTransformation::IntentAbsoluteColorimetric,
KoColorConversionTransformation::NoOptimization);
}
qApp->processEvents();
doc->image()->waitForDone();
QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
QImage resultImage(resultFileInfo.absoluteFilePath());
resultImage = resultImage.convertToFormat(QImage::Format_ARGB32);
sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32);
QPoint pt;
- if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels)) {
+ if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels, showDebug)) {
failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1();
sourceImage.save(sourceFileInfo.fileName() + ".png");
resultImage.save(resultFileInfo.fileName() + ".expected.png");
continue;
}
delete doc;
}
}
if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) {
return;
}
qWarning() << "Comparison failures: " << failuresCompare;
qWarning() << "No image failures: " << failuresDocImage;
qWarning() << "No comparison image: " << failuresFileInfo;
QFAIL("Failed testing files");
}
+
+void prepareFile(QFileInfo sourceFileInfo, bool removePermissionToWrite, bool removePermissionToRead)
+{
+
+ QFileDevice::Permissions permissionsBefore;
+ if (sourceFileInfo.exists()) {
+ permissionsBefore = QFile::permissions(sourceFileInfo.absoluteFilePath());
+ ENTER_FUNCTION() << permissionsBefore;
+ } else {
+ QFile file(sourceFileInfo.absoluteFilePath());
+ bool opened = file.open(QIODevice::ReadWrite);
+ if (!opened) {
+ qDebug() << "The file cannot be opened/created: " << file.error() << file.errorString();
+ }
+ permissionsBefore = file.permissions();
+ file.close();
+ }
+ QFileDevice::Permissions permissionsNow = permissionsBefore;
+ if (removePermissionToRead) {
+ permissionsNow = permissionsBefore &
+ (~QFileDevice::ReadUser & ~QFileDevice::ReadOwner
+ & ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther);
+ }
+ if (removePermissionToWrite) {
+ permissionsNow = permissionsBefore &
+ (~QFileDevice::WriteUser & ~QFileDevice::WriteOwner
+ & ~QFileDevice::WriteGroup & ~QFileDevice::WriteOther);
+ }
+
+ QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsNow);
+
+}
+
+void restorePermissionsToReadAndWrite(QFileInfo sourceFileInfo)
+{
+ QFileDevice::Permissions permissionsNow = sourceFileInfo.permissions();
+ QFileDevice::Permissions permissionsAfter = permissionsNow
+ | (QFileDevice::ReadUser | QFileDevice::ReadOwner
+ | QFileDevice::ReadGroup | QFileDevice::ReadOther)
+ | (QFileDevice::WriteUser | QFileDevice::WriteOwner
+ | QFileDevice::WriteGroup | QFileDevice::WriteOther);
+ QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter);
+}
+
+
+void testImportFromWriteonly(const QString& _dirname, QString mimetype = "")
+{
+ QString writeonlyFilename = _dirname + "writeonlyFile.txt";
+ QFileInfo sourceFileInfo(writeonlyFilename);
+
+ prepareFile(sourceFileInfo, false, true);
+
+ KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
+
+ KisImportExportManager manager(doc);
+ doc->setFileBatchMode(true);
+
+ KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype);
+ qDebug() << "import result = " << status;
+
+ QString failMessage = "";
+ bool fail = false;
+
+ if (status == ImportExportCodes::FileFormatIncorrect) {
+ qDebug() << "Make sure you set the correct mimetype in the test case.";
+ failMessage = "Incorrect status.";
+ fail = true;
+ }
+
+ qApp->processEvents();
+
+ if (doc->image()) {
+ doc->image()->waitForDone();
+ }
+
+ delete doc;
+
+ restorePermissionsToReadAndWrite(sourceFileInfo);
+
+ QVERIFY(!status.isOk());
+ if (fail) {
+ QFAIL(failMessage.toUtf8());
+ }
+
+}
+
+
+void testExportToReadonly(const QString& _dirname, QString mimetype = "", bool useDocumentExport=false)
+{
+ QString readonlyFilename = _dirname + "readonlyFile.txt";
+
+ QFileInfo sourceFileInfo(readonlyFilename);
+ prepareFile(sourceFileInfo, true, false);
+
+ KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
+
+ KisImportExportManager manager(doc);
+ doc->setFileBatchMode(true);
+
+ KisImportExportErrorCode status = ImportExportCodes::OK;
+ QString failMessage = "";
+ bool fail = false;
+
+ {
+ MaskParent p;
+ ENTER_FUNCTION() << doc->image();
+
+ doc->setCurrentImage(p.image);
+
+ if (useDocumentExport) {
+ bool result = doc->exportDocumentSync(QUrl(sourceFileInfo.absoluteFilePath()), mimetype.toUtf8());
+ status = result ? ImportExportCodes::OK : ImportExportCodes::Failure;
+ } else {
+ status = manager.exportDocument(sourceFileInfo.absoluteFilePath(), sourceFileInfo.absoluteFilePath(), mimetype.toUtf8());
+ }
+
+ qDebug() << "export result = " << status;
+
+ if (!useDocumentExport && status == ImportExportCodes::FileFormatIncorrect) {
+ qDebug() << "Make sure you set the correct mimetype in the test case.";
+ failMessage = "Incorrect status.";
+ fail = true;
+ }
+
+
+ qApp->processEvents();
+
+ if (doc->image()) {
+ doc->image()->waitForDone();
+ }
+
+ }
+ delete doc;
+
+ restorePermissionsToReadAndWrite(sourceFileInfo);
+
+ QVERIFY(!status.isOk());
+ if (fail) {
+ QFAIL(failMessage.toUtf8());
+ }
+}
+
+
+
+void testImportIncorrectFormat(const QString& _dirname, QString mimetype = "")
+{
+ QString incorrectFormatFilename = _dirname + "incorrectFormatFile.txt";
+ QFileInfo sourceFileInfo(incorrectFormatFilename);
+
+ prepareFile(sourceFileInfo, false, false);
+
+ KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
+
+ KisImportExportManager manager(doc);
+ doc->setFileBatchMode(true);
+
+ KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype);
+ qDebug() << "import result = " << status;
+
+
+ qApp->processEvents();
+
+ if (doc->image()) {
+ doc->image()->waitForDone();
+ }
+
+ delete doc;
+
+ QVERIFY(!status.isOk());
+ QVERIFY(status == KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect)
+ || status == KisImportExportErrorCode(ImportExportCodes::ErrorWhileReading)); // in case the filter doesn't know if it can't read or just parse
+
+}
+
+
+void testExportToColorSpace(const QString& _dirname, QString mimetype, const KoColorSpace* space, KisImportExportErrorCode expected, bool useDocumentExport=false)
+{
+ QString colorspaceFilename = _dirname + "colorspace.txt";
+
+ QFileInfo sourceFileInfo(colorspaceFilename);
+ prepareFile(sourceFileInfo, true, true);
+ restorePermissionsToReadAndWrite(sourceFileInfo);
+
+ KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
+
+ KisImportExportManager manager(doc);
+ doc->setFileBatchMode(true);
+
+ KisImportExportErrorCode statusExport = ImportExportCodes::OK;
+ KisImportExportErrorCode statusImport = ImportExportCodes::OK;
+
+ QString failMessage = "";
+ bool fail = false;
+
+ {
+ MaskParent p;
+
+ doc->setCurrentImage(p.image);
+ doc->image()->convertImageColorSpace(space, KoColorConversionTransformation::Intent::IntentPerceptual, KoColorConversionTransformation::ConversionFlag::Empty);
+
+
+ if (useDocumentExport) {
+ bool result = doc->exportDocumentSync(QUrl(QString("file:") + QString(colorspaceFilename)), mimetype.toUtf8());
+ statusExport = result ? ImportExportCodes::OK : ImportExportCodes::Failure;
+ } else {
+ statusExport = manager.exportDocument(colorspaceFilename, colorspaceFilename, mimetype.toUtf8());
+ }
+
+ statusImport = manager.importDocument(colorspaceFilename, mimetype.toUtf8());
+ if (!(statusImport == ImportExportCodes::OK)) {
+ fail = true;
+ failMessage = "Incorrect status";
+ }
+
+ bool mismatch = (*(doc->image()->colorSpace()) != *space) || (doc->image()->colorSpace()->profile() != space->profile());
+ if (mismatch) {
+ qDebug() << "Document color space = " << (doc->image()->colorSpace())->id();
+ qDebug() << "Saved color space = " << space->id();
+ fail = true;
+ failMessage = "Mismatch of color spaces";
+ }
+
+ if (!useDocumentExport && statusExport == ImportExportCodes::FileFormatIncorrect) {
+ qDebug() << "Make sure you set the correct mimetype in the test case.";
+ failMessage = "Incorrect status.";
+ fail = true;
+ }
+
+
+ qApp->processEvents();
+
+ if (doc->image()) {
+ doc->image()->waitForDone();
+ }
+
+ }
+ delete doc;
+
+ QFile::remove(colorspaceFilename);
+
+ if (fail) {
+ QFAIL(failMessage.toUtf8());
+ }
+
+ QVERIFY(statusExport.isOk());
+ if (!useDocumentExport) {
+ QVERIFY(statusExport == expected);
+ }
+
+}
+
+
+
+
+
+
}
#endif
diff --git a/sdk/tests/qimage_test_util.h b/sdk/tests/qimage_test_util.h
index e1d0a294ce..fffbb596fc 100644
--- a/sdk/tests/qimage_test_util.h
+++ b/sdk/tests/qimage_test_util.h
@@ -1,245 +1,246 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef QIMAGE_TEST_UTIL_H
#define QIMAGE_TEST_UTIL_H
#ifdef FILES_OUTPUT_DIR
#include <QProcessEnvironment>
#include <QDir>
namespace TestUtil {
inline QString fetchExternalDataFileName(const QString relativeFileName)
{
static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
static QString unittestsDataDirPath = "KRITA_UNITTESTS_DATA_DIR";
QString path;
if (!env.contains(unittestsDataDirPath)) {
warnKrita << "Environment variable" << unittestsDataDirPath << "is not set";
return QString();
} else {
path = env.value(unittestsDataDirPath, "");
}
QString filename =
path +
QDir::separator() +
relativeFileName;
return filename;
}
inline QString fetchDataFileLazy(const QString relativeFileName, bool externalTest = false)
{
if (externalTest) {
return fetchExternalDataFileName(relativeFileName);
} else {
QString filename =
QString(FILES_DATA_DIR) +
QDir::separator() +
relativeFileName;
if (QFileInfo(filename).exists()) {
return filename;
}
filename =
QString(FILES_DEFAULT_DATA_DIR) +
QDir::separator() +
relativeFileName;
if (QFileInfo(filename).exists()) {
return filename;
}
}
return QString();
}
// quint8 arguments are automatically converted into int
inline bool compareChannels(int ch1, int ch2, int fuzzy)
{
return qAbs(ch1 - ch2) <= fuzzy;
}
-inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0)
+inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0, bool showDebug = true)
{
// QTime t;
// t.start();
const int w1 = image1.width();
const int h1 = image1.height();
const int w2 = image2.width();
const int h2 = image2.height();
const int bytesPerLine = image1.bytesPerLine();
if (w1 != w2 || h1 != h2) {
pt.setX(-1);
pt.setY(-1);
qDebug() << "Images have different sizes" << image1.size() << image2.size();
return false;
}
int numFailingPixels = 0;
for (int y = 0; y < h1; ++y) {
const QRgb * const firstLine = reinterpret_cast<const QRgb *>(image2.scanLine(y));
const QRgb * const secondLine = reinterpret_cast<const QRgb *>(image1.scanLine(y));
if (memcmp(firstLine, secondLine, bytesPerLine) != 0) {
for (int x = 0; x < w1; ++x) {
const QRgb a = firstLine[x];
const QRgb b = secondLine[x];
const bool same =
compareChannels(qRed(a), qRed(b), fuzzy) &&
compareChannels(qGreen(a), qGreen(b), fuzzy) &&
compareChannels(qBlue(a), qBlue(b), fuzzy);
const bool sameAlpha = compareChannels(qAlpha(a), qAlpha(b), fuzzyAlpha);
const bool bothTransparent = sameAlpha && qAlpha(a)==0;
if (!bothTransparent && (!same || !sameAlpha)) {
pt.setX(x);
pt.setY(y);
numFailingPixels++;
- qDebug() << " Different at" << pt
- << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a)
- << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b)
- << "fuzzy" << fuzzy
- << "fuzzyAlpha" << fuzzyAlpha
- << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )";
-
+ if (showDebug) {
+ qDebug() << " Different at" << pt
+ << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a)
+ << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b)
+ << "fuzzy" << fuzzy
+ << "fuzzyAlpha" << fuzzyAlpha
+ << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )";
+ }
if (numFailingPixels > maxNumFailingPixels) {
return false;
}
}
}
}
}
// qDebug() << "compareQImages time elapsed:" << t.elapsed();
// qDebug() << "Images are identical";
return true;
}
inline bool checkQImageImpl(bool externalTest,
const QImage &srcImage, const QString &testName,
const QString &prefix, const QString &name,
int fuzzy, int fuzzyAlpha, int maxNumFailingPixels)
{
QImage image = srcImage.convertToFormat(QImage::Format_ARGB32);
if (fuzzyAlpha == -1) {
fuzzyAlpha = fuzzy;
}
QString filename(prefix + "_" + name + ".png");
QString dumpName(prefix + "_" + name + "_expected.png");
const QString standardPath =
testName + QDir::separator() +
prefix + QDir::separator() + filename;
QString fullPath = fetchDataFileLazy(standardPath, externalTest);
if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
// Try without the testname subdirectory
fullPath = fetchDataFileLazy(prefix + QDir::separator() +
filename,
externalTest);
}
if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
// Try without the prefix subdirectory
fullPath = fetchDataFileLazy(testName + QDir::separator() +
filename,
externalTest);
}
if (!QFileInfo(fullPath).exists()) {
fullPath = "";
}
bool canSkipExternalTest = fullPath.isEmpty() && externalTest;
QImage ref(fullPath);
bool valid = true;
QPoint t;
if(!compareQImages(t, image, ref, fuzzy, fuzzyAlpha, maxNumFailingPixels)) {
bool saveStandardResults = true;
if (canSkipExternalTest) {
static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
static QString writeUnittestsVar = "KRITA_WRITE_UNITTESTS";
int writeUnittests = env.value(writeUnittestsVar, "0").toInt();
if (writeUnittests) {
QString path = fetchExternalDataFileName(standardPath);
QFileInfo pathInfo(path);
QDir directory;
directory.mkpath(pathInfo.path());
qDebug() << "--- Saving reference image:" << name << path;
image.save(path);
saveStandardResults = false;
} else {
qDebug() << "--- External image not found. Skipping..." << name;
}
} else {
qDebug() << "--- Wrong image:" << name;
valid = false;
}
if (saveStandardResults) {
image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + filename);
ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + dumpName);
}
}
return valid;
}
inline bool checkQImage(const QImage &image, const QString &testName,
const QString &prefix, const QString &name,
int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
{
return checkQImageImpl(false, image, testName,
prefix, name,
fuzzy, fuzzyAlpha, maxNumFailingPixels);
}
inline bool checkQImageExternal(const QImage &image, const QString &testName,
const QString &prefix, const QString &name,
int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
{
return checkQImageImpl(true, image, testName,
prefix, name,
fuzzy, fuzzyAlpha, maxNumFailingPixels);
}
}
#endif // FILES_OUTPUT_DIR
#endif // QIMAGE_TEST_UTIL_H