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 }