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

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