File indexing completed on 2024-04-28 11:21:06

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com>
0004     SPDX-FileCopyrightText: 2018-2022 Alexander Semke <alexander.semke@web.de>
0005 */
0006 
0007 #include "worksheetview.h"
0008 #include "worksheet.h"
0009 
0010 #include <QApplication>
0011 #include <QFocusEvent>
0012 #include <QParallelAnimationGroup>
0013 #include <QPropertyAnimation>
0014 #include <QTimeLine>
0015 #include <QScrollBar>
0016 
0017 WorksheetView::WorksheetView(Worksheet* scene, QWidget* parent) : QGraphicsView(scene, parent),
0018     m_worksheet(scene)
0019 {
0020     connect(scene, SIGNAL(sceneRectChanged(QRectF)),
0021             this, SLOT(sceneRectChanged(QRectF)));
0022     setAlignment(Qt::AlignLeft | Qt::AlignTop);
0023     //setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0024     setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0025 }
0026 
0027 void WorksheetView::makeVisible(const QRectF& sceneRect)
0028 {
0029     const qreal w = viewport()->width();
0030     const qreal h = viewport()->height();
0031 
0032     QRectF rect(m_scale*sceneRect.topLeft(), m_scale*sceneRect.size());
0033 
0034     qreal x,y;
0035     if (m_animation) {
0036         x = m_hAnimation->endValue().toReal();
0037         y = m_vAnimation->endValue().toReal();
0038 
0039         if (QRectF(x,y,w,h).contains(rect))
0040             return;
0041     }
0042 
0043     if (horizontalScrollBar())
0044         x = horizontalScrollBar()->value();
0045     else
0046         x = 0;
0047     if (verticalScrollBar())
0048         y = verticalScrollBar()->value();
0049     else
0050         y = 0;
0051 
0052     if (!m_animation && QRectF(x,y,w,h).contains(rect))
0053         return;
0054 
0055     qreal nx, ny;
0056     if (y > rect.y() || rect.height() > h)
0057         ny = rect.y();
0058     else
0059         ny = rect.y() + rect.height() - h;
0060     if (rect.x() + rect.width() <= w || x > rect.x())
0061         nx = 0;
0062     else
0063         nx = rect.x() + rect.width() - w;
0064 
0065     if (!m_worksheet->animationsEnabled()) {
0066         if (horizontalScrollBar())
0067             horizontalScrollBar()->setValue(nx);
0068         if (verticalScrollBar())
0069             verticalScrollBar()->setValue(ny);
0070         return;
0071     }
0072 
0073     if (!m_animation)
0074         m_animation = new QParallelAnimationGroup(this);
0075 
0076     if (horizontalScrollBar()) {
0077         if (!m_hAnimation) {
0078             m_hAnimation = new QPropertyAnimation(horizontalScrollBar(),
0079                                                   "value", this);
0080             m_hAnimation->setStartValue(horizontalScrollBar()->value());
0081             nx = qBound(qreal(0.0), nx, qreal(0.0+horizontalScrollBar()->maximum()));
0082             m_hAnimation->setEndValue(nx);
0083             m_hAnimation->setDuration(100);
0084             m_animation->addAnimation(m_hAnimation);
0085         } else {
0086             qreal progress = static_cast<qreal>(m_hAnimation->currentTime()) /
0087                 m_hAnimation->totalDuration();
0088             QEasingCurve curve = m_hAnimation->easingCurve();
0089             qreal value = curve.valueForProgress(progress);
0090             qreal sx = 1/(1-value)*(m_hAnimation->currentValue().toReal() -
0091                                     value * nx);
0092             m_hAnimation->setStartValue(sx);
0093             m_hAnimation->setEndValue(nx);
0094         }
0095     } else {
0096         m_hAnimation = nullptr;
0097     }
0098 
0099     if (verticalScrollBar()) {
0100         if (!m_vAnimation) {
0101             m_vAnimation = new QPropertyAnimation(verticalScrollBar(),
0102                                                   "value", this);
0103             m_vAnimation->setStartValue(verticalScrollBar()->value());
0104             ny = qBound(qreal(0.0), ny, qreal(0.0+verticalScrollBar()->maximum()));
0105             m_vAnimation->setEndValue(ny);
0106             m_vAnimation->setDuration(100);
0107             m_animation->addAnimation(m_vAnimation);
0108         } else {
0109             qreal progress = static_cast<qreal>(m_vAnimation->currentTime()) /
0110                 m_vAnimation->totalDuration();
0111             QEasingCurve curve = m_vAnimation->easingCurve();
0112             qreal value = curve.valueForProgress(progress);
0113             qreal sy = 1/(1-value)*(m_vAnimation->currentValue().toReal() -
0114                                     value * ny);
0115             m_vAnimation->setStartValue(sy);
0116             m_vAnimation->setEndValue(ny);
0117         }
0118     } else {
0119         m_vAnimation = nullptr;
0120     }
0121 
0122     connect(m_animation, &QParallelAnimationGroup::finished, this, &WorksheetView::endAnimation);
0123     m_animation->start();
0124 }
0125 
0126 void WorksheetView::scrollTo(int y)
0127 {
0128     if (!verticalScrollBar())
0129         return;
0130 
0131     qreal dy = y - verticalScrollBar()->value();
0132     scrollBy(dy);
0133 }
0134 
0135 
0136 bool WorksheetView::isVisible(const QRectF& sceneRect) const
0137 {
0138     const qreal w = viewport()->width();
0139     const qreal h = viewport()->height();
0140 
0141     QRectF rect(m_scale*sceneRect.topLeft(), m_scale*sceneRect.size());
0142 
0143     qreal x,y;
0144     if (m_animation) {
0145         x = m_hAnimation->endValue().toReal();
0146         y = m_vAnimation->endValue().toReal();
0147     } else {
0148         if (horizontalScrollBar())
0149             x = horizontalScrollBar()->value();
0150         else
0151             x = 0;
0152         if (verticalScrollBar())
0153             y = verticalScrollBar()->value();
0154         else
0155             y = 0;
0156     }
0157 
0158     return QRectF(x,y,w,h).contains(rect);
0159 }
0160 
0161 bool WorksheetView::isAtEnd() const
0162 {
0163     bool atEnd = true;
0164     if (verticalScrollBar())
0165         atEnd &= (verticalScrollBar()->value()==verticalScrollBar()->maximum());
0166     return atEnd;
0167 }
0168 
0169 void WorksheetView::scrollToEnd() const
0170 {
0171     if (verticalScrollBar())
0172         verticalScrollBar()->setValue(verticalScrollBar()->maximum());
0173 }
0174 
0175 void WorksheetView::scrollBy(int dy)
0176 {
0177     if (!verticalScrollBar())
0178         return;
0179 
0180     int ny = verticalScrollBar()->value() + dy;
0181     if (ny < 0)
0182         ny = 0;
0183     else if (ny > verticalScrollBar()->maximum())
0184         ny = verticalScrollBar()->maximum();
0185 
0186     int x;
0187     if (horizontalScrollBar())
0188         x = horizontalScrollBar()->value();
0189     else
0190         x = 0;
0191 
0192     const qreal w = viewport()->width() / m_scale;
0193     const qreal h = viewport()->height() / m_scale;
0194     makeVisible(QRectF(x, ny, w, h));
0195 }
0196 
0197 void WorksheetView::endAnimation()
0198 {
0199     if (!m_animation)
0200         return;
0201 
0202     m_animation->deleteLater();
0203     m_hAnimation = nullptr;
0204     m_vAnimation = nullptr;
0205     m_animation = nullptr;
0206 }
0207 
0208 QPoint WorksheetView::viewCursorPos() const
0209 {
0210     return viewport()->mapFromGlobal(QCursor::pos());
0211 }
0212 
0213 QPointF WorksheetView::sceneCursorPos() const
0214 {
0215     return mapToScene(viewCursorPos());
0216 }
0217 
0218 QRectF WorksheetView::viewRect() const
0219 {
0220     const qreal w = viewport()->width() / m_scale;
0221     const qreal h = viewport()->height() / m_scale;
0222     qreal y = verticalScrollBar()->value();
0223     qreal x = horizontalScrollBar() ? horizontalScrollBar()->value() : 0;
0224     return QRectF(x, y, w, h);
0225 }
0226 
0227 void WorksheetView::resizeEvent(QResizeEvent* event)
0228 {
0229     QGraphicsView::resizeEvent(event);
0230     updateSceneSize();
0231 }
0232 
0233 void WorksheetView::focusInEvent(QFocusEvent* event)
0234 {
0235     QGraphicsView::focusInEvent(event);
0236     m_worksheet->resumeAnimations();
0237 }
0238 
0239 void WorksheetView::focusOutEvent(QFocusEvent* event)
0240 {
0241     QGraphicsView::focusOutEvent(event);
0242     if (!scene()->hasFocus())
0243         m_worksheet->stopAnimations();
0244 }
0245 
0246 void WorksheetView::wheelEvent(QWheelEvent* event)
0247 {
0248     if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) {
0249         //https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView
0250         QPoint numDegrees = event->angleDelta() / 8;
0251         int numSteps = numDegrees.y() / 15; // see QWheelEvent documentation
0252         zoom(numSteps);
0253     } else
0254         QGraphicsView::wheelEvent(event);
0255 }
0256 
0257 void WorksheetView::zoom(int numSteps)
0258 {
0259     m_numScheduledScalings += numSteps;
0260     if (m_numScheduledScalings * numSteps < 0) // if user moved the wheel in another direction, we reset previously scheduled scalings
0261         m_numScheduledScalings = numSteps;
0262 
0263     auto* anim = new QTimeLine(350, this);
0264     anim->setUpdateInterval(20);
0265 
0266     connect(anim, &QTimeLine::valueChanged, this, &WorksheetView::scalingTime);
0267     connect(anim, &QTimeLine::finished, this, &WorksheetView::animFinished);
0268     anim->start();
0269 }
0270 
0271 void WorksheetView::scalingTime()
0272 {
0273     qreal factor = 1.0 + qreal(m_numScheduledScalings) / 300.0;
0274     m_scale *= factor;
0275     updateSceneSize();
0276     scale(factor, factor);
0277 }
0278 
0279 void WorksheetView::animFinished()
0280 {
0281     if (m_numScheduledScalings > 0)
0282         m_numScheduledScalings--;
0283     else
0284         m_numScheduledScalings++;
0285     sender()->~QObject();
0286     emit scaleFactorChanged(m_scale);
0287 }
0288 
0289 qreal WorksheetView::scaleFactor() const
0290 {
0291     return m_scale;
0292 }
0293 
0294 void WorksheetView::setScaleFactor(qreal zoom, bool emitSignal)
0295 {
0296     scale(1/m_scale * zoom, 1/m_scale * zoom);
0297     m_scale = zoom;
0298     updateSceneSize();
0299     if (emitSignal)
0300         emit scaleFactorChanged(m_scale);
0301 }
0302 
0303 void WorksheetView::updateSceneSize()
0304 {
0305     QSize s = viewport()->size();
0306     m_worksheet->setViewSize(s.width()/m_scale, s.height()/m_scale, m_scale);
0307     sendViewRectChange();
0308 }
0309 
0310 void WorksheetView::sceneRectChanged(const QRectF& sceneRect) const
0311 {
0312     Q_UNUSED(sceneRect);
0313     if (verticalScrollBar())
0314         connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
0315                 this, SLOT(sendViewRectChange()), Qt::UniqueConnection);
0316     if (horizontalScrollBar())
0317         connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
0318                 this, SLOT(sendViewRectChange()), Qt::UniqueConnection);
0319 }
0320 
0321 void WorksheetView::sendViewRectChange() const
0322 {
0323     emit viewRectChanged(viewRect());
0324 }
0325 
0326 void WorksheetView::zoomIn()
0327 {
0328     m_scale *= 1.1;
0329     scale(1.1, 1.1);
0330     updateSceneSize();
0331     emit scaleFactorChanged(m_scale);
0332 }
0333 
0334 void WorksheetView::zoomOut()
0335 {
0336     m_scale /= 1.1;
0337     scale(1/1.1, 1/1.1);
0338     updateSceneSize();
0339     emit scaleFactorChanged(m_scale);
0340 }
0341 
0342 void WorksheetView::actualSize()
0343 {
0344     scale (1/m_scale, 1/m_scale);
0345     m_scale = 1.0;
0346     updateSceneSize();
0347     emit scaleFactorChanged(m_scale);
0348 }