tahoma2d/toonz/sources/toonz/sceneviewerevents.cpp
2023-10-17 06:52:29 -04:00

2025 lines
65 KiB
C++

#include <QtGlobal>
#include "sceneviewercontextmenu.h"
// Tnz6 includes
#include "sceneviewer.h"
#include "sceneviewerevents.h"
#include "viewerpane.h"
#include "tapp.h"
#include "iocommand.h"
#include "menubarcommandids.h"
#include "onionskinmaskgui.h"
#include "ruler.h"
#include "locatorpopup.h"
#include "cellselection.h"
#include "styleshortcutswitchablepanel.h"
#include "stopmotion.h"
#include "tstopwatch.h"
#include "viewereventlogpopup.h"
// TnzQt includes
#include "toonzqt/tselectionhandle.h"
#include "toonzqt/styleselection.h"
// TnzTools includes
#include "tools/cursors.h"
#include "tools/toolhandle.h"
#include "tools/cursormanager.h"
#include "tools/toolcommandids.h"
#include "toonzqt/viewcommandids.h"
// TnzLib includes
#include "toonz/toonzscene.h"
#include "toonz/tcamera.h"
#include "toonz/palettecontroller.h"
#include "toonz/tscenehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/tobjecthandle.h"
#include "toonz/tpalettehandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tonionskinmaskhandle.h"
#include "toonz/dpiscale.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/txshsimplelevel.h"
#include "toonzqt/selection.h"
#include "toonzqt/imageutils.h"
#include "toonzqt/dvdialog.h"
#include "toonzqt/gutil.h"
#include "subcameramanager.h"
// TnzCore includes
#include "timagecache.h"
#include "trasterimage.h"
#include "tvectorimage.h"
#include "tpalette.h"
// Qt includes
#include <QUrl>
#include <QApplication>
#include <QDebug>
#include <QMimeData>
#include <QGestureEvent>
// definito - per ora - in tapp.cpp
extern QString updateToolEnableStatus(TTool *tool);
//-----------------------------------------------------------------------------
namespace {
//-----------------------------------------------------------------------------
void initToonzEvent(TMouseEvent &toonzEvent, QMouseEvent *event,
int widgetHeight, double pressure, int devPixRatio) {
toonzEvent.m_pos = TPointD(event->pos().x() * devPixRatio,
widgetHeight - 1 - event->pos().y() * devPixRatio);
toonzEvent.m_mousePos = event->pos();
toonzEvent.m_pressure = 1.0;
toonzEvent.setModifiers(event->modifiers() & Qt::ShiftModifier,
event->modifiers() & Qt::AltModifier,
event->modifiers() & Qt::ControlModifier);
toonzEvent.m_buttons = event->buttons();
toonzEvent.m_button = event->button();
toonzEvent.m_isTablet = false;
}
//-----------------------------------------------------------------------------
void initToonzEvent(TMouseEvent &toonzEvent, QTabletEvent *event,
int widgetHeight, double pressure, int devPixRatio,
bool isHighFrequent = false) {
toonzEvent.m_pos = TPointD(
event->posF().x() * (float)devPixRatio,
(float)widgetHeight - 1.0f - event->posF().y() * (float)devPixRatio);
toonzEvent.m_mousePos = event->posF();
toonzEvent.m_pressure = pressure;
toonzEvent.setModifiers(event->modifiers() & Qt::ShiftModifier,
event->modifiers() & Qt::AltModifier,
event->modifiers() & Qt::ControlModifier);
toonzEvent.m_buttons = event->buttons();
toonzEvent.m_button = event->button();
toonzEvent.m_isTablet = true;
toonzEvent.m_isHighFrequent = isHighFrequent;
// this delays autosave during stylus button press until after the next
// brush stroke - this minimizes problems from interruptions to tablet input
TApp::instance()->getCurrentTool()->setToolBusy(true);
}
//-----------------------------------------------------------------------------
void initToonzEvent(TMouseEvent &toonzEvent, QKeyEvent *event) {
toonzEvent.m_pos = TPointD();
toonzEvent.m_mousePos = QPointF();
toonzEvent.m_pressure = 0;
toonzEvent.setModifiers(event->modifiers() & Qt::ShiftModifier,
event->modifiers() & Qt::AltModifier,
event->modifiers() & Qt::ControlModifier);
toonzEvent.m_buttons = Qt::NoButton;
toonzEvent.m_button = Qt::NoButton;
toonzEvent.m_isTablet = false;
}
//-----------------------------------------------------------------------------
//====================================
// SceneViewerShortcutReceiver
//------------------------------------
class SceneViewerShortcutReceiver {
SceneViewer *m_viewer;
public:
SceneViewerShortcutReceiver(SceneViewer *viewer) : m_viewer(viewer) {}
~SceneViewerShortcutReceiver() {}
bool exec(QKeyEvent *event) {
CommandManager *cManager = CommandManager::instance();
if (event->key() == cManager->getKeyFromId(MI_RegeneratePreview)) {
m_viewer->regeneratePreview();
return true;
}
if (event->key() == cManager->getKeyFromId(MI_RegenerateFramePr)) {
m_viewer->regeneratePreviewFrame();
return true;
}
if (event->key() == cManager->getKeyFromId(MI_SavePreviewedFrames)) {
Previewer::instance(m_viewer->getPreviewMode() ==
SceneViewer::SUBCAMERA_PREVIEW)
->saveRenderedFrames();
return true;
}
return false;
}
};
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
using namespace DVGui;
void SceneViewer::onButtonPressed(FlipConsole::EGadget button) {
if (m_freezedStatus != NO_FREEZED) return;
switch (button) {
case FlipConsole::eSaveImg: {
if (m_previewMode == NO_PREVIEW) {
DVGui::warning(QObject::tr(
"It is not possible to save images in camera stand view."));
return;
}
TApp *app = TApp::instance();
int row = app->getCurrentFrame()->getFrame();
Previewer *previewer =
Previewer::instance(m_previewMode == SUBCAMERA_PREVIEW);
if (!previewer->isFrameReady(row)) {
DVGui::warning(QObject::tr("The preview images are not ready yet."));
return;
}
TRasterP ras =
previewer->getRaster(row, m_visualSettings.m_recomputeIfNeeded);
TImageCache::instance()->add(QString("TnzCompareImg"),
TRasterImageP(ras->clone()));
break;
}
case FlipConsole::eSave:
Previewer::instance(m_previewMode == SUBCAMERA_PREVIEW)
->saveRenderedFrames();
break;
case FlipConsole::eHisto: {
QAction *action = CommandManager::instance()->getAction(MI_ViewerHistogram);
action->trigger();
break;
}
case FlipConsole::eDefineSubCamera:
m_editPreviewSubCamera = !m_editPreviewSubCamera;
update();
break;
// open locator. Create one for the first time
case FlipConsole::eLocator:
if (!m_locator) m_locator = new LocatorPopup(this);
m_locator->show();
m_locator->raise();
m_locator->activateWindow();
break;
case FlipConsole::eZoomIn:
zoomIn();
break;
case FlipConsole::eZoomOut:
zoomOut();
break;
case FlipConsole::eFlipHorizontal:
flipX();
break;
case FlipConsole::eFlipVertical:
flipY();
break;
case FlipConsole::eResetView:
resetSceneViewer();
break;
default:
break;
}
}
//-----------------------------------------------------------------------------
// when using tablet on OSX, use tabletEvent instead of all the mouse-related
// events
// on Windows or on other OS, handle only left-button case
void SceneViewer::tabletEvent(QTabletEvent *e) {
if (m_freezedStatus != NO_FREEZED) return;
m_tabletEvent = true;
#if defined(LINUX) || defined(FREEBSD)
// For Linux, ignore pressure when not actively pressing
// Means we are hovering
if (m_tabletState != None)
#endif
m_pressure = e->pressure();
m_tabletMove = false;
// Management of the Eraser pointer
ToolHandle *toolHandle = TApp::instance()->getCurrentTool();
if (e->pointerType() == QTabletEvent::Eraser) {
if (!m_eraserPointerOn) {
if (toolHandle->getTool()->getName() != T_Eraser) {
// Save the current tool
m_backupTool = QString::fromStdString(toolHandle->getTool()->getName());
// Switch to the Eraser tool
toolHandle->setTool(T_Eraser);
m_eraserPointerOn = true;
}
}
} else {
if (m_eraserPointerOn) {
// Restore the previous tool
toolHandle->setTool(m_backupTool);
m_eraserPointerOn = false;
}
}
switch (e->type()) {
case QEvent::TabletPress: {
#ifdef MACOSX
// In OSX tablet action may cause only tabletEvent, not followed by
// mousePressEvent.
// So call onPress here in order to enable processing.
if (e->button() == Qt::LeftButton) m_tabletState = Touched;
TMouseEvent mouseEvent;
initToonzEvent(mouseEvent, e, height(), m_pressure, getDevPixRatio());
onPress(mouseEvent);
// create context menu on right click here
if (e->button() == Qt::RightButton) {
m_mouseButton = Qt::NoButton;
onContextMenu(e->pos(), e->globalPos());
}
#else
// for Windows, use tabletEvent only for the left Button
// (regular pen touch), because
// the current Qt seems to fail to catch the Wacom's button binding properly
// with QTabletEvent->button() when pressing middle or right button.
// So, in such case set m_tabletEvent = FALSE and let the mousePressEvent to
// work.
if (e->button() == Qt::LeftButton) {
// Process the 1st tabletPress encountered and ignore back-to-back
// tabletPress events. Treat it as if it happened so a following
// mousePressEvent gets ignored
if (m_tabletState == Released || m_tabletState == None) {
TMouseEvent mouseEvent;
initToonzEvent(mouseEvent, e, height(), m_pressure, getDevPixRatio());
m_tabletState = Touched;
onPress(mouseEvent);
} else if (m_tabletState == Touched) {
m_tabletState = StartStroke;
}
} else
m_tabletEvent = false;
#endif
#if defined(LINUX) || defined(FREEBSD)
// for Linux, create context menu on right click here.
// could possibly merge with OSX code above
if (e->button() == Qt::RightButton) {
m_mouseButton = Qt::NoButton;
onContextMenu(e->pos(), e->globalPos());
}
#endif
} break;
case QEvent::TabletRelease: {
#ifdef MACOSX
if (m_tabletState == StartStroke || m_tabletState == OnStroke)
m_tabletState = Released;
TMouseEvent mouseEvent;
initToonzEvent(mouseEvent, e, height(), m_pressure, getDevPixRatio());
onRelease(mouseEvent);
if (TApp::instance()->getCurrentTool()->isToolBusy())
TApp::instance()->getCurrentTool()->setToolBusy(false);
#else
if (m_tabletState == StartStroke || m_tabletState == OnStroke) {
m_tabletState = Released;
TMouseEvent mouseEvent;
initToonzEvent(mouseEvent, e, height(), m_pressure, getDevPixRatio());
onRelease(mouseEvent);
} else
m_tabletEvent = false;
#endif
} break;
case QEvent::TabletMove: {
#ifdef MACOSX
// for now OSX seems to fail to call enter/leaveEvent properly while
// the tablet is floating
bool isHoveringInsideViewer =
!rect().marginsRemoved(QMargins(5, 5, 5, 5)).contains(e->pos());
// call the fake enter event
if (isHoveringInsideViewer) onEnter();
#elif defined(_WIN32)
// for Windowsm, use tabletEvent only for the left Button
if (m_tabletState != StartStroke && m_tabletState != OnStroke) {
m_tabletEvent = false;
break;
}
#endif
QPointF curPos = e->posF() * getDevPixRatio();
#if defined(_WIN32) && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// Use the application attribute Qt::AA_CompressTabletEvents instead of the
// delay timer
// 21/4/2021 High frequent tablet event caused slowness when deforming with
// Raster Selection Tool. So I re-introduced the delay timer to make
// necessary interval for it. Deformation will be processed at interval of
// 20msec. (See RasterSelectionTool::leftButtonDrag())
if (curPos != m_lastMousePos) {
TMouseEvent mouseEvent;
initToonzEvent(mouseEvent, e, height(), m_pressure, getDevPixRatio(),
m_isBusyOnTabletMove);
if (!m_isBusyOnTabletMove) {
m_isBusyOnTabletMove = true;
QTimer::singleShot(20, this, SLOT(releaseBusyOnTabletMove()));
}
#else
// It seems that the tabletEvent is called more often than mouseMoveEvent.
// So I fire the interval timer in order to limit the following process
// to be called in 50fps in maximum.
if (curPos != m_lastMousePos && !m_isBusyOnTabletMove) {
#ifndef LINUX
m_isBusyOnTabletMove = true;
#endif
TMouseEvent mouseEvent;
initToonzEvent(mouseEvent, e, height(), m_pressure, getDevPixRatio());
QTimer::singleShot(20, this, SLOT(releaseBusyOnTabletMove()));
#endif
// cancel stroke to prevent drawing while floating
// 23/1/2018 There is a case that the pressure becomes zero at the start
// and the end of stroke. For such case, stroke should not be cancelled.
// So, added a process to finish stroke here instead of cancelling.
// This will be called only if the state is OnStroke so at the start
// of the stroke this condition will be passed.
if (m_tabletState == OnStroke && m_pressure == 0.0) {
m_tabletState = Released;
mouseEvent.m_button = Qt::LeftButton;
onRelease(mouseEvent);
} else {
m_tabletMove = true;
onMove(mouseEvent); // m_tabletState is set to OnStrole here
}
}
#ifdef MACOSX
// call the fake leave event if the pen is hovering the viewer edge
if (!isHoveringInsideViewer) onLeave();
#endif
} break;
default:
break;
}
}
//-----------------------------------------------------------------------------
void SceneViewer::leaveEvent(QEvent *) { onLeave(); }
//-----------------------------------------------------------------------------
void SceneViewer::onLeave() {
if (!m_isMouseEntered) return;
m_isMouseEntered = false;
if (m_freezedStatus != NO_FREEZED) return;
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (tool && tool->isEnabled()) tool->onLeave();
// force reset the flipping of shift & trace
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked())
TTool::getTool("T_ShiftTrace", TTool::ToonzImage)->onLeave();
update();
}
//-----------------------------------------------------------------------------
void SceneViewer::enterEvent(QEvent *) { onEnter(); }
//-----------------------------------------------------------------------------
void SceneViewer::onEnter() {
if (m_isMouseEntered) return;
m_isMouseEntered = true;
TApp *app = TApp::instance();
app->setActiveViewer(this);
TTool *tool = app->getCurrentTool()->getTool();
TXshLevel *level = app->getCurrentLevel()->getLevel();
if (level && level->getSimpleLevel())
m_dpiScale =
getCurrentDpiScale(level->getSimpleLevel(), tool->getCurrentFid());
else
m_dpiScale = TPointD(1, 1);
if (m_freezedStatus != NO_FREEZED) return;
invalidateToolStatus();
if (tool && tool->isEnabled()) {
tool->setViewer(this);
tool->updateMatrix();
tool->onEnter();
}
// grab the focus, unless a line-edit is focused currently
bool shouldSetFocus = true;
QWidget *focusWidget = qApp->focusWidget();
if (focusWidget) {
QLineEdit *lineEdit = dynamic_cast<QLineEdit *>(focusWidget);
if (lineEdit) {
shouldSetFocus = false;
}
}
if (shouldSetFocus) {
setFocus();
}
update();
}
//-----------------------------------------------------------------------------
void SceneViewer::mouseMoveEvent(QMouseEvent *event) {
// if this is called just after tabletEvent, skip the execution
if (m_tabletEvent) return;
// and touchscreens but not touchpads...
if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen) {
return;
}
// Strangely, mouseMoveEvent seems to be called once just after releasing
// tablet. This condition avoids to proceed further in such case.
if (event->buttons() != Qt::NoButton && m_mouseButton == Qt::NoButton) return;
// there are three cases to come here :
// 1. on mouse is moved (no tablet is used)
// 2. on tablet is moved, with middle or right button is pressed
// 3. on tablet is moved, BUT tabletEvent was not called
// 4. on tablet is moved without pen touching (i.e. floating move)
// the case 3 sometimes occurs on OSX. (reporteed in QTBUG-26532 for Qt4, but
// not confirmed in Qt5)
// the case 4 can be removed once start using Qt5.9 and call
// setTabletTracking(true).
TMouseEvent mouseEvent;
initToonzEvent(mouseEvent, event, height(), 1.0, getDevPixRatio());
onMove(mouseEvent);
}
//-----------------------------------------------------------------------------
void SceneViewer::onMove(const TMouseEvent &event) {
if (m_freezedStatus != NO_FREEZED) return;
// in case mouseReleaseEvent is not called, finish the action for the previous
// button first.
if (m_mouseButton != Qt::NoButton && event.m_buttons == Qt::NoButton) {
TMouseEvent preEvent = event;
preEvent.m_button = m_mouseButton;
onRelease(preEvent);
}
int devPixRatio = getDevPixRatio();
QPointF curPos = event.mousePos() * devPixRatio;
bool cursorSet = false;
m_lastMousePos = curPos;
if (event.buttons() == Qt::LeftButton && m_mouseRotating > 0) {
if (m_mouseRotating == 1) {
TPointD pos = winToWorld(event.mousePos() * getDevPixRatio());
;
m_mouseRotating = 2;
m_angle = 0.0;
m_dragging = true;
m_oldPos = pos;
m_oldMousePos = event.m_pos;
m_sw.start(true);
return;
}
// panning
mouseRotate(event);
return;
} else if (event.buttons() == Qt::LeftButton && m_mouseZooming > 0) {
if (m_mouseZooming == 1) {
m_mouseZooming = 2;
m_dragging = true;
int v = 1;
if (event.isAltPressed()) v = -1;
m_oldY = event.m_pos.y;
// m_center = getViewer()->winToWorld(e.m_pos);
m_center = TPointD(event.m_pos.x, event.m_pos.y);
m_factor = 1;
return;
}
// panning
mouseZoom(event);
return;
} else if (event.buttons() == Qt::LeftButton && m_mousePanning > 0) {
if (m_mousePanning == 1) {
m_mousePanning = 2;
m_dragging = true;
m_oldPos = event.m_pos;
m_sw.start(true);
return;
}
// panning
mousePan(event);
return;
}
if (m_mouseZooming > 0) {
setToolCursor(this, ToolCursor::ZoomCursor);
return;
}
if (m_mouseRotating > 0) {
setToolCursor(this, ToolCursor::RotateCursor);
return;
}
if (m_mousePanning > 0) {
setToolCursor(this, ToolCursor::PanCursor);
return;
}
if (m_editPreviewSubCamera) {
if (!PreviewSubCameraManager::instance()->mouseMoveEvent(this, event)) {
if (m_tabletEvent && m_tabletState == StartStroke && m_tabletMove) {
m_tabletState = OnStroke;
}
return;
}
}
// if the "compare with snapshot" mode is activated, change the mouse cursor
// on the border handle
if (m_visualSettings.m_doCompare && isPreviewEnabled()) {
if (std::abs(curPos.x() - width() * m_compareSettings.m_compareX) < 20) {
cursorSet = true;
setToolCursor(this, ToolCursor::ScaleHCursor);
} else if (std::abs((height() - curPos.y()) -
height() * m_compareSettings.m_compareY) < 20) {
cursorSet = true;
setToolCursor(this, ToolCursor::ScaleVCursor);
}
// control of the border handle when the "compare with snapshot" mode is
// activated
if (m_compareSettings.m_dragCompareX || m_compareSettings.m_dragCompareY) {
if (m_compareSettings.m_dragCompareX)
m_compareSettings.m_compareX +=
((double)(curPos.x() - m_pos.x())) / width();
else if (m_compareSettings.m_dragCompareY)
m_compareSettings.m_compareY -=
((double)(curPos.y() - m_pos.y())) / height();
m_compareSettings.m_compareX =
tcrop(m_compareSettings.m_compareX, 0.0, 1.0);
m_compareSettings.m_compareY =
tcrop(m_compareSettings.m_compareY, 0.0, 1.0);
update();
m_pos = curPos;
}
} else if (m_mouseButton == Qt::NoButton || m_mouseButton == Qt::LeftButton) {
if (is3DView()) {
if (m_current3DDevice != NONE && m_mouseButton == Qt::LeftButton)
return;
else if (m_mouseButton == Qt::NoButton) {
TRectD rect = TRectD(TPointD(m_topRasterPos.x, m_topRasterPos.y),
TDimensionD(20 * devPixRatio, 20 * devPixRatio));
if (rect.contains(TPointD(curPos.x(), curPos.y())))
m_current3DDevice = TOP_3D;
else {
rect = TRectD(TPointD(m_sideRasterPos.x, m_sideRasterPos.y),
TDimensionD(20 * devPixRatio, 20 * devPixRatio));
if (rect.contains(TPointD(curPos.x(), curPos.y()))) {
if (m_phi3D > 0)
m_current3DDevice = SIDE_RIGHT_3D;
else
m_current3DDevice = SIDE_LEFT_3D;
} else
m_current3DDevice = NONE;
}
if (m_current3DDevice != NONE) {
cursorSet = true;
setToolCursor(this, ToolCursor::CURSOR_ARROW);
}
}
}
// if the middle mouse button is pressed while dragging, then do panning
if (event.buttons() & Qt::MidButton) {
// panning
QPointF p = curPos - m_pos;
if (m_pos == QPointF() && p.manhattanLength() > 300) return;
panQt(curPos - m_pos);
m_pos = curPos;
return;
}
TPointD pickPos = winToWorld(curPos);
// grab screen picking for stop motion live view zoom
if ((event.buttons() & Qt::LeftButton) &&
StopMotion::instance()->isPickLiveViewZoom()) {
StopMotion::instance()->makeZoomPoint(pickPos);
return;
}
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (!tool || !tool->isEnabled()) {
m_tabletEvent = false;
return;
}
tool->setViewer(this);
TPointD worldPos = winToWorld(curPos);
TPointD pos = tool->getMatrix().inv() * worldPos;
if (m_locator) {
m_locator->onChangeViewAff(worldPos);
}
TObjectHandle *objHandle = TApp::instance()->getCurrentObject();
if (tool->getToolType() & TTool::LevelTool && !objHandle->isSpline()) {
pos.x /= m_dpiScale.x;
pos.y /= m_dpiScale.y;
}
// qDebug() << "mouseMoveEvent. " << (m_tabletEvent?"tablet":"mouse")
// << " pressure=" << m_pressure << " mouseButton=" << m_mouseButton
// << " buttonClicked=" << m_buttonClicked;
// separate tablet events from mouse events
if (m_tabletEvent &&
(m_tabletState == OnStroke || m_tabletState == StartStroke) &&
m_tabletMove) {
if (m_toolSwitched) tool->leftButtonDown(pos, event);
tool->leftButtonDrag(pos, event);
m_tabletState = OnStroke;
}
else if (m_mouseButton == Qt::LeftButton) {
// sometimes the mousePressedEvent is postponed to a wrong mouse move
// event!
// if (m_buttonClicked && !m_toolSwitched) tool->leftButtonDrag(pos,
// event);
if (m_toolSwitched) tool->leftButtonDown(pos, event);
tool->leftButtonDrag(pos, event);
m_mouseState = OnStroke;
} else if (m_pressure == 0.0) {
tool->mouseMove(pos, event);
}
if (!cursorSet) setToolCursor(this, tool->getCursorId());
if (StopMotion::instance()->isPickLiveViewZoom())
setToolCursor(this, ToolCursor::ZoomCursor);
m_pos = curPos;
m_tabletMove = false;
m_toolSwitched = false;
} else if (m_mouseButton == Qt::MidButton) {
if ((event.buttons() & Qt::MidButton) == 0) m_mouseButton = Qt::NoButton;
// scrub with shift and middle click
else if (event.isShiftPressed() && event.isCtrlPressed()) {
if (curPos.x() > m_pos.x()) {
CommandManager::instance()->execute("MI_NextFrame");
} else if (curPos.x() < m_pos.x()) {
CommandManager::instance()->execute("MI_PrevFrame");
}
} else {
// panning
panQt(curPos - m_pos);
}
m_pos = curPos;
return;
}
}
//-----------------------------------------------------------------------------
void SceneViewer::mousePressEvent(QMouseEvent *event) {
// source can be useful for detecting touch vs mouse,
// but has reports of being unreliable on mac
// so m_gestureActive is the marker for rejecting touch events
int source = event->source();
// if this is called just after tabletEvent, skip the execution
if (m_tabletEvent) return;
// and touchscreens but not touchpads...
if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen) {
return;
}
// For now OSX has a critical problem that mousePressEvent is called just
// after releasing the stylus, which causes the irregular stroke while
// float-moving.
// In such case resetTabletStatus() will be called on
// QEvent::TabletLeaveProximity
// and will cancel the onPress operation called here.
TMouseEvent mouseEvent;
m_mouseState = Touched;
initToonzEvent(mouseEvent, event, height(), 1.0, getDevPixRatio());
onPress(mouseEvent);
}
//-----------------------------------------------------------------------------
void SceneViewer::onPress(const TMouseEvent &event) {
m_dragging = true;
if (m_mousePanning > 0 || m_mouseRotating > 0 || m_mouseZooming > 0) {
m_pos = event.mousePos() * getDevPixRatio();
m_mouseButton = event.button();
m_buttonClicked = true;
if (m_tabletEvent && m_tabletState == Touched) {
m_tabletState = StartStroke;
} else if (m_mouseButton == Qt::LeftButton) {
m_mouseState = StartStroke;
}
return;
}
if (m_mouseButton != Qt::NoButton) {
if (event.m_isTablet && m_mouseState != None) {
// qDebug() << "[onPress] Switched from MousePress to TabletPress";
// TabletPress came immediately after MousePress. Let's switch to
// tabletPress but not end the prior action or it leaves an extra
// stroke
m_mouseState = None;
m_buttonClicked = false;
} else {
// if some button is pressed while another button being active,
// finish the action for the previous button first.
TMouseEvent preEvent = event;
preEvent.m_button = m_mouseButton;
onRelease(preEvent);
}
}
// evita i press ripetuti
if (m_buttonClicked) return;
m_buttonClicked = true;
m_toolSwitched = false;
if (m_freezedStatus != NO_FREEZED) return;
m_pos = event.mousePos() * getDevPixRatio();
m_mouseButton = event.button();
// when using tablet, avoid unexpected drawing behavior occurs when
// middle-click
if (m_tabletEvent && m_mouseButton == Qt::MidButton &&
event.isLeftButtonPressed()) {
return;
}
if (is3DView() && m_current3DDevice != NONE &&
m_mouseButton == Qt::LeftButton)
return;
else if (m_mouseButton == Qt::LeftButton && m_editPreviewSubCamera) {
if (!PreviewSubCameraManager::instance()->mousePressEvent(this, event)) {
if (m_tabletEvent && m_tabletState == Touched)
m_tabletState = StartStroke;
return;
}
} else if (m_mouseButton == Qt::LeftButton && m_visualSettings.m_doCompare) {
if (std::abs(m_pos.x() - width() * m_compareSettings.m_compareX) < 20) {
m_compareSettings.m_dragCompareX = true;
m_compareSettings.m_dragCompareY = false;
m_compareSettings.m_compareY = ImagePainter::DefaultCompareValue;
update();
m_tabletEvent = false;
m_tabletState = None;
return;
} else if (std::abs((height() - m_pos.y()) -
height() * m_compareSettings.m_compareY) < 20) {
m_compareSettings.m_dragCompareY = true;
m_compareSettings.m_dragCompareX = false;
m_compareSettings.m_compareX = ImagePainter::DefaultCompareValue;
update();
m_tabletEvent = false;
m_tabletState = None;
return;
} else
m_compareSettings.m_dragCompareX = m_compareSettings.m_dragCompareY =
false;
}
if (m_freezedStatus != NO_FREEZED) return;
TPointD pickPos = winToWorld(m_pos);
// grab screen picking for stop motion live view zoom
if (StopMotion::instance()->isPickLiveViewZoom()) {
StopMotion::instance()->makeZoomPoint(pickPos);
return;
}
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (!tool || !tool->isEnabled()) {
m_tabletEvent = false;
m_tabletState = None;
return;
}
tool->setViewer(this);
if (m_mouseButton == Qt::LeftButton && tool->preLeftButtonDown()) {
tool = TApp::instance()->getCurrentTool()->getTool();
if (!tool || !tool->isEnabled()) {
m_tabletEvent = false;
m_tabletState = None;
return;
}
tool->setViewer(this);
}
// if(!m_tabletEvent) qDebug() << "-----------------MOUSE PRESS 'PURO'.
// POSSIBILE EMBOLO";
TPointD pos = tool->getMatrix().inv() * winToWorld(m_pos);
TObjectHandle *objHandle = TApp::instance()->getCurrentObject();
if (tool->getToolType() & TTool::LevelTool && !objHandle->isSpline()) {
pos.x /= m_dpiScale.x;
pos.y /= m_dpiScale.y;
}
// separate tablet and mouse events
if (m_tabletEvent && m_tabletState == Touched) {
TApp::instance()->getCurrentTool()->setToolBusy(true);
m_tabletState = StartStroke;
tool->leftButtonDown(pos, event);
} else if (m_mouseButton == Qt::LeftButton) {
m_mouseState = StartStroke;
TApp::instance()->getCurrentTool()->setToolBusy(true);
tool->leftButtonDown(pos, event);
}
if (m_mouseButton == Qt::RightButton) tool->rightButtonDown(pos, event);
}
//-----------------------------------------------------------------------------
void SceneViewer::mouseReleaseEvent(QMouseEvent *event) {
// if this is called just after tabletEvent, skip the execution
if (m_tabletEvent) {
// mouseRelease should not clear flag if we are starting or in middle of
// stroke
// initiated by tableEvent. All other cases, it's ok to clear flag
if (m_tabletState == Released || m_tabletState == None)
m_tabletEvent = false;
return;
}
// for touchscreens but not touchpads...
if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen) {
m_gestureActive = false;
m_rotating = false;
m_zooming = false;
m_panning = false;
m_scaleFactor = 0.0;
m_rotationDelta = 0.0;
return;
}
TMouseEvent mouseEvent;
if (m_mouseState != None) m_mouseState = Released;
initToonzEvent(mouseEvent, event, height(), 1.0, getDevPixRatio());
onRelease(mouseEvent);
}
//-----------------------------------------------------------------------------
void SceneViewer::onRelease(const TMouseEvent &event) {
m_dragging = false;
if (m_mousePanning > 0 || m_mouseRotating > 0 || m_mouseZooming > 0) {
if (m_resetOnRelease) {
m_mousePanning = 0;
m_mouseRotating = 0;
m_mouseZooming = 0;
if (m_keyAction) {
m_keyAction->setEnabled(true);
m_keyAction = 0;
invalidateToolStatus();
}
m_resetOnRelease = false;
} else if (m_mousePanning > 0)
m_mousePanning = 1;
else if (m_mouseRotating > 0)
m_mouseRotating = 1;
else if (m_mouseZooming > 0)
m_mouseZooming = 1;
m_sw.stop();
invalidateAll();
GLInvalidateAll();
invalidateToolStatus();
m_buttonClicked = false;
doQuit();
return;
}
// evita i release ripetuti
if (!m_buttonClicked) {
doQuit();
return;
}
m_buttonClicked = false;
// tool is declared up here to prevent an error with jumping to goto before
// all variables are instantiated.
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
bool canonJumpToQuit = false;
// Stop if we're picking live view for StopMotion
if (StopMotion::instance()->isPickLiveViewZoom()) {
canonJumpToQuit = true;
}
if (canonJumpToQuit) {
doQuit();
return;
}
if (!tool || !tool->isEnabled()) {
if (!m_toolDisableReason.isEmpty() && m_mouseButton == Qt::LeftButton &&
!m_editPreviewSubCamera)
DVGui::warning(m_toolDisableReason);
}
if (m_freezedStatus != NO_FREEZED) return;
if (m_mouseButton != event.button()) return;
if (m_current3DDevice != NONE) {
m_mouseButton = Qt::NoButton;
m_tabletEvent = false;
m_pressure = 0;
if (m_current3DDevice == SIDE_LEFT_3D)
set3DLeftSideView();
else if (m_current3DDevice == SIDE_RIGHT_3D)
set3DRightSideView();
else if (m_current3DDevice == TOP_3D)
set3DTopView();
return;
}
if (m_mouseButton == Qt::LeftButton && m_editPreviewSubCamera) {
if (!PreviewSubCameraManager::instance()->mouseReleaseEvent(this)) {
doQuit();
return;
}
}
if (m_compareSettings.m_dragCompareX || m_compareSettings.m_dragCompareY) {
m_compareSettings.m_dragCompareX = m_compareSettings.m_dragCompareY = false;
doQuit();
return;
}
m_pos = QPointF();
if (!tool || !tool->isEnabled()) {
doQuit();
return;
}
tool->setViewer(this);
{
TPointD pos = tool->getMatrix().inv() * winToWorld(m_lastMousePos);
TObjectHandle *objHandle = TApp::instance()->getCurrentObject();
if (tool->getToolType() & TTool::LevelTool && !objHandle->isSpline()) {
pos.x /= m_dpiScale.x;
pos.y /= m_dpiScale.y;
}
if (m_mouseButton == Qt::LeftButton || m_tabletState == Released) {
if (!m_toolSwitched) tool->leftButtonUp(pos, event);
TApp::instance()->getCurrentTool()->setToolBusy(false);
}
}
doQuit();
}
void SceneViewer::doQuit() {
m_mouseButton = Qt::NoButton;
// Leave m_tabletEvent as-is in order to check whether the onRelease is called
// from tabletEvent or not in mouseReleaseEvent.
if (m_tabletState == Released) // only clear if tabletRelease event
m_tabletEvent = false;
// If m_tabletState is "Touched", we've been called by tabletPress event.
// Don't clear it out table state so the tablePress event will process
// correctly.
if (m_tabletState != Touched) m_tabletState = None;
m_mouseState = None;
m_tabletMove = false;
m_pressure = 0;
}
//-----------------------------------------------------------------------------
// on OSX, there is a critical bug that SceneViewer::mousePressEvent is called
// when leaving the stylus and it causes unwanted stroke drawn while
// hover-moving of the pen.
// When QEvent::TabletLeaveProximity is detected, call this function
// in order to force initializing such irregular mouse press.
// NOTE: For now QEvent::TabletLeaveProximity is NOT detected on Windows. See
// QTBUG-53628.
void SceneViewer::resetTabletStatus() {
if (!m_buttonClicked) return;
m_mouseButton = Qt::NoButton;
m_tabletEvent = false;
m_tabletState = None;
m_tabletMove = false;
m_pressure = 0;
m_buttonClicked = false;
if (TApp::instance()->getCurrentTool()->isToolBusy())
TApp::instance()->getCurrentTool()->setToolBusy(false);
}
//-----------------------------------------------------------------------------
void SceneViewer::wheelEvent(QWheelEvent *event) {
if (m_freezedStatus != NO_FREEZED) return;
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) {
// scrub with mouse wheel
if ((event->modifiers() & Qt::AltModifier) &&
(event->modifiers() & Qt::ShiftModifier) &&
(event->modifiers() & Qt::ControlModifier)) {
if (delta < 0) {
CommandManager::instance()->execute("MI_NextStep");
} else if (delta > 0) {
CommandManager::instance()->execute("MI_PrevStep");
}
} else if ((event->modifiers() & Qt::ControlModifier) &&
(event->modifiers() & Qt::ShiftModifier)) {
if (delta < 0) {
CommandManager::instance()->execute("MI_NextFrame");
} else if (delta > 0) {
CommandManager::instance()->execute("MI_PrevFrame");
}
} else if ((event->modifiers() & Qt::ShiftModifier) &&
(event->modifiers() & Qt::AltModifier)) {
if (delta < 0) {
CommandManager::instance()->execute("MI_NextDrawing");
} else if (delta > 0) {
CommandManager::instance()->execute("MI_PrevDrawing");
}
}
// Mouse wheel zoom interfered with touchpad panning (touch enabled)
// Now if touch is enabled, touchpads ignore the mouse wheel zoom
else if ((m_gestureActive == true &&
m_touchDevice == QTouchDevice::TouchScreen) ||
m_gestureActive == false) {
zoomQt(event->pos() * getDevPixRatio(), exp(0.001 * delta));
m_panning = false;
}
}
event->accept();
}
//-----------------------------------------------------------------------------
void SceneViewer::gestureEvent(QGestureEvent *e) {
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<QPinchGesture *>(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_rotating = false;
m_zooming = false;
m_scaleFactor = 0.0;
m_rotationDelta = 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_rotating && !m_zooming) {
double delta = scaleFactor - 1;
m_scaleFactor += delta;
if (m_scaleFactor > .2 || m_scaleFactor < -.2) {
m_zooming = true;
}
}
if (m_zooming) {
zoomQt(firstCenter * getDevPixRatio(), scaleFactor);
m_panning = false;
}
m_gestureActive = true;
}
if (changeFlags & QPinchGesture::RotationAngleChanged) {
qreal rotationDelta =
gesture->rotationAngle() - gesture->lastRotationAngle();
if (m_isFlippedX != m_isFlippedY) rotationDelta = -rotationDelta;
TAffine aff = getViewMatrix().inv();
TPointD center = aff * TPointD(0, 0);
if (!m_rotating && !m_zooming) {
m_rotationDelta += rotationDelta;
double absDelta = std::abs(m_rotationDelta);
if (absDelta >= 10) {
m_rotating = true;
}
}
if (m_rotating) {
rotate(center, -rotationDelta);
}
}
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 SceneViewer::touchEvent(QTouchEvent *e, int type) {
if (type == QEvent::TouchBegin) {
if (m_tabletEvent) return;
m_touchActive = true;
m_firstPanPoint = e->touchPoints().at(0).pos();
m_undoPoint = m_firstPanPoint;
// 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_rotating) {
m_panning = true;
}
}
if (m_panning) {
QPointF centerDelta = (panPoint.pos() * getDevPixRatio()) -
(panPoint.lastPos() * getDevPixRatio());
panQt(centerDelta.toPoint());
}
} else if (e->touchPoints().count() == 3) {
QPointF newPoint = e->touchPoints().at(0).pos();
if (m_undoPoint.x() - newPoint.x() > 100) {
CommandManager::instance()->execute("MI_Undo");
m_undoPoint = newPoint;
}
if (m_undoPoint.x() - newPoint.x() < -100) {
CommandManager::instance()->execute("MI_Redo");
m_undoPoint = newPoint;
}
}
}
if (type == QEvent::TouchEnd || type == QEvent::TouchCancel) {
m_touchActive = false;
m_panning = false;
}
e->accept();
}
//-----------------------------------------------------------------------------
bool SceneViewer::event(QEvent *e) {
switch (e->type()) {
case QEvent::Enter:
case QEvent::Leave:
case QEvent::TabletPress:
case QEvent::TabletMove:
case QEvent::TabletRelease:
case QEvent::TouchBegin:
case QEvent::TouchEnd:
case QEvent::TouchCancel:
case QEvent::Gesture:
case QEvent::MouseButtonPress:
case QEvent::MouseMove:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::KeyPress:
case QEvent::KeyRelease:
ViewerEventLogManager::instance()->addEventMessage(e);
break;
default:
// qDebug() << "[enter] ************************** Event: "<< e;
break;
}
int key = 0;
if (e->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
key = keyEvent->key();
if ((m_stopMotion->isPickLiveViewZoom() ||
m_stopMotion->isZooming()) &&
(key == Qt::Key_Left || key == Qt::Key_Right || key == Qt::Key_Up ||
key == Qt::Key_Down || key == Qt::Key_2 || key == Qt::Key_4 ||
key == Qt::Key_6 || key == Qt::Key_8)) {
if (m_stopMotion->isLiveViewZoomReadyToPick()) {
if (key == Qt::Key_Left || key == Qt::Key_4) {
m_stopMotion->adjustLiveViewZoomPickPoint(-10, 0);
}
if (key == Qt::Key_Right || key == Qt::Key_6) {
m_stopMotion->adjustLiveViewZoomPickPoint(10, 0);
}
if (key == Qt::Key_Up || key == Qt::Key_8) {
m_stopMotion->adjustLiveViewZoomPickPoint(0, 10);
}
if (key == Qt::Key_Down || key == Qt::Key_2) {
m_stopMotion->adjustLiveViewZoomPickPoint(0, -10);
}
if (m_stopMotion->isZooming()) {
m_stopMotion->setZoomPoint();
}
}
m_stopMotion->calculateZoomPoint();
e->accept();
return true;
} else if (m_stopMotion->isPickLiveViewZoom() &&
(key == Qt::Key_Escape || key == Qt::Key_Enter ||
key == Qt::Key_Return)) {
m_stopMotion->toggleZoomPicking();
e->accept();
return true;
} else if (m_stopMotion->m_liveViewStatus == 2 &&
(key == Qt::Key_Enter || key == Qt::Key_Return)) {
m_stopMotion->captureImage();
e->accept();
return true;
}
}
if (e->type() == QEvent::Gesture &&
CommandManager::instance()
->getAction(MI_TouchGestureControl)
->isChecked()) {
gestureEvent(static_cast<QGestureEvent *>(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<QTouchEvent *>(e), e->type());
m_gestureActive = true;
return true;
}
bool isTyping = false;
bool toolEnabled = false;
bool toolDragging = false;
bool toolActive = false;
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (tool && tool->isEnabled() && tool->getName() == T_Type &&
tool->isActive()) {
isTyping = true;
}
if (tool && tool->isEnabled()) toolEnabled = true;
if (tool && tool->isActive()) toolActive = true;
if (tool && tool->isDragging()) toolDragging = true;
if (!isTyping && (e->type() == QEvent::ShortcutOverride ||
e->type() == QEvent::KeyPress)) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (!keyEvent->isAutoRepeat()) {
TApp::instance()->getCurrentTool()->storeTool();
}
std::string keyStr = QKeySequence(keyEvent->key() + keyEvent->modifiers())
.toString()
.toStdString();
QAction *action = CommandManager::instance()->getActionFromShortcut(keyStr);
std::string actionId = CommandManager::instance()->getIdFromAction(action);
if (actionId == T_Hand) {
if (m_mousePanning == 0) {
m_mousePanning = 1;
m_keyAction = action;
m_keyAction->setEnabled(false);
setToolCursor(this, ToolCursor::PanCursor);
}
e->accept();
return true;
} else if (actionId == T_Zoom) {
if (m_mouseZooming == 0) {
m_mouseZooming = 1;
m_keyAction = action;
m_keyAction->setEnabled(false);
setToolCursor(this, ToolCursor::ZoomCursor);
}
e->accept();
return true;
} else if (actionId == T_Rotate) {
if (m_mouseRotating == 0) {
m_mouseRotating = 1;
m_keyAction = action;
m_keyAction->setEnabled(false);
setToolCursor(this, ToolCursor::RotateCursor);
}
e->accept();
return true;
}
}
if (e->type() == QEvent::ShortcutOverride) {
if (tool && tool->isEnabled() && tool->getName() == T_Type &&
tool->isActive())
e->accept();
// for other tools, check if the pressed keys should be catched instead of
// triggering the shortcut command actions
else if (tool && tool->isEventAcceptable(e)) {
e->accept();
}
// if the Shift & Trace mode is active, then override F1, F2 and F3 key
// actions by flipping feature
else if (CommandManager::instance()
->getAction(MI_ShiftTrace)
->isChecked() &&
TTool::getTool("T_ShiftTrace", TTool::ToonzImage)
->isEventAcceptable(e)) {
e->accept();
} else if (m_keyAction)
e->accept();
// Disable keyboard shortcuts while the tool is busy with a mouse drag
// operation.
if (tool->isDragging()) {
e->accept();
}
return true;
}
if (!isTyping && e->type() == QEvent::KeyRelease) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (!keyEvent->isAutoRepeat() && !m_keyAction) {
QWidget *focusWidget = QApplication::focusWidget();
if (focusWidget == 0 ||
QString(focusWidget->metaObject()->className()) == "SceneViewer")
TApp::instance()->getCurrentTool()->restoreTool();
}
if (m_keyAction) {
if (keyEvent->isAutoRepeat()) {
e->accept();
return true;
} else {
m_mousePanning = 0;
m_mouseZooming = 0;
m_mouseRotating = 0;
m_keyAction->setEnabled(true);
m_keyAction = 0;
invalidateToolStatus();
e->accept();
return true;
}
}
}
// discard too frequent move events
static QTime clock;
if (e->type() == QEvent::MouseButtonPress)
clock.start();
else if (e->type() == QEvent::MouseMove) {
if (clock.isValid() && clock.elapsed() < 10) {
e->accept();
return true;
}
clock.start();
}
return QOpenGLWidget::event(e);
}
//-----------------------------------------------------------------------------
class ViewerZoomer final : public ImageUtils::ShortcutZoomer {
public:
ViewerZoomer(SceneViewer *parent) : ShortcutZoomer(parent) {}
bool zoom(bool zoomin, bool resetView) override {
SceneViewer *sceneViewer = static_cast<SceneViewer *>(getWidget());
resetView ? sceneViewer->resetSceneViewer()
: sceneViewer->zoomQt(zoomin, resetView);
return true;
}
bool fit() override {
static_cast<SceneViewer *>(getWidget())->fitToCamera();
return true;
}
bool setActualPixelSize() override {
static_cast<SceneViewer *>(getWidget())->setActualPixelSize();
return true;
}
bool setFlipX() override {
static_cast<SceneViewer *>(getWidget())->flipX();
return true;
}
bool setFlipY() override {
static_cast<SceneViewer *>(getWidget())->flipY();
return true;
}
bool resetZoom() override {
static_cast<SceneViewer *>(getWidget())->resetZoom();
return true;
}
bool resetRotation() override {
static_cast<SceneViewer *>(getWidget())->resetRotation();
return true;
}
bool resetPosition() override {
static_cast<SceneViewer *>(getWidget())->resetPosition();
return true;
}
bool toggleFullScreen(bool quit) override {
if (ImageUtils::FullScreenWidget *fsWidget =
dynamic_cast<ImageUtils::FullScreenWidget *>(
getWidget()->parentWidget()))
return fsWidget->toggleFullScreen(quit);
return false;
}
};
//-----------------------------------------------------------------------------
bool changeFrameSkippingHolds(QKeyEvent *e) {
if ((e->modifiers() & Qt::ShiftModifier) == 0 ||
(e->key() != Qt::Key_Down && e->key() != Qt::Key_Up))
return false;
TApp *app = TApp::instance();
TFrameHandle *fh = app->getCurrentFrame();
if (!fh->isEditingScene()) return false;
int row = fh->getFrame();
int col = app->getCurrentColumn()->getColumnIndex();
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
TXshCell cell = xsh->getCell(row, col);
if (e->key() == Qt::Key_Down) {
int r0, r1;
bool range = xsh->getCellRange(col, r0, r1);
if (cell.isEmpty()) {
if (range) {
while (row <= r1 && xsh->getCell(row, col).isEmpty()) row++;
if (xsh->getCell(row, col).isEmpty()) return false;
} else
return false;
} else {
while (row <= r1 && xsh->getCell(row, col) == cell) row++;
}
} else {
// Key_Up
while (row >= 0 && xsh->getCell(row, col) == cell) row--;
if (row < 0) return false;
cell = xsh->getCell(row, col);
while (row > 0 && xsh->getCell(row - 1, col) == cell) row--;
}
fh->setFrame(row);
return true;
}
//-----------------------------------------------------------------------------
void SceneViewer::keyPressEvent(QKeyEvent *event) {
if (m_freezedStatus != NO_FREEZED) return;
int key = event->key();
// resolving priority and tool-specific key events in this lambda
auto ret = [&]() -> bool {
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (!tool) return false;
bool isTextToolActive = tool->getName() == T_Type && tool->isActive();
if (!isTextToolActive) {
if (ViewerZoomer(this).exec(event)) return true;
if (SceneViewerShortcutReceiver(this).exec(event)) return true;
// If this object is child of Viewer or ComboViewer
// (m_isStyleShortcutSelective = true),
// then consider about shortcut for the current style selection.
if (m_isStyleShortcutSwitchable &&
Preferences::instance()->isUseNumpadForSwitchingStylesEnabled() &&
(event->modifiers() == Qt::NoModifier ||
event->modifiers() == Qt::ShiftModifier ||
event->modifiers() == Qt::KeypadModifier) &&
((Qt::Key_0 <= key && key <= Qt::Key_9) || key == Qt::Key_Tab ||
key == Qt::Key_Backtab)) {
// When the viewer is in full screen mode, directly call the style
// shortcut function since the viewer is retrieved from the parent
// panel.
if (parentWidget() &&
parentWidget()->windowState() & Qt::WindowFullScreen) {
StyleShortcutSwitchablePanel::onKeyPress(event);
return true;
}
event->ignore();
return true;
}
// pressing F1, F2 or F3 key will flip between corresponding ghost
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() &&
(Qt::Key_F1 <= key && key <= Qt::Key_F3)) {
OnionSkinMask osm =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
if (osm.getGhostFlipKey() != key) {
osm.appendGhostFlipKey(key);
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
}
return true;
}
}
if (!tool->isEnabled()) return false;
tool->setViewer(this);
if (key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt ||
key == Qt::Key_AltGr) {
// when the user presses shift / ctrl etc. some tools (eg pinch) must
// immediately change the shape of the cursor, without waiting for the
// next move
TMouseEvent toonzEvent;
initToonzEvent(toonzEvent, event);
toonzEvent.m_pos = TPointD(m_lastMousePos.x(),
(double)(height() - 1) - m_lastMousePos.y());
TPointD pos = tool->getMatrix().inv() * winToWorld(m_lastMousePos);
TObjectHandle *objHandle = TApp::instance()->getCurrentObject();
if (tool->getToolType() & TTool::LevelTool && !objHandle->isSpline()) {
pos.x /= m_dpiScale.x;
pos.y /= m_dpiScale.y;
}
tool->mouseMove(pos, toonzEvent);
setToolCursor(this, tool->getCursorId());
}
if (key == Qt::Key_Menu || key == Qt::Key_Meta) return false;
return tool->keyDown(event);
}();
if (!ret) {
if (changeFrameSkippingHolds(event)) return;
TFrameHandle *fh = TApp::instance()->getCurrentFrame();
int origFrame = fh->getFrame();
if (key == Qt::Key_Up || key == Qt::Key_Left)
fh->prevFrame();
else if (key == Qt::Key_Down || key == Qt::Key_Right) {
// If on a level frame pass the frame id after the last frame to allow
// creating a new frame with the down arrow key
TFrameId newId = 0;
if (Preferences::instance()->getDownArrowLevelStripNewFrame() &&
fh->getFrameType() == TFrameHandle::LevelFrame) {
TXshSimpleLevel *level =
TApp::instance()->getCurrentLevel()->getLevel()->getSimpleLevel();
if (level) {
std::vector<TFrameId> fids;
level->getFids(fids);
if (!fids.empty()) {
int frameCount = (int)fids.size();
newId = level->index2fid(frameCount);
}
}
}
fh->nextFrame(newId);
} else if (key == Qt::Key_Home)
fh->firstFrame();
else if (key == Qt::Key_End)
fh->lastFrame();
// Use arrow keys to shift the cell selection.
if (Preferences::instance()->isUseArrowKeyToShiftCellSelectionEnabled() &&
fh->getFrameType() != TFrameHandle::LevelFrame) {
TCellSelection *cellSel =
dynamic_cast<TCellSelection *>(TSelection::getCurrent());
if (cellSel && !cellSel->isEmpty()) {
int r0, c0, r1, c1;
cellSel->getSelectedCells(r0, c0, r1, c1);
int shiftFrame = fh->getFrame() - origFrame;
cellSel->selectCells(r0 + shiftFrame, c0, r1 + shiftFrame, c1);
TApp::instance()->getCurrentSelection()->notifySelectionChanged();
}
}
}
update();
// TODO: devo accettare l'evento?
}
//-----------------------------------------------------------------------------
void SceneViewer::keyReleaseEvent(QKeyEvent *event) {
if (m_freezedStatus != NO_FREEZED) return;
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (!tool || !tool->isEnabled()) return;
tool->setViewer(this);
int key = event->key();
if (key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt ||
key == Qt::Key_AltGr) {
// when the user presses shift / ctrl etc. some tools (eg pinch)
// must immediately change the shape of the cursor, without waiting for the
// next move
TMouseEvent toonzEvent;
initToonzEvent(toonzEvent, event);
toonzEvent.m_pos = TPointD(m_lastMousePos.x(),
(double)(height() - 1) - m_lastMousePos.y());
TPointD pos = tool->getMatrix().inv() * winToWorld(m_lastMousePos);
TObjectHandle *objHandle = TApp::instance()->getCurrentObject();
if (tool->getToolType() & TTool::LevelTool && !objHandle->isSpline()) {
pos.x /= m_dpiScale.x;
pos.y /= m_dpiScale.y;
}
tool->mouseMove(pos, toonzEvent);
setToolCursor(this, tool->getCursorId());
}
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() &&
(Qt::Key_F1 <= key && key <= Qt::Key_F3)) {
OnionSkinMask osm =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
osm.removeGhostFlipKey(key);
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
}
if (tool->getName() == T_Type)
event->accept();
else
event->ignore();
}
//-----------------------------------------------------------------------------
void SceneViewer::mouseDoubleClickEvent(QMouseEvent *event) {
if (m_gestureActive) {
fitToCamera();
m_gestureActive = false;
return;
}
if (m_freezedStatus != NO_FREEZED) return;
int frame = TApp::instance()->getCurrentFrame()->getFrame();
if (frame == -1) return;
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (!tool || !tool->isEnabled()) return;
TMouseEvent toonzEvent;
initToonzEvent(toonzEvent, event, height(), 1.0, getDevPixRatio());
TPointD pos =
tool->getMatrix().inv() * winToWorld(event->pos() * getDevPixRatio());
TObjectHandle *objHandle = TApp::instance()->getCurrentObject();
if (tool->getToolType() & TTool::LevelTool && !objHandle->isSpline()) {
pos.x /= m_dpiScale.x;
pos.y /= m_dpiScale.y;
}
if (event->button() == Qt::LeftButton)
tool->leftButtonDoubleClick(pos, toonzEvent);
}
//-----------------------------------------------------------------------------
using namespace ImageUtils;
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void SceneViewer::contextMenuEvent(QContextMenuEvent *e) {
if (m_tabletEvent) return;
#ifndef _WIN32
/* On windows the widget receive the release event before the menu
is shown, on linux and osx the release event is lost, never
received by the widget */
QMouseEvent fakeRelease(QEvent::MouseButtonRelease, e->pos(), Qt::RightButton,
Qt::NoButton, Qt::NoModifier);
QApplication::instance()->sendEvent(this, &fakeRelease);
#endif
onContextMenu(e->pos(), e->globalPos());
}
//-----------------------------------------------------------------------------
void SceneViewer::onContextMenu(const QPoint &pos, const QPoint &globalPos) {
if (m_freezedStatus != NO_FREEZED) return;
if (m_isLocator) return;
static bool menuVisible = false;
if (menuVisible) return;
menuVisible = true;
int devPixRatio = getDevPixRatio();
TPoint winPos(pos.x() * devPixRatio, height() - pos.y() * devPixRatio);
std::vector<int> columnIndices;
// enable to select all the columns regardless of the click position
for (int i = 0;
i < TApp::instance()->getCurrentXsheet()->getXsheet()->getColumnCount();
i++)
columnIndices.push_back(i);
TXshLevelHandle *level = TApp::instance()->getCurrentLevel();
if (level) {
TXshSimpleLevel *sl = level->getSimpleLevel();
if (sl) {
int vp = sl->getProperties()->getVanishingPoints().size();
if (vp > 0) m_canShowPerspectiveGrids = true;
}
}
SceneViewerContextMenu *menu = new SceneViewerContextMenu(this);
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
TPointD p = ((tool) ? tool->getMatrix().inv() : TAffine()) *
winToWorld(pos * devPixRatio);
menu->addEnterGroupCommands(p);
menu->addLevelCommands(columnIndices);
BaseViewerPanel *bvp =
qobject_cast<BaseViewerPanel *>(parentWidget()->parentWidget());
if (bvp) {
menu->addSeparator();
bvp->addShowHideContextMenu(menu);
}
menu->exec(globalPos);
delete menu;
menuVisible = false;
}
//-----------------------------------------------------------------------------
void SceneViewer::inputMethodEvent(QInputMethodEvent *e) {
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (tool && tool->isEnabled()) {
tool->onInputText(e->preeditString().toStdWString(),
e->commitString().toStdWString(), e->replacementStart(),
e->replacementLength());
}
}
//-----------------------------------------------------------------------------
void SceneViewer::dragEnterEvent(QDragEnterEvent *event) {
if (m_freezedStatus != NO_FREEZED) return;
const QMimeData *mimeData = event->mimeData();
if (acceptResourceOrFolderDrop(mimeData->urls())) {
// Force CopyAction
event->setDropAction(Qt::CopyAction);
event->accept();
} else {
event->ignore();
}
}
//-----------------------------------------------------------------------------
void SceneViewer::dropEvent(QDropEvent *e) {
if (m_freezedStatus != NO_FREEZED) return;
const QMimeData *mimeData = e->mimeData();
if (mimeData->hasUrls()) {
IoCmd::LoadResourceArguments args;
for (const QUrl &url : mimeData->urls()) {
TFilePath fp(url.toLocalFile().toStdWString());
args.resourceDatas.push_back(fp);
}
IoCmd::loadResources(args);
if (acceptResourceOrFolderDrop(mimeData->urls())) {
// Force Copy Action
e->setDropAction(Qt::CopyAction);
// For files, don't accept original proposed action in case it's a move
e->accept();
return;
}
}
e->acceptProposedAction();
}
//-----------------------------------------------------------------------------
void SceneViewer::onToolSwitched() {
m_forceGlFlush = true;
m_toolSwitched = true;
invalidateToolStatus();
TTool *tool = TApp::instance()->getCurrentTool()->getTool();
if (tool) {
tool->updateMatrix();
if (tool->getViewer()) tool->getViewer()->setGuidedStrokePickerMode(0);
}
onLevelChanged();
update();
}
//-----------------------------------------------------------------------------
void SceneViewer::mousePan(const TMouseEvent &e) {
if (m_sw.getTotalTime() < 10) return;
m_sw.stop();
m_sw.start(true);
TPointD delta = e.m_pos - m_oldPos;
delta.y = -delta.y;
pan(delta);
m_oldPos = e.m_pos;
}
//-----------------------------------------------------------------------------
void SceneViewer::mouseZoom(const TMouseEvent &e) {
int d = m_oldY - e.m_pos.y;
m_oldY = e.m_pos.y;
double f = exp(-d * 0.01);
m_factor = f;
zoom(m_center, f);
}
//-----------------------------------------------------------------------------
void SceneViewer::mouseRotate(const TMouseEvent &e) {
if (m_sw.getTotalTime() < 50) return;
double u = 50;
if (false)
m_center = TPointD(0, 0);
else {
TAffine aff = getViewMatrix().inv();
if (getIsFlippedX()) aff = aff * TScale(-1, 1);
if (getIsFlippedY()) aff = aff * TScale(1, -1);
u = u * sqrt(aff.det());
m_center = aff * TPointD(0, 0);
}
m_sw.stop();
m_sw.start(true);
TPointD p = winToWorld(e.mousePos() * getDevPixRatio());
if (is3DView()) {
TPointD d = e.m_pos - m_oldMousePos;
m_oldMousePos = e.m_pos;
double factor = 0.5;
rotate3D(factor * d.x, -factor * d.y);
} else {
TPointD a = p - m_center;
TPointD b = m_oldPos - m_center;
if (norm2(a) > 0 && norm2(b) > 0) {
double ang = asin(cross(b, a) / (norm(a) * norm(b))) * M_180_PI;
m_angle = m_angle + ang;
rotate(m_center, m_angle);
}
}
m_oldPos = p;
tglDrawSegment(TPointD(-u + m_center.x, m_center.y),
TPointD(u + m_center.x, m_center.y));
tglDrawSegment(TPointD(m_center.x, -u + m_center.y),
TPointD(m_center.x, u + m_center.y));
}
//-----------------------------------------------------------------------------
void SceneViewer::resetNavigation() {
if (m_dragging)
m_resetOnRelease = true;
else {
m_mousePanning = 0;
m_mouseZooming = 0;
m_mouseRotating = 0;
if (m_keyAction) {
m_keyAction->setEnabled(true);
m_keyAction = 0;
invalidateToolStatus();
}
}
}