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 }