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 }