diff --git a/stuff/doc/LICENSE/LICENSE_winink.txt b/stuff/doc/LICENSE/LICENSE_winink.txt new file mode 100644 index 0000000..d45cec5 --- /dev/null +++ b/stuff/doc/LICENSE/LICENSE_winink.txt @@ -0,0 +1,23 @@ +Windows Pointer Input Message support code files from Krita +Copyright (c) 2017 Alvin Wong + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/toonz/sources/include/toonz/preferences.h b/toonz/sources/include/toonz/preferences.h index 9ea1202..4d5141c 100644 --- a/toonz/sources/include/toonz/preferences.h +++ b/toonz/sources/include/toonz/preferences.h @@ -575,6 +575,11 @@ public: } //! \sa The \p sysctl unix command. std::string getLayerNameEncoding() const { return m_layerNameEncoding; }; + // Tablet tab + + void enableWinInk(bool on); + bool isWinInkEnabled() const { return m_enableWinInk; } + Q_SIGNALS: void stopAutoSave(); @@ -706,6 +711,8 @@ private: TPixel32 m_currentColumnColor; + bool m_enableWinInk = false; + private: Preferences(); ~Preferences(); diff --git a/toonz/sources/toonz/CMakeLists.txt b/toonz/sources/toonz/CMakeLists.txt index e914f41..b262ffc 100644 --- a/toonz/sources/toonz/CMakeLists.txt +++ b/toonz/sources/toonz/CMakeLists.txt @@ -123,6 +123,7 @@ set(MOC_HEADERS svnupdatedialog.h svnpurgedialog.h tapp.h + kis_tablet_support_win8.h tasksviewer.h testpanel.h tfarmstuff.h @@ -259,6 +260,7 @@ set(SOURCES soundtrackexport.cpp startuppopup.cpp subcameramanager.cpp + kis_tablet_support_win8.cpp timestretchpopup.cpp trackerpopup.cpp vectorizerpopup.cpp diff --git a/toonz/sources/toonz/kis_tablet_support_win8.cpp b/toonz/sources/toonz/kis_tablet_support_win8.cpp new file mode 100644 index 0000000..c196885 --- /dev/null +++ b/toonz/sources/toonz/kis_tablet_support_win8.cpp @@ -0,0 +1,1232 @@ +/* +* This file is based on the Windows Pointer Input Message support code files by +* Alvin Wong. +* Notwithstanding the license specified in this repository, this file is +* redistributed under BSD 2-Clause license written below in order to keep +* backporting available. +* +* All contributions by Alvin Wong: +* Copyright (c) 2017 Alvin Wong +* All rights reserved. +* +* All other contributions: +* Copyright (c) 2018, the respective contributors. +* All rights reserved. +* +* Each contributor holds copyright over their respective contributions. +* The project versioning (Git) records all such contribution source information. +* +* Redistribution and use in source and binary forms, with or without +- modification, are permitted provided that the following conditions +- are met: +- +- 1. Redistributions of source code must retain the above copyright +- notice, this list of conditions and the following disclaimer. +- 2. Redistributions in binary form must reproduce the above copyright +- notice, this list of conditions and the following disclaimer in the +- documentation and/or other materials provided with the distribution. +- +- THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +- OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +- IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +- NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +- THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Get Windows 8 API prototypes and types +#ifdef WINVER +#undef WINVER +#endif +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define WINVER 0x0602 +#define _WIN32_WINNT 0x0602 + +#include "kis_tablet_support_win8.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef _WIN32 + +#ifdef KRITA +#include +#endif + +#include +#include + +#ifndef Q_OS_WIN +#error This file must not be compiled for non-Windows systems +#endif + +namespace { + +class Win8PointerInputApi { +#define WIN8_POINTER_INPUT_API_LIST(FUNC) \ + /* Pointer Input Functions */ \ + FUNC(GetPointerPenInfo) \ + FUNC(GetPointerPenInfoHistory) \ + FUNC(GetPointerType) \ + /* Pointer Device Functions */ \ + FUNC(GetPointerDevices) \ + /*FUNC(GetPointerDeviceProperties)*/ \ + FUNC(GetPointerDevice) \ + FUNC(GetPointerDeviceRects) \ + /*FUNC(RegisterPointerDeviceNotifications)*/ \ + /* end */ + + bool m_loaded; + +public: +#define DEFINE_FP_FROM_WINAPI(func) \ + \ +public: \ + using p##func##_t = std::add_pointer::type; \ + \ +private: \ + p##func##_t m_p##func = nullptr; \ + \ +public: \ + const p##func##_t &func = m_p##func; // const fp ref to member + + WIN8_POINTER_INPUT_API_LIST(DEFINE_FP_FROM_WINAPI) + +#undef DEFINE_FP_FROM_WINAPI + +public: + Win8PointerInputApi() : m_loaded(false) {} + + bool init() { + if (m_loaded) { + return true; + } + + QLibrary user32Lib("user32"); + if (!user32Lib.load()) { + qWarning() << "Failed to load user32.dll! This really should not happen."; + return false; + } + +#ifdef KRITA +#define LOAD_AND_CHECK_FP_FROM_WINAPI(func) \ + m_p##func = reinterpret_cast(user32Lib.resolve(#func)); \ + if (!m_p##func) { \ + dbgTablet << "Failed to load function " #func " from user32.dll"; \ + return false; \ + } +#else +#define LOAD_AND_CHECK_FP_FROM_WINAPI(func) \ + m_p##func = reinterpret_cast(user32Lib.resolve(#func)); \ + if (!m_p##func) { \ + return false; \ + } +#endif + + WIN8_POINTER_INPUT_API_LIST(LOAD_AND_CHECK_FP_FROM_WINAPI) + +#undef LOAD_AND_CHECK_FP_FROM_WINAPI + +#ifdef KRITA + dbgTablet << "Loaded Windows 8 Pointer Input API functions"; +#endif + m_loaded = true; + return true; + } + + bool isLoaded() { return m_loaded; } + +#undef WIN8_POINTER_INPUT_API_LIST +}; // class Win8PointerInputApi + +Win8PointerInputApi api; + +class PointerFlagsWrapper { + const POINTER_FLAGS f; + +public: + PointerFlagsWrapper(POINTER_FLAGS flags) : f(flags) {} + + static PointerFlagsWrapper fromPointerInfo(const POINTER_INFO &pointerInfo) { + return PointerFlagsWrapper(pointerInfo.pointerFlags); + } + + static PointerFlagsWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) { + return fromPointerInfo(penInfo.pointerInfo); + } + + bool isNew() const { return f & POINTER_FLAG_NEW; } + + bool isInRange() const { return f & POINTER_FLAG_INRANGE; } + + bool isInContact() const { return f & POINTER_FLAG_INCONTACT; } + + bool isFirstButtonDown() const { return f & POINTER_FLAG_FIRSTBUTTON; } + + bool isSecondButtonDown() const { return f & POINTER_FLAG_SECONDBUTTON; } + + bool isThirdButtonDown() const { return f & POINTER_FLAG_THIRDBUTTON; } + + bool isForthButtonDown() const { return f & POINTER_FLAG_FOURTHBUTTON; } + + bool isFifthButtonDown() const { return f & POINTER_FLAG_FIFTHBUTTON; } + + bool isPrimary() const { return f & POINTER_FLAG_PRIMARY; } + + bool isConfidence() const { return f & POINTER_FLAG_CONFIDENCE; } + + bool isCancelled() const { return f & POINTER_FLAG_CANCELED; } + + bool isDown() const { return f & POINTER_FLAG_DOWN; } + + bool isUpdate() const { return f & POINTER_FLAG_UPDATE; } + + bool isUp() const { return f & POINTER_FLAG_UP; } + + bool isWheel() const { return f & POINTER_FLAG_WHEEL; } + + bool isHWheel() const { return f & POINTER_FLAG_HWHEEL; } + + bool isCaptureChanged() const { return f & POINTER_FLAG_CAPTURECHANGED; } + + bool hasTransform() const { + // mingw-w64 headers is missing this flag + // return f & POINTER_FLAG_HASTRANSFORM; + return f & 0x00400000; + } +}; // class PointerFlagsWrapper + +class PenFlagsWrapper { + const PEN_FLAGS f; + +public: + PenFlagsWrapper(PEN_FLAGS flags) : f(flags) {} + + static PenFlagsWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) { + return PenFlagsWrapper(penInfo.penFlags); + } + + bool isBarrelPressed() const { return f & PEN_FLAG_BARREL; } + + bool isInverted() const { return f & PEN_FLAG_INVERTED; } + + bool isEraserPressed() const { return f & PEN_FLAG_ERASER; } +}; // class PenFlagsWrapper + +class PenMaskWrapper { + const PEN_MASK f; + +public: + PenMaskWrapper(PEN_MASK mask) : f(mask) {} + + static PenMaskWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) { + return PenMaskWrapper(penInfo.penMask); + } + + bool pressureValid() const { return f & PEN_MASK_PRESSURE; } + + bool rotationValid() const { return f & PEN_MASK_ROTATION; } + + bool tiltXValid() const { return f & PEN_MASK_TILT_X; } + + bool tiltYValid() const { return f & PEN_MASK_TILT_Y; } +}; // class PenMaskWrapper + +struct PointerDeviceItem { + // HANDLE handle; + // RECT pointerDeviceRect; + // RECT displayRect; + qreal himetricToPixelX; + qreal himetricToPixelY; + qreal pixelOffsetX; + qreal pixelOffsetY; + DISPLAYCONFIG_ROTATION deviceOrientation; // This is needed to fix tilt +}; + +QHash penDevices; + +struct PenPointerItem { + // int pointerId; + // POINTER_PEN_INFO penInfo; + HWND hwnd; + HANDLE deviceHandle; + QPointer activeWidget; // Current widget receiving events + qreal oneOverDpr; // 1 / devicePixelRatio of activeWidget + bool widgetIsCaptured; // Current widget is capturing a pen cown event + bool widgetIsIgnored; // Pen events should be ignored until pen up + bool widgetAcceptsPenEvent; // Whether the widget accepts pen events + + bool isCaptured() const { return widgetIsCaptured; } +}; + +QHash penPointers; +// int primaryPenPointerId; + +bool handlePointerMsg(const MSG &msg); + +// extern "C" { +// +// LRESULT CALLBACK pointerDeviceNotificationsWndProc(HWND hwnd, UINT uMsg, +// WPARAM wParam, LPARAM lParam) +// { +// switch (uMsg) { +// case WM_POINTERDEVICECHANGE: +// dbgTablet << "I would want to handle this WM_POINTERDEVICECHANGE +// event, but ms just doesn't want me to use it"; +// dbgTablet << " wParam:" << wParam; +// dbgTablet << " lParam:" << lParam; +// return 0; +// case WM_POINTERDEVICEINRANGE: +// dbgTablet << "I would want to handle this WM_POINTERDEVICEINRANGE +// event, but ms just doesn't want me to use it"; +// dbgTablet << " wParam:" << wParam; +// dbgTablet << " lParam:" << lParam; +// return 0; +// case WM_POINTERDEVICEOUTOFRANGE: +// dbgTablet << "I would want to handle this WM_POINTERDEVICEOUTOFRANGE +// event, but ms just doesn't want me to use it"; +// dbgTablet << " wParam:" << wParam; +// dbgTablet << " lParam:" << lParam; +// return 0; +// } +// return DefWindowProcW(hwnd, uMsg, wParam, lParam); +// } +// +// } // extern "C" + +} // namespace + +bool KisTabletSupportWin8::isAvailable() { + // Just try loading the APIs + return api.init(); +} + +bool KisTabletSupportWin8::isPenDeviceAvailable() { + if (!api.init()) { + return false; + } + UINT32 deviceCount = 0; + if (!api.GetPointerDevices(&deviceCount, nullptr)) { +#ifdef KRITA + dbgTablet << "GetPointerDevices failed"; +#endif + return false; + } + if (deviceCount == 0) { +#ifdef KRITA + dbgTablet << "No pointer devices"; +#endif + return false; + } + QVector devices(deviceCount); + if (!api.GetPointerDevices(&deviceCount, devices.data())) { +#ifdef KRITA + dbgTablet << "GetPointerDevices failed"; +#endif + return false; + } + bool hasPenDevice = false; + Q_FOREACH (const POINTER_DEVICE_INFO &device, devices) { +#ifdef KRITA + dbgTablet << "Found pointer device" << static_cast(device.device) + << QString::fromWCharArray(device.productString) + << "type:" << device.pointerDeviceType; +#endif + if (device.pointerDeviceType == POINTER_DEVICE_TYPE_INTEGRATED_PEN || + device.pointerDeviceType == POINTER_DEVICE_TYPE_EXTERNAL_PEN) { + hasPenDevice = true; + } + } +#ifdef KRITA + dbgTablet << "hasPenDevice:" << hasPenDevice; +#endif + return hasPenDevice; +} + +bool KisTabletSupportWin8::init() { return api.init(); } + +// void KisTabletSupportWin8::registerPointerDeviceNotifications() +// { +// const wchar_t *className = L"w8PointerMsgWindow"; +// HINSTANCE hInst = static_cast(GetModuleHandleW(nullptr)); +// WNDCLASSEXW wc; +// wc.cbSize = sizeof(WNDCLASSEXW); +// wc.style = 0; +// wc.lpfnWndProc = pointerDeviceNotificationsWndProc; +// wc.cbClsExtra = 0; +// wc.cbWndExtra = 0; +// wc.hInstance = hInst; +// wc.hCursor = 0; +// wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW); +// wc.hIcon = 0; +// wc.hIconSm = 0; +// wc.lpszMenuName = 0; +// wc.lpszClassName = className; +// +// if (RegisterClassEx(&wc)) { +// HWND hwnd = CreateWindowEx(0, className, nullptr, 0, 0, 0, 0, 0, +// HWND_MESSAGE, nullptr, hInst, nullptr); +// api.RegisterPointerDeviceNotifications(hwnd, TRUE); +// } else { +// dbgTablet << "Cannot register dummy window"; +// } +// } + +bool KisTabletSupportWin8::nativeEventFilter(const QByteArray &eventType, + void *message, long *result) { + if (!result) { + // I don't know why this even happens, but it actually does + // And the same event is sent in again with result != nullptr + return false; + } + + // This is only installed on Windows so there is no reason to check eventType + MSG &msg = *static_cast(message); + + switch (msg.message) { + case WM_POINTERDOWN: + case WM_POINTERUP: + case WM_POINTERENTER: + case WM_POINTERLEAVE: + case WM_POINTERUPDATE: + case WM_POINTERCAPTURECHANGED: { + bool handled = handlePointerMsg(msg); + if (handled) { + *result = 0; + return true; + } + break; + } + case WM_TABLET_QUERYSYSTEMGESTURESTATUS: + *result = 0; + return true; + } + + Q_UNUSED(eventType) + return false; +} + +namespace { + +QDebug operator<<(QDebug debug, const POINT &pt) { + QDebugStateSaver saver(debug); + debug.nospace() << '(' << pt.x << ", " << pt.y << ')'; + + return debug; +} + +QDebug operator<<(QDebug debug, const RECT &rect) { + QDebugStateSaver saver(debug); + debug.nospace() << '(' << rect.left << ", " << rect.top << ", " << rect.right + << ", " << rect.bottom << ')'; + + return debug; +} + +bool registerOrUpdateDevice(HANDLE deviceHandle, const RECT &pointerDeviceRect, + const RECT &displayRect, + const DISPLAYCONFIG_ROTATION deviceOrientation) { + bool isPreviouslyRegistered = penDevices.contains(deviceHandle); + PointerDeviceItem &deviceItem = penDevices[deviceHandle]; + PointerDeviceItem oldDeviceItem = deviceItem; + // deviceItem.handle = deviceHandle; + deviceItem.himetricToPixelX = + static_cast(displayRect.right - displayRect.left) / + (pointerDeviceRect.right - pointerDeviceRect.left); + deviceItem.himetricToPixelY = + static_cast(displayRect.bottom - displayRect.top) / + (pointerDeviceRect.bottom - pointerDeviceRect.top); + deviceItem.pixelOffsetX = + static_cast(displayRect.left) - + deviceItem.himetricToPixelX * pointerDeviceRect.left; + deviceItem.pixelOffsetY = static_cast(displayRect.top) - + deviceItem.himetricToPixelY * pointerDeviceRect.top; + deviceItem.deviceOrientation = deviceOrientation; + if (!isPreviouslyRegistered) { +#ifdef KRITA + dbgTablet << "Registered pen device" << deviceHandle << "with displayRect" + << displayRect << "and deviceRect" << pointerDeviceRect << "scale" + << deviceItem.himetricToPixelX << deviceItem.himetricToPixelY + << "offset" << deviceItem.pixelOffsetX << deviceItem.pixelOffsetY + << "orientation" << deviceItem.deviceOrientation; +#endif + } else if (deviceItem.himetricToPixelX != oldDeviceItem.himetricToPixelX || + deviceItem.himetricToPixelY != oldDeviceItem.himetricToPixelY || + deviceItem.pixelOffsetX != oldDeviceItem.pixelOffsetX || + deviceItem.pixelOffsetY != oldDeviceItem.pixelOffsetY || + deviceItem.deviceOrientation != oldDeviceItem.deviceOrientation) { +#ifdef KRITA + dbgTablet << "Updated pen device" << deviceHandle << "with displayRect" + << displayRect << "and deviceRect" << pointerDeviceRect << "scale" + << deviceItem.himetricToPixelX << deviceItem.himetricToPixelY + << "offset" << deviceItem.pixelOffsetX << deviceItem.pixelOffsetY + << "orientation" << deviceItem.deviceOrientation; +#endif + } + return true; +} + +bool registerOrUpdateDevice(HANDLE deviceHandle) { + RECT pointerDeviceRect, displayRect; + if (!api.GetPointerDeviceRects(deviceHandle, &pointerDeviceRect, + &displayRect)) { +#ifdef KRITA + dbgTablet << "GetPointerDeviceRects failed"; +#endif + return false; + } + POINTER_DEVICE_INFO pointerDeviceInfo; + if (!api.GetPointerDevice(deviceHandle, &pointerDeviceInfo)) { +#ifdef KRITA + dbgTablet << "GetPointerDevice failed"; +#endif + return false; + } + return registerOrUpdateDevice(deviceHandle, pointerDeviceRect, displayRect, + static_cast( + pointerDeviceInfo.displayOrientation)); +} + +QTabletEvent makeProximityTabletEvent(const QEvent::Type eventType, + const POINTER_PEN_INFO &penInfo) { + PenFlagsWrapper penFlags = PenFlagsWrapper::fromPenInfo(penInfo); + QTabletEvent::PointerType pointerType = + penFlags.isInverted() ? QTabletEvent::Eraser : QTabletEvent::Pen; + const QPointF emptyPoint; + return QTabletEvent( + eventType, // type + emptyPoint, // pos + emptyPoint, // globalPos + QTabletEvent::Stylus, // device + pointerType, // pointerType + 0, // pressure + 0, // xTilt + 0, // yTilt + 0, // tangentialPressure + 0, // rotation + 0, // z + Qt::NoModifier, // keyState + reinterpret_cast(penInfo.pointerInfo.sourceDevice), // uniqueID + Qt::NoButton, // button + (Qt::MouseButtons)0 // buttons + ); +} + +// void rotateTiltAngles(int &tiltX, int &tiltY, const DISPLAYCONFIG_ROTATION +// orientation) { +// int newTiltX, newTiltY; +// switch (orientation) { +// case DISPLAYCONFIG_ROTATION_ROTATE90: +// newTiltX = -tiltY; +// newTiltY = tiltX; +// break; +// case DISPLAYCONFIG_ROTATION_ROTATE180: +// newTiltX = -tiltX; +// newTiltY = -tiltY; +// break; +// case DISPLAYCONFIG_ROTATION_ROTATE270: +// newTiltX = tiltY; +// newTiltY = -tiltX; +// break; +// case DISPLAYCONFIG_ROTATION_IDENTITY: +// default: +// newTiltX = tiltX; +// newTiltY = tiltY; +// break; +// } +// tiltX = newTiltX; +// tiltY = newTiltY; +// } + +QTabletEvent makePositionalTabletEvent(const QWidget *targetWidget, + const QEvent::Type eventType, + const POINTER_PEN_INFO &penInfo, + const PointerDeviceItem &deviceItem, + const PenPointerItem &penPointerItem) { + PenFlagsWrapper penFlags = PenFlagsWrapper::fromPenInfo(penInfo); + PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo); + PenMaskWrapper penMask = PenMaskWrapper::fromPenInfo(penInfo); + + const QPointF globalPosF((deviceItem.himetricToPixelX * + penInfo.pointerInfo.ptHimetricLocationRaw.x + + deviceItem.pixelOffsetX) * + penPointerItem.oneOverDpr, + (deviceItem.himetricToPixelY * + penInfo.pointerInfo.ptHimetricLocationRaw.y + + deviceItem.pixelOffsetY) * + penPointerItem.oneOverDpr); + const QPoint globalPos = globalPosF.toPoint(); + const QPoint localPos = targetWidget->mapFromGlobal(globalPos); + const QPointF delta = globalPosF - globalPos; + const QPointF localPosF = localPos + delta; + + const QTabletEvent::PointerType pointerType = + penFlags.isInverted() ? QTabletEvent::Eraser : QTabletEvent::Pen; + + Qt::MouseButton mouseButton; + if (eventType == QEvent::TabletPress) { + if (penInfo.pointerInfo.ButtonChangeType == + POINTER_CHANGE_SECONDBUTTON_DOWN) { + mouseButton = Qt::RightButton; + } else { +#ifdef KRITA + KIS_SAFE_ASSERT_RECOVER(penInfo.pointerInfo.ButtonChangeType == + POINTER_CHANGE_FIRSTBUTTON_DOWN) { +#else + if (!(penInfo.pointerInfo.ButtonChangeType == + POINTER_CHANGE_FIRSTBUTTON_DOWN)) { +#endif + qWarning() << "WM_POINTER* sent unknown ButtonChangeType" + << penInfo.pointerInfo.ButtonChangeType; + } + mouseButton = Qt::LeftButton; + } + } else if (eventType == QEvent::TabletRelease) { + if (penInfo.pointerInfo.ButtonChangeType == + POINTER_CHANGE_SECONDBUTTON_UP) { + mouseButton = Qt::RightButton; + } else { +#ifdef KRITA + KIS_SAFE_ASSERT_RECOVER(penInfo.pointerInfo.ButtonChangeType == + POINTER_CHANGE_FIRSTBUTTON_UP) { +#else + if (!(penInfo.pointerInfo.ButtonChangeType == + POINTER_CHANGE_FIRSTBUTTON_UP)) { +#endif + qWarning() << "WM_POINTER* sent unknown ButtonChangeType" + << penInfo.pointerInfo.ButtonChangeType; + } + mouseButton = Qt::LeftButton; + } + } else { + mouseButton = Qt::NoButton; + } + + Qt::MouseButtons mouseButtons; + if (pointerFlags.isFirstButtonDown()) { + mouseButtons |= Qt::LeftButton; + } + if (pointerFlags.isSecondButtonDown()) { + mouseButtons |= Qt::RightButton; + } + + int tiltX = 0, tiltY = 0; + if (penMask.tiltXValid()) { + tiltX = qBound(-60, penInfo.tiltX, 60); + } + if (penMask.tiltYValid()) { + tiltY = qBound(-60, penInfo.tiltY, 60); + } + // rotateTiltAngles(tiltX, tiltY, deviceItem.deviceOrientation); + + int rotation = 0; + if (penMask.rotationValid()) { + rotation = + 360 - penInfo.rotation; // Flip direction and convert to signed int + if (rotation > 180) { + rotation -= 360; + } + } + + return QTabletEvent( + eventType, // type + localPosF, // pos + globalPosF, // globalPos + QTabletEvent::Stylus, // device + pointerType, // pointerType + penMask.pressureValid() ? static_cast(penInfo.pressure) / 1024 + : 0, // pressure + tiltX, // xTilt + tiltY, // yTilt + 0, // tangentialPressure + rotation, // rotation + 0, // z + QApplication::queryKeyboardModifiers(), // keyState + reinterpret_cast(penInfo.pointerInfo.sourceDevice), // uniqueID + mouseButton, // button + mouseButtons // buttons + ); +} + +bool sendProximityTabletEvent(const QEvent::Type eventType, + const POINTER_PEN_INFO &penInfo) { +#ifdef KRITA + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE( + eventType == QEvent::TabletEnterProximity || + eventType == QEvent::TabletLeaveProximity, + false); +#else + if (!(eventType == QEvent::TabletEnterProximity || + eventType == QEvent::TabletLeaveProximity)) { + return false; + } +#endif + QTabletEvent ev = makeProximityTabletEvent(eventType, penInfo); + ev.setAccepted(false); + ev.setTimestamp(penInfo.pointerInfo.dwTime); + QCoreApplication::sendEvent(qApp, &ev); + return ev.isAccepted(); +} + +void synthesizeMouseEvent(const QTabletEvent &ev, + const POINTER_PEN_INFO &penInfo) { + // Update the cursor position + BOOL result = SetCursorPos(penInfo.pointerInfo.ptPixelLocationRaw.x, + penInfo.pointerInfo.ptPixelLocationRaw.y); + if (!result) { +#ifdef KRITA + dbgInput << "SetCursorPos failed, err" << GetLastError(); +#endif + return; + } + // Send mousebutton down/up events. Windows stores the button state. + DWORD inputDataFlags = 0; + switch (ev.type()) { + case QEvent::TabletPress: + switch (ev.button()) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTDOWN; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTDOWN; + break; + default: + return; + } + break; + case QEvent::TabletRelease: + switch (ev.button()) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTUP; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTUP; + break; + default: + return; + } + break; + case QEvent::TabletMove: + default: + return; + } + INPUT inputData = {}; + inputData.type = INPUT_MOUSE; + inputData.mi.dwFlags = inputDataFlags; + inputData.mi.dwExtraInfo = + 0xFF515700 | + 0x01; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320%28v=vs.85%29.aspx + UINT result2 = SendInput(1, &inputData, sizeof(inputData)); + if (result2 != 1) { +#ifdef KRITA + dbgInput << "SendInput failed, err" << GetLastError(); +#endif + return; + } +} + +bool sendPositionalTabletEvent(QWidget *target, const QEvent::Type eventType, + const POINTER_PEN_INFO &penInfo, + const PointerDeviceItem &device, + const PenPointerItem &penPointerItem, + const bool shouldSynthesizeMouseEvent) { +#ifdef KRITA + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(eventType == QEvent::TabletMove || + eventType == QEvent::TabletPress || + eventType == QEvent::TabletRelease, + false); +#else + if (!(eventType == QEvent::TabletMove || eventType == QEvent::TabletPress || + eventType == QEvent::TabletRelease)) { + return false; + } +#endif + QTabletEvent ev = makePositionalTabletEvent(target, eventType, penInfo, + device, penPointerItem); + ev.setAccepted(false); + ev.setTimestamp(penInfo.pointerInfo.dwTime); + QCoreApplication::sendEvent(target, &ev); + if (!shouldSynthesizeMouseEvent) { + // For pen update with multiple updates, only the last update should + // be used to synthesize a mouse event. + return false; + } + // This is some specialized code to handle synthesizing of mouse events from + // the pen events. Issues being: + // 1. We must accept the pointer down/up and the intermediate update events + // to indicate that we want all the pen pointer events for painting, + // otherwise Windows may do weird stuff and skip passing pointer events. + // 2. Some Qt and Krita code uses QCursor::pos() which calls GetCursorPos to + // get the cursor position. This doesn't work nicely before ver 1709 and + // doesn't work at all on ver 1709 if the pen events are handled, so we + // need some way to nudge the cursor on the OS level. + // It appears that using the same way (as in synthesizeMouseEvent) to nudge + // the cursor does work fine for when painting on canvas (which handles + // the QTabletEvent), but not for other widgets because it introduces a lag + // with mouse move events on move start and immediately after mouse down. + // The resolution is to simulate mouse movement with our own code only for + // handled pen events, which is what the following code does. + if (ev.type() == QEvent::TabletMove && ev.buttons() == Qt::NoButton) { + // Let Windows synthesize mouse hover events + return false; + } + if (ev.type() == QEvent::TabletPress && !ev.isAccepted()) { + // On pen down event, if the widget doesn't handle the event, let + // Windows translate the event to touch, mouse or whatever + return false; + } + if (ev.type() != QEvent::TabletPress && + !penPointerItem.widgetAcceptsPenEvent) { + // For other events, if the previous pen down event wasn't handled by + // the widget, continue to let Windows translate the event + return false; + } + // Otherwise, we synthesize our mouse events + synthesizeMouseEvent(ev, penInfo); + return true; // and tell Windows that we do want the pen events. +} + +bool handlePenEnterMsg(const POINTER_PEN_INFO &penInfo) { + PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo); + if (!pointerFlags.isPrimary()) { +// Don't handle non-primary pointer messages for now +#ifdef KRITA + dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "of device" + << penInfo.pointerInfo.sourceDevice << "is not flagged PRIMARY"; +#endif + return false; + } + + // Update the device scaling factors here + // It doesn't cost much to recalculate anyway + // This ensures that the screen resolution changes are reflected + // WM_POINTERDEVICECHANGE might be useful for this, but its docs are too + // unclear to use + registerOrUpdateDevice(penInfo.pointerInfo.sourceDevice); + // TODO: Need a way to remove from device registration when devices are + // changed + + // We now only handle one pointer at a time, so just clear the pointer + // registration + penPointers.clear(); + + int pointerId = penInfo.pointerInfo.pointerId; + PenPointerItem penPointerItem; + penPointerItem.hwnd = penInfo.pointerInfo.hwndTarget; + penPointerItem.deviceHandle = penInfo.pointerInfo.sourceDevice; + penPointerItem.activeWidget = nullptr; + penPointerItem.oneOverDpr = 1.0; + penPointerItem.widgetIsCaptured = false; + penPointerItem.widgetIsIgnored = false; + penPointerItem.widgetAcceptsPenEvent = false; + // penPointerItem.pointerId = pointerId; + + penPointers.insert(pointerId, penPointerItem); + // primaryPenPointerId = pointerId; + + // penEnter + sendProximityTabletEvent(QEvent::TabletEnterProximity, penInfo); + + return false; +} + +bool handlePenLeaveMsg(const POINTER_PEN_INFO &penInfo) { + if (!penPointers.contains(penInfo.pointerInfo.pointerId)) { +#ifdef KRITA + dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId + << "wasn't being handled"; +#endif + return false; + } + if (!penDevices.contains(penInfo.pointerInfo.sourceDevice)) { +#ifdef KRITA + dbgTablet << "Device is gone from the registration???"; +#endif + // TODO: re-register device? + penPointers.remove(penInfo.pointerInfo.pointerId); + return false; + } + + // penLeave + sendProximityTabletEvent(QEvent::TabletLeaveProximity, penInfo); + + penPointers.remove(penInfo.pointerInfo.pointerId); + + return false; +} + +bool handleSinglePenUpdate(PenPointerItem &penPointerItem, + const POINTER_PEN_INFO &penInfo, + const PointerDeviceItem &device, + const bool shouldSynthesizeMouseEvent) { + QWidget *targetWidget; + if (penPointerItem.isCaptured()) { + if (penPointerItem.widgetIsIgnored) { + return false; + } + targetWidget = penPointerItem.activeWidget; + if (!targetWidget) { + return false; + } + } else { + QWidget *hwndWidget = + QWidget::find(reinterpret_cast(penInfo.pointerInfo.hwndTarget)); + if (!hwndWidget) { +#ifdef KRITA + dbgTablet << "HWND cannot be mapped to QWidget (what?)"; +#endif + return false; + } + { + // Check popup / modal widget + QWidget *modalWidget = QApplication::activePopupWidget(); + if (!modalWidget) { + modalWidget = QApplication::activeModalWidget(); + } + if (modalWidget && modalWidget != hwndWidget && + !modalWidget->isAncestorOf(hwndWidget)) { + return false; + } + } + { + QWindow *topLevelWindow = hwndWidget->windowHandle(); + if (topLevelWindow) { + penPointerItem.oneOverDpr = 1.0 / topLevelWindow->devicePixelRatio(); + } else { + penPointerItem.oneOverDpr = 1.0 / qApp->devicePixelRatio(); + } + } + QPoint posInHwndWidget = hwndWidget->mapFromGlobal( + QPoint(static_cast(penInfo.pointerInfo.ptPixelLocationRaw.x * + penPointerItem.oneOverDpr), + static_cast(penInfo.pointerInfo.ptPixelLocationRaw.y * + penPointerItem.oneOverDpr))); + targetWidget = hwndWidget->childAt(posInHwndWidget); + if (!targetWidget) { + // dbgTablet << "No childQWidget at cursor position"; + targetWidget = hwndWidget; + } + // penPointerItem.activeWidget = targetWidget; + } + + bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletMove, + penInfo, device, penPointerItem, + shouldSynthesizeMouseEvent); + return handled; +} + +bool handlePenUpdateMsg(const POINTER_PEN_INFO &penInfo) { + auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId); + if (currentPointerIt == penPointers.end()) { + // dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being + // handled"; + return false; + } + + const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice); + if (devIt == penDevices.end()) { +#ifdef KRITA + dbgTablet << "Device not registered???"; +#endif + return false; + } + + // UINT32 entriesCount = 0; + // if (!api.GetPointerPenInfoHistory(penInfo.pointerInfo.pointerId, + // &entriesCount, nullptr)) { + // dbgTablet << "GetPointerPenInfoHistory (getting count) failed"; + // return false; + // } + UINT32 entriesCount = penInfo.pointerInfo.historyCount; + // dbgTablet << "entriesCount:" << entriesCount; + bool handled = false; + if (entriesCount != 1) { + QVector penInfoArray(entriesCount); + if (!api.GetPointerPenInfoHistory(penInfo.pointerInfo.pointerId, + &entriesCount, penInfoArray.data())) { +#ifdef KRITA + dbgTablet << "GetPointerPenInfoHistory failed"; +#endif + return false; + } + bool handled = false; + // The returned array is in reverse chronological order + const auto rbegin = penInfoArray.rbegin(); + const auto rend = penInfoArray.rend(); + const auto rlast = + rend - 1; // Only synthesize mouse event for the last one + for (auto it = rbegin; it != rend; ++it) { + handled |= handleSinglePenUpdate( + *currentPointerIt, *it, *devIt, + it == rlast); // Bitwise OR doesn't short circuit + } + } else { + handled = handleSinglePenUpdate(*currentPointerIt, penInfo, *devIt, true); + } + return handled; +} + +bool handlePenDownMsg(const POINTER_PEN_INFO &penInfo) { + // PointerFlagsWrapper pointerFlags = + // PointerFlagsWrapper::fromPenInfo(penInfo); + // if (!pointerFlags.isPrimary()) { + // // Don't handle non-primary pointer messages for now + // return false; + // } + auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId); + if (currentPointerIt == penPointers.end()) { +#ifdef KRITA + dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId + << "wasn't being handled"; +#endif + return false; + } + currentPointerIt->hwnd = + penInfo.pointerInfo + .hwndTarget; // They *should* be the same, but just in case + QWidget *hwndWidget = + QWidget::find(reinterpret_cast(penInfo.pointerInfo.hwndTarget)); + if (!hwndWidget) { +#ifdef KRITA + dbgTablet << "HWND cannot be mapped to QWidget (what?)"; +#endif + return false; + } + { + QWindow *topLevelWindow = hwndWidget->windowHandle(); + if (topLevelWindow) { + currentPointerIt->oneOverDpr = 1.0 / topLevelWindow->devicePixelRatio(); + } else { + currentPointerIt->oneOverDpr = 1.0 / qApp->devicePixelRatio(); + } + } + QPoint posInHwndWidget = hwndWidget->mapFromGlobal( + QPoint(static_cast(penInfo.pointerInfo.ptPixelLocationRaw.x * + currentPointerIt->oneOverDpr), + static_cast(penInfo.pointerInfo.ptPixelLocationRaw.y * + currentPointerIt->oneOverDpr))); + QWidget *targetWidget = hwndWidget->childAt(posInHwndWidget); + if (!targetWidget) { +#ifdef KRITA + dbgTablet << "No childQWidget at cursor position"; +#endif + targetWidget = hwndWidget; + } + + currentPointerIt->activeWidget = targetWidget; + currentPointerIt->widgetIsCaptured = true; + // dbgTablet << "QWidget" << targetWidget->windowTitle() << "is capturing + // pointer" << penInfo.pointerInfo.pointerId; + { + // Check popup / modal widget + QWidget *modalWidget = QApplication::activePopupWidget(); + if (!modalWidget) { + modalWidget = QApplication::activeModalWidget(); + } + if (modalWidget && modalWidget != hwndWidget && + !modalWidget->isAncestorOf(hwndWidget)) { + currentPointerIt->widgetIsIgnored = true; +#ifdef KRITA + dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId + << "is being captured but will be ignored"; +#endif + return false; + } + } + + // penDown + const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice); + if (devIt == penDevices.end()) { +#ifdef KRITA + dbgTablet << "Device not registered???"; +#endif + return false; + } + + bool handled = + sendPositionalTabletEvent(targetWidget, QEvent::TabletPress, penInfo, + *devIt, *currentPointerIt, true); + currentPointerIt->widgetAcceptsPenEvent = handled; + if (!handled) { + // dbgTablet << "QWidget did not handle tablet down event"; + } + return handled; +} + +bool handlePenUpMsg(const POINTER_PEN_INFO &penInfo) { + auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId); + if (currentPointerIt == penPointers.end()) { +#ifdef KRITA + dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId + << "wasn't being handled"; +#endif + return false; + } + PenPointerItem &penPointerItem = *currentPointerIt; + + if (!penPointerItem.isCaptured()) { +#ifdef KRITA + dbgTablet << "Pointer wasn't captured"; +#endif + return false; + } + if (penPointerItem.widgetIsIgnored) { + penPointerItem.widgetIsCaptured = false; + penPointerItem.widgetIsIgnored = false; + return false; + } + + // penUp + QWidget *targetWidget = penPointerItem.activeWidget; + if (!targetWidget) { +#ifdef KRITA + dbgTablet << "Previously captured target has been deleted"; +#endif + penPointerItem.widgetIsCaptured = false; + return false; + } + + const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice); + if (devIt == penDevices.end()) { +#ifdef KRITA + dbgTablet << "Device not registered???"; +#endif + return false; + } + + bool handled = + sendPositionalTabletEvent(targetWidget, QEvent::TabletRelease, penInfo, + *devIt, penPointerItem, true); + + // dbgTablet << "QWidget" << currentPointerIt->activeWidget->windowTitle() << + // "is releasing capture to pointer" << penInfo.pointerInfo.pointerId; + penPointerItem.widgetIsCaptured = false; + penPointerItem.widgetAcceptsPenEvent = false; + return handled; +} + +bool handlePointerMsg(const MSG &msg) { + if (!api.isLoaded()) { + qWarning() << "Windows 8 Pointer Input API functions not loaded"; + return false; + } + + int pointerId = GET_POINTERID_WPARAM(msg.wParam); + POINTER_INPUT_TYPE pointerType; + if (!api.GetPointerType(pointerId, &pointerType)) { +#ifdef KRITA + dbgTablet << "GetPointerType failed"; +#endif + return false; + } + if (pointerType != PT_PEN) { + // dbgTablet << "pointerType" << pointerType << "is not PT_PEN"; + return false; + } + + POINTER_PEN_INFO penInfo; + if (!api.GetPointerPenInfo(pointerId, &penInfo)) { +#ifdef KRITA + dbgTablet << "GetPointerPenInfo failed"; +#endif + return false; + } + + switch (msg.message) { + case WM_POINTERDOWN: + // dbgTablet << "WM_POINTERDOWN"; + break; + case WM_POINTERUP: + // dbgTablet << "WM_POINTERUP"; + break; + case WM_POINTERENTER: + // dbgTablet << "WM_POINTERENTER"; + break; + case WM_POINTERLEAVE: + // dbgTablet << "WM_POINTERLEAVE"; + break; + case WM_POINTERUPDATE: + // dbgTablet << "WM_POINTERUPDATE"; + break; + case WM_POINTERCAPTURECHANGED: + // dbgTablet << "WM_POINTERCAPTURECHANGED"; + break; + default: +#ifdef KRITA + dbgTablet << "I missed this message: " << msg.message; +#endif + break; + } + // dbgTablet << " hwnd: " << penInfo.pointerInfo.hwndTarget; + // dbgTablet << " msg hwnd: " << msg.hwnd; + // dbgTablet << " pointerId: " << pointerId; + // dbgTablet << " sourceDevice:" << penInfo.pointerInfo.sourceDevice; + // dbgTablet << " pointerFlags:" << penInfo.pointerInfo.pointerFlags; + // dbgTablet << " btnChgType: " << penInfo.pointerInfo.ButtonChangeType; + // dbgTablet << " penFlags: " << penInfo.penFlags; + // dbgTablet << " penMask: " << penInfo.penMask; + // dbgTablet << " pressure: " << penInfo.pressure; + // dbgTablet << " rotation: " << penInfo.rotation; + // dbgTablet << " tiltX: " << penInfo.tiltX; + // dbgTablet << " tiltY: " << penInfo.tiltY; + // dbgTablet << " ptPixelLocationRaw: " << + // penInfo.pointerInfo.ptPixelLocationRaw; + // dbgTablet << " ptHimetricLocationRaw:" << + // penInfo.pointerInfo.ptHimetricLocationRaw; + // RECT pointerDeviceRect, displayRect; + // if (!api.GetPointerDeviceRects(penInfo.pointerInfo.sourceDevice, + // &pointerDeviceRect, &displayRect)) { + // dbgTablet << "GetPointerDeviceRects failed"; + // return false; + // } + // dbgTablet << " pointerDeviceRect:" << pointerDeviceRect; + // dbgTablet << " displayRect:" << displayRect; + // dbgTablet << " scaled X:" << + // static_cast(penInfo.pointerInfo.ptHimetricLocationRaw.x) / + // (pointerDeviceRect.right - pointerDeviceRect.left) * (displayRect.right - + // displayRect.left); + // dbgTablet << " scaled Y:" << + // static_cast(penInfo.pointerInfo.ptHimetricLocationRaw.y) / + // (pointerDeviceRect.bottom - pointerDeviceRect.top) * (displayRect.bottom - + // displayRect.top); + + switch (msg.message) { + case WM_POINTERDOWN: + return handlePenDownMsg(penInfo); + case WM_POINTERUP: + return handlePenUpMsg(penInfo); + case WM_POINTERENTER: + return handlePenEnterMsg(penInfo); + case WM_POINTERLEAVE: + return handlePenLeaveMsg(penInfo); + case WM_POINTERUPDATE: + return handlePenUpdateMsg(penInfo); + case WM_POINTERCAPTURECHANGED: +// TODO: Should this event be handled? +#ifdef KRITA + dbgTablet << "FIXME: WM_POINTERCAPTURECHANGED isn't handled"; +#endif + break; + } + + return false; +} + +} // namespace + +#endif // _WIN32 diff --git a/toonz/sources/toonz/kis_tablet_support_win8.h b/toonz/sources/toonz/kis_tablet_support_win8.h new file mode 100644 index 0000000..cb3c0e1 --- /dev/null +++ b/toonz/sources/toonz/kis_tablet_support_win8.h @@ -0,0 +1,73 @@ +/* +* This file is based on the Windows Pointer Input Message support code files by +* Alvin Wong. +* Notwithstanding the license specified in this repository, this file is +* redistributed under BSD 2-Clause license written below in order to keep +* backporting available. +* +* All contributions by Alvin Wong: +* Copyright (c) 2017 Alvin Wong +* All rights reserved. +* +* All other contributions: +* Copyright (c) 2018, the respective contributors. +* All rights reserved. +* +* Each contributor holds copyright over their respective contributions. +* The project versioning (Git) records all such contribution source information. +* +* Redistribution and use in source and binary forms, with or without +- modification, are permitted provided that the following conditions +- are met: +- +- 1. Redistributions of source code must retain the above copyright +- notice, this list of conditions and the following disclaimer. +- 2. Redistributions in binary form must reproduce the above copyright +- notice, this list of conditions and the following disclaimer in the +- documentation and/or other materials provided with the distribution. +- +- THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +- OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +- IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +- NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +- THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef KIS_TABLET_SUPPORT_WIN8_H +#define KIS_TABLET_SUPPORT_WIN8_H + +#include + +#ifdef _WIN32 + +#ifdef KRITA +#include + +class KRITAUI_EXPORT KisTabletSupportWin8 : public QAbstractNativeEventFilter +#else +class KisTabletSupportWin8 : public QAbstractNativeEventFilter +#endif +{ + Q_DISABLE_COPY(KisTabletSupportWin8) + +public: + static bool isAvailable(); + static bool isPenDeviceAvailable(); + + KisTabletSupportWin8() = default; + ~KisTabletSupportWin8() = default; + + bool init(); + // void registerPointerDeviceNotifications(); + virtual bool nativeEventFilter(const QByteArray &eventType, void *message, + long *result) override; +}; + +#endif // _WIN32 + +#endif // KIS_TABLET_SUPPORT_WIN8_H diff --git a/toonz/sources/toonz/main.cpp b/toonz/sources/toonz/main.cpp index 676c7ca..64d0f34 100644 --- a/toonz/sources/toonz/main.cpp +++ b/toonz/sources/toonz/main.cpp @@ -64,6 +64,8 @@ #include "tvectorbrushstyle.h" #include "tfont.h" +#include "kis_tablet_support_win8.h" + #ifdef MACOSX #include "tipc.h" #endif @@ -731,6 +733,17 @@ int main(int argc, char *argv[]) { #endif #endif +#ifdef _WIN32 + if (Preferences::instance()->isWinInkEnabled()) { + KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); + if (penFilter->init()) { + a.installNativeEventFilter(penFilter); + } else { + delete penFilter; + } + } +#endif + a.installEventFilter(TApp::instance()); int ret = a.exec(); diff --git a/toonz/sources/toonz/preferencespopup.cpp b/toonz/sources/toonz/preferencespopup.cpp index 50a6fe3..409d555 100644 --- a/toonz/sources/toonz/preferencespopup.cpp +++ b/toonz/sources/toonz/preferencespopup.cpp @@ -35,6 +35,8 @@ #include "tsystem.h" #include "tfont.h" +#include "kis_tablet_support_win8.h" + // Qt includes #include #include @@ -1224,6 +1226,12 @@ void PreferencesPopup::onCurrentColumnDataChanged(const TPixel32 &, m_pref->setCurrentColumnData(m_currentColumnColor->getColor()); } +//--------------------------------------------------------------------------------------- + +void PreferencesPopup::onEnableWinInkChanged(int index) { + m_pref->enableWinInk(index == Qt::Checked); +} + //********************************************************************************** // PrefencesPopup's constructor //********************************************************************************** @@ -1234,6 +1242,12 @@ PreferencesPopup::PreferencesPopup() , m_inksOnly(0) , m_blanksCount(0) , m_blankColor(0) { + bool showTabletSettings = false; + +#ifdef _WIN32 + showTabletSettings = KisTabletSupportWin8::isAvailable(); +#endif + setWindowTitle(tr("Preferences")); setObjectName("PreferencesPopup"); @@ -1561,6 +1575,19 @@ PreferencesPopup::PreferencesPopup() new QLabel(tr("* Changes will take effect the next time you run Toonz")); note_version->setStyleSheet("font-size: 10px; font: italic;"); + QLabel *note_tablet; + //--- Tablet Settings ------------------------------ + if (showTabletSettings) { + categoryList->addItem(tr("Tablet Settings")); + + m_enableWinInk = + new DVGui::CheckBox(tr("Enable Windows Ink Support* (EXPERIMENTAL)")); + + note_tablet = new QLabel( + tr("* Changes will take effect the next time you run Toonz")); + note_tablet->setStyleSheet("font-size: 10px; font: italic;"); + } + /*--- set default parameters ---*/ categoryList->setFixedWidth(160); categoryList->setCurrentRow(0); @@ -1883,6 +1910,9 @@ PreferencesPopup::PreferencesPopup() m_pref->isAutomaticSVNFolderRefreshEnabled()); checkForTheLatestVersionCB->setChecked(m_pref->isLatestVersionCheckEnabled()); + //--- Tablet Settings ------------------------------ + if (showTabletSettings) m_enableWinInk->setChecked(m_pref->isWinInkEnabled()); + /*--- layout ---*/ QHBoxLayout *mainLayout = new QHBoxLayout(); @@ -2621,6 +2651,23 @@ PreferencesPopup::PreferencesPopup() versionControlBox->setLayout(vcLay); stackedWidget->addWidget(versionControlBox); + //--- Tablet Settings -------------------------- + if (showTabletSettings) { + QWidget *tabletSettingsBox = new QWidget(this); + QVBoxLayout *tsLay = new QVBoxLayout(); + tsLay->setMargin(15); + tsLay->setSpacing(10); + { + tsLay->addWidget(m_enableWinInk, 0, Qt::AlignLeft | Qt::AlignVCenter); + + tsLay->addStretch(1); + + tsLay->addWidget(note_tablet, 0); + } + tabletSettingsBox->setLayout(tsLay); + stackedWidget->addWidget(tabletSettingsBox); + } + mainLayout->addWidget(stackedWidget, 1); } setLayout(mainLayout); @@ -2945,6 +2992,12 @@ PreferencesPopup::PreferencesPopup() SLOT(onAutomaticSVNRefreshChanged(int))); ret = ret && connect(checkForTheLatestVersionCB, SIGNAL(clicked(bool)), SLOT(onCheckLatestVersionChanged(bool))); + + //--- Tablet Settings ---------------------- + if (showTabletSettings) + ret = ret && connect(m_enableWinInk, SIGNAL(stateChanged(int)), + SLOT(onEnableWinInkChanged(int))); + assert(ret); } diff --git a/toonz/sources/toonz/preferencespopup.h b/toonz/sources/toonz/preferencespopup.h index 057183e..f361667 100644 --- a/toonz/sources/toonz/preferencespopup.h +++ b/toonz/sources/toonz/preferencespopup.h @@ -82,7 +82,7 @@ private: *m_useHigherDpiOnVectorSimplifyCB, *m_keepFillOnVectorSimplifyCB, *m_newLevelToCameraSizeCB, *m_ignoreImageDpiCB, *m_syncLevelRenumberWithXsheet, *m_downArrowInLevelStripCreatesNewFrame, - *m_enableAutoStretch; + *m_enableAutoStretch, *m_enableWinInk; DVGui::FileField *m_customProjectRootFileField; @@ -217,6 +217,7 @@ private slots: void onCursorBrushStyleChanged(int index); void onCursorOutlineChanged(int); void onCurrentColumnDataChanged(const TPixel32 &, bool isDragging); + void onEnableWinInkChanged(int index); }; //********************************************************************************** diff --git a/toonz/sources/toonzlib/preferences.cpp b/toonz/sources/toonzlib/preferences.cpp index 8f5975d..606b2ac 100644 --- a/toonz/sources/toonzlib/preferences.cpp +++ b/toonz/sources/toonzlib/preferences.cpp @@ -343,7 +343,8 @@ Preferences::Preferences() , m_cursorBrushType("Small") , m_cursorBrushStyle("Default") , m_cursorOutlineEnabled(true) - , m_currentColumnColor(TPixel::Black) { + , m_currentColumnColor(TPixel::Black) + , m_enableWinInk(false) { TCamera camera; m_defLevelType = PLI_XSHLEVEL; m_defLevelWidth = camera.getSize().lx; @@ -715,6 +716,8 @@ Preferences::Preferences() getValue(*m_settings, "currentColumnColor.g", g); getValue(*m_settings, "currentColumnColor.b", b); m_currentColumnColor = TPixel32(r, g, b); + + getValue(*m_settings, "winInkEnabled", m_enableWinInk); } //----------------------------------------------------------------- @@ -1737,3 +1740,8 @@ void Preferences::setCurrentColumnData(const TPixel ¤tColumnColor) { m_settings->setValue("currentColumnColor.b", QString::number(currentColumnColor.b)); } + +void Preferences::enableWinInk(bool on) { + m_enableWinInk = on; + m_settings->setValue("winInkEnabled", on ? "1" : "0"); +}