Warning, file /office/calligra/qtquick/CQPresentationCanvas.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  * This file is part of the KDE project
0003  *
0004  * SPDX-FileCopyrightText: 2013 Shantanu Tushar <shantanu@kde.org>
0005  * SPDX-FileCopyrightText: 2013 Arjen Hiemstra <ahiemstra@heimr.nl>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.0-or-later
0008  *
0009  */
0010 
0011 #include "CQPresentationCanvas.h"
0012 #include "CQPresentationView.h"
0013 #include "CQCanvasController.h"
0014 
0015 #include "gemini/ViewModeSwitchEvent.h"
0016 
0017 #include <QStyleOptionGraphicsItem>
0018 
0019 #include <KoPluginLoader.h>
0020 #include <KoDocumentEntry.h>
0021 #include <KoDocumentResourceManager.h>
0022 #include <KoShapeManager.h>
0023 #include <KoSelection.h>
0024 #include <KoTextEditor.h>
0025 #include <KoPart.h>
0026 #include <KoFindText.h>
0027 #include <KoCanvasBase.h>
0028 #include <KoToolManager.h>
0029 #include <KoZoomController.h>
0030 #include <KoZoomHandler.h>
0031 #include <KoPADocument.h>
0032 #include <KoPACanvasItem.h>
0033 #include <KoPAPageBase.h>
0034 #include <stage/part/KPrDocument.h>
0035 
0036 #include <KActionCollection>
0037 
0038 #include <QPluginLoader>
0039 #include <QMimeDatabase>
0040 #include <QGraphicsWidget>
0041 #include <QTextDocument>
0042 #include <QTextFrame>
0043 #include <QTextLayout>
0044 #include <QApplication>
0045 
0046 
0047 
0048 class CQPresentationCanvas::Private
0049 {
0050 public:
0051     Private() : canvasBase(0), view(0), document(0), part(0), currentSlide(0) { }
0052 
0053     KoCanvasBase* canvasBase;
0054     CQPresentationView* view;
0055     KPrDocument* document;
0056     KoPart* part;
0057 
0058     int currentSlide;
0059     QSizeF pageSize;
0060     QObjectList linkTargets;
0061 
0062     void updateLinkTargets()
0063     {
0064         qDeleteAll(linkTargets);
0065         linkTargets.clear();
0066 
0067         if (!view) {
0068             return;
0069         }
0070         foreach(const KoShape* shape, view->activePage()->shapes()) {
0071             if (!shape->hyperLink().isEmpty()) {
0072                 QObject * obj = new QObject(view);
0073                 obj->setProperty("linkRect", shape->boundingRect());
0074                 obj->setProperty("linkTarget", QUrl(shape->hyperLink()));
0075                 linkTargets.append(obj);
0076             }
0077         }
0078 
0079         QList<QTextDocument*> texts;
0080         KoFindText::findTextInShapes(view->activePage()->shapes(), texts);
0081         foreach(QTextDocument* text, texts) {
0082             QTextBlock block = text->rootFrame()->firstCursorPosition().block();
0083             for (; block.isValid(); block = block.next()) {
0084                 block.begin();
0085                 QTextBlock::iterator it;
0086                 for (it = block.begin(); !(it.atEnd()); ++it) {
0087                     QTextFragment fragment = it.fragment();
0088                     if (fragment.isValid()) {
0089                         QTextCharFormat format = fragment.charFormat();
0090                         if (format.isAnchor()) {
0091                             // This is an anchor, store target and position...
0092                             QObject * obj = new QObject(view);
0093                             QRectF rect = getFragmentPosition(block, fragment);
0094                             obj->setProperty("linkRect", canvasBase->viewConverter()->documentToView(rect));
0095                             obj->setProperty("linkTarget", QUrl(format.anchorHref()));
0096                             linkTargets.append(obj);
0097                         }
0098                     }
0099                 }
0100             }
0101         }
0102     }
0103 
0104     QRectF getFragmentPosition(const QTextBlock& block, const QTextFragment& fragment)
0105     {
0106         // TODO this only produces a position for the first part, if the link spans more than one line...
0107         // Need to sort that somehow, unfortunately probably by slapping this code into the above function.
0108         // For now leave it like this, more important things are needed.
0109         QTextLayout* layout = block.layout();
0110         QTextLine line = layout->lineForTextPosition(fragment.position() - block.position());
0111         if (!line.isValid())
0112         {
0113             // fragment has no valid position and consequently no line...
0114             return QRectF();
0115         }
0116         qreal top = line.position().y();
0117         qreal bottom = line.position().y() + line.height();
0118         qreal left = line.cursorToX(fragment.position() - block.position());
0119         qreal right = line.cursorToX((fragment.position() - block.position()) + fragment.length());
0120         QRectF fragmentPosition(QPointF(top, left), QPointF(bottom, right));
0121         return fragmentPosition.adjusted(layout->position().x(), layout->position().y(), 0, 0);
0122     }
0123 };
0124 
0125 CQPresentationCanvas::CQPresentationCanvas(QDeclarativeItem* parent)
0126     : CQCanvasBase(parent), d(new Private)
0127 {
0128 
0129 }
0130 
0131 CQPresentationCanvas::~CQPresentationCanvas()
0132 {
0133     d->part->removeMainWindow(d->part->currentMainwindow());
0134     KoToolManager::instance()->removeCanvasController(d->canvasBase->canvasController());
0135     delete d;
0136 }
0137 
0138 int CQPresentationCanvas::currentSlide() const
0139 {
0140     return d->currentSlide;
0141 }
0142 
0143 int CQPresentationCanvas::slideCount() const
0144 {
0145     return d->document->pageCount();
0146 }
0147 
0148 QObjectList CQPresentationCanvas::linkTargets() const
0149 {
0150     return d->linkTargets;
0151 }
0152 
0153 KPrDocument* CQPresentationCanvas::document() const
0154 {
0155     return d->document;
0156 }
0157 
0158 QObject* CQPresentationCanvas::doc() const
0159 {
0160     return d->document;
0161 }
0162 
0163 QObject* CQPresentationCanvas::part() const
0164 {
0165     return d->part;
0166 }
0167 
0168 QSizeF CQPresentationCanvas::pageSize() const
0169 {
0170     return d->pageSize;
0171 }
0172 
0173 void CQPresentationCanvas::setCurrentSlide(int slide)
0174 {
0175     slide = qBound(0, slide, d->document->pageCount() - 1);
0176     if (slide != d->currentSlide) {
0177         d->currentSlide = slide;
0178         d->view->doUpdateActivePage(d->document->pageByIndex(slide, false));
0179         d->pageSize = d->view->activePage()->size();
0180         emit currentSlideChanged();
0181         d->updateLinkTargets();
0182         emit linkTargetsChanged();
0183     }
0184 }
0185 
0186 void CQPresentationCanvas::render(QPainter* painter, const QRectF& target)
0187 {
0188     QStyleOptionGraphicsItem option;
0189     option.exposedRect = target;
0190     option.rect = target.toAlignedRect();
0191     d->canvasBase->canvasItem()->paint(painter, &option);
0192 }
0193 
0194 QObject* CQPresentationCanvas::textEditor() const
0195 {
0196     if (d->canvasBase) {
0197         return KoTextEditor::getTextEditorFromCanvas(d->canvasBase);
0198     }
0199     return 0;
0200 }
0201 
0202 void CQPresentationCanvas::deselectEverything()
0203 {
0204     KoTextEditor* editor = KoTextEditor::getTextEditorFromCanvas(d->canvasBase);
0205     if (editor) {
0206         editor->clearSelection();
0207     }
0208     d->canvasBase->shapeManager()->selection()->deselectAll();
0209 }
0210 
0211 qreal CQPresentationCanvas::shapeTransparency() const
0212 {
0213     if (d->canvasBase && d->canvasBase->shapeManager()) {
0214         KoShape* shape = d->canvasBase->shapeManager()->selection()->firstSelectedShape();
0215         if (shape) {
0216             return shape->transparency();
0217         }
0218     }
0219     return CQCanvasBase::shapeTransparency();
0220 }
0221 
0222 void CQPresentationCanvas::setShapeTransparency(qreal newTransparency)
0223 {
0224     if (d->canvasBase && d->canvasBase->shapeManager()) {
0225         KoShape* shape = d->canvasBase->shapeManager()->selection()->firstSelectedShape();
0226         if (shape) {
0227             if (!qFuzzyCompare(1 + shape->transparency(), 1 + newTransparency)) {
0228                 shape->setTransparency(newTransparency);
0229                 CQCanvasBase::setShapeTransparency(newTransparency);
0230             }
0231         }
0232     }
0233 }
0234 
0235 void CQPresentationCanvas::openFile(const QString& uri)
0236 {
0237     emit loadingBegun();
0238 
0239     KoDocumentEntry entry;
0240     QList<QPluginLoader*> pluginLoaders = KoPluginLoader::pluginLoaders("calligra/parts");
0241     Q_FOREACH (QPluginLoader *loader, pluginLoaders) {
0242         if (loader->fileName().contains(QLatin1String("stagepart"))) {
0243             entry = KoDocumentEntry(loader);
0244             pluginLoaders.removeOne(loader);
0245             break;
0246         }
0247     }
0248     qDeleteAll(pluginLoaders);
0249     if (entry.isEmpty()) {
0250         qWarning("Unable to load Stage plugin, aborting!");
0251         return;
0252     }
0253 
0254     // QT5TODO: ownership of d->part unclear
0255     d->part = entry.createKoPart();
0256     d->document = dynamic_cast<KPrDocument*>(d->part->document());
0257     d->document->setAutoSave(0);
0258     d->document->setCheckAutoSaveFile(false);
0259     if (uri.endsWith(QLatin1String("otp"), Qt::CaseInsensitive)) {
0260         QUrl url(uri);
0261         bool ok = d->document->loadNativeFormat(url.toLocalFile());
0262         d->document->setModified(false);
0263         d->document->undoStack()->clear();
0264 
0265         if (ok) {
0266             QString mimeType = QMimeDatabase().mimeTypeForUrl(url).name();
0267             // in case this is a open document template remove the -template from the end
0268             mimeType.remove( QRegExp( "-template$" ) );
0269             d->document->setMimeTypeAfterLoading(mimeType);
0270             d->document->resetURL();
0271             d->document->setEmpty();
0272         } else {
0273             // some kind of error reporting thing here... failed to load template, tell the user
0274             // why their canvas is so terribly empty.
0275             d->document->initEmpty();
0276         }
0277     } else {
0278         d->document->openUrl (QUrl (uri));
0279     }
0280 
0281     d->document->setModified(false);
0282     qApp->processEvents();
0283 
0284     KoPACanvasItem *paCanvasItem = static_cast<KoPACanvasItem*>(d->part->canvasItem(d->part->document()));
0285     d->canvasBase = paCanvasItem;
0286     createAndSetCanvasControllerOn(d->canvasBase);
0287 
0288     d->view = new CQPresentationView(canvasController(), static_cast<KoPACanvasBase*>(d->canvasBase), dynamic_cast<KPrDocument*>(d->document));
0289     paCanvasItem->setView(d->view);
0290 
0291     d->canvasBase->resourceManager()->setResource(KoDocumentResourceManager::HandleRadius, 9);
0292     d->canvasBase->resourceManager()->setResource(KoDocumentResourceManager::GrabSensitivity, 9);
0293 
0294     createAndSetZoomController(d->canvasBase);
0295     d->view->setZoomController(zoomController());
0296     d->view->connectToZoomController();
0297 
0298     QGraphicsWidget *graphicsWidget = dynamic_cast<QGraphicsWidget*>(d->canvasBase);
0299     graphicsWidget->setParentItem(this);
0300     graphicsWidget->installEventFilter(this);
0301     graphicsWidget->setVisible(true);
0302     graphicsWidget->setGeometry(x(), y(), width(), height());
0303 
0304     if (d->document->pageCount() > 0) {
0305         d->view->doUpdateActivePage(d->document->pageByIndex(0, false));
0306         d->pageSize = d->view->activePage()->size();
0307         emit currentSlideChanged();
0308 
0309         d->updateLinkTargets();
0310         emit linkTargetsChanged();
0311     }
0312 
0313     emit documentChanged();
0314     emit loadingFinished();
0315 }
0316 
0317 void CQPresentationCanvas::createAndSetCanvasControllerOn(KoCanvasBase* canvas)
0318 {
0319     //TODO: pass a proper action collection
0320     CQCanvasController *controller = new CQCanvasController(new KActionCollection(this));
0321     setCanvasController(controller);
0322     controller->setCanvas(canvas);
0323     KoToolManager::instance()->addController (controller);
0324 }
0325 
0326 void CQPresentationCanvas::createAndSetZoomController(KoCanvasBase* canvas)
0327 {
0328     KoZoomHandler* zoomHandler = static_cast<KoZoomHandler*> (canvas->viewConverter());
0329     setZoomController(new KoZoomController(canvasController(),
0330                                            zoomHandler,
0331                                            new KActionCollection(this)));
0332 
0333     KoPACanvasItem* canvasItem = static_cast<KoPACanvasItem*>(canvas);
0334 
0335     // update the canvas whenever we scroll, the canvas controller must emit this signal on scrolling/panning
0336     connect (canvasController()->proxyObject,
0337                 SIGNAL(moveDocumentOffset(QPoint)), canvasItem, SLOT(slotSetDocumentOffset(QPoint)));
0338     // whenever the size of the document viewed in the canvas changes, inform the zoom controller
0339     connect (canvasItem, SIGNAL(documentSize(QSize)), this, SLOT(updateDocumentSize(QSize)));
0340     canvasItem->updateSize();
0341     canvasItem->update();
0342 }
0343 
0344 void CQPresentationCanvas::updateDocumentSize(const QSize& size)
0345 {
0346     zoomController()->setDocumentSize(d->canvasBase->viewConverter()->viewToDocument(size), false);
0347 }
0348 
0349 bool CQPresentationCanvas::event(QEvent* event)
0350 {    switch(static_cast<int>(event->type())) {
0351         case ViewModeSwitchEvent::AboutToSwitchViewModeEvent: {
0352             ViewModeSynchronisationObject* syncObject = static_cast<ViewModeSwitchEvent*>(event)->synchronisationObject();
0353 
0354             // Simplest of transfer - no zoom transfer for presentations, just current slide
0355             syncObject->currentSlide = d->currentSlide;
0356             syncObject->shapes = d->canvasBase->shapeManager()->shapes();
0357             syncObject->initialized = true;
0358 
0359             return true;
0360         }
0361         case ViewModeSwitchEvent::SwitchedToTouchModeEvent: {
0362             ViewModeSynchronisationObject* syncObject = static_cast<ViewModeSwitchEvent*>(event)->synchronisationObject();
0363 
0364             if (syncObject->initialized) {
0365                 d->canvasBase->shapeManager()->setShapes(syncObject->shapes);
0366 
0367                 zoomController()->setZoom(KoZoomMode::ZOOM_PAGE, 1.0);
0368                 zoomController()->zoomAction()->zoomOut();
0369 
0370                 setCurrentSlide(syncObject->currentSlide);
0371                 qApp->processEvents();
0372 
0373                 KoToolManager::instance()->switchToolRequested("InteractionTool");
0374             }
0375 
0376             return true;
0377         }
0378 //         case KisTabletEvent::TabletPressEx:
0379 //         case KisTabletEvent::TabletReleaseEx:
0380 //             emit interactionStarted();
0381 //             d->canvas->inputManager()->eventFilter(this, event);
0382 //             return true;
0383 //         case KisTabletEvent::TabletMoveEx:
0384 //             d->tabletEventCount++; //Note that this will wraparound at some point; This is intentional.
0385 // #ifdef Q_OS_X11
0386 //             if (d->tabletEventCount % 2 == 0)
0387 // #endif
0388 //                 d->canvas->inputManager()->eventFilter(this, event);
0389 //             return true;
0390         default:
0391             break;
0392     }
0393     return QDeclarativeItem::event( event );
0394 }
0395 
0396 void CQPresentationCanvas::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
0397 {
0398     if (d->canvasBase) {
0399         QGraphicsWidget *widget = dynamic_cast<QGraphicsWidget*>(d->canvasBase);
0400         if (widget) {
0401             widget->setGeometry(newGeometry);
0402         }
0403     }
0404     QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
0405 }