diff --git a/3rdparty/ext_fftw3/CMakeLists.txt b/3rdparty/ext_fftw3/CMakeLists.txt index 64a6b69fe9..2df9e12fe1 100644 --- a/3rdparty/ext_fftw3/CMakeLists.txt +++ b/3rdparty/ext_fftw3/CMakeLists.txt @@ -1,13 +1,29 @@ +if (WIN32) SET(PREFIX_ext_fftw3 "${EXTPREFIX}" ) ExternalProject_Add( ext_fftw3 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://www.fftw.org/fftw-3.3.8.tar.gz URL_MD5 8aac833c943d8e90d51b697b27d4384d PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch INSTALL_DIR ${PREFIX_ext_fftw3} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_fftw3} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DWITH_COMBINED_THREADS=ON -ENABLE_SSE=ON -ENABLE_SSE2=ON -DBUILD_TESTS=OFF UPDATE_COMMAND "" ) +else() +SET(PREFIX_ext_fftw3 "${EXTPREFIX}" ) +ExternalProject_Add( ext_fftw3 + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://www.fftw.org/fftw-3.3.8.tar.gz + URL_MD5 8aac833c943d8e90d51b697b27d4384d + + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch + + INSTALL_DIR ${PREFIX_ext_fftw3} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_fftw3} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DDENABLE_THREADS=ON -ENABLE_SSE=ON -ENABLE_SSE2=ON -DBUILD_TESTS=OFF + + UPDATE_COMMAND "" +) +endif() diff --git a/3rdparty/ext_poppler/CMakeLists.txt b/3rdparty/ext_poppler/CMakeLists.txt index fe0fcbbb90..f1606260e5 100644 --- a/3rdparty/ext_poppler/CMakeLists.txt +++ b/3rdparty/ext_poppler/CMakeLists.txt @@ -1,13 +1,22 @@ SET(PREFIX_ext_poppler "${EXTPREFIX}" ) + +list(APPEND _PopplerPatch_COMMAND + ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/no_tests.diff + ) +if(APPLE) + list(APPEND _PopplerPatch_COMMAND + COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/mac_popplerIconv.patch + ) +endif(APPLE) ExternalProject_Add( ext_poppler DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://poppler.freedesktop.org/poppler-0.62.0.tar.xz URL_MD5 42b801f2defaccb6b6cf1bf783ee1552 - PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/no_tests.diff + PATCH_COMMAND ${_PopplerPatch_COMMAND} INSTALL_DIR ${PREFIX_ext_poppler} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_poppler} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DBUILD_GTK_TESTS=OFF -DBUILD_QT5_TESTS=FALSE -DBUILD_CPP_TESTS=FALSE -DENABLE_UTILS=FALSE -DENABLE_GLIB=FALSE -DENABLE_LIBOPENJPEG=none UPDATE_COMMAND "" ) diff --git a/3rdparty/ext_poppler/mac_popplerIconv.patch b/3rdparty/ext_poppler/mac_popplerIconv.patch new file mode 100644 index 0000000000..a259f75c0b --- /dev/null +++ b/3rdparty/ext_poppler/mac_popplerIconv.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/modules/FindIconv.cmake b/cmake/modules/FindIconv.cmake +index 338d17d..5f029b7 100644 +--- a/cmake/modules/FindIconv.cmake ++++ b/cmake/modules/FindIconv.cmake +@@ -15,7 +15,7 @@ ENDIF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + + FIND_PATH(ICONV_INCLUDE_DIR iconv.h) + +-FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c) ++FIND_LIBRARY(ICONV_LIBRARIES NO_DEFAULT_PATH PATHS ${CMAKE_INSTALL_PREFIX}/lib /usr/lib NAMES iconv libiconv libiconv-2 ) + + IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + SET(ICONV_FOUND TRUE) 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 new file mode 100644 index 0000000000..5822b87ee7 --- /dev/null +++ b/3rdparty/ext_qt/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch @@ -0,0 +1,137 @@ +From f62b528cbc9e3ef75584839d995e4b7369ad3ff8 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 + +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 + 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 new file mode 100644 index 0000000000..20a410cb36 --- /dev/null +++ b/3rdparty/ext_qt/0025-Disable-tablet-relative-mode-in-Qt.patch @@ -0,0 +1,28 @@ +From 30f28be2a239d82c2d7f2d6a37df4acb300d7cef Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sat, 13 Apr 2019 20:29:14 +0300 +Subject: [PATCH] Disable tablet relative mode in Qt + +--- + src/plugins/platforms/windows/qwindowstabletsupport.cpp | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp +index 31655101..3e35b146 100644 +--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp ++++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp +@@ -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.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch b/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch new file mode 100644 index 0000000000..41da7dd0fa --- /dev/null +++ b/3rdparty/ext_qt/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch @@ -0,0 +1,217 @@ +From 087561675d1c1922265c3c52faeac61ab1bc2ecf Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Sat, 13 Apr 2019 23:24:01 +0300 +Subject: [PATCH] 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 18ec05e4..6a1bf02b 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); +@@ -534,8 +619,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() + // in which case we snap the position to the mouse position. + // It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext + // area is always the virtual desktop. +- const QRect virtualDesktopArea = +- QWindowsScreen::virtualGeometry(QGuiApplication::primaryScreen()->handle()); ++ ++ const QRect virtualDesktopArea = m_effectiveScreenGeometry; + + if (QWindowsContext::verbose > 1) { + qCDebug(lcQpaTablet) << __FUNCTION__ << "processing" << packetCount +diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.h b/src/plugins/platforms/windows/qwindowstabletsupport.h +index 5b1ddb52..e8b0c01b 100644 +--- a/src/plugins/platforms/windows/qwindowstabletsupport.h ++++ b/src/plugins/platforms/windows/qwindowstabletsupport.h +@@ -45,7 +45,9 @@ + + #include + #include ++#include + #include ++#include + + #include + +@@ -56,6 +58,7 @@ QT_BEGIN_NAMESPACE + class QDebug; + class QWindow; + class QRect; ++class QScreen; + + struct QWindowsWinTab32DLL + { +@@ -108,7 +111,7 @@ struct QWindowsTabletDeviceData + QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t); + #endif + +-class QWindowsTabletSupport ++class QWindowsTabletSupport : public QObject + { + Q_DISABLE_COPY(QWindowsTabletSupport) + +@@ -141,9 +144,14 @@ public: + int absoluteRange() const { return m_absoluteRange; } + void setAbsoluteRange(int a) { m_absoluteRange = a; } + ++private Q_SLOTS: ++ void slotPrimaryScreenChanged(QScreen *screen); ++ void slotScreenGeometryChanged(); ++ + private: + unsigned options() const; + QWindowsTabletDeviceData tabletInit(qint64 uniqueId, UINT cursorType) const; ++ void updateEffectiveScreenGeometry(); + + static QWindowsWinTab32DLL m_winTab32DLL; + const HWND m_window; +@@ -154,6 +162,9 @@ private: + int m_currentDevice = -1; + Mode m_mode = PenMode; + State m_state = PenUp; ++ QScreen *m_connectedScreen = 0; ++ QRect m_wintabScreenGeometry; ++ QRect m_effectiveScreenGeometry; + }; + + QT_END_NAMESPACE +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch b/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch new file mode 100644 index 0000000000..6048d12aaf --- /dev/null +++ b/3rdparty/ext_qt/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch @@ -0,0 +1,61 @@ +From 8b82db980186cec2834d86f213b5fd1504ed8d0a Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Wed, 17 Apr 2019 17:39:10 +0300 +Subject: [PATCH] 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 6a1bf02b..d82b33c1 100644 +--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp ++++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp +@@ -603,7 +603,6 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() + return false; + + const int currentDevice = m_devices.at(m_currentDevice).currentDevice; +- const int currentPointer = m_devices.at(m_currentDevice).currentPointerType; + const qint64 uniqueId = m_devices.at(m_currentDevice).uniqueId; + + // The tablet can be used in 2 different modes (reflected in enum Mode), +@@ -633,6 +632,28 @@ bool QWindowsTabletSupport::translateTabletPacketEvent() + 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.20.1.windows.1 + diff --git a/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch b/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch new file mode 100644 index 0000000000..29da88c514 --- /dev/null +++ b/3rdparty/ext_qt/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch @@ -0,0 +1,38 @@ +From 7dd05b77fe213ca2feb125503eb10c2567a4faee Mon Sep 17 00:00:00 2001 +From: Dmitry Kazakov +Date: Thu, 18 Apr 2019 15:42:17 +0300 +Subject: [PATCH] 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 d82b33c1..3393b091 100644 +--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp ++++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp +@@ -519,6 +519,14 @@ bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, L + if (m_currentDevice < 0) { + m_currentDevice = m_devices.size(); + m_devices.push_back(tabletInit(uniqueId, cursorType)); ++ } else { ++ /** ++ * The user can switch pressure sensitivity level in the driver, ++ * which will make our saved values invalid (this option is ++ * provided by Wacom drivers for compatibility reasons, and ++ * it can be adjusted on the fly) ++ */ ++ m_devices[m_currentDevice] = tabletInit(uniqueId, cursorType); + } + + /** +-- +2.20.1.windows.1 + diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 7fd1e77a9e..0604ba8c34 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,247 +1,257 @@ 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 COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Fix-swizzling-when-rendering-QPainter-on-QOpenGLWidg.patch ) else() set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Don-t-request-the-MIME-image-every-time-Windows-asks.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Hack-always-return-we-support-DIBV5.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Fix-debug-on-openGL-ES.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-cumulative-patch-for-hdr.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Fix-swizzling-when-rendering-QPainter-on-QOpenGLWidg.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0021-Fix-QTabletEvent-uniqueId-when-Qt-uses-WinInk.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0022-Fix-generation-of-Leave-events-when-using-tablet-dev.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0023-Implement-a-switch-for-tablet-API-on-Windows.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0025-Disable-tablet-relative-mode-in-Qt.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch ) 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 + ) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz URL_MD5 99c2eb46e533371798b4ca2d1458e065 PATCH_COMMAND ${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.2/single/qt-everywhere-src-5.12.2.tar.xz URL_MD5 99c2eb46e533371798b4ca2d1458e065 PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/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.2/single/qt-everywhere-src-5.12.2.tar.xz URL_MD5 99c2eb46e533371798b4ca2d1458e065 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 new file mode 100644 index 0000000000..9f10ec54a7 --- /dev/null +++ b/3rdparty/ext_qt/remove-fullscreen-border-hack.patch @@ -0,0 +1,38 @@ +From e9b40ddf98af52f1c1866c766c9add6245e66a92 Mon Sep 17 00:00:00 2001 +From: Alvin Wong +Date: Thu, 18 Apr 2019 18:59:58 +0800 +Subject: [PATCH] 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 | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp +index 9705eb7293..08c743aaae 100644 +--- a/src/plugins/platforms/windows/qwindowswindow.cpp ++++ b/src/plugins/platforms/windows/qwindowswindow.cpp +@@ -1995,7 +1995,7 @@ bool QWindowsWindow::isFullScreen_sys() const + return false; + QRect geometry = geometry_sys(); + if (testFlag(HasBorderInFullScreen)) +- geometry += QMargins(1, 1, 1, 1); ++ geometry += QMargins(0, 0, 0, 1); + QPlatformScreen *screen = screenForGeometry(geometry); + return screen && geometry == screen->geometry(); + } +@@ -2066,7 +2066,7 @@ 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); ++ SetWindowPos(m_data.hwnd, HWND_TOP, r.left() - 1, r.top() - 1, r.width() + 2, r.height() + 1, swpf); + if (!wasSync) + clearFlag(SynchronousGeometryChangeEvent); + clearFlag(MaximizeToFullScreen); +-- +2.18.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 new file mode 100644 index 0000000000..f818fd8439 --- /dev/null +++ b/3rdparty/ext_qt/set-has-border-in-full-screen-default.patch @@ -0,0 +1,162 @@ +From f377ab8da1acb279e5b23ab1d2ef4afabe5f481c Mon Sep 17 00:00:00 2001 +From: Friedemann Kleint +Date: Wed, 21 Nov 2018 09:06:50 +0100 +Subject: [PATCH] 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 e51c2fde67..032dcafa6e 100644 +--- a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h ++++ b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.h +@@ -81,6 +81,15 @@ public: + func(window, border); + } + ++ typedef void (*SetHasBorderInFullScreenDefault)(bool border); ++ static const QByteArray setHasBorderInFullScreenDefaultIdentifier() { return QByteArrayLiteral("WindowsSetHasBorderInFullScreenDefault"); } ++ static void setHasBorderInFullScreenDefault(bool border) ++ { ++ auto func = reinterpret_cast(QGuiApplication::platformFunction(setHasBorderInFullScreenDefaultIdentifier())); ++ if (func) ++ func(border); ++ } ++ + typedef void (*SetWindowActivationBehaviorType)(WindowActivationBehavior); + static const QByteArray setWindowActivationBehaviorIdentifier() { return QByteArrayLiteral("WindowsSetWindowActivationBehavior"); } + +diff --git a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc +index a52bbe061b..0c52cde753 100644 +--- a/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc ++++ b/src/platformheaders/windowsfunctions/qwindowswindowfunctions.qdoc +@@ -93,7 +93,40 @@ + is true then it will enable the WS_BORDER flag in full screen mode to enable other top level windows + inside the application to appear on top when required. + ++ \note The setting must be applied before showing the window or switching it ++ to full screen. For QML, setHasBorderInFullScreenDefault() can be used to ++ set a default value. ++ ++ See also \l [QtDoc] {Fullscreen OpenGL Based Windows} ++*/ ++ ++/*! ++ \typedef QWindowsWindowFunctions::SetHasBorderInFullScreenDefault ++ \since 5.13 ++ ++ This is the typedef for the function returned by QGuiApplication::platformFunction ++ when passed setHasBorderInFullScreenDefaultIdentifier. ++*/ ++ ++/*! ++ \fn QByteArray QWindowsWindowFunctions::setHasBorderInFullScreenDefaultIdentifier() ++ \since 5.13 ++ ++ This function returns the bytearray that can be used to query ++ QGuiApplication::platformFunction to retrieve the SetHasBorderInFullScreen function. ++*/ ++ ++/*! ++ \fn void QWindowsWindowFunctions::setHasBorderInFullScreenDefault(bool border) ++ \since 5.13 ++ ++ This is a convenience function that can be used directly instead of resolving ++ the function pointer. \a border will be relayed to the function retrieved by ++ QGuiApplication. When \a border is true, the WS_BORDER flag will be set ++ in full screen mode for all windows by default. ++ + See also \l [QtDoc] {Fullscreen OpenGL Based Windows} ++ \sa setHasBorderInFullScreen() + */ + + /*! +diff --git a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp +index 05d6ac9201..eeb1aa58a3 100644 +--- a/src/plugins/platforms/windows/qwindowsnativeinterface.cpp ++++ b/src/plugins/platforms/windows/qwindowsnativeinterface.cpp +@@ -293,6 +293,8 @@ QFunctionPointer QWindowsNativeInterface::platformFunction(const QByteArray &fun + return QFunctionPointer(QWindowsWindow::setTouchWindowTouchTypeStatic); + if (function == QWindowsWindowFunctions::setHasBorderInFullScreenIdentifier()) + return QFunctionPointer(QWindowsWindow::setHasBorderInFullScreenStatic); ++ if (function == QWindowsWindowFunctions::setHasBorderInFullScreenDefaultIdentifier()) ++ return QFunctionPointer(QWindowsWindow::setHasBorderInFullScreenDefault); + if (function == QWindowsWindowFunctions::setWindowActivationBehaviorIdentifier()) + return QFunctionPointer(QWindowsNativeInterface::setWindowActivationBehavior); + if (function == QWindowsWindowFunctions::isTabletModeIdentifier()) +diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp +index 910d8dd209..9705eb7293 100644 +--- a/src/plugins/platforms/windows/qwindowswindow.cpp ++++ b/src/plugins/platforms/windows/qwindowswindow.cpp +@@ -1180,6 +1180,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), +@@ -1217,7 +1218,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) + + if (aWindow->isTopLevel()) + setWindowIcon(aWindow->icon()); +- if (aWindow->property(hasBorderInFullScreenProperty).toBool()) ++ if (m_borderInFullScreenDefault || aWindow->property(hasBorderInFullScreenProperty).toBool()) + setFlag(HasBorderInFullScreen); + clearFlag(WithinCreate); + } +@@ -2820,6 +2821,11 @@ void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border + window->setProperty(hasBorderInFullScreenProperty, QVariant(border)); + } + ++void QWindowsWindow::setHasBorderInFullScreenDefault(bool border) ++{ ++ m_borderInFullScreenDefault = border; ++} ++ + void QWindowsWindow::setHasBorderInFullScreen(bool border) + { + if (testFlag(HasBorderInFullScreen) == border) +diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h +index b9b398b67b..b07bd15d2a 100644 +--- a/src/plugins/platforms/windows/qwindowswindow.h ++++ b/src/plugins/platforms/windows/qwindowswindow.h +@@ -341,6 +341,7 @@ public: + static void setTouchWindowTouchTypeStatic(QWindow *window, QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes); + void registerTouchWindow(QWindowsWindowFunctions::TouchWindowTouchTypes touchTypes = QWindowsWindowFunctions::NormalTouch); + static void setHasBorderInFullScreenStatic(QWindow *window, bool border); ++ static void setHasBorderInFullScreenDefault(bool border); + void setHasBorderInFullScreen(bool border); + static QString formatWindowTitle(const QString &title); + +@@ -386,6 +387,7 @@ private: + // note: intentionally not using void * in order to avoid breaking x86 + VkSurfaceKHR m_vkSurface = 0; + #endif ++ static bool m_borderInFullScreenDefault; + }; + + #ifndef QT_NO_DEBUG_STREAM +-- +2.18.0.windows.1 + diff --git a/3rdparty/ext_qt/windeployqt-force-allow-debug-info.patch b/3rdparty/ext_qt/windeployqt-force-allow-debug-info.patch new file mode 100644 index 0000000000..3cd66e7e5e --- /dev/null +++ b/3rdparty/ext_qt/windeployqt-force-allow-debug-info.patch @@ -0,0 +1,20 @@ +diff --git a/src/shared/winutils/utils.cpp b/src/shared/winutils/utils.cpp +index 00a20543..6467dee8 100644 +--- a/src/shared/winutils/utils.cpp ++++ b/src/shared/winutils/utils.cpp +@@ -126,6 +126,7 @@ QStringList findSharedLibraries(const QDir &directory, Platform platform, + if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll, + (platform == WindowsDesktopMinGW))) { + matches = debugDll == (debugMatchMode == MatchDebug); ++ matches = true; + } else { + std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(dllPath) + << ": " << errorMessage; +@@ -826,6 +827,7 @@ inline void determineDebugAndDependentLibs(const ImageNtHeader *nth, const void + if (isMinGW) { + // Use logic that's used e.g. in objdump / pfd library + *isDebugIn = !(nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED); ++ *isDebugIn = false; + } else { + // When an MSVC debug entry is present, check whether the debug runtime + // is actually used to detect -release / -force-debug-info builds. diff --git a/3rdparty/ext_vc/CMakeLists.txt b/3rdparty/ext_vc/CMakeLists.txt index cd618c5167..b61ec24408 100644 --- a/3rdparty/ext_vc/CMakeLists.txt +++ b/3rdparty/ext_vc/CMakeLists.txt @@ -1,44 +1,45 @@ SET(PREFIX_ext_vc "${EXTPREFIX}" ) if(APPLE) ExternalProject_Add( ext_vc DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/VcDevel/Vc/releases/download/1.4.1/Vc-1.4.1.tar.gz URL_HASH SHA1=46e852ab69192cf017a31d7c6d0b35d8aa8fea2d INSTALL_DIR ${PREFIX_ext_vc} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 UPDATE_COMMAND "" ) elseif (WIN32) ExternalProject_Add( ext_vc DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz URL_HASH SHA1=3d3ddd29eca7c2b541fd8d0f00923e57f58d5ef0 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Workaround-AVX-argument-failures.patch + COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/relocatable-vc-1.3-install.patch INSTALL_DIR ${PREFIX_ext_vc} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 UPDATE_COMMAND "" ) else() ExternalProject_Add( ext_vc DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz URL_HASH SHA1=3d3ddd29eca7c2b541fd8d0f00923e57f58d5ef0 INSTALL_DIR ${PREFIX_ext_vc} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_vc} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PROCESSOR=x86 UPDATE_COMMAND "" ) endif() diff --git a/3rdparty/ext_vc/relocatable-vc-1.3-install.patch b/3rdparty/ext_vc/relocatable-vc-1.3-install.patch new file mode 100644 index 0000000000..21d53090d2 --- /dev/null +++ b/3rdparty/ext_vc/relocatable-vc-1.3-install.patch @@ -0,0 +1,42 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index a9882637..2c2bad85 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -206,8 +206,12 @@ set(Vc_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(Vc_VERSION_MINOR ${CMAKE_MATCH_2}) + set(Vc_VERSION_PATCH ${CMAKE_MATCH_3}) + +-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/VcConfig.cmake.in +- ${CMAKE_CURRENT_BINARY_DIR}/cmake/VcConfig.cmake @ONLY) ++include(CMakePackageConfigHelpers) ++configure_package_config_file( ++ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/VcConfig.cmake.in ++ ${CMAKE_CURRENT_BINARY_DIR}/cmake/VcConfig.cmake ++ INSTALL_DESTINATION lib${LIB_SUFFIX}/cmake/Vc ++ PATH_VARS CMAKE_INSTALL_PREFIX) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/VcConfigVersion.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cmake/VcConfigVersion.cmake @ONLY) + +diff --git a/cmake/VcConfig.cmake.in b/cmake/VcConfig.cmake.in +index 4a47e41e..48d37a80 100644 +--- a/cmake/VcConfig.cmake.in ++++ b/cmake/VcConfig.cmake.in +@@ -1,13 +1,15 @@ ++@PACKAGE_INIT@ ++ + set(Vc_VERSION_MAJOR @Vc_VERSION_MAJOR@) + set(Vc_VERSION_MINOR @Vc_VERSION_MINOR@) + set(Vc_VERSION_PATCH @Vc_VERSION_PATCH@) + set(Vc_VERSION @Vc_VERSION_MAJOR@.@Vc_VERSION_MINOR@.@Vc_VERSION_PATCH@) + set(Vc_VERSION_STRING "@Vc_VERSION_MAJOR@.@Vc_VERSION_MINOR@.@Vc_VERSION_PATCH@") + +-set(Vc_INSTALL_DIR "@CMAKE_INSTALL_PREFIX@") ++set(Vc_INSTALL_DIR "@PACKAGE_CMAKE_INSTALL_PREFIX@") + +-set(Vc_LIB_DIR "@CMAKE_INSTALL_PREFIX@/lib@LIB_SUFFIX@") +-find_path(Vc_INCLUDE_DIR Vc/global.h HINTS "@CMAKE_INSTALL_PREFIX@/include") ++set(Vc_LIB_DIR "@PACKAGE_CMAKE_INSTALL_PREFIX@/lib@LIB_SUFFIX@") ++find_path(Vc_INCLUDE_DIR Vc/global.h HINTS "@PACKAGE_CMAKE_INSTALL_PREFIX@/include") + find_path(Vc_CMAKE_MODULES_DIR AddCompilerFlag.cmake HINTS "${Vc_LIB_DIR}/cmake/Vc") + list(APPEND CMAKE_MODULE_PATH "${Vc_CMAKE_MODULES_DIR}") + diff --git a/CMakeLists.txt b/CMakeLists.txt index 737bd778fc..bc50ad770c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,924 +1,936 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.6.0) set(MIN_FRAMEWORKS_VERSION 5.18.0) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.11 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) add_definitions(-Wno-suggest-override) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.2.0-pre-alpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 2) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## # FIXME: Apparently there is no better way to do this in android toolchain if(ANDROID) set (Qt5_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5/) set (Qt5Core_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Core/) set (Qt5Gui_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Gui/) set (Qt5Widgets_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Widgets/) set (Qt5Xml_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Xml/) set (Qt5Network_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Network/) set (Qt5PrintSupport_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5PrintSupport/) set (Qt5Svg_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Svg/) set (Qt5Test_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Test/) set (Qt5Concurrent_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Concurrent/) set (Qt5Multimedia_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Multimedia/) set (Qt5Qml_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Qml/) set (Qt5Quick_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5Quick/) set (Qt5QuickWidgets_DIR $ENV{QT_ANDROID}/lib/cmake/Qt5QuickWidgets/) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) set(ECM_DIR ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/share/ECM/cmake) set(KF5Config_DIR ${KF5_LIBRARIES}/cmake/KF5Config/) set(KF5Config_DIR ${KF5_LIBRARIES}/cmake/KF5Config/) set(KF5I18n_DIR ${KF5_LIBRARIES}/cmake/KF5I18n/) set(KF5WidgetsAddons_DIR ${KF5_LIBRARIES}/cmake/KF5WidgetsAddons) set(KF5Completion_DIR ${KF5_LIBRARIES}/cmake/KF5Completion) set(KF5GuiAddons_DIR ${KF5_LIBRARIES}/cmake/KF5GuiAddons) set(KF5ItemViews_DIR ${KF5_LIBRARIES}/cmake/KF5ItemViews) set(KF5WindowSystem_DIR ${KF5_LIBRARIES}/cmake/KF5WindowSystem) set(KF5ItemModels_DIR ${KF5_LIBRARIES}/cmake/KF5ItemModels) set(KF5CoreAddons_DIR ${KF5_LIBRARIES}/cmake/KF5CoreAddons) endif() find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) +if (WIN32) + CHECK_CXX_SOURCE_COMPILES(" +#include +int main(int argc, char *argv[]) { +QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); +} +" + HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT + ) + configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) +endif (WIN32) + unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### if (ANDROID) set (PNG_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/i/lib/libpng16.so) set (PNG_PNG_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/include/libpng16) set (LibExiv2_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/i/lib/libexiv2.so) set (LibExiv2_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/i/include/) set (LCMS2_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/include) set (LCMS2_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/i/lib/liblcms2.so) set (QUAZIP_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/include/quazip5) set (QUAZIP_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/i/lib/libquazip5.so) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (GSL_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/i/lib/libgsl.so) set (GSL_CBLAS_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/i/lib/libgslcblas.so) set (GSL_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/include/) set (GSL_CONFIG ${CMAKE_CURRENT_BINARY_DIR}/i/bin/gsl-config) set (EXPAT_LIBRARY ${CMAKE_CURRENT_BINARY_DIR}/i/lib/libexpat.so) # libraries to be used when bundling android apk # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY} ${LibExiv2_LIBRARIES} ${QUAZIP_LIBRARIES} ${LCMS2_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so) endif() find_package(PNG REQUIRED) if (APPLE) # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "http://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "http://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) add_subdirectory(benchmarks) add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() else() message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index b2b8427fba..671bcb63a0 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,817 +1,817 @@ @echo off setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --jobs ^ Set parallel jobs count when building echo Defaults to no. of logical cores echo --skip-deps Skips (re)building of deps echo --skip-krita Skips (re)building of Krita echo --cmd Launch a cmd prompt instead of building. echo The environment is set up like the build echo environment with some helper command macros. echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location. echo --download-dir ^ Specify deps download dir echo Can be omitted if --skip-deps is used echo --deps-build-dir ^ Specify deps build dir echo Can be omitted if --skip-deps is used echo --deps-install-dir ^ Specify deps install dir echo --krita-build-dir ^ Specify Krita build dir echo Can be omitted if --skip-krita is used echo --krita-install-dir ^ Specify Krita install dir echo Can be omitted if --skip-krita is used echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita build script for Windows echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_JOBS= set ARG_SKIP_DEPS= set ARG_SKIP_KRITA= set ARG_SRC_DIR= set ARG_DOWNLOAD_DIR= set ARG_DEPS_BUILD_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_BUILD_DIR= set ARG_KRITA_INSTALL_DIR= set ARG_CMD= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--jobs" ( if not "%ARG_JOBS%" == "" ( echo ERROR: Arg --jobs specified more than once 1>&2 echo. goto usage_and_fail ) set /a "ARG_JOBS=%2" if not !ARG_JOBS! GTR 0 ( echo ERROR: Arg --jobs is not a positive integer 1>&2 echo. goto usage_and_fail ) shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--skip-deps" ( set ARG_SKIP_DEPS=1 set CURRENT_MATCHED=1 ) if "%1" == "--skip-krita" ( set ARG_SKIP_KRITA=1 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--download-dir" ( if not "%ARG_DOWNLOAD_DIR%" == "" ( echo ERROR: Arg --download-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --download-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-build-dir" ( if not "%ARG_DEPS_BUILD_DIR%" == "" ( echo ERROR: Arg --deps-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-build-dir" ( if not "%ARG_KRITA_BUILD_DIR%" == "" ( echo ERROR: Arg --krita-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--cmd" ( set ARG_CMD=1 set CURRENT_MATCHED=1 ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_SKIP_DEPS%" == "1" ( if "%ARG_SKIP_KRITA%" == "1" ( echo ERROR: You cannot skip both deps and Krita 1>&2 echo. exit /b 102 ) echo Building of deps will be skipped. ) else ( if "%ARG_SKIP_KRITA%" == "1" ( echo Building of Krita will be skipped. ) else ( echo Both deps and Krita will be built. ) ) :: Check environment config if "%CMAKE_EXE%" == "" ( call :find_on_path CMAKE_EXE cmake.exe if "!CMAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" ) if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) else ( echo Found CMake on PATH: !CMAKE_EXE! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) ) ) ) echo CMake: %CMAKE_EXE% if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if "!SEVENZIP_EXE!" == "" ( echo 7-Zip not found ) ) ) if "%SEVENZIP_EXE%" == "" ( echo 7-Zip: %SEVENZIP_EXE% ) if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% if "%PYTHON_BIN_DIR%" == "" ( call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" ) if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) else ( call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" echo Found Python on PATH: !PYTHON_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) ) ) ) echo Python: %PYTHON_BIN_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%" ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%\bin\%%a\" ) ) ) popd ) set QT_ENABLE_DYNAMIC_OPENGL=ON if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 102 ) ) set QT_ENABLE_DYNAMIC_OPENGL=OFF ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% :skip_windows_sdk_dir_check if not "%ARG_JOBS%" == "" ( set "PARALLEL_JOBS=%ARG_JOBS%" ) if "%PARALLEL_JOBS%" == "" ( echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS% echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS% if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs" if "!PARALLEL_JOBS!" == "" ( echo ERROR: Invalid job count! 1>&2 exit /b 102 ) ) ) ) echo Parallel jobs count: %PARALLEL_JOBS% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-21!" == "\build-tools\windows\" ( if exist "!_temp:~0,-21!\CMakeLists.txt" ( if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-21!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check if not "%ARG_DOWNLOAD_DIR%" == "" ( set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%" ) if "%DEPS_DOWNLOAD_DIR%" == "" ( set DEPS_DOWNLOAD_DIR=%CD%\d\ echo Using default deps download dir: !DEPS_DOWNLOAD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir" ) ) if "!DEPS_DOWNLOAD_DIR!" == "" ( echo ERROR: Deps download dir not set! 1>&2 exit /b 102 ) ) echo Deps download dir: %DEPS_DOWNLOAD_DIR% if not "%ARG_DEPS_BUILD_DIR%" == "" ( set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%" ) if "%DEPS_BUILD_DIR%" == "" ( set DEPS_BUILD_DIR=%CD%\b_deps\ echo Using default deps build dir: !DEPS_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir" ) ) if "!DEPS_BUILD_DIR!" == "" ( echo ERROR: Deps build dir not set! 1>&2 exit /b 102 ) ) echo Deps build dir: %DEPS_BUILD_DIR% :skip_deps_args_check if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check if not "%ARG_KRITA_BUILD_DIR%" == "" ( set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%" ) if "%KRITA_BUILD_DIR%" == "" ( set KRITA_BUILD_DIR=%CD%\b\ echo Using default Krita build dir: !KRITA_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir" ) ) if "!KRITA_BUILD_DIR!" == "" ( echo ERROR: Krita build dir not set! 1>&2 exit /b 102 ) ) echo Krita build dir: %KRITA_BUILD_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :skip_krita_args_check echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH% echo Creating dirs... if NOT "%ARG_SKIP_DEPS%" == "1" ( mkdir %DEPS_DOWNLOAD_DIR% if errorlevel 1 ( if not exist "%DEPS_DOWNLOAD_DIR%\" ( echo ERROR: Cannot create deps download dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_BUILD_DIR% if errorlevel 1 ( if not exist "%DEPS_BUILD_DIR%\" ( echo ERROR: Cannot create deps build dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_INSTALL_DIR% if errorlevel 1 ( if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot create deps install dir! 1>&2 exit /b 103 ) ) ) if NOT "%ARG_SKIP_KRITA%" == "1" ( mkdir %KRITA_BUILD_DIR% if errorlevel 1 ( if not exist "%KRITA_BUILD_DIR%\" ( echo ERROR: Cannot create Krita build dir! 1>&2 exit /b 103 ) ) mkdir %KRITA_INSTALL_DIR% if errorlevel 1 ( if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot create Krita install dir! 1>&2 exit /b 103 ) ) ) echo. set CMAKE_BUILD_TYPE=RelWithDebInfo set QT_ENABLE_DEBUG_INFO=OFF :: Paths for CMake set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%" set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %" set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%" set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %" set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%" set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %" set PATH=%DEPS_INSTALL_DIR%\bin;%PATH% if not "%GETTEXT_SEARCH_PATH%" == "" ( set PATH=!PATH!;!GETTEXT_SEARCH_PATH! ) :: Prepare the CMake command lines set CMDLINE_CMAKE_DEPS="%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -G "MinGW Makefiles" ^ -DUSE_QT_TABLET_WINDOWS=ON ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% set CMDLINE_CMAKE_KRITA="%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ -DBoost_DEBUG=OFF ^ -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ -DBUILD_TESTING=OFF ^ -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ -DFOUNDATION_BUILD=ON ^ -DUSE_QT_TABLET_WINDOWS=ON ^ -Wno-dev ^ -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% :: Launch CMD prompt if requested if "%ARG_CMD%" == "1" ( doskey cmake-deps=cmd /c "pushd %DEPS_BUILD_DIR% && %CMDLINE_CMAKE_DEPS%" doskey cmake-krita=cmd /c "pushd %KRITA_BUILD_DIR% && %CMDLINE_CMAKE_KRITA%" doskey make-deps=cmd /c "pushd %DEPS_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target $*" doskey make-krita=cmd /c "pushd %KRITA_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%" echo. title Krita build - %KRITA_SRC_DIR% ^(deps: %DEPS_BUILD_DIR%, krita: %KRITA_BUILD_DIR%^) echo You're now in the build environment. echo The following macros are available: echo cmake-deps echo -- Run CMake for the deps. echo make-deps ^ echo -- Run build for the specified deps target. The target name should echo include the `ext_` prefix, e.g. `ext_qt`. echo cmake-krita echo -- Run CMake for Krita. echo make-krita echo -- Run build for Krita's `install` target. echo. echo For more info, type `doskey /macros` to view the macro commands. cmd /k exit ) if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps pushd %DEPS_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter deps build dir! 1>&2 exit /b 104 ) echo Running CMake for deps... @echo on %CMDLINE_CMAKE_DEPS% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off echo. -set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost eigen3 exiv2 fftw3 +set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost exiv2 fftw3 eigen3 set EXT_TARGETS=%EXT_TARGETS% ilmbase jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic set EXT_TARGETS=%EXT_TARGETS% python sip pyqt set EXT_TARGETS=%EXT_TARGETS% quazip for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... @echo on %CMDLINE_CMAKE_KRITA% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off echo. echo Building Krita... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS% if errorlevel 1 ( echo ERROR: Building of Krita failed! 1>&2 exit /b 105 ) echo. echo ******** Built Krita ******** popd :skip_build_krita echo Krita build completed! diff --git a/config-set-has-border-in-full-screen-default.h.in b/config-set-has-border-in-full-screen-default.h.in new file mode 100644 index 0000000000..40b7b3a032 --- /dev/null +++ b/config-set-has-border-in-full-screen-default.h.in @@ -0,0 +1,4 @@ +/* config-hdr.h. Generated by cmake from config-set-has-border-in-full-screen-default.h.in */ + +/* Define if QWindowsWindowFunctions::setHasBorderInFullScreenDefault is available */ +#cmakedefine HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT 1 diff --git a/krita/main.cc b/krita/main.cc index d91a292eae..86996c745c 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,509 +1,530 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050900 #include #endif #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include +#else +#include +#endif +#include "config-set-has-border-in-full-screen-default.h" +#ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT +#include #endif #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif #ifdef Q_OS_ANDROID __attribute__ ((visibility ("default"))) #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); #if QT_VERSION >= 0x050900 QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); #ifdef Q_OS_WIN // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", "d3d11"); #endif const QSurfaceFormat format = KisOpenGL::selectSurfaceFormat(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); if (format.renderableType() == QSurfaceFormat::OpenGLES) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); } else { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); } KisOpenGL::setDefaultSurfaceFormat(format); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); + + if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && + qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { + + QRect customTabletRect; + KisDlgCustomTabletResolution::Mode tabletMode = + KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); + KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); + } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); +#ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT + if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { + QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); + } +#endif + KisUsageLogger::writeHeader(); if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; #if QT_VERSION >= 0x050900 QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #endif #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information KisUsageLogger::write("\nHardware Information\n"); KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp index b60a462d62..0e5bf9af50 100644 --- a/libs/image/layerstyles/kis_ls_utils.cpp +++ b/libs/image/layerstyles/kis_ls_utils.cpp @@ -1,586 +1,587 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_utils.h" #include #include #include #include "psd.h" #include "kis_default_bounds.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_iterator_ng.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_layer_style_filter_environment.h" #include "kis_selection_filters.h" #include "kis_multiple_projection.h" namespace KisLsUtils { QRect growSelectionUniform(KisPixelSelectionSP selection, int growSize, const QRect &applyRect) { QRect changeRect = applyRect; if (growSize > 0) { KisGrowSelectionFilter filter(growSize, growSize); changeRect = filter.changeRect(applyRect); filter.process(selection, applyRect); } else if (growSize < 0) { KisShrinkSelectionFilter filter(qAbs(growSize), qAbs(growSize), false); changeRect = filter.changeRect(applyRect); filter.process(selection, applyRect); } return changeRect; } KisSelectionSP selectionFromAlphaChannel(KisPaintDeviceSP device, const QRect &srcRect) { const KoColorSpace *cs = device->colorSpace(); KisSelectionSP baseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); KisPixelSelectionSP selection = baseSelection->pixelSelection(); KisSequentialConstIterator srcIt(device, srcRect); KisSequentialIterator dstIt(selection, srcRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); const quint8* srcPtr = srcIt.rawDataConst(); *dstPtr = cs->opacityU8(srcPtr); } return baseSelection; } void findEdge(KisPixelSelectionSP selection, const QRect &applyRect, const bool edgeHidden) { KisSequentialIterator dstIt(selection, applyRect); if (edgeHidden) { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = (*pixelPtr < 24) ? *pixelPtr * 10 : 0xFF; } } else { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = 0xFF; } } } QRect growRectFromRadius(const QRect &rc, int radius) { int halfSize = KisGaussianKernel::kernelSizeFromRadius(radius) / 2; return rc.adjusted(-halfSize, -halfSize, halfSize, halfSize); } void applyGaussianWithTransaction(KisPixelSelectionSP selection, const QRect &applyRect, qreal radius) { KisGaussianKernel::applyGaussian(selection, applyRect, radius, radius, QBitArray(), 0, true); } namespace Private { void getGradientTable(const KoAbstractGradient *gradient, QVector *table, const KoColorSpace *colorSpace) { KIS_ASSERT_RECOVER_RETURN(table->size() == 256); for (int i = 0; i < 256; i++) { gradient->colorAt(((*table)[i]), qreal(i) / 255.0); (*table)[i].convertTo(colorSpace); } } struct LinearGradientIndex { int popOneIndex(int selectionAlpha) { return 255 - selectionAlpha; } bool nextPixel() { return true; } }; struct JitterGradientIndex { JitterGradientIndex(const QRect &applyRect, int jitter, const KisLayerStyleFilterEnvironment *env) : randomSelection(env->cachedRandomSelection(applyRect)), noiseIt(randomSelection, applyRect), m_jitterCoeff(jitter * 255 / 100) { } int popOneIndex(int selectionAlpha) { int gradientIndex = 255 - selectionAlpha; gradientIndex += m_jitterCoeff * *noiseIt.rawDataConst() >> 8; gradientIndex &= 0xFF; return gradientIndex; } bool nextPixel() { return noiseIt.nextPixel(); } private: KisPixelSelectionSP randomSelection; KisSequentialConstIterator noiseIt; int m_jitterCoeff; }; template void applyGradientImpl(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, IndexFetcher &indexFetcher) { KIS_ASSERT_RECOVER_RETURN( *table.first().colorSpace() == *device->colorSpace()); const KoColorSpace *cs = device->colorSpace(); const int pixelSize = cs->pixelSize(); KisSequentialConstIterator selIt(selection, applyRect); KisSequentialIterator dstIt(device, applyRect); if (edgeHidden) { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { quint8 selAlpha = *selIt.rawDataConst(); int gradientIndex = indexFetcher.popOneIndex(selAlpha); const KoColor &color = table[gradientIndex]; quint8 tableAlpha = color.opacityU8(); memcpy(dstIt.rawData(), color.data(), pixelSize); if (selAlpha < 24 && tableAlpha == 255) { tableAlpha = int(selAlpha) * 10 * tableAlpha >> 8; cs->setOpacity(dstIt.rawData(), tableAlpha, 1); } } } else { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { int gradientIndex = indexFetcher.popOneIndex(*selIt.rawDataConst()); const KoColor &color = table[gradientIndex]; memcpy(dstIt.rawData(), color.data(), pixelSize); } } } void applyGradient(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, int jitter, const KisLayerStyleFilterEnvironment *env) { if (!jitter) { LinearGradientIndex fetcher; applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } else { JitterGradientIndex fetcher(applyRect, jitter, env); applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } } } const int noiseNeedBorder = 8; void applyNoise(KisPixelSelectionSP selection, const QRect &applyRect, int noise, const psd_layer_effects_context *context, const KisLayerStyleFilterEnvironment *env) { Q_UNUSED(context); const QRect overlayRect = kisGrowRect(applyRect, noiseNeedBorder); KisPixelSelectionSP randomSelection = env->cachedRandomSelection(overlayRect); KisPixelSelectionSP randomOverlay = new KisPixelSelection(); KisSequentialConstIterator noiseIt(randomSelection, overlayRect); KisSequentialConstIterator srcIt(selection, overlayRect); KisRandomAccessorSP dstIt = randomOverlay->createRandomAccessorNG(overlayRect.x(), overlayRect.y()); while (noiseIt.nextPixel() && srcIt.nextPixel()) { int itX = noiseIt.x(); int itY = noiseIt.y(); int x = itX + (*noiseIt.rawDataConst() >> 4) - 8; int y = itY + (*noiseIt.rawDataConst() & 0x0F) - 8; x = (x + itX) >> 1; y = (y + itY) >> 1; dstIt->moveTo(x, y); quint8 dstAlpha = *dstIt->rawData(); quint8 srcAlpha = *srcIt.rawDataConst(); int value = qMin(255, dstAlpha + srcAlpha); *dstIt->rawData() = value; } noise = noise * 255 / 100; KisPainter gc(selection); gc.setOpacity(noise); gc.setCompositeOp(COMPOSITE_COPY); gc.bitBlt(applyRect.topLeft(), randomOverlay, applyRect); } //const int FULL_PERCENT_RANGE = 100; void adjustRange(KisPixelSelectionSP selection, const QRect &applyRect, const int range) { KIS_ASSERT_RECOVER_RETURN(range >= 1 && range <= 100); quint8 rangeTable[256]; for(int i = 0; i < 256; i ++) { quint8 value = i * 100 / range; rangeTable[i] = qMin(value, quint8(255)); } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = rangeTable[*pixelPtr]; } } void applyContourCorrection(KisPixelSelectionSP selection, const QRect &applyRect, const quint8 *lookup_table, bool antiAliased, bool edgeHidden) { quint8 contour[PSD_LOOKUP_TABLE_SIZE] = { 0x00, 0x0b, 0x16, 0x21, 0x2c, 0x37, 0x42, 0x4d, 0x58, 0x63, 0x6e, 0x79, 0x84, 0x8f, 0x9a, 0xa5, 0xb0, 0xbb, 0xc6, 0xd1, 0xdc, 0xf2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; if (edgeHidden) { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[i] >> 8; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)] >> 8; } } } else { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[i]; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)]; } } } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = contour[*pixelPtr]; } } void knockOutSelection(KisPixelSelectionSP selection, KisPixelSelectionSP knockOutSelection, const QRect &srcRect, const QRect &dstRect, const QRect &totalNeedRect, const bool knockOutInverted) { KIS_ASSERT_RECOVER_RETURN(knockOutSelection); QRect knockOutRect = !knockOutInverted ? srcRect : totalNeedRect; knockOutRect &= dstRect; KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(knockOutRect.topLeft(), knockOutSelection, knockOutRect); } void fillPattern(KisPaintDeviceSP fillDevice, const QRect &applyRect, KisLayerStyleFilterEnvironment *env, int scale, KoPattern *pattern, int horizontalPhase, int verticalPhase, bool alignWithLayer) { if (scale != 100) { warnKrita << "KisLsOverlayFilter::applyOverlay(): Pattern scaling is NOT implemented!"; } + KIS_SAFE_ASSERT_RECOVER_RETURN(pattern); QSize psize(pattern->width(), pattern->height()); QPoint patternOffset(qreal(psize.width()) * horizontalPhase / 100, qreal(psize.height()) * verticalPhase / 100); const QRect boundsRect = alignWithLayer ? env->layerBounds() : env->defaultBounds(); patternOffset += boundsRect.topLeft(); patternOffset.rx() %= psize.width(); patternOffset.ry() %= psize.height(); QRect fillRect = applyRect | applyRect.translated(patternOffset); KisFillPainter gc(fillDevice); gc.fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), pattern, -patternOffset); gc.end(); } void fillOverlayDevice(KisPaintDeviceSP fillDevice, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) { if (config->fillType() == psd_fill_solid_color) { KoColor color(config->color(), fillDevice->colorSpace()); fillDevice->setDefaultPixel(color); } else if (config->fillType() == psd_fill_pattern) { fillPattern(fillDevice, applyRect, env, config->scale(), config->pattern(), config->horizontalPhase(), config->verticalPhase(), config->alignWithLayer()); } else if (config->fillType() == psd_fill_gradient) { const QRect boundsRect = config->alignWithLayer() ? env->layerBounds() : env->defaultBounds(); QPoint center = boundsRect.center(); center += QPoint(boundsRect.width() * config->gradientXOffset() / 100, boundsRect.height() * config->gradientYOffset() / 100); int width = (boundsRect.width() * config->scale() + 100) / 200; int height = (boundsRect.height() * config->scale() + 100) / 200; /* copy paste from libpsd */ int angle = config->angle(); int corner_angle = (int)(atan((qreal)boundsRect.height() / boundsRect.width()) * 180 / M_PI + 0.5); int sign_x = 1; int sign_y = 1; if(angle < 0) { angle += 360; } if (angle >= 90 && angle < 180) { angle = 180 - angle; sign_x = -1; } else if (angle >= 180 && angle < 270) { angle = angle - 180; sign_x = -1; sign_y = -1; } else if (angle >= 270 && angle <= 360) { angle = 360 - angle; sign_y = -1; } int radius_x = 0; int radius_y = 0; if (angle <= corner_angle) { radius_x = width; radius_y = (int)(radius_x * tan(kisDegreesToRadians(qreal(angle))) + 0.5); } else { radius_y = height; radius_x = (int)(radius_y / tan(kisDegreesToRadians(qreal(angle))) + 0.5); } int radius_corner = (int)(std::sqrt((qreal)(radius_x * radius_x + radius_y * radius_y)) + 0.5); /* end of copy paste from libpsd */ KisGradientPainter gc(fillDevice); gc.setGradient(config->gradient().data()); QPointF gradStart; QPointF gradEnd; KisGradientPainter::enumGradientRepeat repeat = KisGradientPainter::GradientRepeatNone; QPoint rectangularOffset(sign_x * radius_x, -sign_y * radius_y); switch(config->style()) { case psd_gradient_style_linear: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; case psd_gradient_style_radial: gc.setGradientShape(KisGradientPainter::GradientShapeRadial); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + QPointF(radius_corner, 0); break; case psd_gradient_style_angle: gc.setGradientShape(KisGradientPainter::GradientShapeConical); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + rectangularOffset; break; case psd_gradient_style_reflected: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatAlternate; gradStart = center - rectangularOffset; gradEnd = center; break; case psd_gradient_style_diamond: gc.setGradientShape(KisGradientPainter::GradientShapeBiLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; default: qFatal("Gradient Overlay: unknown switch case!"); break; } gc.paintGradient(gradStart, gradEnd, repeat, 0.0, config->reverse(), applyRect); } } void applyFinalSelection(const QString &projectionId, KisSelectionSP baseSelection, KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &/*srcRect*/, const QRect &dstRect, const psd_layer_effects_context */*context*/, const psd_layer_effects_shadow_base *config, const KisLayerStyleFilterEnvironment *env) { const KoColor effectColor(config->color(), srcDevice->colorSpace()); const QRect effectRect(dstRect); const QString compositeOp = config->blendMode(); const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); KisPaintDeviceSP dstDevice = dst->getProjection(projectionId, compositeOp, opacityU8, QBitArray(), srcDevice); if (config->fillType() == psd_fill_solid_color) { KisFillPainter gc(dstDevice); gc.setCompositeOp(COMPOSITE_COPY); gc.setSelection(baseSelection); gc.fillSelection(effectRect, effectColor); gc.end(); } else if (config->fillType() == psd_fill_gradient) { if (!config->gradient()) { warnKrita << "KisLsUtils::applyFinalSelection: Gradient object is null! Skipping..."; return; } QVector table(256); Private::getGradientTable(config->gradient().data(), &table, dstDevice->colorSpace()); Private::applyGradient(dstDevice, baseSelection->pixelSelection(), effectRect, table, true, config->jitter(), env); } //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("6_device_shadow.png"); } bool checkEffectEnabled(const psd_layer_effects_shadow_base *config, KisMultipleProjection *dst) { bool result = config->effectEnabled(); if (!result) { dst->freeAllProjections(); } return result; } } diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 94c9fa83f4..1607674ec7 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,600 +1,617 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) if (ANDROID) add_definitions(-DQT_OPENGL_ES_3) add_definitions(-DHAS_ONLY_OPENGL_ES) endif() add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp canvas/KisMirrorAxisConfig.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc KisOcioConfiguration.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp KisNodeDisplayModeAdapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp tool/KisSelectionToolFactoryBase.cpp tool/KisToolPaintFactoryBase.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisLayerStyleAngleSelector.cpp widgets/KisMemoryReportButton.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp + utils/KisSpinBoxSplineUnitConverter.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp actions/KisTransformToolActivationCommand.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h ) if(WIN32) + # Private headers are needed for: + # * KisDlgCustomTabletResolution + # * KisScreenInformationAdapter + include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) + set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) + else() + set(kritaui_LIB_SRCS + ${kritaui_LIB_SRCS} + dialogs/KisDlgCustomTabletResolution.cpp + ) endif() endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui layerstyles/wdgKisLayerStyleAngleSelector.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) -if(WIN32 AND NOT USE_QT_TABLET_WINDOWS) - ki18n_wrap_ui(kritaui_LIB_SRCS - input/wintab/kis_screen_size_choice_dialog.ui - ) -endif() +if(WIN32) + if(USE_QT_TABLET_WINDOWS) + ki18n_wrap_ui(kritaui_LIB_SRCS + dialogs/KisDlgCustomTabletResolution.ui + ) + else() + ki18n_wrap_ui(kritaui_LIB_SRCS + input/wintab/kis_screen_size_choice_dialog.ui + ) + endif() +endif() add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2 ) if (ANDROID) target_link_libraries(kritaui GLESv3) target_link_libraries(kritaui Qt5::Gui) endif() if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE AND NOT ANDROID) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index cd99593a55..b27b049c23 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,859 +1,859 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { - QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; + QStringList styles = QStringList() /*<< "breeze"*/ << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType("ko_gamutmasks", "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } void KisApplication::loadResources() { // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); } else if (exportSequence) { KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated if (!doc->image()->animationInterface()->hasAnimation()) { errKrita << "This file has no animation." << endl; QTimer::singleShot(0, this, SLOT(quit())); return 1; } doc->setFileBatchMode(true); int sequenceStart = 0; KisAsyncAnimationFramesSaveDialog exporter(doc->image(), doc->image()->animationInterface()->fullClipRange(), exportFileName, sequenceStart, 0); exporter.setBatchMode(d->batchRun); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(0); if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { errKrita << i18n("Failed to render animation frames!") << endl; } QTimer::singleShot(0, this, SLOT(quit())); } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! // Hidden autosave files QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra"); // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Visibile autosave files filters = QStringList() << QString("krita-*-*-autosave.kra"); autosaveFiles += dir.entryList(filters, QDir::Files); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); if (templateURL.scheme().isEmpty()) { templateURL.setScheme("file"); } KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 6ea0ea6521..4381cf8d54 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2636 +1,2627 @@ /* 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 -#ifdef Q_OS_WIN - #include -#endif - 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_OSX 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(); -#ifdef Q_OS_WIN - auto w = qApp->activeWindow(); - if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); -#endif - 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) { 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(); } } 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( ")"); } d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); 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 mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; QString versionString = KritaVersionWrapper::versionString(true); #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setCaption(QString("%1: %2").arg(versionString).arg(caption), mod); return; #endif setCaption(caption, mod); } 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); 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); // 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); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); 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_OSX 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(); QBrush brush(cfg.getMDIBackgroundColor()); 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_OSX 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/brushhud/kis_brush_hud.cpp b/libs/ui/brushhud/kis_brush_hud.cpp index 17968359e2..34d93ebde3 100644 --- a/libs/ui/brushhud/kis_brush_hud.cpp +++ b/libs/ui/brushhud/kis_brush_hud.cpp @@ -1,287 +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_brush_hud.h" #include #include #include #include #include #include #include #include #include #include #include "kis_uniform_paintop_property.h" #include "kis_slider_based_paintop_property.h" #include "kis_uniform_paintop_property_widget.h" #include "kis_canvas_resource_provider.h" #include "kis_paintop_preset.h" #include "kis_paintop_settings.h" #include "kis_signal_auto_connection.h" #include "kis_paintop_settings_update_proxy.h" #include "kis_icon_utils.h" #include "kis_dlg_brush_hud_config.h" #include "kis_brush_hud_properties_config.h" #include "kis_elided_label.h" #include "kis_debug.h" struct KisBrushHud::Private { QPointer lblPresetName; QPointer lblPresetIcon; QPointer wdgProperties; QPointer wdgPropertiesArea; QPointer propertiesLayout; QPointer btnConfigure; KisCanvasResourceProvider *provider; KisSignalAutoConnectionsStore connections; KisSignalAutoConnectionsStore presetConnections; KisPaintOpPresetSP currentPreset; }; KisBrushHud::KisBrushHud(KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint), m_d(new Private) { m_d->provider = provider; QVBoxLayout *layout = new QVBoxLayout(); QHBoxLayout *labelLayout = new QHBoxLayout(); m_d->lblPresetIcon = new QLabel(this); const QSize iconSize = QSize(22,22); m_d->lblPresetIcon->setMinimumSize(iconSize); m_d->lblPresetIcon->setMaximumSize(iconSize); m_d->lblPresetIcon->setScaledContents(true); m_d->lblPresetName = new KisElidedLabel("", Qt::ElideMiddle, this); m_d->btnConfigure = new QToolButton(this); m_d->btnConfigure->setAutoRaise(true); connect(m_d->btnConfigure, SIGNAL(clicked()), SLOT(slotConfigBrushHud())); labelLayout->addWidget(m_d->lblPresetIcon); labelLayout->addWidget(m_d->lblPresetName); labelLayout->addWidget(m_d->btnConfigure); layout->addLayout(labelLayout); m_d->wdgPropertiesArea = new QScrollArea(this); m_d->wdgPropertiesArea->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_d->wdgPropertiesArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_d->wdgPropertiesArea->setWidgetResizable(true); m_d->wdgProperties = new QWidget(this); m_d->propertiesLayout = new QVBoxLayout(this); m_d->propertiesLayout->setSpacing(0); m_d->propertiesLayout->setContentsMargins(0, 0, 22, 0); m_d->propertiesLayout->setSizeConstraint(QLayout::SetMinimumSize); // not adding any widgets until explicitly requested m_d->wdgProperties->setLayout(m_d->propertiesLayout); m_d->wdgPropertiesArea->setWidget(m_d->wdgProperties); layout->addWidget(m_d->wdgPropertiesArea); updateIcons(); setLayout(layout); setCursor(Qt::ArrowCursor); // Prevent tablet events from being captured by the canvas setAttribute(Qt::WA_NoMousePropagation, true); } KisBrushHud::~KisBrushHud() { } QSize KisBrushHud::sizeHint() const { QSize size = QWidget::sizeHint(); return QSize(size.width(), parentWidget()->height()); } void KisBrushHud::updateIcons() { + this->setPalette(qApp->palette()); + for(int i=0; ichildren().size(); i++) { + QWidget *w = qobject_cast(this->children().at(i)); + if (w) { + w->setPalette(qApp->palette()); + } + } + for(int i=0; iwdgProperties->children().size(); i++) { + KisUniformPaintOpPropertyWidget *w = qobject_cast(m_d->wdgProperties->children().at(i)); + if (w) { + w->slotThemeChanged(qApp->palette()); + } + } m_d->btnConfigure->setIcon(KisIconUtils::loadIcon("applications-system")); } void KisBrushHud::slotReloadProperties() { m_d->presetConnections.clear(); clearProperties(); updateProperties(); } void KisBrushHud::clearProperties() const { while (m_d->propertiesLayout->count()) { QLayoutItem *item = m_d->propertiesLayout->takeAt(0); QWidget *w = item->widget(); if (w) { w->deleteLater(); } delete item; } m_d->currentPreset.clear(); } void KisBrushHud::updateProperties() { KisPaintOpPresetSP preset = m_d->provider->currentPreset(); if (preset == m_d->currentPreset) return; m_d->presetConnections.clear(); clearProperties(); m_d->currentPreset = preset; m_d->presetConnections.addConnection( m_d->currentPreset->updateProxy(), SIGNAL(sigUniformPropertiesChanged()), this, SLOT(slotReloadProperties())); m_d->lblPresetIcon->setPixmap(QPixmap::fromImage(preset->image())); m_d->lblPresetName->setLongText(preset->name()); QList properties; { QList allProperties = preset->uniformProperties(); QList discardedProperties; KisBrushHudPropertiesConfig cfg; cfg.filterProperties(preset->paintOp().id(), allProperties, &properties, &discardedProperties); } Q_FOREACH(auto property, properties) { QWidget *w = 0; if (!property->isVisible()) continue; if (property->type() == KisUniformPaintOpProperty::Int) { w = new KisUniformPaintOpPropertyIntSlider(property, m_d->wdgProperties); } else if (property->type() == KisUniformPaintOpProperty::Double) { w = new KisUniformPaintOpPropertyDoubleSlider(property, m_d->wdgProperties); } else if (property->type() == KisUniformPaintOpProperty::Bool) { w = new KisUniformPaintOpPropertyCheckBox(property, m_d->wdgProperties); } else if (property->type() == KisUniformPaintOpProperty::Combo) { w = new KisUniformPaintOpPropertyComboBox(property, m_d->wdgProperties); } if (w) { w->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_d->propertiesLayout->addWidget(w); } } m_d->propertiesLayout->addStretch(); resize(sizeHint()); } void KisBrushHud::showEvent(QShowEvent *event) { m_d->connections.clear(); m_d->connections.addUniqueConnection( m_d->provider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); updateProperties(); QWidget::showEvent(event); } void KisBrushHud::hideEvent(QHideEvent *event) { m_d->connections.clear(); QWidget::hideEvent(event); clearProperties(); } void KisBrushHud::slotCanvasResourceChanged(int key, const QVariant &resource) { Q_UNUSED(resource); if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) { updateProperties(); } } void KisBrushHud::paintEvent(QPaintEvent *event) { QColor bgColor = palette().color(QPalette::Window); QPainter painter(this); painter.fillRect(rect() & event->rect(), bgColor); painter.end(); QWidget::paintEvent(event); } bool KisBrushHud::event(QEvent *event) { switch (event->type()) { case QEvent::TabletPress: case QEvent::TabletMove: case QEvent::TabletRelease: // Allow the tablet event to be translated to a mouse event on certain platforms break; case QEvent::MouseButtonPress: case QEvent::MouseMove: case QEvent::MouseButtonRelease: case QEvent::Wheel: event->accept(); return true; default: break; } return QWidget::event(event); } void KisBrushHud::slotConfigBrushHud() { if (!m_d->currentPreset) return; KisDlgConfigureBrushHud dlg(m_d->currentPreset); dlg.exec(); slotReloadProperties(); } diff --git a/libs/ui/brushhud/kis_uniform_paintop_property_widget.cpp b/libs/ui/brushhud/kis_uniform_paintop_property_widget.cpp index c531494ba0..fbd05e5639 100644 --- a/libs/ui/brushhud/kis_uniform_paintop_property_widget.cpp +++ b/libs/ui/brushhud/kis_uniform_paintop_property_widget.cpp @@ -1,225 +1,235 @@ /* * 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_uniform_paintop_property_widget.h" #include #include #include #include "kis_slider_spin_box.h" #include "kis_acyclic_signal_connector.h" #include "kis_slider_based_paintop_property.h" #include "kis_combo_based_paintop_property.h" #include "kis_debug.h" /****************************************************************/ /* KisUniformPaintOpPropertyWidget */ /****************************************************************/ struct KisUniformPaintOpPropertyWidget::Private { Private(KisUniformPaintOpPropertySP _property) : property(_property) {} typedef KisUniformPaintOpProperty::Type Type; KisUniformPaintOpPropertySP property; }; KisUniformPaintOpPropertyWidget::KisUniformPaintOpPropertyWidget(KisUniformPaintOpPropertySP property, QWidget *parent) : QWidget(parent), m_d(new Private(property)) { KisAcyclicSignalConnector *conn = new KisAcyclicSignalConnector(this); conn->connectForwardVariant(property.data(), SIGNAL(valueChanged(QVariant)), this, SLOT(setValue(QVariant))); conn->connectBackwardVariant(this, SIGNAL(valueChanged(QVariant)), property.data(), SLOT(setValue(QVariant))); } KisUniformPaintOpPropertyWidget::~KisUniformPaintOpPropertyWidget() { } KisUniformPaintOpPropertySP KisUniformPaintOpPropertyWidget::property() const { return m_d->property; } +void KisUniformPaintOpPropertyWidget::slotThemeChanged(QPalette pal) +{ + for(int i=0; ichildren().size(); i++) { + QWidget *w = qobject_cast(this->children().at(i)); + if (w) { + w->setPalette(pal); + } + } +} + /****************************************************************/ /* KisUniformPaintOpPropertyIntSlider */ /****************************************************************/ KisUniformPaintOpPropertyIntSlider::KisUniformPaintOpPropertyIntSlider(KisUniformPaintOpPropertySP property, QWidget *parent) : KisUniformPaintOpPropertyWidget(property, parent) { const QString prefix = QString("%1: ").arg(property->name()); QVBoxLayout *layout = new QVBoxLayout(this); KisIntSliderBasedPaintOpProperty *sliderProperty = dynamic_cast(property.data()); KIS_ASSERT_RECOVER_RETURN(sliderProperty); m_slider = new KisSliderSpinBox(this); m_slider->setBlockUpdateSignalOnDrag(true); m_slider->setRange(sliderProperty->min(), sliderProperty->max()); m_slider->setSingleStep(sliderProperty->singleStep()); m_slider->setPageStep(sliderProperty->pageStep()); m_slider->setPrefix(prefix); m_slider->setSuffix(sliderProperty->suffix()); m_slider->setExponentRatio(sliderProperty->exponentRatio()); m_slider->setValue(sliderProperty->value().toInt()); connect(m_slider, SIGNAL(valueChanged(int)), SLOT(slotSliderChanged(int))); layout->addWidget(m_slider); setLayout(layout); } void KisUniformPaintOpPropertyIntSlider::setValue(const QVariant &value) { m_slider->setValue(value.toInt()); } void KisUniformPaintOpPropertyIntSlider::slotSliderChanged(int value) { emit valueChanged(value); } /****************************************************************/ /* KisUniformPaintOpPropertyDoubleSlider */ /****************************************************************/ KisUniformPaintOpPropertyDoubleSlider::KisUniformPaintOpPropertyDoubleSlider(KisUniformPaintOpPropertySP property, QWidget *parent) : KisUniformPaintOpPropertyWidget(property, parent) { const QString prefix = QString("%1: ").arg(property->name()); QVBoxLayout *layout = new QVBoxLayout(this); KisDoubleSliderBasedPaintOpProperty *sliderProperty = dynamic_cast(property.data()); KIS_ASSERT_RECOVER_RETURN(sliderProperty); m_slider = new KisDoubleSliderSpinBox(this); m_slider->setBlockUpdateSignalOnDrag(true); m_slider->setRange(sliderProperty->min(), sliderProperty->max(), sliderProperty->decimals()); m_slider->setSingleStep(sliderProperty->singleStep()); m_slider->setPrefix(prefix); m_slider->setSuffix(sliderProperty->suffix()); m_slider->setExponentRatio(sliderProperty->exponentRatio()); m_slider->setValue(sliderProperty->value().toReal()); connect(m_slider, SIGNAL(valueChanged(qreal)), SLOT(slotSliderChanged(qreal))); layout->addWidget(m_slider); setLayout(layout); } void KisUniformPaintOpPropertyDoubleSlider::setValue(const QVariant &value) { m_slider->setValue(value.toReal()); } void KisUniformPaintOpPropertyDoubleSlider::slotSliderChanged(qreal value) { emit valueChanged(value); } /****************************************************************/ /* KisUniformPaintOpPropertyCheckBox */ /****************************************************************/ KisUniformPaintOpPropertyCheckBox::KisUniformPaintOpPropertyCheckBox(KisUniformPaintOpPropertySP property, QWidget *parent) : KisUniformPaintOpPropertyWidget(property, parent) { QVBoxLayout *layout = new QVBoxLayout(this); m_checkBox = new QCheckBox(property->name(), this); m_checkBox->setChecked(property->value().toBool()); connect(m_checkBox, SIGNAL(toggled(bool)), SLOT(slotCheckBoxChanged(bool))); layout->addWidget(m_checkBox); setLayout(layout); } void KisUniformPaintOpPropertyCheckBox::setValue(const QVariant &value) { m_checkBox->setChecked(value.toBool()); } void KisUniformPaintOpPropertyCheckBox::slotCheckBoxChanged(bool value) { emit valueChanged(value); } /****************************************************************/ /* KisUniformPaintOpPropertyComboBox */ /****************************************************************/ KisUniformPaintOpPropertyComboBox::KisUniformPaintOpPropertyComboBox(KisUniformPaintOpPropertySP property, QWidget *parent) : KisUniformPaintOpPropertyWidget(property, parent) { QVBoxLayout *layout = new QVBoxLayout(this); KisComboBasedPaintOpProperty *comboProperty = dynamic_cast(property.data()); KIS_ASSERT_RECOVER_RETURN(comboProperty); const QList items = comboProperty->items(); const QList icons = comboProperty->icons(); m_comboBox = new QComboBox(this); KIS_SAFE_ASSERT_RECOVER_RETURN(icons.isEmpty() || items.size() == icons.size()); if (!icons.isEmpty()) { auto itemIt = items.constBegin(); auto iconIt = icons.constBegin(); while (itemIt != items.constEnd() && iconIt != icons.constEnd()) { m_comboBox->addItem(*iconIt, *itemIt); ++itemIt; ++iconIt; } } else { Q_FOREACH (const QString &item, items) { m_comboBox->addItem(item); } } m_comboBox->setCurrentIndex(property->value().toInt()); connect(m_comboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotComboBoxChanged(int))); layout->addWidget(m_comboBox); setLayout(layout); } void KisUniformPaintOpPropertyComboBox::setValue(const QVariant &value) { m_comboBox->setCurrentIndex(value.toInt()); } void KisUniformPaintOpPropertyComboBox::slotComboBoxChanged(int value) { emit valueChanged(value); } diff --git a/libs/ui/brushhud/kis_uniform_paintop_property_widget.h b/libs/ui/brushhud/kis_uniform_paintop_property_widget.h index 92303e34d8..1b521ea485 100644 --- a/libs/ui/brushhud/kis_uniform_paintop_property_widget.h +++ b/libs/ui/brushhud/kis_uniform_paintop_property_widget.h @@ -1,115 +1,116 @@ /* * 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. */ #ifndef __KIS_UNIFORM_PAINTOP_PROPERTY_WIDGET_H #define __KIS_UNIFORM_PAINTOP_PROPERTY_WIDGET_H #include #include #include "kis_uniform_paintop_property.h" class KisUniformPaintOpPropertyWidget : public QWidget { Q_OBJECT public: KisUniformPaintOpPropertyWidget(KisUniformPaintOpPropertySP property, QWidget *parent); ~KisUniformPaintOpPropertyWidget() override; + void slotThemeChanged(QPalette pal); protected: KisUniformPaintOpPropertySP property() const; protected Q_SLOTS: virtual void setValue(const QVariant &value) = 0; Q_SIGNALS: void valueChanged(const QVariant &value); private: struct Private; const QScopedPointer m_d; }; class KisSliderSpinBox; class KisDoubleSliderSpinBox; class QCheckBox; class KisUniformPaintOpPropertyIntSlider : public KisUniformPaintOpPropertyWidget { Q_OBJECT public: KisUniformPaintOpPropertyIntSlider(KisUniformPaintOpPropertySP property, QWidget *parent); void setValue(const QVariant &value) override; private Q_SLOTS: void slotSliderChanged(int value); private: KisSliderSpinBox *m_slider; }; class KisUniformPaintOpPropertyDoubleSlider : public KisUniformPaintOpPropertyWidget { Q_OBJECT public: KisUniformPaintOpPropertyDoubleSlider(KisUniformPaintOpPropertySP property, QWidget *parent); void setValue(const QVariant &value) override; private Q_SLOTS: void slotSliderChanged(qreal value); private: KisDoubleSliderSpinBox *m_slider; }; class KisUniformPaintOpPropertyCheckBox : public KisUniformPaintOpPropertyWidget { Q_OBJECT public: KisUniformPaintOpPropertyCheckBox(KisUniformPaintOpPropertySP property, QWidget *parent); void setValue(const QVariant &value) override; private Q_SLOTS: void slotCheckBoxChanged(bool value); private: QCheckBox *m_checkBox; }; class QComboBox; class KisUniformPaintOpPropertyComboBox : public KisUniformPaintOpPropertyWidget { Q_OBJECT public: KisUniformPaintOpPropertyComboBox(KisUniformPaintOpPropertySP property, QWidget *parent); void setValue(const QVariant &value) override; private Q_SLOTS: void slotComboBoxChanged(int value); private: QComboBox *m_comboBox; }; #endif /* __KIS_UNIFORM_PAINTOP_PROPERTY_WIDGET_H */ diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 91ff1c101c..8f505a5540 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1262 +1,1278 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.. */ #include "kis_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_undo_adapter.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" #include "KoZoomController.h" #include #include "opengl/kis_opengl_canvas_debugger.h" #include "kis_algebra_2d.h" #include "kis_image_signal_router.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceProvider* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , selectedShapesProxy(&shapeManager) , toolProxy(parent) , displayColorConverter(resourceManager, view) , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor canvasUpdateCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfigurationSP proofingConfig; bool softProofing = false; bool gamutCheck = false; bool proofingConfigUpdated = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInImage = false; bool bootstrapLodBlocked; QPointer currentlyActiveShapeManager; KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup; KisSignalCompressor frameRenderStartCompressor; KisSignalCompressor regionOfInterestUpdateCompressor; QRect regionOfInterest; QRect renderingLimit; int isBatchUpdateActive = 0; bool effectiveLodAllowedInImage() { return lodAllowedInImage && !bootstrapLodBlocked; } void setActiveShapeManager(KoShapeManager *shapeManager); }; namespace { KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node) { KoShapeManager *shapeManager = 0; KisSelectionSP selection; if (KisLayer *layer = dynamic_cast(node.data())) { KisShapeLayer *shapeLayer = dynamic_cast(layer); if (shapeLayer) { shapeManager = shapeLayer->shapeManager(); } } else if (KisSelectionMask *mask = dynamic_cast(node.data())) { selection = mask->selection(); } if (!shapeManager && selection && selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0); shapeManager = shapeSelection->shapeManager(); } return shapeManager; } } KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceProvider *resourceManager, KisView *view, KoShapeControllerBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); connect(view->mainWindow(), SIGNAL(screenChanged()), SLOT(slotConfigChanged())); KisImageConfig config(false); m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit()); m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit()); m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInImage = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInImage); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), selectedShapesProxy(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start())); connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection())); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest())); connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged())); initializeFpsDecoration(); } void KisCanvas2::initializeFpsDecoration() { KisConfig cfg(true); const bool shouldShowDebugOverlay = (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) || cfg.enableBrushSpeedLogging(); if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); if (cfg.enableBrushSpeedLogging()) { connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) { m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag); disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(KisAbstractCanvasWidget *widget) { if (m_d->popupPalette) { m_d->popupPalette->setParent(widget->widget()); } if (m_d->canvasWidget != 0) { widget->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = widget; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->widget()->setAutoFillBackground(false); widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent); widget->widget()->setMouseTracking(true); widget->widget()->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller && controller->canvas() == this) { controller->changeCanvasWidget(widget->widget()); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager) { if (shapeManager != currentlyActiveShapeManager) { currentlyActiveShapeManager = shapeManager; selectedShapesProxy.setShapeManager(shapeManager); } } KoShapeManager* KisCanvas2::shapeManager() const { KoShapeManager *localShapeManager = this->localShapeManager(); // sanity check for consistency of the local shape manager KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) { localShapeManager = globalShapeManager(); } return localShapeManager ? localShapeManager : globalShapeManager(); } KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const { return &m_d->selectedShapesProxy; } KoShapeManager* KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } KoShapeManager *KisCanvas2::localShapeManager() const { KisNodeSP node = m_d->view->currentNode(); KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node); if (localShapeManager != m_d->currentlyActiveShapeManager) { m_d->setActiveShapeManager(localShapeManager); } return localShapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const { return m_d->inputActionGroupsMask; } void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask) { m_d->inputActionGroupsMask = mask; } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg(true); m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg(true); QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL && KisOpenGL::hasOpenGL()); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL && !KisOpenGL::hasOpenGL()) { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; useOpenGL = false; } m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL); if (useOpenGL) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; m_d->displayColorConverter.notifyOpenGLCanvasIsActive(false); createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageSP image = m_d->view->image(); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); m_d->coordinatesConverter->setImage(image); m_d->toolProxy.initializeImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateStarted()), SLOT(slotBeginUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateEnded()), SLOT(slotEndUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigRequestLodPlanesSyncBlocked(bool)), SLOT(slotSetLodUpdatesBlocked(bool)), Qt::DirectConnection); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(startResizingImage()), Qt::DirectConnection); connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager())); connect(image, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged())); connect(image, SIGNAL(sigProfileChanged(const KoColorProfile*)), SLOT(slotImageColorSpaceChanged())); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); setLodAllowedInCanvas(m_d->lodAllowedInImage); emit sigCanvasEngineChanged(); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg(true); bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { /** * We don't do patched loading for openGL canvas, becasue it loads * the tiles, which are bascially "patches". Therefore, big chunks * of memory are never allocated. */ if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } QSharedPointer KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } void KisCanvas2::slotImageColorSpaceChanged() { KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); image->barrierLock(); m_d->canvasWidget->notifyImageColorSpaceChanged(image->colorSpace()); image->unlock(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { KisImageConfig cfg(false); m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; #if QT_VERSION >= 0x050700 if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof); if (softProof) { conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck); } } #else if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::SoftProofing; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing; } if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::GamutCheck; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck; } #endif m_d->proofingConfig->conversionFlags = conversionFlags; m_d->proofingConfigUpdated = true; startUpdateInPatches(this->image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotChangeProofingConfig() { setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::setProofingConfigUpdated(bool updated) { m_d->proofingConfigUpdated = updated; } bool KisCanvas2::proofingConfigUpdated() { return m_d->proofingConfigUpdated; } KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { m_d->proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { auto tryIssueCanvasUpdates = [this](const QRect &vRect) { if (!m_d->isBatchUpdateActive) { // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { m_d->savedUpdateRect = QRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) { m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } } }; auto uploadData = [this, tryIssueCanvasUpdates](const QVector &infoObjects) { QVector viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects); const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(), QRect(), std::bit_or()); tryIssueCanvasUpdates(vRect); }; bool shouldExplicitlyIssueUpdates = false; QVector infoObjects; KisUpdateInfoList originalInfoObjects; m_d->projectionUpdatesCompressor.takeUpdateInfo(originalInfoObjects); for (auto it = originalInfoObjects.constBegin(); it != originalInfoObjects.constEnd(); ++it) { KisUpdateInfoSP info = *it; const KisMarkerUpdateInfo *batchInfo = dynamic_cast(info.data()); if (batchInfo) { if (!infoObjects.isEmpty()) { uploadData(infoObjects); infoObjects.clear(); } if (batchInfo->type() == KisMarkerUpdateInfo::StartBatch) { m_d->isBatchUpdateActive++; } else if (batchInfo->type() == KisMarkerUpdateInfo::EndBatch) { m_d->isBatchUpdateActive--; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->isBatchUpdateActive >= 0); if (m_d->isBatchUpdateActive == 0) { shouldExplicitlyIssueUpdates = true; } } else if (batchInfo->type() == KisMarkerUpdateInfo::BlockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(true); } else if (batchInfo->type() == KisMarkerUpdateInfo::UnblockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(false); shouldExplicitlyIssueUpdates = true; } } else { infoObjects << info; } } if (!infoObjects.isEmpty()) { uploadData(infoObjects); } else if (shouldExplicitlyIssueUpdates) { tryIssueCanvasUpdates(m_d->coordinatesConverter->imageRectInImagePixels()); } } void KisCanvas2::slotBeginUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::StartBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotEndUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::EndBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotSetLodUpdatesBlocked(bool value) { KisUpdateInfoSP info = new KisMarkerUpdateInfo(value ? KisMarkerUpdateInfo::BlockLodUpdates : KisMarkerUpdateInfo::UnblockLodUpdates, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotDoCanvasUpdate() { /** * WARNING: in isBusy() we access openGL functions without making the painting * context current. We hope that currently active context will be Qt's one, * which is shared with our own. */ if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->canvasUpdateCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->canvasUpdateCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction m_d->regionOfInterestUpdateCompressor.start(); } QRect KisCanvas2::regionOfInterest() const { return m_d->regionOfInterest; } void KisCanvas2::slotUpdateRegionOfInterest() { const QRect oldRegionOfInterest = m_d->regionOfInterest; const qreal ratio = 0.25; const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect(); const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels(); m_d->regionOfInterest = imageRect.contains(proposedRoi) ? proposedRoi : imageRect; if (m_d->regionOfInterest != oldRegionOfInterest) { emit sigRegionOfInterestChanged(m_d->regionOfInterest); } } void KisCanvas2::slotReferenceImagesChanged() { canvasController()->resetScrollBars(); } void KisCanvas2::setRenderingLimit(const QRect &rc) { m_d->renderingLimit = rc; } QRect KisCanvas2::renderingLimit() const { return m_d->renderingLimit; } void KisCanvas2::slotTrySwitchShapeManager() { KisNodeSP node = m_d->view->currentNode(); QPointer newManager; newManager = fetchShapeManagerFromNode(node); m_d->setActiveShapeManager(newManager); } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInImage()) return; const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg(true); const int maxLod = cfg.numMipmapLevels(); const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); if (m_d->effectiveLodAllowedInImage()) { KisImageSP image = this->image(); image->setDesiredLevelOfDetail(lod); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); - m_d->coordinatesConverter->setDocumentOffset(documentOffset); + + qreal devicePixelRatio = m_d->coordinatesConverter->devicePixelRatio(); + // The given offset is in widget logical pixels. In order to prevent fuzzy + // canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio + // is not integral, we adjusts the offset to map to whole device pixels. + // We use qFloor here since the offset can be negative. + int deviceOffsetX = qFloor(documentOffset.x() * devicePixelRatio); + int deviceOffsetY = qFloor(documentOffset.y() * devicePixelRatio); + // These adjusted offsets will be in logical pixel but is aligned in device + // pixel space for pixel-perfect rendering. + qreal pixelPerfectOffsetX = deviceOffsetX / devicePixelRatio; + qreal pixelPerfectOffsetY = deviceOffsetY / devicePixelRatio; + // FIXME: This is a temporary hack for fixing the canvas under fractional + // DPI scaling before a new coordinate system is introduced. + QPointF offsetAdjusted(pixelPerfectOffsetX, pixelPerfectOffsetY); + + m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); m_d->regionOfInterestUpdateCompressor.start(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget()))); initializeFpsDecoration(); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::setDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); { KisImageSP image = this->image(); KisImageBarrierLocker l(image); m_d->canvasWidget->setDisplayColorConverter(&m_d->displayColorConverter); } refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->view->resourceProvider(), m_d->canvasWidget->widget()); connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) { m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom notifyZoomChanged(); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInImage); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsLoD()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInImage = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage()); } notifyLevelOfDetailChange(); KisConfig cfg(false); cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInImage; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const { KisCanvasDecorationSP deco = decoration("referenceImagesDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/canvas/kis_coordinates_converter.cpp b/libs/ui/canvas/kis_coordinates_converter.cpp index c8e3f5c34e..2dab83e47f 100644 --- a/libs/ui/canvas/kis_coordinates_converter.cpp +++ b/libs/ui/canvas/kis_coordinates_converter.cpp @@ -1,464 +1,469 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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 "kis_coordinates_converter.h" #include #include #include #include struct KisCoordinatesConverter::Private { Private(): isXAxisMirrored(false), isYAxisMirrored(false), rotationAngle(0.0), devicePixelRatio(1.0) { } KisImageWSP image; bool isXAxisMirrored; bool isYAxisMirrored; qreal rotationAngle; QSizeF canvasWidgetSize; qreal devicePixelRatio; QPointF documentOffset; QTransform flakeToWidget; QTransform imageToDocument; QTransform documentToFlake; QTransform widgetToViewport; }; /** * When vastScrolling value is less than 0.5 it is possible * that the whole scrolling area (viewport) will be smaller than * the size of the widget. In such cases the image should be * centered in the widget. Previously we used a special parameter * documentOrigin for this purpose, now the value for this * centering is calculated dynamically, helping the offset to * center the image inside the widget * * Note that the correction is null when the size of the document * plus vast scrolling reserve is larger than the widget. This * is always true for vastScrolling parameter > 0.5. */ QPointF KisCoordinatesConverter::centeringCorrection() const { KisConfig cfg(true); QSize documentSize = imageRectInWidgetPixels().toAlignedRect().size(); QPointF dPoint(documentSize.width(), documentSize.height()); QPointF wPoint(m_d->canvasWidgetSize.width(), m_d->canvasWidgetSize.height()); QPointF minOffset = -cfg.vastScrolling() * wPoint; QPointF maxOffset = dPoint - wPoint + cfg.vastScrolling() * wPoint; QPointF range = maxOffset - minOffset; range.rx() = qMin(range.x(), (qreal)0.0); range.ry() = qMin(range.y(), (qreal)0.0); range /= 2; return -range; } /** * The document offset and the position of the top left corner of the * image must always coincide, that is why we need to correct them to * and fro. * * When we change zoom level, the calculation of the new offset is * done by KoCanvasControllerWidget, that is why we just passively fix * the flakeToWidget transform to conform the offset and wait until * the canvas controller will recenter us. * * But when we do our own transformations of the canvas, like rotation * and mirroring, we cannot rely on the centering of the canvas * controller and we do it ourselves. Then we just set new offset and * return its value to be set in the canvas controller explicitly. */ void KisCoordinatesConverter::correctOffsetToTransformation() { m_d->documentOffset = -(imageRectInWidgetPixels().topLeft() - centeringCorrection()).toPoint(); } void KisCoordinatesConverter::correctTransformationToOffset() { QPointF topLeft = imageRectInWidgetPixels().topLeft(); QPointF diff = (-topLeft) - m_d->documentOffset; diff += centeringCorrection(); m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); } void KisCoordinatesConverter::recalculateTransformations() { if(!m_d->image) return; m_d->imageToDocument = QTransform::fromScale(1 / m_d->image->xRes(), 1 / m_d->image->yRes()); qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); m_d->documentToFlake = QTransform::fromScale(zoomX, zoomY); correctTransformationToOffset(); QRectF irect = imageRectInWidgetPixels(); QRectF wrect = QRectF(QPoint(0,0), m_d->canvasWidgetSize); QRectF rrect = irect & wrect; QTransform reversedTransform = flakeToWidgetTransform().inverted(); QRectF canvasBounds = reversedTransform.mapRect(rrect); QPointF offset = canvasBounds.topLeft(); m_d->widgetToViewport = reversedTransform * QTransform::fromTranslate(-offset.x(), -offset.y()); } KisCoordinatesConverter::KisCoordinatesConverter() : m_d(new Private) { } KisCoordinatesConverter::~KisCoordinatesConverter() { delete m_d; } -void KisCoordinatesConverter::setCanvasWidgetSize(QSize size) +void KisCoordinatesConverter::setCanvasWidgetSize(QSizeF size) { m_d->canvasWidgetSize = size; recalculateTransformations(); } void KisCoordinatesConverter::setDevicePixelRatio(qreal value) { m_d->devicePixelRatio = value; } void KisCoordinatesConverter::setImage(KisImageWSP image) { m_d->image = image; recalculateTransformations(); } -void KisCoordinatesConverter::setDocumentOffset(const QPoint& offset) +void KisCoordinatesConverter::setDocumentOffset(const QPointF& offset) { QPointF diff = m_d->documentOffset - offset; m_d->documentOffset = offset; m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); recalculateTransformations(); } +qreal KisCoordinatesConverter::devicePixelRatio() const +{ + return m_d->devicePixelRatio; +} + QPoint KisCoordinatesConverter::documentOffset() const { return QPoint(int(m_d->documentOffset.x()), int(m_d->documentOffset.y())); } qreal KisCoordinatesConverter::rotationAngle() const { return m_d->rotationAngle; } void KisCoordinatesConverter::setZoom(qreal zoom) { KoZoomHandler::setZoom(zoom); recalculateTransformations(); } qreal KisCoordinatesConverter::effectiveZoom() const { qreal scaleX, scaleY; this->imageScale(&scaleX, &scaleY); if (scaleX != scaleY) { qWarning() << "WARNING: Zoom is not isotropic!" << ppVar(scaleX) << ppVar(scaleY) << ppVar(qFuzzyCompare(scaleX, scaleY)); } // zoom by average of x and y return 0.5 * (scaleX + scaleY); } QPoint KisCoordinatesConverter::rotate(QPointF center, qreal angle) { QTransform rot; rot.rotate(angle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = std::fmod(m_d->rotationAngle + angle, 360.0); correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } QPoint KisCoordinatesConverter::mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis) { bool keepOrientation = false; // XXX: Keep here for now, maybe some day we can restore the parameter again. bool doXMirroring = m_d->isXAxisMirrored ^ mirrorXAxis; bool doYMirroring = m_d->isYAxisMirrored ^ mirrorYAxis; qreal scaleX = doXMirroring ? -1.0 : 1.0; qreal scaleY = doYMirroring ? -1.0 : 1.0; QTransform mirror = QTransform::fromScale(scaleX, scaleY); QTransform rot; rot.rotate(m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); if (keepOrientation) { m_d->flakeToWidget *= rot.inverted(); } m_d->flakeToWidget *= mirror; if (keepOrientation) { m_d->flakeToWidget *= rot; } m_d->flakeToWidget *= QTransform::fromTranslate(center.x(),center.y()); if (!keepOrientation && (doXMirroring ^ doYMirroring)) { m_d->rotationAngle = -m_d->rotationAngle; } m_d->isXAxisMirrored = mirrorXAxis; m_d->isYAxisMirrored = mirrorYAxis; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } bool KisCoordinatesConverter::xAxisMirrored() const { return m_d->isXAxisMirrored; } bool KisCoordinatesConverter::yAxisMirrored() const { return m_d->isYAxisMirrored; } QPoint KisCoordinatesConverter::resetRotation(QPointF center) { QTransform rot; rot.rotate(-m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(), -center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = 0.0; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } QTransform KisCoordinatesConverter::imageToWidgetTransform() const{ return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget; } QTransform KisCoordinatesConverter::imageToDocumentTransform() const { return m_d->imageToDocument; } QTransform KisCoordinatesConverter::documentToFlakeTransform() const { return m_d->documentToFlake; } QTransform KisCoordinatesConverter::flakeToWidgetTransform() const { return m_d->flakeToWidget; } QTransform KisCoordinatesConverter::documentToWidgetTransform() const { return m_d->documentToFlake * m_d->flakeToWidget; } QTransform KisCoordinatesConverter::viewportToWidgetTransform() const { return m_d->widgetToViewport.inverted(); } QTransform KisCoordinatesConverter::imageToViewportTransform() const { return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget * m_d->widgetToViewport; } void KisCoordinatesConverter::getQPainterCheckersInfo(QTransform *transform, QPointF *brushOrigin, QPolygonF *polygon, const bool scrollCheckers) const { /** * Qt has different rounding for QPainter::drawRect/drawImage. * The image is rounded mathematically, while rect in aligned * to the next integer. That causes transparent line appear on * the canvas. * * See: https://bugreports.qt.nokia.com/browse/QTBUG-22827 */ QRectF imageRect = imageRectInViewportPixels(); imageRect.adjust(0,0,-0.5,-0.5); if (scrollCheckers) { *transform = viewportToWidgetTransform(); *polygon = imageRect; *brushOrigin = imageToViewport(QPointF(0,0)); } else { *transform = QTransform(); *polygon = viewportToWidgetTransform().map(imageRect); *brushOrigin = QPoint(0,0); } } void KisCoordinatesConverter::getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, const bool scrollCheckers) const { if(scrollCheckers) { *textureTransform = QTransform(); *textureRect = QRectF(0, 0, viewportRect.width(),viewportRect.height()); } else { *textureTransform = viewportToWidgetTransform(); *textureRect = viewportRect; } *modelTransform = viewportToWidgetTransform(); *modelRect = viewportRect; } QPointF KisCoordinatesConverter::imageCenterInWidgetPixel() const { if(!m_d->image) return QPointF(); QPolygonF poly = imageToWidget(QPolygon(m_d->image->bounds())); return (poly[0] + poly[1] + poly[2] + poly[3]) / 4.0; } // these functions return a bounding rect if the canvas is rotated QRectF KisCoordinatesConverter::imageRectInWidgetPixels() const { if(!m_d->image) return QRectF(); return imageToWidget(m_d->image->bounds()); } QRectF KisCoordinatesConverter::imageRectInViewportPixels() const { if(!m_d->image) return QRectF(); return imageToViewport(m_d->image->bounds()); } QRect KisCoordinatesConverter::imageRectInImagePixels() const { if(!m_d->image) return QRect(); return m_d->image->bounds(); } QRectF KisCoordinatesConverter::imageRectInDocumentPixels() const { if(!m_d->image) return QRectF(); return imageToDocument(m_d->image->bounds()); } QSizeF KisCoordinatesConverter::imageSizeInFlakePixels() const { if(!m_d->image) return QSizeF(); qreal scaleX, scaleY; imageScale(&scaleX, &scaleY); QSize imageSize = m_d->image->size(); return QSizeF(imageSize.width() * scaleX, imageSize.height() * scaleY); } QRectF KisCoordinatesConverter::widgetRectInFlakePixels() const { return widgetToFlake(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); } QRectF KisCoordinatesConverter::widgetRectInImagePixels() const { return widgetToImage(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); } QPointF KisCoordinatesConverter::flakeCenterPoint() const { QRectF widgetRect = widgetRectInFlakePixels(); return QPointF(widgetRect.left() + widgetRect.width() / 2, widgetRect.top() + widgetRect.height() / 2); } QPointF KisCoordinatesConverter::widgetCenterPoint() const { return QPointF(m_d->canvasWidgetSize.width() / 2.0, m_d->canvasWidgetSize.height() / 2.0); } void KisCoordinatesConverter::imageScale(qreal *scaleX, qreal *scaleY) const { if(!m_d->image) { *scaleX = 1.0; *scaleY = 1.0; return; } // get the x and y zoom level of the canvas qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); // Get the KisImage resolution qreal resX = m_d->image->xRes(); qreal resY = m_d->image->yRes(); // Compute the scale factors *scaleX = zoomX / resX; *scaleY = zoomY / resY; } void KisCoordinatesConverter::imagePhysicalScale(qreal *scaleX, qreal *scaleY) const { imageScale(scaleX, scaleY); *scaleX *= m_d->devicePixelRatio; *scaleY *= m_d->devicePixelRatio; } diff --git a/libs/ui/canvas/kis_coordinates_converter.h b/libs/ui/canvas/kis_coordinates_converter.h index 9a7bd2061d..9d73c25b6d 100644 --- a/libs/ui/canvas/kis_coordinates_converter.h +++ b/libs/ui/canvas/kis_coordinates_converter.h @@ -1,166 +1,167 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_COORDINATES_CONVERTER_H #define KIS_COORDINATES_CONVERTER_H #include #include #include "kritaui_export.h" #include "kis_types.h" #define EPSILON 1e-6 #define SCALE_LESS_THAN(scX, scY, value) \ (scX < (value) - EPSILON && scY < (value) - EPSILON) #define SCALE_MORE_OR_EQUAL_TO(scX, scY, value) \ (scX > (value) - EPSILON && scY > (value) - EPSILON) namespace _Private { template struct Traits { typedef T Result; static T map(const QTransform& transform, const T& obj) { return transform.map(obj); } }; template<> struct Traits { typedef QRectF Result; static QRectF map(const QTransform& transform, const QRectF& rc) { return transform.mapRect(rc); } }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; } class KRITAUI_EXPORT KisCoordinatesConverter: public KoZoomHandler { public: KisCoordinatesConverter(); ~KisCoordinatesConverter() override; - void setCanvasWidgetSize(QSize size); + void setCanvasWidgetSize(QSizeF size); void setDevicePixelRatio(qreal value); void setImage(KisImageWSP image); - void setDocumentOffset(const QPoint &offset); + void setDocumentOffset(const QPointF &offset); + qreal devicePixelRatio() const; QPoint documentOffset() const; qreal rotationAngle() const; QPoint rotate(QPointF center, qreal angle); QPoint mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis); bool xAxisMirrored() const; bool yAxisMirrored() const; QPoint resetRotation(QPointF center); void setZoom(qreal zoom) override; /** * A composition of to scale methods: zoom level + image resolution */ qreal effectiveZoom() const; template typename _Private::Traits::Result imageToViewport(const T& obj) const { return _Private::Traits::map(imageToViewportTransform(), obj); } template typename _Private::Traits::Result viewportToImage(const T& obj) const { return _Private::Traits::map(imageToViewportTransform().inverted(), obj); } template typename _Private::Traits::Result flakeToWidget(const T& obj) const { return _Private::Traits::map(flakeToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToFlake(const T& obj) const { return _Private::Traits::map(flakeToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result widgetToViewport(const T& obj) const { return _Private::Traits::map(viewportToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result viewportToWidget(const T& obj) const { return _Private::Traits::map(viewportToWidgetTransform(), obj); } template typename _Private::Traits::Result documentToWidget(const T& obj) const { return _Private::Traits::map(documentToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToDocument(const T& obj) const { return _Private::Traits::map(documentToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result imageToDocument(const T& obj) const { return _Private::Traits::map(imageToDocumentTransform(), obj); } template typename _Private::Traits::Result documentToImage(const T& obj) const { return _Private::Traits::map(imageToDocumentTransform().inverted(), obj); } template typename _Private::Traits::Result documentToFlake(const T& obj) const { return _Private::Traits::map(documentToFlakeTransform(), obj); } template typename _Private::Traits::Result flakeToDocument(const T& obj) const { return _Private::Traits::map(documentToFlakeTransform().inverted(), obj); } template typename _Private::Traits::Result imageToWidget(const T& obj) const { return _Private::Traits::map(imageToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToImage(const T& obj) const { return _Private::Traits::map(imageToWidgetTransform().inverted(), obj); } QTransform imageToWidgetTransform() const; QTransform imageToDocumentTransform() const; QTransform documentToFlakeTransform() const; QTransform imageToViewportTransform() const; QTransform viewportToWidgetTransform() const; QTransform flakeToWidgetTransform() const; QTransform documentToWidgetTransform() const; void getQPainterCheckersInfo(QTransform *transform, QPointF *brushOrigin, QPolygonF *poligon, const bool scrollCheckers) const; void getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, const bool scrollCheckers) const; QPointF imageCenterInWidgetPixel() const; QRectF imageRectInWidgetPixels() const; QRectF imageRectInViewportPixels() const; QSizeF imageSizeInFlakePixels() const; QRectF widgetRectInFlakePixels() const; QRectF widgetRectInImagePixels() const; QRect imageRectInImagePixels() const; QRectF imageRectInDocumentPixels() const; QPointF flakeCenterPoint() const; QPointF widgetCenterPoint() const; void imageScale(qreal *scaleX, qreal *scaleY) const; void imagePhysicalScale(qreal *scaleX, qreal *scaleY) const; private: friend class KisZoomAndPanTest; QPointF centeringCorrection() const; void correctOffsetToTransformation(); void correctTransformationToOffset(); void recalculateTransformations(); private: struct Private; Private * const m_d; }; #endif /* KIS_COORDINATES_CONVERTER_H */ diff --git a/libs/ui/dialogs/KisDlgCustomTabletResolution.cpp b/libs/ui/dialogs/KisDlgCustomTabletResolution.cpp new file mode 100644 index 0000000000..fa561dbbff --- /dev/null +++ b/libs/ui/dialogs/KisDlgCustomTabletResolution.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2019 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 "KisDlgCustomTabletResolution.h" +#include "ui_KisDlgCustomTabletResolution.h" + +#include +#include "kis_debug.h" + +#include +#include +#include +#include + +namespace { +QString rectToString(const QRect &rc) { + return QString("%1, %2 %3 x %4") + .arg(rc.x()) + .arg(rc.y()) + .arg(rc.width()) + .arg(rc.height()); +} +} + +KisDlgCustomTabletResolution::KisDlgCustomTabletResolution(QWidget *parent) : + QDialog(parent), + ui(new Ui::KisDlgCustomTabletResolution) +{ + ui->setupUi(this); + connect(ui->btnBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(ui->btnBox, SIGNAL(accepted()), this, SLOT(accept())); + + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->lblXOffset, SLOT(setEnabled(bool))); + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->lblYOffset, SLOT(setEnabled(bool))); + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->lblWidth, SLOT(setEnabled(bool))); + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->lblHeight, SLOT(setEnabled(bool))); + + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->intXOffset, SLOT(setEnabled(bool))); + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->intYOffset, SLOT(setEnabled(bool))); + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->intWidth, SLOT(setEnabled(bool))); + connect(ui->radioCustomMapping, SIGNAL(toggled(bool)), ui->intHeight, SLOT(setEnabled(bool))); + + const QRect virtualScreenRect = calcNativeScreenRect(); + + ui->radioMapToEntireScreen->setText(i18nc("@option:radio", "Map to entire virtual screen (%1)", rectToString(virtualScreenRect))); + + QRect nativeScreenRect; + QPlatformScreen *screen = qGuiApp->primaryScreen()->handle(); + Q_FOREACH (QPlatformScreen *scr, screen->virtualSiblings()) { + nativeScreenRect |= scr->geometry(); + } + + QRect customScreenRect = virtualScreenRect; + Mode mode = getTabletMode(&customScreenRect); + + if (mode == USE_CUSTOM) { + ui->radioCustomMapping->setChecked(true); + } else if (mode == USE_VIRTUAL_SCREEN) { + ui->radioMapToEntireScreen->setChecked(true); + } else { + ui->radioMapAsWintab->setChecked(true); + } + + ui->intXOffset->setValue(customScreenRect.x()); + ui->intYOffset->setValue(customScreenRect.y()); + ui->intWidth->setValue(customScreenRect.width()); + ui->intHeight->setValue(customScreenRect.height()); +} + +void KisDlgCustomTabletResolution::accept() +{ + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings cfg(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + + if (ui->radioMapAsWintab->isChecked()) { + cfg.setValue("wintabResolutionMode", "wintab"); + } else if (ui->radioMapToEntireScreen->isChecked()) { + cfg.setValue("wintabResolutionMode", "virtual-screen"); + } else if (ui->radioCustomMapping->isChecked()) { + cfg.setValue("wintabResolutionMode", "custom"); + + cfg.setValue("wintabCustomResolutionX", ui->intXOffset->value()); + cfg.setValue("wintabCustomResolutionY", ui->intYOffset->value()); + cfg.setValue("wintabCustomResolutionWidth", ui->intWidth->value()); + cfg.setValue("wintabCustomResolutionHeight", ui->intHeight->value()); + } + + // apply the mode right now + { + QRect customTabletRect; + const Mode tabletMode = getTabletMode(&customTabletRect); + applyConfiguration(tabletMode, customTabletRect); + } + + QDialog::accept(); +} + +KisDlgCustomTabletResolution::~KisDlgCustomTabletResolution() +{ + delete ui; +} + +QRect KisDlgCustomTabletResolution::calcNativeScreenRect() +{ + QRect nativeScreenRect; + QPlatformScreen *screen = qGuiApp->primaryScreen()->handle(); + Q_FOREACH (QPlatformScreen *scr, screen->virtualSiblings()) { + nativeScreenRect |= scr->geometry(); + } + return nativeScreenRect; +} + +KisDlgCustomTabletResolution::Mode KisDlgCustomTabletResolution::getTabletMode(QRect *customRect) +{ + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings cfg(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + + const QString mode = cfg.value("wintabResolutionMode", QString("wintab")).toString(); + Mode modeValue = USE_WINTAB; + + if (mode == "custom") { + modeValue = USE_CUSTOM; + } else if (mode == "virtual-screen") { + modeValue = USE_VIRTUAL_SCREEN; + } else { + modeValue = USE_WINTAB; + } + + if (mode == "custom") { + customRect->setX(cfg.value("wintabCustomResolutionX", customRect->x()).toInt()); + customRect->setY(cfg.value("wintabCustomResolutionY", customRect->y()).toInt()); + customRect->setWidth(cfg.value("wintabCustomResolutionWidth", customRect->width()).toInt()); + customRect->setHeight(cfg.value("wintabCustomResolutionHeight", customRect->height()).toInt()); + } + + return modeValue; +} + +void KisDlgCustomTabletResolution::applyConfiguration(KisDlgCustomTabletResolution::Mode tabletMode, const QRect &customTabletRect) +{ + if (tabletMode == KisDlgCustomTabletResolution::USE_CUSTOM) { + qputenv("QT_WINTAB_DESKTOP_RECT", + QString("%1;%2;%3;%4") + .arg(customTabletRect.x()) + .arg(customTabletRect.y()) + .arg(customTabletRect.width()) + .arg(customTabletRect.height()).toLatin1()); + qunsetenv("QT_IGNORE_WINTAB_MAPPING"); + } else if (tabletMode == KisDlgCustomTabletResolution::USE_VIRTUAL_SCREEN) { + qputenv("QT_IGNORE_WINTAB_MAPPING", "1"); + qunsetenv("QT_WINTAB_DESKTOP_RECT"); + } else { + qunsetenv("QT_WINTAB_DESKTOP_RECT"); + qunsetenv("QT_IGNORE_WINTAB_MAPPING"); + } +} diff --git a/libs/ui/dialogs/KisDlgCustomTabletResolution.h b/libs/ui/dialogs/KisDlgCustomTabletResolution.h new file mode 100644 index 0000000000..164a8bd610 --- /dev/null +++ b/libs/ui/dialogs/KisDlgCustomTabletResolution.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KISDLGCUSTOMTABLETRESOLUTION_H +#define KISDLGCUSTOMTABLETRESOLUTION_H + +#include +#include "kritaui_export.h" + +namespace Ui { +class KisDlgCustomTabletResolution; +} + +class KRITAUI_EXPORT KisDlgCustomTabletResolution : public QDialog +{ + Q_OBJECT + +public: + enum Mode { + USE_WINTAB = 0, + USE_VIRTUAL_SCREEN, + USE_CUSTOM + }; + + void accept(); + +public: + explicit KisDlgCustomTabletResolution(QWidget *parent = 0); + ~KisDlgCustomTabletResolution(); + + static QRect calcNativeScreenRect(); + static Mode getTabletMode(QRect *customRect); + + static void applyConfiguration(Mode mode, const QRect &customRect); + +private: + Ui::KisDlgCustomTabletResolution *ui; +}; + +#endif // KISDLGCUSTOMTABLETRESOLUTION_H diff --git a/libs/ui/dialogs/KisDlgCustomTabletResolution.ui b/libs/ui/dialogs/KisDlgCustomTabletResolution.ui new file mode 100644 index 0000000000..158cb3faef --- /dev/null +++ b/libs/ui/dialogs/KisDlgCustomTabletResolution.ui @@ -0,0 +1,198 @@ + + + KisDlgCustomTabletResolution + + + + 0 + 0 + 321 + 255 + + + + Advanced Tablet Setting + + + + + + Select area your tablet is mapped to: + + + + + + Use information provided by tablet + + + + + + + Map to entire virtual screen NO_I18N + + + + + + + + + Map to custom area + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + Width: + + + + + + + px + + + 10000 + + + + + + + X offset: + + + + + + + px + + + -10000 + + + 10000 + + + + + + + Height: + + + + + + + px + + + 10000 + + + + + + + Y offset: + + + + + + + px + + + -10000 + + + 10000 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 15 + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/libs/ui/dialogs/kis_dlg_layer_style.cpp b/libs/ui/dialogs/kis_dlg_layer_style.cpp index ff4a617d37..f632b94176 100644 --- a/libs/ui/dialogs/kis_dlg_layer_style.cpp +++ b/libs/ui/dialogs/kis_dlg_layer_style.cpp @@ -1,1355 +1,1385 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_layer_style.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cmb_contour.h" #include "kis_cmb_gradient.h" #include "KisResourceServerProvider.h" #include "kis_psd_layer_style_resource.h" #include "kis_psd_layer_style.h" #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_canvas_resource_provider.h" #include KoAbstractGradient* fetchGradientLazy(KoAbstractGradient *gradient, KisCanvasResourceProvider *resourceProvider) { if (!gradient) { gradient = resourceProvider->currentGradient(); } return gradient; } KisDlgLayerStyle::KisDlgLayerStyle(KisPSDLayerStyleSP layerStyle, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : KoDialog(parent) , m_layerStyle(layerStyle) , m_initialLayerStyle(layerStyle->clone()) , m_isSwitchingPredefinedStyle(false) , m_sanityLayerStyleDirty(false) { setCaption(i18n("Layer Styles")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_configChangedCompressor = new KisSignalCompressor(1000, KisSignalCompressor::POSTPONE, this); connect(m_configChangedCompressor, SIGNAL(timeout()), SIGNAL(configChanged())); QWidget *page = new QWidget(this); wdgLayerStyles.setupUi(page); setMainWidget(page); wdgLayerStyles.chkPreview->setVisible(false); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(notifyGuiConfigChanged())); m_stylesSelector = new StylesSelector(this); connect(m_stylesSelector, SIGNAL(styleSelected(KisPSDLayerStyleSP)), SLOT(notifyPredefinedStyleSelected(KisPSDLayerStyleSP))); wdgLayerStyles.stylesStack->addWidget(m_stylesSelector); m_blendingOptions = new BlendingOptions(this); wdgLayerStyles.stylesStack->addWidget(m_blendingOptions); m_dropShadow = new DropShadow(DropShadow::DropShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_dropShadow); connect(m_dropShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerShadow = new DropShadow(DropShadow::InnerShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_innerShadow); connect(m_innerShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_outerGlow = new InnerGlow(InnerGlow::OuterGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_outerGlow); connect(m_outerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerGlow = new InnerGlow(InnerGlow::InnerGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_innerGlow); connect(m_innerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); + // Contour and Texture are sub-styles of Bevel and Emboss + // They are only applied to canvas when Bevel and Emboss is active. m_contour = new Contour(this); m_texture = new Texture(this); m_bevelAndEmboss = new BevelAndEmboss(m_contour, m_texture, this); wdgLayerStyles.stylesStack->addWidget(m_bevelAndEmboss); wdgLayerStyles.stylesStack->addWidget(m_contour); wdgLayerStyles.stylesStack->addWidget(m_texture); + // slotBevelAndEmbossChanged(QListWidgetItem*) enables/disables Contour and Texture on "Bevel and Emboss" toggle. + connect(wdgLayerStyles.lstStyleSelector, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(slotBevelAndEmbossChanged(QListWidgetItem*))); connect(m_bevelAndEmboss, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_satin = new Satin(this); wdgLayerStyles.stylesStack->addWidget(m_satin); connect(m_satin, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_colorOverlay = new ColorOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_colorOverlay); connect(m_colorOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_gradientOverlay = new GradientOverlay(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_gradientOverlay); connect(m_gradientOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_patternOverlay = new PatternOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_patternOverlay); connect(m_patternOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_stroke = new Stroke(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_stroke); connect(m_stroke, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); KisConfig cfg(true); wdgLayerStyles.stylesStack->setCurrentIndex(cfg.readEntry("KisDlgLayerStyle::current", 1)); wdgLayerStyles.lstStyleSelector->setCurrentRow(cfg.readEntry("KisDlgLayerStyle::current", 1)); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); // improve the checkbox visibility by altering the style sheet list a bit // the dark themes make them hard to see QPalette newPalette = palette(); newPalette.setColor(QPalette::Active, QPalette::Background, palette().text().color() ); wdgLayerStyles.lstStyleSelector->setPalette(newPalette); notifyPredefinedStyleSelected(layerStyle); connect(m_dropShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_innerShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_bevelAndEmboss, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(wdgLayerStyles.btnNewStyle, SIGNAL(clicked()), SLOT(slotNewStyle())); connect(wdgLayerStyles.btnLoadStyle, SIGNAL(clicked()), SLOT(slotLoadStyle())); connect(wdgLayerStyles.btnSaveStyle, SIGNAL(clicked()), SLOT(slotSaveStyle())); connect(wdgLayerStyles.chkMasterFxSwitch, SIGNAL(toggled(bool)), SLOT(slotMasterFxSwitchChanged(bool))); connect(this, SIGNAL(accepted()), SLOT(slotNotifyOnAccept())); connect(this, SIGNAL(rejected()), SLOT(slotNotifyOnReject())); } KisDlgLayerStyle::~KisDlgLayerStyle() { } void KisDlgLayerStyle::slotMasterFxSwitchChanged(bool value) { wdgLayerStyles.lstStyleSelector->setEnabled(value); wdgLayerStyles.stylesStack->setEnabled(value); wdgLayerStyles.btnNewStyle->setEnabled(value); wdgLayerStyles.btnLoadStyle->setEnabled(value); wdgLayerStyles.btnSaveStyle->setEnabled(value); notifyGuiConfigChanged(); } void KisDlgLayerStyle::notifyGuiConfigChanged() { if (m_isSwitchingPredefinedStyle) return; m_configChangedCompressor->start(); m_layerStyle->setUuid(QUuid::createUuid()); m_sanityLayerStyleDirty = true; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } void KisDlgLayerStyle::notifyPredefinedStyleSelected(KisPSDLayerStyleSP style) { m_isSwitchingPredefinedStyle = true; setStyle(style); m_isSwitchingPredefinedStyle = false; m_configChangedCompressor->start(); } +void KisDlgLayerStyle::slotBevelAndEmbossChanged(QListWidgetItem*) { + QListWidgetItem *item; + + if (wdgLayerStyles.lstStyleSelector->item(6)->checkState() == Qt::Checked) { + // Enable "Contour" (list item 7) + item = wdgLayerStyles.lstStyleSelector->item(7); + Qt::ItemFlags currentFlags7 = item->flags(); + item->setFlags(currentFlags7 | Qt::ItemIsEnabled); + + // Enable "Texture" (list item 8) + item = wdgLayerStyles.lstStyleSelector->item(8); + Qt::ItemFlags currentFlags8 = item->flags(); + item->setFlags(currentFlags8 | Qt::ItemIsEnabled); + } + else { + // Disable "Contour" + item = wdgLayerStyles.lstStyleSelector->item(7); + Qt::ItemFlags currentFlags7 = item->flags(); + item->setFlags(currentFlags7 & (~Qt::ItemIsEnabled)); + + // Disable "Texture" + item = wdgLayerStyles.lstStyleSelector->item(8); + Qt::ItemFlags currentFlags8 = item->flags(); + item->setFlags(currentFlags8 & (~Qt::ItemIsEnabled)); + } +} void KisDlgLayerStyle::slotNotifyOnAccept() { if (m_configChangedCompressor->isActive()) { m_configChangedCompressor->stop(); emit configChanged(); } } void KisDlgLayerStyle::slotNotifyOnReject() { notifyPredefinedStyleSelected(m_initialLayerStyle); m_configChangedCompressor->stop(); emit configChanged(); } bool checkCustomNameAvailable(const QString &name) { const QString customName = "CustomStyles.asl"; KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); KoResource *resource = server->resourceByName(customName); if (!resource) return true; KisPSDLayerStyleCollectionResource *collection = dynamic_cast(resource); Q_FOREACH (KisPSDLayerStyleSP style, collection->layerStyles()) { if (style->name() == name) { return false; } } return true; } QString selectAvailableStyleName(const QString &name) { QString finalName = name; if (checkCustomNameAvailable(finalName)) { return finalName; } int i = 0; do { finalName = QString("%1%2").arg(name).arg(i++); } while (!checkCustomNameAvailable(finalName)); return finalName; } void KisDlgLayerStyle::slotNewStyle() { QString styleName = QInputDialog::getText(this, i18nc("@title:window", "Enter new style name"), i18nc("@label:textbox", "Name:"), QLineEdit::Normal, i18nc("Default name for a new style", "New Style")); KisPSDLayerStyleSP style = this->style(); style->setName(selectAvailableStyleName(styleName)); m_stylesSelector->addNewStyle(style->clone()); } void KisDlgLayerStyle::slotLoadStyle() { QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::OpenFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); m_stylesSelector->loadCollection(filename); wdgLayerStyles.lstStyleSelector->setCurrentRow(0); } void KisDlgLayerStyle::slotSaveStyle() { QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::SaveFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); QScopedPointer collection( new KisPSDLayerStyleCollectionResource(filename)); KisPSDLayerStyleSP newStyle = style()->clone(); newStyle->setName(QFileInfo(filename).baseName()); KisPSDLayerStyleCollectionResource::StylesVector vector = collection->layerStyles(); vector << newStyle; collection->setLayerStyles(vector); collection->save(); } void KisDlgLayerStyle::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) { current = previous; } wdgLayerStyles.stylesStack->setCurrentIndex(wdgLayerStyles.lstStyleSelector->row(current)); } void KisDlgLayerStyle::setStyle(KisPSDLayerStyleSP style) { // we may self-assign style is some cases if (style != m_layerStyle) { *m_layerStyle = *style; } m_sanityLayerStyleDirty = false; { KisSignalsBlocker b(m_stylesSelector); m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } QListWidgetItem *item; item = wdgLayerStyles.lstStyleSelector->item(2); item->setCheckState(m_layerStyle->dropShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(3); item->setCheckState(m_layerStyle->innerShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(4); item->setCheckState(m_layerStyle->outerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(5); item->setCheckState(m_layerStyle->innerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(6); item->setCheckState(m_layerStyle->bevelAndEmboss()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(7); item->setCheckState(m_layerStyle->bevelAndEmboss()->contourEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(8); item->setCheckState(m_layerStyle->bevelAndEmboss()->textureEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(9); item->setCheckState(m_layerStyle->satin()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(10); item->setCheckState(m_layerStyle->colorOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(11); item->setCheckState(m_layerStyle->gradientOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(12); item->setCheckState(m_layerStyle->patternOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(13); item->setCheckState(m_layerStyle->stroke()->effectEnabled() ? Qt::Checked : Qt::Unchecked); m_dropShadow->setShadow(m_layerStyle->dropShadow()); m_innerShadow->setShadow(m_layerStyle->innerShadow()); m_outerGlow->setConfig(m_layerStyle->outerGlow()); m_innerGlow->setConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->setBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->setSatin(m_layerStyle->satin()); m_colorOverlay->setColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->setGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->setPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->setStroke(m_layerStyle->stroke()); wdgLayerStyles.chkMasterFxSwitch->setChecked(m_layerStyle->isEnabled()); slotMasterFxSwitchChanged(m_layerStyle->isEnabled()); } KisPSDLayerStyleSP KisDlgLayerStyle::style() const { m_layerStyle->setEnabled(wdgLayerStyles.chkMasterFxSwitch->isChecked()); m_layerStyle->dropShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(2)->checkState() == Qt::Checked); m_layerStyle->innerShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(3)->checkState() == Qt::Checked); m_layerStyle->outerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(4)->checkState() == Qt::Checked); m_layerStyle->innerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(5)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(6)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setContourEnabled(wdgLayerStyles.lstStyleSelector->item(7)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setTextureEnabled(wdgLayerStyles.lstStyleSelector->item(8)->checkState() == Qt::Checked); m_layerStyle->satin()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(9)->checkState() == Qt::Checked); m_layerStyle->colorOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(10)->checkState() == Qt::Checked); m_layerStyle->gradientOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(11)->checkState() == Qt::Checked); m_layerStyle->patternOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(12)->checkState() == Qt::Checked); m_layerStyle->stroke()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(13)->checkState() == Qt::Checked); m_dropShadow->fetchShadow(m_layerStyle->dropShadow()); m_innerShadow->fetchShadow(m_layerStyle->innerShadow()); m_outerGlow->fetchConfig(m_layerStyle->outerGlow()); m_innerGlow->fetchConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->fetchBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->fetchSatin(m_layerStyle->satin()); m_colorOverlay->fetchColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->fetchGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->fetchPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->fetchStroke(m_layerStyle->stroke()); m_sanityLayerStyleDirty = false; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); return m_layerStyle; } void KisDlgLayerStyle::syncGlobalAngle(int angle) { KisPSDLayerStyleSP style = this->style(); if (style->dropShadow()->useGlobalLight()) { style->dropShadow()->setAngle(angle); } if (style->innerShadow()->useGlobalLight()) { style->innerShadow()->setAngle(angle); } if (style->bevelAndEmboss()->useGlobalLight()) { style->bevelAndEmboss()->setAngle(angle); } setStyle(style); } /********************************************************************/ /***** Styles Selector **********************************************/ /********************************************************************/ class StyleItem : public QListWidgetItem { public: StyleItem(KisPSDLayerStyleSP style) : QListWidgetItem(style->name()) , m_style(style) { } public: KisPSDLayerStyleSP m_style; }; StylesSelector::StylesSelector(QWidget *parent) : QWidget(parent) { ui.setupUi(this); connect(ui.cmbStyleCollections, SIGNAL(activated(QString)), this, SLOT(loadStyles(QString))); connect(ui.listStyles, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(selectStyle(QListWidgetItem*,QListWidgetItem*))); refillCollections(); if (ui.cmbStyleCollections->count()) { ui.cmbStyleCollections->setCurrentIndex(0); loadStyles(ui.cmbStyleCollections->currentText()); } } void StylesSelector::refillCollections() { QString previousCollection = ui.cmbStyleCollections->currentText(); ui.cmbStyleCollections->clear(); Q_FOREACH (KoResource *res, KisResourceServerProvider::instance()->layerStyleCollectionServer()->resources()) { ui.cmbStyleCollections->addItem(res->name()); } if (!previousCollection.isEmpty()) { KisSignalsBlocker blocker(this); int index = ui.cmbStyleCollections->findText(previousCollection); ui.cmbStyleCollections->setCurrentIndex(index); } } void StylesSelector::notifyExternalStyleChanged(const QString &name, const QUuid &uuid) { int currentIndex = -1; for (int i = 0; i < ui.listStyles->count(); i++ ) { StyleItem *item = dynamic_cast(ui.listStyles->item(i)); QString itemName = item->m_style->name(); if (itemName == name) { bool isDirty = item->m_style->uuid() != uuid; if (isDirty) { itemName += "*"; } currentIndex = i; } item->setText(itemName); } ui.listStyles->setCurrentRow(currentIndex); } void StylesSelector::loadStyles(const QString &name) { ui.listStyles->clear(); KoResource *res = KisResourceServerProvider::instance()->layerStyleCollectionServer()->resourceByName(name); KisPSDLayerStyleCollectionResource *collection = dynamic_cast(res); if (collection) { Q_FOREACH (KisPSDLayerStyleSP style, collection->layerStyles()) { // XXX: also use the preview image, when we have one ui.listStyles->addItem(new StyleItem(style)); } } } void StylesSelector::selectStyle(QListWidgetItem *current, QListWidgetItem* /*previous*/) { StyleItem *item = dynamic_cast(current); if (item) { emit styleSelected(item->m_style); } } void StylesSelector::loadCollection(const QString &fileName) { if (!QFileInfo(fileName).exists()) { warnKrita << "Loaded style collection doesn't exist!"; return; } KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource(fileName); collection->load(); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); collection->setFilename(server->saveLocation() + QDir::separator() + collection->name()); server->addResource(collection); refillCollections(); int index = ui.cmbStyleCollections->findText(collection->name()); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(collection->name()); } void StylesSelector::addNewStyle(KisPSDLayerStyleSP style) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); // NOTE: not translatable, since it is a key! const QString customName = "CustomStyles.asl"; const QString saveLocation = server->saveLocation(); const QString fullFilename = saveLocation + customName; KoResource *resource = server->resourceByName(customName); KisPSDLayerStyleCollectionResource *collection = 0; if (!resource) { collection = new KisPSDLayerStyleCollectionResource(""); collection->setName(customName); collection->setFilename(fullFilename); KisPSDLayerStyleCollectionResource::StylesVector vector; vector << style; collection->setLayerStyles(vector); server->addResource(collection); } else { collection = dynamic_cast(resource); KisPSDLayerStyleCollectionResource::StylesVector vector; vector = collection->layerStyles(); vector << style; collection->setLayerStyles(vector); collection->save(); } refillCollections(); // select in gui int index = ui.cmbStyleCollections->findText(customName); KIS_ASSERT_RECOVER_RETURN(index >= 0); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(customName); notifyExternalStyleChanged(style->name(), style->uuid()); } /********************************************************************/ /***** Bevel and Emboss *********************************************/ /********************************************************************/ BevelAndEmboss::BevelAndEmboss(Contour *contour, Texture *texture, QWidget *parent) : QWidget(parent) , m_contour(contour) , m_texture(texture) { ui.setupUi(this); // Structure ui.intDepth->setRange(0, 100); ui.intDepth->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intSoften->setRange(0, 18); ui.intSoften->setSuffix(i18n(" px")); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbDirection, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSoften, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Shading ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intOpacity2->setRange(0, 100); ui.intOpacity2->setSuffix(i18n(" %")); ui.angleSelector->enableGlobalLight(true); connect(ui.angleSelector, SIGNAL(globalAngleChanged(int)), SIGNAL(globalAngleChanged(int))); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.intAltitude, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbHighlightMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnHighlightColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbShadowMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnShadowColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Contour m_contour->ui.intRange->setRange(1, 100); m_contour->ui.intRange->setSuffix(i18n(" %")); connect(m_contour->ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(m_contour->ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_contour->ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Texture m_texture->ui.intScale->setRange(0, 100); m_texture->ui.intScale->setSuffix(i18n(" %")); m_texture->ui.intDepth->setRange(-1000, 1000); m_texture->ui.intDepth->setSuffix(i18n(" %")); connect(m_texture->ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(m_texture->ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_texture->ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void BevelAndEmboss::setBevelAndEmboss(const psd_layer_effects_bevel_emboss *bevelAndEmboss) { ui.cmbStyle->setCurrentIndex((int)bevelAndEmboss->style()); ui.cmbTechnique->setCurrentIndex((int)bevelAndEmboss->technique()); ui.intDepth->setValue(bevelAndEmboss->depth()); ui.cmbDirection->setCurrentIndex((int)bevelAndEmboss->direction()); ui.intSize->setValue(bevelAndEmboss->size()); ui.intSoften->setValue(bevelAndEmboss->soften()); ui.angleSelector->setValue(bevelAndEmboss->angle()); ui.angleSelector->setUseGlobalLight(bevelAndEmboss->useGlobalLight()); ui.intAltitude->setValue(bevelAndEmboss->altitude()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(bevelAndEmboss->glossAntiAliased()); ui.cmbHighlightMode->selectCompositeOp(KoID(bevelAndEmboss->highlightBlendMode())); KoColor highlightshadow(KoColorSpaceRegistry::instance()->rgb8()); highlightshadow.fromQColor(bevelAndEmboss->highlightColor()); ui.bnHighlightColor->setColor(highlightshadow); ui.intOpacity->setValue(bevelAndEmboss->highlightOpacity()); ui.cmbShadowMode->selectCompositeOp(KoID(bevelAndEmboss->shadowBlendMode())); highlightshadow.fromQColor(bevelAndEmboss->shadowColor()); ui.bnShadowColor->setColor(highlightshadow); ui.intOpacity2->setValue(bevelAndEmboss->shadowOpacity()); // FIXME: curve editing // m_contour->ui.cmbContour; m_contour->ui.chkAntiAliased->setChecked(bevelAndEmboss->antiAliased()); m_contour->ui.intRange->setValue(bevelAndEmboss->contourRange()); m_texture->ui.patternChooser->setCurrentPattern(bevelAndEmboss->texturePattern()); m_texture->ui.intScale->setValue(bevelAndEmboss->textureScale()); m_texture->ui.intDepth->setValue(bevelAndEmboss->textureDepth()); m_texture->ui.chkInvert->setChecked(bevelAndEmboss->textureInvert()); m_texture->ui.chkLinkWithLayer->setChecked(bevelAndEmboss->textureAlignWithLayer()); } void BevelAndEmboss::fetchBevelAndEmboss(psd_layer_effects_bevel_emboss *bevelAndEmboss) const { bevelAndEmboss->setStyle((psd_bevel_style)ui.cmbStyle->currentIndex()); bevelAndEmboss->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); bevelAndEmboss->setDepth(ui.intDepth->value()); bevelAndEmboss->setDirection((psd_direction)ui.cmbDirection->currentIndex()); bevelAndEmboss->setSize(ui.intSize->value()); bevelAndEmboss->setSoften(ui.intSoften->value()); bevelAndEmboss->setAngle(ui.angleSelector->value()); bevelAndEmboss->setUseGlobalLight(ui.angleSelector->useGlobalLight()); bevelAndEmboss->setAltitude(ui.intAltitude->value()); bevelAndEmboss->setGlossAntiAliased(ui.chkAntiAliased->isChecked()); bevelAndEmboss->setHighlightBlendMode(ui.cmbHighlightMode->selectedCompositeOp().id()); bevelAndEmboss->setHighlightColor(ui.bnHighlightColor->color().toQColor()); bevelAndEmboss->setHighlightOpacity(ui.intOpacity->value()); bevelAndEmboss->setShadowBlendMode(ui.cmbShadowMode->selectedCompositeOp().id()); bevelAndEmboss->setShadowColor(ui.bnShadowColor->color().toQColor()); bevelAndEmboss->setShadowOpacity(ui.intOpacity2->value()); // FIXME: curve editing bevelAndEmboss->setAntiAliased(m_contour->ui.chkAntiAliased->isChecked()); bevelAndEmboss->setContourRange(m_contour->ui.intRange->value()); bevelAndEmboss->setTexturePattern(static_cast(m_texture->ui.patternChooser->currentResource())); bevelAndEmboss->setTextureScale(m_texture->ui.intScale->value()); bevelAndEmboss->setTextureDepth(m_texture->ui.intDepth->value()); bevelAndEmboss->setTextureInvert(m_texture->ui.chkInvert->isChecked()); bevelAndEmboss->setTextureAlignWithLayer(m_texture->ui.chkLinkWithLayer->isChecked()); } /********************************************************************/ /***** Texture *********************************************/ /********************************************************************/ Texture::Texture(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Contour *********************************************/ /********************************************************************/ Contour::Contour(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Blending Options *********************************************/ /********************************************************************/ BlendingOptions::BlendingOptions(QWidget *parent) : QWidget(parent) { ui.setupUi(this); // FIXME: Blend options are not implemented yet ui.grpBlendingOptions->setTitle(QString("%1 (%2)").arg(ui.grpBlendingOptions->title()).arg(i18n("Not Implemented Yet"))); ui.grpBlendingOptions->setEnabled(false); } /********************************************************************/ /***** Color Overlay *********************************************/ /********************************************************************/ ColorOverlay::ColorOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); } void ColorOverlay::setColorOverlay(const psd_layer_effects_color_overlay *colorOverlay) { ui.cmbCompositeOp->selectCompositeOp(KoID(colorOverlay->blendMode())); ui.intOpacity->setValue(colorOverlay->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(colorOverlay->color()); ui.bnColor->setColor(color); } void ColorOverlay::fetchColorOverlay(psd_layer_effects_color_overlay *colorOverlay) const { colorOverlay->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); colorOverlay->setOpacity(ui.intOpacity->value()); colorOverlay->setColor(ui.bnColor->color().toQColor()); } /********************************************************************/ /***** Drop Shadow **************************************************/ /********************************************************************/ DropShadow::DropShadow(Mode mode, QWidget *parent) : QWidget(parent), m_mode(mode) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 500); ui.intDistance->setSuffix(i18n(" px")); ui.intDistance->setExponentRatio(3.0); ui.intSpread->setRange(0, 100); ui.intSpread->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); ui.angleSelector->enableGlobalLight(true); connect(ui.angleSelector, SIGNAL(globalAngleChanged(int)), SIGNAL(globalAngleChanged(int))); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); // connect everything to configChanged() signal connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSpread, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.chkLayerKnocksOutDropShadow, SIGNAL(toggled(bool)), SIGNAL(configChanged())); if (m_mode == InnerShadowMode) { ui.chkLayerKnocksOutDropShadow->setVisible(false); ui.grpMain->setTitle(i18n("Inner Shadow")); ui.lblSpread->setText(i18n("Choke:")); } } void DropShadow::setShadow(const psd_layer_effects_shadow_common *shadow) { ui.cmbCompositeOp->selectCompositeOp(KoID(shadow->blendMode())); ui.intOpacity->setValue(shadow->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(shadow->color()); ui.bnColor->setColor(color); ui.angleSelector->setValue(shadow->angle()); ui.angleSelector->setUseGlobalLight(shadow->useGlobalLight()); ui.intDistance->setValue(shadow->distance()); ui.intSpread->setValue(shadow->spread()); ui.intSize->setValue(shadow->size()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(shadow->antiAliased()); ui.intNoise->setValue(shadow->noise()); if (m_mode == DropShadowMode) { const psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); ui.chkLayerKnocksOutDropShadow->setChecked(shadow->knocksOut()); } } void DropShadow::fetchShadow(psd_layer_effects_shadow_common *shadow) const { shadow->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); shadow->setOpacity(ui.intOpacity->value()); shadow->setColor(ui.bnColor->color().toQColor()); shadow->setAngle(ui.angleSelector->value()); shadow->setUseGlobalLight(ui.angleSelector->useGlobalLight()); shadow->setDistance(ui.intDistance->value()); shadow->setSpread(ui.intSpread->value()); shadow->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; shadow->setAntiAliased(ui.chkAntiAliased->isChecked()); shadow->setNoise(ui.intNoise->value()); if (m_mode == DropShadowMode) { psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); realDropShadow->setKnocksOut(ui.chkLayerKnocksOutDropShadow->isChecked()); } } class GradientPointerConverter { public: static KoAbstractGradientSP resourceToStyle(KoAbstractGradient *gradient) { return gradient ? KoAbstractGradientSP(gradient->clone()) : KoAbstractGradientSP(); } static KoAbstractGradient* styleToResource(KoAbstractGradientSP gradient) { if (!gradient) return 0; KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); KoAbstractGradient *resource = server->resourceByMD5(gradient->md5()); if (!resource) { KoAbstractGradient *clone = gradient->clone(); clone->setName(findAvailableName(gradient->name())); server->addResource(clone, false); resource = clone; } return resource; } private: static QString findAvailableName(const QString &name) { KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); QString newName = name; int i = 0; while (server->resourceByName(newName)) { newName = QString("%1%2").arg(name).arg(i++); } return newName; } }; /********************************************************************/ /***** Gradient Overlay *********************************************/ /********************************************************************/ GradientOverlay::GradientOverlay(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void GradientOverlay::setGradientOverlay(const psd_layer_effects_gradient_overlay *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); KoAbstractGradient *gradient = fetchGradientLazy( GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(config->reverse()); ui.cmbStyle->setCurrentIndex((int)config->style()); ui.chkAlignWithLayer->setCheckable(config->alignWithLayer()); ui.angleSelector->setValue(config->angle()); ui.intScale->setValue(config->scale()); } void GradientOverlay::fetchGradientOverlay(psd_layer_effects_gradient_overlay *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setReverse(ui.chkReverse->isChecked()); config->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); config->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); config->setAngle(ui.angleSelector->value()); config->setScale(ui.intScale->value()); } /********************************************************************/ /***** Innner Glow *********************************************/ /********************************************************************/ InnerGlow::InnerGlow(Mode mode, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_mode(mode), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); ui.intChoke->setRange(0, 100); ui.intChoke->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intRange->setRange(1, 100); ui.intRange->setSuffix(i18n(" %")); ui.intJitter->setRange(0, 100); ui.intJitter->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.radioColor, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.radioGradient, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbSource, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intChoke, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intJitter, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); if (m_mode == OuterGlowMode) { ui.cmbSource->hide(); ui.lblSource->hide(); ui.lblChoke->setText(i18nc("layer styles parameter", "Spread:")); } } void InnerGlow::setConfig(const psd_layer_effects_glow_common *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); ui.intNoise->setValue(config->noise()); ui.radioColor->setChecked(config->fillType() == psd_fill_solid_color); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(config->color()); ui.bnColor->setColor(color); ui.radioGradient->setChecked(config->fillType() == psd_fill_gradient); KoAbstractGradient *gradient = fetchGradientLazy( GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.cmbTechnique->setCurrentIndex((int)config->technique()); ui.intChoke->setValue(config->spread()); ui.intSize->setValue(config->size()); if (m_mode == InnerGlowMode) { const psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); ui.cmbSource->setCurrentIndex(iglow->source() == psd_glow_edge); } // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(config->antiAliased()); ui.intRange->setValue(config->range()); ui.intJitter->setValue(config->jitter()); } void InnerGlow::fetchConfig(psd_layer_effects_glow_common *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setNoise(ui.intNoise->value()); if (ui.radioColor->isChecked()) { config->setFillType(psd_fill_solid_color); } else { config->setFillType(psd_fill_gradient); } config->setColor(ui.bnColor->color().toQColor()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); config->setSpread(ui.intChoke->value()); config->setSize(ui.intSize->value()); if (m_mode == InnerGlowMode) { psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); iglow->setSource((psd_glow_source)ui.cmbSource->currentIndex()); } // FIXME: Curve editing //ui.cmbContour; config->setAntiAliased(ui.chkAntiAliased->isChecked()); config->setRange(ui.intRange->value()); config->setJitter(ui.intJitter->value()); } /********************************************************************/ /***** Pattern Overlay *********************************************/ /********************************************************************/ PatternOverlay::PatternOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void PatternOverlay::setPatternOverlay(const psd_layer_effects_pattern_overlay *pattern) { ui.cmbCompositeOp->selectCompositeOp(KoID(pattern->blendMode())); ui.intOpacity->setValue(pattern->opacity()); ui.patternChooser->setCurrentPattern(pattern->pattern()); ui.chkLinkWithLayer->setChecked(pattern->alignWithLayer()); ui.intScale->setValue(pattern->scale()); } void PatternOverlay::fetchPatternOverlay(psd_layer_effects_pattern_overlay *pattern) const { pattern->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); pattern->setOpacity(ui.intOpacity->value()); pattern->setPattern(static_cast(ui.patternChooser->currentResource())); pattern->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); pattern->setScale(ui.intScale->value()); } /********************************************************************/ /***** Satin *********************************************/ /********************************************************************/ Satin::Satin(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 250); ui.intDistance->setSuffix(i18n(" px")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void Satin::setSatin(const psd_layer_effects_satin *satin) { ui.cmbCompositeOp->selectCompositeOp(KoID(satin->blendMode())); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(satin->color()); ui.bnColor->setColor(color); ui.intOpacity->setValue(satin->opacity()); ui.angleSelector->setValue(satin->angle()); ui.intDistance->setValue(satin->distance()); ui.intSize->setValue(satin->size()); // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(satin->antiAliased()); ui.chkInvert->setChecked(satin->invert()); } void Satin::fetchSatin(psd_layer_effects_satin *satin) const { satin->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); satin->setOpacity(ui.intOpacity->value()); satin->setColor(ui.bnColor->color().toQColor()); satin->setAngle(ui.angleSelector->value()); satin->setDistance(ui.intDistance->value()); satin->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; satin->setAntiAliased(ui.chkAntiAliased->isChecked()); satin->setInvert(ui.chkInvert->isChecked()); } /********************************************************************/ /***** Stroke *********************************************/ /********************************************************************/ Stroke::Stroke(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); ui.intScale_2->setRange(0, 100); ui.intScale_2->setSuffix(i18n(" %")); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), ui.fillStack, SLOT(setCurrentIndex(int))); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbPosition, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResource*)), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale_2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // cold initialization ui.fillStack->setCurrentIndex(ui.cmbFillType->currentIndex()); } void Stroke::setStroke(const psd_layer_effects_stroke *stroke) { ui.intSize->setValue(stroke->size()); ui.cmbPosition->setCurrentIndex((int)stroke->position()); ui.cmbCompositeOp->selectCompositeOp(KoID(stroke->blendMode())); ui.intOpacity->setValue(stroke->opacity()); ui.cmbFillType->setCurrentIndex((int)stroke->fillType()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(stroke->color()); ui.bnColor->setColor(color); KoAbstractGradient *gradient = fetchGradientLazy(GradientPointerConverter::styleToResource(stroke->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(stroke->antiAliased()); ui.cmbStyle->setCurrentIndex((int)stroke->style()); ui.chkAlignWithLayer->setCheckable(stroke->alignWithLayer()); ui.angleSelector->setValue(stroke->angle()); ui.intScale->setValue(stroke->scale()); ui.patternChooser->setCurrentPattern(stroke->pattern()); ui.chkLinkWithLayer->setChecked(stroke->alignWithLayer()); ui.intScale_2->setValue(stroke->scale()); } void Stroke::fetchStroke(psd_layer_effects_stroke *stroke) const { stroke->setSize(ui.intSize->value()); stroke->setPosition((psd_stroke_position)ui.cmbPosition->currentIndex()); stroke->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); stroke->setOpacity(ui.intOpacity->value()); stroke->setFillType((psd_fill_type)ui.cmbFillType->currentIndex()); stroke->setColor(ui.bnColor->color().toQColor()); stroke->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); stroke->setReverse(ui.chkReverse->isChecked()); stroke->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); stroke->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); stroke->setAngle(ui.angleSelector->value()); stroke->setScale(ui.intScale->value()); stroke->setPattern(static_cast(ui.patternChooser->currentResource())); stroke->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); stroke->setScale(ui.intScale->value()); } diff --git a/libs/ui/dialogs/kis_dlg_layer_style.h b/libs/ui/dialogs/kis_dlg_layer_style.h index 0101b62933..443d3434d4 100644 --- a/libs/ui/dialogs/kis_dlg_layer_style.h +++ b/libs/ui/dialogs/kis_dlg_layer_style.h @@ -1,299 +1,301 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DLG_LAYER_STYLE_H #define KIS_DLG_LAYER_STYLE_H #include #include #include "kis_types.h" #include #include "ui_wdglayerstyles.h" #include "ui_wdgBevelAndEmboss.h" #include "ui_wdgblendingoptions.h" #include "ui_WdgColorOverlay.h" #include "ui_wdgContour.h" #include "ui_wdgdropshadow.h" #include "ui_WdgGradientOverlay.h" #include "ui_wdgInnerGlow.h" #include "ui_WdgPatternOverlay.h" #include "ui_WdgSatin.h" #include "ui_WdgStroke.h" #include "ui_wdgstylesselector.h" #include "ui_wdgTexture.h" #include class QListWidgetItem; class KisSignalCompressor; class KisCanvasResourceProvider; class Contour : public QWidget { Q_OBJECT public: Contour(QWidget *parent); Ui::WdgContour ui; }; class Texture : public QWidget { Q_OBJECT public: Texture(QWidget *parent); Ui::WdgTexture ui; }; class BevelAndEmboss : public QWidget { Q_OBJECT public: BevelAndEmboss(Contour *contour, Texture *texture, QWidget *parent); void setBevelAndEmboss(const psd_layer_effects_bevel_emboss *bevelAndEmboss); void fetchBevelAndEmboss(psd_layer_effects_bevel_emboss *bevelAndEmboss) const; Q_SIGNALS: void configChanged(); void globalAngleChanged(int value); private: Contour *m_contour; Texture *m_texture; Ui::WdgBevelAndEmboss ui; }; class BlendingOptions : public QWidget { Q_OBJECT public: BlendingOptions(QWidget *parent); Q_SIGNALS: void configChanged(); private: Ui::WdgBlendingOptions ui; }; class ColorOverlay : public QWidget { Q_OBJECT public: ColorOverlay(QWidget *parent); void setColorOverlay(const psd_layer_effects_color_overlay *colorOverlay); void fetchColorOverlay(psd_layer_effects_color_overlay *colorOverlay) const; Q_SIGNALS: void configChanged(); private: Ui::WdgColorOverlay ui; }; class DropShadow : public QWidget { Q_OBJECT public: enum Mode { DropShadowMode, InnerShadowMode }; public: DropShadow(Mode mode, QWidget *parent); void setShadow(const psd_layer_effects_shadow_common *shadow); void fetchShadow(psd_layer_effects_shadow_common *shadow) const; Q_SIGNALS: void configChanged(); void globalAngleChanged(int value); private: Ui::WdgDropShadow ui; Mode m_mode; }; class GradientOverlay : public QWidget { Q_OBJECT public: GradientOverlay(KisCanvasResourceProvider *resourceProvider, QWidget *parent); void setGradientOverlay(const psd_layer_effects_gradient_overlay *gradient); void fetchGradientOverlay(psd_layer_effects_gradient_overlay *gradient) const; Q_SIGNALS: void configChanged(); private: Ui::WdgGradientOverlay ui; KisCanvasResourceProvider *m_resourceProvider; }; class InnerGlow : public QWidget { Q_OBJECT public: enum Mode { InnerGlowMode = 0, OuterGlowMode }; public: InnerGlow(Mode mode, KisCanvasResourceProvider *resourceProvider, QWidget *parent); void setConfig(const psd_layer_effects_glow_common *innerGlow); void fetchConfig(psd_layer_effects_glow_common *innerGlow) const; Q_SIGNALS: void configChanged(); private: Ui::WdgInnerGlow ui; Mode m_mode; KisCanvasResourceProvider *m_resourceProvider; }; class PatternOverlay : public QWidget { Q_OBJECT public: PatternOverlay(QWidget *parent); void setPatternOverlay(const psd_layer_effects_pattern_overlay *pattern); void fetchPatternOverlay(psd_layer_effects_pattern_overlay *pattern) const; Q_SIGNALS: void configChanged(); private: Ui::WdgPatternOverlay ui; }; class Satin : public QWidget { Q_OBJECT public: Satin(QWidget *parent); void setSatin(const psd_layer_effects_satin *satin); void fetchSatin(psd_layer_effects_satin *satin) const; Q_SIGNALS: void configChanged(); private: Ui::WdgSatin ui; }; class Stroke : public QWidget { Q_OBJECT public: Stroke(KisCanvasResourceProvider *resourceProvider, QWidget *parent); void setStroke(const psd_layer_effects_stroke *stroke); void fetchStroke(psd_layer_effects_stroke *stroke) const; Q_SIGNALS: void configChanged(); private: Ui::WdgStroke ui; KisCanvasResourceProvider *m_resourceProvider; }; class StylesSelector : public QWidget { Q_OBJECT public: StylesSelector(QWidget *parent); void notifyExternalStyleChanged(const QString &name, const QUuid &uuid); void addNewStyle(KisPSDLayerStyleSP style); void loadCollection(const QString &fileName); private Q_SLOTS: void loadStyles(const QString &name); void selectStyle(QListWidgetItem *previous, QListWidgetItem* current); Q_SIGNALS: void styleSelected(KisPSDLayerStyleSP style); private: void refillCollections(); private: Ui::WdgStylesSelector ui; }; class KisDlgLayerStyle : public KoDialog { Q_OBJECT public: explicit KisDlgLayerStyle(KisPSDLayerStyleSP layerStyle, KisCanvasResourceProvider *resourceProvider, QWidget *parent = 0); ~KisDlgLayerStyle() override; KisPSDLayerStyleSP style() const; Q_SIGNALS: void configChanged(); public Q_SLOTS: void slotMasterFxSwitchChanged(bool value); void syncGlobalAngle(int angle); void notifyGuiConfigChanged(); void notifyPredefinedStyleSelected(KisPSDLayerStyleSP style); + void slotBevelAndEmbossChanged(QListWidgetItem*); + void changePage(QListWidgetItem *, QListWidgetItem*); void slotNotifyOnAccept(); void slotNotifyOnReject(); // Sets all the widgets to the contents of the given style void setStyle(KisPSDLayerStyleSP style); void slotLoadStyle(); void slotSaveStyle(); void slotNewStyle(); private: KisPSDLayerStyleSP m_layerStyle; KisPSDLayerStyleSP m_initialLayerStyle; Ui::WdgStylesDialog wdgLayerStyles; BevelAndEmboss *m_bevelAndEmboss; BlendingOptions *m_blendingOptions; ColorOverlay *m_colorOverlay; Contour *m_contour; DropShadow *m_dropShadow; GradientOverlay *m_gradientOverlay; InnerGlow *m_innerGlow; DropShadow *m_innerShadow; InnerGlow *m_outerGlow; PatternOverlay * m_patternOverlay; Satin *m_satin; Stroke *m_stroke; StylesSelector *m_stylesSelector; Texture *m_texture; KisSignalCompressor *m_configChangedCompressor; bool m_isSwitchingPredefinedStyle; /** * Used for debugging purposes only to track if m_layerStyle is in * sync with what is stored in the GUI */ mutable bool m_sanityLayerStyleDirty; }; #endif // KIS_DLG_LAYER_STYLE_H diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index fd1677c7d2..388c995136 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1659 +1,1680 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoID.h" #include #include #include #include #include #include #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" # ifndef USE_QT_TABLET_WINDOWS # include # endif #endif struct BackupSuffixValidator : public QValidator { BackupSuffixValidator(QObject *parent) : QValidator(parent) , invalidCharacters(QStringList() << "0" << "1" << "2" << "3" << "4" << "5" << "6" << "7" << "8" << "9" << "/" << "\\" << ":" << ";" << " ") {} ~BackupSuffixValidator() override {} const QStringList invalidCharacters; State validate(QString &line, int &/*pos*/) const override { Q_FOREACH(const QString invalidChar, invalidCharacters) { if (line.contains(invalidChar)) { return Invalid; } } return Acceptable; } }; GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg(true); // // Cursor Tab // m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline()); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor()); cursorColorBtutton->setColor(cursorColor); // // Window Tab // m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); m_mdiColor->setColor(mdiColor); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", true).toBool()); chkUsageLogging->setChecked(kritarc.value("LogUsage", true).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); // // Tools tab // m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); cmbFlowMode->setCurrentIndex((int)!cfg.readEntry("useCreamyAlphaDarken", true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste()); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled()); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Middle-Click Drag")); //m_cmbKineticScrollingGesture->addItem(i18n("On Right Click Drag")); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); m_kineticScrollingSensitivitySlider->setRange(0, 100); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity()); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars()); // // File handling // int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); chkHideAutosaveFiles->setChecked(cfg.readEntry("autosavefileshidden", true)); m_chkCompressKra->setChecked(cfg.compressKra()); chkZip64->setChecked(cfg.useZip64()); m_backupFileCheckBox->setChecked(cfg.backupFile()); cmbBackupFileLocation->setCurrentIndex(cfg.readEntry("backupfilelocation", 0)); txtBackupFileSuffix->setText(cfg.readEntry("backupfilesuffix", "~")); QValidator *validator = new BackupSuffixValidator(txtBackupFileSuffix); txtBackupFileSuffix->setValidator(validator); intNumBackupFiles->setValue(cfg.readEntry("numberofbackupfiles", 1)); // // Miscellaneous // cmbStartupSession->addItem(i18n("Open default window")); cmbStartupSession->addItem(i18n("Load previous session")); cmbStartupSession->addItem(i18n("Show session manager")); cmbStartupSession->setCurrentIndex(cfg.sessionOnStartup()); chkSaveSessionOnQuit->setChecked(cfg.saveSessionOnQuit(false)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_undoStackSize->setValue(cfg.undoStackLimit()); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); chkShowRootLayer->setChecked(cfg.showRootLayer()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); } void GeneralTab::setDefault() { KisConfig cfg(true); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); chkHideAutosaveFiles->setChecked(true); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); cmbBackupFileLocation->setCurrentIndex(0); txtBackupFileSuffix->setText("~"); intNumBackupFiles->setValue(1); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); chkZip64->setChecked(cfg.useZip64(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); chkUsageLogging->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); cmbFlowMode->setCurrentIndex(0); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::useZip64() { return chkZip64->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::kineticScrollingEnabled() { return m_groupBoxKineticScrollingSettings->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivitySlider->value(); } bool GeneralTab::kineticScrollingHiddenScrollbars() { return m_chkKineticScrollingHideScrollbars->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; KisSqueezedComboBox *cmb = new KisSqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors()); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg(true); refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { KisConfig cfg(true); if (useSystemProfile) { QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA")); KisConfig cfg(true); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { KisConfig cfg(true); m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true)); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true)); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); } #else m_page->grpTabletApi->setVisible(false); #endif } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } + +#ifdef USE_QT_TABLET_WINDOWS + connect(m_page->btnResolutionSettings, SIGNAL(clicked()), SLOT(slotResolutionSettings())); + connect(m_page->radioWintab, SIGNAL(toggled(bool)), m_page->btnResolutionSettings, SLOT(setEnabled(bool))); + m_page->btnResolutionSettings->setEnabled(m_page->radioWintab->isChecked()); +#else + m_page->btnResolutionSettings->setVisible(false); +#endif + #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } +#if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS +#include "KisDlgCustomTabletResolution.h" +#endif + +void TabletSettingsTab::slotResolutionSettings() +{ +#if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS + KisDlgCustomTabletResolution dlg(this); + dlg.exec(); +#endif +} + //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { return KisImageConfig(true).totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg(true); const double totalRAM = cfg.totalRAM(); lblTotalMemory->setText(KFormat().formatByteSize(totalRAM * 1024 * 1024, 0, KFormat::IECBinaryDialect, KFormat::UnitMegaByte)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); sliderThreadsLimit->setRange(1, QThread::idealThreadCount()); sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount()); sliderFpsLimit->setRange(20, 100); sliderFpsLimit->setSuffix(i18n(" fps")); connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int))); connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int))); intCachedFramesSizeLimit->setRange(1, 10000); intCachedFramesSizeLimit->setSuffix(i18n(" px")); intCachedFramesSizeLimit->setSingleStep(1); intCachedFramesSizeLimit->setPageStep(1000); intRegionOfInterestMargin->setRange(1, 100); intRegionOfInterestMargin->setSuffix(i18n(" %")); intRegionOfInterestMargin->setSingleStep(1); intRegionOfInterestMargin->setPageStep(10); connect(chkCachedFramesSizeLimit, SIGNAL(toggled(bool)), intCachedFramesSizeLimit, SLOT(setEnabled(bool))); connect(chkUseRegionOfInterest, SIGNAL(toggled(bool)), intRegionOfInterestMargin, SLOT(setEnabled(bool))); #ifndef Q_OS_WIN // AVX workaround is needed on Windows+GCC only chkDisableAVXOptimizations->setVisible(false); #endif load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg(true); sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault); m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2(true); chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); #ifdef Q_OS_WIN chkDisableAVXOptimizations->setChecked(cfg2.disableAVXOptimizations(requestDefault)); #endif chkBackgroundCacheGeneration->setChecked(cfg2.calculateAnimationCacheInBackground(requestDefault)); } if (cfg.useOnDiskAnimationCacheSwapping(requestDefault)) { optOnDisk->setChecked(true); } else { optInMemory->setChecked(true); } chkCachedFramesSizeLimit->setChecked(cfg.useAnimationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setValue(cfg.animationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setEnabled(chkCachedFramesSizeLimit->isChecked()); chkUseRegionOfInterest->setChecked(cfg.useAnimationCacheRegionOfInterest(requestDefault)); intRegionOfInterestMargin->setValue(cfg.animationCacheRegionOfInterestMargin(requestDefault) * 100.0); intRegionOfInterestMargin->setEnabled(chkUseRegionOfInterest->isChecked()); } void PerformanceTab::save() { KisImageConfig cfg(false); cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); cfg.setMaxNumberOfThreads(sliderThreadsLimit->value()); cfg.setFrameRenderingClones(sliderFrameClonesLimit->value()); cfg.setFpsLimit(sliderFpsLimit->value()); { KisConfig cfg2(true); cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); #ifdef Q_OS_WIN cfg2.setDisableAVXOptimizations(chkDisableAVXOptimizations->isChecked()); #endif cfg2.setCalculateAnimationCacheInBackground(chkBackgroundCacheGeneration->isChecked()); } cfg.setUseOnDiskAnimationCacheSwapping(optOnDisk->isChecked()); cfg.setUseAnimationCacheFrameSizeLimit(chkCachedFramesSizeLimit->isChecked()); cfg.setAnimationCacheFrameSizeLimit(intCachedFramesSizeLimit->value()); cfg.setUseAnimationCacheRegionOfInterest(chkUseRegionOfInterest->isChecked()); cfg.setAnimationCacheRegionOfInterestMargin(intRegionOfInterestMargin->value() / 100.0); } void PerformanceTab::selectSwapDir() { KisImageConfig cfg(true); QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); if (swapDir.isEmpty()) { return; } lblSwapFileLocation->setText(swapDir); } void PerformanceTab::slotThreadsLimitChanged(int value) { KisSignalsBlocker b(sliderFrameClonesLimit); sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value)); m_lastUsedThreadsLimit = value; } void PerformanceTab::slotFrameClonesLimitChanged(int value) { KisSignalsBlocker b(sliderThreadsLimit); sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value)); m_lastUsedClonesLimit = value; } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" #include "opengl/KisOpenGLModeProber.h" #include "opengl/KisScreenInformationAdapter.h" #include #include QString colorSpaceString(KisSurfaceColorSpace cs, int depth) { const QString csString = #ifdef HAVE_HDR cs == KisSurfaceColorSpace::bt2020PQColorSpace ? "Rec. 2020 PQ" : cs == KisSurfaceColorSpace::scRGBColorSpace ? "Rec. 709 Linear" : #endif cs == KisSurfaceColorSpace::sRGBColorSpace ? "sRGB" : cs == KisSurfaceColorSpace::DefaultColorSpace ? "sRGB" : "Unknown Color Space"; return QString("%1 (%2 bit)").arg(csString).arg(depth); } int formatToIndex(KisConfig::RootSurfaceFormat fmt) { return fmt == KisConfig::BT2020_PQ ? 1 : fmt == KisConfig::BT709_G10 ? 2 : 0; } KisConfig::RootSurfaceFormat indexToFormat(int value) { return value == 1 ? KisConfig::BT2020_PQ : value == 2 ? KisConfig::BT709_G10 : KisConfig::BT709_G22; } DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg(true); const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); #ifdef Q_OS_WIN const QString rendererOpenGLESText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); #else const QString rendererOpenGLESText = i18nc("canvas renderer", "OpenGL ES"); #endif lblCurrentRenderer->setText(KisOpenGL::hasOpenGLES() ? rendererOpenGLESText : rendererOpenGLText); cmbPreferredRenderer->clear(); QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererOpenGLES) { qtPreferredRendererText = rendererOpenGLESText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbPreferredRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbPreferredRenderer->setCurrentIndex(0); if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #ifdef Q_OS_WIN if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->addItem(rendererOpenGLESText, KisOpenGL::RendererOpenGLES); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #endif if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } lblCurrentDisplayFormat->setText(""); lblCurrentRootSurfaceFormat->setText(""); lblHDRWarning->setText(""); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::sRGBColorSpace, 8)); #ifdef HAVE_HDR cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::bt2020PQColorSpace, 10)); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::scRGBColorSpace, 16)); #endif cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); QOpenGLContext *context = QOpenGLContext::currentContext(); if (!context) { context = QOpenGLContext::globalShareContext(); } if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (screen && adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { QStringList toolTip; toolTip << i18n("Display Id: %1", info.screen->name()); toolTip << i18n("Display Name: %1 %2", info.screen->manufacturer(), info.screen->model()); toolTip << i18n("Min Luminance: %1", info.minLuminance); toolTip << i18n("Max Luminance: %1", info.maxLuminance); toolTip << i18n("Max Full Frame Luminance: %1", info.maxFullFrameLuminance); toolTip << i18n("Red Primary: %1, %2", info.redPrimary[0], info.redPrimary[1]); toolTip << i18n("Green Primary: %1, %2", info.greenPrimary[0], info.greenPrimary[1]); toolTip << i18n("Blue Primary: %1, %2", info.bluePrimary[0], info.bluePrimary[1]); toolTip << i18n("White Point: %1, %2", info.whitePoint[0], info.whitePoint[1]); lblCurrentDisplayFormat->setToolTip(toolTip.join('\n')); lblCurrentDisplayFormat->setText(colorSpaceString(info.colorSpace, info.bitsPerColor)); } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); } } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); qWarning() << "Failed to fetch display info:" << adapter.errorString(); } const QSurfaceFormat currentFormat = KisOpenGLModeProber::instance()->surfaceformatInUse(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisSurfaceColorSpace colorSpace = currentFormat.colorSpace(); #else KisSurfaceColorSpace colorSpace = KisSurfaceColorSpace::DefaultColorSpace; #endif lblCurrentRootSurfaceFormat->setText(colorSpaceString(colorSpace, currentFormat.redBufferSize())); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(cfg.rootSurfaceFormat())); connect(cmbPreferedRootSurfaceFormat, SIGNAL(currentIndexChanged(int)), SLOT(slotPreferredSurfaceFormatChanged(int))); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } #ifndef HAVE_HDR grpHDRSettings->setVisible(false); #endif const QStringList openglWarnings = KisOpenGL::getOpenGLWarnings(); if (openglWarnings.isEmpty()) { lblOpenGLWarnings->setVisible(false); } else { QString text(" "); text.append(i18n("Warning(s):")); text.append("
    "); Q_FOREACH (const QString &warning, openglWarnings) { text.append("
  • "); text.append(warning.toHtmlEscaped()); text.append("
  • "); } text.append("
"); lblOpenGLWarnings->setText(text); lblOpenGLWarnings->setVisible(true); } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(imageCfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor()); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); } void DisplaySettingsTab::setDefault() { KisConfig cfg(true); cmbPreferredRenderer->setCurrentIndex(0); if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } void DisplaySettingsTab::slotPreferredSurfaceFormatChanged(int index) { Q_UNUSED(index); QOpenGLContext *context = QOpenGLContext::currentContext(); if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { if (cmbPreferedRootSurfaceFormat->currentIndex() != formatToIndex(KisConfig::BT709_G22) && info.colorSpace == KisSurfaceColorSpace::sRGBColorSpace) { lblHDRWarning->setText(i18n("WARNING: current display doesn't support HDR rendering")); } else { lblHDRWarning->setText(""); } } } } } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); setFaceType(KPageDialog::Tree); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); m_pages << page; addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); m_pages << page; addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); m_pages << page; // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); m_pages << page; addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); m_pages << page; addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); m_pages << page; addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); m_pages << page; addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); m_pages << page; addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); m_pages << page; QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText(i18nc("@action:button", "Restore Defaults")); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); KisConfig cfg(true); QString currentPageName = cfg.readEntry("KisDlgPreferences/CurrentPage"); Q_FOREACH(KPageWidgetItem *page, m_pages) { if (page->objectName() == currentPageName) { setCurrentPage(page); break; } } } KisDlgPreferences::~KisDlgPreferences() { KisConfig cfg(true); cfg.writeEntry("KisDlgPreferences/CurrentPage", currentPage()->objectName()); } void KisDlgPreferences::showEvent(QShowEvent *event){ KPageDialog::showEvent(event); button(QDialogButtonBox::Cancel)->setAutoDefault(false); button(QDialogButtonBox::Ok)->setAutoDefault(false); button(QDialogButtonBox::RestoreDefaults)->setAutoDefault(false); button(QDialogButtonBox::Cancel)->setDefault(false); button(QDialogButtonBox::Ok)->setDefault(false); button(QDialogButtonBox::RestoreDefaults)->setDefault(false); } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg(false); cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setForceAlwaysFullSizedOutline(!dialog->m_general->m_changeBrushOutline->isChecked()); cfg.setSessionOnStartup(dialog->m_general->sessionOnStartup()); cfg.setSaveSessionOnQuit(dialog->m_general->saveSessionOnQuit()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", dialog->m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.writeEntry("autosavefileshidden", dialog->m_general->chkHideAutosaveFiles->isChecked()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.writeEntry("backupfilelocation", dialog->m_general->cmbBackupFileLocation->currentIndex()); cfg.writeEntry("backupfilesuffix", dialog->m_general->txtBackupFileSuffix->text()); cfg.writeEntry("numberofbackupfiles", dialog->m_general->intNumBackupFiles->value()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); cfg.setUseZip64(dialog->m_general->useZip64()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); kritarc.setValue("LogUsage", dialog->m_general->chkUsageLogging->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.writeEntry("useCreamyAlphaDarken", (bool)!dialog->m_general->cmbFlowMode->currentIndex()); cfg.setKineticScrollingEnabled(dialog->m_general->kineticScrollingEnabled()); cfg.setKineticScrollingGesture(dialog->m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(dialog->m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingHideScrollbars(dialog->m_general->kineticScrollingHiddenScrollbars()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); cfg.setActivateTransformToolAfterPaste(dialog->m_general->chkEnableTranformToolAfterPaste->isChecked()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage(false); cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setForcePaletteColors(dialog->m_colorSettings->m_page->chkForcePaletteColor->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif dialog->m_performanceSettings->save(); { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbPreferredRenderer->itemData( dialog->m_displaySettings->cmbPreferredRenderer->currentIndex()).toInt()); KisOpenGL::setUserPreferredOpenGLRendererConfig(renderer); } if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setRootSurfaceFormat(&kritarc, indexToFormat(dialog->m_displaySettings->cmbPreferedRootSurfaceFormat->currentIndex())); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfgImage.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); cfg.setCursorMainColor(dialog->m_general->cursorColorBtutton->color().toQColor()); cfg.setPixelGridColor(dialog->m_displaySettings->pixelGridColorButton->color().toQColor()); cfg.setPixelGridDrawingThreshold(dialog->m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/dialogs/kis_dlg_preferences.h b/libs/ui/dialogs/kis_dlg_preferences.h index d34c6beec1..bfc2cc6e0f 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.h +++ b/libs/ui/dialogs/kis_dlg_preferences.h @@ -1,355 +1,356 @@ /* * preferencesdlg.h - part of KImageShop^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_DLG_PREFERENCES_H_ #define _KIS_DLG_PREFERENCES_H_ #include #include #include #include #include #include #include "kis_global.h" #include #include "ui_wdggeneralsettings.h" #include "ui_wdgdisplaysettings.h" #include "ui_wdgcolorsettings.h" #include "ui_wdgtabletsettings.h" #include "ui_wdgperformancesettings.h" #include "ui_wdgfullscreensettings.h" #include "KisShortcutsDialog.h" class KoID; class KisInputConfigurationPage; class KoConfigAuthorPage; /** * "General"-tab for preferences dialog */ class WdgGeneralSettings : public QWidget, public Ui::WdgGeneralSettings { Q_OBJECT public: WdgGeneralSettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); chkShowRootLayer->setVisible(false); } }; class GeneralTab : public WdgGeneralSettings { Q_OBJECT public: GeneralTab(QWidget *parent = 0, const char *name = 0); CursorStyle cursorStyle(); OutlineStyle outlineStyle(); KisConfig::SessionOnStartup sessionOnStartup() const; bool saveSessionOnQuit() const; bool showRootLayer(); int autoSaveInterval(); void setDefault(); int undoStackSize(); bool showOutlineWhilePainting(); int mdiMode(); int favoritePresets(); bool showCanvasMessages(); bool compressKra(); bool useZip64(); bool toolOptionsInDocker(); bool kineticScrollingEnabled(); int kineticScrollingGesture(); int kineticScrollingSensitivity(); bool kineticScrollingHiddenScrollbars(); bool switchSelectionCtrlAlt(); bool convertToImageColorspaceOnImport(); private Q_SLOTS: void getBackgroundImage(); void clearBackgroundImage(); }; /** * "Shortcuts" tab for preferences dialog */ class WdgShortcutSettings : public KisShortcutsDialog { Q_OBJECT public: WdgShortcutSettings(QWidget *parent) : KisShortcutsDialog(KisShortcutsEditor::AllActions, KisShortcutsEditor::LetterShortcutsAllowed, parent) { } }; class KisActionsSnapshot; class ShortcutSettingsTab : public QWidget { Q_OBJECT public: ShortcutSettingsTab(QWidget *parent = 0, const char *name = 0); ~ShortcutSettingsTab() override; public: void setDefault(); WdgShortcutSettings *m_page; QScopedPointer m_snapshot; public Q_SLOTS: void saveChanges(); void cancelChanges(); }; /** * "Color" tab for preferences dialog */ class WdgColorSettings : public QWidget, public Ui::WdgColorSettings { Q_OBJECT public: WdgColorSettings(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class ColorSettingsTab : public QWidget { Q_OBJECT public: ColorSettingsTab(QWidget *parent = 0, const char *name = 0); private Q_SLOTS: void refillMonitorProfiles(const KoID & s); void installProfile(); void toggleAllowMonitorProfileSelection(bool useSystemProfile); public: void setDefault(); WdgColorSettings *m_page; QButtonGroup m_pasteBehaviourGroup; QList m_monitorProfileLabels; QList m_monitorProfileWidgets; }; //======================= class WdgTabletSettings : public QWidget, public Ui::WdgTabletSettings { Q_OBJECT public: WdgTabletSettings(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class TabletSettingsTab : public QWidget { Q_OBJECT public: TabletSettingsTab(QWidget *parent = 0, const char *name = 0); private Q_SLOTS: void slotTabletTest(); + void slotResolutionSettings(); public: void setDefault(); WdgTabletSettings *m_page; }; //======================= /** * "Performance"-tab for preferences dialog */ class SliderAndSpinBoxSync; class WdgPerformanceSettings : public QWidget, public Ui::WdgPerformanceSettings { Q_OBJECT public: WdgPerformanceSettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); } }; class PerformanceTab : public WdgPerformanceSettings { Q_OBJECT public: PerformanceTab(QWidget *parent = 0, const char *name = 0); ~PerformanceTab() override; void load(bool requestDefault); void save(); private Q_SLOTS: void selectSwapDir(); void slotThreadsLimitChanged(int value); void slotFrameClonesLimitChanged(int value); private: int realTilesRAM(); private: QVector m_syncs; int m_lastUsedThreadsLimit; int m_lastUsedClonesLimit; }; //======================= class WdgDisplaySettings : public QWidget, public Ui::WdgDisplaySettings { Q_OBJECT public: WdgDisplaySettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); } }; /** * Display settings tab for preferences dialog */ class DisplaySettingsTab : public WdgDisplaySettings { Q_OBJECT public: DisplaySettingsTab(QWidget *parent = 0, const char *name = 0); public: void setDefault(); protected Q_SLOTS: void slotUseOpenGLToggled(bool isChecked); void slotPreferredSurfaceFormatChanged(int index); public: }; //======================= /** * Full screen settings tab for preferences dialog */ class WdgFullscreenSettingsBase : public QWidget, public Ui::WdgFullscreenSettings { Q_OBJECT public: WdgFullscreenSettingsBase(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class FullscreenSettingsTab : public WdgFullscreenSettingsBase { Q_OBJECT public: FullscreenSettingsTab(QWidget *parent); public: void setDefault(); }; //======================= /** * Preferences dialog of KImageShop^WKrayon^WKrita */ class KisDlgPreferences : public KPageDialog { Q_OBJECT public: static bool editPreferences(); protected: KisDlgPreferences(QWidget *parent = 0, const char *name = 0); ~KisDlgPreferences() override; void showEvent(QShowEvent *event) override; protected: GeneralTab *m_general; ShortcutSettingsTab *m_shortcutSettings; ColorSettingsTab *m_colorSettings; PerformanceTab *m_performanceSettings; DisplaySettingsTab *m_displaySettings; TabletSettingsTab *m_tabletSettings; FullscreenSettingsTab *m_fullscreenSettings; KisInputConfigurationPage *m_inputConfiguration; KoConfigAuthorPage *m_authorPage; QList m_pages; protected Q_SLOTS: void slotDefault(); }; #endif diff --git a/libs/ui/forms/wdgtabletsettings.ui b/libs/ui/forms/wdgtabletsettings.ui index 604c4d0744..786fd7bd00 100644 --- a/libs/ui/forms/wdgtabletsettings.ui +++ b/libs/ui/forms/wdgtabletsettings.ui @@ -1,210 +1,234 @@ WdgTabletSettings 0 0 569 - 433 + 453 0 0 Color Settings 10 10 10 10 10 0 0 200 250 false 0 0 Low Pressure Qt::Horizontal 40 20 0 0 High Pressure 1.0 Qt::Vertical 20 40 0.0 Input Pressure Global Curve Qt::Horizontal 40 20 Qt::Vertical 20 40 Open Tablet Tester... Tablet Input API (changing this requires restarting Krita) - - - WinTab - - + + + + + WinTab + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Advanced... + + + + Windows 8+ Pointer Input (depends on Windows Ink) (EXPERIMENTAL) KisCurveWidget QWidget
widgets/kis_curve_widget.h
1
diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index d173543bc4..e11d7e7f53 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,740 +1,754 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Arjen Hiemstra * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager.h" #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include #include #include #include #include #include #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_pan_action.h" #include "kis_alternate_invocation_action.h" #include "kis_rotate_canvas_action.h" #include "kis_zoom_action.h" #include "kis_show_palette_action.h" #include "kis_change_primary_setting_action.h" #include "kis_shortcut_matcher.h" #include "kis_stroke_shortcut.h" #include "kis_single_action_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile.h" #include "kis_input_profile_manager.h" #include "kis_shortcut_configuration.h" #include #include #include "kis_extended_modifiers_mapper.h" #include "kis_input_manager_p.h" template uint qHash(QPointer value) { return reinterpret_cast(value.data()); } KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { d->setupActions(); connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool())); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged())); connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent())); QApplication::instance()-> installEventFilter(new Private::ProximityNotifier(d, this)); } KisInputManager::~KisInputManager() { delete d; } void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.addCanvas(canvas); } void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.removeCanvas(canvas); } void KisInputManager::toggleTabletLogger() { KisTabletDebugger::instance()->toggleDebugging(); } void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority) { Private::PriorityList::iterator begin = d->priorityEventFilter.begin(); Private::PriorityList::iterator it = begin; Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(begin, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) return; it = std::find_if(begin, end, [priority] (const Private::PriorityPair &a) { return a.first > priority; }); d->priorityEventFilter.insert(it, qMakePair(priority, filter)); d->priorityEventFilterSeqNo++; } void KisInputManager::detachPriorityEventFilter(QObject *filter) { Private::PriorityList::iterator it = d->priorityEventFilter.begin(); Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(it, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) { d->priorityEventFilter.erase(it); } } void KisInputManager::setupAsEventFilter(QObject *receiver) { if (d->eventsReceiver) { d->eventsReceiver->removeEventFilter(this); } d->eventsReceiver = receiver; if (d->eventsReceiver) { d->eventsReceiver->installEventFilter(this); } } void KisInputManager::stopIgnoringEvents() { d->allowMouseEvents(); } #if defined (__clang__) #pragma GCC diagnostic ignored "-Wswitch" #endif bool KisInputManager::eventFilter(QObject* object, QEvent* event) { if (object != d->eventsReceiver) return false; if (d->eventEater.eventFilter(object, event)) return false; if (!d->matcher.hasRunningShortcut()) { int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo; for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) { const QPointer &filter = it->second; if (filter.isNull()) { it = d->priorityEventFilter.erase(it); d->priorityEventFilterSeqNo++; savedPriorityEventFilterSeqNo++; continue; } if (filter->eventFilter(object, event)) return true; /** * If the filter removed itself from the filters list or * added something there, just exit the loop */ if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) { return true; } ++it; } // KoToolProxy needs to pre-process some events to ensure the // global shortcuts (not the input manager's ones) are not // executed, in particular, this line will accept events when the // tool is in text editing, preventing shortcut triggering if (d->toolProxy) { d->toolProxy->processEvent(event); } } // Continue with the actual switch statement... return eventFilterImpl(event); } // Qt's events do not have copy-ctors yes, so we should emulate them // See https://bugreports.qt.io/browse/QTBUG-72488 template void copyEventHack(Event *src, QScopedPointer &dst); template<> void copyEventHack(QMouseEvent *src, QScopedPointer &dst) { QMouseEvent *tmp = new QMouseEvent(src->type(), src->localPos(), src->windowPos(), src->screenPos(), src->button(), src->buttons(), src->modifiers(), src->source()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template<> void copyEventHack(QTabletEvent *src, QScopedPointer &dst) { QTabletEvent *tmp = new QTabletEvent(src->type(), src->posF(), src->globalPosF(), src->device(), src->pointerType(), src->pressure(), src->xTilt(), src->yTilt(), src->tangentialPressure(), src->rotation(), src->z(), src->modifiers(), src->uniqueId(), src->button(), src->buttons()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template bool KisInputManager::compressMoveEventCommon(Event *event) { /** * We construct a copy of this event object, so we must ensure it * has a correct type. */ static_assert(std::is_same::value || std::is_same::value, "event should be a mouse or a tablet event"); bool retval = false; /** * Compress the events if the tool doesn't need high resolution input */ if ((event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove) && (!d->matcher.supportsHiResInputEvents() || d->testingCompressBrushEvents)) { copyEventHack(event, d->compressedMoveEvent); d->moveEventCompressor.start(); /** * On Linux Qt eats the rest of unneeded events if we * ignore the first of the chunk of tablet events. So * generally we should never activate this feature. Only * for testing purposes! */ if (d->testingAcceptCompressedTabletEvents) { event->setAccepted(true); } retval = true; } else { slotCompressedMoveEvent(); retval = d->handleCompressedTabletEvent(event); } return retval; } +bool shouldResetWheelDelta(QEvent * event) +{ + return + event->type() == QEvent::FocusIn || + event->type() == QEvent::FocusOut || + event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::MouseButtonDblClick || + event->type() == QEvent::TabletPress || + event->type() == QEvent::TabletRelease || + event->type() == QEvent::Enter || + event->type() == QEvent::Leave || + event->type() == QEvent::TouchBegin || + event->type() == QEvent::TouchEnd || + event->type() == QEvent::TouchCancel || + event->type() == QEvent::NativeGesture; + +} + bool KisInputManager::eventFilterImpl(QEvent * event) { bool retval = false; - if (event->type() != QEvent::Wheel) { + if (shouldResetWheelDelta(event)) { d->accumulatedScrollDelta = 0; } switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { d->debugEvent(event); - //Block mouse press events on Genius tablets - if (d->tabletActive) break; - if (d->ignoringQtCursorEvents()) break; if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent); } //Reset signal compressor to prevent processing events before press late d->resetCompressor(); event->setAccepted(retval); break; } case QEvent::MouseButtonRelease: { d->debugEvent(event); - if (d->ignoringQtCursorEvents()) break; if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent); event->setAccepted(retval); break; } case QEvent::ShortcutOverride: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); if (!keyEvent->isAutoRepeat()) { retval = d->matcher.keyPressed(key); } else { retval = d->matcher.autoRepeatedKeyPressed(key); } /** * Workaround for temporary switching of tools by * KoCanvasControllerWidget. We don't need this switch because * we handle it ourselves. */ retval |= !d->forwardAllEventsToTool && (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Escape); break; } case QEvent::KeyRelease: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); if (!keyEvent->isAutoRepeat()) { Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); - if (d->ignoringQtCursorEvents()) break; QMouseEvent *mouseEvent = static_cast(event); retval = compressMoveEventCommon(mouseEvent); break; } case QEvent::Wheel: { d->debugEvent(event); QWheelEvent *wheelEvent = static_cast(event); #ifdef Q_OS_OSX // Some QT wheel events are actually touch pad pan events. From the QT docs: // "Wheel events are generated for both mouse wheels and trackpad scroll gestures." // We differentiate between touchpad events and real mouse wheels by inspecting the // event source. if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent); break; } #endif d->accumulatedScrollDelta += wheelEvent->delta(); KisSingleActionShortcut::WheelAction action; /** * Ignore delta 0 events on OSX, since they are triggered by tablet * proximity when using Wacom devices. */ #ifdef Q_OS_OSX if(wheelEvent->delta() == 0) { retval = true; break; } #endif if (wheelEvent->orientation() == Qt::Horizontal) { if(wheelEvent->delta() < 0) { action = KisSingleActionShortcut::WheelRight; } else { action = KisSingleActionShortcut::WheelLeft; } } else { if(wheelEvent->delta() > 0) { action = KisSingleActionShortcut::WheelUp; } else { action = KisSingleActionShortcut::WheelDown; } } if (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(action, wheelEvent); d->accumulatedScrollDelta = 0; } else { retval = true; } break; } case QEvent::Enter: d->debugEvent(event); //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); if (!d->containsPointer) { d->containsPointer = true; d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; } d->matcher.enterEvent(); break; case QEvent::Leave: d->debugEvent(event); d->containsPointer = false; /** * We won't get a TabletProximityLeave event when the tablet * is hovering above some other widget, so restore cursor * events processing right now. */ d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; d->matcher.leaveEvent(); break; case QEvent::FocusIn: d->debugEvent(event); KisAbstractInputAction::setInputManager(this); //Clear all state so we don't have half-matched shortcuts dangling around. d->matcher.reinitialize(); { // Emulate pressing of the key that are already pressed KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); eventFilterImpl(&kevent); } } d->allowMouseEvents(); break; case QEvent::FocusOut: { d->debugEvent(event); KisAbstractInputAction::setInputManager(this); QPointF currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); d->matcher.lostFocusEvent(currentLocalPos); break; } case QEvent::TabletPress: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent); if (!d->containsPointer) { d->containsPointer = true; d->touchHasBlockedPressEvents = false; } } event->setAccepted(true); retval = true; d->blockMouseEvents(); //Reset signal compressor to prevent processing events before press late d->resetCompressor(); d->eatOneMousePress(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TabletMove: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = compressMoveEventCommon(tabletEvent); if (d->tabletLatencyTracker) { d->tabletLatencyTracker->push(tabletEvent->timestamp()); } /** * The flow of tablet events means the tablet is in the * proximity area, so activate it even when the * TabletEnterProximity event was missed (may happen when * changing focus of the window with tablet in the proximity * area) */ d->blockMouseEvents(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TabletRelease: { #ifdef Q_OS_MAC d->allowMouseEvents(); #endif d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent); retval = true; event->setAccepted(true); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TouchBegin: { if (startTouch(retval)) { QTouchEvent *tevent = static_cast(event); KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchBeginEvent(tevent); event->accept(); } break; } case QEvent::TouchUpdate: { QTouchEvent *tevent = static_cast(event); #ifdef Q_OS_MAC int count = 0; Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; } } if (count < 2 && tevent->touchPoints().length() > count) { d->touchHasBlockedPressEvents = false; retval = d->matcher.touchEndEvent(tevent); } else { #endif d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchUpdateEvent(tevent); #ifdef Q_OS_OSX } #endif event->accept(); break; } case QEvent::TouchEnd: { endTouch(); QTouchEvent *tevent = static_cast(event); retval = d->matcher.touchEndEvent(tevent); event->accept(); break; } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); switch (gevent->gestureType()) { case Qt::BeginNativeGesture: { if (startTouch(retval)) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureBeginEvent(gevent); event->accept(); } break; } case Qt::EndNativeGesture: { endTouch(); retval = d->matcher.nativeGestureEndEvent(gevent); event->accept(); break; } default: { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureEvent(gevent); event->accept(); break; } } break; } default: break; } return !retval ? d->processUnhandledEvent(event) : true; } bool KisInputManager::startTouch(bool &retval) { d->touchHasBlockedPressEvents = KisConfig(true).disableTouchOnCanvas(); // Touch rejection: if touch is disabled on canvas, no need to block mouse press events if (KisConfig(true).disableTouchOnCanvas()) { d->eatOneMousePress(); } if (d->tryHidePopupPalette()) { retval = true; return false; } else { return true; } } void KisInputManager::endTouch() { d->touchHasBlockedPressEvents = false; } void KisInputManager::slotCompressedMoveEvent() { if (d->compressedMoveEvent) { // d->touchHasBlockedPressEvents = false; (void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data()); d->compressedMoveEvent.reset(); //dbgInput << "Compressed move event received."; } else { //dbgInput << "Unexpected empty move event"; } } KisCanvas2* KisInputManager::canvas() const { return d->canvas; } QPointer KisInputManager::toolProxy() const { return d->toolProxy; } void KisInputManager::slotAboutToChangeTool() { QPointF currentLocalPos; if (canvas() && canvas()->canvasWidget()) { currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); } d->matcher.lostFocusEvent(currentLocalPos); } void KisInputManager::slotToolChanged() { if (!d->canvas) return; KoToolManager *toolManager = KoToolManager::instance(); KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId()); if (tool) { d->setMaskSyntheticEvents(tool->maskSyntheticEvents()); if (tool->isInTextMode()) { d->forwardAllEventsToTool = true; d->matcher.suppressAllActions(true); } else { d->forwardAllEventsToTool = false; d->matcher.suppressAllActions(false); } } } void KisInputManager::profileChanged() { d->matcher.clearShortcuts(); KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile(); if (profile) { const QList shortcuts = profile->allShortcuts(); for (KisShortcutConfiguration * const shortcut : shortcuts) { dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name(); switch(shortcut->type()) { case KisShortcutConfiguration::KeyCombinationType: d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys()); break; case KisShortcutConfiguration::MouseButtonType: d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons()); break; case KisShortcutConfiguration::MouseWheelType: d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel()); break; case KisShortcutConfiguration::GestureType: if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) { d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture()); } break; default: break; } } } else { dbgInput << "No Input Profile Found: canvas interaction will be impossible"; } } diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp index cca9e85cc7..509dc11b0b 100644 --- a/libs/ui/input/kis_input_manager_p.cpp +++ b/libs/ui/input/kis_input_manager_p.cpp @@ -1,617 +1,670 @@ /* * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager_p.h" #include #include #include #include #include "kis_input_manager.h" #include "kis_config.h" #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" #include "kis_input_profile_manager.h" +#include "kis_extended_modifiers_mapper.h" /** * This hungry class EventEater encapsulates event masking logic. * * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after * tablet events. Those events are sent in order to allow widgets that haven't * implemented tablet specific functionality to seamlessly behave as if one were * using a mouse. These synthetic events are *supposed* to be optional, or at * least come with a flag saying "This is a fake event!!" but neither of those * methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.) * * Qt 5.4 provides no reliable way to see if a user's tablet is being hovered * over the pad, since it converts all tablethover events into mousemove, with * no option to turn this off. Moreover, sometimes the MouseButtonPress event * from the tapping their tablet happens BEFORE the TabletPress event. This * means we have to resort to a somewhat complicated logic. What makes this * truly a joke is that we are not guaranteed to observe TabletProximityEnter * events when we're using a tablet, either, you may only see an Enter event. * * Once we see tablet events heading our way, we can say pretty confidently that * every mouse event is fake. There are two painful cases to consider - a * mousePress event could arrive before the tabletPress event, or it could * arrive much later, e.g. after tabletRelease. The first was only seen on Linux * with Qt's XInput2 code, the solution was to hold onto mousePress events * temporarily and wait for tabletPress later, this is contained in git history * but is now removed. The second case is currently handled by the * eatOneMousePress function, which waits as long as necessary to detect and * block a single mouse press event. */ static bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease || t == QEvent::MouseButtonDblClick); } +KisInputManager::Private::EventEater::EventEater() +{ + KisConfig cfg(true); + activateSecondaryButtonsWorkaround = cfg.readEntry("rightMiddleTabletButtonWorkaround", false); +} + bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) { Q_UNUSED(target) auto debugEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QMouseEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; + auto debugTabletEvent = [&](int i) { + if (KisTabletDebugger::instance()->debugEnabled()) { + QString pre = QString("[BLOCKED %1:]").arg(i); + QTabletEvent *ev = static_cast(event); + dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); + } + }; + if (peckish && event->type() == QEvent::MouseButtonPress // Drop one mouse press following tabletPress or touchBegin && (static_cast(event)->button() == Qt::LeftButton)) { peckish = false; debugEvent(1); return true; } - else if (isMouseEventType(event->type()) && + + if (activateSecondaryButtonsWorkaround) { + if (event->type() == QEvent::TabletPress || + event->type() == QEvent::TabletRelease) { + + QTabletEvent *te = static_cast(event); + if (te->button() != Qt::LeftButton) { + debugTabletEvent(3); + return true; + } + } else if (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::MouseButtonDblClick) { + + QMouseEvent *me = static_cast(event); + if (me->button() != Qt::LeftButton) { + return false; + } + } + } + + if (isMouseEventType(event->type()) && (hungry // On Mac, we need mouse events when the tablet is in proximity, but not pressed down // since tablet move events are not generated until after tablet press. #ifndef Q_OS_MAC || (eatSyntheticEvents && static_cast(event)->source() != Qt::MouseEventNotSynthesized) #endif )) { // Drop mouse events if enabled or event was synthetic & synthetic events are disabled debugEvent(2); return true; } return false; // All clear - let this one through! } void KisInputManager::Private::EventEater::activate() { if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Start blocking mouse events"; } hungry = true; } void KisInputManager::Private::EventEater::deactivate() { if (hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Stop blocking mouse events"; } hungry = false; } void KisInputManager::Private::EventEater::eatOneMousePress() { // Enable on other platforms if getting full-pressure splotches peckish = true; } bool KisInputManager::Private::ignoringQtCursorEvents() { return eventEater.hungry; } void KisInputManager::Private::setMaskSyntheticEvents(bool value) { eventEater.eatSyntheticEvents = value; } -void KisInputManager::Private::setTabletActive(bool value) -{ - tabletActive = value; -} - KisInputManager::Private::Private(KisInputManager *qq) : q(qq) , moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE) , priorityEventFilterSeqNo(0) , canvasSwitcher(this, qq) { KisConfig cfg(true); moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); if (cfg.trackTabletEventLatency()) { tabletLatencyTracker = new TabletLatencyTracker(); } matcher.setInputActionGroupsMaskCallback( [this] () { return this->canvas ? this->canvas->inputActionGroupsMask() : AllActionGroup; }); } static const int InputWidgetsThreshold = 2000; static const int OtherWidgetsThreshold = 400; KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p) : QObject(p), d(_d), eatOneMouseStroke(false), focusSwitchThreshold(InputWidgetsThreshold) { } void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object) { QWidget *widget = qobject_cast(object); KIS_SAFE_ASSERT_RECOVER_RETURN(widget); thresholdConnections.clear(); thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus())); } void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas) { if (!canvas) return; QObject *canvasWidget = canvas->canvasWidget(); if (!canvasResolver.contains(canvasWidget)) { canvasResolver.insert(canvasWidget, canvas); d->q->setupAsEventFilter(canvasWidget); canvasWidget->installEventFilter(this); setupFocusThreshold(canvasWidget); focusSwitchThreshold.setEnabled(false); d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); } else { KIS_ASSERT_RECOVER_RETURN(d->canvas == canvas); } } void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas) { QObject *widget = canvas->canvasWidget(); canvasResolver.remove(widget); if (d->eventsReceiver == widget) { d->q->setupAsEventFilter(0); } widget->removeEventFilter(this); } bool isInputWidget(QWidget *w) { if (!w) return false; QList types; types << QLatin1String("QAbstractSlider"); types << QLatin1String("QAbstractSpinBox"); types << QLatin1String("QLineEdit"); types << QLatin1String("QTextEdit"); types << QLatin1String("QPlainTextEdit"); types << QLatin1String("QComboBox"); types << QLatin1String("QKeySequenceEdit"); Q_FOREACH (const QLatin1String &type, types) { if (w->inherits(type.data())) { return true; } } return false; } bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event ) { if (canvasResolver.contains(object)) { switch (event->type()) { case QEvent::FocusIn: { QFocusEvent *fevent = static_cast(event); KisCanvas2 *canvas = canvasResolver.value(object); // only relevant canvases from the same main window should be // registered in the switcher KIS_SAFE_ASSERT_RECOVER_BREAK(canvas); if (canvas != d->canvas) { eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason); } d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); d->q->setupAsEventFilter(object); object->removeEventFilter(this); object->installEventFilter(this); setupFocusThreshold(object); focusSwitchThreshold.setEnabled(false); QEvent event(QEvent::Enter); d->q->eventFilter(object, &event); break; } case QEvent::FocusOut: { focusSwitchThreshold.setEnabled(true); break; } case QEvent::Enter: { break; } case QEvent::Leave: { focusSwitchThreshold.stop(); break; } case QEvent::Wheel: { QWidget *widget = static_cast(object); widget->setFocus(); break; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::TabletPress: case QEvent::TabletRelease: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { eatOneMouseStroke--; return true; } break; case QEvent::MouseButtonDblClick: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { return true; } break; case QEvent::MouseMove: case QEvent::TabletMove: { - QWidget *widget = static_cast(object); if (!widget->hasFocus()) { const int delay = isInputWidget(QApplication::focusWidget()) ? InputWidgetsThreshold : OtherWidgetsThreshold; focusSwitchThreshold.setDelayThreshold(delay); focusSwitchThreshold.start(); } } break; default: break; } } return QObject::eventFilter(object, event); } KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p) : QObject(p), d(_d) {} bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event ) { /** * All Qt builds in range 5.7.0...5.11.X on X11 had a problem that made all * the tablet events be accepted by default. It meant that no mouse * events were synthesized, and, therefore, no Enter/Leave were generated. * * The fix for this bug has been added only in Qt 5.12.0: * https://codereview.qt-project.org/#/c/239918/ * * To avoid this problem we should explicitly ignore all the tablet events. */ #if defined Q_OS_LINUX && \ QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) && \ QT_VERSION < QT_VERSION_CHECK(5, 12, 0) if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { event->ignore(); } #endif switch (event->type()) { case QEvent::TabletEnterProximity: d->debugEvent(event); // Tablet proximity events are unreliable AND fake mouse events do not // necessarily come after tablet events, so this is insufficient. // d->eventEater.eatOneMousePress(); // Qt sends fake mouse events instead of hover events, so not very useful. // Don't block mouse events on tablet since tablet move events are not generated until // after tablet press. #ifndef Q_OS_OSX d->blockMouseEvents(); #else // Notify input manager that tablet proximity is entered for Genius tablets. d->setTabletActive(true); #endif break; case QEvent::TabletLeaveProximity: d->debugEvent(event); d->allowMouseEvents(); #ifdef Q_OS_OSX d->setTabletActive(false); #endif break; default: break; } return QObject::eventFilter(object, event); } void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, Qt::MouseButtons buttons) { KisStrokeShortcut *strokeShortcut = new KisStrokeShortcut(action, index); QList buttonList; if(buttons & Qt::LeftButton) { buttonList << Qt::LeftButton; } if(buttons & Qt::RightButton) { buttonList << Qt::RightButton; } if(buttons & Qt::MidButton) { buttonList << Qt::MidButton; } if(buttons & Qt::XButton1) { buttonList << Qt::XButton1; } if(buttons & Qt::XButton2) { buttonList << Qt::XButton2; } if (buttonList.size() > 0) { strokeShortcut->setButtons(QSet::fromList(modifiers), QSet::fromList(buttonList)); matcher.addShortcut(strokeShortcut); } else { delete strokeShortcut; } } void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index, const QList &keys) { if (keys.size() == 0) return; KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); //Note: Ordering is important here, Shift + V is different from V + Shift, //which is the reason we use the last key here since most users will enter //shortcuts as "Shift + V". Ideally this should not happen, but this is //the way the shortcut matcher is currently implemented. QList allKeys = keys; Qt::Key key = allKeys.takeLast(); QSet modifiers = QSet::fromList(allKeys); keyShortcut->setKey(modifiers, key); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction) { QScopedPointer keyShortcut( new KisSingleActionShortcut(action, index)); KisSingleActionShortcut::WheelAction a; switch(wheelAction) { case KisShortcutConfiguration::WheelUp: a = KisSingleActionShortcut::WheelUp; break; case KisShortcutConfiguration::WheelDown: a = KisSingleActionShortcut::WheelDown; break; case KisShortcutConfiguration::WheelLeft: a = KisSingleActionShortcut::WheelLeft; break; case KisShortcutConfiguration::WheelRight: a = KisSingleActionShortcut::WheelRight; break; case KisShortcutConfiguration::WheelTrackpad: a = KisSingleActionShortcut::WheelTrackpad; break; default: return; } keyShortcut->setWheel(QSet::fromList(modifiers), a); matcher.addShortcut(keyShortcut.take()); } void KisInputManager::Private::addTouchShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { KisTouchShortcut *shortcut = new KisTouchShortcut(action, index); switch(gesture) { case KisShortcutConfiguration::PinchGesture: shortcut->setMinimumTouchPoints(2); shortcut->setMaximumTouchPoints(2); break; case KisShortcutConfiguration::PanGesture: shortcut->setMinimumTouchPoints(3); shortcut->setMaximumTouchPoints(10); break; default: break; } matcher.addShortcut(shortcut); } bool KisInputManager::Private::addNativeGestureShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { // Qt5 only implements QNativeGestureEvent for macOS Qt::NativeGestureType type; switch (gesture) { #ifdef Q_OS_OSX case KisShortcutConfiguration::PinchGesture: type = Qt::ZoomNativeGesture; break; case KisShortcutConfiguration::PanGesture: type = Qt::PanNativeGesture; break; case KisShortcutConfiguration::RotateGesture: type = Qt::RotateNativeGesture; break; case KisShortcutConfiguration::SmartZoomGesture: type = Qt::SmartZoomNativeGesture; break; #endif default: return false; } KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type); matcher.addShortcut(shortcut); return true; } void KisInputManager::Private::setupActions() { QList actions = KisInputProfileManager::instance()->actions(); Q_FOREACH (KisAbstractInputAction *action, actions) { KisToolInvocationAction *toolAction = dynamic_cast(action); if(toolAction) { defaultInputAction = toolAction; } } connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged())); if(KisInputProfileManager::instance()->currentProfile()) { q->profileChanged(); } } bool KisInputManager::Private::processUnhandledEvent(QEvent *event) { bool retval = false; if (forwardAllEventsToTool || event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { defaultInputAction->processUnhandledEvent(event); retval = true; } return retval && !forwardAllEventsToTool; } bool KisInputManager::Private::tryHidePopupPalette() { if (canvas->isPopupPaletteVisible()) { canvas->slotShowPopupPalette(); return true; } return false; } #ifdef HAVE_X11 inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y()); } inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y()); } #endif void KisInputManager::Private::blockMouseEvents() { eventEater.activate(); } void KisInputManager::Private::allowMouseEvents() { eventEater.deactivate(); } void KisInputManager::Private::eatOneMousePress() { eventEater.eatOneMousePress(); } void KisInputManager::Private::resetCompressor() { compressedMoveEvent.reset(); moveEventCompressor.stop(); } bool KisInputManager::Private::handleCompressedTabletEvent(QEvent *event) { bool retval = false; + /** + * When Krita (as an application) has no input focus, we cannot + * handle key events. But at the same time, when the user hovers + * Krita canvas, we should still show him the correct cursor. + * + * So here we just add a simple workaround to resync shortcut + * matcher's state at least against the basic modifiers, like + * Shift, Control and Alt. + */ + QWidget *recievingWidget = dynamic_cast(eventsReceiver); + if (recievingWidget && !recievingWidget->hasFocus()) { + QVector guessedKeys; + + KisExtendedModifiersMapper mapper; + Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); + Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { + QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); + guessedKeys << KisExtendedModifiersMapper::workaroundShiftAltMetaHell(&kevent); + } + + matcher.recoveryModifiersWithoutFocus(guessedKeys); + } + if (!matcher.pointerMoved(event) && toolProxy) { toolProxy->forwardHoverEvent(event); } retval = true; event->setAccepted(true); return retval; } qint64 KisInputManager::Private::TabletLatencyTracker::currentTimestamp() const { // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp, // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that // we compare against ourselves in QWindowSystemInterface. QElapsedTimer elapsed; elapsed.start(); return elapsed.msecsSinceReference(); } void KisInputManager::Private::TabletLatencyTracker::print(const QString &message) { dbgTablet << qUtf8Printable(message); } diff --git a/libs/ui/input/kis_input_manager_p.h b/libs/ui/input/kis_input_manager_p.h index f33e95c04d..b0ad408ec3 100644 --- a/libs/ui/input/kis_input_manager_p.h +++ b/libs/ui/input/kis_input_manager_p.h @@ -1,158 +1,159 @@ /* * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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 "kis_input_manager.h" #include "kis_shortcut_matcher.h" #include "kis_shortcut_configuration.h" #include "kis_canvas2.h" #include "kis_tool_proxy.h" #include "kis_signal_compressor.h" #include "input/kis_tablet_debugger.h" #include "kis_timed_signal_threshold.h" #include "kis_signal_auto_connection.h" #include "kis_latency_tracker.h" class KisToolInvocationAction; class KisInputManager::Private { public: Private(KisInputManager *qq); bool tryHidePopupPalette(); void addStrokeShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, Qt::MouseButtons buttons); void addKeyShortcut(KisAbstractInputAction* action, int index,const QList &keys); void addTouchShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture ); bool addNativeGestureShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture ); void addWheelShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction); bool processUnhandledEvent(QEvent *event); void setupActions(); bool handleCompressedTabletEvent(QEvent *event); KisInputManager *q; QPointer canvas; QPointer toolProxy; bool forwardAllEventsToTool = false; bool ignoringQtCursorEvents(); bool touchHasBlockedPressEvents = false; KisShortcutMatcher matcher; KisToolInvocationAction *defaultInputAction = 0; QObject *eventsReceiver = 0; KisSignalCompressor moveEventCompressor; QScopedPointer compressedMoveEvent; bool testingAcceptCompressedTabletEvents = false; bool testingCompressBrushEvents = false; - bool tabletActive = false; // Indicates whether or not tablet is in proximity typedef QPair > PriorityPair; typedef QList PriorityList; PriorityList priorityEventFilter; int priorityEventFilterSeqNo; void blockMouseEvents(); void allowMouseEvents(); void eatOneMousePress(); void setMaskSyntheticEvents(bool value); - void setTabletActive(bool value); void resetCompressor(); template void debugEvent(QEvent *event) { if (!KisTabletDebugger::instance()->debugEnabled()) return; QString msg1 = useBlocking && ignoringQtCursorEvents() ? "[BLOCKED] " : "[ ]"; Event *specificEvent = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*specificEvent, msg1); } class ProximityNotifier : public QObject { public: ProximityNotifier(Private *_d, QObject *p); bool eventFilter(QObject* object, QEvent* event ) override; private: KisInputManager::Private *d; }; class CanvasSwitcher : public QObject { public: CanvasSwitcher(Private *_d, QObject *p); void addCanvas(KisCanvas2 *canvas); void removeCanvas(KisCanvas2 *canvas); bool eventFilter(QObject* object, QEvent* event ) override; private: void setupFocusThreshold(QObject *object); private: KisInputManager::Private *d; QMap> canvasResolver; int eatOneMouseStroke; KisTimedSignalThreshold focusSwitchThreshold; KisSignalAutoConnectionsStore thresholdConnections; }; CanvasSwitcher canvasSwitcher; struct EventEater { + EventEater(); + bool eventFilter(QObject* target, QEvent* event); // This should be called after we're certain a tablet stroke has started. void activate(); // This should be called after a tablet stroke has ended. void deactivate(); // On Windows, we sometimes receive mouse events very late, so watch & wait. void eatOneMousePress(); bool hungry{false}; // Continue eating mouse strokes bool peckish{false}; // Eat a single mouse press event bool eatSyntheticEvents{false}; // Mask all synthetic events + bool activateSecondaryButtonsWorkaround{false}; // Use mouse events for right- and middle-clicks }; EventEater eventEater; bool containsPointer = false; int accumulatedScrollDelta = 0; class TabletLatencyTracker : public KisLatencyTracker { protected: virtual qint64 currentTimestamp() const; virtual void print(const QString &message); }; KisSharedPtr tabletLatencyTracker; }; diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp index 2805034642..0ba70e0db3 100644 --- a/libs/ui/input/kis_shortcut_matcher.cpp +++ b/libs/ui/input/kis_shortcut_matcher.cpp @@ -1,668 +1,690 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shortcut_matcher.h" #include #include #include #include "kis_assert.h" #include "kis_abstract_input_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" #ifdef DEBUG_MATCHER #include #define DEBUG_ACTION(text) dbgInput << __FUNCTION__ << "-" << text; #define DEBUG_SHORTCUT(text, shortcut) dbgInput << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name(); #define DEBUG_KEY(text) dbgInput << __FUNCTION__ << "-" << text << "keys:" << m_d->keys; #define DEBUG_BUTTON_ACTION(text, button) dbgInput << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys; #define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInput << __FUNCTION__ << "-" << text << "type:" << event->type();} #else #define DEBUG_ACTION(text) #define DEBUG_KEY(text) #define DEBUG_SHORTCUT(text, shortcut) #define DEBUG_BUTTON_ACTION(text, button) #define DEBUG_EVENT_ACTION(text, event) #endif class Q_DECL_HIDDEN KisShortcutMatcher::Private { public: Private() : runningShortcut(0) , readyShortcut(0) , touchShortcut(0) , nativeGestureShortcut(0) , actionGroupMask([] () { return AllActionGroup; }) , suppressAllActions(false) , cursorEntered(false) , usingTouch(false) , usingNativeGesture(false) {} ~Private() { qDeleteAll(singleActionShortcuts); qDeleteAll(strokeShortcuts); qDeleteAll(touchShortcuts); } QList singleActionShortcuts; QList strokeShortcuts; QList touchShortcuts; QList nativeGestureShortcuts; QSet keys; // Model of currently pressed keys QSet buttons; // Model of currently pressed buttons KisStrokeShortcut *runningShortcut; KisStrokeShortcut *readyShortcut; QList candidateShortcuts; KisTouchShortcut *touchShortcut; KisNativeGestureShortcut *nativeGestureShortcut; std::function actionGroupMask; bool suppressAllActions; bool cursorEntered; bool usingTouch; bool usingNativeGesture; inline bool actionsSuppressed() const { return suppressAllActions || !cursorEntered; } inline bool actionsSuppressedIgnoreFocus() const { return suppressAllActions; } inline bool isUsingTouch() const { return usingTouch || usingNativeGesture; } }; KisShortcutMatcher::KisShortcutMatcher() : m_d(new Private) {} KisShortcutMatcher::~KisShortcutMatcher() { delete m_d; } bool KisShortcutMatcher::hasRunningShortcut() const { return m_d->runningShortcut; } void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut) { m_d->singleActionShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut) { m_d->strokeShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut ) { m_d->touchShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisNativeGestureShortcut *shortcut) { m_d->nativeGestureShortcuts.append(shortcut); } bool KisShortcutMatcher::supportsHiResInputEvents() { return m_d->runningShortcut && m_d->runningShortcut->action() && m_d->runningShortcut->action()->supportsHiResInputEvents(); } bool KisShortcutMatcher::keyPressed(Qt::Key key) { bool retval = false; if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); } if (!m_d->runningShortcut) { retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys); } m_d->keys.insert(key); DEBUG_KEY("Pressed"); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key) { bool retval = false; if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); } if (!m_d->runningShortcut) { // Autorepeated key should not be included in the shortcut QSet filteredKeys = m_d->keys; filteredKeys.remove(key); retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys); } return retval; } bool KisShortcutMatcher::keyReleased(Qt::Key key) { if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed"); else m_d->keys.remove(key); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return false; } bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event) { DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); } if (!m_d->runningShortcut) { prepareReadyShortcuts(); retval = tryRunReadyShortcut(button, event); } m_d->buttons.insert(button); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event) { DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->runningShortcut && !m_d->readyShortcut) { retval = tryEndRunningShortcut(button, event); DEBUG_BUTTON_ACTION("ended", button); } if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed"); else m_d->buttons.remove(button); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { if (m_d->runningShortcut || m_d->isUsingTouch()) { DEBUG_ACTION("Wheel event canceled."); return false; } return tryRunWheelShortcut(wheelAction, event); } bool KisShortcutMatcher::pointerMoved(QEvent *event) { if (m_d->isUsingTouch() || !m_d->runningShortcut) { return false; } m_d->runningShortcut->action()->inputEvent(event); return true; } void KisShortcutMatcher::enterEvent() { m_d->cursorEntered = true; if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::leaveEvent() { m_d->cursorEntered = false; if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event ) { Q_UNUSED(event) return true; } bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event ) { bool retval = false; if (m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) { retval = tryEndTouchShortcut( event ); } if (!m_d->touchShortcut ) { retval = tryRunTouchShortcut( event ); } else { m_d->touchShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event ) { m_d->usingTouch = false; // we need to say we are done because qt will not send further event // we should try and end the shortcut too (it might be that there is none? (sketch)) if (tryEndTouchShortcut(event)) { return true; } return false; } bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event) { Q_UNUSED(event) return true; } bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event) { bool retval = false; if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) { retval = tryEndNativeGestureShortcut( event ); } if ( !m_d->nativeGestureShortcut ) { retval = tryRunNativeGestureShortcut( event ); } else { m_d->nativeGestureShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event) { Q_UNUSED(event) m_d->usingNativeGesture = false; return true; } Qt::MouseButtons listToFlags(const QList &list) { Qt::MouseButtons flags; Q_FOREACH (Qt::MouseButton b, list) { flags |= b; } return flags; } void KisShortcutMatcher::reinitialize() { reset("reinitialize"); if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } +void KisShortcutMatcher::recoveryModifiersWithoutFocus(const QVector &keys) +{ + Q_FOREACH (Qt::Key key, m_d->keys) { + if (!keys.contains(key)) { + keyReleased(key); + } + } + + Q_FOREACH (Qt::Key key, keys) { + if (!m_d->keys.contains(key)) { + keyPressed(key); + } + } + + if (!m_d->runningShortcut) { + prepareReadyShortcuts(); + tryActivateReadyShortcut(); + } + + DEBUG_ACTION("recoverySyncModifiers"); +} + void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos) { if (m_d->runningShortcut) { forceEndRunningShortcut(localPos); } } void KisShortcutMatcher::reset() { m_d->keys.clear(); m_d->buttons.clear(); DEBUG_ACTION("reset!"); } void KisShortcutMatcher::reset(QString msg) { m_d->keys.clear(); m_d->buttons.clear(); Q_UNUSED(msg); DEBUG_ACTION(msg); } void KisShortcutMatcher::suppressAllActions(bool value) { m_d->suppressAllActions = value; } void KisShortcutMatcher::clearShortcuts() { reset("Clearing shortcuts"); qDeleteAll(m_d->singleActionShortcuts); m_d->singleActionShortcuts.clear(); qDeleteAll(m_d->strokeShortcuts); qDeleteAll(m_d->touchShortcuts); m_d->strokeShortcuts.clear(); m_d->candidateShortcuts.clear(); m_d->touchShortcuts.clear(); m_d->runningShortcut = 0; m_d->readyShortcut = 0; } void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function func) { m_d->actionGroupMask = func; } bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys); } // Note: sometimes event can be zero!! template bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState) { if (m_d->actionsSuppressedIgnoreFocus()) { DEBUG_EVENT_ACTION("Event suppressed", event) return false; } KisSingleActionShortcut *goodCandidate = 0; Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) { if(s->isAvailable(m_d->actionGroupMask()) && s->match(keysState, param) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { DEBUG_EVENT_ACTION("Beginning action for event", event) goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); goodCandidate->action()->end(0); } else { DEBUG_EVENT_ACTION("Could not match a candidate for event", event) } return goodCandidate; } void KisShortcutMatcher::prepareReadyShortcuts() { m_d->candidateShortcuts.clear(); if (m_d->actionsSuppressed()) return; Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) { if (s->matchReady(m_d->keys, m_d->buttons)) { m_d->candidateShortcuts.append(s); } } } bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ) { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (s->isAvailable(m_d->actionGroupMask()) && s->matchBegin(button) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut) { if (m_d->readyShortcut != goodCandidate) { m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } m_d->readyShortcut = 0; } else { DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } DEBUG_SHORTCUT("Starting new action", goodCandidate); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->runningShortcut = goodCandidate; } return goodCandidate; } void KisShortcutMatcher::tryActivateReadyShortcut() { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (!goodCandidate || s->priority() > goodCandidate->priority()) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) { DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } if (!m_d->readyShortcut) { DEBUG_SHORTCUT("Preparing new ready action", goodCandidate); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); m_d->readyShortcut = goodCandidate; } } else if (m_d->readyShortcut) { DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } } bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ) { Q_ASSERT(m_d->runningShortcut); Q_ASSERT(!m_d->readyShortcut); if (m_d->runningShortcut->matchBegin(button)) { // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_EVENT_ACTION("Ending running shortcut at event", event); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); action->end(event); action->deactivate(shortcutIndex); } } return !m_d->runningShortcut; } void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut); KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->readyShortcut); // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_ACTION("Forced ending running shortcut at event"); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); QMouseEvent event = runningShortcut->fakeEndEvent(localPos); action->end(&event); action->deactivate(shortcutIndex); } } bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event ) { KisTouchShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) { if (shortcut->isAvailable(m_d->actionGroupMask()) && shortcut->match( event ) && (!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) { goodCandidate = shortcut; } } if( goodCandidate ) { if( m_d->runningShortcut ) { QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->touchPoints().at(0).pos().toPoint(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); tryEndRunningShortcut(Qt::LeftButton, &mouseEvent); } goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->touchShortcut = goodCandidate; m_d->usingTouch = true; } return goodCandidate; } bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event ) { if(m_d->touchShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisTouchShortcut *touchShortcut = m_d->touchShortcut; touchShortcut->action()->end(event); touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex()); m_d->touchShortcut = 0; // empty it out now that we are done with it return true; } return false; } bool KisShortcutMatcher::tryRunNativeGestureShortcut(QNativeGestureEvent* event) { KisNativeGestureShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) { if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) { goodCandidate = shortcut; } } if (goodCandidate) { goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->nativeGestureShortcut = goodCandidate; m_d->usingNativeGesture = true; return true; } return false; } bool KisShortcutMatcher::tryEndNativeGestureShortcut(QNativeGestureEvent* event) { if (m_d->nativeGestureShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut; nativeGestureShortcut->action()->end(event); nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex()); m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it return true; } return false; } diff --git a/libs/ui/input/kis_shortcut_matcher.h b/libs/ui/input/kis_shortcut_matcher.h index 7a4d5d923c..58611cb093 100644 --- a/libs/ui/input/kis_shortcut_matcher.h +++ b/libs/ui/input/kis_shortcut_matcher.h @@ -1,254 +1,264 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SHORTCUT_MATCHER_H #define __KIS_SHORTCUT_MATCHER_H #include #include "kis_single_action_shortcut.h" #include "KisInputActionGroup.h" #include class QEvent; class QWheelEvent; class QTouchEvent; class QNativeGestureEvent; class QString; class QPointF; class KisStrokeShortcut; class KisTouchShortcut; class KisNativeGestureShortcut; /** * The class that manages connections between shortcuts and actions. * * It processes input events and generates state transitions for the * actions basing on the data, represented by the shortcuts. * * The class works with two types of actions: long running * (represented by KisStrokeShortcuts) and "atomic" * (KisSingleActionShortcut). The former one invole some long * interaction with the user by means of a mouse cursor or a tablet, * the latter one simple action like "Zoom 100%" or "Reset Rotation". * * The single action shortcuts are handled quite easily. The matcher * listens to the events coming, manages two lists of the pressed keys * and buttons and when their content corresponds to some single * action shortcut it just runs this shortcut once. * * The strategy for handling the stroke shortcuts is a bit more * complex. Each such action may be in one of the three states: * * Idle <-> Ready <-> Running * * In "Idle" state the action is completely inactive and has no access * to the user * * When the action is in "Ready" state, it means that all the * modifiers for the action are already pressed and we are only * waiting for a user to press the mouse button and start a stroke. In * this state the action can show the user its Cursor to notify the user * what is going to happen next. * * In the "Running" state, the action has full access to the user * input and is considered to perform all the work it was created for. * * To implement such state transitions for the actions, * KisShortcutMatcher first forms a list of the actions which can be * moved to a ready state (m_d->readyShortcuts), then chooses the one * with the highest priority to be the only shortcut in the "Ready" * state and activates it (m_d->readyShortcut). Then when the user * presses the mouse button, the matcher looks through the list of * ready shortcuts, chooses which will be running now, deactivates (if * needed) currently activated action and starts the chosen one. * * \see KisSingleActionShortcut * \see KisStrokeShortcut */ class KRITAUI_EXPORT KisShortcutMatcher { public: KisShortcutMatcher(); ~KisShortcutMatcher(); bool hasRunningShortcut() const; void addShortcut(KisSingleActionShortcut *shortcut); void addShortcut(KisStrokeShortcut *shortcut); void addShortcut(KisTouchShortcut *shortcut); void addShortcut(KisNativeGestureShortcut *shortcut); /** * Returns true if the currently running shortcut supports * processing hi resolution flow of events from the tablet * device. In most of the cases (except of the painting itself) * too many events make the execution of the action too slow, so * the action can decide whether it needs it. */ bool supportsHiResInputEvents(); /** * Handles a key press event. * No autorepeat events should be passed to this method. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool keyPressed(Qt::Key key); /** * Handles a key press event that has been generated by the * autorepeat. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool autoRepeatedKeyPressed(Qt::Key key); /** * Handles a key release event. * No autorepeat events should be passed to this method. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool keyReleased(Qt::Key key); /** * Handles button presses from a tablet or mouse. * * \param event the event that caused this call. * Must be of type QTabletEvent or QMouseEvent. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool buttonPressed(Qt::MouseButton button, QEvent *event); /** * Handles the mouse button release event * * \param event the event that caused this call. * Must be of type QTabletEvent or QMouseEvent. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool buttonReleased(Qt::MouseButton button, QEvent *event); /** * Handles the mouse wheel event * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event); /** * Handles tablet and mouse move events. * * \param event the event that caused this call * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool pointerMoved(QEvent *event); /** * Handle cursor's Enter event. * We never eat it because it might be used by someone else */ void enterEvent(); /** * Handle cursor's Leave event. * We never eat it because it might be used by someone else */ void leaveEvent(); bool touchBeginEvent(QTouchEvent *event); bool touchUpdateEvent(QTouchEvent *event); bool touchEndEvent(QTouchEvent *event); bool nativeGestureBeginEvent(QNativeGestureEvent *event); bool nativeGestureEvent(QNativeGestureEvent *event); bool nativeGestureEndEvent(QNativeGestureEvent *event); /** * Resets the internal state of the matcher and activates the * prepared action if possible. * * This should be done when the window has lost the focus for * some time, so that several events could be lost */ void reinitialize(); + /** + * Resets the internal state of the matcher, tries to resync it to the state + * passed via argument and activates the prepared action if possible. + * + * This synchronization should happen when the user hovers Krita windows, + * **without** having keyboard focus set to it (therefore matcher cannot + * get key press and release events). + */ + void recoveryModifiersWithoutFocus(const QVector &keys); + /** * Kirta lost focus, it means that all the running actions should be ended * forcefully. */ void lostFocusEvent(const QPointF &localPos); /** * Disables the start of any actions. * * WARNING: the actions that has been started before this call * will *not* be ended. They will be ended in their usual way, * when the mouse button will be released. */ void suppressAllActions(bool value); /** * Remove all shortcuts that have been registered. */ void clearShortcuts(); void setInputActionGroupsMaskCallback(std::function func); private: friend class KisInputManagerTest; void reset(); void reset(QString msg); bool tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event); template bool tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState); void prepareReadyShortcuts(); bool tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ); void tryActivateReadyShortcut(); bool tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ); void forceEndRunningShortcut(const QPointF &localPos); bool tryRunTouchShortcut(QTouchEvent *event); bool tryEndTouchShortcut(QTouchEvent *event); bool tryRunNativeGestureShortcut(QNativeGestureEvent *event); bool tryEndNativeGestureShortcut(QNativeGestureEvent *event); private: class Private; Private * const m_d; }; #endif /* __KIS_SHORTCUT_MATCHER_H */ diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index 6cee38a82a..d14aca9ee3 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,986 +1,994 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_canvas2.h" #include "kis_config.h" #include "kis_popup_palette.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include "KisResourceServerProvider.h" #include #include #include #include #include #include #include #include #include #include "kis_signal_compressor.h" #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include "kis_signals_blocker.h" #include "kis_canvas_controller.h" #include "kis_acyclic_signal_connector.h" class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) , m_dragging(false) { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; // this will tell the pop-up palette widget to close if(event->button() == Qt::RightButton) { emit requestCloseContainer(); } // ignore any tablet events that are done with the right click // Tablet move events don't return a "button", so catch that too if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove) { switch (event->type()) { case QEvent::TabletPress: mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = true; mousePressEvent(mouseEvent); break; case QEvent::TabletMove: mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), (m_dragging) ? Qt::LeftButton : Qt::NoButton, (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); mouseMoveEvent(mouseEvent); break; case QEvent::TabletRelease: mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = false; mouseReleaseEvent(mouseEvent); break; default: break; } } delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_coordinatesConverter(coordinatesConverter) , m_viewManager(viewManager) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_acyclicConnector(new KisAcyclicSignalConnector(this)) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig(true).readEntry("popuppalette/usevisualcolorselector", false)) { m_triangleColorSelector = new KisVisualColorSelector(this); } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), this, SLOT(slotExternalFgColorChanged(KoColor))); m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); // This is used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible m_timer.setSingleShot(true); connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setFixedHeight(int(m_popupPaletteSize)); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg(true); m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setFixedSize(35, 35); mirrorMode->setToolTip(i18n("Mirror Canvas")); mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas")); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setToolTip(i18n("Canvas Only")); canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only")); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomSliderMinValue = 10; // set in % zoomSliderMaxValue = 200; // set in % zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed())); connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased())); slotUpdateIcons(); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); opacityChange = new QGraphicsOpacityEffect(this); setGraphicsEffect(opacityChange); // Prevent tablet events from being captured by the canvas setAttribute(Qt::WA_NoMousePropagation, true); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { //hack to get around cmyk for now. if (color.colorSpace()->colorChannelCount()>3) { KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromKoColor(color); m_triangleColorSelector->slotSetColor(c); } else { m_triangleColorSelector->slotSetColor(color); } } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotTriggerTimer() { m_timer.start(750); } void KisPopupPalette::slotEnableChangeFGColor() { emit sigEnableChangeFGColor(true); } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::slotZoomSliderPressed() { m_isZoomingCanvas = true; } void KisPopupPalette::slotZoomSliderReleased() { m_isZoomingCanvas = false; } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::slotUpdateIcons() { + this->setPalette(qApp->palette()); + + for(int i=0; ichildren().size(); i++) { + QWidget *w = qobject_cast(this->children().at(i)); + if (w) { + w->setPalette(qApp->palette()); + } + } zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon()); m_brushHud->updateIcons(); m_settingsButton->setIcon(KisIconUtils::loadIcon("tag")); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg(false); cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { m_hadMousePressSinceOpening = false; m_timeSinceOpening.start(); // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within // the bounds and cause the canvas to jump between the slider's min and max if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ KisSignalsBlocker b(zoomCanvasSlider); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider } emit sigEnableChangeFGColor(!show); } else { emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); // painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); // painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); // painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); // painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } // painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } // painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) { opacityChange->setOpacity(0.4); } else { opacityChange->setOpacity(1.0); } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; float canvasIndicatorMiddle = canvasIndicatorSize/2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name()); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); #ifdef Q_OS_WIN const int tableMouseEventsFlowDelay = 500; #else const int tableMouseEventsFlowDelay = 100; #endif /** * Tablet support code generates a spurious right-click right after opening * the window, so we should ignore it. Next right-click will be used for * closing the popup palette */ if (!m_hadMousePressSinceOpening && m_timeSinceOpening.elapsed() > tableMouseEventsFlowDelay) { m_hadMousePressSinceOpening = true; } if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QStringList tags = rServer->tagNamesList(); std::sort(tags.begin(), tags.end()); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction *action = menu.exec(QCursor::pos()); if (action) { m_resourceManager->setCurrentTag(action->text()); } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction *action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent *event) { event->ignore(); } void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); // see a comment in KisPopupPalette::mousePressEvent if (m_hadMousePressSinceOpening && event->buttons() == Qt::NoButton && event->button() == Qt::RightButton) { showPopupPalette(false); return; } m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal radians = qDegreesToRadians((360.0/10)/2); qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2; radians = qDegreesToRadians(angleSlice/2); qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)); //If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking. qreal length = m_colorHistoryOuterRadius + presetRadius; // can we can fit in a second row? We don't want the preset icons to get too tiny. if (maxRadius > presetRadius) { //redo all calculations assuming a second row. if (numSlots() % 2) { angleSlice = 360.0/(numSlots()+1); startingAngle = -(index * angleSlice) + 90; } if (numSlots() != m_cachedNumSlots){ qreal tempRadius = presetRadius; qreal distance = 0; do{ tempRadius+=0.1; // Calculate the XY of two adjectant circles using this tempRadius. qreal length1 = m_colorHistoryOuterRadius + tempRadius; qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius); qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius; qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius; qreal startingAngle2 = -(index+1 * angleSlice) + 90; qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius; qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius; // Use Pythagorean Theorem to calculate the distance between these two values. qreal m1 = pathX2-pathX1; qreal m2 = pathY2-pathY1; distance = sqrt((m1*m1)+(m2*m2)); } //As long at there's more distance than the radius of the two presets, continue increasing the radius. while((tempRadius+1)*2 < distance); m_cachedRadius = tempRadius; } m_cachedNumSlots = numSlots(); presetRadius = m_cachedRadius; length = m_colorHistoryOuterRadius + presetRadius; if (index % 2) { length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius); } } QPainterPath path; qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config(true); return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/layerstyles/wdglayerstyles.ui b/libs/ui/layerstyles/wdglayerstyles.ui index 55bab992ee..4812a41b93 100644 --- a/libs/ui/layerstyles/wdglayerstyles.ui +++ b/libs/ui/layerstyles/wdglayerstyles.ui @@ -1,203 +1,209 @@ WdgStylesDialog 0 0 - 473 - 335 + 1607 + 924 Enable Effects true Styles Blending Options Drop Shadow Unchecked Inner Shadow Unchecked Outer Glow Unchecked Inner Glow Unchecked Bevel and Emboss Unchecked Contour Unchecked + + ItemIsSelectable|ItemIsDragEnabled|ItemIsUserCheckable + Texture Unchecked + + ItemIsSelectable|ItemIsDragEnabled|ItemIsUserCheckable + Satin Unchecked Color Overlay Unchecked Gradient Overlay Unchecked Pattern Overlay Unchecked Stroke Unchecked Ne&w... &Import... &Export... Pre&view 0 0 Qt::Vertical 20 40 diff --git a/libs/ui/opengl/KisScreenInformationAdapter.cpp b/libs/ui/opengl/KisScreenInformationAdapter.cpp index 8efb71cb27..236d42bb92 100644 --- a/libs/ui/opengl/KisScreenInformationAdapter.cpp +++ b/libs/ui/opengl/KisScreenInformationAdapter.cpp @@ -1,306 +1,297 @@ /* * Copyright (c) 2019 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 "KisScreenInformationAdapter.h" #include "kis_debug.h" #include #include #include #include -#ifdef Q_OS_WIN -#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 2)) -#include -#elif (QT_VERSION == QT_VERSION_CHECK(5, 12, 0)) -#include -#elif (QT_VERSION == QT_VERSION_CHECK(5, 12, 1)) -#include -#elif (QT_VERSION == QT_VERSION_CHECK(5, 12, 2)) -#include -#elif (QT_VERSION == QT_VERSION_CHECK(5, 13, 0)) -#include -#endif + +#ifdef Q_OS_WIN +#include #include #include #include #include "EGL/egl.h" #include "EGL/eglext.h" #endif namespace { struct EGLException { EGLException() {} EGLException(const QString &what) : m_what(what) {} QString what() const { return m_what; } private: QString m_what; }; template void getProcAddressSafe(QOpenGLContext *context, const char *funcName, FuncType &func) { func = reinterpret_cast(context->getProcAddress(funcName)); if (!func) { throw EGLException(QString("failed to fetch function %1").arg(funcName)); } } #ifdef Q_OS_WIN typedef const char *(EGLAPIENTRYP PFNEGLQUERYSTRINGPROC) (EGLDisplay dpy, EGLint name); #endif } struct KisScreenInformationAdapter::Private { void initialize(QOpenGLContext *context); QOpenGLContext *context; QString errorString; #ifdef Q_OS_WIN Microsoft::WRL::ComPtr dxgiAdapter; #endif }; KisScreenInformationAdapter::KisScreenInformationAdapter(QOpenGLContext *context) : m_d(new Private) { m_d->initialize(context); } KisScreenInformationAdapter::~KisScreenInformationAdapter() { } void KisScreenInformationAdapter::Private::initialize(QOpenGLContext *newContext) { context = newContext; errorString.clear(); try { #if defined Q_OS_WIN && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (!context->isOpenGLES()) { throw EGLException("the context is not OpenGL ES"); } PFNEGLQUERYSTRINGPROC queryString = nullptr; getProcAddressSafe(context, "eglQueryString", queryString); const char* client_extensions = queryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); const QList extensions = QByteArray(client_extensions).split(' '); if (!extensions.contains("EGL_ANGLE_platform_angle_d3d") || !extensions.contains("EGL_ANGLE_device_creation_d3d11")) { throw EGLException("the context is not Angle + D3D11"); } PFNEGLQUERYDISPLAYATTRIBEXTPROC queryDisplayAttribEXT = nullptr; PFNEGLQUERYDEVICEATTRIBEXTPROC queryDeviceAttribEXT = nullptr; getProcAddressSafe(context, "eglQueryDisplayAttribEXT", queryDisplayAttribEXT); getProcAddressSafe(context, "eglQueryDeviceAttribEXT", queryDeviceAttribEXT); QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface(); EGLDisplay display = reinterpret_cast(nativeInterface->nativeResourceForContext("egldisplay", context)); if (!display) { throw EGLException( QString("couldn't request EGLDisplay handle, display = 0x%1").arg(uintptr_t(display), 0, 16)); } EGLAttrib value = 0; EGLBoolean result = false; result = queryDisplayAttribEXT(display, EGL_DEVICE_EXT, &value); if (!result || value == EGL_NONE) { throw EGLException( QString("couldn't request EGLDeviceEXT handle, result = 0x%1, value = 0x%2") .arg(result, 0, 16).arg(value, 0, 16)); } EGLDeviceEXT device = reinterpret_cast(value); result = queryDeviceAttribEXT(device, EGL_D3D11_DEVICE_ANGLE, &value); if (!result || value == EGL_NONE) { throw EGLException( QString("couldn't request ID3D11Device pointer, result = 0x%1, value = 0x%2") .arg(result, 0, 16).arg(value, 0, 16)); } ID3D11Device *deviceD3D = reinterpret_cast(value); { HRESULT result = 0; Microsoft::WRL::ComPtr dxgiDevice; result = deviceD3D->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice); if (FAILED(result)) { throw EGLException( QString("couldn't request IDXGIDevice pointer, result = 0x%1").arg(result, 0, 16)); } Microsoft::WRL::ComPtr dxgiAdapter; result = dxgiDevice->GetParent(__uuidof(IDXGIAdapter1), (void**)&dxgiAdapter); if (FAILED(result)) { throw EGLException( QString("couldn't request IDXGIAdapter1 pointer, result = 0x%1").arg(result, 0, 16)); } this->dxgiAdapter = dxgiAdapter; } #else throw EGLException("current platform doesn't support fetching display information"); #endif } catch (EGLException &e) { this->context = 0; this->errorString = e.what(); #ifdef Q_OS_WIN this->dxgiAdapter.Reset(); #endif } } bool KisScreenInformationAdapter::isValid() const { #ifdef Q_OS_WIN return m_d->context && m_d->dxgiAdapter; #else return false; #endif } QString KisScreenInformationAdapter::errorString() const { return m_d->errorString; } KisScreenInformationAdapter::ScreenInfo KisScreenInformationAdapter::infoForScreen(QScreen *screen) const { ScreenInfo info; #if defined Q_OS_WIN && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface(); HMONITOR monitor = reinterpret_cast(nativeInterface->nativeResourceForScreen("handle", screen)); if (!monitor) { qWarning("%s: failed to get HMONITOR handle for screen: screen = 0x%X, monitor = 0x%X", __PRETTY_FUNCTION__, screen, monitor); } UINT i = 0; Microsoft::WRL::ComPtr currentOutput; while (m_d->dxgiAdapter->EnumOutputs(i, ¤tOutput) != DXGI_ERROR_NOT_FOUND) { HRESULT result = 0; Microsoft::WRL::ComPtr output6; result = currentOutput.As(&output6); if (output6) { DXGI_OUTPUT_DESC1 desc; result = output6->GetDesc1(&desc); if (desc.Monitor == monitor) { info.screen = screen; info.bitsPerColor = desc.BitsPerColor; info.redPrimary[0] = desc.RedPrimary[0]; info.redPrimary[1] = desc.RedPrimary[1]; info.greenPrimary[0] = desc.GreenPrimary[0]; info.greenPrimary[1] = desc.GreenPrimary[1]; info.bluePrimary[0] = desc.BluePrimary[0]; info.bluePrimary[1] = desc.BluePrimary[1]; info.whitePoint[0] = desc.WhitePoint[0]; info.whitePoint[1] = desc.WhitePoint[1]; info.minLuminance = desc.MinLuminance; info.maxLuminance = desc.MaxLuminance; info.maxFullFrameLuminance = desc.MaxFullFrameLuminance; info.colorSpace = KisSurfaceColorSpace::DefaultColorSpace; if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) { info.colorSpace = KisSurfaceColorSpace::sRGBColorSpace; } else if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) { #ifdef HAVE_HDR info.colorSpace = KisSurfaceColorSpace::scRGBColorSpace; #else qWarning("WARNING: scRGB display color space is not supported by Qt's build"); #endif } else if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) { #ifdef HAVE_HDR info.colorSpace = KisSurfaceColorSpace::bt2020PQColorSpace; #else qWarning("WARNING: bt2020-pq display color space is not supported by Qt's build"); #endif } else { qWarning("WARNING: unknown display color space! 0x%X", desc.ColorSpace); } break; } } i++; } #endif Q_UNUSED(screen); return info; } QDebug operator<<(QDebug dbg, const KisScreenInformationAdapter::ScreenInfo &info) { QDebugStateSaver saver(dbg); if (info.isValid()) { dbg.nospace() << "ScreenInfo(" << "screen " << info.screen << ", bitsPerColor " << info.bitsPerColor << ", colorSpace " << info.colorSpace << ", redPrimary " << "(" << info.redPrimary[0] << ", " << info.redPrimary[1] << ")" << ", greenPrimary " << "(" << info.greenPrimary[0] << ", " << info.greenPrimary[1] << ")" << ", bluePrimary " << "(" << info.bluePrimary[0] << ", " << info.bluePrimary[1] << ")" << ", whitePoint " << "(" << info.whitePoint[0] << ", " << info.whitePoint[1] << ")" << ", minLuminance " << info.minLuminance << ", maxLuminance " << info.maxLuminance << ", maxFullFrameLuminance " << info.maxFullFrameLuminance << ')'; } else { dbg.nospace() << "ScreenInfo()"; } return dbg; } diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 55d0b72fd8..b3f58a8ada 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,1012 +1,1046 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #if !defined(Q_OS_OSX) && !defined(HAS_ONLY_OPENGL_ES) #include #endif #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete solidColorShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; bool displayShaderCompiledWithDisplayFilterSupport{false}; GLfloat checkSizeScale; bool scrollCheckers; QSharedPointer displayFilter; KisOpenGL::FilterMode filterMode; bool proofingConfigIsUpdated=false; GLsync glSyncObject{0}; bool wrapAroundMode{false}; // Stores a quad for drawing the canvas QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; // Stores data for drawing tool outlines QOpenGLVertexArrayObject outlineVAO; QOpenGLBuffer lineBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; #if !defined(Q_OS_OSX) && !defined(HAS_ONLY_OPENGL_ES) QOpenGLFunctions_2_1 *glFn201; #endif qreal pixelGridDrawingThreshold; bool pixelGridEnabled; QColor gridColor; QColor cursorColor; bool lodSwitchInProgress = false; int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { KisConfig cfg(false); cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_OSX setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, false); setAttribute(Qt::WA_DontCreateNativeAncestors, true); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // we should make sure the texture doesn't have alpha channel, // otherwise blending will not work correctly. if (KisOpenGLModeProber::instance()->useHDRMode()) { setTextureFormat(GL_RGBA16F); } else { /** * When in pure OpenGL mode, the canvas surface will have alpha * channel. Therefore, if our canvas blending algorithm produces * semi-transparent pixels (and it does), then Krita window itself * will become transparent. Which is not good. * * In Angle mode, GL_RGB8 is not available (and the transparence effect * doesn't exist at all). */ if (!KisOpenGL::hasOpenGLES()) { setTextureFormat(GL_RGB8); } } #endif setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotPixelGridModeChanged())); slotConfigChanged(); slotPixelGridModeChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::notifyImageColorSpaceChanged(const KoColorSpace *cs) { // FIXME: on color space change the data is refetched multiple // times by different actors! if (d->openGLImageTextures->setImageColorSpace(cs)) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } 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()); } void KisOpenGLCanvas2::initializeGL() { KisOpenGL::initializeContext(context()); initializeOpenGLFunctions(); #if !defined(Q_OS_OSX) && !defined(HAS_ONLY_OPENGL_ES) if (!KisOpenGL::hasOpenGLES()) { d->glFn201 = context()->versionFunctions(); if (!d->glFn201) { warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used"; } } else { d->glFn201 = nullptr; } #endif KisConfig cfg(true); d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeShaders(); // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { d->quadVAO.create(); d->quadVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // Create the vertex buffer object, it has 6 vertices with 3 components d->quadBuffers[0].create(); d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[0].bind(); d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create the texture buffer object, it has 6 texture coordinates with 2 components d->quadBuffers[1].create(); d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[1].bind(); d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data d->outlineVAO.create(); d->outlineVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly d->lineBuffer.create(); d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); delete d->checkerShader; delete d->solidColorShader; d->checkerShader = 0; d->solidColorShader = 0; try { d->checkerShader = d->shaderLoader.loadCheckerShader(); d->solidColorShader = d->shaderLoader.loadSolidColorShader(); } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } initializeDisplayShader(); } void KisOpenGLCanvas2::initializeDisplayShader() { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; delete d->displayShader; d->displayShader = 0; try { d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter; } catch (const ShaderLoaderException &e) { reportFailedShaderCompilation(e.what()); } } /** * Displays a message box telling the user that * shader compilation failed and turns off OpenGL. */ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context) { KisConfig cfg(false); qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.", context), QMessageBox::Close); cfg.setUseOpenGL(false); cfg.setCanvasState("OPENGL_FAILED"); } -void KisOpenGLCanvas2::resizeGL(int width, int height) +void KisOpenGLCanvas2::resizeGL(int /*width*/, int /*height*/) { - coordinatesConverter()->setCanvasWidgetSize(QSize(width, height)); + // The given size is the widget size but here we actually want to give + // KisCoordinatesConverter the viewport size aligned to device pixels. + coordinatesConverter()->setCanvasWidgetSize(widgetSizeAlignedToDevicePixel()); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); renderCanvasGL(); if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg(false); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->solidColorShader->bind()) { return; } + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); + // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); #ifndef Q_OS_OSX if (d->glFn201) { d->glFn201->glLogicOp(GL_XOR); } #else glLogicOp(GL_XOR); #endif // Q_OS_OSX #else // HAS_ONLY_OPENGL_ES KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif // HAS_ONLY_OPENGL_ES } else { glEnable(GL_BLEND); glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE, GL_ONE); } d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->cursorColor.redF(), d->cursorColor.greenF(), d->cursorColor.blueF(), 1.0f)); // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); for (int i = 0; i < subPathPolygons.size(); i++) { const QPolygonF& polygon = subPathPolygons.at(i); QVector vertices; vertices.resize(polygon.count()); for (int j = 0; j < polygon.count(); j++) { QPointF p = polygon.at(j); vertices[j].setX(p.x()); vertices[j].setY(p.y()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); } glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } if (!KisOpenGL::hasOpenGLES()) { #ifndef HAS_ONLY_OPENGL_ES glDisable(GL_COLOR_LOGIC_OP); #else KIS_ASSERT_X(false, "KisOpenGLCanvas2::paintToolOutline", "Unexpected KisOpenGL::hasOpenGLES returned false"); #endif } else { glDisable(GL_BLEND); } d->solidColorShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::setLodResetInProgress(bool value) { d->lodSwitchInProgress = value; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : - converter->widgetToViewport(this->rect()); + converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height())); if (!canvas()->renderingLimit().isEmpty()) { const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect(); viewportRect &= vrect; } converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); if (!d->checkerShader->bind()) { qWarning() << "Could not bind checker shader"; return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::drawGrid() { if (!d->solidColorShader->bind()) { return; } + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); + QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(d->gridColor.redF(), d->gridColor.greenF(), d->gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } - QRectF widgetRect(0,0, width(), height()); + QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { wr &= d->openGLImageTextures->storedImageBounds(); } QPoint topLeftCorner = wr.topLeft(); QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1); QVector grid; for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) { grid.append(QVector3D(i, topLeftCorner.y(), 0)); grid.append(QVector3D(i, bottomRightCorner.y(), 0)); } for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) { grid.append(QVector3D(topLeftCorner.x(), i, 0)); grid.append(QVector3D(bottomRightCorner.x(), i, 0)); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData()); } glDrawArrays(GL_LINES, 0, grid.size()); if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); glDisable(GL_BLEND); } void KisOpenGLCanvas2::drawImage() { if (!d->displayShader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisCoordinatesConverter *converter = coordinatesConverter(); d->displayShader->bind(); + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); + QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); - QRectF widgetRect(0,0, width(), height()); + QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); const QRect renderingLimit = canvas()->renderingLimit(); if (!renderingLimit.isEmpty()) { widgetRectInImagePixels &= renderingLimit; } qreal scaleX, scaleY; converter->imagePhysicalScale(&scaleX, &scaleY); d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest wr &= ir; } int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); int minColumn = d->openGLImageTextures->xToCol(ir.left()); int maxColumn = d->openGLImageTextures->xToCol(ir.right()); int minRow = d->openGLImageTextures->yToRow(ir.top()); int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); if (!tile) { warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; continue; } /* * We create a float rect here to workaround Qt's * "history reasons" in calculation of right() * and bottom() coordinates of integer rects. */ QRectF textureRect; QRectF modelRect; if (renderingLimit.isEmpty()) { textureRect = tile->tileRectInTexturePixels(); modelRect = tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } else { const QRect limitedTileRect = tile->tileRectInImagePixels() & renderingLimit; textureRect = tile->imageRectInTexturePixels(limitedTileRect); modelRect = limitedTileRect.translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()); } //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } if (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); } glActiveTexture(GL_TEXTURE0); const int currentLodPlane = tile->bindToActiveTexture(d->lodSwitchInProgress); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch(d->filterMode) { case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; case KisOpenGL::HighQualityFiltering: if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisable(GL_BLEND); } +QSize KisOpenGLCanvas2::viewportDevicePixelSize() const +{ + // This is how QOpenGLCanvas sets the FBO and the viewport size. If + // devicePixelRatioF() is non-integral, the result is truncated. + int viewportWidth = static_cast(width() * devicePixelRatioF()); + int viewportHeight = static_cast(height() * devicePixelRatioF()); + return QSize(viewportWidth, viewportHeight); +} + +QSizeF KisOpenGLCanvas2::widgetSizeAlignedToDevicePixel() const +{ + QSize viewportSize = viewportDevicePixelSize(); + qreal scaledWidth = viewportSize.width() / devicePixelRatioF(); + qreal scaledHeight = viewportSize.height() / devicePixelRatioF(); + return QSizeF(scaledWidth, scaledHeight); +} + void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg(true); d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); d->cursorColor = cfg.getCursorMainColor(); notifyConfigChanged(); } void KisOpenGLCanvas2::slotPixelGridModeChanged() { KisConfig cfg(true); d->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold(); d->pixelGridEnabled = cfg.pixelGridEnabled(); d->gridColor = cfg.getPixelGridColor(); update(); } QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } void KisOpenGLCanvas2::renderCanvasGL() { { // Draw the border (that is, clear the whole widget to the border color) QColor widgetBackgroundColor = borderColor(); KoColor convertedBackgroudColor = canvas()->displayColorConverter()->applyDisplayFiltering( KoColor(widgetBackgroundColor, KoColorSpaceRegistry::instance()->rgb8()), Float32BitsColorDepthID); const float *pixel = reinterpret_cast(convertedBackgroudColor.data()); glClearColor(pixel[0], pixel[1], pixel[2], 1.0); } glClear(GL_COLOR_BUFFER_BIT); if ((d->displayFilter && d->displayFilter->updateShader()) || (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized); d->canvasInitialized = false; // TODO: check if actually needed? initializeDisplayShader(); d->canvasInitialized = true; } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); if ((coordinatesConverter()->effectiveZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayColorConverter(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->openGLCanvasSurfaceProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); if (canvas()->proofingConfigUpdated()) { d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); canvas()->setProofingConfigUpdated(false); } return d->openGLImageTextures->updateCache(rc, d->openGLImageTextures->image()); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info, d->lodSwitchInProgress); } return QRect(); // FIXME: Implement dirty rect for OpenGL } QVector KisOpenGLCanvas2::updateCanvasProjection(const QVector &infoObjects) { #ifdef Q_OS_OSX /** * On OSX openGL defferent (shared) contexts have different execution queues. * It means that the textures uploading and their painting can be easily reordered. * To overcome the issue, we should ensure that the textures are uploaded in the * same openGL context as the painting is done. */ QOpenGLContext *oldContext = QOpenGLContext::currentContext(); QSurface *oldSurface = oldContext ? oldContext->surface() : 0; this->makeCurrent(); #endif QVector result = KisCanvasWidgetBase::updateCanvasProjection(infoObjects); #ifdef Q_OS_OSX if (oldContext) { oldContext->makeCurrent(oldSurface); } else { this->doneCurrent(); } #endif return result; } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/opengl/kis_opengl_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h index 5266968594..400e1ffdbf 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.h +++ b/libs/ui/opengl/kis_opengl_canvas2.h @@ -1,130 +1,132 @@ /* * Copyright (C) Boudewijn Rempt , (C) 2006 * Copyright (C) Michael Abrahams , (C) 2015 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_OPENGL_CANVAS_2_H #define KIS_OPENGL_CANVAS_2_H #include #ifndef Q_OS_OSX #include #else #include #endif #include "canvas/kis_canvas_widget_base.h" #include "opengl/kis_opengl_image_textures.h" #include "kritaui_export.h" #include "kis_ui_types.h" class KisCanvas2; class KisDisplayColorConverter; class QOpenGLShaderProgram; class QPainterPath; #ifndef Q_MOC_RUN #ifndef Q_OS_OSX #define GLFunctions QOpenGLFunctions #else #define GLFunctions QOpenGLFunctions_3_2_Core #endif #endif /** * KisOpenGLCanvas is the widget that shows the actual image using OpenGL * * NOTE: if you change something in the event handling here, also change it * in the qpainter canvas. * */ class KRITAUI_EXPORT KisOpenGLCanvas2 : public QOpenGLWidget #ifndef Q_MOC_RUN , protected GLFunctions #endif , public KisCanvasWidgetBase { Q_OBJECT public: KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter); ~KisOpenGLCanvas2() override; public: // QOpenGLWidget void resizeGL(int width, int height) override; void initializeGL() override; void paintGL() override; QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; void inputMethodEvent(QInputMethodEvent *event) override; public: void renderCanvasGL(); void renderDecorations(QPainter *painter); void paintToolOutline(const QPainterPath &path); public: // Implement kis_abstract_canvas_widget interface void setDisplayFilter(QSharedPointer displayFilter) override; void notifyImageColorSpaceChanged(const KoColorSpace *cs) override; void setWrapAroundViewingMode(bool value) override; void channelSelectionChanged(const QBitArray &channelFlags) override; void setDisplayColorConverter(KisDisplayColorConverter *colorConverter) override; void finishResizingImage(qint32 w, qint32 h) override; KisUpdateInfoSP startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) override; QRect updateCanvasProjection(KisUpdateInfoSP info) override; QVector updateCanvasProjection(const QVector &infoObjects) override; QWidget *widget() override { return this; } bool isBusy() const override; void setLodResetInProgress(bool value) override; void setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing); KisOpenGLImageTexturesSP openGLImageTextures() const; public Q_SLOTS: void slotConfigChanged(); void slotPixelGridModeChanged(); protected: // KisCanvasWidgetBase bool callFocusNextPrevChild(bool next) override; private: void initializeShaders(); void initializeDisplayShader(); void reportFailedShaderCompilation(const QString &context); void drawImage(); void drawCheckers(); void drawGrid(); + QSize viewportDevicePixelSize() const; + QSizeF widgetSizeAlignedToDevicePixel() const; private: struct Private; Private * const d; }; #endif // KIS_OPENGL_CANVAS_2_H diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt index a72785f369..cfa4b9c64c 100644 --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -1,181 +1,182 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include(ECMAddTests) macro_add_unittest_definitions() ecm_add_tests( kis_image_view_converter_test.cpp kis_shape_selection_test.cpp kis_doc2_test.cpp kis_coordinates_converter_test.cpp kis_grid_config_test.cpp kis_stabilized_events_sampler_test.cpp kis_brush_hud_properties_config_test.cpp kis_shape_commands_test.cpp kis_shape_layer_test.cpp kis_stop_gradient_editor_test.cpp kis_file_layer_test.cpp kis_multinode_property_test.cpp KisFrameSerializerTest.cpp KisFrameCacheStoreTest.cpp kis_animation_exporter_test.cpp kis_prescaled_projection_test.cpp kis_asl_layer_style_serializer_test.cpp kis_animation_importer_test.cpp + KisSpinBoxSplineUnitConverterTest.cpp LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-" ) ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME KisSelectionDecorationTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeDummiesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeShapesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisModelIndexConverterTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp TEST_NAME KisCategorizedListModelTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeJugglerCompressedTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisInputManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_model_test.cpp modeltest.cpp TEST_NAME kis_node_model_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ##### Tests that currently fail and should be fixed ##### include(KritaAddBrokenUnitTest) krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp TEST_NAME kis_shape_controller_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_exiv2_test.cpp TEST_NAME KisExiv2Test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_clipboard_test.cpp TEST_NAME KisClipboardTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeBenchmark LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME KisPaintOnTransparencyMaskTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FillProcessingVisitorTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME FilterStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_selection_manager_test.cpp TEST_NAME KisSelectionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_node_manager_test.cpp TEST_NAME KisNodeManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisDummiesFacadeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisZoomAndPanTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300) krita_add_broken_unit_test( kis_action_manager_test.cpp TEST_NAME KisActionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp TEST_NAME KisCategoriesMapperTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp TEST_NAME kis_animation_frame_cache_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") krita_add_broken_unit_test( kis_derived_resources_test.cpp TEST_NAME kis_derived_resources_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") diff --git a/libs/ui/tests/KisSpinBoxSplineUnitConverterTest.cpp b/libs/ui/tests/KisSpinBoxSplineUnitConverterTest.cpp new file mode 100644 index 0000000000..6a398eb07c --- /dev/null +++ b/libs/ui/tests/KisSpinBoxSplineUnitConverterTest.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Agata Cacko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + + +#include + + +void KisSpinBoxSplineUnitConverterTest::testCurveCalculationToCurve_data() +{ + QTest::addColumn("x"); + QTest::addColumn("min"); + QTest::addColumn("max"); + QTest::addColumn("expected"); + + QTest::newRow("0.6 in (0, 10) = 6") << 6 << 0 << 10 << 0.6; +} + + +void KisSpinBoxSplineUnitConverterTest::testCurveCalculationToCurve() +{ + QFETCH(int, x); + QFETCH(int, min); + QFETCH(int, max); + QFETCH(double, expected); + + qreal result = converter.io2sp(x, min, max); + QCOMPARE(result, expected); +} + +void KisSpinBoxSplineUnitConverterTest::testCurveCalculationToSpinBox_data() +{ + QTest::addColumn("x"); + QTest::addColumn("min"); + QTest::addColumn("max"); + QTest::addColumn("expected"); + + QTest::newRow("0.4 in (0, 10) = 4") << 0.4 << 0 << 10 << 4; +} + +void KisSpinBoxSplineUnitConverterTest::testCurveCalculationToSpinBox() +{ + QFETCH(double, x); + QFETCH(int, min); + QFETCH(int, max); + QFETCH(int, expected); + + int result = converter.sp2io(x, min, max); + QCOMPARE(result, expected); +} + +void KisSpinBoxSplineUnitConverterTest::testCurveCalculationTwoWay_data() +{ + QTest::addColumn("xDouble"); + QTest::addColumn("min"); + QTest::addColumn("max"); + QTest::addColumn("xInt"); + + QTest::newRow("0.5 in (0, 10) = 5") << 0.5 << 0 << 10 << 5; + QTest::newRow("0.3 in (0, 10) = 3") << 0.3 << 0 << 10 << 3; + QTest::newRow("0.7 in (0, 10) = 7") << 0.7 << 0 << 10 << 7; + + QTest::newRow("0.5 in (10, 0) = 5") << 0.5 << 10 << 0 << 5; + QTest::newRow("0.7 in (10, 0) = 3") << 0.7 << 10 << 0 << 3; + QTest::newRow("0.3 in (10, 0) = 7") << 0.3 << 10 << 0 << 7; +} + + +void KisSpinBoxSplineUnitConverterTest::testCurveCalculationTwoWay() +{ + QFETCH(double, xDouble); + QFETCH(int, min); + QFETCH(int, max); + QFETCH(int, xInt); + + + int resultInt = converter.sp2io(xDouble, min, max); + QCOMPARE(resultInt, xInt); + + double resultDouble = converter.io2sp(xInt, min, max); + QCOMPARE(resultDouble, xDouble); +} + +void KisSpinBoxSplineUnitConverterTest::testCurveCalculationCase64() +{ + int inX = 64; + int min = 90, max = 0; + + double resultDouble = converter.io2sp(inX, min, max); + int resultInt = converter.sp2io(resultDouble, min, max); + + QCOMPARE(resultInt, inX); +} + + + +KISTEST_MAIN(KisSpinBoxSplineUnitConverterTest) diff --git a/libs/ui/tests/KisSpinBoxSplineUnitConverterTest.h b/libs/ui/tests/KisSpinBoxSplineUnitConverterTest.h new file mode 100644 index 0000000000..525f1171e5 --- /dev/null +++ b/libs/ui/tests/KisSpinBoxSplineUnitConverterTest.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Agata Cacko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KIS_ANIMATION_EXPORTER_TEST_H +#define KIS_ANIMATION_EXPORTER_TEST_H + +#include +#include + +/** + * KisSpinBoxSplineUnitConverterTest contains tests + * for class KisSpinBoxSplineUnitConverter + */ +class KisSpinBoxSplineUnitConverterTest : public QObject +{ + Q_OBJECT + + KisSpinBoxSplineUnitConverter converter; + +private Q_SLOTS: + void testCurveCalculationToSpinBox(); + void testCurveCalculationToSpinBox_data(); + + void testCurveCalculationToCurve(); + void testCurveCalculationToCurve_data(); + + void testCurveCalculationTwoWay(); + void testCurveCalculationTwoWay_data(); + + void testCurveCalculationCase64(); + +}; +#endif + diff --git a/libs/ui/utils/KisSpinBoxSplineUnitConverter.cpp b/libs/ui/utils/KisSpinBoxSplineUnitConverter.cpp new file mode 100644 index 0000000000..4e24526a66 --- /dev/null +++ b/libs/ui/utils/KisSpinBoxSplineUnitConverter.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Agata Cacko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KisSpinBoxSplineUnitConverter.h" +#include +#include + + + +double KisSpinBoxSplineUnitConverter::io2sp(int x, int min, int max) +{ + int reversedRange = max - min > 0 ? 1 : -1; // tilt elevation has range (90; 0) + int rangeLen = qAbs(max - min); + + double response = reversedRange * double(x - min) / rangeLen; + return response; +} + +int KisSpinBoxSplineUnitConverter::sp2io(double x, int min, int max) +{ + int rangeLen = max - min; // tilt elevation has range (90; 0) + int response = qRound(x*rangeLen) + min; + + return response; +} + + + + diff --git a/libs/ui/utils/KisSpinBoxSplineUnitConverter.h b/libs/ui/utils/KisSpinBoxSplineUnitConverter.h new file mode 100644 index 0000000000..5d2958a5c7 --- /dev/null +++ b/libs/ui/utils/KisSpinBoxSplineUnitConverter.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Agata Cacko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KIS_SPIN_BOX_SPLINE_UNITS_CONVERTER_H +#define KIS_SPIN_BOX_SPLINE_UNITS_CONVERTER_H + +#include + + + +/** + * KisSpinBoxSplineUnitConverter is a class that converts points + * from ranges(0, 1) to ranges (min, max). + * In case of reverted ranges (min > max), + * smaller values from range (0, 1) correspond + * to bigger values from range (min, max) + * and the other way around. + * Example: + * 3 from (0, 10) is 0.3 from (0, 1) + * 3 from (10, 0) is 0.7 from (0, 1) + */ +class KRITAUI_EXPORT KisSpinBoxSplineUnitConverter +{ + +public: + double io2sp(int x, int min, int max); + int sp2io(double x, int min, int max); + +}; + + + +#endif // KIS_SPIN_BOX_SPLINE_UNITS_CONVERTER_H diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index e75b2ae32d..1344e10228 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,793 +1,793 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillCmbDepths(KoID))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); QListWidgetItem *defaultProfile = new QListWidgetItem; defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
"+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
"+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
"+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
"+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
"); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
"+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
"+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
"+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

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

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

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

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

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

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

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

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

" + profileName + "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Extra notes on profiles by Elle Stone:

" "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) //C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Fluorescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/ui/widgets/kis_color_space_selector.cc b/libs/ui/widgets/kis_color_space_selector.cc index d92ae4e57c..c1ae3bbe8c 100644 --- a/libs/ui/widgets/kis_color_space_selector.cc +++ b/libs/ui/widgets/kis_color_space_selector.cc @@ -1,237 +1,237 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * 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_color_space_selector.h" #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselector.h" struct KisColorSpaceSelector::Private { Ui_WdgColorSpaceSelector* colorSpaceSelector; QString knsrcFile; bool profileValid; QString defaultsuffix; }; KisColorSpaceSelector::KisColorSpaceSelector(QWidget* parent) : QWidget(parent), m_advancedSelector(0), d(new Private) { setObjectName("KisColorSpaceSelector"); d->colorSpaceSelector = new Ui_WdgColorSpaceSelector; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillCmbDepths(KoID))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbProfile, SIGNAL(activated(QString)), this, SLOT(colorSpaceChanged())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); d->defaultsuffix = " "+i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)"); connect(d->colorSpaceSelector->bnAdvanced, SIGNAL(clicked()), this, SLOT(slotOpenAdvancedSelector())); fillCmbProfiles(); } KisColorSpaceSelector::~KisColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisColorSpaceSelector::fillCmbProfiles() { const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->cmbProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName + d->defaultsuffix); } else { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName); } } d->colorSpaceSelector->cmbProfile->setCurrent(defaultProfileName + d->defaultsuffix); colorSpaceChanged(); } void KisColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); d->colorSpaceSelector->cmbColorDepth->setIDList(depths, false); if (depths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } const KoColorSpace* KisColorSpaceSelector::currentColorSpace() { QString profilenamestring = d->colorSpaceSelector->cmbProfile->itemHighlighted(); if (profilenamestring.contains(d->defaultsuffix)) { profilenamestring.remove(d->defaultsuffix); return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } else { return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } } void KisColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillCmbDepths(id); } void KisColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillCmbProfiles(); } void KisColorSpaceSelector::setCurrentProfile(const QString& name) { d->colorSpaceSelector->cmbProfile->setCurrent(name); } void KisColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisColorSpaceSelector::showColorBrowserButton(bool showButton) { d->colorSpaceSelector->bnAdvanced->setVisible(showButton); } void KisColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->cmbProfile->count() != 0; d->profileValid = valid; emit(selectionChanged(valid)); if(valid) { emit colorSpaceChanged(currentColorSpace()); QString text = currentColorSpace()->profile()->name(); } } void KisColorSpaceSelector::installProfile() { QStringList mime; KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillCmbProfiles(); } void KisColorSpaceSelector::slotOpenAdvancedSelector() { if (!m_advancedSelector) { - m_advancedSelector = new KisAdvancedColorSpaceSelector(this, "Select a Colorspace"); + m_advancedSelector = new KisAdvancedColorSpaceSelector(this, i18n("Select a Colorspace")); m_advancedSelector->setModal(true); if (currentColorSpace()) { m_advancedSelector->setCurrentColorSpace(currentColorSpace()); } connect(m_advancedSelector, SIGNAL(selectionChanged(bool)), this, SLOT(slotProfileValid(bool)) ); } QDialog::DialogCode result = (QDialog::DialogCode)m_advancedSelector->exec(); if (result) { if (d->profileValid==true) { setCurrentColorSpace(m_advancedSelector->currentColorSpace()); } } } void KisColorSpaceSelector::slotProfileValid(bool valid) { d->profileValid = valid; } diff --git a/libs/ui/widgets/kis_curve_widget.cpp b/libs/ui/widgets/kis_curve_widget.cpp index 9f18242dbc..ae6592771c 100644 --- a/libs/ui/widgets/kis_curve_widget.cpp +++ b/libs/ui/widgets/kis_curve_widget.cpp @@ -1,517 +1,535 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 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. */ // C++ includes. #include #include // Qt includes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes. #include #include #include // Local includes. #include "widgets/kis_curve_widget.h" #define bounds(x,a,b) (xb ? b :x)) #define MOUSE_AWAY_THRES 15 #define POINT_AREA 1E-4 #define CURVE_AREA 1E-4 #include "kis_curve_widget_p.h" KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new KisCurveWidget::Private(this)) { setObjectName("KisCurveWidget"); d->m_grab_point_index = -1; d->m_readOnlyMode = false; d->m_guideVisible = false; d->m_pixmapDirty = true; d->m_pixmapCache = 0; d->setState(ST_NORMAL); d->m_intIn = 0; d->m_intOut = 0; setMouseTracking(true); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent); setMinimumSize(150, 50); setMaximumSize(250, 250); d->setCurveModified(); setFocusPolicy(Qt::StrongFocus); } KisCurveWidget::~KisCurveWidget() { delete d->m_pixmapCache; delete d; } void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax) { d->m_intIn = in; d->m_intOut = out; if (!d->m_intIn || !d->m_intOut) return; d->m_inMin = inMin; d->m_inMax = inMax; d->m_outMin = outMin; d->m_outMax = outMax; - d->m_intIn->setRange(d->m_inMin, d->m_inMax); + int realInMin = qMin(inMin, inMax); // tilt elevation has range (90, 0), which QSpinBox can't handle + int realInMax = qMax(inMin, inMax); + + d->m_intIn->setRange(realInMin, realInMax); d->m_intOut->setRange(d->m_outMin, d->m_outMax); connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); + d->syncIOControls(); } void KisCurveWidget::dropInOutControls() { if (!d->m_intIn || !d->m_intOut) return; disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); d->m_intIn = d->m_intOut = 0; } void KisCurveWidget::inOutChanged(int) { QPointF pt; Q_ASSERT(d->m_grab_point_index >= 0); pt.setX(d->io2sp(d->m_intIn->value(), d->m_inMin, d->m_inMax)); pt.setY(d->io2sp(d->m_intOut->value(), d->m_outMin, d->m_outMax)); + bool newPoint = false; if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) { + newPoint = true; + d->m_curve.setPoint(d->m_grab_point_index, pt); d->m_grab_point_index = d->m_curve.points().indexOf(pt); emit pointSelectedChanged(); - } else + } else { pt = d->m_curve.points()[d->m_grab_point_index]; + } + if (!newPoint) { + // if there is a new Point, no point in rewriting values in spinboxes - d->m_intIn->blockSignals(true); - d->m_intOut->blockSignals(true); + d->m_intIn->blockSignals(true); + d->m_intOut->blockSignals(true); - d->m_intIn->setValue(d->sp2io(pt.x(), d->m_inMin, d->m_inMax)); - d->m_intOut->setValue(d->sp2io(pt.y(), d->m_outMin, d->m_outMax)); + d->m_intIn->setValue(d->sp2io(pt.x(), d->m_inMin, d->m_inMax)); + d->m_intOut->setValue(d->sp2io(pt.y(), d->m_outMin, d->m_outMax)); - d->m_intIn->blockSignals(false); - d->m_intOut->blockSignals(false); + d->m_intIn->blockSignals(false); + d->m_intOut->blockSignals(false); + } - d->setCurveModified(); + d->setCurveModified(false); } void KisCurveWidget::reset(void) { d->m_grab_point_index = -1; emit pointSelectedChanged(); d->m_guideVisible = false; //remove total - 2 points. while (d->m_curve.points().count() - 2 ) { d->m_curve.removePoint(d->m_curve.points().count() - 2); } d->setCurveModified(); } void KisCurveWidget::setCurveGuide(const QColor & color) { d->m_guideVisible = true; d->m_colorGuide = color; } void KisCurveWidget::setPixmap(const QPixmap & pix) { d->m_pix = pix; d->m_pixmapDirty = true; d->setCurveRepaint(); } QPixmap KisCurveWidget::getPixmap() { return d->m_pix; } void KisCurveWidget::setBasePixmap(const QPixmap &pix) { d->m_pixmapBase = pix; } QPixmap KisCurveWidget::getBasePixmap() { return d->m_pixmapBase; } bool KisCurveWidget::pointSelected() const { return d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1; } void KisCurveWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) { //x() find closest point to get focus afterwards double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x(); int left_of_grab_point_index = d->m_grab_point_index - 1; int right_of_grab_point_index = d->m_grab_point_index + 1; int new_grab_point_index; if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) < fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) { new_grab_point_index = left_of_grab_point_index; } else { new_grab_point_index = d->m_grab_point_index; } d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = new_grab_point_index; emit pointSelectedChanged(); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); } e->accept(); d->setCurveModified(); } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) { d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) ); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); e->accept(); d->setCurveModified(); } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) { /* FIXME: Lets user choose the hotkeys */ addPointInTheMiddle(); e->accept(); } else QWidget::keyPressEvent(e); } void KisCurveWidget::addPointInTheMiddle() { QPointF pt(0.5, d->m_curve.value(0.5)); if (!d->jumpOverExistingPoints(pt, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(pt); emit pointSelectedChanged(); if (d->m_intIn) d->m_intIn->setFocus(Qt::TabFocusReason); d->setCurveModified(); } void KisCurveWidget::resizeEvent(QResizeEvent *e) { d->m_pixmapDirty = true; QWidget::resizeEvent(e); } void KisCurveWidget::paintEvent(QPaintEvent *) { int wWidth = width() - 1; int wHeight = height() - 1; QPainter p(this); // Antialiasing is not a good idea here, because // the grid will drift one pixel to any side due to rounding of int // FIXME: let's user tell the last word (in config) //p.setRenderHint(QPainter::Antialiasing); QPalette appPalette = QApplication::palette(); p.fillRect(rect(), appPalette.color(QPalette::Base)); // clear out previous paint call results // make the entire widget grayed out if it is disabled if (!this->isEnabled()) { p.setOpacity(0.2); } // draw background if (!d->m_pix.isNull()) { if (d->m_pixmapDirty || !d->m_pixmapCache) { delete d->m_pixmapCache; d->m_pixmapCache = new QPixmap(width(), height()); QPainter cachePainter(d->m_pixmapCache); cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height()); cachePainter.drawPixmap(0, 0, d->m_pix); d->m_pixmapDirty = false; } p.drawPixmap(0, 0, *d->m_pixmapCache); } d->drawGrid(p, wWidth, wHeight); KisConfig cfg(true); if (cfg.antialiasCurves()) { p.setRenderHint(QPainter::Antialiasing); } // Draw curve. double curY; double normalizedX; int x; QPolygonF poly; p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); for (x = 0 ; x < wWidth ; x++) { normalizedX = double(x) / wWidth; curY = wHeight - d->m_curve.value(normalizedX) * wHeight; /** * Keep in mind that QLineF rounds doubles * to ints mathematically, not just rounds down * like in C */ poly.append(QPointF(x, curY)); } poly.append(QPointF(x, wHeight - d->m_curve.value(1.0) * wHeight)); p.drawPolyline(poly); QPainterPath fillCurvePath; QPolygonF fillPoly = poly; fillPoly.append(QPoint(rect().width(), rect().height())); fillPoly.append(QPointF(0,rect().height())); // add a couple points to the edges so it fills in below always QColor fillColor = appPalette.color(QPalette::Text); fillColor.setAlphaF(0.2); fillCurvePath.addPolygon(fillPoly); p.fillPath(fillCurvePath, fillColor); // Drawing curve handles. double curveX; double curveY; if (!d->m_readOnlyMode) { for (int i = 0; i < d->m_curve.points().count(); ++i) { curveX = d->m_curve.points().at(i).x(); curveY = d->m_curve.points().at(i).y(); + int handleSize = 12; // how big should control points be (diamater size) + if (i == d->m_grab_point_index) { - p.setPen(QPen(appPalette.color(QPalette::Text), 6, Qt::SolidLine)); - p.drawEllipse(QRectF(curveX * wWidth - 2, - wHeight - 2 - curveY * wHeight, 4, 4)); + // active point is slightly more "bold" + p.setPen(QPen(appPalette.color(QPalette::Text), 4, Qt::SolidLine)); + p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), + wHeight - (handleSize*0.5) - curveY * wHeight, + handleSize, + handleSize)); } else { p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); - p.drawEllipse(QRectF(curveX * wWidth - 3, - wHeight - 3 - curveY * wHeight, 6, 6)); + p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), + wHeight - (handleSize*0.5) - curveY * wHeight, + handleSize, + handleSize)); } } } // add border around widget to help contain everything QPainterPath widgetBoundsPath; widgetBoundsPath.addRect(rect()); p.strokePath(widgetBoundsPath, appPalette.color(QPalette::Text)); p.setOpacity(1.0); // reset to 1.0 in case we were drawing a disabled widget before } void KisCurveWidget::mousePressEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height()); if (closest_point_index < 0) { QPointF newPoint(x, y); if (!d->jumpOverExistingPoints(newPoint, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(newPoint); emit pointSelectedChanged(); } else { d->m_grab_point_index = closest_point_index; emit pointSelectedChanged(); } d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x(); d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y(); d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x; d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y; d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY)); d->m_draggedAwayPointIndex = -1; d->setState(ST_DRAG); d->setCurveModified(); } void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); d->setCurveModified(); } void KisCurveWidget::mouseMoveEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); if (d->state() == ST_NORMAL) { // If no point is selected set the cursor shape if on top int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height()); if (nearestPointIndex < 0) setCursor(Qt::ArrowCursor); else setCursor(Qt::CrossCursor); } else { // Else, drag the selected point bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES || e->pos().x() < -MOUSE_AWAY_THRES; bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES || e->pos().y() < -MOUSE_AWAY_THRES; bool removePoint = (crossedHoriz || crossedVert); if (!removePoint && d->m_draggedAwayPointIndex >= 0) { // point is no longer dragged away so reinsert it QPointF newPoint(d->m_draggedAwayPoint); d->m_grab_point_index = d->m_curve.addPoint(newPoint); d->m_draggedAwayPointIndex = -1; } if (removePoint && (d->m_draggedAwayPointIndex >= 0)) return; setCursor(Qt::CrossCursor); x += d->m_grabOffsetX; y += d->m_grabOffsetY; double leftX; double rightX; if (d->m_grab_point_index == 0) { leftX = 0.0; if (d->m_curve.points().count() > 1) rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; else rightX = 1.0; } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) { leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = 1.0; } else { Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1); // the 1E-4 addition so we can grab the dot later. leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; } x = bounds(x, leftX, rightX); y = bounds(y, 0., 1.); d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y)); if (removePoint && d->m_curve.points().count() > 2) { d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index]; d->m_draggedAwayPointIndex = d->m_grab_point_index; d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1); emit pointSelectedChanged(); } d->setCurveModified(); } } KisCubicCurve KisCurveWidget::curve() { return d->m_curve; } void KisCurveWidget::setCurve(KisCubicCurve inlist) { d->m_curve = inlist; d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1); emit pointSelectedChanged(); d->setCurveModified(); } void KisCurveWidget::leaveEvent(QEvent *) { } diff --git a/libs/ui/widgets/kis_curve_widget_p.h b/libs/ui/widgets/kis_curve_widget_p.h index ba67893926..fe40104e8b 100644 --- a/libs/ui/widgets/kis_curve_widget_p.h +++ b/libs/ui/widgets/kis_curve_widget_p.h @@ -1,271 +1,276 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CURVE_WIDGET_P_H_ #define _KIS_CURVE_WIDGET_P_H_ #include #include #include +#include enum enumState { ST_NORMAL, ST_DRAG }; /** * Private members for KisCurveWidget class */ class Q_DECL_HIDDEN KisCurveWidget::Private { KisCurveWidget *m_curveWidget; + KisSpinBoxSplineUnitConverter unitConverter; public: Private(KisCurveWidget *parent); /* Dragging variables */ int m_grab_point_index; double m_grabOffsetX; double m_grabOffsetY; double m_grabOriginalX; double m_grabOriginalY; QPointF m_draggedAwayPoint; int m_draggedAwayPointIndex; bool m_readOnlyMode; bool m_guideVisible; QColor m_colorGuide; /* The curve itself */ bool m_splineDirty; KisCubicCurve m_curve; QPixmap m_pix; QPixmap m_pixmapBase; bool m_pixmapDirty; QPixmap *m_pixmapCache; /* In/Out controls */ QSpinBox *m_intIn; QSpinBox *m_intOut; /* Working range of them */ int m_inMin; int m_inMax; int m_outMin; int m_outMax; /** * State functions. * At the moment used only for dragging. */ enumState m_state; inline void setState(enumState st); inline enumState state() const; /*** Internal routines ***/ /** * Common update routines */ - void setCurveModified(); + void setCurveModified(bool rewriteSpinBoxesValues); void setCurveRepaint(); /** * Convert working range of * In/Out controls to normalized * range of spline (and reverse) + * See notes on KisSpinBoxSplineUnitConverter */ double io2sp(int x, int min, int max); int sp2io(double x, int min, int max); /** * Check whether newly created/moved point @p pt doesn't overlap * with any of existing ones from @p m_points and adjusts its coordinates. * @p skipIndex is the index of the point, that shouldn't be taken * into account during the search * (e.g. because it's @p pt itself) * * Returns false in case the point can't be placed anywhere * without overlapping */ bool jumpOverExistingPoints(QPointF &pt, int skipIndex); /** * Synchronize In/Out spinboxes with the curve */ void syncIOControls(); /** * Find the nearest point to @p pt from m_points */ int nearestPointInRange(QPointF pt, int wWidth, int wHeight) const; /** * Nothing to be said! =) */ inline void drawGrid(QPainter &p, int wWidth, int wHeight); }; KisCurveWidget::Private::Private(KisCurveWidget *parent) { m_curveWidget = parent; } double KisCurveWidget::Private::io2sp(int x, int min, int max) { - int rangeLen = max - min; - return double(x - min) / rangeLen; + return unitConverter.io2sp(x, min, max); } int KisCurveWidget::Private::sp2io(double x, int min, int max) { - int rangeLen = max - min; - return int(x*rangeLen + 0.5) + min; + return unitConverter.sp2io(x, min, max); } bool KisCurveWidget::Private::jumpOverExistingPoints(QPointF &pt, int skipIndex) { Q_FOREACH (const QPointF &it, m_curve.points()) { if (m_curve.points().indexOf(it) == skipIndex) continue; - if (fabs(it.x() - pt.x()) < POINT_AREA) + if (fabs(it.x() - pt.x()) < POINT_AREA) { pt.rx() = pt.x() >= it.x() ? it.x() + POINT_AREA : it.x() - POINT_AREA; + } } return (pt.x() >= 0 && pt.x() <= 1.); } int KisCurveWidget::Private::nearestPointInRange(QPointF pt, int wWidth, int wHeight) const { double nearestDistanceSquared = 1000; int nearestIndex = -1; int i = 0; Q_FOREACH (const QPointF & point, m_curve.points()) { double distanceSquared = (pt.x() - point.x()) * (pt.x() - point.x()) + (pt.y() - point.y()) * (pt.y() - point.y()); if (distanceSquared < nearestDistanceSquared) { nearestIndex = i; nearestDistanceSquared = distanceSquared; } ++i; } if (nearestIndex >= 0) { if (fabs(pt.x() - m_curve.points()[nearestIndex].x()) *(wWidth - 1) < 5 && fabs(pt.y() - m_curve.points()[nearestIndex].y()) *(wHeight - 1) < 5) { return nearestIndex; } } return -1; } #define div2_round(x) (((x)+1)>>1) #define div4_round(x) (((x)+2)>>2) void KisCurveWidget::Private::drawGrid(QPainter &p, int wWidth, int wHeight) { /** * Hint: widget size should conform * formula 4n+5 to draw grid correctly * without curious shifts between * spline and it caused by rounding * * That is not mandatory but desirable */ QPalette appPalette = QApplication::palette(); p.setPen(QPen(appPalette.color(QPalette::Background), 1, Qt::SolidLine)); p.drawLine(div4_round(wWidth), 0, div4_round(wWidth), wHeight); p.drawLine(div2_round(wWidth), 0, div2_round(wWidth), wHeight); p.drawLine(div4_round(3*wWidth), 0, div4_round(3*wWidth), wHeight); p.drawLine(0, div4_round(wHeight), wWidth, div4_round(wHeight)); p.drawLine(0, div2_round(wHeight), wWidth, div2_round(wHeight)); p.drawLine(0, div4_round(3*wHeight), wWidth, div4_round(3*wHeight)); } void KisCurveWidget::Private::syncIOControls() { if (!m_intIn || !m_intOut) return; bool somethingSelected = (m_grab_point_index >= 0); m_intIn->setEnabled(somethingSelected); m_intOut->setEnabled(somethingSelected); if (m_grab_point_index >= 0) { m_intIn->blockSignals(true); m_intOut->blockSignals(true); m_intIn->setValue(sp2io(m_curve.points()[m_grab_point_index].x(), m_inMin, m_inMax)); m_intOut->setValue(sp2io(m_curve.points()[m_grab_point_index].y(), m_outMin, m_outMax)); m_intIn->blockSignals(false); m_intOut->blockSignals(false); } else { /*FIXME: Ideally, these controls should hide away now */ } } -void KisCurveWidget::Private::setCurveModified() +void KisCurveWidget::Private::setCurveModified(bool rewriteSpinBoxesValues = true) { - syncIOControls(); + + if (rewriteSpinBoxesValues) { + syncIOControls(); + } m_splineDirty = true; m_curveWidget->update(); m_curveWidget->emit modified(); } void KisCurveWidget::Private::setCurveRepaint() { m_curveWidget->update(); } void KisCurveWidget::Private::setState(enumState st) { m_state = st; } enumState KisCurveWidget::Private::state() const { return m_state; } #endif /* _KIS_CURVE_WIDGET_P_H_ */ diff --git a/libs/widgets/KoResourceItemChooser.cpp b/libs/widgets/KoResourceItemChooser.cpp index 30a1ab1ae3..eec4ed6c10 100644 --- a/libs/widgets/KoResourceItemChooser.cpp +++ b/libs/widgets/KoResourceItemChooser.cpp @@ -1,586 +1,579 @@ /* This file is part of the KDE project Copyright (c) 2002 Patrick Julien Copyright (c) 2007 Jan Hambrecht Copyright (c) 2007 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2011 José Luis Vergara Copyright (c) 2013 Sascha Suelzer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoResourceItemChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceServerAdapter.h" #include "KoResourceItemView.h" #include "KoResourceItemDelegate.h" #include "KoResourceModel.h" #include #include "KoResourceTaggingManager.h" #include "KoTagFilterWidget.h" #include "KoTagChooserWidget.h" #include "KoResourceItemChooserSync.h" #include "kis_assert.h" #include class Q_DECL_HIDDEN KoResourceItemChooser::Private { public: Private() : model(0) , view(0) , buttonGroup(0) , viewModeButton(0) , usePreview(false) , previewScroller(0) , previewLabel(0) , splitter(0) , tiledPreview(false) , grayscalePreview(false) , synced(false) , updatesBlocked(false) , savedResourceWhileReset(0) {} KoResourceModel *model; KoResourceTaggingManager *tagManager; KoResourceItemView *view; QButtonGroup *buttonGroup; QToolButton *viewModeButton; bool usePreview; QScrollArea *previewScroller; QLabel *previewLabel; QSplitter *splitter; QGridLayout *buttonLayout; bool tiledPreview; bool grayscalePreview; bool synced; bool updatesBlocked; KoResource *savedResourceWhileReset; QList customButtons; }; KoResourceItemChooser::KoResourceItemChooser(QSharedPointer resourceAdapter, QWidget *parent, bool usePreview) : QWidget(parent) , d(new Private()) { Q_ASSERT(resourceAdapter); d->splitter = new QSplitter(this); d->model = new KoResourceModel(resourceAdapter, this); connect(d->model, SIGNAL(beforeResourcesLayoutReset(KoResource*)), SLOT(slotBeforeResourcesLayoutReset(KoResource*))); connect(d->model, SIGNAL(afterResourcesLayoutReset()), SLOT(slotAfterResourcesLayoutReset())); d->view = new KoResourceItemView(this); d->view->setObjectName("ResourceItemview"); d->view->setModel(d->model); d->view->setItemDelegate(new KoResourceItemDelegate(this)); d->view->setSelectionMode(QAbstractItemView::SingleSelection); d->view->viewport()->installEventFilter(this); connect(d->view, SIGNAL(currentResourceChanged(QModelIndex)), this, SLOT(activated(QModelIndex))); connect(d->view, SIGNAL(currentResourceClicked(QModelIndex)), this, SLOT(clicked(QModelIndex))); connect(d->view, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint))); connect(d->view, SIGNAL(sigSizeChanged()), this, SLOT(updateView())); d->splitter->addWidget(d->view); d->splitter->setStretchFactor(0, 2); d->usePreview = usePreview; if (d->usePreview) { d->previewScroller = new QScrollArea(this); d->previewScroller->setWidgetResizable(true); d->previewScroller->setBackgroundRole(QPalette::Dark); d->previewScroller->setVisible(true); d->previewScroller->setAlignment(Qt::AlignCenter); d->previewLabel = new QLabel(this); d->previewScroller->setWidget(d->previewLabel); d->splitter->addWidget(d->previewScroller); if (d->splitter->count() == 2) { d->splitter->setSizes(QList() << 280 << 160); } QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(d->previewScroller); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(d->splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterMoved())); d->buttonGroup = new QButtonGroup(this); d->buttonGroup->setExclusive(false); QGridLayout *layout = new QGridLayout(this); d->buttonLayout = new QGridLayout(); importButton = new QPushButton(this); importButton->setToolTip(i18nc("@info:tooltip", "Import resource")); importButton->setEnabled(true); d->buttonGroup->addButton(importButton, Button_Import); d->buttonLayout->addWidget(importButton, 0, 0); deleteButton = new QPushButton(this); deleteButton->setToolTip(i18nc("@info:tooltip", "Delete resource")); deleteButton->setEnabled(false); d->buttonGroup->addButton(deleteButton, Button_Remove); d->buttonLayout->addWidget(deleteButton, 0, 1); connect(d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotButtonClicked(int))); d->buttonLayout->setColumnStretch(0, 1); d->buttonLayout->setColumnStretch(1, 1); d->buttonLayout->setColumnStretch(2, 2); d->buttonLayout->setSpacing(0); d->buttonLayout->setMargin(0); d->viewModeButton = new QToolButton(this); d->viewModeButton->setPopupMode(QToolButton::InstantPopup); d->viewModeButton->setVisible(false); d->tagManager = new KoResourceTaggingManager(d->model, this); connect(d->tagManager, SIGNAL(updateView()), this, SLOT(updateView())); layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0); layout->addWidget(d->viewModeButton, 0, 1); layout->addWidget(d->splitter, 1, 0, 1, 2); layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 2); layout->addLayout(d->buttonLayout, 3, 0, 1, 2); layout->setMargin(0); layout->setSpacing(0); updateView(); updateButtonState(); showTaggingBar(false); activated(d->model->index(0, 0)); } KoResourceItemChooser::~KoResourceItemChooser() { disconnect(); delete d; } void KoResourceItemChooser::slotButtonClicked(int button) { if (button == Button_Import) { QString extensions = d->model->extensions(); QStringList mimeTypes; Q_FOREACH(const QString &suffix, extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->model->importResourceFile(filename); } else if (button == Button_Remove) { QModelIndex index = d->view->currentIndex(); int row = index.row(); int column = index.column(); if (index.isValid()) { KoResource *resource = resourceFromModelIndex(index); if (resource) { d->model->removeResource(resource); } } if (column == 0) { int rowMin = --row; row = qBound(0, rowMin, row); } int columnMin = --column; column = qBound(0, columnMin, column); setCurrentItem(row, column); activated(d->model->index(row, column)); } updateButtonState(); } void KoResourceItemChooser::showButtons(bool show) { foreach (QAbstractButton * button, d->buttonGroup->buttons()) { show ? button->show() : button->hide(); } Q_FOREACH (QAbstractButton *button, d->customButtons) { show ? button->show() : button->hide(); } } void KoResourceItemChooser::addCustomButton(QAbstractButton *button, int cell) { d->buttonLayout->addWidget(button, 0, cell); d->buttonLayout->setColumnStretch(2, 1); d->buttonLayout->setColumnStretch(3, 1); } void KoResourceItemChooser::showTaggingBar(bool show) { d->tagManager->showTaggingBar(show); } void KoResourceItemChooser::setRowCount(int rowCount) { int resourceCount = d->model->resourcesCount(); d->model->setColumnCount(static_cast(resourceCount) / rowCount); //Force an update to get the right row height (in theory) QRect geometry = d->view->geometry(); d->view->setViewMode(KoResourceItemView::FIXED_ROWS); d->view->setGeometry(geometry.adjusted(0, 0, 0, 1)); d->view->setGeometry(geometry); } void KoResourceItemChooser::setColumnCount(int columnCount) { d->model->setColumnCount(columnCount); } void KoResourceItemChooser::setRowHeight(int rowHeight) { d->view->verticalHeader()->setDefaultSectionSize(rowHeight); } void KoResourceItemChooser::setColumnWidth(int columnWidth) { d->view->horizontalHeader()->setDefaultSectionSize(columnWidth); } void KoResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate) { d->view->setItemDelegate(delegate); } KoResource *KoResourceItemChooser::currentResource() const { QModelIndex index = d->view->currentIndex(); if (index.isValid()) { return resourceFromModelIndex(index); } return 0; } void KoResourceItemChooser::setCurrentResource(KoResource *resource) { // don't update if the change came from the same chooser if (d->updatesBlocked) { return; } QModelIndex index = d->model->indexFromResource(resource); d->view->setCurrentIndex(index); updatePreview(index.isValid() ? resource : 0); } void KoResourceItemChooser::slotBeforeResourcesLayoutReset(KoResource *activateAfterReset) { d->savedResourceWhileReset = activateAfterReset ? activateAfterReset : currentResource(); } void KoResourceItemChooser::slotAfterResourcesLayoutReset() { if (d->savedResourceWhileReset) { this->blockSignals(true); setCurrentResource(d->savedResourceWhileReset); this->blockSignals(false); } } void KoResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation) { d->splitter->setOrientation(orientation); } void KoResourceItemChooser::setPreviewTiled(bool tiled) { d->tiledPreview = tiled; } void KoResourceItemChooser::setGrayscalePreview(bool grayscale) { d->grayscalePreview = grayscale; } void KoResourceItemChooser::setCurrentItem(int row, int column) { QModelIndex index = d->model->index(row, column); if (!index.isValid()) return; d->view->setCurrentIndex(index); if (index.isValid()) { updatePreview(resourceFromModelIndex(index)); } } void KoResourceItemChooser::setProxyModel(QAbstractProxyModel *proxyModel) { proxyModel->setSourceModel(d->model); d->view->setModel(proxyModel); } void KoResourceItemChooser::activated(const QModelIndex &index) { if (!index.isValid()) return; KoResource *resource = 0; if (index.isValid()) { resource = resourceFromModelIndex(index); } KIS_SAFE_ASSERT_RECOVER (resource) { resource = currentResource(); } if (resource) { d->updatesBlocked = true; emit resourceSelected(resource); d->updatesBlocked = false; updatePreview(resource); updateButtonState(); } } void KoResourceItemChooser::clicked(const QModelIndex &index) { Q_UNUSED(index); KoResource *resource = currentResource(); if (resource) { emit resourceClicked(resource); } } void KoResourceItemChooser::updateButtonState() { QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove); if (! removeButton) return; KoResource *resource = currentResource(); if (resource) { removeButton->setEnabled(!resource->permanent()); return; } removeButton->setEnabled(false); } void KoResourceItemChooser::updatePreview(KoResource *resource) { if (!d->usePreview) return; if (!resource) { d->previewLabel->setPixmap(QPixmap()); return; } QImage image = resource->image(); if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32 && image.format() != QImage::Format_ARGB32_Premultiplied) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); } if (d->tiledPreview) { int width = d->previewScroller->width() * 4; int height = d->previewScroller->height() * 4; QImage img(width, height, image.format()); QPainter gc(&img); gc.fillRect(img.rect(), Qt::white); gc.setPen(Qt::NoPen); gc.setBrush(QBrush(image)); gc.drawRect(img.rect()); image = img; } // Only convert to grayscale if it is rgb. Otherwise, it's gray already. if (d->grayscalePreview && !image.isGrayscale()) { QRgb *pixel = reinterpret_cast(image.bits()); for (int row = 0; row < image.height(); ++row) { for (int col = 0; col < image.width(); ++col) { const QRgb currentPixel = pixel[row * image.width() + col]; const int red = qRed(currentPixel); const int green = qGreen(currentPixel); const int blue = qBlue(currentPixel); const int grayValue = (red * 11 + green * 16 + blue * 5) / 32; pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue); } } } d->previewLabel->setPixmap(QPixmap::fromImage(image)); } KoResource *KoResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const { if (!index.isValid()) return 0; const QAbstractProxyModel *proxyModel = dynamic_cast(index.model()); if (proxyModel) { //Get original model index, because proxy models destroy the internalPointer QModelIndex originalIndex = proxyModel->mapToSource(index); return static_cast(originalIndex.internalPointer()); } return static_cast(index.internalPointer()); } QSize KoResourceItemChooser::viewSize() const { return d->view->size(); } KoResourceItemView *KoResourceItemChooser::itemView() const { return d->view; } void KoResourceItemChooser::contextMenuRequested(const QPoint &pos) { d->tagManager->contextMenuRequested(currentResource(), pos); } void KoResourceItemChooser::setViewModeButtonVisible(bool visible) { d->viewModeButton->setVisible(visible); } QToolButton *KoResourceItemChooser::viewModeButton() const { return d->viewModeButton; } void KoResourceItemChooser::setSynced(bool sync) { if (d->synced == sync) return; d->synced = sync; KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance(); if (sync) { connect(chooserSync, SIGNAL(baseLengthChanged(int)), SLOT(baseLengthChanged(int))); baseLengthChanged(chooserSync->baseLength()); } else { chooserSync->disconnect(this); } } void KoResourceItemChooser::baseLengthChanged(int length) { if (d->synced) { int resourceCount = d->model->resourcesCount(); int width = d->view->width(); int maxColumns = width / length; int cols = width / (2 * length) + 1; while (cols <= maxColumns) { int size = width / cols; int rows = ceil(resourceCount / (double)cols); if (rows * size < (d->view->height() - 5)) { break; } cols++; } setColumnCount(cols); } d->view->updateView(); } bool KoResourceItemChooser::eventFilter(QObject *object, QEvent *event) { if (d->synced && event->type() == QEvent::Wheel) { KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance(); QWheelEvent *qwheel = static_cast(event); if (qwheel->modifiers() & Qt::ControlModifier) { int degrees = qwheel->delta() / 8; int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10; chooserSync->setBaseLength(newBaseLength); return true; } } return QObject::eventFilter(object, event); } void KoResourceItemChooser::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateView(); } void KoResourceItemChooser::showEvent(QShowEvent *event) { QWidget::showEvent(event); updateView(); - - /* Give the search field focus onShowEvent to allow for - * faster preset filtering... */ - if( d->tagManager ) { - d->tagManager->searchField()->setFocus(Qt::FocusReason::OtherFocusReason); - d->tagManager->searchField()->selectAll(); - } } void KoResourceItemChooser::updateView() { if (d->synced) { KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance(); baseLengthChanged(chooserSync->baseLength()); } /// helps to set icons here in case the theme is changed d->viewModeButton->setIcon(koIcon("view-choose")); importButton->setIcon(koIcon("document-open")); deleteButton->setIcon(koIcon("trash-empty")); } diff --git a/libs/widgets/KoResourceTaggingManager.cpp b/libs/widgets/KoResourceTaggingManager.cpp index 685b359907..41dca4fe6c 100644 --- a/libs/widgets/KoResourceTaggingManager.cpp +++ b/libs/widgets/KoResourceTaggingManager.cpp @@ -1,397 +1,392 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * 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 "KoResourceTaggingManager.h" #include #include #include #include #include #include #include #include "KoTagFilterWidget.h" #include "KoTagChooserWidget.h" #include "KoResourceModel.h" #include #include "KoResourceItemChooserContextMenu.h" #include class TaggedResourceSet { public: TaggedResourceSet() {} TaggedResourceSet(const QString& tagName, const QList& resources) : tagName(tagName) , resources(resources) {} QString tagName; QList resources; }; class KoResourceTaggingManager::Private { public: QString currentTag; QList originalResources; TaggedResourceSet lastDeletedTag; KoTagChooserWidget* tagChooser; KoTagFilterWidget* tagFilter; QCompleter* tagCompleter; QPointer model; }; KoResourceTaggingManager::KoResourceTaggingManager(KoResourceModel *model, QWidget* parent) : QObject(parent) , d(new Private()) { d->model = model; d->tagChooser = new KoTagChooserWidget(parent); d->tagChooser->addReadOnlyItem("All"); // not translatable until other tags made translatable! d->tagChooser->addItems(d->model->tagNamesList()); d->tagFilter = new KoTagFilterWidget(parent); connect(d->tagChooser, SIGNAL(tagChosen(QString)), this, SLOT(tagChooserIndexChanged(QString))); connect(d->tagChooser, SIGNAL(newTagRequested(QString)), this, SLOT(contextCreateNewTag(QString))); connect(d->tagChooser, SIGNAL(tagDeletionRequested(QString)), this, SLOT(removeTagFromComboBox(QString))); connect(d->tagChooser, SIGNAL(tagRenamingRequested(QString,QString)), this, SLOT(renameTag(QString,QString))); connect(d->tagChooser, SIGNAL(tagUndeletionRequested(QString)), this, SLOT(undeleteTag(QString))); connect(d->tagChooser, SIGNAL(tagUndeletionListPurgeRequested()), this, SLOT(purgeTagUndeleteList())); connect(d->tagFilter, SIGNAL(saveButtonClicked()), this, SLOT(tagSaveButtonPressed())); connect(d->tagFilter, SIGNAL(filterTextChanged(QString)), this, SLOT(tagSearchLineEditTextChanged(QString))); connect(d->model, SIGNAL(tagBoxEntryAdded(QString)), this, SLOT(syncTagBoxEntryAddition(QString))); connect(d->model, SIGNAL(tagBoxEntryRemoved(QString)), this, SLOT(syncTagBoxEntryRemoval(QString))); connect(d->model, SIGNAL(tagBoxEntryModified()), this, SLOT(syncTagBoxEntries())); // FIXME: fix tag completer // d->tagCompleter = new QCompleter(this); // d->tagSearchLineEdit->setCompleter(d->tagCompleter); syncTagBoxEntries(); } KoResourceTaggingManager::~KoResourceTaggingManager() { delete d; } void KoResourceTaggingManager::showTaggingBar(bool show) { show ? d->tagFilter->show() : d->tagFilter->hide(); show ? d->tagChooser->show() : d->tagChooser->hide(); blockSignals(!show); QString tag("All"); if (show) { KConfigGroup group = KSharedConfig::openConfig()->group("SelectedTags"); tag = group.readEntry(d->model->serverType(), "All"); } int idx = d->tagChooser->findIndexOf(tag); if (idx < 0) idx = 0; d->tagChooser->setCurrentIndex(idx); } void KoResourceTaggingManager::purgeTagUndeleteList() { d->lastDeletedTag = TaggedResourceSet(); d->tagChooser->setUndeletionCandidate(QString()); } void KoResourceTaggingManager::undeleteTag(const QString & tagToUndelete) { QString tagName = tagToUndelete; QStringList allTags = availableTags(); if (allTags.contains(tagName)) { bool ok; tagName = QInputDialog::getText( d->tagChooser, i18n("Unable to undelete tag"), i18n("The tag you are trying to undelete already exists in tag list.
Please enter a new, unique name for it.
"), QLineEdit::Normal, tagName, &ok); if (!ok || allTags.contains(tagName) || tagName.isEmpty()) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setText(i18n("Tag was not undeleted.")); msgBox.exec(); return; } } QList serverResources = d->model->serverResources(); Q_FOREACH (KoResource * resource, d->lastDeletedTag.resources) { if (serverResources.contains(resource)) { addResourceTag(resource, tagName); } } d->model->tagCategoryAdded(tagName); d->tagChooser->setCurrentIndex(d->tagChooser->findIndexOf(tagName)); d->tagChooser->setUndeletionCandidate(QString()); d->lastDeletedTag = TaggedResourceSet(); } QStringList KoResourceTaggingManager::availableTags() const { return d->tagChooser->allTags(); } void KoResourceTaggingManager::addResourceTag(KoResource* resource, const QString& tagName) { QStringList tagsList = d->model->assignedTagsList(resource); if (tagsList.isEmpty()) { d->model->addTag(resource, tagName); } else { Q_FOREACH (const QString & tag, tagsList) { if (tag.compare(tagName)) { d->model->addTag(resource, tagName); } } } } void KoResourceTaggingManager::syncTagBoxEntryAddition(const QString& tag) { d->tagChooser->insertItem(tag); } void KoResourceTaggingManager::contextCreateNewTag(const QString& tag) { if (!tag.isEmpty()) { d->model->addTag(0, tag); d->model->tagCategoryAdded(tag); d->tagChooser->setCurrentIndex(d->tagChooser->findIndexOf(tag)); updateTaggedResourceView(); } } void KoResourceTaggingManager::contextCreateNewTag(KoResource* resource , const QString& tag) { if (!tag.isEmpty()) { d->model->tagCategoryAdded(tag); if (resource) { addResourceTag(resource, tag); } } } void KoResourceTaggingManager::syncTagBoxEntryRemoval(const QString& tag) { d->tagChooser->removeItem(tag); } void KoResourceTaggingManager::syncTagBoxEntries() { QStringList tags = d->model->tagNamesList(); tags.sort(); Q_FOREACH (const QString &tag, tags) { d->tagChooser->insertItem(tag); } } void KoResourceTaggingManager::contextAddTagToResource(KoResource* resource, const QString& tag) { addResourceTag(resource, tag); d->model->tagCategoryMembersChanged(); updateTaggedResourceView(); } void KoResourceTaggingManager::contextRemoveTagFromResource(KoResource* resource, const QString& tag) { removeResourceTag(resource, tag); d->model->tagCategoryMembersChanged(); updateTaggedResourceView(); } void KoResourceTaggingManager::removeTagFromComboBox(const QString &tag) { QList resources = d->model->currentlyVisibleResources(); Q_FOREACH (KoResource * resource, resources) { removeResourceTag(resource, tag); } d->model->tagCategoryRemoved(tag); d->lastDeletedTag = TaggedResourceSet(tag, resources); d->tagChooser->setUndeletionCandidate(tag); } void KoResourceTaggingManager::removeResourceTag(KoResource* resource, const QString& tagName) { QStringList tagsList = d->model->assignedTagsList(resource); Q_FOREACH (const QString & oldName, tagsList) { if (!oldName.compare(tagName)) { d->model->deleteTag(resource, oldName); } } } void KoResourceTaggingManager::renameTag(const QString &oldName, const QString& newName) { if (!d->model->tagNamesList().contains(newName)) { QList resources = d->model->currentlyVisibleResources(); Q_FOREACH (KoResource * resource, resources) { removeResourceTag(resource, oldName); addResourceTag(resource, newName); } contextCreateNewTag(newName); d->model->tagCategoryRemoved(oldName); d->model->tagCategoryAdded(newName); } } void KoResourceTaggingManager::updateTaggedResourceView() { d->model->setCurrentTag(d->currentTag); d->model->updateServer(); d->originalResources = d->model->currentlyVisibleResources(); emit updateView(); } void KoResourceTaggingManager::tagChooserIndexChanged(const QString& lineEditText) { if (!d->tagChooser->selectedTagIsReadOnly()) { d->currentTag = lineEditText; d->tagFilter->allowSave(true); d->model->enableResourceFiltering(true); } else { d->model->enableResourceFiltering(false); d->tagFilter->allowSave(false); d->currentTag.clear(); } d->tagFilter->clear(); updateTaggedResourceView(); } void KoResourceTaggingManager::tagSearchLineEditTextChanged(const QString& lineEditText) { if (d->tagChooser->selectedTagIsReadOnly()) { d->model->enableResourceFiltering(!lineEditText.isEmpty()); } else { d->model->enableResourceFiltering(true); } d->model->searchTextChanged(lineEditText); d->model->updateServer(); ///FIXME: fix completer // d->tagCompleter = new QCompleter(tagNamesList(lineEditText),this); // d->tagSearchLineEdit->setCompleter(d->tagCompleter); emit updateView(); } void KoResourceTaggingManager::tagSaveButtonPressed() { if (!d->tagChooser->selectedTagIsReadOnly()) { QList newResources = d->model->currentlyVisibleResources(); Q_FOREACH (KoResource * oldRes, d->originalResources) { if (!newResources.contains(oldRes)) removeResourceTag(oldRes, d->currentTag); } Q_FOREACH (KoResource * newRes, newResources) { if (!d->originalResources.contains(newRes)) addResourceTag(newRes, d->currentTag); } d->model->tagCategoryMembersChanged(); } updateTaggedResourceView(); } void KoResourceTaggingManager::contextMenuRequested(KoResource* resource, const QStringList& resourceTags, const QPoint& pos) { /* no visible tag chooser usually means no intended tag interaction, * context menu makes no sense then either */ if (!resource || !d->tagChooser->isVisible()) return; KoResourceItemChooserContextMenu menu(resource, resourceTags, d->tagChooser->currentlySelectedTag(), d->tagChooser->allTags()); connect(&menu, SIGNAL(resourceTagAdditionRequested(KoResource*,QString)), this, SLOT(contextAddTagToResource(KoResource*,QString))); connect(&menu, SIGNAL(resourceTagRemovalRequested(KoResource*,QString)), this, SLOT(contextRemoveTagFromResource(KoResource*,QString))); connect(&menu, SIGNAL(resourceAssignmentToNewTagRequested(KoResource*,QString)), this, SLOT(contextCreateNewTag(KoResource*,QString))); menu.exec(pos); } void KoResourceTaggingManager::contextMenuRequested(KoResource* currentResource, QPoint pos) { if (currentResource) { contextMenuRequested(currentResource, d->model->assignedTagsList(currentResource), pos); } } KoTagChooserWidget* KoResourceTaggingManager::tagChooserWidget() { return d->tagChooser; } -QLineEdit* KoResourceTaggingManager::searchField() -{ - return d->tagFilter->searchField(); -} - KoTagFilterWidget* KoResourceTaggingManager::tagFilterWidget() { return d->tagFilter; } diff --git a/libs/widgets/KoResourceTaggingManager.h b/libs/widgets/KoResourceTaggingManager.h index 2c73f34230..161266e0f2 100644 --- a/libs/widgets/KoResourceTaggingManager.h +++ b/libs/widgets/KoResourceTaggingManager.h @@ -1,94 +1,93 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * 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 KORESOURCETAGGINGMANAGER_H #define KORESOURCETAGGINGMANAGER_H #include class QWidget; class QStringList; class QString; class QPoint; class KoTagFilterWidget; class KoTagChooserWidget; class KoResourceModel; class KoResource; /** * @brief The KoResourceTaggingManager class is ... * * XXX: this needs to be documented! */ class KoResourceTaggingManager : public QObject { Q_OBJECT public: explicit KoResourceTaggingManager(KoResourceModel* model, QWidget* parent); ~KoResourceTaggingManager() override; void showTaggingBar(bool show); QStringList availableTags() const; void contextMenuRequested(KoResource* currentResource, QPoint pos); void allowTagModification( bool set ); bool allowTagModification(); KoTagFilterWidget* tagFilterWidget(); KoTagChooserWidget* tagChooserWidget(); - class QLineEdit* searchField(); Q_SIGNALS: - void updateView(); + void updateView(); private Q_SLOTS: void undeleteTag(const QString& tagToUndelete); void purgeTagUndeleteList(); void contextCreateNewTag(KoResource* resource, const QString& tag); void contextCreateNewTag(const QString& tag); void syncTagBoxEntryRemoval(const QString& tag); void syncTagBoxEntryAddition(const QString& tag); void syncTagBoxEntries(); void tagSaveButtonPressed(); void contextRemoveTagFromResource(KoResource* resource, const QString& tag); void contextAddTagToResource(KoResource* resource, const QString& tag); void renameTag(const QString &oldName, const QString &newName); void tagChooserIndexChanged(const QString& lineEditText); void tagSearchLineEditTextChanged(const QString& lineEditText); void removeTagFromComboBox(const QString& tag); private: void contextMenuRequested(KoResource* resource, const QStringList& resourceTags, const QPoint& pos); void enableContextMenu(bool enable); void removeResourceTag(KoResource* resource, const QString& tagName); void addResourceTag(KoResource* resource, const QString& tagName); void updateTaggedResourceView(); class Private; Private* const d; }; #endif // KORESOURCETAGGINGINTERFACE_H diff --git a/libs/widgets/KoTagFilterWidget.cpp b/libs/widgets/KoTagFilterWidget.cpp index 6698f66ad6..654bec1548 100644 --- a/libs/widgets/KoTagFilterWidget.cpp +++ b/libs/widgets/KoTagFilterWidget.cpp @@ -1,145 +1,140 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * 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 "KoTagFilterWidget.h" #include #include #include #include #include #include class KoTagFilterWidget::Private { public: QString tagSearchBarTooltip_saving_disabled; QString tagSearchBarTooltip_saving_enabled; QLineEdit* tagSearchLineEdit; QPushButton* tagSearchSaveButton; QGridLayout* filterBarLayout; }; KoTagFilterWidget::KoTagFilterWidget(QWidget* parent): QWidget(parent) ,d( new Private()) { d->tagSearchBarTooltip_saving_disabled = i18nc ( "@info:tooltip", "Entering search terms here will add to, or remove resources from the current tag view." "

To filter based on the partial, case insensitive name of a resource:
" "partialname or !partialname.

" "

In-/exclusion of other tag sets:
" "[Tagname] or ![Tagname].

" "

Case sensitive and full name matching in-/exclusion:
" "\"ExactMatch\" or !\"ExactMatch\".

" "Filter results cannot be saved for the All Presets view.
" "In this view, pressing Enter or clearing the filter box will restore all items.
" "Create and/or switch to a different tag if you want to save filtered resources into named sets." ); d->tagSearchBarTooltip_saving_enabled = i18nc ( "@info:tooltip", "Entering search terms here will add to, or remove resources from the current tag view." "

To filter based on the partial, case insensitive name of a resource:
" "partialname or !partialname.

" "

In-/exclusion of other tag sets:
" "[Tagname] or ![Tagname].

" "

Case sensitive and full name matching in-/exclusion:
" "\"ExactMatch\" or !\"ExactMatch\".

" "Pressing Enter or clicking the Save button will save the changes." ); QGridLayout* filterBarLayout = new QGridLayout; d->tagSearchLineEdit = new QLineEdit(this); d->tagSearchLineEdit->setClearButtonEnabled(true); d->tagSearchLineEdit->setPlaceholderText(i18n("Search")); d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_disabled); d->tagSearchLineEdit->setEnabled(true); filterBarLayout->setSpacing(0); filterBarLayout->setMargin(0); filterBarLayout->setColumnStretch(0, 1); filterBarLayout->addWidget(d->tagSearchLineEdit, 0, 0); d->tagSearchSaveButton = new QPushButton(this); d->tagSearchSaveButton->setIcon(koIcon("media-floppy")); d->tagSearchSaveButton->setToolTip(i18nc("@info:tooltip", "Save the currently filtered set as the new members of the current tag.")); d->tagSearchSaveButton->setEnabled(false); filterBarLayout->addWidget(d->tagSearchSaveButton, 0, 1); connect(d->tagSearchSaveButton, SIGNAL(pressed()), this, SLOT(onSaveButtonClicked())); connect(d->tagSearchLineEdit, SIGNAL(returnPressed()), this, SLOT(onSaveButtonClicked())); connect(d->tagSearchLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString))); allowSave(false); this->setLayout(filterBarLayout); } KoTagFilterWidget::~KoTagFilterWidget() { delete d; } void KoTagFilterWidget::allowSave(bool allow) { if (allow) { d->tagSearchSaveButton->show(); d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_enabled); } else { d->tagSearchSaveButton->hide(); d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_disabled); } } void KoTagFilterWidget::clear() { d->tagSearchLineEdit->clear(); d->tagSearchSaveButton->setEnabled(false); } -QLineEdit *KoTagFilterWidget::searchField() -{ - return d->tagSearchLineEdit; -} - void KoTagFilterWidget::onTextChanged(const QString& lineEditText) { d->tagSearchSaveButton->setEnabled(!lineEditText.isEmpty()); emit filterTextChanged(lineEditText); } void KoTagFilterWidget::onSaveButtonClicked() { emit saveButtonClicked(); clear(); } diff --git a/libs/widgets/KoTagFilterWidget.h b/libs/widgets/KoTagFilterWidget.h index 56ef4c8873..917f6c3db6 100644 --- a/libs/widgets/KoTagFilterWidget.h +++ b/libs/widgets/KoTagFilterWidget.h @@ -1,53 +1,52 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * 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 KOTAGFILTERWIDGET_H #define KOTAGFILTERWIDGET_H #include class KoTagFilterWidget : public QWidget { Q_OBJECT public: explicit KoTagFilterWidget(QWidget* parent); ~KoTagFilterWidget() override; void allowSave(bool allow); void clear(); - class QLineEdit* searchField(); Q_SIGNALS: void filterTextChanged(const QString &filterText); void saveButtonClicked(); private Q_SLOTS: void onTextChanged(const QString &lineEditText); void onSaveButtonClicked(); private: class Private; Private* const d; }; #endif // KOTAGFILTERWIDGET_H diff --git a/packaging/linux/appimage/build-deps.sh b/packaging/linux/appimage/build-deps.sh index 001ccb03f8..04d15556a6 100755 --- a/packaging/linux/appimage/build-deps.sh +++ b/packaging/linux/appimage/build-deps.sh @@ -1,84 +1,84 @@ #!/bin/bash # # Build all Krita's dependencies on Ubuntu 14.04. # # Prerequisites: cmake git build-essential libxcb-keysyms1-dev plus all deps for Qt5 # # Halt on errors and be verbose about what we are doing set -e set -x # Read in our parameters export BUILD_PREFIX=$1 export KRITA_SOURCES=$2 # qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. # That's not always the case, so make sure it is export LC_ALL=en_US.UTF-8 export LANG=en_us.UTF-8 # We want to use $prefix/deps/usr/ for all our dependencies export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/ export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/ # Setup variables needed to help everything find what we build export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib:$LD_LIBRARY_PATH export PATH=$DEPS_INSTALL_PREFIX/bin:$PATH export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig:$DEPS_INSTALL_PREFIX/lib/pkgconfig:/usr/lib/pkgconfig:$PKG_CONFIG_PATH export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH # A krita build layout looks like this: # krita/ -- the source directory # downloads/ -- downloads of the dependencies from files.kde.org # deps-build/ -- build directory for the dependencies # deps/ -- the location for the built dependencies # build/ -- build directory for krita itself # krita.appdir/ -- install directory for krita and the dependencies # Make sure our downloads directory exists if [ ! -d $DOWNLOADS_DIR ] ; then mkdir -p $DOWNLOADS_DIR fi # Make sure our build directory exists if [ ! -d $BUILD_PREFIX/deps-build/ ] ; then mkdir -p $BUILD_PREFIX/deps-build/ fi # The 3rdparty dependency handling in Krita also requires the install directory to be pre-created if [ ! -d $DEPS_INSTALL_PREFIX ] ; then mkdir -p $DEPS_INSTALL_PREFIX fi # Switch to our build directory as we're basically ready to start building... cd $BUILD_PREFIX/deps-build/ # Configure the dependencies for building cmake $KRITA_SOURCES/3rdparty -DCMAKE_INSTALL_PREFIX=$DEPS_INSTALL_PREFIX -DINSTALL_ROOT=$DEPS_INSTALL_PREFIX -DEXTERNALS_DOWNLOAD_DIR=$DOWNLOADS_DIR # Now start building everything we need, in the appropriate order #cmake --build . --config RelWithDebInfo --target ext_png #cmake --build . --config RelWithDebInfo --target ext_tiff #cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_boost +cmake --build . --config RelWithDebInfo --target ext_fftw3 cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 -cmake --build . --config RelWithDebInfo --target ext_fftw3 cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw cmake --build . --config RelWithDebInfo --target ext_giflib #cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_python #cmake --build . --config RelWithDebInfo --target ext_freetype #cmake --build . --config RelWithDebInfo --target ext_fontconfig cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_poppler cmake --build . --config RelWithDebInfo --target ext_kcrash cmake --build . --config RelWithDebInfo --target ext_gmic cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt cmake --build . --config RelWithDebInfo --target ext_quazip diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh index ea65ce7341..34af0c300d 100755 --- a/packaging/macos/osxbuild.sh +++ b/packaging/macos/osxbuild.sh @@ -1,427 +1,433 @@ #!/usr/bin/env sh # Stop at any error # For debug purposes # set -e # set -x # osxbuild.sh automates building and installing of krita and krita dependencies # for OSX, the script only needs you to set BUILDROOT environment to work # properly. # # Run with no args for a short help about each command. # builddeps: Attempts to build krita dependencies in the necessary order, # intermediate steps for creating symlinks and fixing rpath of some # packages midway is also managed. Order goes from top to bottom, to add # new steps just place them in the proper place. # rebuilddeps: This re-runs all make and make install of dependencies 3rdparty # this was needed as deleting the entire install directory an rerunning build # step for dependencies does not install if they are already built. This step # forces installation. Have not tested it lately so it might not be needed anymore # build: Runs cmake build and make step for krita sources. It always run cmake step, so # it might take a bit longer than a pure on the source tree. The script tries # to set the make flag -jN to a proper N. # install: Runs install step for krita sources. # fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by # default it fails to set a proper @rpath # buildinstall: Runs build, install and fixboost steps. if test -z $BUILDROOT; then echo "ERROR: BUILDROOT env not set, exiting!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" exit fi echo "BUILDROOT set to ${BUILDROOT}" export KIS_SRC_DIR=${BUILDROOT}/krita export KIS_TBUILD_DIR=${BUILDROOT}/depbuild export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall export KIS_DOWN_DIR=${BUILDROOT}/down export KIS_BUILD_DIR=${BUILDROOT}/kisbuild export KIS_INSTALL_DIR=${BUILDROOT}/i # flags for OSX environment # We only support from 10.11 up export MACOSX_DEPLOYMENT_TARGET=10.11 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11 # Build time variables if test -z $(which cmake); then echo "ERROR: cmake not found, exiting!" exit fi export PATH=${KIS_INSTALL_DIR}/bin:$PATH export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${C_INCLUDE_PATH} export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${CPLUS_INCLUDE_PATH} export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:/usr/lib:${LIBRARY_PATH} # export CPPFLAGS=-I${KIS_INSTALL_DIR}/include # export LDFLAGS=-L${KIS_INSTALL_DIR}/lib # export PYTHONHOME=${KIS_INSTALL_DIR} # export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5 # This will make the debug output prettier export KDE_COLOR_DEBUG=1 export QTEST_COLORED=1 DEPBUILD_LOG="${BUILDROOT}/builddeps_.log" # configure max core for make compile ((MAKE_THREADS=1)) if test ${OSTYPE} == "darwin*"; then ((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1)) fi # Prints log to file # $2 error message # $3 success message build_errorlog () { if [[ "${1}" -ne 0 ]]; then printf "ERROR: %s\n" "$2" >> ${DEPBUILD_LOG} else printf "OK: %s\n" "$3" >> ${DEPBUILD_LOG} fi echo ${1} } check_dir_path () { printf "%s" "Checking if ${1} exists and is dir... " if test -d ${1}; then echo "OK" return 0 elif test -e ${1}; then echo "\n\tERROR: file ${1} exists but is not a directory!" return 1 else echo "Creating ${1}" mkdir ${1} fi return 0 } # builds dependencies for the first time cmake_3rdparty () { cd ${KIS_TBUILD_DIR} for package in ${@:1:${#@}}; do printf "STATUS: %s\n" "Building ${package}" >> ${DEPBUILD_LOG} cmake --build . --config RelWithDebInfo --target ${package} 2>> ${DEPBUILD_LOG} local build_error=$(build_errorlog ${?} "Failed build ${package}" "Build Success! ${package}") # run package fixes if [[ ${2} != "1" ]]; then build_3rdparty_fixes ${package} fi done } build_3rdparty_fixes(){ osxbuild_count=$((${osxbuild_count} + 1)) pkg=${1} if [[ "${pkg}" = "ext_qt" && -e "${KIS_INSTALL_DIR}/bin/qmake" ]]; then ln -sf qmake "${KIS_INSTALL_DIR}/bin/qmake-qt5" elif [[ "${pkg}" = "ext_openexr" ]]; then # open exr will fail the first time is called # rpath needs to be fixed an build rerun echo "Fixing rpath on openexr file: b44ExpLogTable" echo "Fixing rpath on openexr file: dwaLookups" install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups # we must rerun build! cmake_3rdparty ext_openexr "1" fi } build_3rdparty () { echo "building in ${KIS_TBUILD_DIR}" check_dir_path ${KIS_TBUILD_DIR} check_dir_path ${KIS_DOWN_DIR} check_dir_path ${KIS_INSTALL_DIR} cd ${KIS_TBUILD_DIR} cmake ${KIS_SRC_DIR}/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \ -DINSTALL_ROOT=${KIS_INSTALL_DIR} # -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \ # -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib echo "finished 3rdparty build setup" # make preinstall echo "finished make step" if ! test -z ${1}; then cmake_3rdparty ${@} exit fi # build 3rdparty tools # The order must not be changed! cmake_3rdparty \ ext_pkgconfig \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_lcms2 \ ext_ocio \ ext_openexr cmake_3rdparty \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ - ext_giflib + ext_giflib \ + ext_fontconfig \ + ext_freetype \ + ext_poppler # Stop if qmake link was not created # this meant qt build fail and further builds will # also fail. test -L "${KIS_INSTALL_DIR}/bin/qmake-qt5" if [[ $(build_errorlog ${?} "qmake link missing!" "qmake link present, continuing...") -ne 0 ]];then printf " link: ${KIS_INSTALL_DIR}/bin/qmake-qt5 missing! It probably means ext_qt failed!! check, fix and rerun!\n" exit fi # for python cmake_3rdparty \ ext_python \ ext_sip \ ext_pyqt cmake_3rdparty ext_libheif cmake_3rdparty \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } # Recall cmake for all 3rd party packages # make is only on target first run # subsequent runs only call make install rebuild_3rdparty () { echo "starting rebuild of 3rdparty packages" build_install_ext() { for pkg in ${@:1:${#@}}; do { cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_heif/${pkg}-prefix/src/${pkg}-stamp } echo "Installing ${pkg} files..." rm ${pkg}-configure ${pkg}-build ${pkg}-install cmake_3rdparty ${pkg} >> ${BUILDROOT}/rebuilddeps_.log cd ${KIS_TBUILD_DIR} done } # Do not process complete list only pkgs given. if ! test -z ${1}; then build_install_ext ${@} exit fi build_install_ext \ ext_pkgconfig \ ext_iconv \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_expat \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_patch \ ext_lcms2 \ ext_ocio \ ext_ilmbase \ ext_openexr \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ + ext_fontconfig \ + ext_freetype \ + ext_poppler \ ext_python \ ext_sip \ ext_pyqt \ build_install_ext \ ext_yasm \ ext_nasm \ ext_libx265 \ ext_libde265 \ ext_libheif \ # Build kde_frameworks build_install_ext \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } #not tested set_krita_dirs() { if ! test -z ${1}; then KIS_BUILD_DIR=${BUILDROOT}/b_${1} KIS_INSTALL_DIR=${BUILDROOT}/i_${1} KIS_SRC_DIR=${BUILDROOT}/src_${1} fi } # build_krita # run cmake krita build_krita () { set_krita_dirs ${1} echo ${KIS_BUILD_DIR} echo ${KIS_INSTALL_DIR} check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} cmake ${KIS_SRC_DIR} \ -DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DDEFINE_NO_DEPRECATED=1 \ -DBUILD_TESTING=OFF \ -DHIDE_SAFE_ASSERTS=ON \ -DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \ -DPYQT_SIP_DIR_OVERRIDE=$KIS_INSTALL_DIR/share/sip/ \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 # copiling phase make -j${MAKE_THREADS} # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make -j${MAKE_THREADS} fi } install_krita () { set_krita_dirs ${1} check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} make install # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make install fi } fix_boost_rpath () { set_krita_dirs ${1} # install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt if $(install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita); then echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin" fi # install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita FILES=$(find -L ${KIS_INSTALL_DIR} -name '*so' -o -name '*dylib') for FILE in $FILES; do if test -n "$(otool -L $FILE | grep boost)"; then echo "Fixing… $FILE" install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE fi done } print_usage () { echo "USAGE: osxbuild.sh [pkg]" echo "BUILDSTEPS:\t\t" echo "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg" echo "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg \t\t\t usefull for cleaning install directory and quickly reinstall all deps." echo "\n fixboost \t\t Fixes broken boost \@rpath on OSX" echo "\n build \t\t\t Builds krita" echo "\n install \t\t Installs krita" echo "\n buildinstall \t\t Build and Installs krita, running fixboost after installing" echo "" } if test ${#} -eq 0; then echo "ERROR: No option given!" print_usage exit 1 fi if test ${1} = "builddeps"; then build_3rdparty "${@:2}" elif test ${1} = "rebuilddeps"; then rebuild_3rdparty "${@:2}" elif test ${1} = "fixboost"; then fix_boost_rpath elif test ${1} = "build"; then build_krita ${2} elif test ${1} = "install"; then install_krita ${2} fix_boost_rpath ${2} elif test ${1} = "buildinstall"; then build_krita ${2} install_krita ${2} fix_boost_rpath ${2} elif test ${1} = "test"; then ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita else echo "Option ${1} not supported" print_usage fi # after finishig sometimes it complains about missing matching quotes. \ No newline at end of file diff --git a/packaging/macos/osxdeploy.sh b/packaging/macos/osxdeploy.sh index e188b5b4d8..2ee18fa828 100755 --- a/packaging/macos/osxdeploy.sh +++ b/packaging/macos/osxdeploy.sh @@ -1,452 +1,458 @@ #!/usr/bin/env sh # Krita tool to create dmg from installed source # Copies all files to a folder to be converted into the final dmg # Background image must be set for it to deploy correcly # osxdeploy.sh automates the creation of the release DMG. It needs an image # either png or jpg as first argument as it will use the image to set # the background of the DMG. # Necessary files can be downloaded from https://drive.google.com/drive/folders/15cUhCou7ya9ktjfhbzRaL7_IpntzxG4j?usp=sharing # A short explanation of what it does: # - Creates a copy of "krita-template" folder (this containes Terms of use # and Applications) into kritadmg folder. # Application folder symlink can be created with applescript but Terms of use contents cannot, # also working like this allows to add other files to dmg if needed. # Place the folder in ${BUILDROOT} # - Copies krita.app contents to kritadmg folder # - Copies i/share to Contents/Resources excluding unnecesary files # - Copies translations, qml and quicklook PlugIns # - Copies i/plugins and i/lib/plugins to Contents/PlugIns # - Runs macdeployqt: macdeployqt is not built by default in ext_qt # build by: # cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src # make sub-macdeployqt-all # make sub-macdeployqt-install_subtargets # make install # the script changes dir to installation/bin to run macdeployqt as it can be buggy # if not runned from the same folder as the binary is on. # - Fix rpath from krita bin # - Find missing libraries from plugins and copy to Framworks or plugins. # This uses oTool iterative to find all unique libraries, then it searches each # library fond in folder, and if not found attempts to copy contents # to the appropiate folder, either Frameworks (if frameworks is in namefile, or # library has plugin isnot in path), or plugin if otherwise. # - Builds DMG # Building DMG creates a new dmg with the contents of # mounts the dmg and sets the style for dmg. # unmount # Compress resulting dmg into krita_nightly-.dmg # deletes temporary files. if test -z ${BUILDROOT}; then echo "ERROR: BUILDROOT env not set!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" echo "exiting..." exit fi DMG_title="krita" #if changed krita.temp.dmg must be deleted manually # There is some duplication between build and deploy scripts # a config env file could would be a nice idea. KIS_INSTALL_DIR=${BUILDROOT}/i KIS_BUILD_DIR=${BUILDROOT}/kisbuild # only used for getting git sha number KRITA_DMG=${BUILDROOT}/kritadmg KRITA_DMG_TEMPLATE=${BUILDROOT}/kritadmg-template export PATH=${KIS_INSTALL_DIR}/bin:$PATH # flags for OSX environment # We only support from 10.11 up export MACOSX_DEPLOYMENT_TARGET=10.11 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11 print_usage () { echo "USAGE: osxdeploy.sh [-s=] " echo "\t -s Code sign identity for codesign" echo "\t osxdeploy needs an input image to add to the dmg background \t image recomended size is at least 950x500\n" } # Attempt to detach previous mouted DMG if [[ -d "/Volumes/${DMG_title}" ]]; then echo "WARNING: Another Krita DMG is mounted!" echo "Attempting eject…" hdiutil detach "/Volumes/${DMG_title}" if [ $? -ne 0 ]; then exit fi echo "Success!" fi # Parse input args if test ${#} -eq 0; then echo "ERROR: no option given" print_usage exit 1 fi for arg in "${@}"; do if [[ -f ${arg} ]]; then DMG_validBG=0 echo "attempting to check background is valid jpg or png..." BG_FORMAT=$(sips --getProperty format ${arg} | awk '{printf $2}') if [[ "png" = ${BG_FORMAT} || "jpeg" = ${BG_FORMAT} ]];then echo "valid image file" DMG_background=$(cd "$(dirname "${arg}")"; pwd -P)/$(basename "${arg}") DMG_validBG=1 + # check imageDPI + BG_DPI=$(sips --getProperty dpiWidth ${DMG_background} | grep dpi | awk '{print $2}') + if [[ $(echo "${BG_DPI} > 150" | bc -l) -eq 1 ]]; then + printf "WARNING: image dpi has an effect on apparent size! + Check dpi is adequate for screen display if image appears very small + Current dpi is: %s\n" ${BG_DPI} + fi fi fi # If string starts with -sign if [[ ${arg} = -s=* ]]; then CODE_SIGNATURE="${arg#*=}" fi if [[ ${arg} = "-h" || ${arg} = "--help" ]]; then print_usage exit fi done if [[ ${DMG_validBG} -eq 0 ]]; then echo "No jpg or png valid file detected!!" echo "exiting" exit fi if [[ -z "${CODE_SIGNATURE}" ]]; then echo "WARNING: No signature provided, Code will not be signed" else printf 'Code will be signed with "%s"\n' "${CODE_SIGNATURE}" fi # Helper functions countArgs () { echo "${#}" } stringContains () { echo "$(grep "${2}" <<< "${1}")" - } add_lib_to_list() { local llist=${2} if test -z "$(grep ${1##*/} <<< ${llist})" ; then local llist="${llist} ${1##*/} " fi - echo ${llist} + echo "${llist}" } # Find all @rpath and Absolute to buildroot path libs # Add to libs_used # converts absolute buildroot path to @rpath find_needed_libs () { # echo "Analizing libraries with oTool..." >&2 local libs_used="" # input lib_lists founded for libFile in ${@}; do if test -z "$(file ${libFile} | grep 'Mach-O')" ; then # echo "skipping ${libFile}" >&2 continue fi oToolResult=$(otool -L ${libFile} | awk '{print $1}') resultArray=(${oToolResult}) # convert to array for lib in ${resultArray[@]:1}; do if test "${lib:0:1}" = "@"; then - local libs_used=$(add_lib_to_list ${lib} "${libs_used}") + local libs_used=$(add_lib_to_list "${lib}" "${libs_used}") fi if [[ "${lib:0:${#BUILDROOT}}" = "${BUILDROOT}" ]]; then printf "Fixing %s: %s\n" "${libFile#${KRITA_DMG}/}" "${lib##*/}" >&2 if [[ "${lib##*/}" = "${libFile##*/}" ]]; then install_name_tool -id ${lib##*/} "${libFile}" else install_name_tool -change ${lib} "@rpath/${lib##*${BUILDROOT}/i/lib/}" "${libFile}" - local libs_used=$(add_lib_to_list ${lib} "${libs_used}") + local libs_used=$(add_lib_to_list "${lib}" "${libs_used}") fi fi done done - echo ${libs_used} # return updated list + echo "${libs_used}" # return updated list } find_missing_libs (){ # echo "Searching for missing libs on deployment folders…" >&2 local libs_missing="" for lib in ${@}; do if test -z "$(find ${KRITA_DMG}/krita.app/Contents/ -name ${lib})"; then # echo "Adding ${lib} to missing libraries." >&2 libs_missing="${libs_missing} ${lib}" fi done - echo ${libs_missing} + echo "${libs_missing}" } copy_missing_libs () { for lib in ${@}; do - result=$(find "${BUILDROOT}/i" -name "${lib}") + result=$(find -L "${BUILDROOT}/i" -name "${lib}") if test $(countArgs ${result}) -eq 1; then if [ "$(stringContains "${result}" "plugin")" ]; then cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/PlugIns/ krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/PlugIns/${result##*/}" else cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/Frameworks/ krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/Frameworks/${result##*/}" fi else echo "${lib} might be a missing framework" if [ "$(stringContains "${result}" "framework")" ]; then echo "copying framework ${BUILDROOT}/i/lib/${lib}.framework to dmg" # rsync only included ${lib} Resources Versions rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/${lib} ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Resources ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Versions ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ krita_findmissinglibs "$(find "${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/" -perm u+x)" fi fi done } krita_findmissinglibs() { neededLibs=$(find_needed_libs "${@}") missingLibs=$(find_missing_libs ${neededLibs}) if test $(countArgs ${missingLibs}) -gt 0; then printf "Found missing libs: %s\n" "${missingLibs}" copy_missing_libs ${missingLibs} fi } krita_deploy () { # fix_boost_rpath cd ${BUILDROOT} # Update files in krita.app echo "Deleting previous kritadmg run..." rm -rf ./krita.dmg ${KRITA_DMG} # Copy new builtFiles echo "Preparing ${KRITA_DMG} for deployment..." echo "Copying krita.app..." rsync -riul ${KRITA_DMG_TEMPLATE}/ ${KRITA_DMG} rsync -prul ${KIS_INSTALL_DIR}/bin/krita.app ${KRITA_DMG} mkdir -p ${KRITA_DMG}/krita.app/Contents/PlugIns mkdir -p ${KRITA_DMG}/krita.app/Contents/Frameworks echo "Copying share..." # Deletes old copies of translation and qml to be recreated cd ${KIS_INSTALL_DIR}/share/ rsync -prul --delete ./ \ --exclude krita_SRCS.icns \ --exclude aclocal \ --exclude doc \ --exclude ECM \ --exclude eigen3 \ --exclude emacs \ --exclude gettext \ --exclude gettext-0.19.8 \ --exclude info \ --exclude kf5 \ --exclude kservices5 \ --exclude man \ --exclude ocio \ --exclude pkgconfig \ --exclude mime \ --exclude translations \ --exclude qml \ ${KRITA_DMG}/krita.app/Contents/Resources cd ${BUILDROOT} echo "Copying translations..." rsync -prul ${KIS_INSTALL_DIR}/translations/ \ ${KRITA_DMG}/krita.app/Contents/Resources/translations echo "Copying kritaquicklook..." mkdir -p ${KRITA_DMG}/krita.app/Contents/Library/QuickLook rsync -prul ${KIS_INSTALL_DIR}/plugins/kritaquicklook.qlgenerator ${KRITA_DMG}/krita.app/Contents/Library/QuickLook cd ${KRITA_DMG}/krita.app/Contents ln -shF Resources share echo "Copying qml..." rsync -prul ${KIS_INSTALL_DIR}/qml Resources/qml echo "Copying plugins..." # exclude kritaquicklook.qlgenerator/ cd ${KIS_INSTALL_DIR}/plugins/ rsync -prul --delete --delete-excluded ./ \ --exclude kritaquicklook.qlgenerator \ ${KRITA_DMG}/krita.app/Contents/PlugIns cd ${BUILDROOT} rsync -prul ${KIS_INSTALL_DIR}/lib/kritaplugins/ ${KRITA_DMG}/krita.app/Contents/PlugIns # rsync -prul {KIS_INSTALL_DIR}/lib/libkrita* Frameworks/ # activate for python enabled Krita # echo "Copying python..." # cp -r ${KIS_INSTALL_DIR}/lib/python3.5 Frameworks/ # cp -r ${KIS_INSTALL_DIR}/lib/krita-python-libs Frameworks/ # XXX: fix rpath for krita.so # echo "Copying sip..." # rsync -Prvul ${KIS_INSTALL_DIR}/sip Frameworks/ # To avoid errors macdeployqt must be run from bin location # ext_qt will not build macdeployqt by default so it must be build manually # cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src # make sub-macdeployqt-all # make sub-macdeployqt-install_subtargets # make install echo "Running macdeployqt..." cd ${KIS_INSTALL_DIR}/bin ./macdeployqt ${KRITA_DMG}/krita.app \ -verbose=0 \ -executable=${KRITA_DMG}/krita.app/Contents/MacOS/krita \ -libpath=${KIS_INSTALL_DIR}/lib \ -qmldir=${KIS_INSTALL_DIR}/qml \ # -extra-plugins=${KIS_INSTALL_DIR}/lib/kritaplugins \ # -extra-plugins=${KIS_INSTALL_DIR}/lib/plugins \ # -extra-plugins=${KIS_INSTALL_DIR}/plugins cd ${BUILDROOT} echo "macdeployqt done!" install_name_tool -delete_rpath @loader_path/../../../../lib ${KRITA_DMG}/krita.app/Contents/MacOS/krita rm -rf ${KRITA_DMG}/krita.app/Contents/PlugIns/kf5/org.kde.kwindowsystem.platforms # repair krita for plugins printf "Searching for missing libraries\n" krita_findmissinglibs $(find ${KRITA_DMG}/krita.app/Contents -type f -name "*.dylib" -or -name "*.so" -or -perm u+x) echo "Done!" } # helper to define function only once batch_codesign() { xargs -P4 -I FILE codesign -f -s "${CODE_SIGNATURE}" FILE } # Code sign must be done as recommended by apple "sign code inside out in individual stages" signBundle() { cd ${KRITA_DMG} # sign Frameworks and libs cd ${KRITA_DMG}/krita.app/Contents/Frameworks # remove debug version as both versions cant be signed. rm ${KRITA_DMG}/krita.app/Contents/Frameworks/QtScript.framework/Versions/Current/QtScript_debug find . -type d -name "*.framework" | xargs printf "%s/Versions/Current\n" | batch_codesign find . -type f -name "*.dylib" -or -name "*.so" | batch_codesign # Sign all other files in Framework (needed) # there are many files in pyton do we need to sign them? TODO # find krita-python-libs -type f | batch_codesign # find python -type f | batch_codesign # Sing only libraries and plugins cd ${KRITA_DMG}/krita.app/Contents/PlugIns find . -type f | batch_codesign cd ${KRITA_DMG}/krita.app/Contents/Library/QuickLook printf "kritaquicklook.qlgenerator" | batch_codesign # It is recommended to sign every Resource file cd ${KRITA_DMG}/krita.app/Contents/Resources find . -type f | batch_codesign #Finally sign krita and krita.app printf "${KRITA_DMG}/krita.app/Contents/MacOS/krita" | batch_codesign printf "${KRITA_DMG}/krita.app" | batch_codesign } createDMG () { printf "Creating of dmg with contents of %s...\n" "${KRITA_DMG}" cd ${BUILDROOT} DMG_size=500 ## Build dmg from folder # create dmg on local system # usage of -fsargs minimze gaps at front of filesystem (reduce size) hdiutil create -srcfolder "${KRITA_DMG}" -volname "${DMG_title}" -fs HFS+ \ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DMG_size}m krita.temp.dmg # Next line is only useful if we have a dmg as a template! # previous hdiutil must be uncommented # cp krita-template.dmg krita.dmg device=$(hdiutil attach -readwrite -noverify -noautoopen "krita.temp.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}') # rsync -priul --delete ${KRITA_DMG}/krita.app "/Volumes/${DMG_title}" # Set style for dmg if [[ ! -d "/Volumes/${DMG_title}/.background" ]]; then mkdir "/Volumes/${DMG_title}/.background" fi cp ${DMG_background} "/Volumes/${DMG_title}/.background/" ## Apple script to set style printf ' tell application "Finder" tell disk "%s" open set current view of container window to icon view set toolbar visible of container window to false set statusbar visible of container window to false set the bounds of container window to {291, 95, 1097, 558} set theViewOptions to the icon view options of container window set arrangement of theViewOptions to not arranged set icon size of theViewOptions to 80 set background picture of theViewOptions to file ".background:%s" set position of item "krita.app" of container window to {172, 70} set position of item "Applications" of container window to {167, 189} set position of item "Terms of Use" of container window to {166, 314} update without registering applications delay 1 close end tell end tell ' "${DMG_title}" "${DMG_background##*/}" | osascript chmod -Rf go-w "/Volumes/${DMG_title}" # ensure all writting operations to dmg are over sync hdiutil detach $device hdiutil convert "krita.temp.dmg" -format UDZO -imagekey -zlib-level=9 -o krita-out.dmg # Add git version number GIT_SHA=$(grep "#define KRITA_GIT_SHA1_STRING" ${KIS_BUILD_DIR}/libs/version/kritagitversion.h | awk '{gsub(/"/, "", $3); printf $3}') mv krita-out.dmg krita-nightly_${GIT_SHA}.dmg echo "moved krita-out.dmg to krita-nightly_${GIT_SHA}.dmg" rm krita.temp.dmg echo "dmg done!" } ####################### # Program starts!! ######################## # Run deploy command, instalation is assumed to exist in BUILDROOT/i krita_deploy # Code sign krita.app if signature given if [[ -n "${CODE_SIGNATURE}" ]]; then signBundle fi # Create DMG from files insiede ${KRITA_DMG} folder createDMG diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp index a719130a46..52207e1567 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp @@ -1,560 +1,560 @@ /* * Copyright (c) 2010 Adam Celarek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software */ #include "kis_color_selector_base.h" #include #include #include #include #include #include #include #include #include #include #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "kis_node.h" #include "KisViewManager.h" #include #include "kis_image.h" #include "kis_global.h" #include "kis_display_color_converter.h" #include class KisColorPreviewPopup : public QWidget { public: KisColorPreviewPopup(KisColorSelectorBase* parent) : QWidget(parent), m_parent(parent) { setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint); setQColor(QColor(0,0,0)); m_baseColor = QColor(0,0,0,0); m_previousColor = QColor(0,0,0,0); m_lastUsedColor = QColor(0,0,0,0); } void show() { updatePosition(); QWidget::show(); } void updatePosition() { QPoint parentPos = m_parent->mapToGlobal(QPoint(0,0)); QRect availRect = QApplication::desktop()->availableGeometry(this); QPoint targetPos; if ( parentPos.x() - 100 > availRect.x() ) { targetPos = QPoint(parentPos.x() - 100, parentPos.y()); } else if ( parentPos.x() + m_parent->width() + 100 < availRect.right()) { targetPos = m_parent->mapToGlobal(QPoint(m_parent->width(), 0)); } else if ( parentPos.y() - 100 > availRect.y() ) { targetPos = QPoint(parentPos.x(), parentPos.y() - 100); } else { targetPos = QPoint(parentPos.x(), parentPos.y() + m_parent->height()); } setGeometry(targetPos.x(), targetPos.y(), 100, 150); setAttribute(Qt::WA_TranslucentBackground); } void setQColor(const QColor& color) { m_color = color; update(); } void setPreviousColor() { m_previousColor = m_baseColor; } void setBaseColor(const QColor& color) { m_baseColor = color; update(); } void setLastUsedColor(const QColor& color) { m_lastUsedColor = color; update(); } protected: void paintEvent(QPaintEvent *e) override { Q_UNUSED(e); QPainter p(this); p.fillRect(0, 0, width(), width(), m_color); p.fillRect(50, width(), width(), height(), m_previousColor); p.fillRect(0, width(), 50, height(), m_lastUsedColor); } void enterEvent(QEvent *e) override { QWidget::enterEvent(e); m_parent->tryHideAllPopups(); } void leaveEvent(QEvent *e) override { QWidget::leaveEvent(e); m_parent->tryHideAllPopups(); } private: KisColorSelectorBase* m_parent; QColor m_color; QColor m_baseColor; QColor m_previousColor; QColor m_lastUsedColor; }; KisColorSelectorBase::KisColorSelectorBase(QWidget *parent) : QWidget(parent), m_canvas(0), m_popup(0), m_parent(0), m_colorUpdateAllowed(true), m_colorUpdateSelf(false), m_hideTimer(new QTimer(this)), m_popupOnMouseOver(false), m_popupOnMouseClick(true), m_colorSpace(0), m_isPopup(false), m_hideOnMouseClick(false), m_colorPreviewPopup(new KisColorPreviewPopup(this)) { m_hideTimer->setInterval(0); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hidePopup())); using namespace std::placeholders; // For _1 placeholder auto function = std::bind(&KisColorSelectorBase::slotUpdateColorAndPreview, this, _1); m_updateColorCompressor.reset(new ColorCompressorType(20 /* ms */, function)); } KisColorSelectorBase::~KisColorSelectorBase() { delete m_popup; delete m_colorPreviewPopup; } void KisColorSelectorBase::setPopupBehaviour(bool onMouseOver, bool onMouseClick) { m_popupOnMouseClick = onMouseClick; m_popupOnMouseOver = onMouseOver; if(onMouseClick) { m_popupOnMouseOver = false; } if(m_popupOnMouseOver) { setMouseTracking(true); } } void KisColorSelectorBase::setColorSpace(const KoColorSpace *colorSpace) { m_colorSpace = colorSpace; } void KisColorSelectorBase::setCanvas(KisCanvas2 *canvas) { if (m_canvas) { m_canvas->disconnectCanvasObserver(this); } m_canvas = canvas; if (m_canvas) { connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(canvasResourceChanged(int,QVariant)), Qt::UniqueConnection); connect(m_canvas->displayColorConverter(), SIGNAL(displayConfigurationChanged()), SLOT(reset()), Qt::UniqueConnection); connect(canvas->imageView()->resourceProvider(), SIGNAL(sigFGColorUsed(KoColor)), this, SLOT(updateLastUsedColorPreview(KoColor)), Qt::UniqueConnection); if (m_canvas->viewManager() && m_canvas->viewManager()->canvasResourceProvider()) { setColor(Acs::currentColor(m_canvas->viewManager()->canvasResourceProvider(), Acs::Foreground)); } } if (m_popup) { m_popup->setCanvas(canvas); } reset(); } void KisColorSelectorBase::unsetCanvas() { if (m_popup) { m_popup->unsetCanvas(); } m_canvas = 0; } void KisColorSelectorBase::mousePressEvent(QMouseEvent* event) { event->accept(); -//this boolean here is to check if the colour selector is updating the resource, so it won't update itself when the resource is updated// +//this boolean here is to check if the color selector is updating the resource, so it won't update itself when the resource is updated// if (m_colorUpdateSelf==false) {m_colorUpdateSelf=true;} if(!m_isPopup && m_popupOnMouseClick && event->button() == Qt::MidButton) { lazyCreatePopup(); int x = event->globalX(); int y = event->globalY(); int popupsize = m_popup->width(); x-=popupsize/2; y-=popupsize/2; QRect availRect = QApplication::desktop()->availableGeometry(this); if(xwidth()>availRect.x()+availRect.width()) x = availRect.x()+availRect.width()-m_popup->width(); if(y+m_popup->height()>availRect.y()+availRect.height()) y = availRect.y()+availRect.height()-m_popup->height(); m_popup->move(x, y); m_popup->setHidingTime(200); showPopup(DontMove); } else if (m_isPopup && event->button() == Qt::MidButton) { if (m_colorPreviewPopup) { m_colorPreviewPopup->hide(); } hide(); } else { showColorPreview(); event->ignore(); } } void KisColorSelectorBase::mouseReleaseEvent(QMouseEvent *e) { Q_UNUSED(e); if (e->button() == Qt::MidButton) { e->accept(); } else if (m_isPopup && (m_hideOnMouseClick && !m_popupOnMouseOver) && !m_hideTimer->isActive()) { if (m_colorPreviewPopup) { m_colorPreviewPopup->hide(); } hide(); } } void KisColorSelectorBase::enterEvent(QEvent *e) { if (m_popup && m_popup->isVisible()) { m_popup->m_hideTimer->stop(); } if (m_isPopup && m_hideTimer->isActive()) { m_hideTimer->stop(); } // do not show the popup when boxed in // the configuration dialog (m_canvas == 0) if (m_canvas && !m_isPopup && m_popupOnMouseOver && (!m_popup || m_popup->isHidden())) { lazyCreatePopup(); const QRect availRect = QApplication::desktop()->availableGeometry(this); QPoint proposedTopLeft = rect().center() - m_popup->rect().center(); proposedTopLeft = mapToGlobal(proposedTopLeft); QRect popupRect = QRect(proposedTopLeft, m_popup->size()); popupRect = kisEnsureInRect(popupRect, availRect); m_popup->setGeometry(popupRect); m_popup->setHidingTime(200); showPopup(DontMove); } QWidget::enterEvent(e); } void KisColorSelectorBase::leaveEvent(QEvent *e) { tryHideAllPopups(); QWidget::leaveEvent(e); } void KisColorSelectorBase::keyPressEvent(QKeyEvent *) { if (m_isPopup) { hidePopup(); } } void KisColorSelectorBase::dragEnterEvent(QDragEnterEvent *e) { if(e->mimeData()->hasColor()) e->acceptProposedAction(); if(e->mimeData()->hasText() && QColor(e->mimeData()->text()).isValid()) e->acceptProposedAction(); } void KisColorSelectorBase::dropEvent(QDropEvent *e) { QColor color; if(e->mimeData()->hasColor()) { color = qvariant_cast(e->mimeData()->colorData()); } else if(e->mimeData()->hasText()) { color.setNamedColor(e->mimeData()->text()); if(!color.isValid()) return; } KoColor kocolor(color , KoColorSpaceRegistry::instance()->rgb8()); updateColor(kocolor, Acs::Foreground, true); } void KisColorSelectorBase::updateColor(const KoColor &color, Acs::ColorRole role, bool needsExplicitColorReset) { commitColor(color, role); if (needsExplicitColorReset) { setColor(color); } } void KisColorSelectorBase::requestUpdateColorAndPreview(const KoColor &color, Acs::ColorRole role) { m_updateColorCompressor->start(qMakePair(color, role)); } void KisColorSelectorBase::slotUpdateColorAndPreview(QPair color) { updateColorPreview(color.first); updateColor(color.first, color.second, false); } void KisColorSelectorBase::setColor(const KoColor& color) { Q_UNUSED(color); } void KisColorSelectorBase::setHidingTime(int time) { KIS_ASSERT_RECOVER_NOOP(m_isPopup); m_hideTimer->setInterval(time); } void KisColorSelectorBase::lazyCreatePopup() { if (!m_popup) { m_popup = createPopup(); Q_ASSERT(m_popup); m_popup->setParent(this); /** * On Linux passing Qt::X11BypassWindowManagerHint makes * the window never hide if one switches it with Alt+Tab * or something like that. The window also don't get any * mouse-enter/leave events. So we have to make it normal * window (which is visible to the window manager and * appears in the tasks bar). Therefore we don't use it * anyomore. */ m_popup->setWindowFlags(Qt::FramelessWindowHint | Qt::Window | Qt::NoDropShadowWindowHint); m_popup->m_parent = this; m_popup->m_isPopup=true; } m_popup->setCanvas(m_canvas); m_popup->updateSettings(); } void KisColorSelectorBase::showPopup(Move move) { // This slot may be called by some action, // so we need to be able to handle it lazyCreatePopup(); QPoint cursorPos = QCursor::pos(); if (move == MoveToMousePosition) { m_popup->move(QPoint(cursorPos.x()-m_popup->width()/2, cursorPos.y()-m_popup->height()/2)); QRect rc = m_popup->geometry(); if (rc.x() < 0) rc.setX(0); if (rc.y() < 0) rc.setY(0); m_popup->setGeometry(rc); } if (m_colorPreviewPopup) { m_colorPreviewPopup->hide(); } m_popup->show(); m_popup->m_colorPreviewPopup->show(); } void KisColorSelectorBase::hidePopup() { KIS_ASSERT_RECOVER_RETURN(m_isPopup); m_colorPreviewPopup->hide(); hide(); } void KisColorSelectorBase::commitColor(const KoColor& color, Acs::ColorRole role) { if (!m_canvas) return; m_colorUpdateAllowed=false; if (role == Acs::Foreground) m_canvas->resourceManager()->setForegroundColor(color); else m_canvas->resourceManager()->setBackgroundColor(color); m_colorUpdateAllowed=true; } void KisColorSelectorBase::showColorPreview() { if(m_colorPreviewPopup->isHidden()) { m_colorPreviewPopup->show(); } } void KisColorSelectorBase::updateColorPreview(const KoColor &color) { m_colorPreviewPopup->setQColor(converter()->toQColor(color)); } void KisColorSelectorBase::canvasResourceChanged(int key, const QVariant &v) { if (key == KoCanvasResourceProvider::ForegroundColor || key == KoCanvasResourceProvider::BackgroundColor) { KoColor realColor(v.value()); updateColorPreview(realColor); if (m_colorUpdateAllowed && !m_colorUpdateSelf) { setColor(realColor); } } } const KoColorSpace* KisColorSelectorBase::colorSpace() const { return converter()->paintingColorSpace(); } void KisColorSelectorBase::updateSettings() { if(m_popup) { m_popup->updateSettings(); } KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); int zoomSelectorOptions = (int) cfg.readEntry("zoomSelectorOptions", 0) ; if (zoomSelectorOptions == 0) { setPopupBehaviour(false, true); // middle mouse button click will open zoom selector } else if (zoomSelectorOptions == 1) { setPopupBehaviour(true, false); // move over will open the zoom selector } else { setPopupBehaviour(false, false); // do not show zoom selector } if(m_isPopup) { m_hideOnMouseClick = cfg.readEntry("hidePopupOnClickCheck", false); const int zoomSize = cfg.readEntry("zoomSize", 280); resize(zoomSize, zoomSize); } reset(); } void KisColorSelectorBase::reset() { update(); } void KisColorSelectorBase::updateBaseColorPreview(const KoColor &color) { m_colorPreviewPopup->setBaseColor(converter()->toQColor(color)); } void KisColorSelectorBase::updatePreviousColorPreview() { m_colorPreviewPopup->setPreviousColor(); } void KisColorSelectorBase::updateLastUsedColorPreview(const KoColor &color) { m_colorPreviewPopup->setLastUsedColor(converter()->toQColor(color)); } KisDisplayColorConverter* KisColorSelectorBase::converter() const { return m_canvas ? m_canvas->displayColorConverter() : KisDisplayColorConverter::dumbConverterInstance(); } void KisColorSelectorBase::tryHideAllPopups() { if (m_colorPreviewPopup->isVisible()) { - m_colorUpdateSelf=false; //this is for allowing advanced selector to listen to outside colour-change events. + m_colorUpdateSelf=false; //this is for allowing advanced selector to listen to outside color-change events. m_colorPreviewPopup->hide(); } if (m_popup && m_popup->isVisible()) { m_popup->m_hideTimer->start(); } if (m_isPopup && !m_hideTimer->isActive()) { m_hideTimer->start(); } } void KisColorSelectorBase::mouseMoveEvent(QMouseEvent *event) { event->accept(); } diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_base.h b/plugins/dockers/advancedcolorselector/kis_color_selector_base.h index eca0b2f979..9358370992 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_base.h +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_base.h @@ -1,132 +1,132 @@ /* * Copyright (c) 2010 Adam Celarek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software */ #ifndef KIS_COLOR_SELECTOR_BASE_H #define KIS_COLOR_SELECTOR_BASE_H #include #include #include #include #include "kis_acs_types.h" #include "kis_signal_compressor_with_param.h" class KoColor; class QTimer; class KoColorSpace; class KisCanvas2; class KisColorPreviewPopup; class KisDisplayColorConverter; class KoGamutMask; /// Base class for all color selectors, that should support color management and zooming. class KisColorSelectorBase : public QWidget { Q_OBJECT public: enum Move {MoveToMousePosition, DontMove}; explicit KisColorSelectorBase(QWidget *parent = 0); ~KisColorSelectorBase() override; void setPopupBehaviour(bool onMouseOver, bool onMouseClick); void setColorSpace(const KoColorSpace* colorSpace); virtual void setCanvas(KisCanvas2* canvas); virtual void unsetCanvas(); const KoColorSpace* colorSpace() const; KisDisplayColorConverter* converter() const; void tryHideAllPopups(); public: void updateColor(const KoColor &color, Acs::ColorRole role, bool needsExplicitColorReset); void updateColorPreview(const KoColor &color); void showColorPreview(); void updateBaseColorPreview(const KoColor &color); void updatePreviousColorPreview(); virtual void setColor(const KoColor& color); public Q_SLOTS: /** * Flushes caches and redraws the selectors */ virtual void reset(); virtual void updateSettings(); virtual void showPopup(Move move=MoveToMousePosition); public: void enterEvent(QEvent *e) override; void leaveEvent(QEvent *e) override; void mousePressEvent(QMouseEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; protected: void keyPressEvent(QKeyEvent *) override; virtual KisColorSelectorBase* createPopup() const = 0; void dragEnterEvent(QDragEnterEvent *) override; void dropEvent(QDropEvent *) override; void setHidingTime(int time); bool isPopup() const { return m_isPopup; } void mouseMoveEvent(QMouseEvent *event) override; void requestUpdateColorAndPreview(const KoColor &color, Acs::ColorRole role); private: void commitColor(const KoColor& koColor, Acs::ColorRole role); protected Q_SLOTS: void hidePopup(); - /// if you overwrite this, keep in mind, that you should set the colour only, if m_colorUpdateAllowed is true + /// if you overwrite this, keep in mind, that you should set the color only, if m_colorUpdateAllowed is true virtual void canvasResourceChanged(int key, const QVariant& v); void updateLastUsedColorPreview(const KoColor &color); public: // This is a private interface for signal compressor, don't use it. // Use requestUpdateColorAndPreview() instead void slotUpdateColorAndPreview(QPair color); private: void lazyCreatePopup(); protected: QPointer m_canvas; KisColorSelectorBase* m_popup; QWidget* m_parent; bool m_colorUpdateAllowed; bool m_colorUpdateSelf; private: QTimer* m_hideTimer; bool m_popupOnMouseOver; bool m_popupOnMouseClick; mutable const KoColorSpace* m_colorSpace; bool m_isPopup; //this instance is a popup bool m_hideOnMouseClick; KisColorPreviewPopup* m_colorPreviewPopup; typedef KisSignalCompressorWithParam> ColorCompressorType; QScopedPointer m_updateColorCompressor; }; #endif diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_component.h b/plugins/dockers/advancedcolorselector/kis_color_selector_component.h index eca2e3e37a..79da0c12aa 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_component.h +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_component.h @@ -1,131 +1,131 @@ /* * Copyright (c) 2010 Adam Celarek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software */ #ifndef KIS_COLOR_SELECTOR_COMPONENT_H #define KIS_COLOR_SELECTOR_COMPONENT_H #include #include #include #include "kis_color_selector.h" class KoColorSpace; class QPainter; class KisColorSelectorComponent : public QObject { Q_OBJECT public: typedef KisColorSelectorConfiguration::Parameters Parameter; typedef KisColorSelectorConfiguration::Type Type; explicit KisColorSelectorComponent(KisColorSelector* parent); void setGeometry(int x, int y, int width, int height); void paintEvent(QPainter*); /// saves the mouse position, so that a blip can be created. virtual void mouseEvent(int x, int y); /// return the color, that was selected by calling mouseEvent KoColor currentColor(); int width() const; int height() const; /// setConfiguration can be ignored (for instance ring and triangle, as they can have only one config) void setConfiguration(Parameter param, Type type); /// set the color, blibs etc virtual void setColor(const KoColor& color); /// force subsequent redraw of the component void setDirty(); /// returns true, if this component wants to grab the mouse (normally true, if containsPoint returns true) bool wantsGrab(int x, int y) {return containsPointInComponentCoords(x-m_x, y-m_y);} void setGamutMask(KoGamutMask* gamutMask); void unsetGamutMask(); void updateGamutMaskPreview(); void toggleGamutMask(bool state); public Q_SLOTS: /// set hue, saturation, value or/and lightness /// unused parameters should be set to -1 void setParam(qreal hue, qreal hsvSaturation, qreal value, qreal hslSaturation, qreal lightness, qreal hsiSaturation, qreal intensity, qreal hsySaturation, qreal luma); Q_SIGNALS: /// request for repaint, for instance, if the hue changes. void update(); /// -1, if unaffected void paramChanged(qreal hue, qreal hsvSaturation, qreal value, qreal hslSaturation, qreal lightness, qreal hsiSaturation, qreal intensity, qreal hsySaturation, qreal luma); protected: const KoColorSpace* colorSpace() const; - /// returns true, if ether the colour space, the size or the parameters have changed since the last paint event + /// returns true, if ether the color space, the size or the parameters have changed since the last paint event bool isDirty() const; - /// this method must be overloaded to return the colour at position x/y and draw a marker on that position + /// this method must be overloaded to return the color at position x/y and draw a marker on that position virtual KoColor selectColor(int x, int y) = 0; /// paint component using given painter /// the component should respect width() and height() (eg. scale to width and height), but doesn't /// have to care about x/y coordinates (top left corner) virtual void paint(QPainter*) = 0; /// a subclass can implement this method, the default returns true if the coordinates are in the component rect /// values for the subclasses are provided in component coordinates, eg (0,0) is top left of component virtual bool containsPointInComponentCoords(int x, int y) const; /// a subclass can implement this method to note that the point, although it is in /// containsPointInComponentCoords area, still cannot be selected as a color (e.g. /// it is masked out). Default implementation always returns true. virtual bool allowsColorSelectionAtPoint(const QPoint &) const; // Workaround for Bug 287001 void setLastMousePosition(int x, int y); qreal m_hue; qreal m_hsvSaturation; qreal m_value; qreal m_hslSaturation; qreal m_lightness; qreal m_hsiSaturation; qreal m_intensity; qreal m_hsySaturation; qreal m_luma; Parameter m_parameter; Type m_type; KisColorSelector* m_parent; bool m_gamutMaskOn; KoGamutMask* m_currentGamutMask; bool m_maskPreviewActive; qreal m_lastX; qreal m_lastY; int m_x; int m_y; private: int m_width; int m_height; bool m_dirty; const KoColorSpace* m_lastColorSpace; KoColor m_lastSelectedColor; }; #endif // KIS_COLOR_SELECTOR_COMPONENT_H diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp index 3c3be144d0..86d688fa15 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_settings.cpp @@ -1,632 +1,632 @@ /* * Copyright (C) 2010 Celarek Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_selector_settings.h" #include "ui_wdg_color_selector_settings.h" #include #include #include #include #include #include #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorProfile.h" #include "kis_color_selector_combo_box.h" #include "kis_color_selector.h" #include "kis_config.h" KisColorSelectorSettings::KisColorSelectorSettings(QWidget *parent) : KisPreferenceSet(parent), ui(new Ui::KisColorSelectorSettings) { ui->setupUi(this); resize(minimumSize()); ui->colorSelectorConfiguration->setColorSpace(ui->colorSpace->currentColorSpace()); ui->useDifferentColorSpaceCheckbox->setChecked(false); connect(ui->useDifferentColorSpaceCheckbox, SIGNAL(clicked(bool)), this, SLOT(useDifferentColorSpaceChecked(bool))); /* color docker selector drop down */ ui->dockerColorSettingsComboBox->addItem(i18n("Advanced Color Selector")); //ui->dockerColorSettingsComboBox->addItem(i18n("Color Sliders")); ui->dockerColorSettingsComboBox->addItem(i18n("Color Hotkeys")); ui->dockerColorSettingsComboBox->setCurrentIndex(0); // start off seeing advanced color selector properties connect( ui->dockerColorSettingsComboBox, SIGNAL(currentIndexChanged(int)),this, SLOT(changedColorDocker(int))); changedColorDocker(0); /* advanced color docker options */ ui->dockerResizeOptionsComboBox->addItem(i18n("Change to a Horizontal Layout")); ui->dockerResizeOptionsComboBox->addItem(i18n("Hide Shade Selector")); ui->dockerResizeOptionsComboBox->addItem(i18n("Do Nothing")); ui->dockerResizeOptionsComboBox->setCurrentIndex(0); ui->zoomSelectorOptionComboBox->addItem(i18n("When Pressing Middle Mouse Button")); ui->zoomSelectorOptionComboBox->addItem(i18n("On Mouse Over")); ui->zoomSelectorOptionComboBox->addItem(i18n("Never")); ui->zoomSelectorOptionComboBox->setCurrentIndex(0); ui->colorSelectorTypeComboBox->addItem(i18n("HSV")); ui->colorSelectorTypeComboBox->addItem(i18n("HSL")); ui->colorSelectorTypeComboBox->addItem(i18n("HSI")); ui->colorSelectorTypeComboBox->addItem(i18n("HSY'")); ui->colorSelectorTypeComboBox->setCurrentIndex(0); connect( ui->colorSelectorTypeComboBox, SIGNAL(currentIndexChanged(int)),this, SLOT(changedACSColorSelectorType(int))); changedACSColorSelectorType(0); // initialize everything to HSV at the start ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSV")); ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSL")); ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSI")); ui->ACSshadeSelectorMyPaintColorModelComboBox->addItem(i18n("HSY'")); ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(0); ui->ACSShadeSelectorTypeComboBox->addItem(i18n("MyPaint")); ui->ACSShadeSelectorTypeComboBox->addItem(i18n("Minimal")); ui->ACSShadeSelectorTypeComboBox->addItem(i18n("Do Not Show")); ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(0); changedACSShadeSelectorType(0); // show/hide UI elements for MyPaint settings connect( ui->ACSShadeSelectorTypeComboBox, SIGNAL(currentIndexChanged(int)),this, SLOT(changedACSShadeSelectorType(int))); ui->commonColorsAlignVertical->setChecked(true); ui->commonColorsAlignHorizontal->setChecked(true); connect( ui->commonColorsAlignHorizontal, SIGNAL(toggled(bool)), this, SLOT(changedACSColorAlignment(bool))); connect( ui->lastUsedColorsAlignHorizontal, SIGNAL(toggled(bool)), this, SLOT(changedACSLastUsedColorAlignment(bool))); changedACSColorAlignment(ui->commonColorsAlignHorizontal->isChecked()); changedACSLastUsedColorAlignment(ui->lastUsedColorsAlignHorizontal->isChecked()); connect(ui->colorSpace, SIGNAL(colorSpaceChanged(const KoColorSpace*)), ui->colorSelectorConfiguration, SLOT(setColorSpace(const KoColorSpace*))); connect(this, SIGNAL(hsxchanged(int)), ui->colorSelectorConfiguration, SLOT(setList(int))); connect(ui->minimalShadeSelectorLineCount, SIGNAL(valueChanged(int)), ui->minimalShadeSelectorLineSettings, SLOT(setLineCount(int))); connect(ui->minimalShadeSelectorLineSettings, SIGNAL(lineCountChanged(int)), ui->minimalShadeSelectorLineCount, SLOT(setValue(int))); connect(ui->minimalShadeSelectorAsGradient, SIGNAL(toggled(bool)), ui->minimalShadeSelectorLineSettings, SIGNAL(setGradient(bool))); connect(ui->minimalShadeSelectorAsColorPatches, SIGNAL(toggled(bool)), ui->minimalShadeSelectorLineSettings, SIGNAL(setPatches(bool))); connect(ui->minimalShadeSelectorLineHeight, SIGNAL(valueChanged(int)), ui->minimalShadeSelectorLineSettings, SIGNAL(setLineHeight(int))); connect(ui->minimalShadeSelectorPatchesPerLine, SIGNAL(valueChanged(int)), ui->minimalShadeSelectorLineSettings, SIGNAL(setPatchCount(int))); } KisColorSelectorSettings::~KisColorSelectorSettings() { delete ui; } QString KisColorSelectorSettings::id() { return QString("advancedColorSelector"); } QString KisColorSelectorSettings::name() { return header(); } QString KisColorSelectorSettings::header() { return QString(i18n("Color Selector Settings")); } QIcon KisColorSelectorSettings::icon() { return KisIconUtils::loadIcon("extended_color_selector"); } void KisColorSelectorSettings::savePreferences() const { // write cfg KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); KConfigGroup hsxcfg = KSharedConfig::openConfig()->group("hsxColorSlider"); KConfigGroup hotkeycfg = KSharedConfig::openConfig()->group("colorhotkeys"); // advanced color selector cfg.writeEntry("onDockerResize", ui->dockerResizeOptionsComboBox->currentIndex()); cfg.writeEntry("zoomSelectorOptions", ui->zoomSelectorOptionComboBox->currentIndex() ); cfg.writeEntry("zoomSize", ui->popupSize->value()); cfg.writeEntry("showColorSelector", ui->chkShowColorSelector->isChecked()); bool useCustomColorSpace = ui->useDifferentColorSpaceCheckbox->isChecked(); const KoColorSpace* colorSpace = useCustomColorSpace ? ui->colorSpace->currentColorSpace() : 0; KisConfig kisconfig(false); kisconfig.setCustomColorSelectorColorSpace(colorSpace); //color patches cfg.writeEntry("lastUsedColorsShow", ui->lastUsedColorsShow->isChecked()); cfg.writeEntry("lastUsedColorsAlignment", ui->lastUsedColorsAlignVertical->isChecked()); cfg.writeEntry("lastUsedColorsScrolling", ui->lastUsedColorsAllowScrolling->isChecked()); cfg.writeEntry("lastUsedColorsNumCols", ui->lastUsedColorsNumCols->value()); cfg.writeEntry("lastUsedColorsNumRows", ui->lastUsedColorsNumRows->value()); cfg.writeEntry("lastUsedColorsCount", ui->lastUsedColorsPatchCount->value()); cfg.writeEntry("lastUsedColorsWidth", ui->lastUsedColorsWidth->value()); cfg.writeEntry("lastUsedColorsHeight", ui->lastUsedColorsHeight->value()); cfg.writeEntry("commonColorsShow", ui->commonColorsShow->isChecked()); cfg.writeEntry("commonColorsAlignment", ui->commonColorsAlignVertical->isChecked()); cfg.writeEntry("commonColorsScrolling", ui->commonColorsAllowScrolling->isChecked()); cfg.writeEntry("commonColorsNumCols", ui->commonColorsNumCols->value()); cfg.writeEntry("commonColorsNumRows", ui->commonColorsNumRows->value()); cfg.writeEntry("commonColorsCount", ui->commonColorsPatchCount->value()); cfg.writeEntry("commonColorsWidth", ui->commonColorsWidth->value()); cfg.writeEntry("commonColorsHeight", ui->commonColorsHeight->value()); cfg.writeEntry("commonColorsAutoUpdate", ui->commonColorsAutoUpdate->isChecked()); //shade selector int shadeSelectorTypeIndex = ui->ACSShadeSelectorTypeComboBox->currentIndex(); if(shadeSelectorTypeIndex == 0) { cfg.writeEntry("shadeSelectorType", "MyPaint"); } else if (shadeSelectorTypeIndex == 1) { cfg.writeEntry("shadeSelectorType", "Minimal"); } else { cfg.writeEntry("shadeSelectorType", "Hidden"); } cfg.writeEntry("shadeSelectorUpdateOnRightClick", ui->shadeSelectorUpdateOnRightClick->isChecked()); cfg.writeEntry("shadeSelectorUpdateOnForeground", ui->shadeSelectorUpdateOnForeground->isChecked()); cfg.writeEntry("shadeSelectorUpdateOnLeftClick", ui->shadeSelectorUpdateOnLeftClick->isChecked()); cfg.writeEntry("shadeSelectorUpdateOnBackground", ui->shadeSelectorUpdateOnBackground->isChecked()); cfg.writeEntry("hidePopupOnClickCheck", ui->hidePopupOnClickCheck->isChecked()); //mypaint model int shadeMyPaintComboBoxIndex = ui->ACSshadeSelectorMyPaintColorModelComboBox->currentIndex(); if (shadeMyPaintComboBoxIndex == 0 ) { cfg.writeEntry("shadeMyPaintType", "HSV"); } else if (shadeMyPaintComboBoxIndex == 1 ) { cfg.writeEntry("shadeMyPaintType", "HSL"); } else if (shadeMyPaintComboBoxIndex == 2 ) { cfg.writeEntry("shadeMyPaintType", "HSI"); } else { // HSY cfg.writeEntry("shadeMyPaintType", "HSY"); } cfg.writeEntry("minimalShadeSelectorAsGradient", ui->minimalShadeSelectorAsGradient->isChecked()); cfg.writeEntry("minimalShadeSelectorPatchCount", ui->minimalShadeSelectorPatchesPerLine->value()); cfg.writeEntry("minimalShadeSelectorLineConfig", ui->minimalShadeSelectorLineSettings->toString()); cfg.writeEntry("minimalShadeSelectorLineHeight", ui->minimalShadeSelectorLineHeight->value()); //color selector KisColorSelectorComboBox* cstw = dynamic_cast(ui->colorSelectorConfiguration); cfg.writeEntry("colorSelectorConfiguration", cstw->configuration().toString()); cfg.writeEntry("hsxSettingType", ui->colorSelectorTypeComboBox->currentIndex()); //luma// cfg.writeEntry("lumaR", ui->l_lumaR->value()); cfg.writeEntry("lumaG", ui->l_lumaG->value()); cfg.writeEntry("lumaB", ui->l_lumaB->value()); cfg.writeEntry("gamma", ui->SP_Gamma->value()); //slider// hsxcfg.writeEntry("hsvH", ui->csl_hsvH->isChecked()); hsxcfg.writeEntry("hsvS", ui->csl_hsvS->isChecked()); hsxcfg.writeEntry("hsvV", ui->csl_hsvV->isChecked()); hsxcfg.writeEntry("hslH", ui->csl_hslH->isChecked()); hsxcfg.writeEntry("hslS", ui->csl_hslS->isChecked()); hsxcfg.writeEntry("hslL", ui->csl_hslL->isChecked()); hsxcfg.writeEntry("hsiH", ui->csl_hsiH->isChecked()); hsxcfg.writeEntry("hsiS", ui->csl_hsiS->isChecked()); hsxcfg.writeEntry("hsiI", ui->csl_hsiI->isChecked()); hsxcfg.writeEntry("hsyH", ui->csl_hsyH->isChecked()); hsxcfg.writeEntry("hsyS", ui->csl_hsyS->isChecked()); hsxcfg.writeEntry("hsyY", ui->csl_hsyY->isChecked()); //hotkeys// hotkeycfg.writeEntry("steps_lightness", ui->sb_lightness->value()); hotkeycfg.writeEntry("steps_saturation", ui->sb_saturation->value()); hotkeycfg.writeEntry("steps_hue", ui->sb_hue->value()); hotkeycfg.writeEntry("steps_redgreen", ui->sb_rg->value()); hotkeycfg.writeEntry("steps_blueyellow", ui->sb_by->value()); emit settingsChanged(); } //void KisColorSelectorSettings::changeEvent(QEvent *e) //{ // QDialog::changeEvent(e); // switch (e->type()) { // case QEvent::LanguageChange: // ui->retranslateUi(this); // break; // default: // break; // } //} void KisColorSelectorSettings::changedColorDocker(int index) { // having a situation where too many sections are visible makes the window too large. turn all off before turning more on ui->colorSliderOptions->hide(); ui->advancedColorSelectorOptions->hide(); ui->hotKeyOptions->hide(); if (index == 0) { // advanced color selector options selected ui->advancedColorSelectorOptions->show(); ui->colorSliderOptions->hide(); ui->hotKeyOptions->hide(); } // else if (index == 1) { // color slider options selected // ui->advancedColorSelectorOptions->hide(); // ui->hotKeyOptions->hide(); // ui->colorSliderOptions->show(); // } else { ui->colorSliderOptions->hide(); ui->advancedColorSelectorOptions->hide(); ui->hotKeyOptions->show(); } } void KisColorSelectorSettings::changedACSColorSelectorType(int index) { ui->lumaCoefficientGroupbox->setVisible(false); if (index == 0) { // HSV - ui->ACSTypeDescriptionLabel->setText(i18n("Values goes from black to white, or black to the most saturated colour. Saturation, in turn, goes from the most saturated colour to white, grey or black.")); + ui->ACSTypeDescriptionLabel->setText(i18n("Values goes from black to white, or black to the most saturated color. Saturation, in turn, goes from the most saturated color to white, gray or black.")); } else if (index == 1) { // HSL - ui->ACSTypeDescriptionLabel->setText(i18n("Lightness goes from black to white, with middle grey being equal to the most saturated colour.")); + ui->ACSTypeDescriptionLabel->setText(i18n("Lightness goes from black to white, with middle gray being equal to the most saturated color.")); } else if (index == 2) { // HSI ui->ACSTypeDescriptionLabel->setText(i18n("Intensity maps to the sum of rgb components")); } else { // HSY' ui->ACSTypeDescriptionLabel->setText(i18n("Luma(Y') is weighted by its coefficients which are configurable. Default values are set to 'rec 709'.")); ui->lumaCoefficientGroupbox->setVisible(true); } ui->colorSelectorConfiguration->update(); emit hsxchanged(index); } void KisColorSelectorSettings::changedACSColorAlignment(bool toggled) { // this slot is tied to the horizontal radio button's state being changed // you can infer the vertical state ui->lbl_commonColorsNumCols->setDisabled(toggled); ui->commonColorsNumCols->setDisabled(toggled); ui->lbl_commonColorsNumRows->setEnabled(toggled); ui->commonColorsNumRows->setEnabled(toggled); } void KisColorSelectorSettings::changedACSLastUsedColorAlignment(bool toggled) { // this slot is tied to the horizontal radio button's state being changed // you can infer the vertical state ui->lbl_lastUsedNumCols->setDisabled(toggled); ui->lastUsedColorsNumCols->setDisabled(toggled); ui->lbl_lastUsedNumRows->setEnabled(toggled); ui->lastUsedColorsNumRows->setEnabled(toggled); } void KisColorSelectorSettings::changedACSShadeSelectorType(int index) { if (index == 0) { // MyPaint ui->minimalShadeSelectorGroup->hide(); ui->myPaintColorModelLabel->show(); ui->ACSshadeSelectorMyPaintColorModelComboBox->show(); } else if (index == 1) { // Minimal ui->minimalShadeSelectorGroup->show(); ui->myPaintColorModelLabel->hide(); ui->ACSshadeSelectorMyPaintColorModelComboBox->hide(); }else { // do not show ui->minimalShadeSelectorGroup->hide(); ui->myPaintColorModelLabel->hide(); ui->ACSshadeSelectorMyPaintColorModelComboBox->hide(); } } void KisColorSelectorSettings::useDifferentColorSpaceChecked(bool enabled) { ui->colorSpace->setEnabled(enabled); } void KisColorSelectorSettings::loadPreferences() { //read cfg //don't forget to also add a new entry to the default preferences KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); KConfigGroup hsxcfg = KSharedConfig::openConfig()->group("hsxColorSlider"); KConfigGroup hotkeycfg = KSharedConfig::openConfig()->group("colorhotkeys"); // Advanced color selector ui->dockerResizeOptionsComboBox->setCurrentIndex( (int)cfg.readEntry("onDockerResize", 0) ); ui->zoomSelectorOptionComboBox->setCurrentIndex( (int) cfg.readEntry("zoomSelectorOptions", 0) ); ui->popupSize->setValue(cfg.readEntry("zoomSize", 280)); ui->chkShowColorSelector->setChecked((bool) cfg.readEntry("showColorSelector", true)); { KisConfig kisconfig(true); const KoColorSpace *cs = kisconfig.customColorSelectorColorSpace(); if (cs) { ui->useDifferentColorSpaceCheckbox->setChecked(true); ui->colorSpace->setEnabled(true); ui->colorSpace->setCurrentColorSpace(cs); } else { ui->useDifferentColorSpaceCheckbox->setChecked(false); ui->colorSpace->setEnabled(false); } } //color patches ui->lastUsedColorsShow->setChecked(cfg.readEntry("lastUsedColorsShow", true)); bool a = cfg.readEntry("lastUsedColorsAlignment", true); ui->lastUsedColorsAlignVertical->setChecked(a); ui->lastUsedColorsAlignHorizontal->setChecked(!a); ui->lastUsedColorsAllowScrolling->setChecked(cfg.readEntry("lastUsedColorsScrolling", true)); ui->lastUsedColorsNumCols->setValue(cfg.readEntry("lastUsedColorsNumCols", 1)); ui->lastUsedColorsNumRows->setValue(cfg.readEntry("lastUsedColorsNumRows", 1)); ui->lastUsedColorsPatchCount->setValue(cfg.readEntry("lastUsedColorsCount", 20)); ui->lastUsedColorsWidth->setValue(cfg.readEntry("lastUsedColorsWidth", 16)); ui->lastUsedColorsHeight->setValue(cfg.readEntry("lastUsedColorsHeight", 16)); ui->commonColorsShow->setChecked(cfg.readEntry("commonColorsShow", true)); a = cfg.readEntry("commonColorsAlignment", false); ui->commonColorsAlignVertical->setChecked(a); ui->commonColorsAlignHorizontal->setChecked(!a); ui->commonColorsAllowScrolling->setChecked(cfg.readEntry("commonColorsScrolling", true)); ui->commonColorsNumCols->setValue(cfg.readEntry("commonColorsNumCols", 1)); ui->commonColorsNumRows->setValue(cfg.readEntry("commonColorsNumRows", 1)); ui->commonColorsPatchCount->setValue(cfg.readEntry("commonColorsCount", 12)); ui->commonColorsWidth->setValue(cfg.readEntry("commonColorsWidth", 16)); ui->commonColorsHeight->setValue(cfg.readEntry("commonColorsHeight", 16)); ui->commonColorsAutoUpdate->setChecked(cfg.readEntry("commonColorsAutoUpdate", false)); //shade selector QString shadeSelectorType=cfg.readEntry("shadeSelectorType", "Minimal"); if ( shadeSelectorType == "MyPaint") { ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(0); } else if (shadeSelectorType == "Minimal") { ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(1); } else { // Hidden ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(2); } ui->shadeSelectorUpdateOnRightClick->setChecked(cfg.readEntry("shadeSelectorUpdateOnRightClick", false)); ui->shadeSelectorUpdateOnLeftClick->setChecked(cfg.readEntry("shadeSelectorUpdateOnLeftClick", false)); ui->shadeSelectorUpdateOnForeground->setChecked(cfg.readEntry("shadeSelectorUpdateOnForeground", true)); ui->shadeSelectorUpdateOnBackground->setChecked(cfg.readEntry("shadeSelectorUpdateOnBackground", true)); ui->hidePopupOnClickCheck->setChecked(cfg.readEntry("hidePopupOnClickCheck", false)); QString shadeMyPaintType = cfg.readEntry("shadeMyPaintType", "HSV"); if (shadeMyPaintType == "HSV" ) { ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(0); } else if (shadeMyPaintType == "HSL" ) { ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(1); } else if (shadeMyPaintType == "HSI" ) { ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(2); } else { // HSY ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(3); } bool asGradient = cfg.readEntry("minimalShadeSelectorAsGradient", true); if(asGradient) ui->minimalShadeSelectorAsGradient->setChecked(true); else ui->minimalShadeSelectorAsColorPatches->setChecked(true); ui->minimalShadeSelectorPatchesPerLine->setValue(cfg.readEntry("minimalShadeSelectorPatchCount", 10)); ui->minimalShadeSelectorLineSettings->fromString(cfg.readEntry("minimalShadeSelectorLineConfig", "0|0.2|0|0|0|0|0;1|0|1|1|0|0|0;2|0|-1|1|0|0|0;")); ui->minimalShadeSelectorLineHeight->setValue(cfg.readEntry("minimalShadeSelectorLineHeight", 10)); int hsxSettingType= (int)cfg.readEntry("hsxSettingType", 0); ui->colorSelectorTypeComboBox->setCurrentIndex(hsxSettingType); //color selector KisColorSelectorComboBox* cstw = dynamic_cast(ui->colorSelectorConfiguration); cstw->setConfiguration(KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", "3|0|5|0"))); // triangle selector //luma values// ui->l_lumaR->setValue(cfg.readEntry("lumaR", 0.2126)); ui->l_lumaG->setValue(cfg.readEntry("lumaG", 0.7152)); ui->l_lumaB->setValue(cfg.readEntry("lumaB", 0.0722)); ui->SP_Gamma->setValue(cfg.readEntry("gamma", 2.2)); //color sliders// ui->csl_hsvH->setChecked(hsxcfg.readEntry("hsvH", false)); ui->csl_hsvS->setChecked(hsxcfg.readEntry("hsvS", false)); ui->csl_hsvV->setChecked(hsxcfg.readEntry("hsvV", false)); ui->csl_hslH->setChecked(hsxcfg.readEntry("hslH", true)); ui->csl_hslS->setChecked(hsxcfg.readEntry("hslS", true)); ui->csl_hslL->setChecked(hsxcfg.readEntry("hslL", true)); ui->csl_hsiH->setChecked(hsxcfg.readEntry("hsiH", false)); ui->csl_hsiS->setChecked(hsxcfg.readEntry("hsiS", false)); ui->csl_hsiI->setChecked(hsxcfg.readEntry("hsiI", false)); ui->csl_hsyH->setChecked(hsxcfg.readEntry("hsyH", false)); ui->csl_hsyS->setChecked(hsxcfg.readEntry("hsyS", false)); ui->csl_hsyY->setChecked(hsxcfg.readEntry("hsyY", false)); //hotkeys// ui->sb_lightness->setValue(hotkeycfg.readEntry("steps_lightness", 10)); ui->sb_saturation->setValue(hotkeycfg.readEntry("steps_saturation", 10)); ui->sb_hue->setValue(hotkeycfg.readEntry("steps_hue", 36)); ui->sb_rg->setValue(hotkeycfg.readEntry("steps_redgreen", 10)); ui->sb_by->setValue(hotkeycfg.readEntry("steps_blueyellow", 10)); } void KisColorSelectorSettings::loadDefaultPreferences() { //set defaults //if you change something, don't forget that loadPreferences should be kept in sync // advanced color selector docker ui->dockerResizeOptionsComboBox->setCurrentIndex(0); ui->zoomSelectorOptionComboBox->setCurrentIndex(0); ui->popupSize->setValue(280); ui->chkShowColorSelector->setChecked(true); ui->useDifferentColorSpaceCheckbox->setChecked(false); ui->colorSpace->setCurrentColorModel(KoID("RGBA")); ui->colorSpace->setCurrentColorDepth(KoID("U8")); ui->colorSpace->setCurrentProfile(KoColorSpaceRegistry::instance()->rgb8()->profile()->name()); //color patches ui->lastUsedColorsShow->setChecked(true); ui->lastUsedColorsAlignVertical->setChecked(true); ui->lastUsedColorsAlignHorizontal->setChecked(false); ui->lastUsedColorsAllowScrolling->setChecked(true); ui->lastUsedColorsNumCols->setValue(1); ui->lastUsedColorsNumRows->setValue(1); ui->lastUsedColorsPatchCount->setValue(20); ui->lastUsedColorsWidth->setValue(16); ui->lastUsedColorsHeight->setValue(16); ui->commonColorsShow->setChecked(true); ui->commonColorsAlignVertical->setChecked(false); ui->commonColorsAlignHorizontal->setChecked(true); ui->commonColorsAllowScrolling->setChecked(true); ui->commonColorsNumCols->setValue(1); ui->commonColorsNumRows->setValue(1); ui->commonColorsPatchCount->setValue(12); ui->commonColorsWidth->setValue(16); ui->commonColorsHeight->setValue(16); ui->commonColorsAutoUpdate->setChecked(false); //shade selector ui->ACSShadeSelectorTypeComboBox->setCurrentIndex(1); // Minimal ui->ACSshadeSelectorMyPaintColorModelComboBox->setCurrentIndex(0); ui->shadeSelectorUpdateOnRightClick->setChecked(false); ui->shadeSelectorUpdateOnLeftClick->setChecked(false); ui->shadeSelectorUpdateOnForeground->setChecked(true); ui->shadeSelectorUpdateOnBackground->setChecked(true); bool asGradient = true; if(asGradient) ui->minimalShadeSelectorAsGradient->setChecked(true); else ui->minimalShadeSelectorAsColorPatches->setChecked(true); ui->minimalShadeSelectorPatchesPerLine->setValue(10); ui->minimalShadeSelectorLineSettings->fromString("0|0.2|0|0|0|0|0;1|0|1|1|0|0|0;2|0|-1|1|0|0|0;"); ui->minimalShadeSelectorLineHeight->setValue(10); // set advanced color selector to use HSV ui->colorSelectorTypeComboBox->setCurrentIndex(0); KisColorSelectorComboBox* cstw = dynamic_cast(ui->colorSelectorConfiguration); cstw->setConfiguration(KisColorSelectorConfiguration("3|0|5|0")); // triangle selector //luma// ui->l_lumaR->setValue(0.2126); ui->l_lumaG->setValue(0.7152); ui->l_lumaB->setValue(0.0722); ui->SP_Gamma->setValue(2.2); //color sliders// ui->csl_hsvH->setChecked(false); ui->csl_hsvS->setChecked(false); ui->csl_hsvV->setChecked(false); ui->csl_hslH->setChecked(true); ui->csl_hslS->setChecked(true); ui->csl_hslL->setChecked(true); ui->csl_hsiH->setChecked(false); ui->csl_hsiS->setChecked(false); ui->csl_hsiI->setChecked(false); ui->csl_hsyH->setChecked(false); ui->csl_hsyS->setChecked(false); ui->csl_hsyY->setChecked(false); //hotkeys// ui->sb_lightness->setValue(10); ui->sb_saturation->setValue(10); ui->sb_hue->setValue(36); ui->sb_rg->setValue(10); ui->sb_by->setValue(10); } KisColorSelectorSettingsDialog::KisColorSelectorSettingsDialog(QWidget *parent) : QDialog(parent), m_widget(new KisColorSelectorSettings(this)) { QLayout* l = new QVBoxLayout(this); l->addWidget(m_widget); m_widget->loadPreferences(); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults, Qt::Horizontal, this); l->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), m_widget, SLOT(savePreferences())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()), m_widget, SLOT(loadDefaultPreferences())); } diff --git a/plugins/extensions/colorspaceconversion/wdgconvertcolorspace.ui b/plugins/extensions/colorspaceconversion/wdgconvertcolorspace.ui index 50f1c64fa0..e04d28154f 100644 --- a/plugins/extensions/colorspaceconversion/wdgconvertcolorspace.ui +++ b/plugins/extensions/colorspaceconversion/wdgconvertcolorspace.ui @@ -1,163 +1,163 @@ WdgConvertColorSpace 0 0 487 244 Colorspace Conversion &Rendering Intent For images Hue hopefully maintained (but not required), lightness and saturation sacrificed to maintain the perceived color. White point changed to result in neutral grays. Intended for images. Perceptual true Within and outside gamut; same as Absolute Colorimetric. White point changed to result in neutral grays. If adequate table is present in profile, then, it is used. Else reverts to perceptual intent. Relative colorimetric Best for graphs and charts Hue and saturation maintained with lightness sacrificed to maintain saturation. White point changed to result in neutral grays. Intended for business graphics (make it colorful charts, graphs, overheads, ...) If adequate table is present in profile, then, it is used. Else reverts to perceptual intent. Saturation - Best for spot colours + Best for spot colors Within the destination device gamut; hue, lightness and saturation are maintained. Outside the gamut; hue and lightness are maintained, saturation is sacrificed. White point for source and destination; unchanged. Intended for spot colors (Pantone, TruMatch, logo colors, ...) Absolute colorimetric Use Blackpoint &Compensation Krita tries to automatically check or uncheck this based on the information it can get from the profile you are converting from and the profile you are converting to. Allow Little CMS optimizations (uncheck when using linear light RGB or XYZ) true Qt::Vertical 20 40 KisColorSpaceSelector QWidget
widgets/kis_color_space_selector.h
1
diff --git a/plugins/extensions/imagesplit/dlg_imagesplit.cpp b/plugins/extensions/imagesplit/dlg_imagesplit.cpp index ef428c7a07..82c0cfe232 100644 --- a/plugins/extensions/imagesplit/dlg_imagesplit.cpp +++ b/plugins/extensions/imagesplit/dlg_imagesplit.cpp @@ -1,118 +1,115 @@ /* * dlg_imagesplit.cc - part of KimageShop^WKrayon^WKrita * * Copyright (c) 2009 Boudewijn Rempt * 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 "dlg_imagesplit.h" #include #include #include #include #include DlgImagesplit::DlgImagesplit(KisViewManager* view, const QString &suffix, QStringList listMimeFilter, int defaultMimeIndex) : KoDialog(view->mainWindow()) { m_page = new WdgImagesplit(this); setCaption(i18n("Image Split")); setButtons(Apply | Close); setDefaultButton(Apply); connect(this, SIGNAL(applyClicked()), this, SLOT(applyClicked())); setMainWidget(m_page); m_page->lineEdit->setText(suffix); - m_page->setMinimumWidth(200); - m_page->setMinimumHeight(160); - resize(m_page->sizeHint()); m_page->cmbFileType->clear(); m_page->cmbFileType->addItems(listMimeFilter); m_page->cmbFileType->setCurrentIndex(defaultMimeIndex); cmbIndex = defaultMimeIndex; connect(m_page->chkAutoSave, SIGNAL(stateChanged(int)), SLOT(lineEditEnable())); connect(m_page->cmbFileType, SIGNAL(activated(int)), this, SLOT(setMimeType(int))); } DlgImagesplit::~DlgImagesplit() { } void DlgImagesplit::lineEditEnable() { if (m_page->chkAutoSave->isChecked()) { m_page->lblSuffix->setEnabled(true); m_page->lineEdit->setEnabled(true); m_page->lblFileType->setEnabled(true); m_page->cmbFileType->setEnabled(true); } else { m_page->lblSuffix->setEnabled(false); m_page->lineEdit->setEnabled(false); m_page->lblFileType->setEnabled(false); m_page->cmbFileType->setEnabled(false); } } bool DlgImagesplit::autoSave() { if (m_page->chkAutoSave->isChecked()) return true; else return false; } bool DlgImagesplit::sortHorizontal() { if (m_page->chkHorizontal->isChecked()) return true; else return false; } int DlgImagesplit::horizontalLines() { return m_page->intHorizontalSplitLines->value(); } int DlgImagesplit::verticalLines() { return m_page->intVerticalSplitLines->value(); } QString DlgImagesplit::suffix() { return m_page->lineEdit->text(); } void DlgImagesplit::setMimeType(int index) { cmbIndex = index; } void DlgImagesplit::applyClicked() { accept(); } diff --git a/plugins/extensions/imagesplit/wdg_imagesplit.ui b/plugins/extensions/imagesplit/wdg_imagesplit.ui index fd61f5cd99..693bb0bc96 100644 --- a/plugins/extensions/imagesplit/wdg_imagesplit.ui +++ b/plugins/extensions/imagesplit/wdg_imagesplit.ui @@ -1,156 +1,156 @@ WdgImagesplit 0 0 - 202 - 316 + 258 + 339 Image Size Sort Direction: 10 Hori&zontal 10 &Vertical Prefix Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true File Type Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 10 Autosave on Split Horizontal Lines Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Vertical Lines Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical 20 2 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index cae471e7be..6f8499856b 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,464 +1,464 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QMic.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 "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include #include "kis_qmic_applicator.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisActionPlugin(parent) , m_gmicApplicator(0) { #ifndef Q_OS_MAC KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool,int,QString)), this, SLOT(slotGmicFinished(bool,int,QString))); #endif } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { // dbgPlugins << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); // find the krita-gmic-qt plugin QString pluginPath = PluginSettings::gmicQtPath(); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists() || !QFileInfo(pluginPath).isFile()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Krita cannot find the gmic-qt plugin. You can set the location of the gmic-qt plugin in Settings/Configure Krita.")); return; } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); - connect(viewManager(), SIGNAL(destroyed(QObject*o)), m_pluginProcess, SLOT(terminate())); + connect(viewManager(), SIGNAL(destroyed(QObject*)), m_pluginProcess, SLOT(terminate())); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString())); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } dbgPlugins << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { dbgPlugins << "connected"; if (!viewManager()) return; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); // FIXME: Should use read transaction for Qt >= 5.7: // https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); dbgPlugins << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; QString messageBoxWarningText; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { KisSelectionSP selection = viewManager()->image()->globalSelection(); if (selection) { QRect selectionRect = selection->selectedExactRect(); ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height()); } else { ba = QByteArray::number(viewManager()->image()->width()) + "," + QByteArray::number(viewManager()->image()->height()); } } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect(0.0, 0.0, 1.0, 1.0); if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack dbgPlugins << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { messageBoxWarningText = i18n("Sorry, this output mode is not implemented yet."); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { dbgPlugins << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { dbgPlugins << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } dbgPlugins << "Sending" << QString::fromUtf8(ba); // HACK: Make sure QDataStream does not refuse to write! // Proper fix: Change the above read to use read transaction ds.resetStatus(); ds.writeBytes(ba.constData(), ba.length()); // Flush the socket because we might not return to the event loop! if (!socket->waitForBytesWritten(2000)) { qWarning() << "Failed to write response:" << socket->error(); } // Wait for the ack bool r = true; r &= socket->waitForReadyRead(2000); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); if (!socket->waitForDisconnected(2000)) { qWarning() << "Remote not disconnected:" << socket->error(); // Wait again socket->disconnectFromServer(); if (socket->waitForDisconnected(2000)) { qWarning() << "Disconnect timed out:" << socket->error(); } } if (!messageBoxWarningText.isEmpty()) { // Defer the message box to the event loop QTimer::singleShot(0, [messageBoxWarningText]() { QMessageBox::warning(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita"), messageBoxWarningText); }); } } void QMic::pluginStateChanged(QProcess::ProcessState state) { dbgPlugins << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { dbgPlugins << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { dbgPlugins << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { dbgPlugins << "slotStartApplicator();" << gmicImages; if (!viewManager()) return; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); KIS_SAFE_ASSERT_RECOVER_BREAK(parts.size() == 5); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); dbgPlugins << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { dbgPlugins << "Could not lock memory segment" << m.error() << m.errorString(); } dbgPlugins << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; dbgPlugins << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); dbgPlugins << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { dbgPlugins << "Could not unlock memory segment" << m.error() << m.errorString(); } if (!m.detach()) { dbgPlugins << "Could not detach from memory segment" << m.error() << m.errorString(); } images.append(gimg); } } dbgPlugins << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = viewManager()->image()->root(); KisInputOutputMapper mapper(viewManager()->image(), viewManager()->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(viewManager()->image(), rootNode, images, actionName, layers); m_gmicApplicator->apply(); m_gmicApplicator->finish(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { if (!viewManager()) return false; viewManager()->image()->lock(); m_inputMode = (InputLayerMode)inputMode; dbgPlugins << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(viewManager()->image(), viewManager()->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { viewManager()->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); if (node && node->paintDevice()) { QRect cropRect; KisSelectionSP selection = viewManager()->image()->globalSelection(); if (selection) { cropRect = selection->selectedExactRect(); } else { cropRect = viewManager()->image()->bounds(); } dbgPlugins << "Converting node" << node->name() << cropRect; const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc); const QRect resultRect = mappedRect.toAlignedRect(); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(resultRect.width(), resultRect.height(), 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect); message->append(m->key().toUtf8()); m->unlock(); message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(resultRect.width())); message->append(","); message->append(QByteArray::number(resultRect.height())); message->append("\n"); } } dbgPlugins << QString::fromUtf8(*message); viewManager()->image()->unlock(); return true; } #include "QMic.moc" diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp index 33d4c928ab..c918db9e67 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp @@ -1,245 +1,246 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_duplicateop_settings.h" #include "kis_duplicateop_option.h" #include "kis_duplicateop_settings_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include KisDuplicateOpSettings::KisDuplicateOpSettings() : m_isOffsetNotUptodate(false) { } KisDuplicateOpSettings::~KisDuplicateOpSettings() { } bool KisDuplicateOpSettings::paintIncremental() { return false; } QString KisDuplicateOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_COPY; } QPointF KisDuplicateOpSettings::offset() const { return m_offset; } QPointF KisDuplicateOpSettings::position() const { return m_position; } bool KisDuplicateOpSettings::mousePressEvent(const KisPaintInformation &info, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { bool ignoreEvent = true; if (modifiers & Qt::ControlModifier) { if (!m_sourceNode || !(modifiers & Qt::AltModifier)) { m_sourceNode = currentNode; } m_position = info.pos(); m_isOffsetNotUptodate = true; ignoreEvent = false; } else { if (m_isOffsetNotUptodate) { m_offset = info.pos() - m_position; m_isOffsetNotUptodate = false; } ignoreEvent = true; } return ignoreEvent; } KisNodeWSP KisDuplicateOpSettings::sourceNode() const { return m_sourceNode; } void KisDuplicateOpSettings::activate() { } void KisDuplicateOpSettings::fromXML(const QDomElement& elt) { // First, call the parent class fromXML to make sure all the // properties are saved to the map KisPaintOpSettings::fromXML(elt); m_offset.setX(KisDomUtils::toDouble(elt.attribute("OffsetX", "0.0"))); m_offset.setY(KisDomUtils::toDouble(elt.attribute("OffsetY", "0.0"))); m_isOffsetNotUptodate = false; } void KisDuplicateOpSettings::toXML(QDomDocument& doc, QDomElement& rootElt) const { // Then call the parent class fromXML KisPropertiesConfiguration::toXML(doc, rootElt); rootElt.setAttribute("OffsetX", QString::number(m_offset.x())); rootElt.setAttribute("OffsetY", QString::number(m_offset.y())); } KisPaintOpSettingsSP KisDuplicateOpSettings::clone() const { KisPaintOpSettingsSP setting = KisBrushBasedPaintOpSettings::clone(); KisDuplicateOpSettings* s = dynamic_cast(setting.data()); s->m_offset = m_offset; s->m_isOffsetNotUptodate = m_isOffsetNotUptodate; s->m_position = m_position; + s->m_sourceNode = m_sourceNode; return setting; } QPainterPath KisDuplicateOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { QPainterPath path; OutlineMode forcedMode = mode; if (!forcedMode.isVisible) { forcedMode.isVisible = true; forcedMode.forceCircle = true; } // clone tool should always show an outline path = KisBrushBasedPaintOpSettings::brushOutlineImpl(info, forcedMode, 1.0); QPainterPath copy(path); QRectF rect2 = copy.boundingRect(); if (m_isOffsetNotUptodate || !getBool(DUPLICATE_MOVE_SOURCE_POINT)) { copy.translate(m_position - info.pos()); } else { copy.translate(-m_offset); } path.addPath(copy); qreal dx = rect2.width() / 4.0; qreal dy = rect2.height() / 4.0; rect2.adjust(dx, dy, -dx, -dy); path.moveTo(rect2.topLeft()); path.lineTo(rect2.bottomRight()); path.moveTo(rect2.topRight()); path.lineTo(rect2.bottomLeft()); return path; } #include #include "kis_paintop_preset.h" #include "kis_paintop_settings_update_proxy.h" #include "kis_standard_uniform_properties_factory.h" QList KisDuplicateOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(m_uniformProperties); if (props.isEmpty()) { { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "clone_healing", i18n("Healing"), settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); prop->setValue(option.duplicate_healing); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); option.duplicate_healing = prop->value().toBool(); option.writeOptionSetting(prop->settings().data()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "clone_movesource", i18n("Move Source"), settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); prop->setValue(option.duplicate_move_source_point); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); option.duplicate_move_source_point = prop->value().toBool(); option.writeOptionSetting(prop->settings().data()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } } return KisPaintOpSettings::uniformProperties(settings) + props; } diff --git a/plugins/python/comics_project_management_tools/README.html b/plugins/python/comics_project_management_tools/README.html index 71c10efe1c..9acd65790b 100644 --- a/plugins/python/comics_project_management_tools/README.html +++ b/plugins/python/comics_project_management_tools/README.html @@ -1,279 +1,279 @@ Comics Project Management Tools Plugin Manual

Comics Project Management Tools

Version 3

This is the Comics Project Management Tools python plugin for Krita.

CPMT aims to simplify comics creation by:

  • Giving the artist a way to organize and quickly access their pages.
  • Helping the artist(s) deal with the boring bits meta data bits of a comic project by giving a meta-data editor that gives suggestions, explanation and occasionally a dab of humor.
  • Making export set-and-forget type of affair where a single click can export to multiple formats with proper meta-data.

Export-wise, CPMT aims to support:

Advanced Comic Book Format
An open comics format that has detailed markup as well as support for translations.
CBZ
the most popular comic file format, with the following meta-data schemes:
  • ACBF - as above.
  • CoMet.xml
  • ComicBookInfo (Spec is unclear so not 100% certain)
  • ComicInfo.xml(Comic Rack)
Epub
The epub publishing format. Not the most ideal format for handling comics, but most readers can open epub.

Table of Contents:

  1. Usage - quick-start guide
  2. Usage - Meta Data
    1. Adding extra auto-completion keys.
    2. The Author list
  3. Usage - Pages
    1. The Comic Viewer
  4. Usage - Copy Location
  5. Usage - Export
    1. ACBF
    2. EPUB

Usage - quick-start guide:

First, get the comic manager docker(settings → dockers → comic Management Docker). There, select New Project.

It will show a dialog asking for:

The project directory.
This is where everything will be written to.
Concept
so a simple sentence explaining what you want to write the comic about. This concept is just for you.
Project name.
This is not the title, but more of a code name which will be used to create pages. For the impatient artist there is even a generator that produces code names.
Language
The main language, used for all the meta data. By default set to the system locale.
Make a new directory with the project name.
Whether to make a new project directory inside the selected directory. This allows you to have a generic comics directory that you always select and that CPMT will make directories named with the project name inside.
Pages
The name for the directory to store the pages. This is where new pages are placed.
Export
The name for the directory to store the export. This is where the comic will be exported to.
Templates
The name for the directory to store the template. This is where the page templates get stored.
Translations
The translations directory is where the POT file will be stored and where the exporter searches for translation(PO) files.

It will also allow you to edit meta data if you'd want already, but this is not mandatory.

-

Then after you finish, select Open Project, go to the location where you have stored your comics project. There should be a “comicsConfig.json” file there, next to the new folders for the pages, templates and export. Open that.

+

Since version 3 the project is immidiately opened in the docker. If you wish to work on an older project, select Open Project, go to the location where you have stored your comics project. There should be a “comicsConfig.json” file there, next to the new folders for the pages, templates and export. Open that.

Now, click Add Page to add your first page. You will get a dialog asking for the template. Here you can generate one, or import one. CPMT will remember this as the default one.

Double click the new page to open in Krita.

The second column in the docker allows you to see the “subject” line in the document info if it's filled in.

You can press the arrow next to Add Page to get more features, like Add Existing Page, Remove Page, or Batch Resize.

Usage - Meta Data

You can edit the meta data by clicking the dropdown next to Project Settings and selecting Meta Data.

There's quite a few fields here, because there's quite a few different types of meta data. Hover over the fields to get an idea of what needs to be typed.

The meta data is intended to be filled out over the course of the project, so don't worry too much if you cannot instantly think of what a certain entry should be.

The meta data fields have auto completion wherever sensible. You can add your own meta data fields as noted in the following section:

Adding extra auto-completion keys.

First, you need to go to project settings, and there point the extra keys to a folder where extra keys can be found.

It will search that extra folder for the following folders:

  • key_genre
  • key_format
  • key_rating
  • key_characters
  • key_author_roles
  • key_other

You can add extra auto-completion keys by adding a text file with each new key on a separate line to one of the “key” folders. The name of the text file doesn't matter. This way you can add characters by universe, or archive specific keywords by archive name.

So for example, the following file has three superhero names on different lines, nothing more, nothing less.

 Spider-Man
  Hawkeye
  Jean Grey

When you then store it as marvel.txt put into the directory “key_characters”, Krita will use the names from the list as suggestion for the character field in the meta-data.

The exception is the key_ratings, which uses CSV files, using the top row to determine the title, and then has the rating in the first column, and the description on the second. This allows the description to show up as tool-tips.

The Author list

The author list is a table containing all the authors of the project. It allows a distinction between given, family, middle and nickname, as well as role, email and homepage.

You can rearrange the author list by drag and dropping the number at the left, as well as adding and removing authors.

Adding an author will always add “John Doe”. You can double click the names and cells to change their contents. For the role, there are auto completion keys, so to encourage using standardized ways to describe their roles.

In the main docker, there's an option under the pages actions called Scrape Authors, this will make the comics project docker search the pages in the pages list for author info and append that to the author list. It will not attempt to check for duplicates, so be sure to the list afterwards.

Usage - Project Settings

The project settings allows you to change all the technical details of the project:

  • the project name
  • the concept
  • the location of pages, export and templates
  • the default template.
  • the location of the extra auto-completion keys(see metadata)

Usage - Pages

There's several other things you can do with pages. You can either access these feature by clicking the drop-down next to Add Page or right-clicking the pages list.

Adding pages
You can add pages by pressing the Add Page button. The first time you press this, it'll ask for a template. After you create or select a template it will use this as the default. You can set the default in the project settings.
Adding pages from template:
Adding pages from a template will always give the template dialog. This will allow you to have several different templates in the templates directory(it will show all the kra files in the templates directory), so that you can have spread, coverpages and other pages at your finger tips. The create template dialog will allow you to make a simple two layer image with a colored background, and rulers for the bleeds and guides. Import template will copy selected templates to the template directory, keeping all the necessary files inside the comics project.
Remove a page
This allows you to remove the selected page in the list from the pages list. It does NOT delete the page from the disk.
Adding existing pages
This is for when you wish to add existing pages, either because you removed the page from the list, or because you already have a project going and wish to add the pages to the list.
Batch Resize
This will show a window with resize options. After selecting the right options, all the pages will be resized as such. A progress dialog will pop up showing you which pages have been done and how long it will take based on the passed time.
View Page in Window
This will call up the comic viewer.
Scrape Authors
This searches all the files from the pages list for author information and adds that to the author list. It will not check for copies, so you will need to clean up the author list yourself.
Scrape Text for Translations
This triggers a script that will go over each page and take out certain text information it can find. It will use the 'text layer keys' in the export dialog to determine whether a vector layer's text ought to be considered. Then, when done, it will put the text it found into a POT file, together with translatable meta-data, like the comic title, and will save it in the translations folder. The POT file can then be used by translators(using something like PO edit) to create a PO file. The CPMT can in turn use the PO files in the creation of ACBF files which'll embed the translations.
Rearranging pages
You can rearrange pages by drag and dropping the pages themselves.

The Comic Viewer

When you rightclick the pages, or press the down button next to Add Page, There's the option View page in window.This will pop up a comic viewer, which is each page's mergedimage.png file(that is a preview of all visible layers merged), and you can flip through them. This is so that you can have a quick reference for a single page in the event your other referencing tools cannot open kra files.

First, Last
These will set the viewer to the last or first page in the comic. The hotkeys for these are Home and End respectively. These buttons will switch position based on the reading direction configured for the comic.
Previous, Next
These buttons allow you to switch spaces. The hotkeys for these are and respectively. You can also use Space to switch to the next page. These buttons will switch position based on the reading direction configured for the comic.
Spread, single
This button will allow you to switch to single or spread mode.

You can also run the comic viewer standalone:

python3 comics_project_page_viewer.py /path/to/your/comicConfig.json

Usage - Copy Location

Copy location, the button underneath the export button, allows you to copy the current project location to clipboard. Just press it, and paste somewhere else. This is useful when using multiple programs and reference tools and you just want to quickly navigate to the project directory.

Usage - Export

CPMT will not allow export without any export methods set.

You can configure the export settings by going to the drop-down next to Project Settings and selecting Export Settings.

Here you can define...

  • how much a page needs to be cropped
  • which layers to remove by layer color-label
  • to which formats to export, in what file-format and how to resize.

Once you've done that, press export. Krita will pop up a progress bar for you with the estimated time and progress, so you can estimate how long you will have to wait.

CPMT will store the resized files and meta data in separate folders in the export folder. This is so that you can perform optimization methods afterwards and update everything quickly.

ACBF

ACBF is the advanced comic book format. It is a metadata file that can hold extra data like panels and text, and can even store translations for the text.

When you generate a CBZ file, the ACBF file will be generated alongside of it. There's in fact two ACBF files being generated: The one in the metadata folder is the ACBF file as it is inside the CBZ. The other ACBF file, next to the CBZ is the standalonefile. This file has the pages embedded, but there's currently fewer viewers who can read it.

ACBF has a set amount of genres it can cover. This is the default list of genre auto completion keys. Genres outside that will be put into the extra keywords list for ACBF. On top of that, it does allow defining a match to this genre. To set a match to a genre, write a number in brackets indicating how much it matches this genre. So for example “Horror(60), Science Fiction(40)”will have Horror set to 60% and Science Fiction set to 40%. These values are normalized. So if you put in “Romance(550), Fantasy(650)”it will ensure that the two values will become percentages, leading to Romance being set to 46% and Fantasy set to 54%.

The CPMT has some support for frames and text export. If you name a vector layer “text” or “panels” it will search those for shapes. The shapes that are text nodes will be added to the ACBF file as a text in the main language of the comic, using the bounding box of the text-shape. The shapes that aren't text will have their bounding boxes used as frames. The order of frames and text is determined by the shape z-order in Krita, with the bottom shape being the first and the top shape being the last. You can customize these layer selection keys in the export settings dialog.

The CPMT also support translations. ACBF will use the PO files stored in the translations folder. In the export dialog, you can configure whether you want translation comments to be embedded. Then, if there's translation comments in the PO file, ACBF will put those in the reference section and add a link to the line with the translation comment. Translations will have translator note headers. You can configure these in the export, and they will also be put into the POT file when it is generated so they may be translated.

Finally, there's the styles and the text type. You can configure the styles in the export settings dialog tab for acbf. The exporter will use the configuration and alignment to automatically figure out the text-type in the export.

  • Text that is justified will use the “formal”type. There's no method to currently make justified text, so this won't trigger until then
  • Text that is centered will have it's font family compared to the existing styles and their font-families. If it finds a match that will be the type
  • Text that is neither will be marked with “commentary”, that is, it is considered a narrative caption.

To fine tune the export to ACBF, you can go to file→document Information and add the following keywords:

acbf_title
this will flag this page to be used as a table of contents bookmark inside ACBF. The content mark will use the “title” value in the document information to create a bookmark in the project language.
acbf_none
Sets the page transition value to “none” explicitly.
acbf_fade
Sets the page transitio to fade. Viewers that support it will fade to black into this page.
acbf_blend
Sets the page transition to blend. Viewers that support it will fade the previous page to this one.
acbf_horizontal
Sets the page transition to scroll_right. Viewers that support it will scroll right to a new page.
acbf_vertical
Sets the page transition to scroll_down. Viewers that support it will scroll down to a new page.

EPUB

EPUB stands for E-Publication and is a commonly supported e-reading format. The exporter can export comics appropriately. Because the tags and features required for comics support were not supported until EPUB 3, this exporter only exports EPUB 3. An EPUB 2 reader will be able to read these files as well, it just will not look as fancy.

EPUBs generated by the exporter will be pre-paginated, and will take the reading direction into account when assigning pages to left or right side of the spread.

The EPUB exporter will also generate a region-navigation document which'll allow conforming readers to use panel-by-panel navigation. Panels and frame export is, like with the ACBF exporter, determined by searching for a vector layer with the appropriate layer name. By default these names are “text” or “panels” and they can be configured in the export dialog.

Metadata wise, the creator and contributor roles use MARC relators for the role. The exporter will try to match the filled in role with either the official description or its MARC Relator code, and otherwise set it to "oth"(other).

The epub exporter will use the acbf_title keyword exactly like the ACBF exporter will, generating both a EPUB 3 and EPUB 2 style Table of Contents with them. Beyond that, there's the following keyword:

epub_spread
This will mark the page as a spread(two pages next to one another), and thus, if a epub reading software puts pages next to one another, it'll try to avoid that with the spread.

Version History

Because the comic project management tools are bundled with Krita you could argue that you can identify them that way, but here is a history anyway, so you can check what has changed.

Version 3 (Krita 4.2)
-Improved ComicBookInfo and ComicInfo.xml metadata files export. Also improved EPUB export, with support for pre-pagination and region navigation, and improved handling of the metadata files. +Improved ComicBookInfo and ComicInfo.xml metadata files export. Improved EPUB export, with support for pre-pagination and region navigation, and improved handling of the metadata files. Also added warnings for trying to deal with unwritable folders, as well as opening the project after you create it.
Version 2 (Krita 4.1)
90% ACBF support, with improved handling of text, translation support, new keys for genres and author roles, style sheets, text-type recognition, database ref, and background color support. The Comic Page viewer has been fully rewritten to handle flipping through the pages as well as made possible to be run standalone. Furthermore, the comic page viewer has gotten its own dedicated item delegate, meaning the page metadata is drawn nicely and drag and drop is less fiddly.
Version 1 (Krita 4.0)
The initial tools, with project management, page management, meta data management complete with autocompletion keys, copy-location, export to epub, tiff, cbz, and export to zipfile info, ComicInfo.xml and CoMet.xml, and for ACBF support for panels, text and basic metadata. Other features include batch resize, page templates, and author data scraping.
diff --git a/plugins/python/comics_project_management_tools/comics_project_manager_docker.py b/plugins/python/comics_project_management_tools/comics_project_manager_docker.py index 4f0d41e342..c3baf72d29 100644 --- a/plugins/python/comics_project_management_tools/comics_project_manager_docker.py +++ b/plugins/python/comics_project_management_tools/comics_project_manager_docker.py @@ -1,950 +1,960 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ This is a docker that helps you organise your comics project. """ import sys import json import os import zipfile # quick reading of documents import shutil import enum from math import floor import xml.etree.ElementTree as ET from PyQt5.QtCore import QElapsedTimer, QSize, Qt, QRect, QFileSystemWatcher from PyQt5.QtGui import QStandardItem, QStandardItemModel, QImage, QIcon, QPixmap, QFontMetrics, QPainter, QPalette, QFont from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QListView, QToolButton, QMenu, QAction, QPushButton, QSpacerItem, QSizePolicy, QWidget, QAbstractItemView, QProgressDialog, QDialog, QFileDialog, QDialogButtonBox, qApp, QSplitter, QSlider, QLabel, QStyledItemDelegate, QStyle, QMessageBox import math from krita import * from . import comics_metadata_dialog, comics_exporter, comics_export_dialog, comics_project_setup_wizard, comics_template_dialog, comics_project_settings_dialog, comics_project_page_viewer, comics_project_translation_scraper """ A very simple class so we can have a label that is single line, but doesn't force the widget size to be bigger. This is used by the project name. """ class Elided_Text_Label(QLabel): mainText = str() def __init__(self, parent=None): super(QLabel, self).__init__(parent) self.setMinimumWidth(self.fontMetrics().width("...")) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) def setMainText(self, text=str()): self.mainText = text self.elideText() def elideText(self): self.setText(self.fontMetrics().elidedText(self.mainText, Qt.ElideRight, self.width())) def resizeEvent(self, event): self.elideText() class CPE(enum.IntEnum): TITLE = Qt.DisplayRole URL = Qt.UserRole + 1 KEYWORDS = Qt.UserRole+2 DESCRIPTION = Qt.UserRole+3 LASTEDIT = Qt.UserRole+4 EDITOR = Qt.UserRole+5 IMAGE = Qt.DecorationRole class comic_page_delegate(QStyledItemDelegate): def __init__(self, parent=None): super(QStyledItemDelegate, self).__init__(parent) def paint(self, painter, option, index): if (index.isValid() == False): return painter.save() painter.setOpacity(0.6) if(option.state & QStyle.State_Selected): painter.fillRect(option.rect, option.palette.highlight()) if (option.state & QStyle.State_MouseOver): painter.setOpacity(0.25) painter.fillRect(option.rect, option.palette.highlight()) painter.setOpacity(1.0) painter.setFont(option.font) metrics = QFontMetrics(option.font) regular = QFont(option.font) italics = QFont(option.font) italics.setItalic(True) icon = QIcon(index.data(CPE.IMAGE)) rect = option.rect margin = 4 decoratonSize = QSize(option.decorationSize) imageSize = icon.actualSize(option.decorationSize) leftSideThumbnail = (decoratonSize.width()-imageSize.width())/2 if (rect.width() < decoratonSize.width()): leftSideThumbnail = max(0, (rect.width()-imageSize.width())/2) topSizeThumbnail = ((rect.height()-imageSize.height())/2)+rect.top() painter.drawImage(QRect(leftSideThumbnail, topSizeThumbnail, imageSize.width(), imageSize.height()), icon.pixmap(imageSize).toImage()) labelWidth = rect.width()-decoratonSize.width()-(margin*3) if (decoratonSize.width()+(margin*2)< rect.width()): textRect = QRect(decoratonSize.width()+margin, margin+rect.top(), labelWidth, metrics.height()) textTitle = metrics.elidedText(str(index.row()+1)+". "+index.data(CPE.TITLE), Qt.ElideRight, labelWidth) painter.drawText(textRect, Qt.TextWordWrap, textTitle) if rect.height()/(metrics.lineSpacing()+margin) > 5 or index.data(CPE.KEYWORDS) is not None: painter.setOpacity(0.6) textRect = QRect(textRect.left(), textRect.bottom()+margin, labelWidth, metrics.height()) if textRect.bottom() < rect.bottom(): textKeyWords = index.data(CPE.KEYWORDS) if textKeyWords == None: textKeyWords = i18n("No keywords") painter.setOpacity(0.3) painter.setFont(italics) textKeyWords = metrics.elidedText(textKeyWords, Qt.ElideRight, labelWidth) painter.drawText(textRect, Qt.TextWordWrap, textKeyWords) painter.setFont(regular) if rect.height()/(metrics.lineSpacing()+margin) > 3: painter.setOpacity(0.6) textRect = QRect(textRect.left(), textRect.bottom()+margin, labelWidth, metrics.height()) if textRect.bottom()+metrics.height() < rect.bottom(): textLastEdit = index.data(CPE.LASTEDIT) if textLastEdit is None: textLastEdit = i18n("No last edit timestamp") if index.data(CPE.EDITOR) is not None: textLastEdit += " - " + index.data(CPE.EDITOR) if (index.data(CPE.LASTEDIT) is None) and (index.data(CPE.EDITOR) is None): painter.setOpacity(0.3) painter.setFont(italics) textLastEdit = metrics.elidedText(textLastEdit, Qt.ElideRight, labelWidth) painter.drawText(textRect, Qt.TextWordWrap, textLastEdit) painter.setFont(regular) descRect = QRect(textRect.left(), textRect.bottom()+margin, labelWidth, (rect.bottom()-margin) - (textRect.bottom()+margin)) if textRect.bottom()+metrics.height() < rect.bottom(): textRect.setBottom(textRect.bottom()+(margin/2)) textRect.setLeft(textRect.left()-(margin/2)) painter.setOpacity(0.4) painter.drawLine(textRect.bottomLeft(), textRect.bottomRight()) painter.setOpacity(1.0) textDescription = index.data(CPE.DESCRIPTION) if textDescription is None: textDescription = i18n("No description") painter.setOpacity(0.3) painter.setFont(italics) linesTotal = floor(descRect.height()/metrics.lineSpacing()) if linesTotal == 1: textDescription = metrics.elidedText(textDescription, Qt.ElideRight, labelWidth) painter.drawText(descRect, Qt.TextWordWrap, textDescription) else: descRect.setHeight(linesTotal*metrics.lineSpacing()) totalDescHeight = metrics.boundingRect(descRect, Qt.TextWordWrap, textDescription).height() if totalDescHeight>descRect.height(): if totalDescHeight-metrics.lineSpacing()>descRect.height(): painter.setOpacity(0.5) painter.drawText(descRect, Qt.TextWordWrap, textDescription) descRect.setHeight((linesTotal-1)*metrics.lineSpacing()) painter.drawText(descRect, Qt.TextWordWrap, textDescription) descRect.setHeight((linesTotal-2)*metrics.lineSpacing()) painter.drawText(descRect, Qt.TextWordWrap, textDescription) else: painter.setOpacity(0.75) painter.drawText(descRect, Qt.TextWordWrap, textDescription) descRect.setHeight((linesTotal-1)*metrics.lineSpacing()) painter.drawText(descRect, Qt.TextWordWrap, textDescription) else: painter.drawText(descRect, Qt.TextWordWrap, textDescription) painter.setFont(regular) painter.restore() """ This is a Krita docker called 'Comics Manager'. It allows people to create comics project files, load those files, add pages, remove pages, move pages, manage the metadata, and finally export the result. The logic behind this docker is that it is very easy to get lost in a comics project due to the massive amount of files. By having a docker that gives the user quick access to the pages and also allows them to do all of the meta-stuff, like meta data, but also reordering the pages, the chaos of managing the project should take up less time, and more time can be focused on actual writing and drawing. """ class comics_project_manager_docker(DockWidget): setupDictionary = {} stringName = i18n("Comics Manager") projecturl = None pagesWatcher = None def __init__(self): super().__init__() self.setWindowTitle(self.stringName) # Setup layout: base = QHBoxLayout() widget = QWidget() widget.setLayout(base) baseLayout = QSplitter() base.addWidget(baseLayout) self.setWidget(widget) buttonLayout = QVBoxLayout() buttonBox = QWidget() buttonBox.setLayout(buttonLayout) baseLayout.addWidget(buttonBox) # Comic page list and pages model self.comicPageList = QListView() self.comicPageList.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.comicPageList.setDragEnabled(True) self.comicPageList.setDragDropMode(QAbstractItemView.InternalMove) self.comicPageList.setDefaultDropAction(Qt.MoveAction) self.comicPageList.setAcceptDrops(True) self.comicPageList.setItemDelegate(comic_page_delegate()) self.pagesModel = QStandardItemModel() self.comicPageList.doubleClicked.connect(self.slot_open_page) self.comicPageList.setIconSize(QSize(128, 128)) # self.comicPageList.itemDelegate().closeEditor.connect(self.slot_write_description) self.pagesModel.layoutChanged.connect(self.slot_write_config) self.pagesModel.rowsInserted.connect(self.slot_write_config) self.pagesModel.rowsRemoved.connect(self.slot_write_config) self.pagesModel.rowsMoved.connect(self.slot_write_config) self.comicPageList.setModel(self.pagesModel) pageBox = QWidget() pageBox.setLayout(QVBoxLayout()) zoomSlider = QSlider(Qt.Horizontal, None) zoomSlider.setRange(1, 8) zoomSlider.setValue(4) zoomSlider.setTickInterval(1) zoomSlider.setMinimumWidth(10) zoomSlider.valueChanged.connect(self.slot_scale_thumbnails) self.projectName = Elided_Text_Label() pageBox.layout().addWidget(self.projectName) pageBox.layout().addWidget(zoomSlider) pageBox.layout().addWidget(self.comicPageList) baseLayout.addWidget(pageBox) self.btn_project = QToolButton() self.btn_project.setPopupMode(QToolButton.MenuButtonPopup) self.btn_project.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) menu_project = QMenu() self.action_new_project = QAction(i18n("New Project"), self) self.action_new_project.triggered.connect(self.slot_new_project) self.action_load_project = QAction(i18n("Open Project"), self) self.action_load_project.triggered.connect(self.slot_open_config) menu_project.addAction(self.action_new_project) menu_project.addAction(self.action_load_project) self.btn_project.setMenu(menu_project) self.btn_project.setDefaultAction(self.action_load_project) buttonLayout.addWidget(self.btn_project) # Settings dropdown with actions for the different settings menus. self.btn_settings = QToolButton() self.btn_settings.setPopupMode(QToolButton.MenuButtonPopup) self.btn_settings.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_edit_project_settings = QAction(i18n("Project Settings"), self) self.action_edit_project_settings.triggered.connect(self.slot_edit_project_settings) self.action_edit_meta_data = QAction(i18n("Meta Data"), self) self.action_edit_meta_data.triggered.connect(self.slot_edit_meta_data) self.action_edit_export_settings = QAction(i18n("Export Settings"), self) self.action_edit_export_settings.triggered.connect(self.slot_edit_export_settings) menu_settings = QMenu() menu_settings.addAction(self.action_edit_project_settings) menu_settings.addAction(self.action_edit_meta_data) menu_settings.addAction(self.action_edit_export_settings) self.btn_settings.setDefaultAction(self.action_edit_project_settings) self.btn_settings.setMenu(menu_settings) buttonLayout.addWidget(self.btn_settings) self.btn_settings.setDisabled(True) # Add page drop down with different page actions. self.btn_add_page = QToolButton() self.btn_add_page.setPopupMode(QToolButton.MenuButtonPopup) self.btn_add_page.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_add_page = QAction(i18n("Add Page"), self) self.action_add_page.triggered.connect(self.slot_add_new_page_single) self.action_add_template = QAction(i18n("Add Page from Template"), self) self.action_add_template.triggered.connect(self.slot_add_new_page_from_template) self.action_add_existing = QAction(i18n("Add Existing Pages"), self) self.action_add_existing.triggered.connect(self.slot_add_page_from_url) self.action_remove_selected_page = QAction(i18n("Remove Page"), self) self.action_remove_selected_page.triggered.connect(self.slot_remove_selected_page) self.action_resize_all_pages = QAction(i18n("Batch Resize"), self) self.action_resize_all_pages.triggered.connect(self.slot_batch_resize) self.btn_add_page.setDefaultAction(self.action_add_page) self.action_show_page_viewer = QAction(i18n("View Page In Window"), self) self.action_show_page_viewer.triggered.connect(self.slot_show_page_viewer) self.action_scrape_authors = QAction(i18n("Scrape Author Info"), self) self.action_scrape_authors.setToolTip(i18n("Search for author information in documents and add it to the author list. This does not check for duplicates.")) self.action_scrape_authors.triggered.connect(self.slot_scrape_author_list) self.action_scrape_translations = QAction(i18n("Scrape Text for Translation"), self) self.action_scrape_translations.triggered.connect(self.slot_scrape_translations) actionList = [] menu_page = QMenu() actionList.append(self.action_add_page) actionList.append(self.action_add_template) actionList.append(self.action_add_existing) actionList.append(self.action_remove_selected_page) actionList.append(self.action_resize_all_pages) actionList.append(self.action_show_page_viewer) actionList.append(self.action_scrape_authors) actionList.append(self.action_scrape_translations) menu_page.addActions(actionList) self.btn_add_page.setMenu(menu_page) buttonLayout.addWidget(self.btn_add_page) self.btn_add_page.setDisabled(True) self.comicPageList.setContextMenuPolicy(Qt.ActionsContextMenu) self.comicPageList.addActions(actionList) # Export button that... exports. self.btn_export = QPushButton(i18n("Export Comic")) self.btn_export.clicked.connect(self.slot_export) buttonLayout.addWidget(self.btn_export) self.btn_export.setDisabled(True) self.btn_project_url = QPushButton(i18n("Copy Location")) self.btn_project_url.setToolTip(i18n("Copies the path of the project to the clipboard. Useful for quickly copying to a file manager or the like.")) self.btn_project_url.clicked.connect(self.slot_copy_project_url) self.btn_project_url.setDisabled(True) buttonLayout.addWidget(self.btn_project_url) self.page_viewer_dialog = comics_project_page_viewer.comics_project_page_viewer() self.pagesWatcher = QFileSystemWatcher() self.pagesWatcher.fileChanged.connect(self.slot_check_for_page_update) buttonLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)) """ Open the config file and load the json file into a dictionary. """ def slot_open_config(self): self.path_to_config = QFileDialog.getOpenFileName(caption=i18n("Please select the JSON comic config file."), filter=str(i18n("JSON files") + "(*.json)"))[0] if os.path.exists(self.path_to_config) is True: + if os.access(self.path_to_config, os.W_OK) is False: + QMessageBox.warning(None, i18n("Config cannot be used"), i18n("Krita doesn't have write access to this folder, so new files cannot be made. Please configure the folder access or move the project to a folder that can be written to."), QMessageBox.Ok) + return configFile = open(self.path_to_config, "r", newline="", encoding="utf-16") self.setupDictionary = json.load(configFile) self.projecturl = os.path.dirname(str(self.path_to_config)) configFile.close() self.load_config() """ Further config loading. """ def load_config(self): self.projectName.setMainText(text=str(self.setupDictionary["projectName"])) self.fill_pages() self.btn_settings.setEnabled(True) self.btn_add_page.setEnabled(True) self.btn_export.setEnabled(True) self.btn_project_url.setEnabled(True) """ Fill the pages model with the pages from the pages list. """ def fill_pages(self): self.loadingPages = True self.pagesModel.clear() if len(self.pagesWatcher.files())>0: self.pagesWatcher.removePaths(self.pagesWatcher.files()) pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] progress = QProgressDialog() progress.setMinimum(0) progress.setMaximum(len(pagesList)) progress.setWindowTitle(i18n("Loading Pages...")) for url in pagesList: absurl = os.path.join(self.projecturl, url) relative = os.path.relpath(absurl, self.projecturl) if (os.path.exists(absurl)): #page = Application.openDocument(absurl) page = zipfile.ZipFile(absurl, "r") thumbnail = QImage.fromData(page.read("preview.png")) pageItem = QStandardItem() dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) pageItem.setText(dataList[0].replace("_", " ")) pageItem.setDragEnabled(True) pageItem.setDropEnabled(False) pageItem.setEditable(False) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setData(dataList[1], role = CPE.DESCRIPTION) pageItem.setData(relative, role = CPE.URL) self.pagesWatcher.addPath(absurl) pageItem.setData(dataList[2], role = CPE.KEYWORDS) pageItem.setData(dataList[3], role = CPE.LASTEDIT) pageItem.setData(dataList[4], role = CPE.EDITOR) pageItem.setToolTip(relative) page.close() self.pagesModel.appendRow(pageItem) progress.setValue(progress.value() + 1) progress.setValue(len(pagesList)) self.loadingPages = False """ Function that is triggered by the zoomSlider Resizes the thumbnails. """ def slot_scale_thumbnails(self, multiplier=4): self.comicPageList.setIconSize(QSize(multiplier * 32, multiplier * 32)) """ Function that takes the documentinfo.xml and parses it for the title, subject and abstract tags, to get the title and description. @returns a stringlist with the name on 0 and the description on 1. """ def get_description_and_title(self, string): xmlDoc = ET.fromstring(string) calligra = str("{http://www.calligra.org/DTD/document-info}") name = "" if ET.iselement(xmlDoc[0].find(calligra + 'title')): name = xmlDoc[0].find(calligra + 'title').text if name is None: name = " " desc = "" if ET.iselement(xmlDoc[0].find(calligra + 'subject')): desc = xmlDoc[0].find(calligra + 'subject').text if desc is None or desc.isspace() or len(desc) < 1: if ET.iselement(xmlDoc[0].find(calligra + 'abstract')): desc = xmlDoc[0].find(calligra + 'abstract').text if desc is not None: if desc.startswith(""): desc = desc[:-len("]]>")] keywords = "" if ET.iselement(xmlDoc[0].find(calligra + 'keyword')): keywords = xmlDoc[0].find(calligra + 'keyword').text date = "" if ET.iselement(xmlDoc[0].find(calligra + 'date')): date = xmlDoc[0].find(calligra + 'date').text author = [] if ET.iselement(xmlDoc[1].find(calligra + 'creator-first-name')): string = xmlDoc[1].find(calligra + 'creator-first-name').text if string is not None: author.append(string) if ET.iselement(xmlDoc[1].find(calligra + 'creator-last-name')): string = xmlDoc[1].find(calligra + 'creator-last-name').text if string is not None: author.append(string) if ET.iselement(xmlDoc[1].find(calligra + 'full-name')): string = xmlDoc[1].find(calligra + 'full-name').text if string is not None: author.append(string) return [name, desc, keywords, date, " ".join(author)] """ Scrapes authors from the author data in the document info and puts them into the author list. Doesn't check for duplicates. """ def slot_scrape_author_list(self): listOfAuthors = [] if "authorList" in self.setupDictionary.keys(): listOfAuthors = self.setupDictionary["authorList"] if "pages" in self.setupDictionary.keys(): for relurl in self.setupDictionary["pages"]: absurl = os.path.join(self.projecturl, relurl) page = zipfile.ZipFile(absurl, "r") xmlDoc = ET.fromstring(page.read("documentinfo.xml")) calligra = str("{http://www.calligra.org/DTD/document-info}") authorelem = xmlDoc.find(calligra + 'author') author = {} if ET.iselement(authorelem.find(calligra + 'full-name')): author["nickname"] = str(authorelem.find(calligra + 'full-name').text) if ET.iselement(authorelem.find(calligra + 'creator-first-name')): author["first-name"] = str(authorelem.find(calligra + 'creator-first-name').text) if ET.iselement(authorelem.find(calligra + 'initial')): author["initials"] = str(authorelem.find(calligra + 'initial').text) if ET.iselement(authorelem.find(calligra + 'creator-last-name')): author["last-name"] = str(authorelem.find(calligra + 'creator-last-name').text) if ET.iselement(authorelem.find(calligra + 'email')): author["email"] = str(authorelem.find(calligra + 'email').text) if ET.iselement(authorelem.find(calligra + 'contact')): contact = authorelem.find(calligra + 'contact') contactMode = contact.get("type") if contactMode == "email": author["email"] = str(contact.text) if contactMode == "homepage": author["homepage"] = str(contact.text) if ET.iselement(authorelem.find(calligra + 'position')): author["role"] = str(authorelem.find(calligra + 'position').text) listOfAuthors.append(author) page.close() self.setupDictionary["authorList"] = listOfAuthors """ Edit the general project settings like the project name, concept, pages location, export location, template location, metadata """ def slot_edit_project_settings(self): dialog = comics_project_settings_dialog.comics_project_details_editor(self.projecturl) dialog.setConfig(self.setupDictionary, self.projecturl) if dialog.exec_() == QDialog.Accepted: self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() self.projectName.setMainText(str(self.setupDictionary["projectName"])) """ This allows users to select existing pages and add them to the pages list. The pages are currently not copied to the pages folder. Useful for existing projects. """ def slot_add_page_from_url(self): # get the pages. urlList = QFileDialog.getOpenFileNames(caption=i18n("Which existing pages to add?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0] # get the existing pages list. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] # And add each url in the url list to the pages list and the model. for url in urlList: if self.projecturl not in urlList: newUrl = os.path.join(self.projecturl, self.setupDictionary["pagesLocation"], os.path.basename(url)) shutil.move(url, newUrl) url = newUrl relative = os.path.relpath(url, self.projecturl) if url not in pagesList: page = zipfile.ZipFile(url, "r") thumbnail = QImage.fromData(page.read("preview.png")) dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(dataList[0].replace("_", " ")) newPageItem.setData(dataList[1], role = CPE.DESCRIPTION) newPageItem.setData(relative, role = CPE.URL) self.pagesWatcher.addPath(url) newPageItem.setData(dataList[2], role = CPE.KEYWORDS) newPageItem.setData(dataList[3], role = CPE.LASTEDIT) newPageItem.setData(dataList[4], role = CPE.EDITOR) newPageItem.setToolTip(relative) page.close() self.pagesModel.appendRow(newPageItem) """ Remove the selected page from the list of pages. This does not remove it from disk(far too dangerous). """ def slot_remove_selected_page(self): index = self.comicPageList.currentIndex() self.pagesModel.removeRow(index.row()) """ This function adds a new page from the default template. If there's no default template, or the file does not exist, it will show the create/import template dialog. It will remember the selected item as the default template. """ def slot_add_new_page_single(self): templateUrl = "templatepage" templateExists = False if "singlePageTemplate" in self.setupDictionary.keys(): templateUrl = self.setupDictionary["singlePageTemplate"] if os.path.exists(os.path.join(self.projecturl, templateUrl)): templateExists = True if templateExists is False: if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.setupDictionary["singlePageTemplate"] = templateUrl if os.path.exists(os.path.join(self.projecturl, templateUrl)): self.add_new_page(templateUrl) """ This function always asks for a template showing the new template window. This allows users to have multiple different templates created for back covers, spreads, other and have them accessible, while still having the convenience of a singular "add page" that adds a default. """ def slot_add_new_page_from_template(self): if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.add_new_page(templateUrl) """ This is the actual function that adds the template using the template url. It will attempt to name the new page projectName+number. """ def add_new_page(self, templateUrl): # check for page list and or location. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] if not "pageNumber" in self.setupDictionary.keys(): self.setupDictionary['pageNumber'] = 0 if (str(self.setupDictionary["pagesLocation"]).isspace()): self.setupDictionary["pagesLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where should the pages go?"), options=QFileDialog.ShowDirsOnly), self.projecturl) # Search for the possible name. extraUnderscore = str() if str(self.setupDictionary["projectName"])[-1].isdigit(): extraUnderscore = "_" self.setupDictionary['pageNumber'] += 1 pageName = str(self.setupDictionary["projectName"]).replace(" ", "_") + extraUnderscore + str(format(self.setupDictionary['pageNumber'], "03d")) url = os.path.join(str(self.setupDictionary["pagesLocation"]), pageName + ".kra") # open the page by opening the template and resaving it, or just opening it. absoluteUrl = os.path.join(self.projecturl, url) if (os.path.exists(absoluteUrl)): newPage = Application.openDocument(absoluteUrl) else: booltemplateExists = os.path.exists(os.path.join(self.projecturl, templateUrl)) if booltemplateExists is False: templateUrl = os.path.relpath(QFileDialog.getOpenFileName(caption=i18n("Which image should be the basis the new page?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0], self.projecturl) newPage = Application.openDocument(os.path.join(self.projecturl, templateUrl)) newPage.waitForDone() newPage.setFileName(absoluteUrl) newPage.setName(pageName.replace("_", " ")) newPage.save() newPage.waitForDone() # Get out the extra data for the standard item. newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(newPage.thumbnail(256, 256)))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(pageName.replace("_", " ")) newPageItem.setData("", role = CPE.DESCRIPTION) newPageItem.setData(url, role = CPE.URL) newPageItem.setData("", role = CPE.KEYWORDS) newPageItem.setData("", role = CPE.LASTEDIT) newPageItem.setData("", role = CPE.EDITOR) newPageItem.setToolTip(url) # close page document. while os.path.exists(absoluteUrl) is False: qApp.processEvents() self.pagesWatcher.addPath(absoluteUrl) newPage.close() # add item to page. self.pagesModel.appendRow(newPageItem) """ Write to the json configuration file. This also checks the current state of the pages list. """ def slot_write_config(self): # Don't load when the pages are still being loaded, otherwise we'll be overwriting our own pages list. if (self.loadingPages is False): print("CPMT: writing comic configuration...") # Generate a pages list from the pagesmodel. pagesList = [] for i in range(self.pagesModel.rowCount()): index = self.pagesModel.index(i, 0) url = str(self.pagesModel.data(index, role=CPE.URL)) if url not in pagesList: pagesList.append(url) self.setupDictionary["pages"] = pagesList # Save to our json file. configFile = open(self.path_to_config, "w", newline="", encoding="utf-16") json.dump(self.setupDictionary, configFile, indent=4, sort_keys=True, ensure_ascii=False) configFile.close() print("CPMT: done") """ Open a page in the pagesmodel in Krita. """ def slot_open_page(self, index): if index.column() is 0: # Get the absolute url from the relative one in the pages model. absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(index, role=CPE.URL))) # Make sure the page exists. if os.path.exists(absoluteUrl): page = Application.openDocument(absoluteUrl) # Set the title to the filename if it was empty. It looks a bit neater. if page.name().isspace or len(page.name()) < 1: page.setName(str(self.pagesModel.data(index, role=Qt.DisplayRole)).replace("_", " ")) # Add views for the document so the user can use it. Application.activeWindow().addView(page) Application.setActiveDocument(page) else: print("CPMT: The page cannot be opened because the file doesn't exist:", absoluteUrl) """ Call up the metadata editor dialog. Only when the dialog is "Accepted" will the metadata be saved. """ def slot_edit_meta_data(self): dialog = comics_metadata_dialog.comic_meta_data_editor() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ An attempt at making the description editable from the comic pages list. It is currently not working because ZipFile has no overwrite mechanism, and I don't have the energy to write one yet. """ def slot_write_description(self, index): for row in range(self.pagesModel.rowCount()): index = self.pagesModel.index(row, 1) indexUrl = self.pagesModel.index(row, 0) absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(indexUrl, role=CPE.URL))) page = zipfile.ZipFile(absoluteUrl, "a") xmlDoc = ET.ElementTree() ET.register_namespace("", "http://www.calligra.org/DTD/document-info") location = os.path.join(self.projecturl, "documentinfo.xml") xmlDoc.parse(location) xmlroot = ET.fromstring(page.read("documentinfo.xml")) calligra = "{http://www.calligra.org/DTD/document-info}" aboutelem = xmlroot.find(calligra + 'about') if ET.iselement(aboutelem.find(calligra + 'subject')): desc = aboutelem.find(calligra + 'subject') desc.text = self.pagesModel.data(index, role=Qt.EditRole) xmlstring = ET.tostring(xmlroot, encoding='unicode', method='xml', short_empty_elements=False) page.writestr(zinfo_or_arcname="documentinfo.xml", data=xmlstring) for document in Application.documents(): if str(document.fileName()) == str(absoluteUrl): document.setDocumentInfo(xmlstring) page.close() """ Calls up the export settings dialog. Only when accepted will the configuration be written. """ def slot_edit_export_settings(self): dialog = comics_export_dialog.comic_export_setting_dialog() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ Export the comic. Won't work without export settings set. """ def slot_export(self): #ensure there is a unique identifier if "uuid" not in self.setupDictionary.keys(): uuid = str() if "acbfID" in self.setupDictionary.keys(): uuid = str(self.setupDictionary["acbfID"]) else: uuid = QUuid.createUuid().toString() self.setupDictionary["uuid"] = uuid exporter = comics_exporter.comicsExporter() exporter.set_config(self.setupDictionary, self.projecturl) exportSuccess = exporter.export() if exportSuccess: print("CPMT: Export success! The files have been written to the export folder!") QMessageBox.information(self, i18n("Export success"), i18n("The files have been written to the export folder."), QMessageBox.Ok) """ Calls up the comics project setup wizard so users can create a new json file with the basic information. """ def slot_new_project(self): setup = comics_project_setup_wizard.ComicsProjectSetupWizard() setup.showDialog() + self.path_to_config = os.path.join(setup.projectDirectory, "comicConfig.json") + if os.path.exists(self.path_to_config) is True: + configFile = open(self.path_to_config, "r", newline="", encoding="utf-16") + self.setupDictionary = json.load(configFile) + self.projecturl = os.path.dirname(str(self.path_to_config)) + configFile.close() + self.load_config() """ This is triggered by any document save. It checks if the given url in in the pages list, and if so, updates the appropriate page thumbnail. This helps with the management of the pages, because the user will be able to see the thumbnails as a todo for the whole comic, giving a good overview over whether they still need to ink, color or the like for a given page, and it thus also rewards the user whenever they save. """ def slot_check_for_page_update(self, url): if "pages" in self.setupDictionary.keys(): relUrl = os.path.relpath(url, self.projecturl) if relUrl in self.setupDictionary["pages"]: index = self.pagesModel.index(self.setupDictionary["pages"].index(relUrl), 0) if index.isValid(): if os.path.exists(url) is False: # we cannot check from here whether the file in question has been renamed or deleted. self.pagesModel.removeRow(index.row()) return else: # Krita will trigger the filesystemwatcher when doing backupfiles, # so ensure the file is still watched if it exists. self.pagesWatcher.addPath(url) pageItem = self.pagesModel.itemFromIndex(index) page = zipfile.ZipFile(url, "r") dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) thumbnail = QImage.fromData(page.read("preview.png")) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setText(dataList[0]) pageItem.setData(dataList[1], role = CPE.DESCRIPTION) pageItem.setData(relUrl, role = CPE.URL) pageItem.setData(dataList[2], role = CPE.KEYWORDS) pageItem.setData(dataList[3], role = CPE.LASTEDIT) pageItem.setData(dataList[4], role = CPE.EDITOR) self.pagesModel.setItem(index.row(), index.column(), pageItem) """ Resize all the pages in the pages list. It will show a dialog with the options for resizing. Then, it will try to pop up a progress dialog while resizing. The progress dialog shows the remaining time and pages. """ def slot_batch_resize(self): dialog = QDialog() dialog.setWindowTitle(i18n("Resize all Pages")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) sizesBox = comics_export_dialog.comic_export_resize_widget("Scale", batch=True, fileType=False) exporterSizes = comics_exporter.sizesCalculator() dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(sizesBox) dialog.layout().addWidget(buttons) if dialog.exec_() == QDialog.Accepted: progress = QProgressDialog(i18n("Resizing pages..."), str(), 0, len(self.setupDictionary["pages"])) progress.setWindowTitle(i18n("Resizing Pages")) progress.setCancelButton(None) timer = QElapsedTimer() timer.start() config = {} config = sizesBox.get_config(config) for p in range(len(self.setupDictionary["pages"])): absoluteUrl = os.path.join(self.projecturl, self.setupDictionary["pages"][p]) progress.setValue(p) timePassed = timer.elapsed() if (p > 0): timeEstimated = (len(self.setupDictionary["pages"]) - p) * (timePassed / p) passedString = str(int(timePassed / 60000)) + ":" + format(int(timePassed / 1000), "02d") + ":" + format(timePassed % 1000, "03d") estimatedString = str(int(timeEstimated / 60000)) + ":" + format(int(timeEstimated / 1000), "02d") + ":" + format(int(timeEstimated % 1000), "03d") progress.setLabelText(str(i18n("{pages} of {pagesTotal} done. \nTime passed: {passedString}:\n Estimated:{estimated}")).format(pages=p, pagesTotal=len(self.setupDictionary["pages"]), passedString=passedString, estimated=estimatedString)) qApp.processEvents() if os.path.exists(absoluteUrl): doc = Application.openDocument(absoluteUrl) listScales = exporterSizes.get_scale_from_resize_config(config["Scale"], [doc.width(), doc.height(), doc.resolution(), doc.resolution()]) doc.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic") doc.waitForDone() doc.save() doc.waitForDone() doc.close() def slot_show_page_viewer(self): index = int(self.comicPageList.currentIndex().row()) self.page_viewer_dialog.load_comic(self.path_to_config) self.page_viewer_dialog.go_to_page_index(index) self.page_viewer_dialog.show() """ Function to copy the current project location into the clipboard. This is useful for users because they'll be able to use that url to quickly move to the project location in outside applications. """ def slot_copy_project_url(self): if self.projecturl is not None: clipboard = qApp.clipboard() clipboard.setText(str(self.projecturl)) """ Scrape text files with the textlayer keys for text, and put those in a POT file. This makes it possible to handle translations. """ def slot_scrape_translations(self): translationFolder = self.setupDictionary.get("translationLocation", "translations") fullTranslationPath = os.path.join(self.projecturl, translationFolder) os.makedirs(fullTranslationPath, exist_ok=True) textLayersToSearch = self.setupDictionary.get("textLayerNames", ["text"]) scraper = comics_project_translation_scraper.translation_scraper(self.projecturl, translationFolder, textLayersToSearch, self.setupDictionary["projectName"]) # Run text scraper. language = self.setupDictionary.get("language", "en") metadata = {} metadata["title"] = self.setupDictionary.get("title", "") metadata["summary"] = self.setupDictionary.get("summary", "") metadata["keywords"] = ", ".join(self.setupDictionary.get("otherKeywords", [""])) metadata["transnotes"] = self.setupDictionary.get("translatorHeader", "Translator's Notes") scraper.start(self.setupDictionary["pages"], language, metadata) QMessageBox.information(self, i18n("Scraping success"), str(i18n("POT file has been written to: {file}")).format(file=fullTranslationPath), QMessageBox.Ok) """ This is required by the dockwidget class, otherwise unused. """ def canvasChanged(self, canvas): pass """ Add docker to program """ Application.addDockWidgetFactory(DockWidgetFactory("comics_project_manager_docker", DockWidgetFactoryBase.DockRight, comics_project_manager_docker)) diff --git a/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py b/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py index b375062b45..9bddf33bca 100644 --- a/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py +++ b/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py @@ -1,226 +1,233 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ This is a wizard that helps you set up a comics project in Krita. """ import json # For writing to json. import os # For finding the script location. from pathlib import Path # For reading all the files in a directory. import random # For selecting two random words from a list. from PyQt5.QtWidgets import QWidget, QWizard, QWizardPage, QHBoxLayout, QFormLayout, QFileDialog, QLineEdit, QPushButton, QCheckBox, QLabel, QDialog from PyQt5.QtCore import QDate, QLocale, QUuid from krita import * from . import comics_metadata_dialog """ The actual wizard. """ class ComicsProjectSetupWizard(): setupDictionary = {} projectDirectory = "" def __init__(self): # super().__init__(parent) # Search the location of the script for the two lists that are used with the projectname generator. mainP = Path(__file__).parent self.generateListA = [] self.generateListB = [] if Path(mainP / "projectGenLists" / "listA.txt").exists(): for l in open(str(mainP / "projectGenLists" / "listA.txt"), "r"): if l.isspace() == False: self.generateListA.append(l.strip("\n")) if Path(mainP / "projectGenLists" / "listB.txt").exists(): for l in open(str(mainP / "projectGenLists" / "listB.txt"), "r"): if l.isspace() == False: self.generateListB.append(l.strip("\n")) def showDialog(self): # Initialise the setup directory empty toavoid exceptions. self.setupDictionary = {} # ask for a project directory. - self.projectDirectory = QFileDialog.getExistingDirectory(caption=i18n("Where should the comic project go?"), options=QFileDialog.ShowDirsOnly) - if os.path.exists(self.projectDirectory) is False: - return + self.projectDirectory = None + + while self.projectDirectory == None: + self.projectDirectory = QFileDialog.getExistingDirectory(caption=i18n("Where should the comic project go?"), options=QFileDialog.ShowDirsOnly) + if os.path.exists(self.projectDirectory) is False: + return + if os.access(self.projectDirectory, os.W_OK) is False: + QMessageBox.warning(None, i18n("Folder cannot be used"), i18n("Krita doesn't have write access to this folder, so files cannot be made. Please choose a different folder."), QMessageBox.Ok) + self.projectDirectory = None self.pagesDirectory = os.path.relpath(self.projectDirectory, self.projectDirectory) self.exportDirectory = os.path.relpath(self.projectDirectory, self.projectDirectory) wizard = QWizard() wizard.setWindowTitle(i18n("Comic Project Setup")) wizard.setOption(QWizard.IndependentPages, True) # Set up the UI for the wizard basicsPage = QWizardPage() basicsPage.setTitle(i18n("Basic Comic Project Settings")) formLayout = QFormLayout() basicsPage.setLayout(formLayout) projectLayout = QHBoxLayout() self.lnProjectName = QLineEdit() basicsPage.registerField("Project Name*", self.lnProjectName) self.lnProjectName.setToolTip(i18n("A Project name. This can be different from the eventual title")) btnRandom = QPushButton() btnRandom.setText(i18n("Generate")) btnRandom.setToolTip(i18n("If you cannot come up with a project name, our highly sophisticated project name generator will serve to give a classy yet down to earth name.")) btnRandom.clicked.connect(self.slot_generate) projectLayout.addWidget(self.lnProjectName) projectLayout.addWidget(btnRandom) lnConcept = QLineEdit() lnConcept.setToolTip(i18n("What is your comic about? This is mostly for your own convenience so do not worry about what it says too much.")) self.cmbLanguage = comics_metadata_dialog.language_combo_box() self.cmbLanguage.setToolTip(i18n("The main language the comic is in")) self.cmbLanguage.setEntryToCode(str(QLocale.system().name()).split("_")[0]) self.cmbCountry = comics_metadata_dialog.country_combo_box() if QLocale.system() != QLocale.c(): self.slot_update_countries() self.cmbCountry.setEntryToCode(str(QLocale.system().name()).split("_")[-1]) else: self.cmbLanguage.setEntryToCode("en") self.slot_update_countries() self.cmbCountry.setEntryToCode("US") self.cmbLanguage.currentIndexChanged.connect(self.slot_update_countries) self.lnProjectDirectory = QLabel(self.projectDirectory) self.chkMakeProjectDirectory = QCheckBox() labelDirectory = QLabel(i18n("Make a new directory with the project name.")) labelDirectory.setWordWrap(True) stringDirectoryTooltip = i18n("This allows you to select a generic comics project directory, in which a new folder will be made for the project using the given project name.") self.chkMakeProjectDirectory.setToolTip(stringDirectoryTooltip) labelDirectory.setToolTip(stringDirectoryTooltip) self.chkMakeProjectDirectory.setChecked(True) self.lnPagesDirectory = QLineEdit() self.lnPagesDirectory.setText(i18n("pages")) self.lnPagesDirectory.setToolTip(i18n("The name for the folder where the pages are contained. If it does not exist, it will be created.")) self.lnExportDirectory = QLineEdit() self.lnExportDirectory.setText(i18n("export")) self.lnExportDirectory.setToolTip(i18n("The name for the folder where the export is put. If it does not exist, it will be created.")) self.lnTemplateLocation = QLineEdit() self.lnTemplateLocation.setText(i18n("templates")) self.lnTemplateLocation.setToolTip(i18n("The name for the folder where the page templates are sought in.")) self.lnTranslationLocation = QLineEdit() self.lnTranslationLocation.setText(i18n("translations")) self.lnTranslationLocation.setToolTip("This is the location that POT files will be stored to and PO files will be read from.") formLayout.addRow(i18n("Comic concept:"), lnConcept) formLayout.addRow(i18n("Project name:"), projectLayout) formLayout.addRow(i18n("Main language:"), self.cmbLanguage) formLayout.addRow("", self.cmbCountry) buttonMetaData = QPushButton(i18n("Meta Data")) buttonMetaData.clicked.connect(self.slot_edit_meta_data) wizard.addPage(basicsPage) foldersPage = QWizardPage() foldersPage.setTitle(i18n("Folder names and other.")) folderFormLayout = QFormLayout() foldersPage.setLayout(folderFormLayout) folderFormLayout.addRow(i18n("Project directory:"), self.lnProjectDirectory) folderFormLayout.addRow(self.chkMakeProjectDirectory, labelDirectory) folderFormLayout.addRow(i18n("Pages directory"), self.lnPagesDirectory) folderFormLayout.addRow(i18n("Export directory"), self.lnExportDirectory) folderFormLayout.addRow(i18n("Template directory"), self.lnTemplateLocation) folderFormLayout.addRow(i18n("Translation directory"), self.lnTranslationLocation) folderFormLayout.addRow("", buttonMetaData) wizard.addPage(foldersPage) # Execute the wizard, and after wards... if (wizard.exec_()): # First get the directories, check if the directories exist, and otherwise make them. self.pagesDirectory = self.lnPagesDirectory.text() self.exportDirectory = self.lnExportDirectory.text() self.templateLocation = self.lnTemplateLocation.text() self.translationLocation = self.lnTranslationLocation.text() projectPath = Path(self.projectDirectory) + # Only make a project directory if the checkbox for that has been checked. if self.chkMakeProjectDirectory.isChecked(): projectPath = projectPath / self.lnProjectName.text() if projectPath.exists() is False: projectPath.mkdir() self.projectDirectory = str(projectPath) if Path(projectPath / self.pagesDirectory).exists() is False: Path(projectPath / self.pagesDirectory).mkdir() if Path(projectPath / self.exportDirectory).exists() is False: Path(projectPath / self.exportDirectory).mkdir() if Path(projectPath / self.templateLocation).exists() is False: Path(projectPath / self.templateLocation).mkdir() if Path(projectPath / self.translationLocation).exists() is False: Path(projectPath / self.translationLocation).mkdir() # Then store the information into the setup diactionary. self.setupDictionary["projectName"] = self.lnProjectName.text() self.setupDictionary["concept"] = lnConcept.text() self.setupDictionary["language"] = str(self.cmbLanguage.codeForCurrentEntry()) self.setupDictionary["pagesLocation"] = self.pagesDirectory self.setupDictionary["exportLocation"] = self.exportDirectory self.setupDictionary["templateLocation"] = self.templateLocation self.setupDictionary["translationLocation"] = self.translationLocation self.setupDictionary["uuid"] = QUuid.createUuid().toString() # Finally, write the dictionary into the json file. self.writeConfig() """ This calls up the metadata dialog, for if people already have information they want to type in at the setup stage. Not super likely, but the organisation and management aspect of the comic manager means we should give the option to organise as smoothly as possible. """ def slot_edit_meta_data(self): dialog = comics_metadata_dialog.comic_meta_data_editor() self.setupDictionary["language"] = str(self.cmbLanguage.codeForCurrentEntry()) dialog.setConfig(self.setupDictionary) dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.cmbLanguage.setEntryToCode(self.setupDictionary["language"]) """ Update the country list when the language list changes. """ def slot_update_countries(self): code = self.cmbLanguage.codeForCurrentEntry() self.cmbCountry.set_country_for_locale(code) """ Write the actual config to the chosen project directory. """ def writeConfig(self): print("CPMT: writing comic configuration...") print(self.projectDirectory) configFile = open(os.path.join(self.projectDirectory, "comicConfig.json"), "w", newline="", encoding="utf-16") json.dump(self.setupDictionary, configFile, indent=4, sort_keys=True, ensure_ascii=False) configFile.close() print("CPMT: done") """ As you may be able to tell, the random projectname generator is hardly sophisticated. It picks a word from a list of names of figures from Greek Mythology, and a name from a list of vegetables and fruits and combines the two camelcased. It makes for good codenames at the least. """ def slot_generate(self): if len(self.generateListA) > 0 and len(self.generateListB) > 0: nameA = self.generateListA[random.randint(0, len(self.generateListA) - 1)] nameB = self.generateListB[random.randint(0, len(self.generateListB) - 1)] self.lnProjectName.setText(str(nameA.title() + nameB.title())) diff --git a/plugins/python/comics_project_management_tools/comics_template_dialog.py b/plugins/python/comics_project_management_tools/comics_template_dialog.py index 96ea2f7233..3ff052ef1b 100644 --- a/plugins/python/comics_project_management_tools/comics_template_dialog.py +++ b/plugins/python/comics_project_management_tools/comics_template_dialog.py @@ -1,469 +1,480 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ Template dialog """ import os import shutil #from PyQt5.QtGui import * -from PyQt5.QtWidgets import QDialog, QComboBox, QDialogButtonBox, QVBoxLayout, QFormLayout, QGridLayout, QWidget, QPushButton, QHBoxLayout, QLabel, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget, QColorDialog +from PyQt5.QtWidgets import QDialog, QComboBox, QDialogButtonBox, QVBoxLayout, QFormLayout, QGridLayout, QWidget, QPushButton, QHBoxLayout, QLabel, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget, QColorDialog, QProgressDialog from PyQt5.QtCore import QLocale, Qt, QByteArray, QRectF from PyQt5.QtGui import QImage, QPainter, QPixmap from krita import * """ Quick and dirty QComboBox subclassing that handles unitconversion for us. """ class simpleUnitBox(QComboBox): pixels = i18n("Pixels") inches = i18n("Inches") centimeter = i18n("Centimeter") millimeter = i18n("millimeter") def __init__(self): super(simpleUnitBox, self).__init__() self.addItem(self.pixels) self.addItem(self.inches) self.addItem(self.centimeter) self.addItem(self.millimeter) if QLocale().system().measurementSystem() is QLocale.MetricSystem: self.setCurrentIndex(2) # set to centimeter if metric system. else: self.setCurrentIndex(1) def pixelsForUnit(self, unit, DPI): if (self.currentText() == self.pixels): return unit elif (self.currentText() == self.inches): return self.inchesToPixels(unit, DPI) elif (self.currentText() == self.centimeter): return self.centimeterToPixels(unit, DPI) elif (self.currentText() == self.millimeter): return self.millimeterToPixels(unit, DPI) def inchesToPixels(self, inches, DPI): return DPI * inches def centimeterToInches(self, cm): return cm / 2.54 def centimeterToPixels(self, cm, DPI): return self.inchesToPixels(self.centimeterToInches(cm), DPI) def millimeterToCentimeter(self, mm): return mm / 10 def millimeterToPixels(self, mm, DPI): return self.inchesToPixels(self.centimeterToInches(self.millimeterToCentimeter(mm)), DPI) class comics_template_dialog(QDialog): templateDirectory = str() templates = QComboBox() buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) def __init__(self, templateDirectory): super().__init__() self.templateDirectory = templateDirectory self.setWindowTitle(i18n("Add new Template")) self.setLayout(QVBoxLayout()) self.templates = QComboBox() self.templates.setEnabled(False) self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.buttons.button(QDialogButtonBox.Ok).setEnabled(False) mainWidget = QWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(self.buttons) mainWidget.setLayout(QVBoxLayout()) btn_create = QPushButton(i18n("Create Template")) btn_create.clicked.connect(self.slot_create_template) btn_import = QPushButton(i18n("Import Templates")) btn_import.clicked.connect(self.slot_import_template) mainWidget.layout().addWidget(self.templates) mainWidget.layout().addWidget(btn_create) mainWidget.layout().addWidget(btn_import) self.fill_templates() def fill_templates(self): self.templates.clear() for entry in os.scandir(self.templateDirectory): if entry.name.endswith('.kra') and entry.is_file(): name = os.path.relpath(entry.path, self.templateDirectory) self.templates.addItem(name) if self.templates.model().rowCount() > 0: self.templates.setEnabled(True) self.buttons.button(QDialogButtonBox.Ok).setEnabled(True) def slot_create_template(self): create = comics_template_create(self.templateDirectory) if create.exec_() == QDialog.Accepted: if (create.prepare_krita_file()): self.fill_templates() def slot_import_template(self): filenames = QFileDialog.getOpenFileNames(caption=i18n("Which files should be added to the template folder?"), directory=self.templateDirectory, filter=str(i18n("Krita files") + "(*.kra)"))[0] for file in filenames: shutil.copy2(file, self.templateDirectory) self.fill_templates() def url(self): return os.path.join(self.templateDirectory, self.templates.currentText()) class comics_template_create(QDialog): urlSavedTemplate = str() templateDirectory = str() currentColor = QColor(Qt.white) def __init__(self, templateDirectory): super().__init__() self.templateDirectory = templateDirectory self.setWindowTitle(i18n("Create new Template")) self.setLayout(QVBoxLayout()) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QWidget() explanation = QLabel(i18n("This allows you to make a template document with guides.\nThe width and height are the size of the live-area, the safe area is the live area minus the margins, and the full image is the live area plus the bleeds.")) explanation.setWordWrap(True) self.layout().addWidget(explanation) self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) mainWidget.setLayout(QHBoxLayout()) elements = QWidget() elements.setLayout(QVBoxLayout()) previewLayout = QWidget() previewLayout.setLayout(QVBoxLayout()) mainWidget.layout().addWidget(elements) mainWidget.layout().addWidget(previewLayout) self.imagePreview = QLabel() self.imagePreview.setMinimumSize(256, 256) previewLayout.layout().addWidget(self.imagePreview) self.templateName = QLineEdit() self.templateName.setPlaceholderText("...") elements.layout().addWidget(self.templateName) templateBGColor = QPushButton(i18n("Select background color")) templateBGColor.clicked.connect(self.slot_call_color_dialog) previewLayout.layout().addWidget(templateBGColor) self.DPI = QSpinBox() self.DPI.setMaximum(1200) self.DPI.setValue(300) self.spn_width = QDoubleSpinBox() self.spn_width.setMaximum(10000) self.spn_height = QDoubleSpinBox() self.spn_height.setMaximum(10000) self.widthUnit = simpleUnitBox() self.heightUnit = simpleUnitBox() widgetSize = QWidget() sizeForm = QFormLayout() sizeForm.addRow(i18n("DPI:"), self.DPI) widthLayout = QHBoxLayout() widthLayout.addWidget(self.spn_width) widthLayout.addWidget(self.widthUnit) sizeForm.addRow(i18n("Width:"), widthLayout) heightLayout = QHBoxLayout() heightLayout.addWidget(self.spn_height) heightLayout.addWidget(self.heightUnit) sizeForm.addRow(i18n("Height:"), heightLayout) widgetSize.setLayout(sizeForm) elements.layout().addWidget(widgetSize) marginAndBleed = QTabWidget() elements.layout().addWidget(marginAndBleed) margins = QWidget() marginForm = QGridLayout() margins.setLayout(marginForm) self.marginLeft = QDoubleSpinBox() self.marginLeft.setMaximum(1000) self.marginLeftUnit = simpleUnitBox() self.marginRight = QDoubleSpinBox() self.marginRight.setMaximum(1000) self.marginRightUnit = simpleUnitBox() self.marginTop = QDoubleSpinBox() self.marginTop.setMaximum(1000) self.marginTopUnit = simpleUnitBox() self.marginBottom = QDoubleSpinBox() self.marginBottom.setMaximum(1000) self.marginBottomUnit = simpleUnitBox() marginForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignRight) marginForm.addWidget(self.marginLeft, 0, 1) marginForm.addWidget(self.marginLeftUnit, 0, 2) marginForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignRight) marginForm.addWidget(self.marginTop, 1, 1) marginForm.addWidget(self.marginTopUnit, 1, 2) marginForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignRight) marginForm.addWidget(self.marginRight, 2, 1) marginForm.addWidget(self.marginRightUnit, 2, 2) marginForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignRight) marginForm.addWidget(self.marginBottom, 3, 1) marginForm.addWidget(self.marginBottomUnit, 3, 2) marginAndBleed.addTab(margins, i18n("Margins")) bleeds = QWidget() bleedsForm = QGridLayout() bleeds.setLayout(bleedsForm) self.bleedLeft = QDoubleSpinBox() self.bleedLeft.setMaximum(1000) self.bleedLeftUnit = simpleUnitBox() self.bleedRight = QDoubleSpinBox() self.bleedRight.setMaximum(1000) self.bleedRightUnit = simpleUnitBox() self.bleedTop = QDoubleSpinBox() self.bleedTop.setMaximum(1000) self.bleedTopUnit = simpleUnitBox() self.bleedBottom = QDoubleSpinBox() self.bleedBottom.setMaximum(1000) self.bleedBottomUnit = simpleUnitBox() bleedsForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedLeft, 0, 1) bleedsForm.addWidget(self.bleedLeftUnit, 0, 2) bleedsForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedTop, 1, 1) bleedsForm.addWidget(self.bleedTopUnit, 1, 2) bleedsForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedRight, 2, 1) bleedsForm.addWidget(self.bleedRightUnit, 2, 2) bleedsForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedBottom, 3, 1) bleedsForm.addWidget(self.bleedBottomUnit, 3, 2) marginAndBleed.addTab(bleeds, i18n("Bleeds")) if QLocale().system().measurementSystem() is QLocale.MetricSystem: self.setDefaults("European") else: self.setDefaults("American") self.spn_width.valueChanged.connect(self.updateImagePreview) self.widthUnit.currentIndexChanged.connect(self.updateImagePreview) self.spn_height.valueChanged.connect(self.updateImagePreview) self.heightUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginLeft.valueChanged.connect(self.updateImagePreview) self.marginLeftUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginRight.valueChanged.connect(self.updateImagePreview) self.marginRightUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginTop.valueChanged.connect(self.updateImagePreview) self.marginTopUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginBottom.valueChanged.connect(self.updateImagePreview) self.marginBottomUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedLeft.valueChanged.connect(self.updateImagePreview) self.bleedLeftUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedRight.valueChanged.connect(self.updateImagePreview) self.bleedRightUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedTop.valueChanged.connect(self.updateImagePreview) self.bleedTopUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedBottom.valueChanged.connect(self.updateImagePreview) self.bleedBottomUnit.currentIndexChanged.connect(self.updateImagePreview) self.updateImagePreview() def slot_call_color_dialog(self): dialog = QColorDialog(self) dialog.setCurrentColor(self.currentColor) if dialog.exec_() == QDialog.Accepted: self.currentColor = dialog.currentColor() self.updateImagePreview() def prepare_krita_file(self): wBase = max(self.widthUnit.pixelsForUnit(self.spn_width.value(), self.DPI.value()), 1) bL = self.bleedLeftUnit.pixelsForUnit(self.bleedLeft.value(), self.DPI.value()) bR = self.bleedRightUnit.pixelsForUnit(self.bleedRight.value(), self.DPI.value()) mL = self.marginLeftUnit.pixelsForUnit(self.marginLeft.value(), self.DPI.value()) mR = self.marginRightUnit.pixelsForUnit(self.marginRight.value(), self.DPI.value()) hBase = max(self.heightUnit.pixelsForUnit(self.spn_height.value(), self.DPI.value()), 1) bT = self.bleedTopUnit.pixelsForUnit(self.bleedTop.value(), self.DPI.value()) bB = self.bleedBottomUnit.pixelsForUnit(self.bleedBottom.value(), self.DPI.value()) mT = self.marginTopUnit.pixelsForUnit(self.marginTop.value(), self.DPI.value()) mB = self.marginBottomUnit.pixelsForUnit(self.marginBottom.value(), self.DPI.value()) template = Application.createDocument((wBase + bL + bR), (hBase + bT + bB), self.templateName.text(), "RGBA", "U8", "sRGB built-in", self.DPI.value()) backgroundName = i18n("Background") if len(template.topLevelNodes()) > 0: backgroundNode = template.topLevelNodes()[0] backgroundNode.setName(backgroundName) else: backgroundNode = template.createNode(backgroundName, "paintlayer") template.rootNode().addChildNode(backgroundNode, None) - red = int(self.currentColor.redF()*255) - green = int(self.currentColor.greenF()*255) - blue = int(self.currentColor.blueF()*255) - alpha = int(self.currentColor.alphaF()*255) + pixelByteArray = QByteArray() - for byteNumber in range(template.width()*template.height()): - pixelByteArray.append( blue.to_bytes(1, byteorder='little')) - pixelByteArray.append(green.to_bytes(1, byteorder='little')) - pixelByteArray.append( red.to_bytes(1, byteorder='little')) - pixelByteArray.append(alpha.to_bytes(1, byteorder='little')) + if self.currentColor == Qt.white: + pixelByteArray = backgroundNode.pixelData(0, 0, template.width(), template.height()) + pixelByteArray.fill(int(255).to_bytes(1, byteorder='little')) + else: + red = int(self.currentColor.redF()*255).to_bytes(1, byteorder='little') + green = int(self.currentColor.greenF()*255).to_bytes(1, byteorder='little') + blue = int(self.currentColor.blueF()*255).to_bytes(1, byteorder='little') + alpha = int(self.currentColor.alphaF()*255).to_bytes(1, byteorder='little') + + progress = QProgressDialog(i18n("Creating template"), str(), 0, template.width()*template.height()) + progress.setCancelButton(None) + progress.show() + qApp.processEvents() + for byteNumber in range(template.width()*template.height()): + pixelByteArray.append( blue) + pixelByteArray.append(green) + pixelByteArray.append( red) + pixelByteArray.append(alpha) + progress.setValue(byteNumber) backgroundNode.setPixelData(pixelByteArray, 0, 0, template.width(), template.height()) backgroundNode.setOpacity(255) backgroundNode.setLocked(True) sketchNode = template.createNode(i18n("Sketch"), "paintlayer") template.rootNode().addChildNode(sketchNode, backgroundNode) verticalGuides = [] verticalGuides.append(bL) verticalGuides.append(bL + mL) verticalGuides.append((bL + wBase) - mR) verticalGuides.append(bL + wBase) horizontalGuides = [] horizontalGuides.append(bT) horizontalGuides.append(bT + mT) horizontalGuides.append((bT + hBase) - mB) horizontalGuides.append(bT + hBase) template.setHorizontalGuides(horizontalGuides) template.setVerticalGuides(verticalGuides) template.setGuidesVisible(True) template.setGuidesLocked(True) template.refreshProjection() self.urlSavedTemplate = os.path.join(self.templateDirectory, self.templateName.text() + ".kra") success = template.exportImage(self.urlSavedTemplate, InfoObject()) print("CPMT: Template", self.templateName.text(), "made and saved.") template.waitForDone() template.close() return success def updateImagePreview(self): maxSize = 256 wBase = max(self.widthUnit.pixelsForUnit(self.spn_width.value(), self.DPI.value()), 1) bL = self.bleedLeftUnit.pixelsForUnit(self.bleedLeft.value(), self.DPI.value()) bR = self.bleedRightUnit.pixelsForUnit(self.bleedRight.value(), self.DPI.value()) mL = self.marginLeftUnit.pixelsForUnit(self.marginLeft.value(), self.DPI.value()) mR = self.marginRightUnit.pixelsForUnit(self.marginRight.value(), self.DPI.value()) hBase = max(self.heightUnit.pixelsForUnit(self.spn_height.value(), self.DPI.value()), 1) bT = self.bleedTopUnit.pixelsForUnit(self.bleedTop.value(), self.DPI.value()) bB = self.bleedBottomUnit.pixelsForUnit(self.bleedBottom.value(), self.DPI.value()) mT = self.marginTopUnit.pixelsForUnit(self.marginTop.value(), self.DPI.value()) mB = self.marginBottomUnit.pixelsForUnit(self.marginBottom.value(), self.DPI.value()) scaleRatio = maxSize / (hBase + bT + bB) if wBase > hBase: scaleRatio = maxSize / (wBase + bR + bL) width = (wBase + bL + bR) * scaleRatio height = (hBase + bT + bB) * scaleRatio topLeft = [max((maxSize - width) / 2, 0), max((maxSize - height) / 2, 0)] image = QImage(maxSize, maxSize, QImage.Format_ARGB32) image.fill(Qt.transparent) p = QPainter(image) p.setBrush(self.currentColor) CanvasSize = QRectF(topLeft[0], topLeft[1], width, height) p.drawRect(CanvasSize.toRect()) # Draw bleeds. PageSize = CanvasSize PageSize.setWidth(width - (bR * scaleRatio)) PageSize.setHeight(height - (bB * scaleRatio)) PageSize.setX(PageSize.x() + (bL * scaleRatio)) PageSize.setY(PageSize.y() + (bT * scaleRatio)) p.setPen(Qt.blue) p.setBrush(Qt.transparent) p.drawRect(PageSize.toRect()) # Draw liveArea LiveArea = PageSize LiveArea.setWidth(LiveArea.width() - (mR * scaleRatio)) LiveArea.setHeight(LiveArea.height() - (mB * scaleRatio)) LiveArea.setX(LiveArea.x() + (mL * scaleRatio)) LiveArea.setY(LiveArea.y() + (mT * scaleRatio)) p.setPen(Qt.blue) p.drawRect(LiveArea.toRect()) p.end() self.imagePreview.setPixmap(QPixmap.fromImage(image)) def setDefaults(self, type): if type == "American": # American 11x17 inch self.spn_width.setValue(11) self.widthUnit.setCurrentIndex(1) self.spn_height.setValue(17) self.heightUnit.setCurrentIndex(1) self.bleedBottom.setValue(1) self.bleedBottomUnit.setCurrentIndex(1) self.bleedTop.setValue(1) self.bleedTopUnit.setCurrentIndex(1) self.bleedLeft.setValue(0.5) self.bleedLeftUnit.setCurrentIndex(1) self.bleedRight.setValue(0.5) self.bleedRightUnit.setCurrentIndex(1) self.marginBottom.setValue(0.745) self.marginBottomUnit.setCurrentIndex(1) self.marginTop.setValue(0.745) self.marginTopUnit.setCurrentIndex(1) self.marginRight.setValue(0.5) self.marginRightUnit.setCurrentIndex(1) self.marginLeft.setValue(0.5) self.marginLeftUnit.setCurrentIndex(1) if type == "European": # European A4 self.spn_width.setValue(21) self.widthUnit.setCurrentIndex(2) self.spn_height.setValue(29.7) self.heightUnit.setCurrentIndex(2) self.bleedBottom.setValue(5) self.bleedBottomUnit.setCurrentIndex(3) self.bleedTop.setValue(5) self.bleedTopUnit.setCurrentIndex(3) self.bleedLeft.setValue(5) self.bleedLeftUnit.setCurrentIndex(3) self.bleedRight.setValue(5) self.bleedRightUnit.setCurrentIndex(3) self.marginBottom.setValue(1.5) self.marginBottomUnit.setCurrentIndex(2) self.marginTop.setValue(1.5) self.marginTopUnit.setCurrentIndex(2) self.marginRight.setValue(1) self.marginRightUnit.setCurrentIndex(2) self.marginLeft.setValue(1) self.marginLeftUnit.setCurrentIndex(2) def url(self): return self.urlSavedTemplate