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 index 13826a32da..100370ce50 100644 --- 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 @@ -1,37 +1,37 @@ -From 9081571e58ceeaa4c623c83ecb41b04dfc7d90f9 Mon Sep 17 00:00:00 2001 +From 0cda446fe4dea3046cb0cea820b9934164df8f19 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov 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 +Subject: [PATCH 01/27] 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 +index 96e34fb4..ca6e0925 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(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 +2.22.0.windows.1 diff --git a/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch b/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch index db93c33e36..3097b63f33 100644 --- a/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch +++ b/3rdparty/ext_qt/0003-Hack-always-return-we-support-DIBV5.patch @@ -1,30 +1,30 @@ -From 6ce23924948d9697e07681bef44f0a47f9fd0d09 Mon Sep 17 00:00:00 2001 +From 025be9d8f4adcdccbc1fd4be329771cf2ef95942 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Tue, 21 Jun 2016 14:50:47 +0300 -Subject: [PATCH 3/4] Hack: always return we support DIBV5 +Subject: [PATCH 02/27] 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 ca6e0925..cff89586 100644 --- a/src/plugins/platforms/windows/qwindowsmime.cpp +++ b/src/plugins/platforms/windows/qwindowsmime.cpp @@ -1055,9 +1055,7 @@ QVector QWindowsMimeImage::formatsForMime(const QString &mimeType, co QVector 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(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 +2.22.0.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 index b341b34d39..79b53123af 100644 --- a/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch +++ b/3rdparty/ext_qt/0004-Fix-debug-on-openGL-ES.patch @@ -1,26 +1,26 @@ -From 4c1e4e693307c3169c6db488ad6bf6cff6aae864 Mon Sep 17 00:00:00 2001 +From 50ec48b6622f07550d71c694d037131c7b0a8c7b Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Mon, 11 Feb 2019 18:07:20 +0300 -Subject: [PATCH 4/4] Fix debug on openGL ES +Subject: [PATCH 03/27] 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 +index 2e628a2b..9f1bb768 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 +2.22.0.windows.1 diff --git a/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch b/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch index 8ebbfa4b3b..57d8103918 100644 --- a/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch +++ b/3rdparty/ext_qt/0005-cumulative-patch-for-hdr.patch @@ -1,1444 +1,3595 @@ +From 6ca9249292cc29ac4169e236f0d28f50006ba3cb Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sat, 8 Dec 2018 15:35:43 +0300 +Subject: [PATCH 04/27] 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 fb251da57..2e1799455 100644 +index fb251da5..2e179945 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 -#include -#include -#include -#include -#include +# include +# include +# include +# include +# include +# include +# include // WARNING: This is actually D3D12! +# include # 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 44da2bbe2..208845745 100644 +index 44da2bbe..20884574 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 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 64bdf9711..8157af580 100644 +index 64bdf971..8157af58 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 dcc98f2ec..b8ee63562 100644 +index dcc98f2e..b8ee6356 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 7657aef79..efd4dd1a2 100644 +index 7657aef7..efd4dd1a 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(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))), - mRenderTargetFormat(state.config->renderTargetFormat), + mColorSpace(static_cast(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 01d257324..ccb793d42 100644 +index 01d25732..ccb793d4 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 b0ef9abdd..f0e497b52 100644 +index b0ef9abd..f0e497b5 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(mDxgiAdapter); mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr); SafeRelease(dxgiAdapter2); + + IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject(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(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 a8c24e681..3516bf779 100644 +index a8c24e68..3516bf77 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 dcfd06484..fc967b90d 100644 +index dcfd0648..fc967b90 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 +#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(mSwapChain); } + if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4) + { + IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject(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(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(&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 eca068210..2a4b9ba27 100644 +index eca06821..2a4b9ba2 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 5394e3d3e..af52c41d0 100644 +index 5394e3d3..af52c41d 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(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 75c629886..58596169a 100644 +index 75c62988..58596169 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(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 9ddee45f0..ce4bb201e 100644 +index 9ddee45f..ce4bb201 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 13a3a9e28..858d7ee92 100644 +index 13a3a9e2..858d7ee9 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 00000000..dfbe3626 +--- /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 ++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 ++-#include ++-#include ++-#include ++-#include ++-#include +++# include +++# include +++# include +++# include +++# include +++# include +++# include // WARNING: This is actually D3D12! +++# include ++ # 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 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(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))), ++- mRenderTargetFormat(state.config->renderTargetFormat), +++ mColorSpace(static_cast(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(mDxgiAdapter); ++ mRenderer11DeviceCaps.supportsDXGI1_2 = (dxgiAdapter2 != nullptr); ++ SafeRelease(dxgiAdapter2); +++ +++ IDXGIAdapter3 *dxgiAdapter3 = d3d11::DynamicCastComObject(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(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 +++#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(mSwapChain); ++ } ++ +++ if (mRenderer->getRenderer11DeviceCaps().supportsDXGI1_4) +++ { +++ IDXGISwapChain3 *swapChain3 = d3d11::DynamicCastComObject(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(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(&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(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(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.22.0.windows.1 + +From 50d5c432a7a7b793750b07b7638f88cb587fb37b Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 13 Feb 2019 16:56:11 +0300 +Subject: [PATCH 05/27] 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 1a814ec21..fc8b9c4f4 100644 +index 1a814ec2..fc8b9c4f 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 ed63eb8bb..9ba6a29b7 100644 +index ed63eb8b..9ba6a29b 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 cae3d516c..ccdccb637 100644 +index cae3d516..ccdccb63 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 063e8115..4cd745ea 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 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(nativeWindow), nullptr); ++ static_cast(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 8a1e1dda..9f7742e6 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 cc6d93d3..61c0e287 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 0376e363..76a443a8 100644 +--- a/src/plugins/platforms/windows/qwindowswindow.cpp ++++ b/src/plugins/platforms/windows/qwindowswindow.cpp +@@ -2741,9 +2741,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.22.0.windows.1 + +From db1a31e31a3ab3df369135ba0665b557a60691f7 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Thu, 22 Nov 2018 15:47:48 +0300 +Subject: [PATCH 06/27] 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 b65df9dc8..5f6dbff29 100644 +index b65df9dc..5f6dbff2 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 +{ +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 vao; GLenum currentTarget; + + int colorSpaceConversion; + QVector 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 2f7c6b1a0..3c87e4e2b 100644 +index 2f7c6b1a..3c87e4e2 100644 --- a/src/gui/opengl/qopengltextureblitter.h +++ b/src/gui/opengl/qopengltextureblitter.h @@ -48,6 +48,9 @@ #include #include +// TODO: less includes!!! +#include + 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 c71d82546..8dd96a66b 100644 +index c71d8254..8dd96a66 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 ®i 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 de5ba964d..f8887bd4c 100644 +index de5ba964..f8887bd4 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 40400e2a1..5d44e6245 100644 +index 40400e2a..5d44e624 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/plugins/platforms/windows/qwindowseglcontext.cpp b/src/plugins/platforms/windows/qwindowseglcontext.cpp -index 063e81150..4cd745eac 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); +diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp +index 89f86015..0abf707e 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: -- if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress) -+ if (!eglGetError || !eglGetDisplay || !eglInitialize || !eglGetProcAddress || !eglQueryString) - return false; + GLuint textureId() const override; + QPlatformTextureList::Flags textureListFlags() override; ++ QSurfaceFormat::ColorSpace colorSpace() const override; - eglGetPlatformDisplayEXT = nullptr; -@@ -197,8 +198,15 @@ bool QWindowsLibGLESv2::init() - } + void initialize(); + void invokeUserPaint(); +@@ -609,6 +611,7 @@ public: + int requestedSamples; + bool inPaintGL; + GLenum textureFormat; ++ QSurfaceFormat::ColorSpace textureColorSpace; + }; - 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; + void QOpenGLWidgetPaintDevicePrivate::beginPaint() +@@ -695,6 +698,11 @@ QPlatformTextureList::Flags QOpenGLWidgetPrivate::textureListFlags() + return flags; } - 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()); ++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; } --void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, int *err) -+void *QWindowsEGLStaticContext::createWindowSurface(void *nativeWindow, void *nativeConfig, -+ QSurfaceFormat::ColorSpace colorSpace, int *err) - { - *err = 0; ++/*! ++ \return the declared color space of the internal texture of the widget. + -+ EGLint eglColorSpace = EGL_GL_COLORSPACE_LINEAR_KHR; -+ bool colorSpaceSupported = false; ++ The texture's color space will be used when composing the widget ++ into the root window surface. + -+ 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; -+ } ++ \note when the color space is set to QSurfaceFormat::DefaultColorSpace, ++ color conversion is effectively disabled. + -+ QVector attributes; ++ \since 5.99 ++ */ ++QSurfaceFormat::ColorSpace QOpenGLWidget::textureColorSpace() const ++{ ++ Q_D(const QOpenGLWidget); ++ return d->textureColorSpace; ++} + -+ if (colorSpaceSupported) { -+ attributes << EGL_GL_COLORSPACE << eglColorSpace; -+ } ++/*! ++ Sets a custom color space for the internal texture of the widget + -+ attributes << EGL_NONE; ++ The color space of the texture will be compared against the color ++ space of the root surface and conversion will be performed if needed. + -+ if (!colorSpaceSupported && colorSpace != QSurfaceFormat::DefaultColorSpace) { -+ qWarning().nospace() << __FUNCTION__ << ": Requested color space is not supported by EGL implementation: " << colorSpace << " (egl: 0x" << hex << eglColorSpace << ")"; -+ } ++ \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; ++} + - EGLSurface surface = libEGL.eglCreateWindowSurface(m_display, nativeConfig, -- static_cast(nativeWindow), nullptr); -+ static_cast(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 8a1e1ddae..9f7742e6f 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; + /*! + \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 9eb4a9ba..eff2d979 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 formatFromConfig(EGLDisplay display, EGLConfig config, const QSurfaceFormat &referenceFormat); -@@ -135,6 +136,9 @@ private: - EGLDisplay *display, EGLint *major, EGLint *minor); ++ QSurfaceFormat::ColorSpace textureColorSpace() const; ++ void setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace); ++ + bool isValid() const; - const EGLDisplay m_display; -+ bool m_hasSRGBColorSpaceSupport; -+ bool m_hasSCRGBColorSpaceSupport; -+ bool m_hasBt2020PQColorSpaceSupport; - }; + void makeCurrent(); +diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h +index 7e4ea2cc..1ff5af42 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 a32eb2a0..db603380 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()); + } - class QWindowsEGLContext : public QWindowsOpenGLContext + for (int i = 0; i < wd->children.size(); ++i) { +-- +2.22.0.windows.1 + +From cd8298f8ed3735396d9274a629df3664298d7554 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Tue, 4 Dec 2018 20:11:34 +0300 +Subject: [PATCH 07/27] 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 ed945ec4b..1c5be4415 100644 +index ed945ec4..1c5be441 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(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 e6f8aae8f..ce395dc5a 100644 +index e6f8aae8..ce395dc5 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/qwindowsopenglcontext.h b/src/plugins/platforms/windows/qwindowsopenglcontext.h -index cc6d93d35..61c0e2876 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/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp -index 0520f8893..b70b0bbe3 100644 +index 0520f889..b70b0bbe 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 824bcb1ad..33c9effa2 100644 +index 824bcb1a..33c9effa 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; } -diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp -index 0376e363f..76a443a89 100644 ---- a/src/plugins/platforms/windows/qwindowswindow.cpp -+++ b/src/plugins/platforms/windows/qwindowswindow.cpp -@@ -2741,9 +2741,13 @@ void *QWindowsWindow::surface(void *nativeConfig, int *err) - return 0; - #endif - #ifndef QT_NO_OPENGL +-- +2.22.0.windows.1 + +From f8cf8c0cf6bde2e81a31b3fabd4c435c87d10b1d Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sun, 10 Feb 2019 22:55:59 +0300 +Subject: [PATCH 08/27] 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 00000000..a84b676f +--- /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 ++#include ++ ++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); ++} + - 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; -diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp -index 89f860150..0abf707e0 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 ++KisGLImageF16::KisGLImageF16(int width, int height, bool clearPixels) ++ : KisGLImageF16(QSize(width, height), clearPixels) +{ -+ 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. ++KisGLImageF16::KisGLImageF16(const KisGLImageF16 &rhs) ++ : m_d(rhs.m_d) ++{ ++} + -+ The texture's color space will be used when composing the widget -+ into the root window surface. ++KisGLImageF16 &KisGLImageF16::operator=(const KisGLImageF16 &rhs) ++{ ++ m_d = rhs.m_d; ++} + -+ \note when the color space is set to QSurfaceFormat::DefaultColorSpace, -+ color conversion is effectively disabled. ++bool operator==(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs) ++{ ++ return lhs.m_d == rhs.m_d; ++} + -+ \since 5.99 -+ */ -+QSurfaceFormat::ColorSpace QOpenGLWidget::textureColorSpace() const ++bool operator!=(const KisGLImageF16 &lhs, const KisGLImageF16 &rhs) +{ -+ Q_D(const QOpenGLWidget); -+ return d->textureColorSpace; ++ return !(lhs == rhs); +} + -+/*! -+ Sets a custom color space for the internal texture of the widget ++KisGLImageF16::~KisGLImageF16() ++{ ++} + -+ The color space of the texture will be compared against the color -+ space of the root surface and conversion will be performed if needed. ++void KisGLImageF16::clearPixels() ++{ ++ if (!m_d->data.isEmpty()) { ++ m_d->data.fill(0); ++ } ++} + -+ \note setting the color space to QSurfaceFormat::DefaultColorSpace will -+ effectively disable color conversion when composing this texture on -+ screen. ++void KisGLImageF16::resize(const QSize &size, bool clearPixels) ++{ ++ const int pixelSize = 2 * 4; + -+ \since 5.99 -+ */ -+void QOpenGLWidget::setTextureColorSpace(QSurfaceFormat::ColorSpace colorSpace) ++ 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_D(QOpenGLWidget); -+ d->textureColorSpace = colorSpace; ++ Q_ASSERT(!m_d->data.isNull()); ++ return reinterpret_cast(m_d->data.data()); +} + - /*! - \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 9eb4a9ba5..eff2d9796 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); ++qfloat16 *KisGLImageF16::data() ++{ ++ m_d->data.detach(); ++ Q_ASSERT(!m_d->data.isNull()); + - bool isValid() const; ++ return reinterpret_cast(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 00000000..335e42ee +--- /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 ++#include ++ ++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 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 00000000..da36ac16 +--- /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 ++#include ++#include ++ ++#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 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 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 00000000..e807064c +--- /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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++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 00000000..b418e54b +--- /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 00000000..b57c6570 +--- /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 00000000..ab5b5719 +--- /dev/null ++++ b/tests/manual/hdr-qopenglwidget/kis_gl_image_widget.qrc +@@ -0,0 +1,6 @@ ++ ++ ++ kis_gl_image_widget.frag ++ kis_gl_image_widget.vert ++ ++ +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 00000000..9578f479 +--- /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 00000000..e517ef85 +--- /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 ++#include "window.h" ++ ++#include "openglprobeutils.h" ++#include ++ ++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 allFormats; ++ ++ QVector 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 00000000..687cc089 +--- /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 ++#include ++#include ++#include ++#include ++ ++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 sharedContextSetter; ++ QScopedPointer glSetter; ++ QScopedPointer glesSetter; ++ QScopedPointer formatSetter; ++ QScopedPointer 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 00000000..3b2f1ec3 +--- /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 ++ ++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 00000000..5729660a +--- /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 ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++ ++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(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 00000000..fd8e5c03 +--- /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 ++ ++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 ab00a5ef..b202ed04 100644 +--- a/tests/manual/manual.pro ++++ b/tests/manual/manual.pro +@@ -59,7 +59,7 @@ qtabbar - void makeCurrent(); -diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h -index 7e4ea2cc0..1ff5af426 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 a32eb2a03..db6033803 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()); - } + qtConfig(opengl) { + SUBDIRS += qopengltextureblitter +- qtConfig(egl): SUBDIRS += qopenglcontext ++ qtConfig(egl): SUBDIRS += qopenglcontext hdr-qopenglwidget + } - for (int i = 0; i < wd->children.size(); ++i) { + win32: SUBDIRS -= network_remote_stresstest network_stresstest +-- +2.22.0.windows.1 + +From 47bda028a372144467a775c57e1269c6b1b1080d Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Thu, 6 Dec 2018 16:16:27 +0300 +Subject: [PATCH 09/27] 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 6c871aae2..19fc2d167 100644 +index 6c871aae..19fc2d16 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); } } -diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro -index ab00a5ef6..b202ed043 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.22.0.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 fe9981b1ae..d03ea62dd7 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,23 +1,50 @@ +From 37be272df1fac2bc4633fbddafd3662d1f0921b9 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Mon, 11 Mar 2019 13:18:06 +0300 +Subject: [PATCH 10/27] 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 fbc71cd0e..729a7f701 100644 +index fbc71cd0..729a7f70 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -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.22.0.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 index 8bb29eca33..61d03a8cfd 100644 --- 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 @@ -1,17 +1,40 @@ +From cd8b94f7d7b47e2b4c1181a2cd2911937ef344a6 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sat, 30 Mar 2019 23:14:07 +0300 +Subject: [PATCH 11/27] 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 9a8b5d512..a83289de7 100644 +index 9a8b5d51..a83289de 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -614,7 +614,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.22.0.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..11bfb7deb7 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 52dd12d4e4eeb64864fe5708e2a3fb775c20c743 Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 3 Apr 2019 18:37:56 +0300 -Subject: [PATCH] Implement a switch for tablet API on Windows +Subject: [PATCH 12/27] 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 --- 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 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -236,10 +236,16 @@ QWindowsIntegrationPrivate::QWindowsIntegrationPrivate(const QStringList ¶mL 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 +2.22.0.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..d67726efc5 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,137 @@ -From f62b528cbc9e3ef75584839d995e4b7369ad3ff8 Mon Sep 17 00:00:00 2001 +From 7d4d465771854cfa860b7cd2eb09f91cfdb8b5cd Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Sat, 13 Apr 2019 18:08:33 +0300 -Subject: [PATCH] Fetch stylus button remapping from WinTab driver +Subject: [PATCH 13/27] 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. --- .../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 --- 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 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; + + 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) { + + *buttons = Qt::NoButton; + for (int i = 0; i < 3; i++) { + int btn = 0x1 << i; + + if (btn & btnNew) { + Qt::MouseButton convertedButton = + buttonValueToEnum(btn, tdd); + + *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. + */ + } + } +} + bool QWindowsTabletSupport::translateTabletPacketEvent() { static PACKET localPacketBuf[TabletPacketQSize]; // our own tablet packet queue. @@ -552,9 +617,14 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation; } + Qt::MouseButtons buttons; + convertTabletButtons(packet.pkButtons, &buttons, + m_devices.at(m_currentDevice), + keyboardModifiers); + QWindowSystemInterface::handleTabletEvent(target, packet.pkTime, QPointF(localPos), globalPosF, currentDevice, currentPointer, - static_cast(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 --- a/src/plugins/platforms/windows/qwindowstabletsupport.h +++ b/src/plugins/platforms/windows/qwindowstabletsupport.h @@ -45,6 +45,7 @@ #include #include +#include #include @@ -100,6 +101,7 @@ struct QWindowsTabletDeviceData qint64 uniqueId = 0; int currentDevice = 0; int currentPointerType = 0; + QMap buttonsMap; }; #ifndef QT_NO_DEBUG_STREAM -- -2.20.1.windows.1 +2.22.0.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 bf980b96e2..c623671c51 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,16 +1,28 @@ +From 7c66490d5579828540c410275c0f8e8d72dae3f5 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sat, 13 Apr 2019 20:29:14 +0300 +Subject: [PATCH 14/27] 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 fa209f09c..d31998825 100644 +index 31655101..3e35b146 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -@@ -497,6 +497,11 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() +@@ -562,6 +562,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.22.0.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 98b0d7ca8d..ad27e93407 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,185 +1,217 @@ +From b33666c064dc255e3d39a0646684445ab907d299 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sat, 13 Apr 2019 23:24:01 +0300 +Subject: [PATCH 15/27] 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 +--- + .../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 3e35b1464..11e6769c8 100644 +index 3e35b146..11e6769c 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -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); @@ -535,8 +620,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 5b1ddb529..59c996cd3 100644 +index 5b1ddb52..e8b0c01b 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.h +++ b/src/plugins/platforms/windows/qwindowstabletsupport.h @@ -45,7 +45,9 @@ #include #include +#include #include +#include #include -@@ -55,7 +57,7 @@ QT_BEGIN_NAMESPACE - +@@ -56,6 +58,7 @@ QT_BEGIN_NAMESPACE class QDebug; class QWindow; --class QRect; + class QRect; +class QScreen; struct QWindowsWinTab32DLL { -@@ -108,7 +110,7 @@ struct QWindowsTabletDeviceData +@@ -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 +143,14 @@ public: +@@ -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 +161,9 @@ private: +@@ -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.22.0.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 d2b688ef66..1e4657f9ca 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,181 +1,61 @@ +From ebfe32b8fe8ff7ce8b8e4330af1191c16b0a8be0 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 17 Apr 2019 17:39:10 +0300 +Subject: [PATCH 16/27] 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. + +https://bugs.kde.org/show_bug.cgi?id=405747 +--- + .../windows/qwindowstabletsupport.cpp | 23 ++++++++++++++++++- + 1 file changed, 22 insertions(+), 1 deletion(-) + diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -index 11e6769c8..b04312769 100644 +index 11e6769c..b0431276 100644 --- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp +++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp @@ -604,7 +604,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), @@ -634,6 +633,28 @@ 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) { + + 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 = -diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp.orig b/src/plugins/platforms/windows/qwindowstabletsupport.cpp.orig -index 316551014..11e6769c8 100644 ---- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp.orig -+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp.orig -@@ -53,6 +53,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -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); -@@ -535,8 +620,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 -@@ -562,6 +647,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.22.0.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 new file mode 100644 index 0000000000..82bcb7b98f --- /dev/null +++ b/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch @@ -0,0 +1,38 @@ +From a354a9a7f0cf081aae223dfff3f1734add050b18 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Thu, 18 Apr 2019 15:42:17 +0300 +Subject: [PATCH 17/27] 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 +--- + 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 b0431276..2de87758 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.22.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?= 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 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 7a30e45777..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,486 +1,554 @@ +From 0735b61f7e0868ab2272838ef52fe34aed0dfe25 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= +Date: Mon, 25 Apr 2016 11:31:34 +0200 +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 +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 22e46e085..cdf6c8669 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 +#include + +#include 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() { -@@ -248,6 +263,191 @@ static inline bool usePixelDensity() +@@ -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 +struct EnumLookup +{ + const char *name; + EnumType value; +}; + +template +static bool operator==(const EnumLookup &e1, const EnumLookup &e2) +{ + return qstricmp(e1.name, e2.name) == 0; +} + +template +static QByteArray joinEnumValues(const EnumLookup *i1, const EnumLookup *i2) +{ + QByteArray result; + for (; i1 < i2; ++i1) { + if (!result.isEmpty()) + result += QByteArrayLiteral(", "); + result += i1->name; + } + return result; +} + +using ScaleFactorRoundingPolicyLookup = EnumLookup; + +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; + +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. -@@ -258,8 +458,6 @@ void QHighDpiScaling::initHighDpiScaling() +@@ -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; } -@@ -380,22 +578,8 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen) +@@ -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 83fc9452c..c664693a0 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 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(); + 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 21ae75ba8..ff76528a0 100644 +index 21ae75ba..ff76528a 100644 --- a/src/gui/kernel/qplatformscreen.cpp +++ b/src/gui/kernel/qplatformscreen.cpp @@ -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 e9d64c8a2..63b5d5a4a 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 7dc8bb808..80757c213 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 f15aeae3f..5dc158e35 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 9ded98df3..a73b97c77 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 0520f8893..9b7b4630f 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 824bcb1ad..cd765f181 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 57dbdc9be..9af0794d2 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 be6c45e41..3f619d71c 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 9db083cd8..2de8ed3bb 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.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?= 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 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 QPlatformScreen::overrideDpi(const QPair &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 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 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 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?= 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 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?= 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 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 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 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 *i1, const EnumLooku return result; } -using ScaleFactorRoundingPolicyLookup = EnumLookup; +using ScaleFactorRoundingPolicyLookup = EnumLookup; 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; @@ -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 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 985e54fde5..8902d797de 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,88 +1,52 @@ +From f56bfdc8a244fbd82a1901220fac642ca78d8c23 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 15 May 2019 19:39:44 +0300 +Subject: [PATCH 18/27] 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 9a8b5d512..4a8322337 100644 +index a83289de..5ba58c24 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -@@ -639,14 +639,16 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin +@@ -644,14 +644,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, -@@ -716,7 +718,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, +@@ -721,7 +723,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; -diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -index fa209f09c..462be8b3f 100644 ---- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp -+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp -@@ -434,6 +434,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); - } - m_devices[m_currentDevice].currentPointerType = pointerType(currentCursor); - m_state = PenProximity; -@@ -454,7 +462,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), -@@ -484,6 +491,28 @@ 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) { -+ -+ 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 = +-- +2.22.0.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 f19abc566c..4435955d3e 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,206 +1,251 @@ +From ec7b01f723961ca3efa7b279398d6680705bd453 Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 15 May 2019 19:54:52 +0300 +Subject: [PATCH 19/27] 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 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. + +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 | 88 ++++++++++++++++++- + 4 files changed, 99 insertions(+), 18 deletions(-) + diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp -index fd01f8bb7..4d61d9fd8 100644 +index fd01f8bb..4d61d9fd 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -2532,6 +2532,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 b0f286912..b3b6167c9 100644 +index b0f28691..b3b6167c 100644 --- a/src/gui/kernel/qwindowsysteminterface.cpp +++ b/src/gui/kernel/qwindowsysteminterface.cpp @@ -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) @@ -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(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 bf98c33a1..fdc5a2fb5 100644 +index bf98c33a..fdc5a2fb 100644 --- a/src/gui/kernel/qwindowsysteminterface.h +++ b/src/gui/kernel/qwindowsysteminterface.h @@ -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 9a8b5d512..93498d58b 100644 +index 5ba58c24..7319d83b 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -535,6 +535,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) { -@@ -622,10 +674,38 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin +@@ -627,10 +679,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); + + const Qt::MouseButtons changedButtons = + oldButtons ^ QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).state; + + Qt::MouseButton pressedButton = Qt::NoButton; + + const QVector 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; + + synthesizeMouseEvent(type, pressedButton, *penInfo); + return true; + } else { + return false; // Allow mouse messages to be generated by OS + } } } return true; +-- +2.22.0.windows.1 + diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 3f987c493e..c9e125e6ee 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,262 +1,263 @@ 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) 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 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 ) 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}/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.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}/0050-Fix-using-tablet-on-QML-widgets.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() 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 qttools -i ${CMAKE_CURRENT_SOURCE_DIR}/windeployqt-force-allow-debug-info.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.3/single/qt-everywhere-src-5.12.3.tar.xz URL_MD5 38017e0ed88b9baba063bd723d9ca8b2 PATCH_COMMAND ${ext_qt_PATCH_COMMAND} INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure.bat ${_QT_conf} BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install UPDATE_COMMAND "" # Use a short name to reduce the chance of exceeding path length limit SOURCE_DIR s BINARY_DIR b DEPENDS ext_patch ext_openssl ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.3/single/qt-everywhere-src-5.12.3.tar.xz URL_MD5 38017e0ed88b9baba063bd723d9ca8b2 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 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 /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 /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 /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.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 /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/remove-fullscreen-border-hack.patch b/3rdparty/ext_qt/remove-fullscreen-border-hack.patch index 97f86a1284..12adb840d5 100644 --- a/3rdparty/ext_qt/remove-fullscreen-border-hack.patch +++ b/3rdparty/ext_qt/remove-fullscreen-border-hack.patch @@ -1,35 +1,51 @@ +From b609d09933ac9e8dfa910d9974f62947360bba33 Mon Sep 17 00:00:00 2001 +From: Alvin Wong +Date: Thu, 18 Apr 2019 18:59:58 +0800 +Subject: [PATCH 21/27] 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 967d479a0..c888847ed 100644 +index 967d479a..c888847e 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -1561,7 +1561,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 @@ -1998,7 +1998,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(); } @@ -2069,7 +2069,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.22.0.windows.1 + diff --git a/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch b/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch index d88f4003a6..4622e1402e 100644 --- a/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch +++ b/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch @@ -1,131 +1,163 @@ +From a659513803b55b09352c3a08a1c52027e389c141 Mon Sep 17 00:00:00 2001 +From: Friedemann Kleint +Date: Wed, 21 Nov 2018 09:06:50 +0100 +Subject: [PATCH 20/27] 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 +Reviewed-by: Andy Shaw +(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 e51c2fde6..032dcafa6 100644 +index e51c2fde..032dcafa 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(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 a52bbe061..0c52cde75 100644 +index a52bbe06..0c52cde7 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 1c5be4415..42193baf4 100644 +index 1c5be441..42193baf 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 76a443a89..967d479a0 100644 +index 76a443a8..967d479a 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -1183,6 +1183,7 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, 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), @@ -1220,7 +1221,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); } @@ -2823,6 +2824,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 2675990cf..0d8096ddf 100644 +index 2675990c..0d8096dd 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -342,6 +342,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); @@ -387,6 +388,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.22.0.windows.1 + diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 54c529a721..643075e884 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,262 +1,263 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita Krita Fundazioa Krita Foundation La Fondation Krita Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa 디지털 페인팅, 자유로운 창의성 Digital Painting, Creative Freedom Digital teikning – kreativ fridom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

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

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

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

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

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

Krita는 디지털 예술 스튜디오입니다.

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

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

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

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

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

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

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

xxKrita is the full-featured digital art studio.xx

Krita 是一款功能齐全的数字绘画工作室软件。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

스케치, 페인팅을 위한 완벽한 도구이며, 생각에서부터 디지털 페인팅 파일을 만들어 낼 수 있는 종합적인 도구를 제공합니다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Krita er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, som RGB og CMYK med 8- og 16-bits heiltals- eller flyttalskanalar.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://www.krita.org/ https://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics KDE krita org.kde.krita.desktop +
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index a4ecc63e48..8aaa5b5d74 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2652 +1,2652 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); widgetStack->addWidget(welcomePage); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); 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(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { 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("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).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 childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::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 layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); 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 windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will 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(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 KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.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 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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisTemplateTree.h" #include "KisTemplateGroup.h" #include "KisTemplate.h" #include "KisDetailsPane.h" #include "KisTemplatesPane.h" #include "ui_KisOpenPaneBase.h" #include #include #include 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(&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(d->m_sectionList->selectedItems().first()); if (selectedItem) { d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus(); } } QList 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)), this, SLOT(saveSplitterSizes(KisDetailsPane*,QList))); setAcceptDrops(true); } KisOpenPane::~KisOpenPane() { if (!d->m_sectionList->selectedItems().isEmpty()) { KoSectionListItem* item = dynamic_cast(d->m_sectionList->selectedItems().first()); if (item) { if (!qobject_cast(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)), this, SIGNAL(splitterResized(KisDetailsPane*,QList))); connect(this, SIGNAL(splitterResized(KisDetailsPane*,QList)), pane, SLOT(resizeSplitter(KisDetailsPane*,QList))); - 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(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(d->m_sectionList->selectedItems().first()); if (!section) return; d->m_widgetStack->setCurrentIndex(section->widgetIndex()); } } void KisOpenPane::saveSplitterSizes(KisDetailsPane* sender, const QList& sizes) { Q_UNUSED(sender); KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog"); cfgGrp.writeEntry("DetailsPaneSplitterSizes", sizes); } void KisOpenPane::itemClicked(QTreeWidgetItem* item) { KoSectionListItem* selectedItem = static_cast(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 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 #include #include #include 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& 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& 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/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 , (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 #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include #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 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* 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/kis_palette_view.cpp b/libs/widgets/kis_palette_view.cpp index e97628174d..09cbb7498d 100644 --- a/libs/widgets/kis_palette_view.cpp +++ b/libs/widgets/kis_palette_view.cpp @@ -1,306 +1,300 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_palette_view.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPaletteDelegate.h" #include "KisPaletteModel.h" #include "kis_color_button.h" #include int KisPaletteView::MININUM_ROW_HEIGHT = 10; struct KisPaletteView::Private { QPointer 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(itemDelegate()); KIS_ASSERT_RECOVER_RETURN(delegate); delegate->setCrossedKeyword(value); } bool KisPaletteView::addEntryWithDialog(KoColor color) { QScopedPointer 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(index.data(KisPaletteModel::IsGroupNameRole))) { QScopedPointer 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) +const KoColor KisPaletteView::closestColor(const KoColor &color) const { - KConfigGroup group(KSharedConfig::openConfig(), ""); - if (group.readEntry("colorsettings/forcepalettecolors", false)) { - selectClosestColor(color); - } + QModelIndex index = m_d->model->indexForClosest(color); + KisSwatch swatch = m_d->model->getEntry(index); + return swatch.color(); } -void KisPaletteView::slotFGColorResourceChanged(const KoColor& color) +void KisPaletteView::slotFGColorChanged(const KoColor &color) { - // 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); + selectClosestColor(color); } void KisPaletteView::setPaletteModel(KisPaletteModel *model) { if (m_d->model) { 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())); } 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 fc4cbbae23..65bae3620f 100644 --- a/libs/widgets/kis_palette_view.h +++ b/libs/widgets/kis_palette_view.h @@ -1,128 +1,130 @@ /* * Copyright (c) 2016 Dmitry Kazakov * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PALETTE_VIEW_H #define __KIS_PALETTE_VIEW_H #include #include #include #include #include #include #include #include "kritawidgets_export.h" #include 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 = 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); - 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 m_d; }; #endif /* __KIS_PALETTE_VIEW_H */ diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp index 69f84c7fe5..10c82a7de5 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.cpp +++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp @@ -1,384 +1,384 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "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(0) , m_resourceProvider(0) , m_rServer(KoResourceServerProvider::instance()->paletteServer()) , 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 != 0); if (canvas) { KisCanvas2 *cv = qobject_cast(canvas); m_ui->paletteView->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); } if (m_activeDocument) { 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); } } if (!m_currentColorSet) { slotSetColorSet(0); } } void PaletteDockerDock::unsetCanvas() { setEnabled(false); m_ui->paletteView->setDisplayRenderer(0); m_paletteEditor->setView(0); for (KoResource *r : m_rServer->resources()) { KoColorSet *c = static_cast(r); if (!c->isGlobal()) { m_rServer->removeResourceFromServer(c); } } if (!m_currentColorSet) { 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* 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(index.data(KisPaletteModel::CheckSlotRole)); if (occupied) { if (!qvariant_cast(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(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; } diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp index a391a0441f..1382e84c86 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp @@ -1,417 +1,419 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2009 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #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(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 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(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(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(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(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 KisGbrBrush *gimpBrush = dynamic_cast(m_brush.data()); if (gimpBrush) { useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask()); } 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_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 * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_custom_brush_widget.h" #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_gbr_brush.h" #include "kis_imagepipe_brush.h" #include #include "kis_brush_server.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include #include #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(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(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(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 > devices; devices.push_back(QVector()); 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 layers = m_image->root()->childNodes(QStringList("KisLayer"), properties); KisNodeSP node; Q_FOREACH (KisNodeSP node, layers) { devices[0].push_back(node->projection().data()); } QVector 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(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 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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 #include #include #include "ui_wdgcustombrush.h" #include #include 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 m_rServerAdapter; }; #endif // KIS_CUSTOM_BRUSH_H_