File indexing completed on 2024-04-28 16:13:35
0001 /* 0002 * This file is part of the KDE project 0003 * 0004 * Copyright (C) 2013 Arjen Hiemstra <ahiemstra@heimr.nl> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Library General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Library General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Library General Public License 0017 * along with this library; see the file COPYING.LIB. If not, write to 0018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 * Boston, MA 02110-1301, USA. 0020 * 0021 */ 0022 0023 #include "ViewController.h" 0024 #include <KoToolManager.h> 0025 0026 #include <QDebug> 0027 #include <QTimer> 0028 #include <QQuickWindow> 0029 #include <QPainter> 0030 #include <QSGTransformNode> 0031 #include <QSGSimpleTextureNode> 0032 0033 #include <KoCanvasController.h> 0034 #include <KoCanvasBase.h> 0035 #include <KoShapeManager.h> 0036 #include <KoZoomMode.h> 0037 #include "gemini/ViewModeSwitchEvent.h" 0038 0039 #include "Document.h" 0040 #include "View.h" 0041 0042 using namespace Calligra::Components; 0043 0044 class ViewController::Private 0045 { 0046 public: 0047 Private() 0048 : view{nullptr} 0049 , flickable{nullptr} 0050 , canvasController{nullptr} 0051 , lastX{0.f} 0052 , lastY{0.f} 0053 , ignoreOffsetChange{false} 0054 , ignoreFlickableChange{false} 0055 , minimumZoom{.5f} 0056 , minimumZoomFitsWidth{false} 0057 , zoom{1.f} 0058 , zoomChange{0.f} 0059 , maximumZoom{2.f} 0060 , useZoomProxy{true} 0061 , zoomProxy{nullptr} 0062 , zoomTimer{nullptr} 0063 { } 0064 0065 ViewController* q; 0066 0067 View* view; 0068 QQuickItem* flickable; 0069 0070 KoCanvasController* canvasController; 0071 0072 float lastX; 0073 float lastY; 0074 bool ignoreOffsetChange; 0075 bool ignoreFlickableChange; 0076 0077 float newX; 0078 float newY; 0079 0080 float minimumZoom; 0081 bool minimumZoomFitsWidth; 0082 float zoom; 0083 float zoomChange; 0084 float maximumZoom; 0085 0086 bool useZoomProxy; 0087 QImage* zoomProxy; 0088 QTimer* zoomTimer; 0089 QVector3D zoomCenter; 0090 0091 QSizeF documentSize; 0092 }; 0093 0094 ViewController::ViewController(QQuickItem* parent) 0095 : QQuickItem{parent}, d{new Private} 0096 { 0097 setFlag(QQuickItem::ItemHasContents, true); 0098 0099 KoZoomMode::setMinimumZoom(d->minimumZoom); 0100 KoZoomMode::setMaximumZoom(d->maximumZoom); 0101 0102 d->zoomTimer = new QTimer{this}; 0103 d->zoomTimer->setInterval(500); 0104 d->zoomTimer->setSingleShot(true); 0105 connect(d->zoomTimer, &QTimer::timeout, this, &ViewController::zoomTimeout); 0106 } 0107 0108 ViewController::~ViewController() 0109 { 0110 delete d; 0111 } 0112 0113 View* ViewController::view() const 0114 { 0115 return d->view; 0116 } 0117 0118 void ViewController::setView(View* newView) 0119 { 0120 if(newView != d->view) { 0121 if(d->view) { 0122 if(d->view->document()) { 0123 if(d->canvasController) { 0124 disconnect(d->canvasController->proxyObject, &KoCanvasControllerProxyObject::moveDocumentOffset, this, &ViewController::documentOffsetChanged); 0125 } 0126 d->view->document()->disconnect(this); 0127 } 0128 disconnect(d->view, &View::documentChanged, this, &ViewController::documentChanged); 0129 } 0130 0131 d->view = newView; 0132 connect(d->view, &View::documentChanged, this, &ViewController::documentChanged); 0133 0134 if(d->view->document()) { 0135 documentChanged(); 0136 } else { 0137 d->canvasController = nullptr; 0138 } 0139 0140 emit viewChanged(); 0141 } 0142 } 0143 0144 QQuickItem* ViewController::flickable() const 0145 { 0146 return d->flickable; 0147 } 0148 0149 void ViewController::setFlickable(QQuickItem* item) 0150 { 0151 if(item != d->flickable) { 0152 if(item && item->metaObject()->indexOfProperty("contentWidth") == -1) { 0153 qWarning() << Q_FUNC_INFO << "Item does not seem to be a flickable, ignoring."; 0154 return; 0155 } 0156 0157 flickableWidthChanged(); 0158 0159 d->flickable = item; 0160 0161 if(item) { 0162 documentSizeChanged(); 0163 connect(d->flickable, SIGNAL(contentXChanged()), this, SLOT(contentPositionChanged()) ); 0164 connect(d->flickable, SIGNAL(contentYChanged()), this, SLOT(contentPositionChanged()) ); 0165 connect(d->flickable, &QQuickItem::widthChanged, this, &ViewController::flickableWidthChanged); 0166 } 0167 emit flickableChanged(); 0168 } 0169 } 0170 0171 float ViewController::minimumZoom() const 0172 { 0173 return d->minimumZoom; 0174 } 0175 0176 void ViewController::setMinimumZoom(float newValue) 0177 { 0178 if(newValue != d->minimumZoom) { 0179 d->minimumZoom = newValue; 0180 KoZoomMode::setMinimumZoom(newValue); 0181 emit minimumZoomChanged(); 0182 } 0183 } 0184 0185 bool ViewController::minimumZoomFitsWidth() const 0186 { 0187 return d->minimumZoomFitsWidth; 0188 } 0189 0190 void ViewController::setMinimumZoomFitsWidth(bool newValue) 0191 { 0192 if(newValue != d->minimumZoomFitsWidth) { 0193 d->minimumZoomFitsWidth = newValue; 0194 0195 flickableWidthChanged(); 0196 0197 emit minimumZoomFitsWidthChanged(); 0198 } 0199 } 0200 0201 float ViewController::zoom() const 0202 { 0203 if(d->useZoomProxy && d->zoomProxy) { 0204 return d->zoom + d->zoomChange; 0205 } 0206 0207 return d->zoom; 0208 } 0209 0210 void ViewController::setZoom(float newZoom) 0211 { 0212 newZoom = qBound(d->minimumZoom, newZoom, d->maximumZoom); 0213 if(newZoom != d->zoom) { 0214 if(d->useZoomProxy && d->view) { 0215 if(!d->zoomProxy) { 0216 d->zoomProxy = new QImage{int(d->flickable->width()), int(d->flickable->height()), QImage::Format_ARGB32}; 0217 0218 QPainter p; 0219 p.begin(d->zoomProxy); 0220 d->view->paint(&p); 0221 p.end(); 0222 0223 d->view->setVisible(false); 0224 } 0225 0226 if(d->zoomCenter.isNull()) { 0227 d->zoomCenter = QVector3D{ float(d->flickable->width()) / 2.f, float(d->flickable->height()) / 2.f, 0.f }; 0228 } 0229 d->zoomChange = newZoom - d->zoom; 0230 update(); 0231 d->zoomTimer->start(); 0232 } else { 0233 d->zoom = newZoom; 0234 0235 if(d->view) { 0236 d->view->setZoom(d->zoom); 0237 } 0238 } 0239 0240 emit zoomChanged(); 0241 } 0242 } 0243 0244 float ViewController::maximumZoom() const 0245 { 0246 return d->maximumZoom; 0247 } 0248 0249 void ViewController::setMaximumZoom(float newValue) 0250 { 0251 if(newValue != d->maximumZoom) { 0252 d->maximumZoom = newValue; 0253 KoZoomMode::setMaximumZoom(newValue); 0254 emit maximumZoomChanged(); 0255 } 0256 } 0257 0258 bool ViewController::useZoomProxy() const 0259 { void updateMinimumZoom(); 0260 0261 return d->useZoomProxy; 0262 } 0263 0264 void ViewController::setUseZoomProxy(bool proxy) 0265 { 0266 if(proxy != d->useZoomProxy) { 0267 d->useZoomProxy = proxy; 0268 0269 if(!d->useZoomProxy && d->zoomProxy) { 0270 delete d->zoomProxy; 0271 d->zoomProxy = nullptr; 0272 update(); 0273 } 0274 0275 emit useZoomProxyChanged(); 0276 } 0277 } 0278 0279 bool ViewController::event(QEvent* event) 0280 { 0281 switch(static_cast<int>(event->type())) { 0282 case ViewModeSwitchEvent::AboutToSwitchViewModeEvent: { 0283 ViewModeSynchronisationObject* syncObject = static_cast<ViewModeSwitchEvent*>(event)->synchronisationObject(); 0284 0285 if (d->canvasController) { 0286 syncObject->scrollBarValue = d->canvasController->documentOffset(); 0287 syncObject->zoomLevel = zoom(); 0288 syncObject->activeToolId = KoToolManager::instance()->activeToolId(); 0289 syncObject->shapes = d->canvasController->canvas()->shapeManager()->shapes(); 0290 syncObject->currentIndex = d->view->document()->currentIndex(); 0291 syncObject->initialized = true; 0292 } 0293 0294 return true; 0295 } 0296 case ViewModeSwitchEvent::SwitchedToTouchModeEvent: { 0297 ViewModeSynchronisationObject* syncObject = static_cast<ViewModeSwitchEvent*>(event)->synchronisationObject(); 0298 0299 if (d->canvasController && syncObject->initialized) { 0300 d->canvasController->canvas()->shapeManager()->setShapes(syncObject->shapes); 0301 0302 KoToolManager::instance()->switchToolRequested("PageToolFactory_ID"); 0303 qApp->processEvents(); 0304 0305 setZoom(syncObject->zoomLevel); 0306 0307 qApp->processEvents(); 0308 if(syncObject->scrollBarValue.isNull()) { 0309 d->view->document()->setCurrentIndex(syncObject->currentIndex); 0310 } 0311 else { 0312 d->canvasController->setScrollBarValue(syncObject->scrollBarValue); 0313 } 0314 emit d->view->document()->requestViewUpdate(); 0315 } 0316 0317 return true; 0318 } 0319 } 0320 return QQuickItem::event(event); 0321 } 0322 0323 void ViewController::zoomAroundPoint(float amount, float x, float y) 0324 { 0325 d->zoomCenter = QVector3D{-1 * x, y, 0.f}; 0326 setZoom(zoom() + amount); 0327 } 0328 0329 void ViewController::zoomToFitWidth(float width) 0330 { 0331 if( width < 0.01f ) 0332 return; 0333 0334 if( d->zoom < 0.01f ) 0335 return; 0336 0337 if( d->documentSize.width() > 0.f && d->documentSize.width() < 2e6 ) 0338 setZoom( width / ( d->documentSize.width() / d->zoom ) ); 0339 } 0340 0341 QSGNode* ViewController::updatePaintNode(QSGNode* node, QQuickItem::UpdatePaintNodeData* ) 0342 { 0343 if(!d->zoomProxy) { 0344 if(node) { 0345 delete node; 0346 } 0347 return 0; 0348 } 0349 0350 auto root = static_cast<QSGTransformNode*>(node); 0351 if(!root) { 0352 root = new QSGTransformNode{}; 0353 } 0354 0355 QMatrix4x4 itemToView; 0356 itemToView.translate(QVector3D{d->flickable->property("contentX").toFloat(), d->flickable->property("contentY").toFloat(), 0.f} + d->zoomCenter); 0357 root->setMatrix(itemToView); 0358 0359 auto center = static_cast<QSGTransformNode*>(root->firstChild()); 0360 if(!center) { 0361 center = new QSGTransformNode{}; 0362 root->appendChildNode(center); 0363 } 0364 0365 float newScale = 1.f + d->zoomChange; 0366 0367 QMatrix4x4 centerToView; 0368 centerToView.scale(newScale); 0369 0370 float newWidth = d->zoomProxy->width() * newScale; 0371 float newHeight = d->zoomProxy->height() * newScale; 0372 0373 float left = -newWidth * (d->zoomCenter.x() / newWidth); 0374 float top = -newHeight * (d->zoomCenter.y() / newHeight); 0375 0376 d->newX = -left; 0377 d->newY = -top; 0378 0379 centerToView.translate(left, top); 0380 center->setMatrix(centerToView); 0381 0382 auto texNode = static_cast<QSGSimpleTextureNode*>(center->firstChild()); 0383 if(!texNode) { 0384 texNode = new QSGSimpleTextureNode{}; 0385 center->appendChildNode(texNode); 0386 } 0387 texNode->setRect(d->zoomProxy->rect()); 0388 0389 auto texture = window()->createTextureFromImage(*d->zoomProxy); 0390 if(texNode->texture()) { 0391 delete texNode->texture(); 0392 } 0393 texNode->setTexture(texture); 0394 0395 return root; 0396 } 0397 0398 void ViewController::contentPositionChanged() 0399 { 0400 if(!d->canvasController || d->ignoreFlickableChange) 0401 return; 0402 0403 float newX = d->flickable->property("contentX").toFloat(); 0404 float newY = d->flickable->property("contentY").toFloat(); 0405 0406 //TODO: The rounding here causes some issues at edges. Need to investigate how to fix it. 0407 QPointF diff = QPointF{newX - d->lastX, newY - d->lastY}; 0408 d->ignoreOffsetChange = true; 0409 d->canvasController->pan(diff.toPoint()); 0410 d->ignoreOffsetChange = false; 0411 0412 d->lastX = newX; 0413 d->lastY = newY; 0414 0415 emit d->view->document()->requestViewUpdate(); 0416 } 0417 0418 void ViewController::documentChanged() 0419 { 0420 connect(d->view->document(), &Document::statusChanged, this, &ViewController::documentStatusChanged); 0421 connect(d->view->document(), &Document::documentSizeChanged, this, &ViewController::documentSizeChanged); 0422 documentStatusChanged(); 0423 documentSizeChanged(); 0424 } 0425 0426 void ViewController::documentSizeChanged() 0427 { 0428 if(d->view && d->view->document() && d->flickable) { 0429 if(!d->canvasController) { 0430 d->canvasController = d->view->document()->canvasController(); 0431 } 0432 0433 d->documentSize = d->view->document()->documentSize(); 0434 //Limit the size of this item to always be at least the same size 0435 //as the flickable, since otherwise we can end up with situations 0436 //where children cannot interact, for example when using the 0437 //LinkArea as a child of this item. 0438 setWidth(qMax(d->flickable->width() - 1, d->documentSize.width())); 0439 setHeight(qMax(d->flickable->height() - 1, d->documentSize.height())); 0440 0441 d->flickable->setProperty("contentWidth", width()); 0442 d->flickable->setProperty("contentHeight", height()); 0443 0444 flickableWidthChanged(); 0445 } 0446 } 0447 0448 void ViewController::documentStatusChanged() 0449 { 0450 if(d->view->document()->status() == DocumentStatus::Loaded) { 0451 d->canvasController = d->view->document()->canvasController(); 0452 connect(d->canvasController->proxyObject, &KoCanvasControllerProxyObject::moveDocumentOffset, this, &ViewController::documentOffsetChanged); 0453 } 0454 } 0455 0456 void ViewController::documentOffsetChanged(const QPoint& offset) 0457 { 0458 if(d->ignoreOffsetChange || !d->flickable) { 0459 return; 0460 } 0461 0462 d->ignoreFlickableChange = true; 0463 d->flickable->setProperty("contentX", offset.x()); 0464 d->flickable->setProperty("contentY", offset.y()); 0465 d->ignoreFlickableChange = false; 0466 0467 d->lastX = offset.x(); 0468 d->lastY = offset.y(); 0469 0470 QMetaObject::invokeMethod(d->flickable, "returnToBounds"); 0471 } 0472 0473 void ViewController::zoomTimeout() 0474 { 0475 delete d->zoomProxy; 0476 d->zoomProxy = nullptr; 0477 0478 float newZoom = d->zoom + d->zoomChange; 0479 0480 float oldX = d->flickable->property("contentX").toReal(); 0481 float oldY = d->flickable->property("contentY").toReal(); 0482 0483 0484 float z = 1.0 + d->zoomChange; 0485 d->flickable->setProperty("contentX", oldX + ((d->zoomCenter.x() * z - d->zoomCenter.x())) ); 0486 d->flickable->setProperty("contentY", oldY + ((d->zoomCenter.y() * z - d->zoomCenter.y())) ); 0487 0488 QMetaObject::invokeMethod(d->flickable, "returnToBounds"); 0489 0490 d->zoom = newZoom; 0491 0492 d->ignoreOffsetChange = true; 0493 d->view->setZoom(newZoom); 0494 d->ignoreOffsetChange = false; 0495 0496 d->view->setVisible(true); 0497 d->zoomCenter = QVector3D{}; 0498 update(); 0499 } 0500 0501 void ViewController::flickableWidthChanged() 0502 { 0503 if(d->minimumZoomFitsWidth && d->flickable && d->documentSize.width() > 0.f) { 0504 setMinimumZoom(d->flickable->width() / (d->documentSize.width() / d->zoom)); 0505 setZoom(d->zoom); 0506 } 0507 }