File indexing completed on 2024-05-12 13:04:45

0001 /*
0002  * This file is part of the KDE project
0003  * SPDX-FileCopyrightText: 2013 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "CQCanvasControllerItem.h"
0009 
0010 #include <QPainter>
0011 #include <QGraphicsScene>
0012 #include <QGraphicsView>
0013 #include <QGLWidget>
0014 
0015 #include <KoCanvasBase.h>
0016 #include <KoCanvasController.h>
0017 #include <KoZoomMode.h>
0018 #include <KoZoomController.h>
0019 
0020 #include "CQCanvasController.h"
0021 #include "CQCanvasBase.h"
0022 #include "CQTextDocumentCanvas.h"
0023 
0024 class CQCanvasControllerItem::Private
0025 {
0026 public:
0027     Private()
0028         : canvas(0),
0029           flickable(0),
0030           canvasController(0),
0031           lastX(0),
0032           lastY(0),
0033           zoom(0.0f),
0034           zoomChange(0.f),
0035           zooming(false),
0036           minimumZoom( -1.f ),
0037           maximumZoom( 2.f ),
0038           useViewport(false)
0039           { }
0040 
0041     CQCanvasBase *canvas;
0042     QDeclarativeItem* flickable;
0043     CQCanvasController *canvasController;
0044 
0045     QSize documentSize;
0046 
0047     float lastX;
0048     float lastY;
0049 
0050     QRectF placeholderTarget;
0051 
0052     qreal zoom;
0053     qreal zoomChange;
0054     bool zooming;
0055     qreal minimumZoom;
0056     qreal maximumZoom;
0057     QPointF zoomCenter;
0058 
0059     bool useViewport;
0060     QImage placeholder;
0061 };
0062 
0063 CQCanvasControllerItem::CQCanvasControllerItem(QDeclarativeItem* parent)
0064     : QDeclarativeItem(parent), d(new Private)
0065 {
0066     setFlag(QGraphicsItem::ItemHasNoContents, false);
0067     setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
0068 }
0069 
0070 CQCanvasControllerItem::~CQCanvasControllerItem()
0071 {
0072     delete d;
0073 }
0074 
0075 void CQCanvasControllerItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* , QWidget*)
0076 {
0077     if (!d->zooming || d->placeholder.isNull()) {
0078         return;
0079     }
0080 
0081     QPointF offset(d->flickable->property("contentX").toReal(), d->flickable->property("contentY").toReal());
0082 
0083     painter->drawImage(QRectF(offset - d->placeholderTarget.topLeft(), d->placeholderTarget.size()), d->placeholder, QRectF(QPointF(0, 0), d->placeholder.size()));
0084 }
0085 
0086 QDeclarativeItem* CQCanvasControllerItem::canvas() const
0087 {
0088     return d->canvas;
0089 }
0090 
0091 void CQCanvasControllerItem::setCanvas(QDeclarativeItem* canvas)
0092 {
0093     if (canvas != d->canvas) {
0094         if (d->canvas) {
0095             disconnect(d->canvas, SIGNAL(canvasControllerChanged()), this, SLOT(canvasControllerChanged()));
0096             disconnect(d->canvas, SIGNAL(positionShouldChange(QPoint)), this, SLOT(updateDocumentPosition(QPoint)));
0097         }
0098 
0099         d->canvas = qobject_cast<CQCanvasBase*>(canvas);
0100         Q_ASSERT(d->canvas);
0101 
0102         connect(d->canvas, SIGNAL(positionShouldChange(QPoint)), this, SLOT(updateDocumentPosition(QPoint)));
0103         connect(d->canvas, SIGNAL(canvasControllerChanged()), SLOT(canvasControllerChanged()));
0104         canvasControllerChanged();
0105 
0106         if (qobject_cast<CQTextDocumentCanvas*>(d->canvas) != 0) {
0107             d->useViewport = true;
0108         } else {
0109             d->useViewport = false;
0110         }
0111 
0112         emit canvasChanged();
0113     }
0114 }
0115 
0116 QDeclarativeItem* CQCanvasControllerItem::flickable() const
0117 {
0118     return d->flickable;
0119 }
0120 
0121 void CQCanvasControllerItem::setFlickable(QDeclarativeItem* item)
0122 {
0123     if (item != d->flickable) {
0124         if (item->metaObject()->indexOfProperty("contentWidth") == -1) {
0125             qWarning() << Q_FUNC_INFO << "item does not look like a flickable, ignoring.";
0126             return;
0127         }
0128 
0129         d->flickable = item;
0130         d->flickable->setProperty("contentWidth", d->documentSize.width());
0131         d->flickable->setProperty("contentHeight", d->documentSize.height());
0132         emit flickableChanged();
0133     }
0134 }
0135 
0136 QSize CQCanvasControllerItem::documentSize() const
0137 {
0138     return d->documentSize;
0139 }
0140 
0141 qreal CQCanvasControllerItem::zoom() const
0142 {
0143     return d->zoom;
0144 }
0145 
0146 void CQCanvasControllerItem::setZoom(qreal newZoom)
0147 {
0148     qreal tempZoom = qBound(KoZoomMode::minimumZoom(), newZoom, KoZoomMode::maximumZoom());
0149     if (!qFuzzyCompare(d->zoom, tempZoom)) {
0150         d->zoom = tempZoom;
0151         if (d->canvas && d->canvas->zoomController()) {
0152             d->canvas->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, tempZoom);
0153         }
0154         emit zoomChanged();
0155     }
0156 }
0157 
0158 void CQCanvasControllerItem::zoomToPage()
0159 {
0160     if (d->canvas && d->canvas->zoomController()) {
0161         // This may seem odd, but it ensures that we scale up as well as down
0162         // when zooming to fit page - without this, it will only scale down,
0163         // not up. To avoid changing the zooming logic in desktop, we just do
0164         // it this way. It's non-invasive anyway, and reasonably cheap, and
0165         // at any rate just covers a corner-case (normally this would only
0166         // happen when in full-screen mode anyway, but scaling down can be
0167         // triggered if in touch mode as a window and that is resized)
0168         d->canvas->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 10);
0169         d->canvas->zoomController()->setZoom(KoZoomMode::ZOOM_PAGE, 1.0);
0170         d->canvas->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, d->canvas->zoomController()->zoomAction()->effectiveZoom());
0171         d->zoom = d->canvas->zoomController()->zoomAction()->effectiveZoom();
0172         emit zoomChanged();
0173     }
0174 }
0175 
0176 qreal CQCanvasControllerItem::minimumZoom() const
0177 {
0178     return d->minimumZoom;
0179 }
0180 
0181 qreal CQCanvasControllerItem::maximumZoom() const
0182 {
0183     return d->maximumZoom;
0184 }
0185 
0186 void CQCanvasControllerItem::setMinimumZoom(qreal newZoom)
0187 {
0188     if (newZoom != d->minimumZoom && newZoom < KoZoomMode::maximumZoom() && newZoom > 0.f) {
0189         d->minimumZoom = newZoom;
0190         KoZoomMode::setMinimumZoom( d->minimumZoom );
0191         emit minimumZoomChanged();
0192     }
0193 }
0194 
0195 void CQCanvasControllerItem::setMaximumZoom(qreal newZoom)
0196 {
0197     if (newZoom != d->maximumZoom && newZoom > KoZoomMode::minimumZoom()) {
0198         d->maximumZoom = newZoom;
0199         KoZoomMode::setMaximumZoom( d->maximumZoom );
0200         emit maximumZoomChanged();
0201     }
0202 }
0203 
0204 void CQCanvasControllerItem::beginZoomGesture()
0205 {
0206     if (d->zooming) {
0207         return;
0208     }
0209 
0210     d->placeholderTarget.setLeft(0.f);
0211     d->placeholderTarget.setTop(0.f);
0212     d->placeholderTarget.setWidth(d->flickable->width());
0213     d->placeholderTarget.setHeight(d->flickable->height());
0214 
0215     if (d->useViewport) {
0216         QGLWidget* gl = qobject_cast<QGLWidget*>(scene()->views().at(0)->viewport());
0217         if (!gl) {
0218             return;
0219         }
0220 
0221         QRectF scene = d->flickable->mapToScene(QRectF(d->flickable->x(), 0, d->flickable->width(), d->flickable->height())).boundingRect();
0222         d->placeholder = gl->grabFrameBuffer(true).copy(scene.toRect());
0223     } else {
0224         d->placeholder = QImage(d->flickable->width(), d->flickable->height(), QImage::Format_ARGB32_Premultiplied);
0225         QPainter painter;
0226         painter.begin(&d->placeholder);
0227         d->canvas->render(&painter, QRectF(0, 0, d->flickable->width(), d->flickable->height()));
0228         painter.end();
0229     }
0230 
0231     d->canvas->setVisible(false);
0232     d->zooming = true;
0233 }
0234 
0235 void CQCanvasControllerItem::endZoomGesture()
0236 {
0237     if (!d->zooming) {
0238         return;
0239     }
0240 
0241     qreal newZoom = d->zoom + d->zoomChange;
0242 
0243     qreal oldX = d->flickable->property("contentX").toReal();
0244     qreal oldY = d->flickable->property("contentY").toReal();
0245 
0246     qreal xoff = (d->zoomCenter.x() + oldX) * newZoom / d->zoom;
0247     d->flickable->setProperty("contentX", xoff - d->zoomCenter.x());
0248 
0249     qreal yoff = (d->zoomCenter.y() + oldY ) * newZoom / d->zoom;
0250     d->flickable->setProperty("contentY", yoff - d->zoomCenter.y());
0251 
0252     setZoom(d->zoom + d->zoomChange);
0253 
0254     d->placeholder = QImage();
0255     d->zoomChange = 0.0;
0256     d->zooming = false;
0257 
0258     d->canvas->setVisible(true);
0259 }
0260 
0261 void CQCanvasControllerItem::zoomBy(qreal amount, const QPointF& center)
0262 {
0263     qreal newZoom = d->zoom + d->zoomChange + amount;
0264     if (d->zooming && newZoom >= KoZoomMode::minimumZoom() && newZoom <= KoZoomMode::maximumZoom() ) {
0265 //         qreal oldWidth = d->placeholderTarget.width();
0266 //         qreal oldHeight = d->placeholderTarget.height();
0267         qreal oldZoom = d->zoom + d->zoomChange;
0268 
0269         d->zoomChange += amount;
0270 
0271         d->placeholderTarget.setWidth((d->placeholderTarget.width() / oldZoom) * newZoom);
0272         d->placeholderTarget.setHeight((d->placeholderTarget.height() / oldZoom) * newZoom);
0273         d->placeholderTarget.moveLeft((center.x() * newZoom / d->zoom) - center.x());
0274         d->placeholderTarget.moveTop((center.y() * newZoom / d->zoom) - center.y());
0275 
0276         d->zoomCenter = center;
0277 
0278         update();
0279     }
0280 }
0281 
0282 void CQCanvasControllerItem::fitToWidth( qreal width )
0283 {
0284     if ( width < 0.01f ) {
0285         return;
0286     }
0287 
0288     if ( d->zoom < 0.01f ) {
0289         return;
0290     }
0291 
0292     if ( d->documentSize.width() > 0.f && d->documentSize.width() < 2e6 ) {
0293         setZoom( width / ( d->documentSize.width() / d->zoom ) );
0294     }
0295 }
0296 
0297 void CQCanvasControllerItem::returnToBounds()
0298 {
0299     QPointF pos(d->flickable->property("contentX").toReal(), d->flickable->property("contentY").toReal());
0300     float xDiff = pos.x() - d->lastX;
0301     float yDiff = pos.y() - d->lastY;
0302     d->canvasController->blockSignals(true);
0303     d->canvasController->pan(QPoint(xDiff, yDiff));
0304     d->canvasController->blockSignals(false);
0305     d->lastX = pos.x();
0306     d->lastY = pos.y();
0307 }
0308 
0309 void CQCanvasControllerItem::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
0310 {
0311     Q_UNUSED(newGeometry);
0312     Q_UNUSED(oldGeometry);
0313 }
0314 
0315 QVariant CQCanvasControllerItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value)
0316 {
0317     Q_UNUSED(value);
0318     if (change == QGraphicsItem::ItemScenePositionHasChanged && d->canvasController) {
0319         QPointF pos(d->flickable->property("contentX").toReal(), d->flickable->property("contentY").toReal());
0320         float xDiff = pos.x() - d->lastX;
0321         float yDiff = pos.y() - d->lastY;
0322         d->canvasController->blockSignals(true);
0323         d->canvasController->pan(QPoint(xDiff, yDiff));
0324         d->canvasController->blockSignals(false);
0325         d->lastX = pos.x();
0326         d->lastY = pos.y();
0327     }
0328 
0329     return value;
0330 }
0331 
0332 void CQCanvasControllerItem::updateDocumentSize(const QSize &size)
0333 {
0334     setSize(size);
0335     d->documentSize = size;
0336 
0337     if (d->flickable) {
0338         d->flickable->setProperty("contentWidth", d->documentSize.width());
0339         d->flickable->setProperty("contentHeight", d->documentSize.height());
0340 
0341         //If we have a correct document size, try to set the minimum zoom level, but
0342         //do not try to set it when we're dealing with a nearly-infinite document. (E.g. Sheets)
0343         if ( d->minimumZoom < 0 && d->documentSize.width() > 0 && d->documentSize.width() < 2e6 )
0344         {
0345             qreal minZoom = d->flickable->width() / ( d->documentSize.width() / ( d->zoom > 0.f ? d->zoom : 0.5 ) );
0346 
0347             if ( KoZoomMode::minimumZoom() != minZoom )
0348             {
0349                 KoZoomMode::setMinimumZoom( minZoom );
0350                 setZoom( d->zoom );
0351             }
0352         }
0353     }
0354 
0355     emit documentSizeChanged();
0356 }
0357 
0358 void CQCanvasControllerItem::updateDocumentPosition(const QPoint& pos)
0359 {
0360     if (d->flickable) {
0361         d->flickable->setProperty("contentX", QVariant::fromValue<qreal>(pos.x()));
0362         d->flickable->setProperty("contentY", QVariant::fromValue<qreal>(pos.y()));
0363     }
0364 }
0365 
0366 void CQCanvasControllerItem::canvasControllerChanged()
0367 {
0368     if (d->canvasController) {
0369         disconnect(d->canvasController, SIGNAL(documentSizeChanged(QSize)), this, SLOT(updateDocumentSize(QSize)));
0370         disconnect(d->canvasController, SIGNAL(documentPositionChanged(QPoint)), this, SLOT(updateDocumentPosition(QPoint)));
0371     }
0372 
0373     d->canvasController = d->canvas->canvasController();
0374     if (d->canvasController) {
0375         connect(d->canvasController, SIGNAL(documentSizeChanged(QSize)), SLOT(updateDocumentSize(QSize)));
0376         connect(d->canvasController, SIGNAL(documentPositionChanged(QPoint)), SLOT(updateDocumentPosition(QPoint)));
0377         updateDocumentSize(d->canvasController->documentSize());
0378     }
0379 }