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 }