/************************************************************************ ** ** @file vmaingraphicsview.cpp ** @author Roman Telezhynskyi ** @date November 15, 2013 ** ** @brief ** @copyright ** This source code is part of the Valentine project, a pattern making ** program, whose allow create and modeling patterns of clothing. ** Copyright (C) 2013-2015 Valentina project ** All Rights Reserved. ** ** Valentina is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** Valentina is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with Valentina. If not, see . ** *************************************************************************/ #include "vmaingraphicsview.h" #include #include #include #include #include "vsimplepoint.h" #include "vmaingraphicsscene.h" #include #include #include const int GraphicsViewZoom::duration = 300; const int GraphicsViewZoom::updateInterval = 40; //--------------------------------------------------------------------------------------------------------------------- GraphicsViewZoom::GraphicsViewZoom(QGraphicsView* view) : QObject(view), _view(view), _modifiers(Qt::ControlModifier), _zoom_factor_base(1.0015), target_scene_pos(QPointF()), target_viewport_pos(QPointF()), verticalScrollAnim(new QTimeLine(duration, this)), _numScheduledVerticalScrollings(0), horizontalScrollAnim(new QTimeLine(duration, this)), _numScheduledHorizontalScrollings(0) { _view->viewport()->installEventFilter(this); _view->setMouseTracking(true); verticalScrollAnim->setUpdateInterval(updateInterval); connect(verticalScrollAnim, &QTimeLine::valueChanged, this, &GraphicsViewZoom::VerticalScrollingTime); connect(verticalScrollAnim, &QTimeLine::finished, this, &GraphicsViewZoom::animFinished); horizontalScrollAnim->setUpdateInterval(updateInterval); connect(horizontalScrollAnim, &QTimeLine::valueChanged, this, &GraphicsViewZoom::HorizontalScrollingTime); connect(horizontalScrollAnim, &QTimeLine::finished, this, &GraphicsViewZoom::animFinished); } //--------------------------------------------------------------------------------------------------------------------- void GraphicsViewZoom::gentle_zoom(double factor) { _view->scale(factor, factor); if (factor < 1) { // Because QGraphicsView centers the picture when it's smaller than the view. And QGraphicsView's scrolls // boundaries don't allow to put any picture point at any viewport position we will provide fictive scene size. // Temporary and bigger than view, scene size will help position an image under cursor. FictiveSceneRect(_view->scene(), _view); } _view->centerOn(target_scene_pos); QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0, _view->viewport()->height() / 2.0); QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos; _view->centerOn(_view->mapToScene(viewport_center.toPoint())); // In the end we just set correct scene size VMainGraphicsView::NewSceneRect(_view->scene(), _view); emit zoomed(); } //--------------------------------------------------------------------------------------------------------------------- // cppcheck-suppress unusedFunction void GraphicsViewZoom::set_modifiers(Qt::KeyboardModifiers modifiers) { _modifiers = modifiers; } //--------------------------------------------------------------------------------------------------------------------- // cppcheck-suppress unusedFunction void GraphicsViewZoom::set_zoom_factor_base(double value) { _zoom_factor_base = value; } //--------------------------------------------------------------------------------------------------------------------- void GraphicsViewZoom::VerticalScrollingTime(qreal x) { Q_UNUSED(x); // Try to adapt scrolling to speed of rotating mouse wheel and scale factor // Value of _numScheduledScrollings is too short, so we scale the value qreal scroll = (qAbs(_numScheduledVerticalScrollings)*(10 + 10/_view->transform().m22()))/(duration/updateInterval); if (qAbs(scroll) < 1) { scroll = 1; } if (_numScheduledVerticalScrollings > 0) { scroll = scroll * -1; } _view->verticalScrollBar()->setValue(qRound(_view->verticalScrollBar()->value() + scroll)); } //--------------------------------------------------------------------------------------------------------------------- void GraphicsViewZoom::HorizontalScrollingTime(qreal x) { Q_UNUSED(x); // Try to adapt scrolling to speed of rotating mouse wheel and scale factor // Value of _numScheduledScrollings is too short, so we scale the value qreal scroll = (qAbs(_numScheduledHorizontalScrollings)*(10 + 10/_view->transform().m11()))/ (duration/updateInterval); if (qAbs(scroll) < 1) { scroll = 1; } if (_numScheduledHorizontalScrollings > 0) { scroll = scroll * -1; } _view->horizontalScrollBar()->setValue(qRound(_view->horizontalScrollBar()->value() + scroll)); } //--------------------------------------------------------------------------------------------------------------------- void GraphicsViewZoom::animFinished() { _numScheduledVerticalScrollings = 0; verticalScrollAnim->stop(); /* * In moust cases cursor position on view doesn't change, but for scene after scrolling position will be different. * We are goint to check changes and save new value. * If don't do that we will zoom using old value cursor position on scene. It is not what we expect. * Almoust the same we do in method GraphicsViewZoom::eventFilter. */ const QPoint pos = _view->mapFromGlobal(QCursor::pos()); const QPointF delta = target_scene_pos - _view->mapToScene(pos); if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) { target_viewport_pos = pos; target_scene_pos = _view->mapToScene(pos); } } //--------------------------------------------------------------------------------------------------------------------- bool GraphicsViewZoom::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::MouseMove) { /* * Here we are saving cursor position on view and scene. * This data need for gentle_zoom(). * Almoust the same we do in method GraphicsViewZoom::animFinished. */ QMouseEvent* mouse_event = static_cast(event); QPointF delta = target_viewport_pos - mouse_event->pos(); if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) { target_viewport_pos = mouse_event->pos(); target_scene_pos = _view->mapToScene(mouse_event->pos()); } return false; } else if (event->type() == QEvent::Wheel) { QWheelEvent* wheel_event = static_cast(event); SCASSERT(wheel_event != nullptr); if (QApplication::keyboardModifiers() == _modifiers) { if (wheel_event->orientation() == Qt::Vertical) { const double angle = wheel_event->angleDelta().y(); const double factor = qPow(_zoom_factor_base, angle); gentle_zoom(factor); return true; } } else { if (QApplication::keyboardModifiers() == Qt::ShiftModifier) { return StartHorizontalScrollings(wheel_event); } else { return StartVerticalScrollings(wheel_event); } } } return QObject::eventFilter(object, event); } //--------------------------------------------------------------------------------------------------------------------- void GraphicsViewZoom::FictiveSceneRect(QGraphicsScene *sc, QGraphicsView *view) { SCASSERT(sc != nullptr); SCASSERT(view != nullptr); //Calculate view rect //to receive the currently visible area, map the widgets bounds to the scene const QPointF a = view->mapToScene(0, 0 ); const QPointF b = view->mapToScene(view->viewport()->width(), view->viewport()->height()); QRectF viewRect = QRectF( a, b ); //Scale view QLineF topLeftRay(viewRect.center(), viewRect.topLeft()); topLeftRay.setLength(topLeftRay.length()*2); QLineF bottomRightRay(viewRect.center(), viewRect.bottomRight()); bottomRightRay.setLength(bottomRightRay.length()*2); viewRect = QRectF(topLeftRay.p2(), bottomRightRay.p2()); //Calculate scene rect const QRectF sceneRect = sc->sceneRect(); //Unite two rects const QRectF newRect = sceneRect.united(viewRect); sc->setSceneRect(newRect); } //--------------------------------------------------------------------------------------------------------------------- bool GraphicsViewZoom::StartVerticalScrollings(QWheelEvent *wheel_event) { SCASSERT(wheel_event != nullptr); const QPoint numPixels = wheel_event->pixelDelta(); const QPoint numDegrees = wheel_event->angleDelta() / 8; int numSteps; if (not numPixels.isNull()) { numSteps = numPixels.y(); } else if (not numDegrees.isNull()) { numSteps = numDegrees.y() / 15; } else { return true;//Just ignore } _numScheduledVerticalScrollings += numSteps; if (_numScheduledVerticalScrollings * numSteps < 0) { // if user moved the wheel in another direction, we reset previously scheduled scalings _numScheduledVerticalScrollings = numSteps; } if (verticalScrollAnim->state() != QTimeLine::Running) { verticalScrollAnim->start(); } return true; } //--------------------------------------------------------------------------------------------------------------------- bool GraphicsViewZoom::StartHorizontalScrollings(QWheelEvent *wheel_event) { SCASSERT(wheel_event != nullptr); const QPoint numPixels = wheel_event->pixelDelta(); const QPoint numDegrees = wheel_event->angleDelta() / 8; int numSteps; if (not numPixels.isNull()) { numSteps = numPixels.y(); } else if (not numDegrees.isNull()) { numSteps = numDegrees.y() / 15; } else { return true;//Just ignore } _numScheduledHorizontalScrollings += numSteps; if (_numScheduledHorizontalScrollings * numSteps < 0) { // if user moved the wheel in another direction, we reset previously scheduled scalings _numScheduledHorizontalScrollings = numSteps; } if (horizontalScrollAnim->state() != QTimeLine::Running) { horizontalScrollAnim->start(); } return true; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief VMainGraphicsView constructor. * @param parent parent object. */ VMainGraphicsView::VMainGraphicsView(QWidget *parent) :QGraphicsView(parent), zoom(nullptr), showToolOptions(true) { zoom = new GraphicsViewZoom(this); this->setResizeAnchor(QGraphicsView::AnchorUnderMouse); this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); this->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); this->setInteractive(true); } //--------------------------------------------------------------------------------------------------------------------- void VMainGraphicsView::ZoomIn() { scale(1.1, 1.1); VMainGraphicsView::NewSceneRect(this->scene(), this); emit NewFactor(1.1); } //--------------------------------------------------------------------------------------------------------------------- void VMainGraphicsView::ZoomOut() { scale(1.0/1.1, 1.0/1.1); VMainGraphicsView::NewSceneRect(this->scene(), this); emit NewFactor(1.0/1.1); } //--------------------------------------------------------------------------------------------------------------------- void VMainGraphicsView::ZoomOriginal() { QTransform trans = this->transform(); trans.setMatrix(1.0, trans.m12(), trans.m13(), trans.m21(), 1.0, trans.m23(), trans.m31(), trans.m32(), trans.m33()); this->setTransform(trans); VMainGraphicsView::NewSceneRect(this->scene(), this); emit NewFactor(1.0); } //--------------------------------------------------------------------------------------------------------------------- void VMainGraphicsView::ZoomFitBest() { VMainGraphicsScene *currentScene = qobject_cast(scene()); SCASSERT(currentScene); currentScene->SetOriginsVisible(false); const QRectF rect = currentScene->VisibleItemsBoundingRect(); currentScene->SetOriginsVisible(true); if (rect.isEmpty()) { return; } this->fitInView(rect, Qt::KeepAspectRatio); VMainGraphicsView::NewSceneRect(scene(), this); emit NewFactor(this->transform().m11()); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief mousePressEvent handle mouse press events. * @param mousePress mouse press event. */ void VMainGraphicsView::mousePressEvent(QMouseEvent *mousePress) { if (mousePress->button() & Qt::LeftButton) { switch (QGuiApplication::keyboardModifiers()) { case Qt::ControlModifier: QGraphicsView::setDragMode(QGraphicsView::ScrollHandDrag); break; case Qt::NoModifier: if (showToolOptions) { QList list = items(mousePress->pos()); if (list.size() == 0) { emit itemClicked(nullptr); break; } for (int i = 0; i < list.size(); ++i) { if (this->scene()->items().contains(list.at(i))) { if (list.at(i)->type() <= VSimplePoint::Type && list.at(i)->type() > QGraphicsItem::UserType) { emit itemClicked(list.at(i)); break; } else { emit itemClicked(nullptr); } } } } break; default: break; } } QGraphicsView::mousePressEvent(mousePress); } //--------------------------------------------------------------------------------------------------------------------- /** * @brief mouseReleaseEvent handle mouse release events. * @param event mouse release event. */ void VMainGraphicsView::mouseReleaseEvent(QMouseEvent *event) { QGraphicsView::mouseReleaseEvent ( event ); QGraphicsView::setDragMode( QGraphicsView::RubberBandDrag ); if (event->button() == Qt::LeftButton) { emit MouseRelease(); } } //--------------------------------------------------------------------------------------------------------------------- void VMainGraphicsView::setShowToolOptions(bool value) { showToolOptions = value; } //--------------------------------------------------------------------------------------------------------------------- /** * @brief NewSceneRect calculate scene rect what contains all items and doesn't less that size of scene view. * @param sc scene. * @param view view. */ void VMainGraphicsView::NewSceneRect(QGraphicsScene *sc, QGraphicsView *view) { SCASSERT(sc != nullptr); SCASSERT(view != nullptr); //Calculate view rect const QRectF viewRect = SceneVisibleArea(view); //Calculate scene rect VMainGraphicsScene *currentScene = qobject_cast(sc); SCASSERT(currentScene); const QRectF itemsRect = currentScene->VisibleItemsBoundingRect(); //Unite two rects sc->setSceneRect(itemsRect.united(viewRect)); } //--------------------------------------------------------------------------------------------------------------------- QRectF VMainGraphicsView::SceneVisibleArea(QGraphicsView *view) { SCASSERT(view != nullptr); //to receive the currently visible area, map the widgets bounds to the scene return QRectF(view->mapToScene(0, 0), view->mapToScene(view->width(), view->height())); }