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

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 "TextDocumentImpl.h"
0024 
0025 #include <QGraphicsWidget>
0026 
0027 #include <kactioncollection.h>
0028 
0029 #include <KWPart.h>
0030 #include <KWDocument.h>
0031 #include <KWCanvasItem.h>
0032 #include <KoColumns.h>
0033 #include <KoFindText.h>
0034 #include <KoShape.h>
0035 #include <KoShapeContainer.h>
0036 #include <KoToolManager.h>
0037 #include <KoUnit.h>
0038 #include <KoZoomController.h>
0039 #include <KoZoomHandler.h>
0040 
0041 #include <QTextDocument>
0042 #include <QTextFrame>
0043 #include <QTextLayout>
0044 #include <QDebug>
0045 #include <QPointer>
0046 #include <QUrlQuery>
0047 
0048 #include "ComponentsKoCanvasController.h"
0049 #include <libs/textlayout/KoTextShapeData.h>
0050 
0051 using namespace Calligra::Components;
0052 
0053 class TextDocumentImpl::Private
0054 {
0055 public:
0056     Private() : part{nullptr}, document{nullptr}
0057     { }
0058 
0059     QPointer<KWPart> part;
0060     QPointer<KWDocument> document;
0061     QPointer<KWCanvasItem> canvas;
0062     QTimer indexChangedDelay;
0063 
0064     QList< QPair< QRectF, QUrl > > links;
0065 
0066     QList<KoShape*> deepShapeFind(QList<KoShape*> shapes)
0067     {
0068         QList<KoShape*> allShapes;
0069         foreach(KoShape* shape, shapes) {
0070             allShapes.append(shape);
0071             KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0072             if(container) {
0073                 allShapes.append(deepShapeFind(container->shapes()));
0074             }
0075         }
0076         return allShapes;
0077     }
0078 
0079     void updateLinkTargets()
0080     {
0081         links.clear();
0082 
0083         if(!canvas || !canvas->shapeManager())
0084             return;
0085 
0086         foreach(const KoShape* shape, canvas->shapeManager()->shapes()) {
0087             if(!shape->hyperLink().isEmpty()) {
0088                 QRectF rect = shape->boundingRect();
0089                 for (KoShapeContainer* parent = shape->parent();
0090                      parent; parent = parent->parent()) {
0091                     rect.translate(parent->position());
0092                 }
0093                 links.append(QPair<QRectF, QUrl>(rect, QUrl(shape->hyperLink())));
0094             }
0095         }
0096 
0097         QList<QTextDocument*> texts;
0098         KoFindText::findTextInShapes(canvas->shapeManager()->shapes(), texts);
0099         QList<KoShape*> allShapes = deepShapeFind(canvas->shapeManager()->shapes());
0100         foreach(QTextDocument* text, texts) {
0101             QTextBlock block = text->rootFrame()->firstCursorPosition().block();
0102             for (; block.isValid(); block = block.next()) {
0103                 block.begin();
0104                 QTextBlock::iterator it;
0105                 for (it = block.begin(); !(it.atEnd()); ++it) {
0106                     QTextFragment fragment = it.fragment();
0107                     if (fragment.isValid()) {
0108                         QTextCharFormat format = fragment.charFormat();
0109                         if(format.isAnchor()) {
0110                             // This is an anchor, store target and position...
0111                             QRectF rect = getFragmentPosition(block, fragment);
0112                             foreach(KoShape* shape, allShapes) {
0113                                 KoTextShapeData *shapeData = dynamic_cast<KoTextShapeData*>(shape->userData());
0114                                 if (!shapeData)
0115                                     continue;
0116                                 if(shapeData->document() == text)
0117                                 {
0118                                     rect.translate(shape->position());
0119                                     for (KoShapeContainer* parent = shape->parent();
0120                                          parent; parent = parent->parent()) {
0121                                         rect.translate(parent->position());
0122                                     }
0123                                     break;
0124                                 }
0125                             }
0126                             KWPage page = document->pageManager()->page(rect.top());
0127                             //rect.translate(page.rightMargin(), page.topMargin());
0128                             //rect = canvas->viewConverter()->documentToView(rect);
0129                             //rect.translate(0, page.pageNumber() * (page.topMargin() + page.bottomMargin()) + 20);
0130                             rect.translate(0, (page.pageNumber() - 1) * (page.topMargin() + 20));
0131                             links.append(QPair<QRectF, QUrl>(canvas->viewConverter()->documentToView(rect), QUrl(format.anchorHref())));
0132                         }
0133                     }
0134                 }
0135             }
0136         }
0137         qDebug() << links;
0138     }
0139 
0140     QRectF getFragmentPosition(QTextBlock block, QTextFragment fragment)
0141     {
0142         // TODO this only produces a position for the first part, if the link spans more than one line...
0143         // Need to sort that somehow, unfortunately probably by slapping this code into the above function.
0144         // For now leave it like this, more important things are needed.
0145         QTextLayout* layout = block.layout();
0146         QTextLine line = layout->lineForTextPosition(fragment.position() - block.position());
0147         if(!line.isValid())
0148         {
0149             // fragment has no valid position and consequently no line...
0150             return QRectF();
0151         }
0152         qreal top = line.position().y() + (line.height() / 2);
0153         qreal bottom = top + line.height();
0154         qreal left = line.cursorToX(fragment.position() - block.position());
0155         qreal right = line.cursorToX((fragment.position() - block.position()) + fragment.length());
0156         QRectF fragmentPosition(QPointF(left, top), QPointF(right, bottom));
0157         return fragmentPosition.adjusted(layout->position().x(), layout->position().y(), 0, 0);
0158     }
0159 
0160     static const float wiggleFactor;
0161 };
0162 
0163 const float Calligra::Components::TextDocumentImpl::Private::wiggleFactor{ 4.f };
0164 
0165 TextDocumentImpl::TextDocumentImpl(QObject* parent)
0166     : DocumentImpl{parent}, d{new Private}
0167 {
0168     setDocumentType(DocumentType::TextDocument);
0169     d->indexChangedDelay.setInterval(0);
0170     connect(&d->indexChangedDelay, SIGNAL(timeout()), this, SIGNAL(currentIndexChanged()));
0171 }
0172 
0173 TextDocumentImpl::~TextDocumentImpl()
0174 {
0175     delete d;
0176 }
0177 
0178 bool TextDocumentImpl::load(const QUrl& url)
0179 {
0180     delete d->part;
0181     delete d->document;
0182 
0183     d->part = new KWPart{this};
0184     d->document = new KWDocument{d->part};
0185     setKoDocument(d->document);
0186     d->part->setDocument(d->document);
0187 
0188     d->document->setAutoSave(0);
0189     d->document->setCheckAutoSaveFile(false);
0190 
0191     bool retval = false;
0192     if (url.scheme() == QStringLiteral("newfile")) {
0193         QUrlQuery query(url);
0194 
0195         d->document->initEmpty();
0196         KWPageStyle style = d->document->pageManager()->defaultPageStyle();
0197         Q_ASSERT(style.isValid());
0198 
0199         KoColumns columns;
0200         columns.count = query.queryItemValue("columncount").toInt();
0201         columns.gapWidth = query.queryItemValue("columngap").toDouble();
0202         style.setColumns(columns);
0203 
0204         KoPageLayout layout = style.pageLayout();
0205         layout.format = KoPageFormat::formatFromString(query.queryItemValue("pageformat"));
0206         layout.orientation = (KoPageFormat::Orientation)query.queryItemValue("pageorientation").toInt();
0207         layout.height = MM_TO_POINT(query.queryItemValue("height").toDouble());
0208         layout.width = MM_TO_POINT(query.queryItemValue("width").toDouble());
0209         if (query.queryItemValue("facingpages").toInt() == 1) {
0210             layout.bindingSide = MM_TO_POINT(query.queryItemValue("leftmargin").toDouble());
0211             layout.pageEdge = MM_TO_POINT(query.queryItemValue("rightmargin").toDouble());
0212             layout.leftMargin = layout.rightMargin = -1;
0213         }
0214         else {
0215             layout.bindingSide = layout.pageEdge = -1;
0216             layout.leftMargin = MM_TO_POINT(query.queryItemValue("leftmargin").toDouble());
0217             layout.rightMargin = MM_TO_POINT(query.queryItemValue("rightmargin").toDouble());
0218         }
0219         layout.topMargin = MM_TO_POINT(query.queryItemValue("topmargin").toDouble());
0220         layout.bottomMargin = MM_TO_POINT(query.queryItemValue("bottommargin").toDouble());
0221         style.setPageLayout(layout);
0222 
0223         d->document->setUnit(KoUnit::fromSymbol(query.queryItemValue("unit")));
0224         d->document->relayout();
0225         retval = true;
0226     }
0227     else if (url.scheme() == QStringLiteral("template")) {
0228         // Nip away the manually added template:// bit of the uri passed from the caller
0229         bool ok = d->document->loadNativeFormat(url.toString().mid(11));
0230         d->document->setModified(false);
0231         d->document->undoStack()->clear();
0232 
0233         if (ok) {
0234             QString mimeType = QMimeDatabase().mimeTypeForUrl(url).name();
0235             // in case this is a open document template remove the -template from the end
0236             mimeType.remove( QRegExp( "-template$" ) );
0237             d->document->setMimeTypeAfterLoading(mimeType);
0238             d->document->resetURL();
0239             d->document->setEmpty();
0240         } else {
0241             d->document->showLoadingErrorDialog();
0242             d->document->initEmpty();
0243         }
0244         retval = true;
0245     }
0246     else {
0247         retval = d->document->openUrl(url);
0248     }
0249     qDebug() << "Attempting to open" << url << "and our success was" << retval;
0250 
0251     d->canvas = static_cast<KWCanvasItem*>(d->part->canvasItem(d->document));
0252 
0253     createAndSetCanvasController(d->canvas);
0254     createAndSetZoomController(d->canvas);
0255     zoomController()->setPageSize(d->document->pageManager()->begin().rect().size());
0256     connect(d->canvas, SIGNAL(documentSize(QSizeF)), zoomController(), SLOT(setDocumentSize(QSizeF)));
0257 
0258     d->canvas->updateSize();
0259 
0260     setCanvas(d->canvas);
0261     connect(canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), &d->indexChangedDelay, SLOT(start()));
0262 
0263     d->updateLinkTargets();
0264 
0265     return retval;
0266 }
0267 
0268 int TextDocumentImpl::currentIndex()
0269 {
0270     if(!d->canvas || !d->canvas->viewConverter()) {
0271         return 0;
0272     }
0273     QPointF newPoint = d->canvas->viewConverter()->viewToDocument(canvasController()->documentOffset());
0274     KWPage page = d->document->pageManager()->page(newPoint.y());
0275     return page.pageNumber();
0276 }
0277 
0278 void TextDocumentImpl::setCurrentIndex(int newValue)
0279 {
0280     KWPage newPage = d->document->pageManager()->page(newValue + 1);
0281     QRectF newRect = d->canvas->viewConverter()->documentToView(newPage.rect());
0282     canvasController()->setScrollBarValue(newRect.topLeft().toPoint());
0283     emit requestViewUpdate();
0284     emit currentIndexChanged();
0285 }
0286 
0287 int TextDocumentImpl::indexCount() const
0288 {
0289     return d->document->pageCount();
0290 }
0291 
0292 QUrl TextDocumentImpl::urlAtPoint(QPoint point)
0293 {
0294     qDebug() << Q_FUNC_INFO << point + (d->canvas->documentOffset() / zoomController()->zoomAction()->effectiveZoom());
0295     for( const QPair< QRectF, QUrl >& link : d->links )
0296     {
0297         QRectF hitTarget{
0298             link.first.x() - Private::wiggleFactor,
0299             link.first.y() - Private::wiggleFactor,
0300             link.first.width() + Private::wiggleFactor * 2,
0301             link.first.height() + Private::wiggleFactor * 2
0302         };
0303 
0304         if( hitTarget.contains( point + (d->canvas->documentOffset() / zoomController()->zoomAction()->effectiveZoom()) ) )
0305             return link.second;
0306     }
0307     return QUrl();
0308 }
0309 
0310 QObject* TextDocumentImpl::part() const
0311 {
0312     return d->part;
0313 }