Revision control
++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qgraphsinputhandler_p.h"
++
++ #include <QtQuick/private/qquickdraghandler_p.h>
++ #include <QtQuick/private/qquickpinchhandler_p.h>
++ #include <QtQuick/private/qquicktaphandler_p.h>
++ #include <QtQuick/private/qquickwheelhandler_p.h>
++
++ #include "qquickgraphsitem_p.h"
++ #include "qgraphs3dlogging_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ QGraphsInputHandler::QGraphsInputHandler(QQuickItem *parent)
++ : QQuickItem(parent)
++ , m_dragMode(QtGraphs3D::DragMode::Rotate)
++ , m_flags{true, true, true, true, true}
++ , m_pendingPoint(QPoint())
++ , m_pinchDiff(.0f)
++ , m_graphsItem(nullptr)
++ {
++ m_pinchHandler = new QQuickPinchHandler(this);
++ m_tapHandler = new QQuickTapHandler(this);
++
++ // This is to support QQuickGraphsItem's mouseMove signal.
++ setAcceptHoverEvents(true);
++ m_dragHandler = new QQuickDragHandler(this);
++ m_panHandler = new QQuickDragHandler(this);
++ m_wheelHandler = new QQuickWheelHandler(this);
++ m_dragHandler->setAcceptedButtons(Qt::MouseButton::RightButton);
++ m_panHandler->setAcceptedButtons(Qt::MouseButton::MiddleButton);
++ m_wheelHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse
++ | QInputDevice::DeviceType::TouchPad);
++ QObject::connect(m_tapHandler, &QQuickTapHandler::tapped, this, &QGraphsInputHandler::onTapped);
++ QObject::connect(m_dragHandler,
++ &QQuickDragHandler::translationChanged,
++ this,
++ &QGraphsInputHandler::onTranslationChanged);
++ QObject::connect(m_panHandler,
++ &QQuickDragHandler::translationChanged,
++ this,
++ &QGraphsInputHandler::onTranslationChanged);
++ QObject::connect(m_dragHandler,
++ &QQuickDragHandler::grabChanged,
++ this,
++ &QGraphsInputHandler::onGrabChanged);
++ QObject::connect(m_wheelHandler,
++ &QQuickWheelHandler::wheel,
++ this,
++ &QGraphsInputHandler::onWheel);
++ QObject::connect(m_pinchHandler,
++ &QQuickPinchHandler::scaleChanged,
++ this,
++ &QGraphsInputHandler::onPinchScaleChanged);
++ QObject::connect(m_pinchHandler,
++ &QQuickPinchHandler::grabChanged,
++ this,
++ &QGraphsInputHandler::onGrabChanged);
++ }
++
++ QGraphsInputHandler::~QGraphsInputHandler() {}
++
++ void QGraphsInputHandler::setZoomEnabled(bool enable)
++ {
++ if (m_flags.zoomEnabled == enable) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << enable;
++ return;
++ }
++ m_flags.zoomEnabled = enable;
++ if (m_graphsItem)
++ emit m_graphsItem->zoomEnabledChanged(enable);
++ }
++
++ bool QGraphsInputHandler::isZoomEnabled()
++ {
++ return m_flags.zoomEnabled;
++ }
++
++ void QGraphsInputHandler::setZoomAtTargetEnabled(bool enable)
++ {
++ if (m_flags.zoomAtTarget == enable) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << enable;
++ return;
++ }
++ m_flags.zoomAtTarget = enable;
++ if (m_graphsItem)
++ emit m_graphsItem->zoomAtTargetEnabledChanged(enable);
++ }
++
++ bool QGraphsInputHandler::isZoomAtTargetEnabled()
++ {
++ return m_flags.zoomAtTarget;
++ }
++
++ void QGraphsInputHandler::setRotationEnabled(bool enable)
++ {
++ if (m_flags.rotationEnabled == enable) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << enable;
++ }
++ m_flags.rotationEnabled = enable;
++ if (m_graphsItem)
++ emit m_graphsItem->rotationEnabledChanged(enable);
++ }
++
++ bool QGraphsInputHandler::isRotationEnabled()
++ {
++ return m_flags.rotationEnabled;
++ }
++
++ void QGraphsInputHandler::setPanEnabled(bool enable)
++ {
++ if (m_flags.panEnabled == enable) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << enable;
++ }
++ m_flags.panEnabled = enable;
++ if (m_graphsItem)
++ emit m_graphsItem->panEnabledChanged(enable);
++ }
++
++ bool QGraphsInputHandler::isPanEnabled()
++ {
++ return m_flags.panEnabled;
++ }
++
++ void QGraphsInputHandler::setDragMode(QtGraphs3D::DragMode mode)
++ {
++ if (m_dragMode == mode) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << mode;
++ }
++ m_dragMode = mode;
++ if (m_graphsItem)
++ emit m_graphsItem->dragModeChanged(mode);
++ }
++
++ QtGraphs3D::DragMode QGraphsInputHandler::dragMode()
++ {
++ return m_dragMode;
++ }
++
++ void QGraphsInputHandler::setSelectionEnabled(bool enable)
++ {
++ if (m_flags.selectionEnabled == enable) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << enable;
++ return;
++ }
++ m_flags.selectionEnabled = enable;
++ if (m_graphsItem)
++ emit m_graphsItem->selectionEnabledChanged(enable);
++ }
++
++ bool QGraphsInputHandler::isSelectionEnabled()
++ {
++ return m_flags.selectionEnabled;
++ }
++
++ void QGraphsInputHandler::setDefaultInputHandler()
++ {
++ setVisible(true);
++ }
++
++ void QGraphsInputHandler::unsetDefaultInputHandler()
++ {
++ setVisible(false);
++ }
++
++ void QGraphsInputHandler::unsetDefaultTapHandler()
++ {
++ QObject::disconnect(m_tapHandler,
++ &QQuickTapHandler::tapped,
++ this,
++ &QGraphsInputHandler::onTapped);
++ }
++
++ void QGraphsInputHandler::unsetDefaultDragHandler()
++ {
++ QObject::disconnect(m_dragHandler,
++ &QQuickDragHandler::translationChanged,
++ this,
++ &QGraphsInputHandler::onTranslationChanged);
++ }
++
++ void QGraphsInputHandler::unsetDefaultWheelHandler()
++ {
++ QObject::disconnect(m_wheelHandler,
++ &QQuickWheelHandler::wheel,
++ this,
++ &QGraphsInputHandler::onWheel);
++ }
++
++ void QGraphsInputHandler::unsetDefaultPinchHandler()
++ {
++ QObject::disconnect(m_pinchHandler,
++ &QQuickPinchHandler::scaleChanged,
++ this,
++ &QGraphsInputHandler::onPinchScaleChanged);
++ }
++
++ void QGraphsInputHandler::setDragButton(Qt::MouseButtons button)
++ {
++ m_dragHandler->setAcceptedButtons(button);
++ }
++
++ void QGraphsInputHandler::setPanButton(Qt::MouseButtons button)
++ {
++ m_panHandler->setAcceptedButtons(button);
++ }
++
++ void QGraphsInputHandler::setGraphsItem(QQuickGraphsItem *item)
++ {
++ m_graphsItem = item;
++ QObject::connect(m_tapHandler, &QQuickTapHandler::tapped, item, &QQuickGraphsItem::tapped);
++ QObject::connect(m_tapHandler,
++ &QQuickTapHandler::doubleTapped,
++ item,
++ &QQuickGraphsItem::doubleTapped);
++ QObject::connect(m_tapHandler,
++ &QQuickTapHandler::longPressed,
++ item,
++ &QQuickGraphsItem::longPressed);
++ QObject::connect(m_dragHandler,
++ &QQuickDragHandler::translationChanged,
++ item,
++ &QQuickGraphsItem::dragged);
++ QObject::connect(m_wheelHandler, &QQuickWheelHandler::wheel, item, &QQuickGraphsItem::wheel);
++ QObject::connect(m_pinchHandler,
++ &QQuickPinchHandler::scaleChanged,
++ item,
++ &QQuickGraphsItem::pinch);
++ QObject::connect(this, &QGraphsInputHandler::mouseMove, item, &QQuickGraphsItem::mouseMove);
++ }
++
++ void QGraphsInputHandler::onTapped()
++ {
++ if (!m_flags.selectionEnabled)
++ return;
++
++ if (m_graphsItem->isSlicingActive()) {
++ m_graphsItem->setSliceActivatedChanged(true);
++ m_graphsItem->update();
++ return;
++ }
++ m_graphsItem->doPicking(m_tapHandler->point().position());
++ }
++
++ void QGraphsInputHandler::onTranslationChanged(QVector2D delta)
++ {
++
++ bool dragButtons = m_dragHandler->centroid().pressedButtons().testAnyFlags(
++ m_dragHandler->acceptedButtons());
++
++ bool panButtons = m_panHandler->centroid().pressedButtons().testAnyFlags(
++ m_panHandler->acceptedButtons());
++
++ QQuickGraphsItem *item = m_graphsItem;
++ float xRotation = item->cameraXRotation();
++ float yRotation = item->cameraYRotation();
++
++ if (panButtons || (dragButtons && m_dragMode == QtGraphs3D::DragMode::Pan)) {
++ if (!m_flags.panEnabled)
++ return;
++
++ QVector3D camPos = item->cameraTargetPosition();
++ QVector3D diff = item->cameraTarget()->mapDirectionToScene({delta.x(), -delta.y(), 0});
++ float zoom = 1 / (item->cameraZoomLevel() / 100.0f);
++ item->setCameraTargetPosition(camPos - diff * zoom);
++
++ } else if (dragButtons && m_dragMode == QtGraphs3D::DragMode::Rotate) {
++ if (!m_flags.rotationEnabled)
++ return;
++
++ float rotationSpeed = 1.0f;
++ #if !defined(Q_OS_IOS)
++ rotationSpeed = 10.0f;
++ #endif
++ // Calculate mouse movement since last frame
++ // Apply to rotations
++ xRotation += (delta.x() / rotationSpeed);
++ yRotation += (delta.y() / rotationSpeed);
++ item->setCameraXRotation(xRotation);
++ item->setCameraYRotation(yRotation);
++ }
++ }
++
++ void QGraphsInputHandler::onGrabChanged(QPointingDevice::GrabTransition transition,
++ QEventPoint point)
++ {
++ static QPointF pickPoint;
++
++ if (transition == QPointingDevice::GrabPassive) {
++ pickPoint = point.position().toPoint();
++ } else if (transition == QPointingDevice::GrabExclusive) {
++ if (m_dragHandler->centroid().pressedButtons().testFlag(Qt::LeftButton))
++ m_graphsItem->doPicking(pickPoint);
++ } else if (transition == QPointingDevice::UngrabExclusive
++ || transition == QPointingDevice::UngrabPassive) {
++ setPosition(QPointF(.0f, .0f));
++ setScale(1.f);
++ setRotation(.0f);
++ emit m_graphsItem->selectedElementChanged(QtGraphs3D::ElementType::None);
++ pickPoint = QPointF();
++ }
++ }
++
++ void QGraphsInputHandler::onWheel(QQuickWheelEvent *event)
++ {
++ if (!m_flags.zoomEnabled) {
++ qCWarning(lcEvents3D, "%s zooming needs to be enabled",
++ qUtf8Printable(QLatin1String(__FUNCTION__)));
++ return;
++ }
++
++ int halfSizeZoomLevel = 50;
++ int oneToOneZoomLevel = 100;
++
++ int driftTowardCenterLevel = 175;
++ float wheelZoomDrift = 0.1f;
++
++ int nearZoomRangeDivider = 12;
++ int midZoomRangeDivider = 60;
++ int farZoomRangeDivider = 120;
++
++ if (m_graphsItem->isSlicingActive())
++ return;
++
++ // Adjust zoom level based on what zoom range we're in.
++ QQuickGraphsItem *item = m_graphsItem;
++ int zoomLevel = int(item->cameraZoomLevel());
++ const int minZoomLevel = int(item->minCameraZoomLevel());
++ const int maxZoomLevel = int(item->maxCameraZoomLevel());
++ if (zoomLevel > oneToOneZoomLevel)
++ zoomLevel += event->angleDelta().y() / nearZoomRangeDivider;
++ else if (zoomLevel > halfSizeZoomLevel)
++ zoomLevel += event->angleDelta().y() / midZoomRangeDivider;
++ else
++ zoomLevel += event->angleDelta().y() / farZoomRangeDivider;
++ zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel);
++
++ if (m_flags.zoomAtTarget) {
++ QVector3D targetPosition = item->graphPositionAt(QPoint(event->x(), event->y()));
++ float previousZoom = item->cameraZoomLevel();
++ item->setCameraZoomLevel(zoomLevel);
++
++ float diffAdj = 0.0f;
++
++ // If zooming in/out outside the graph, or zooming out after certain point,
++ // move towards the center.
++ if ((qAbs(targetPosition.x()) > 2.0f || qAbs(targetPosition.y()) > 2.0f
++ || qAbs(targetPosition.z()) > 2.0f)
++ || (previousZoom > zoomLevel && zoomLevel <= driftTowardCenterLevel)) {
++ targetPosition = QVector3D();
++ // Add some extra correction so that we actually reach the center
++ // eventually
++ diffAdj = wheelZoomDrift;
++ if (previousZoom > zoomLevel)
++ diffAdj *= 2.0f; // Correct towards center little more when zooming out
++ }
++
++ float zoomFraction = 1.0f - (previousZoom / zoomLevel);
++
++ // Adjust camera towards the zoom point, attempting to keep the cursor at
++ // same graph point
++ QVector3D oldTarget = item->cameraTargetPosition();
++ QVector3D origDiff = targetPosition - oldTarget;
++ QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj);
++ if (diff.length() > origDiff.length())
++ diff = origDiff;
++ item->setCameraTargetPosition(oldTarget + diff);
++ } else {
++ item->setCameraZoomLevel(zoomLevel);
++ }
++
++ item->update();
++ }
++
++ void QGraphsInputHandler::onPinchScaleChanged(qreal delta)
++ {
++ if (!m_flags.zoomEnabled) {
++ qCWarning(lcEvents3D, "%s zooming needs to be enabled",
++ qUtf8Printable(QLatin1String(__FUNCTION__)));
++ return;
++ }
++
++ m_pinchDiff += (delta - 1.0f);
++ int driftTowardCenterLevel = 175;
++ float wheelZoomDrift = 0.1f;
++
++ QQuickGraphsItem *item = m_graphsItem;
++ int zoomLevel = int(item->cameraZoomLevel());
++ const int minZoomLevel = int(item->minCameraZoomLevel());
++ const int maxZoomLevel = int(item->maxCameraZoomLevel());
++ float zoomRate = qSqrt(qSqrt(zoomLevel));
++ if (m_pinchDiff > .0f)
++ zoomLevel += zoomRate;
++ else
++ zoomLevel -= zoomRate;
++ zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel);
++ if (m_flags.zoomAtTarget) {
++ QVector3D targetPosition = item->graphPositionAt(
++ m_pinchHandler->centroid().position().toPoint());
++ item->setCameraZoomLevel(zoomLevel);
++
++ float diffAdj = 0.0f;
++
++ // If zooming in/out outside the graph, or zooming out after certain point,
++ // move towards the center.
++ if ((qAbs(targetPosition.x()) > 2.0f || qAbs(targetPosition.y()) > 2.0f
++ || qAbs(targetPosition.z()) > 2.0f)
++ || (m_pinchDiff > .0f && zoomLevel <= driftTowardCenterLevel)) {
++ targetPosition = QVector3D();
++ // Add some extra correction so that we actually reach the center
++ // eventually
++ diffAdj = wheelZoomDrift;
++ if (m_pinchDiff > .0f)
++ diffAdj *= 2.0f; // Correct towards center little more when zooming out
++ }
++
++ // Adjust camera towards the zoom point, attempting to keep the cursor at
++ // same graph point
++ QVector3D oldTarget = item->cameraTargetPosition();
++ QVector3D origDiff = targetPosition - oldTarget;
++ QVector3D diff = origDiff * m_pinchDiff + (origDiff.normalized() * diffAdj);
++ if (diff.length() > origDiff.length())
++ diff = origDiff;
++ item->setCameraTargetPosition(oldTarget + diff);
++ } else {
++ item->setCameraZoomLevel(zoomLevel);
++ }
++ m_pinchDiff = .0f;
++ }
++
++ void QGraphsInputHandler::hoverMoveEvent(QHoverEvent *event)
++ {
++ Q_EMIT mouseMove(event->oldPos());
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qgraphsinputhandler_p.cpp"