From 2c447c1ed4e2a8897682428100662269d9bdd2ab Mon Sep 17 00:00:00 2001 From: manongjohn Date: May 09 2019 06:44:40 +0000 Subject: Image viewer gesture controls (#2500) * Add gesture control to ImageViewer, PlaneViewer and SwatchViewer --- diff --git a/toonz/sources/include/toonzqt/planeviewer.h b/toonz/sources/include/toonzqt/planeviewer.h index f8c27a2..d1c8744 100644 --- a/toonz/sources/include/toonzqt/planeviewer.h +++ b/toonz/sources/include/toonzqt/planeviewer.h @@ -13,6 +13,7 @@ // Qt includes #include +#include #undef DVAPI #undef DVVAR @@ -30,6 +31,8 @@ class TRasterImageP; class TToonzImageP; class TVectorImageP; +class QTouchEvent; +class QGestureEvent; //---------------------------------------------------------------------------- @@ -61,6 +64,18 @@ obsolete class until the shader fx being overhauled. 2016/6/22 Shun */ class DVAPI PlaneViewer : public GLWidgetForHighDpi { + Q_OBJECT + bool m_touchActive = false; + bool m_gestureActive = false; + QTouchDevice::DeviceType m_touchDevice = QTouchDevice::TouchScreen; + bool m_zooming = false; + bool m_panning = false; + double m_scaleFactor; // used for zoom gesture + + bool m_stylusUsed = false; + + bool m_firstDraw; + public: PlaneViewer(QWidget *parent); @@ -99,8 +114,6 @@ public: TAffine &viewAff() { return m_aff; } const TAffine &viewAff() const { return m_aff; } - void resetView(); - void zoomIn(); void zoomOut(); @@ -123,6 +136,11 @@ public: TRaster32P rasterBuffer(); void flushRasterBuffer(); +public slots: + + void resetView(); + void fitView(); + protected: int m_xpos, m_ypos; //!< Mouse position on mouse operations. TAffine m_aff; //!< Affine transform from world to widget coords. @@ -135,13 +153,25 @@ protected: double m_zoomRange[2]; //!< Viewport zoom range (default: [-1024, 1024]). + TRect m_imageBounds; + + double m_dpiX, m_dpiY; + protected: + virtual void contextMenuEvent(QContextMenuEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; virtual void wheelEvent(QWheelEvent *event) override; virtual void keyPressEvent(QKeyEvent *event) override; virtual void hideEvent(QHideEvent *event) override; + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + virtual void tabletEvent(QTabletEvent *e) override; + void touchEvent(QTouchEvent *e, int type); + void gestureEvent(QGestureEvent *e); + virtual bool event(QEvent *e) override; + void initializeGL() override final; void resizeGL(int width, int height) override final; @@ -150,6 +180,7 @@ private: bool m_firstResize; int m_width; int m_height; + QPointF m_firstPanPoint; }; #endif // PLANE_VIEWER_H diff --git a/toonz/sources/include/toonzqt/swatchviewer.h b/toonz/sources/include/toonzqt/swatchviewer.h index d772d36..f6115bc 100644 --- a/toonz/sources/include/toonzqt/swatchviewer.h +++ b/toonz/sources/include/toonzqt/swatchviewer.h @@ -16,6 +16,8 @@ #include "tthread.h" #include "trop.h" +#include + using namespace TThread; #undef DVAPI @@ -28,6 +30,13 @@ using namespace TThread; #define DVVAR DV_IMPORT_VAR #endif +//----------------------------------------------------------------------------- + +// Forward declarations + +class QTouchEvent; +class QGestureEvent; + //============================================================================= class DVAPI BgPainter { @@ -113,6 +122,7 @@ class DVAPI SwatchViewer final : public QWidget { TPointD m_pointPosDelta; bool m_enabled; + bool m_firstEnabled; int m_frame; TThread::Executor m_executor; TThread::Mutex m_mutex; @@ -129,6 +139,15 @@ class DVAPI SwatchViewer final : public QWidget { bool m_computing; + bool m_touchActive = false; + bool m_gestureActive = false; + QTouchDevice::DeviceType m_touchDevice = QTouchDevice::TouchScreen; + bool m_zooming = false; + bool m_panning = false; + double m_scaleFactor; // used for zoom gesture + + bool m_stylusUsed = false; + friend class ContentRender; public: @@ -179,6 +198,8 @@ public slots: void setEnable(bool enabled); void updateSize(const QSize &size); void setBgPainter(TPixel32 color1, TPixel32 color2 = TPixel32()); + void resetView(); + void fitView(); protected: void computeContent(); @@ -186,6 +207,7 @@ protected: TPointD win2world(const TPoint &p) const; void zoom(const TPoint &pos, double factor); void zoom(bool forward, bool reset); + void pan(const TPoint &delta); void updateRaster(); @@ -194,6 +216,7 @@ protected: void setAff(const TAffine &aff); + void contextMenuEvent(QContextMenuEvent *event) override; void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; @@ -203,6 +226,15 @@ protected: void resizeEvent(QResizeEvent *event) override; void hideEvent(QHideEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void tabletEvent(QTabletEvent *e) override; + void touchEvent(QTouchEvent *e, int type); + void gestureEvent(QGestureEvent *e); + bool event(QEvent *e) override; + +private: + QPointF m_firstPanPoint; + signals: void pointPositionChanged(int index, const TPointD &p); }; diff --git a/toonz/sources/toonz/imageviewer.cpp b/toonz/sources/toonz/imageviewer.cpp index c0b2f35..1a0f88f 100644 --- a/toonz/sources/toonz/imageviewer.cpp +++ b/toonz/sources/toonz/imageviewer.cpp @@ -39,6 +39,7 @@ #include #include #include +#include //=================================================================================== @@ -218,7 +219,8 @@ ImageViewer::ImageViewer(QWidget *parent, FlipBook *flipbook, , m_isColorModel(false) , m_histogramPopup(0) , m_isRemakingPreviewFx(false) - , m_rectRGBPick(false) { + , m_rectRGBPick(false) + , m_firstImage(true) { m_visualSettings.m_sceneProperties = TApp::instance()->getCurrentScene()->getScene()->getProperties(); m_visualSettings.m_drawExternalBG = true; @@ -227,6 +229,11 @@ ImageViewer::ImageViewer(QWidget *parent, FlipBook *flipbook, setMouseTracking(true); + setAttribute(Qt::WA_AcceptTouchEvents); + grabGesture(Qt::SwipeGesture); + grabGesture(Qt::PanGesture); + grabGesture(Qt::PinchGesture); + if (m_isHistogramEnable) m_histogramPopup = new HistogramPopup(tr("Flipbook Histogram")); @@ -237,8 +244,6 @@ ImageViewer::ImageViewer(QWidget *parent, FlipBook *flipbook, //----------------------------------------------------------------------------- void ImageViewer::contextMenuEvent(QContextMenuEvent *event) { - if (!m_flipbook) return; - QAction *action; if (m_isColorModel) { @@ -248,53 +253,56 @@ void ImageViewer::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = new QMenu(this); - if (m_flipbook->getPreviewedFx()) { - if (!(windowState() & Qt::WindowFullScreen)) { - action = menu->addAction(tr("Clone Preview")); - action->setShortcut(QKeySequence( - CommandManager::instance()->getKeyFromId(MI_ClonePreview))); - connect(action, SIGNAL(triggered()), m_flipbook, SLOT(clonePreview())); - } + if (m_flipbook) { + if (m_flipbook->getPreviewedFx()) { + if (!(windowState() & Qt::WindowFullScreen)) { + action = menu->addAction(tr("Clone Preview")); + action->setShortcut(QKeySequence( + CommandManager::instance()->getKeyFromId(MI_ClonePreview))); + connect(action, SIGNAL(triggered()), m_flipbook, SLOT(clonePreview())); + } - if (m_flipbook->isFreezed()) { - action = menu->addAction(tr("Unfreeze Preview")); + if (m_flipbook->isFreezed()) { + action = menu->addAction(tr("Unfreeze Preview")); + action->setShortcut(QKeySequence( + CommandManager::instance()->getKeyFromId(MI_FreezePreview))); + connect(action, SIGNAL(triggered()), m_flipbook, + SLOT(unfreezePreview())); + } else { + action = menu->addAction(tr("Freeze Preview")); + action->setShortcut(QKeySequence( + CommandManager::instance()->getKeyFromId(MI_FreezePreview))); + connect(action, SIGNAL(triggered()), m_flipbook, SLOT(freezePreview())); + } + + action = menu->addAction(tr("Regenerate Preview")); action->setShortcut(QKeySequence( - CommandManager::instance()->getKeyFromId(MI_FreezePreview))); - connect(action, SIGNAL(triggered()), m_flipbook, SLOT(unfreezePreview())); - } else { - action = menu->addAction(tr("Freeze Preview")); + CommandManager::instance()->getKeyFromId(MI_RegeneratePreview))); + connect(action, SIGNAL(triggered()), m_flipbook, SLOT(regenerate())); + + action = menu->addAction(tr("Regenerate Frame Preview")); action->setShortcut(QKeySequence( - CommandManager::instance()->getKeyFromId(MI_FreezePreview))); - connect(action, SIGNAL(triggered()), m_flipbook, SLOT(freezePreview())); + CommandManager::instance()->getKeyFromId(MI_RegenerateFramePr))); + connect(action, SIGNAL(triggered()), m_flipbook, SLOT(regenerateFrame())); + + menu->addSeparator(); } - action = menu->addAction(tr("Regenerate Preview")); - action->setShortcut(QKeySequence( - CommandManager::instance()->getKeyFromId(MI_RegeneratePreview))); - connect(action, SIGNAL(triggered()), m_flipbook, SLOT(regenerate())); + action = menu->addAction(tr("Load / Append Images")); + connect(action, SIGNAL(triggered()), m_flipbook, SLOT(loadImages())); - action = menu->addAction(tr("Regenerate Frame Preview")); - action->setShortcut(QKeySequence( - CommandManager::instance()->getKeyFromId(MI_RegenerateFramePr))); - connect(action, SIGNAL(triggered()), m_flipbook, SLOT(regenerateFrame())); + // history of the loaded paths of flipbook + action = CommandManager::instance()->getAction(MI_LoadRecentImage); + menu->addAction(action); + action->setParent(m_flipbook); + if (m_flipbook->isSavable()) { + action = menu->addAction(tr("Save Images")); + connect(action, SIGNAL(triggered()), m_flipbook, SLOT(saveImages())); + } menu->addSeparator(); } - action = menu->addAction(tr("Load / Append Images")); - connect(action, SIGNAL(triggered()), m_flipbook, SLOT(loadImages())); - - // history of the loaded paths of flipbook - action = CommandManager::instance()->getAction(MI_LoadRecentImage); - menu->addAction(action); - action->setParent(m_flipbook); - - if (m_flipbook->isSavable()) { - action = menu->addAction(tr("Save Images")); - connect(action, SIGNAL(triggered()), m_flipbook, SLOT(saveImages())); - } - menu->addSeparator(); - QAction *reset = menu->addAction(tr("Reset View")); reset->setShortcut( QKeySequence(CommandManager::instance()->getKeyFromId(V_ZoomReset))); @@ -305,42 +313,45 @@ void ImageViewer::contextMenuEvent(QContextMenuEvent *event) { QKeySequence(CommandManager::instance()->getKeyFromId(V_ZoomFit))); connect(fit, SIGNAL(triggered()), SLOT(fitView())); + if (m_flipbook) { #ifdef _WIN32 + if (ImageUtils::FullScreenWidget *fsWidget = + dynamic_cast(parentWidget())) { + bool isFullScreen = (fsWidget->windowState() & Qt::WindowFullScreen) != 0; - if (ImageUtils::FullScreenWidget *fsWidget = - dynamic_cast(parentWidget())) { - bool isFullScreen = (fsWidget->windowState() & Qt::WindowFullScreen) != 0; - - action = menu->addAction(isFullScreen ? tr("Exit Full Screen Mode") - : tr("Full Screen Mode")); + action = menu->addAction(isFullScreen ? tr("Exit Full Screen Mode") + : tr("Full Screen Mode")); - action->setShortcut(QKeySequence( - CommandManager::instance()->getKeyFromId(V_ShowHideFullScreen))); - connect(action, SIGNAL(triggered()), fsWidget, SLOT(toggleFullScreen())); - } + action->setShortcut(QKeySequence( + CommandManager::instance()->getKeyFromId(V_ShowHideFullScreen))); + connect(action, SIGNAL(triggered()), fsWidget, SLOT(toggleFullScreen())); + } #endif - bool addedSep = false; + bool addedSep = false; - if (m_isHistogramEnable && - visibleRegion().contains(event->pos() * getDevPixRatio())) { - menu->addSeparator(); - addedSep = true; - action = menu->addAction(tr("Show Histogram")); - connect(action, SIGNAL(triggered()), SLOT(showHistogram())); - } + if (m_isHistogramEnable && + visibleRegion().contains(event->pos() * getDevPixRatio())) { + menu->addSeparator(); + addedSep = true; + action = menu->addAction(tr("Show Histogram")); + connect(action, SIGNAL(triggered()), SLOT(showHistogram())); + } - if (m_visualSettings.m_doCompare) { - if (!addedSep) menu->addSeparator(); - action = menu->addAction(tr("Swap Compared Images")); - connect(action, SIGNAL(triggered()), SLOT(swapCompared())); + if (m_visualSettings.m_doCompare) { + if (!addedSep) menu->addSeparator(); + action = menu->addAction(tr("Swap Compared Images")); + connect(action, SIGNAL(triggered()), SLOT(swapCompared())); + } } menu->exec(event->globalPos()); - action = CommandManager::instance()->getAction(MI_LoadRecentImage); - action->setParent(0); + if (m_flipbook) { + action = CommandManager::instance()->getAction(MI_LoadRecentImage); + action->setParent(0); + } delete menu; update(); @@ -376,6 +387,11 @@ ImageViewer::~ImageViewer() { void ImageViewer::setImage(TImageP image) { m_image = image; + if (m_image && m_firstImage) { + m_firstImage = false; + fitView(); + } + if (m_isHistogramEnable && m_histogramPopup->isVisible()) m_histogramPopup->setImage(image); if (!isColorModel()) @@ -733,6 +749,12 @@ void ImageViewer::updateCursor(const TPoint &curPos) { */ void ImageViewer::mouseMoveEvent(QMouseEvent *event) { if (!m_image) return; + + if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen && + !m_stylusUsed) { + return; + } + QPoint curQPos = event->pos() * getDevPixRatio(); TPoint curPos = TPoint(curQPos.x(), curQPos.y()); @@ -939,6 +961,13 @@ int ImageViewer::getDragType(const TPoint &pos, const TRect &loadbox) { //------------------------------------------------------------------------------- void ImageViewer::mouseDoubleClickEvent(QMouseEvent *event) { if (!m_image) return; + // qDebug() << "[mouseDoubleClickEvent]"; + if (m_gestureActive && !m_stylusUsed) { + m_gestureActive = false; + fitView(); + return; + } + if (m_visualSettings.m_defineLoadbox && m_flipbook) { m_flipbook->setLoadbox(TRect()); update(); @@ -950,6 +979,13 @@ void ImageViewer::mouseDoubleClickEvent(QMouseEvent *event) { void ImageViewer::mousePressEvent(QMouseEvent *event) { if (!m_image) return; + + // qDebug() << "[mousePressEvent]"; + if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen && + !m_stylusUsed) { + return; + } + m_pos = event->pos() * getDevPixRatio(); m_pressedMousePos = TPoint(m_pos.x(), m_pos.y()); m_mouseButton = event->button(); @@ -1026,6 +1062,11 @@ void ImageViewer::mouseReleaseEvent(QMouseEvent *event) { m_mouseButton = Qt::NoButton; m_compareSettings.m_dragCompareX = m_compareSettings.m_dragCompareY = false; + m_gestureActive = false; + m_zooming = false; + m_panning = false; + m_stylusUsed = false; + event->ignore(); } @@ -1035,10 +1076,50 @@ void ImageViewer::mouseReleaseEvent(QMouseEvent *event) { void ImageViewer::wheelEvent(QWheelEvent *event) { if (!m_image) return; if (event->orientation() == Qt::Horizontal) return; - int delta = event->delta() > 0 ? 120 : -120; - QPoint center(event->pos().x() * getDevPixRatio() - width() / 2, - -event->pos().y() * getDevPixRatio() + height() / 2); - zoomQt(center, exp(0.001 * delta)); + int delta = 0; + switch (event->source()) { + case Qt::MouseEventNotSynthesized: { + if (event->modifiers() & Qt::AltModifier) + delta = event->angleDelta().x(); + else + delta = event->angleDelta().y(); + break; + } + + case Qt::MouseEventSynthesizedBySystem: { + QPoint numPixels = event->pixelDelta(); + QPoint numDegrees = event->angleDelta() / 8; + if (!numPixels.isNull()) { + delta = event->pixelDelta().y(); + } else if (!numDegrees.isNull()) { + QPoint numSteps = numDegrees / 15; + delta = numSteps.y(); + } + break; + } + + default: // Qt::MouseEventSynthesizedByQt, + // Qt::MouseEventSynthesizedByApplication + { + std::cout << "not supported event: Qt::MouseEventSynthesizedByQt, " + "Qt::MouseEventSynthesizedByApplication" + << std::endl; + break; + } + + } // end switch + + if (abs(delta) > 0) { + if ((m_gestureActive == true && + m_touchDevice == QTouchDevice::TouchScreen) || + m_gestureActive == false) { + int delta = event->delta() > 0 ? 120 : -120; + QPoint center(event->pos().x() * getDevPixRatio() - width() / 2, + -event->pos().y() * getDevPixRatio() + height() / 2); + zoomQt(center, exp(0.001 * delta)); + } + } + event->accept(); } //----------------------------------------------------------------------------- @@ -1168,6 +1249,180 @@ void ImageViewer::onContextAboutToBeDestroyed() { doneCurrent(); } +//------------------------------------------------------------------ + +void ImageViewer::tabletEvent(QTabletEvent *e) { + // qDebug() << "[tabletEvent]"; + if (e->type() == QTabletEvent::TabletPress) { + m_stylusUsed = e->pointerType() ? true : false; + } else if (e->type() == QTabletEvent::TabletRelease) { + m_stylusUsed = false; + } + + e->accept(); +} + +//------------------------------------------------------------------ + +void ImageViewer::gestureEvent(QGestureEvent *e) { + // qDebug() << "[gestureEvent]"; + m_gestureActive = false; + if (QGesture *swipe = e->gesture(Qt::SwipeGesture)) { + m_gestureActive = true; + } else if (QGesture *pan = e->gesture(Qt::PanGesture)) { + m_gestureActive = true; + } + if (QGesture *pinch = e->gesture(Qt::PinchGesture)) { + QPinchGesture *gesture = static_cast(pinch); + QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags(); + QPoint firstCenter = gesture->centerPoint().toPoint(); + if (m_touchDevice == QTouchDevice::TouchScreen) + firstCenter = mapFromGlobal(firstCenter); + + if (gesture->state() == Qt::GestureStarted) { + m_gestureActive = true; + } else if (gesture->state() == Qt::GestureFinished) { + m_gestureActive = false; + m_zooming = false; + m_scaleFactor = 0.0; + } else { + if (changeFlags & QPinchGesture::ScaleFactorChanged) { + double scaleFactor = gesture->scaleFactor(); + // the scale factor makes for too sensitive scaling + // divide the change in half + if (scaleFactor > 1) { + double decimalValue = scaleFactor - 1; + decimalValue /= 1.5; + scaleFactor = 1 + decimalValue; + } else if (scaleFactor < 1) { + double decimalValue = 1 - scaleFactor; + decimalValue /= 1.5; + scaleFactor = 1 - decimalValue; + } + if (!m_zooming) { + double delta = scaleFactor - 1; + m_scaleFactor += delta; + if (m_scaleFactor > .2 || m_scaleFactor < -.2) { + m_zooming = true; + } + } + if (m_zooming) { + const QPoint center( + firstCenter.x() * getDevPixRatio() - width() / 2, + -firstCenter.y() * getDevPixRatio() + height() / 2); + zoomQt(center, scaleFactor); + m_panning = false; + } + m_gestureActive = true; + } + + if (changeFlags & QPinchGesture::CenterPointChanged) { + QPointF centerDelta = (gesture->centerPoint() * getDevPixRatio()) - + (gesture->lastCenterPoint() * getDevPixRatio()); + if (centerDelta.manhattanLength() > 1) { + // panQt(centerDelta.toPoint()); + } + m_gestureActive = true; + } + } + } + e->accept(); +} + +void ImageViewer::touchEvent(QTouchEvent *e, int type) { + // qDebug() << "[touchEvent]"; + if (type == QEvent::TouchBegin) { + m_touchActive = true; + m_firstPanPoint = e->touchPoints().at(0).pos(); + // obtain device type + m_touchDevice = e->device()->type(); + } else if (m_touchActive) { + // touchpads must have 2 finger panning for tools and navigation to be + // functional on other devices, 1 finger panning is preferred + if ((e->touchPoints().count() == 2 && + m_touchDevice == QTouchDevice::TouchPad) || + (e->touchPoints().count() == 1 && + m_touchDevice == QTouchDevice::TouchScreen)) { + QTouchEvent::TouchPoint panPoint = e->touchPoints().at(0); + if (!m_panning) { + QPointF deltaPoint = panPoint.pos() - m_firstPanPoint; + // minimize accidental and jerky zooming/rotating during 2 finger + // panning + if ((deltaPoint.manhattanLength() > 100) && !m_zooming) { + m_panning = true; + } + } + if (m_panning) { + QPoint curPos = panPoint.pos().toPoint() * getDevPixRatio(); + QPoint lastPos = panPoint.lastPos().toPoint() * getDevPixRatio(); + QPoint centerDelta = curPos - lastPos; + panQt(centerDelta); + } + } + } + if (type == QEvent::TouchEnd || type == QEvent::TouchCancel) { + m_touchActive = false; + m_panning = false; + } + e->accept(); +} + +bool ImageViewer::event(QEvent *e) { + /* + switch (e->type()) { + case QEvent::TabletPress: { + QTabletEvent *te = static_cast(e); + qDebug() << "[event] TabletPress: pointerType(" << te->pointerType() + << ") device(" << te->device() << ")"; + } break; + case QEvent::TabletRelease: + qDebug() << "[event] TabletRelease"; + break; + case QEvent::TouchBegin: + qDebug() << "[event] TouchBegin"; + break; + case QEvent::TouchEnd: + qDebug() << "[event] TouchEnd"; + break; + case QEvent::TouchCancel: + qDebug() << "[event] TouchCancel"; + break; + case QEvent::MouseButtonPress: + qDebug() << "[event] MouseButtonPress"; + break; + case QEvent::MouseButtonDblClick: + qDebug() << "[event] MouseButtonDblClick"; + break; + case QEvent::MouseButtonRelease: + qDebug() << "[event] MouseButtonRelease"; + break; + case QEvent::Gesture: + qDebug() << "[event] Gesture"; + break; + default: + break; + } + */ + + if (e->type() == QEvent::Gesture && + CommandManager::instance() + ->getAction(MI_TouchGestureControl) + ->isChecked()) { + gestureEvent(static_cast(e)); + return true; + } + if ((e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchEnd || + e->type() == QEvent::TouchCancel || e->type() == QEvent::TouchUpdate) && + CommandManager::instance() + ->getAction(MI_TouchGestureControl) + ->isChecked()) { + touchEvent(static_cast(e), e->type()); + m_gestureActive = true; + return true; + } + return GLWidgetForHighDpi::event(e); +} + //----------------------------------------------------------------------------- /*! load image from history */ diff --git a/toonz/sources/toonz/imageviewer.h b/toonz/sources/toonz/imageviewer.h index 0e139a2..363af2c 100644 --- a/toonz/sources/toonz/imageviewer.h +++ b/toonz/sources/toonz/imageviewer.h @@ -6,6 +6,8 @@ #include "toonz/imagepainter.h" #include "toonzqt/glwidget_for_highdpi.h" +#include + //----------------------------------------------------------------------------- // Forward declarations @@ -13,7 +15,8 @@ class FlipBook; class HistogramPopup; class QOpenGLFramebufferObject; class LutCalibrator; - +class QTouchEvent; +class QGestureEvent; //----------------------------------------------------------------------------- //==================== @@ -50,6 +53,7 @@ class ImageViewer final : public GLWidgetForHighDpi { QPoint m_pos; bool m_isHistogramEnable; HistogramPopup *m_histogramPopup; + bool m_firstImage; bool m_isColorModel; // when fx parameter is modified with showing the fx preview, @@ -60,6 +64,15 @@ class ImageViewer final : public GLWidgetForHighDpi { QOpenGLFramebufferObject *m_fbo = NULL; LutCalibrator *m_lutCalibrator = NULL; + bool m_touchActive = false; + bool m_gestureActive = false; + QTouchDevice::DeviceType m_touchDevice = QTouchDevice::TouchScreen; + bool m_zooming = false; + bool m_panning = false; + double m_scaleFactor; // used for zoom gesture + + bool m_stylusUsed = false; + int getDragType(const TPoint &pos, const TRect &loadBox); void updateLoadbox(const TPoint &curPos); void updateCursor(const TPoint &curPos); @@ -127,6 +140,11 @@ protected: void dragCompare(const QPoint &dp); + void tabletEvent(QTabletEvent *e); + void touchEvent(QTouchEvent *e, int type); + void gestureEvent(QGestureEvent *e); + bool event(QEvent *e); + public slots: void updateImageViewer(); @@ -135,6 +153,9 @@ public slots: void showHistogram(); void swapCompared(); void onContextAboutToBeDestroyed(); + +private: + QPointF m_firstPanPoint; }; #endif // IMAGEVIEWER_INCLUDE diff --git a/toonz/sources/toonzqt/CMakeLists.txt b/toonz/sources/toonzqt/CMakeLists.txt index 7863bd9..c86d517 100644 --- a/toonz/sources/toonzqt/CMakeLists.txt +++ b/toonz/sources/toonzqt/CMakeLists.txt @@ -45,6 +45,7 @@ set(MOC_HEADERS ../include/toonzqt/paletteviewer.h ../include/toonzqt/paletteviewergui.h ../include/toonzqt/paramfield.h + ../include/toonzqt/planeviewer.h ../include/toonzqt/popupbutton.h ../include/toonzqt/schematicgroupeditor.h ../include/toonzqt/schematicnode.h @@ -91,7 +92,6 @@ set(HEADERS ../include/toonzqt/lutcalibrator.h ../include/toonzqt/multipleselection.h ../include/toonzqt/pickrgbutils.h - ../include/toonzqt/planeviewer.h ../include/toonzqt/rasterimagedata.h ../include/toonzqt/selection.h ../include/toonzqt/selectioncommandids.h diff --git a/toonz/sources/toonzqt/planeviewer.cpp b/toonz/sources/toonzqt/planeviewer.cpp index 2ec1559..11f247e 100644 --- a/toonz/sources/toonzqt/planeviewer.cpp +++ b/toonz/sources/toonzqt/planeviewer.cpp @@ -4,6 +4,10 @@ // TnzQt includes #include "toonzqt/imageutils.h" +#include "toonzqt/menubarcommand.h" +#include "toonzqt/viewcommandids.h" + +#include "../toonz/menubarcommandids.h" // TnzLib includes #include "toonz/stage.h" @@ -23,6 +27,7 @@ #include #include #include +#include //#define PRINT_AFF @@ -37,6 +42,7 @@ struct PlaneViewerZoomer final : public ImageUtils::ShortcutZoomer { private: bool zoom(bool zoomin, bool resetZoom) override; + bool fit() override; }; //======================================================================== @@ -44,12 +50,19 @@ private: bool PlaneViewerZoomer::zoom(bool zoomin, bool resetZoom) { PlaneViewer &planeViewer = static_cast(*getWidget()); - resetZoom ? planeViewer.resetView() - : zoomin ? planeViewer.zoomIn() : planeViewer.zoomOut(); + resetZoom ? planeViewer.resetView() : zoomin ? planeViewer.zoomIn() + : planeViewer.zoomOut(); return true; } +bool PlaneViewerZoomer::fit() { + PlaneViewer &planeViewer = static_cast(*getWidget()); + + planeViewer.fitView(); + + return true; +} } // namespace //========================================================================================= @@ -60,9 +73,17 @@ PlaneViewer::PlaneViewer(QWidget *parent) , m_xpos(0) , m_ypos(0) , m_aff() // initialized at the first resize - , m_chessSize(40.0) { + , m_chessSize(40.0) + , m_firstDraw(true) + , m_dpiX(0.0) + , m_dpiY(0.0) { m_zoomRange[0] = 1e-3, m_zoomRange[1] = 1024.0; setBgColor(TPixel32(235, 235, 235), TPixel32(235, 235, 235)); + + setAttribute(Qt::WA_AcceptTouchEvents); + grabGesture(Qt::SwipeGesture); + grabGesture(Qt::PanGesture); + grabGesture(Qt::PinchGesture); } //========================================================================================= @@ -162,6 +183,11 @@ void PlaneViewer::resizeGL(int width, int height) { //========================================================================================= void PlaneViewer::mouseMoveEvent(QMouseEvent *event) { + if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen && + !m_stylusUsed) { + return; + } + QPoint curPos = event->pos() * getDevPixRatio(); if (event->buttons() & Qt::MidButton) moveView(curPos.x() - m_xpos, height() - curPos.y() - m_ypos); @@ -172,18 +198,84 @@ void PlaneViewer::mouseMoveEvent(QMouseEvent *event) { //------------------------------------------------------ void PlaneViewer::mousePressEvent(QMouseEvent *event) { + // qDebug() << "[mousePressEvent]"; + if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen && + !m_stylusUsed) { + return; + } + m_xpos = event->x() * getDevPixRatio(); m_ypos = height() - event->y() * getDevPixRatio(); } //------------------------------------------------------ +void PlaneViewer::mouseDoubleClickEvent(QMouseEvent *event) { + // qDebug() << "[mouseDoubleClickEvent]"; + if (m_gestureActive && !m_stylusUsed) { + m_gestureActive = false; + fitView(); + return; + } +} + +//------------------------------------------------------ + +void PlaneViewer::mouseReleaseEvent(QMouseEvent *event) { + m_gestureActive = false; + m_zooming = false; + m_panning = false; + m_stylusUsed = false; +} + +//------------------------------------------------------ + void PlaneViewer::wheelEvent(QWheelEvent *event) { - TPointD pos(event->x() * getDevPixRatio(), - height() - event->y() * getDevPixRatio()); - double zoom_par = 1 + event->delta() * 0.001; + int delta = 0; + switch (event->source()) { + case Qt::MouseEventNotSynthesized: { + if (event->modifiers() & Qt::AltModifier) + delta = event->angleDelta().x(); + else + delta = event->angleDelta().y(); + break; + } + + case Qt::MouseEventSynthesizedBySystem: { + QPoint numPixels = event->pixelDelta(); + QPoint numDegrees = event->angleDelta() / 8; + if (!numPixels.isNull()) { + delta = event->pixelDelta().y(); + } else if (!numDegrees.isNull()) { + QPoint numSteps = numDegrees / 15; + delta = numSteps.y(); + } + break; + } + + default: // Qt::MouseEventSynthesizedByQt, + // Qt::MouseEventSynthesizedByApplication + { + std::cout << "not supported event: Qt::MouseEventSynthesizedByQt, " + "Qt::MouseEventSynthesizedByApplication" + << std::endl; + break; + } + + } // end switch + + if (abs(delta) > 0) { + if ((m_gestureActive == true && + m_touchDevice == QTouchDevice::TouchScreen) || + m_gestureActive == false) { + TPointD pos(event->x() * getDevPixRatio(), + height() - event->y() * getDevPixRatio()); + double zoom_par = 1 + event->delta() * 0.001; - zoomView(pos.x, pos.y, zoom_par); + zoomView(pos.x, pos.y, zoom_par); + } + } + event->accept(); } //------------------------------------------------------ @@ -199,6 +291,201 @@ void PlaneViewer::keyPressEvent(QKeyEvent *event) { //! Disposes of the auxiliary internal rasterBuffer(). void PlaneViewer::hideEvent(QHideEvent *event) { m_rasterBuffer = TRaster32P(); + m_dpiX = 0.0; // reset dpi + m_dpiY = 0.0; +} + +//------------------------------------------------------------------ + +void PlaneViewer::contextMenuEvent(QContextMenuEvent *event) { + QMenu *menu = new QMenu(this); + + QAction *reset = menu->addAction(tr("Reset View")); + reset->setShortcut( + QKeySequence(CommandManager::instance()->getKeyFromId(V_ZoomReset))); + connect(reset, SIGNAL(triggered()), SLOT(resetView())); + + QAction *fit = menu->addAction(tr("Fit To Window")); + fit->setShortcut( + QKeySequence(CommandManager::instance()->getKeyFromId(V_ZoomFit))); + connect(fit, SIGNAL(triggered()), SLOT(fitView())); + + menu->exec(event->globalPos()); + + delete menu; + update(); +} + +//------------------------------------------------------------------ + +void PlaneViewer::tabletEvent(QTabletEvent *e) { + // qDebug() << "[tabletEvent]"; + if (e->type() == QTabletEvent::TabletPress) { + m_stylusUsed = e->pointerType() ? true : false; + } else if (e->type() == QTabletEvent::TabletRelease) { + m_stylusUsed = false; + } + + e->accept(); +} + +//------------------------------------------------------------------ + +void PlaneViewer::gestureEvent(QGestureEvent *e) { + // qDebug() << "[gestureEvent]"; + m_gestureActive = false; + if (QGesture *swipe = e->gesture(Qt::SwipeGesture)) { + m_gestureActive = true; + } else if (QGesture *pan = e->gesture(Qt::PanGesture)) { + m_gestureActive = true; + } + if (QGesture *pinch = e->gesture(Qt::PinchGesture)) { + QPinchGesture *gesture = static_cast(pinch); + QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags(); + QPoint firstCenter = gesture->centerPoint().toPoint(); + if (m_touchDevice == QTouchDevice::TouchScreen) + firstCenter = mapFromGlobal(firstCenter); + + if (gesture->state() == Qt::GestureStarted) { + m_gestureActive = true; + } else if (gesture->state() == Qt::GestureFinished) { + m_gestureActive = false; + m_zooming = false; + m_scaleFactor = 0.0; + } else { + if (changeFlags & QPinchGesture::ScaleFactorChanged) { + double scaleFactor = gesture->scaleFactor(); + // the scale factor makes for too sensitive scaling + // divide the change in half + if (scaleFactor > 1) { + double decimalValue = scaleFactor - 1; + decimalValue /= 1.5; + scaleFactor = 1 + decimalValue; + } else if (scaleFactor < 1) { + double decimalValue = 1 - scaleFactor; + decimalValue /= 1.5; + scaleFactor = 1 - decimalValue; + } + if (!m_zooming) { + double delta = scaleFactor - 1; + m_scaleFactor += delta; + if (m_scaleFactor > .2 || m_scaleFactor < -.2) { + m_zooming = true; + } + } + if (m_zooming) { + zoomView(firstCenter.x() * getDevPixRatio(), + firstCenter.y() * getDevPixRatio(), scaleFactor); + m_panning = false; + } + m_gestureActive = true; + } + + if (changeFlags & QPinchGesture::CenterPointChanged) { + QPointF centerDelta = (gesture->centerPoint() * getDevPixRatio()) - + (gesture->lastCenterPoint() * getDevPixRatio()); + if (centerDelta.manhattanLength() > 1) { + // panQt(centerDelta.toPoint()); + } + m_gestureActive = true; + } + } + } + e->accept(); +} + +void PlaneViewer::touchEvent(QTouchEvent *e, int type) { + // qDebug() << "[touchEvent]"; + if (type == QEvent::TouchBegin) { + m_touchActive = true; + m_firstPanPoint = e->touchPoints().at(0).pos(); + // obtain device type + m_touchDevice = e->device()->type(); + } else if (m_touchActive) { + // touchpads must have 2 finger panning for tools and navigation to be + // functional on other devices, 1 finger panning is preferred + if ((e->touchPoints().count() == 2 && + m_touchDevice == QTouchDevice::TouchPad) || + (e->touchPoints().count() == 1 && + m_touchDevice == QTouchDevice::TouchScreen)) { + QTouchEvent::TouchPoint panPoint = e->touchPoints().at(0); + if (!m_panning) { + QPointF deltaPoint = panPoint.pos() - m_firstPanPoint; + // minimize accidental and jerky zooming/rotating during 2 finger + // panning + if ((deltaPoint.manhattanLength() > 100) && !m_zooming) { + m_panning = true; + } + } + if (m_panning) { + QPoint curPos = panPoint.pos().toPoint() * getDevPixRatio(); + QPoint lastPos = panPoint.lastPos().toPoint() * getDevPixRatio(); + QPoint centerDelta = curPos - lastPos; + moveView(centerDelta.x(), -centerDelta.y()); + } + } + } + if (type == QEvent::TouchEnd || type == QEvent::TouchCancel) { + m_touchActive = false; + m_panning = false; + } + e->accept(); +} + +bool PlaneViewer::event(QEvent *e) { + /* + switch (e->type()) { + case QEvent::TabletPress: { + QTabletEvent *te = static_cast(e); + qDebug() << "[event] TabletPress: pointerType(" << te->pointerType() + << ") device(" << te->device() << ")"; + } break; + case QEvent::TabletRelease: + qDebug() << "[event] TabletRelease"; + break; + case QEvent::TouchBegin: + qDebug() << "[event] TouchBegin"; + break; + case QEvent::TouchEnd: + qDebug() << "[event] TouchEnd"; + break; + case QEvent::TouchCancel: + qDebug() << "[event] TouchCancel"; + break; + case QEvent::MouseButtonPress: + qDebug() << "[event] MouseButtonPress"; + break; + case QEvent::MouseButtonDblClick: + qDebug() << "[event] MouseButtonDblClick"; + break; + case QEvent::MouseButtonRelease: + qDebug() << "[event] MouseButtonRelease"; + break; + case QEvent::Gesture: + qDebug() << "[event] Gesture"; + break; + default: + break; + } + */ + + if (e->type() == QEvent::Gesture && + CommandManager::instance() + ->getAction(MI_TouchGestureControl) + ->isChecked()) { + gestureEvent(static_cast(e)); + return true; + } + if ((e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchEnd || + e->type() == QEvent::TouchCancel || e->type() == QEvent::TouchUpdate) && + CommandManager::instance() + ->getAction(MI_TouchGestureControl) + ->isChecked()) { + touchEvent(static_cast(e), e->type()); + m_gestureActive = true; + return true; + } + return GLWidgetForHighDpi::event(e); } //========================================================================================= @@ -208,6 +495,22 @@ void PlaneViewer::resetView() { update(); } +void PlaneViewer::fitView() { + if (m_imageBounds.isEmpty()) return; + m_aff = TTranslation(0.5 * width(), 0.5 * height()); + + double imageScale = std::min(width() / (double)m_imageBounds.getLx(), + height() / (double)m_imageBounds.getLy()); + + m_aff = TScale(imageScale, imageScale); + if (m_dpiX != 0.0 && m_dpiY != 0.0) + m_aff *= TScale(m_dpiX / Stage::inch, m_dpiY / Stage::inch); + m_aff.a13 = 0.5 * width(); + m_aff.a23 = 0.5 * height(); + + update(); +} + //------------------------------------------------------ void PlaneViewer::setViewPos(double x, double y) { @@ -306,6 +609,16 @@ void PlaneViewer::draw(TRasterP ras, double dpiX, double dpiY, TPalette *pal) { TRaster32P aux(rasterBuffer()); + m_imageBounds = ras->getBounds(); + if (m_dpiX == 0.0 || m_dpiY == 0.0) { + m_dpiX = dpiX; + m_dpiY = dpiY; + } + if (m_firstDraw && !m_imageBounds.isEmpty()) { + m_firstDraw = false; + fitView(); + } + aux->lock(); ras->lock(); @@ -328,8 +641,8 @@ void PlaneViewer::draw(TRasterP ras, double dpiX, double dpiY, TPalette *pal) { } /*NOTE: - glRasterPos2d could be used, along glBitmap and glPixelZoom... - however, i've never been able to use them effectively... +glRasterPos2d could be used, along glBitmap and glPixelZoom... +however, i've never been able to use them effectively... */ //------------------------------------------------------ @@ -360,7 +673,15 @@ void PlaneViewer::draw(TVectorImageP vi) { TRectD bbox(vi->getBBox()); TRect bboxI(tfloor(bbox.x0), tfloor(bbox.y0), tceil(bbox.x1) - 1, tceil(bbox.y1) - 1); - + m_imageBounds = bboxI; + if (m_dpiX == 0.0 || m_dpiY == 0.0) { + m_dpiX = Stage::inch; + m_dpiY = Stage::inch; + } + if (m_firstDraw) { + m_firstDraw = false; + fitView(); + } TVectorRenderData rd(TAffine(), bboxI, vi->getPalette(), 0, true, true); tglDraw(rd, vi.getPointer()); } diff --git a/toonz/sources/toonzqt/swatchviewer.cpp b/toonz/sources/toonzqt/swatchviewer.cpp index a58286e..0e89f69 100644 --- a/toonz/sources/toonzqt/swatchviewer.cpp +++ b/toonz/sources/toonzqt/swatchviewer.cpp @@ -2,9 +2,14 @@ #include "toonzqt/swatchviewer.h" #include "toonzqt/gutil.h" +#include "toonzqt/menubarcommand.h" +#include "toonzqt/viewcommandids.h" + +#include "../toonz/menubarcommandids.h" #include #include +#include #include #include "trasterfx.h" @@ -20,6 +25,8 @@ #include #include +#include + using namespace TFxUtil; //#define USE_SQLITE_HDPOOL @@ -255,7 +262,8 @@ SwatchViewer::SwatchViewer(QWidget *parent, Qt::WFlags flags) , m_firstPos(TPoint()) , m_oldContent() , m_curContent() - , m_executor() { + , m_executor() + , m_firstEnabled(true) { // setMinimumSize(150,150); setMinimumHeight(150); setFixedWidth(150); @@ -268,6 +276,11 @@ SwatchViewer::SwatchViewer(QWidget *parent, Qt::WFlags flags) m_executor.setMaxActiveTasks(1); m_renderer.enablePrecomputing(false); + + setAttribute(Qt::WA_AcceptTouchEvents); + grabGesture(Qt::SwipeGesture); + grabGesture(Qt::PanGesture); + grabGesture(Qt::PinchGesture); } //----------------------------------------------------------------------------- @@ -363,9 +376,13 @@ void SwatchViewer::updateFrame(int frame) { void SwatchViewer::setEnable(bool enabled) { if (m_enabled == enabled) return; m_enabled = enabled; - if (m_enabled) + if (m_enabled) { + if (m_firstEnabled) { + m_firstEnabled = false; + fitView(); + } computeContent(); - else + } else update(); } @@ -425,6 +442,26 @@ void SwatchViewer::zoom(const TPoint &pos, double factor) { //----------------------------------------------------------------------------- +void SwatchViewer::resetView() { setAff(TAffine()); } + +//----------------------------------------------------------------------------- + +void SwatchViewer::fitView() { + if (m_cameraRect.isEmpty()) return; + + double imageScale = std::min( + width() / ((double)getCameraSize().lx * (m_cameraMode ? 1 : 0.44)), + height() / ((double)getCameraSize().ly * (m_cameraMode ? 1 : 0.44))); + + TAffine fitAffine = TScale(imageScale, imageScale); + fitAffine.a13 = 0; + fitAffine.a23 = 0; + + setAff(fitAffine); +} + +//----------------------------------------------------------------------------- + void SwatchViewer::zoom(bool forward, bool reset) { double scale2 = m_aff.det(); if (reset || ((scale2 < 2000 || !forward) && (scale2 > 0.004 || forward))) { @@ -437,6 +474,11 @@ void SwatchViewer::zoom(bool forward, bool reset) { } } +void SwatchViewer::pan(const TPoint &delta) { + TPointD step = convert(delta); + setAff(TTranslation(step.x, -step.y) * m_aff); +} + //----------------------------------------------------------------------------- void SwatchViewer::computeContent() { @@ -447,7 +489,7 @@ void SwatchViewer::computeContent() { // Clear the swatch cache when the zoom scale has changed (cache results are // not compatible // between different scale levels) - if (m_aff.a11 != m_contentAff.a11) + if (m_aff.a11 != m_contentAff.a11 || m_panning) SwatchCacheManager::instance()->clearSwatchResults(); TRect rect(0, 0, width() - 1, height() - 1); @@ -593,6 +635,12 @@ void SwatchViewer::resizeEvent(QResizeEvent *re) { //----------------------------------------------------------------------------- void SwatchViewer::mousePressEvent(QMouseEvent *event) { + // qDebug() << "[mousePressEvent]"; + if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen && + !m_stylusUsed) { + return; + } + TPoint pos = TPoint(event->pos().x(), event->pos().y()); m_mouseButton = event->button(); if (m_mouseButton == Qt::LeftButton) { @@ -651,6 +699,11 @@ void SwatchViewer::mousePressEvent(QMouseEvent *event) { //----------------------------------------------------------------------------- void SwatchViewer::mouseMoveEvent(QMouseEvent *event) { + if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen && + !m_stylusUsed) { + return; + } + TPoint pos = TPoint(event->pos().x(), event->pos().y()); if (m_mouseButton == Qt::LeftButton) { if (m_selectedPoint < 0 || m_selectedPoint >= (int)m_points.size()) return; @@ -671,15 +724,8 @@ void SwatchViewer::mouseMoveEvent(QMouseEvent *event) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } else if (m_mouseButton == Qt::MidButton) { - if (!m_oldContent || !m_curContent) return; - m_curContent->fill(TPixel32::Transparent); - TPointD step = convert(pos - m_pos); - // Devo aggiornare l'affine per riposizionare la camera. - m_aff = TTranslation(step.x, -step.y) * m_aff; - m_pos = pos; - TPoint p = pos - m_firstPos; - m_curContent->copy(m_oldContent, TPoint(p.x, -p.y)); - setContent(m_curContent, TTranslation(step.x, -step.y) * m_contentAff); + pan(pos - m_pos); + m_pos = pos; } } @@ -695,20 +741,78 @@ void SwatchViewer::mouseReleaseEvent(QMouseEvent *event) { setAff(TTranslation(p.x, -p.y) * m_aff); update(); } + m_gestureActive = false; + m_zooming = false; + m_panning = false; + m_stylusUsed = false; } //----------------------------------------------------------------------------- void SwatchViewer::wheelEvent(QWheelEvent *event) { - TPoint center(event->pos().x() - width() / 2, - -event->pos().y() + height() / 2); - zoom(center, exp(0.001 * event->delta())); + int delta = 0; + switch (event->source()) { + case Qt::MouseEventNotSynthesized: { + if (event->modifiers() & Qt::AltModifier) + delta = event->angleDelta().x(); + else + delta = event->angleDelta().y(); + break; + } + + case Qt::MouseEventSynthesizedBySystem: { + QPoint numPixels = event->pixelDelta(); + QPoint numDegrees = event->angleDelta() / 8; + if (!numPixels.isNull()) { + delta = event->pixelDelta().y(); + } else if (!numDegrees.isNull()) { + QPoint numSteps = numDegrees / 15; + delta = numSteps.y(); + } + break; + } + + default: // Qt::MouseEventSynthesizedByQt, + // Qt::MouseEventSynthesizedByApplication + { + std::cout << "not supported event: Qt::MouseEventSynthesizedByQt, " + "Qt::MouseEventSynthesizedByApplication" + << std::endl; + break; + } + + } // end switch + + if (abs(delta) > 0) { + if ((m_gestureActive == true && + m_touchDevice == QTouchDevice::TouchScreen) || + m_gestureActive == false) { + TPoint center(event->pos().x() - width() / 2, + -event->pos().y() + height() / 2); + zoom(center, exp(0.001 * event->delta())); + } + } + event->accept(); } //----------------------------------------------------------------------------- void SwatchViewer::keyPressEvent(QKeyEvent *event) { int key = event->key(); + std::string keyStr = + QKeySequence(key + event->modifiers()).toString().toStdString(); + QAction *action = CommandManager::instance()->getActionFromShortcut(keyStr); + if (action) { + std::string actionId = CommandManager::instance()->getIdFromAction(action); + if (actionId == V_ZoomFit) { + fitView(); + return; + } else if (actionId == V_ZoomReset) { + resetView(); + return; + } + } + if (key == '+' || key == '-' || key == '0') { zoom(key == '+', key == '0'); } @@ -721,6 +825,212 @@ void SwatchViewer::hideEvent(QHideEvent *event) { ::setFxForCaching(0); } +//------------------------------------------------------------------------------- + +void SwatchViewer::mouseDoubleClickEvent(QMouseEvent *event) { + // qDebug() << "[mouseDoubleClickEvent]"; + if (m_gestureActive && !m_stylusUsed) { + m_gestureActive = false; + fitView(); + return; + } +} + +//------------------------------------------------------------------ + +void SwatchViewer::contextMenuEvent(QContextMenuEvent *event) { + QMenu *menu = new QMenu(this); + + QAction *reset = menu->addAction(tr("Reset View")); + reset->setShortcut( + QKeySequence(CommandManager::instance()->getKeyFromId(V_ZoomReset))); + connect(reset, SIGNAL(triggered()), SLOT(resetView())); + + QAction *fit = menu->addAction(tr("Fit To Window")); + fit->setShortcut( + QKeySequence(CommandManager::instance()->getKeyFromId(V_ZoomFit))); + connect(fit, SIGNAL(triggered()), SLOT(fitView())); + + menu->exec(event->globalPos()); + + delete menu; + update(); +} + +//------------------------------------------------------------------ + +void SwatchViewer::tabletEvent(QTabletEvent *e) { + // qDebug() << "[tabletEvent]"; + if (e->type() == QTabletEvent::TabletPress) { + m_stylusUsed = e->pointerType() ? true : false; + } else if (e->type() == QTabletEvent::TabletRelease) { + m_stylusUsed = false; + } + + e->accept(); +} + +//------------------------------------------------------------------ + +void SwatchViewer::gestureEvent(QGestureEvent *e) { + // qDebug() << "[gestureEvent]"; + m_gestureActive = false; + if (QGesture *swipe = e->gesture(Qt::SwipeGesture)) { + m_gestureActive = true; + } else if (QGesture *pan = e->gesture(Qt::PanGesture)) { + m_gestureActive = true; + } + if (QGesture *pinch = e->gesture(Qt::PinchGesture)) { + QPinchGesture *gesture = static_cast(pinch); + QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags(); + QPoint firstCenter = gesture->centerPoint().toPoint(); + if (m_touchDevice == QTouchDevice::TouchScreen) + firstCenter = mapFromGlobal(firstCenter); + + if (gesture->state() == Qt::GestureStarted) { + m_gestureActive = true; + } else if (gesture->state() == Qt::GestureFinished) { + m_gestureActive = false; + m_zooming = false; + m_scaleFactor = 0.0; + } else { + if (changeFlags & QPinchGesture::ScaleFactorChanged) { + double scaleFactor = gesture->scaleFactor(); + // the scale factor makes for too sensitive scaling + // divide the change in half + if (scaleFactor > 1) { + double decimalValue = scaleFactor - 1; + decimalValue /= 1.5; + scaleFactor = 1 + decimalValue; + } else if (scaleFactor < 1) { + double decimalValue = 1 - scaleFactor; + decimalValue /= 1.5; + scaleFactor = 1 - decimalValue; + } + if (!m_zooming) { + double delta = scaleFactor - 1; + m_scaleFactor += delta; + if (m_scaleFactor > .2 || m_scaleFactor < -.2) { + m_zooming = true; + } + } + if (m_zooming) { + TPoint center(firstCenter.x() - width() / 2, + -firstCenter.y() + height() / 2); + zoom(center, scaleFactor); + m_panning = false; + } + m_gestureActive = true; + } + + if (changeFlags & QPinchGesture::CenterPointChanged) { + QPointF centerDelta = + (gesture->centerPoint()) - (gesture->lastCenterPoint()); + if (centerDelta.manhattanLength() > 1) { + // panQt(centerDelta.toPoint()); + } + m_gestureActive = true; + } + } + } + e->accept(); +} + +void SwatchViewer::touchEvent(QTouchEvent *e, int type) { + // qDebug() << "[touchEvent]"; + if (type == QEvent::TouchBegin) { + m_touchActive = true; + m_firstPanPoint = e->touchPoints().at(0).pos(); + // obtain device type + m_touchDevice = e->device()->type(); + } else if (m_touchActive) { + // touchpads must have 2 finger panning for tools and navigation to be + // functional on other devices, 1 finger panning is preferred + if ((e->touchPoints().count() == 2 && + m_touchDevice == QTouchDevice::TouchPad) || + (e->touchPoints().count() == 1 && + m_touchDevice == QTouchDevice::TouchScreen)) { + QTouchEvent::TouchPoint panPoint = e->touchPoints().at(0); + if (!m_panning) { + QPointF deltaPoint = panPoint.pos() - m_firstPanPoint; + // minimize accidental and jerky zooming/rotating during 2 finger + // panning + if ((deltaPoint.manhattanLength() > 100) && !m_zooming) { + m_panning = true; + } + } + if (m_panning) { + QPoint curPos = panPoint.pos().toPoint(); + QPoint lastPos = panPoint.lastPos().toPoint(); + TPoint centerDelta = + TPoint(curPos.x(), curPos.y()) - TPoint(lastPos.x(), lastPos.y()); + pan(centerDelta); + } + } + } + if (type == QEvent::TouchEnd || type == QEvent::TouchCancel) { + m_touchActive = false; + m_panning = false; + } + e->accept(); +} + +bool SwatchViewer::event(QEvent *e) { + /* + switch (e->type()) { + case QEvent::TabletPress: { + QTabletEvent *te = static_cast(e); + qDebug() << "[event] TabletPress: pointerType(" << te->pointerType() + << ") device(" << te->device() << ")"; + } break; + case QEvent::TabletRelease: + qDebug() << "[event] TabletRelease"; + break; + case QEvent::TouchBegin: + qDebug() << "[event] TouchBegin"; + break; + case QEvent::TouchEnd: + qDebug() << "[event] TouchEnd"; + break; + case QEvent::TouchCancel: + qDebug() << "[event] TouchCancel"; + break; + case QEvent::MouseButtonPress: + qDebug() << "[event] MouseButtonPress"; + break; + case QEvent::MouseButtonDblClick: + qDebug() << "[event] MouseButtonDblClick"; + break; + case QEvent::MouseButtonRelease: + qDebug() << "[event] MouseButtonRelease"; + break; + case QEvent::Gesture: + qDebug() << "[event] Gesture"; + break; + default: + break; + } + */ + + if (e->type() == QEvent::Gesture && + CommandManager::instance() + ->getAction(MI_TouchGestureControl) + ->isChecked()) { + gestureEvent(static_cast(e)); + return true; + } + if ((e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchEnd || + e->type() == QEvent::TouchCancel || e->type() == QEvent::TouchUpdate) && + CommandManager::instance() + ->getAction(MI_TouchGestureControl) + ->isChecked()) { + touchEvent(static_cast(e), e->type()); + m_gestureActive = true; + return true; + } + return QWidget::event(e); +} + //============================================================================= // SwatchViewer::ContentRender //-----------------------------------------------------------------------------