File indexing completed on 2024-05-12 16:28:23

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 "PresentationImpl.h"
0024 #include "PresentationKoPAView.h"
0025 
0026 #include <QGraphicsWidget>
0027 #include <QMimeDatabase>
0028 #include <QTextDocument>
0029 #include <QTextFrame>
0030 #include <QTextLayout>
0031 #include <QDebug>
0032 
0033 #include <stage/part/KPrPart.h>
0034 #include <stage/part/KPrDocument.h>
0035 #include <libs/textlayout/KoTextShapeData.h>
0036 #include <KoFindText.h>
0037 #include <KoPACanvasItem.h>
0038 #include <KoPAPageBase.h>
0039 #include <KoShape.h>
0040 #include <KoZoomController.h>
0041 
0042 using namespace Calligra::Components;
0043 
0044 class PresentationImpl::Private
0045 {
0046 public:
0047     Private() : part{nullptr}, document{nullptr}
0048     { }
0049 
0050     KPrPart* part;
0051     KPrDocument* document;
0052 
0053     PresentationKoPAView* koPaView;
0054 
0055     QList< QPair< QRectF, QUrl > > links;
0056 
0057     QList<KoShape*> deepShapeFind(QList<KoShape*> shapes)
0058     {
0059         QList<KoShape*> allShapes;
0060         foreach(KoShape* shape, shapes) {
0061             allShapes.append(shape);
0062             KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0063             if(container) {
0064                 allShapes.append(deepShapeFind(container->shapes()));
0065             }
0066         }
0067         return allShapes;
0068     }
0069 
0070     void updateLinkTargets()
0071     {
0072         links.clear();
0073 
0074         if(!koPaView || !koPaView->activePage())
0075             return;
0076 
0077         foreach(const KoShape* shape, koPaView->activePage()->shapes()) {
0078             if(!shape->hyperLink().isEmpty()) {
0079                 QRectF rect = shape->boundingRect();
0080                 for (KoShapeContainer* parent = shape->parent();
0081                      parent; parent = parent->parent()) {
0082                     rect.translate(parent->position());
0083                 }
0084                 links.append(QPair<QRectF, QUrl>(rect, QUrl(shape->hyperLink())));
0085             }
0086         }
0087 
0088         QList<QTextDocument*> texts;
0089         KoFindText::findTextInShapes(koPaView->activePage()->shapes(), texts);
0090         QList<KoShape*> allShapes = deepShapeFind(koPaView->activePage()->shapes());
0091         foreach(QTextDocument* text, texts) {
0092             QTextBlock block = text->rootFrame()->firstCursorPosition().block();
0093             for (; block.isValid(); block = block.next()) {
0094                 block.begin();
0095                 QTextBlock::iterator it;
0096                 for (it = block.begin(); !(it.atEnd()); ++it) {
0097                     QTextFragment fragment = it.fragment();
0098                     if (fragment.isValid()) {
0099                         QTextCharFormat format = fragment.charFormat();
0100                         if(format.isAnchor()) {
0101                             // This is an anchor, store target and position...
0102                             QRectF rect = getFragmentPosition(block, fragment);
0103                             foreach(KoShape* shape, allShapes) {
0104                                 KoTextShapeData *shapeData = dynamic_cast<KoTextShapeData*>(shape->userData());
0105                                 if (!shapeData)
0106                                     continue;
0107                                 if(shapeData->document() == text)
0108                                 {
0109                                     rect.translate(shape->position());
0110                                     for (KoShapeContainer* parent = shape->parent();
0111                                          parent; parent = parent->parent()) {
0112                                         rect.translate(parent->position());
0113                                     }
0114                                     break;
0115                                 }
0116                             }
0117                             links.append(QPair<QRectF, QUrl>(koPaView->kopaCanvas()->viewConverter()->documentToView(rect), QUrl(format.anchorHref())));
0118                         }
0119                     }
0120                 }
0121             }
0122         }
0123         qDebug() << "Discovered the following links in the slide:" << links;
0124     }
0125 
0126     QRectF getFragmentPosition(QTextBlock block, QTextFragment fragment)
0127     {
0128         // TODO this only produces a position for the first part, if the link spans more than one line...
0129         // Need to sort that somehow, unfortunately probably by slapping this code into the above function.
0130         // For now leave it like this, more important things are needed.
0131         QTextLayout* layout = block.layout();
0132         QTextLine line = layout->lineForTextPosition(fragment.position() - block.position());
0133         if(!line.isValid())
0134         {
0135             // fragment has no valid position and consequently no line...
0136             return QRectF();
0137         }
0138         qreal top = line.position().y() + (line.height() / 2);
0139         qreal bottom = top + line.height();
0140         qreal left = line.cursorToX(fragment.position() - block.position());
0141         qreal right = line.cursorToX((fragment.position() - block.position()) + fragment.length());
0142         QRectF fragmentPosition(QPointF(left, top), QPointF(right, bottom));
0143         return fragmentPosition.adjusted(layout->position().x(), layout->position().y(), 0, 0);
0144     }
0145 
0146     static const float wiggleFactor;
0147 };
0148 
0149 const float Calligra::Components::PresentationImpl::Private::wiggleFactor{ 4.f };
0150 
0151 PresentationImpl::PresentationImpl(QObject* parent)
0152     : DocumentImpl{parent}, d{new Private}
0153 {
0154     setDocumentType(DocumentType::Presentation);
0155 }
0156 
0157 PresentationImpl::~PresentationImpl()
0158 {
0159     delete d;
0160 }
0161 
0162 bool PresentationImpl::load(const QUrl& url)
0163 {
0164     delete d->part;
0165     delete d->document;
0166 
0167     d->part = new KPrPart{this};
0168     d->document = new KPrDocument{d->part};
0169     setKoDocument(d->document);
0170     d->part->setDocument(d->document);
0171 
0172     bool retval = false;
0173     if (url.scheme() == QStringLiteral("template")) {
0174         bool ok = d->document->loadNativeFormat(url.toString().mid(11));
0175         d->document->setModified(false);
0176         d->document->undoStack()->clear();
0177 
0178         if (ok) {
0179             QString mimeType = QMimeDatabase().mimeTypeForUrl(url).name();
0180             // in case this is a open document template remove the -template from the end
0181             mimeType.remove( QRegExp( "-template$" ) );
0182             d->document->setMimeTypeAfterLoading(mimeType);
0183             d->document->resetURL();
0184             d->document->setEmpty();
0185         } else {
0186             // some kind of error reporting thing here... failed to load template, tell the user
0187             // why their canvas is so terribly empty.
0188             d->document->initEmpty();
0189         }
0190         d->document->setModified(true);
0191         retval = true;
0192     } else {
0193         retval = d->document->openUrl(url);
0194     }
0195 
0196     auto canvas = static_cast<KoPACanvasItem*>(d->part->canvasItem(d->document));
0197 
0198     createAndSetCanvasController(canvas);
0199 
0200     d->koPaView = new PresentationKoPAView(canvasController(), canvas, d->document);
0201     canvas->setView(d->koPaView);
0202 
0203     createAndSetZoomController(canvas);
0204     d->koPaView->setZoomController(zoomController());
0205     d->koPaView->connectToZoomController();
0206 
0207     KoPAPageBase* page = d->document->pageByIndex(0, false);
0208     if(page) {
0209         d->koPaView->doUpdateActivePage(page);
0210     }
0211     d->updateLinkTargets();
0212 
0213     setCanvas(canvas);
0214 
0215     return retval;
0216 }
0217 
0218 int PresentationImpl::currentIndex()
0219 {
0220     if (d->document && d->koPaView && d->koPaView->activePage()) {
0221         return d->document->pageIndex(d->koPaView->activePage());
0222     } else {
0223         return -1;
0224     }
0225 }
0226 
0227 void PresentationImpl::setCurrentIndex(int newValue)
0228 {
0229     if(newValue != currentIndex()) {
0230         d->koPaView->doUpdateActivePage(d->document->pageByIndex(newValue, false));
0231         d->updateLinkTargets();
0232         emit requestViewUpdate();
0233         emit currentIndexChanged();
0234     }
0235 }
0236 
0237 int PresentationImpl::indexCount() const
0238 {
0239     return d->document->pageCount();
0240 }
0241 
0242 QUrl PresentationImpl::urlAtPoint(QPoint point)
0243 {
0244     for( const QPair< QRectF, QUrl >& link : d->links )
0245     {
0246         QRectF hitTarget{
0247             link.first.x() - Private::wiggleFactor,
0248             link.first.y() - Private::wiggleFactor,
0249             link.first.width() + Private::wiggleFactor * 2,
0250             link.first.height() + Private::wiggleFactor * 2
0251         };
0252 
0253         if( hitTarget.contains( point ) )
0254         {
0255             return link.second;
0256         }
0257     }
0258     return QUrl();
0259 }
0260 
0261 QObject* PresentationImpl::part() const
0262 {
0263     return d->part;
0264 }