File indexing completed on 2024-05-05 17:09:08

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