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 "SpreadsheetImpl.h" 0024 0025 #include <QGraphicsWidget> 0026 #include <QTextDocument> 0027 #include <QTextFrame> 0028 #include <QTextLayout> 0029 0030 #include <KoToolManager.h> 0031 0032 #include <KoFindText.h> 0033 #include <KoShape.h> 0034 #include <KoShapeContainer.h> 0035 #include <KoZoomController.h> 0036 #include <KoViewConverter.h> 0037 #include <sheets/part/Part.h> 0038 #include <sheets/part/Doc.h> 0039 #include <sheets/part/CanvasItem.h> 0040 #include <sheets/Map.h> 0041 #include <sheets/Sheet.h> 0042 #include <libs/textlayout/KoTextShapeData.h> 0043 #include <Damages.h> 0044 0045 using namespace Calligra::Components; 0046 0047 class SpreadsheetImpl::Private 0048 { 0049 public: 0050 Private() : part{nullptr}, document{nullptr} 0051 { } 0052 0053 Calligra::Sheets::Part* part; 0054 Calligra::Sheets::Doc* document; 0055 Calligra::Sheets::CanvasItem* canvas; 0056 int currentSheet; 0057 0058 QList< QPair< QRectF, QUrl > > links; 0059 0060 QList<KoShape*> deepShapeFind(QList<KoShape*> shapes) 0061 { 0062 QList<KoShape*> allShapes; 0063 foreach(KoShape* shape, shapes) { 0064 allShapes.append(shape); 0065 KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape); 0066 if(container) { 0067 allShapes.append(deepShapeFind(container->shapes())); 0068 } 0069 } 0070 return allShapes; 0071 } 0072 0073 void updateLinkTargets() 0074 { 0075 links.clear(); 0076 0077 if(!canvas) 0078 return; 0079 foreach(const KoShape* shape, canvas->activeSheet()->shapes()) { 0080 if(!shape->hyperLink().isEmpty()) { 0081 QRectF rect = shape->boundingRect(); 0082 for (KoShapeContainer* parent = shape->parent(); 0083 parent; parent = parent->parent()) { 0084 rect.translate(parent->position()); 0085 } 0086 links.append(QPair<QRectF, QUrl>(rect, QUrl(shape->hyperLink()))); 0087 } 0088 } 0089 0090 QList<QTextDocument*> texts; 0091 KoFindText::findTextInShapes(canvas->activeSheet()->shapes(), texts); 0092 QList<KoShape*> allShapes = deepShapeFind(canvas->activeSheet()->shapes()); 0093 foreach(QTextDocument* text, texts) { 0094 QTextBlock block = text->rootFrame()->firstCursorPosition().block(); 0095 for (; block.isValid(); block = block.next()) { 0096 block.begin(); 0097 QTextBlock::iterator it; 0098 for (it = block.begin(); !(it.atEnd()); ++it) { 0099 QTextFragment fragment = it.fragment(); 0100 if (fragment.isValid()) { 0101 QTextCharFormat format = fragment.charFormat(); 0102 if(format.isAnchor()) { 0103 // This is an anchor, store target and position... 0104 QRectF rect = getFragmentPosition(block, fragment); 0105 foreach(KoShape* shape, allShapes) { 0106 KoTextShapeData *shapeData = dynamic_cast<KoTextShapeData*>(shape->userData()); 0107 if (!shapeData) 0108 continue; 0109 if(shapeData->document() == text) 0110 { 0111 rect.translate(shape->position()); 0112 for (KoShapeContainer* parent = shape->parent(); 0113 parent; parent = parent->parent()) { 0114 rect.translate(parent->position()); 0115 } 0116 break; 0117 } 0118 } 0119 links.append(QPair<QRectF, QUrl>(canvas->viewConverter()->documentToView(rect), QUrl(format.anchorHref()))); 0120 } 0121 } 0122 } 0123 } 0124 } 0125 } 0126 0127 QRectF getFragmentPosition(QTextBlock block, QTextFragment fragment) 0128 { 0129 // TODO this only produces a position for the first part, if the link spans more than one line... 0130 // Need to sort that somehow, unfortunately probably by slapping this code into the above function. 0131 // For now leave it like this, more important things are needed. 0132 QTextLayout* layout = block.layout(); 0133 QTextLine line = layout->lineForTextPosition(fragment.position() - block.position()); 0134 if(!line.isValid()) 0135 { 0136 // fragment has no valid position and consequently no line... 0137 return QRectF(); 0138 } 0139 qreal top = line.position().y() + (line.height() / 2); 0140 qreal bottom = top + line.height(); 0141 qreal left = line.cursorToX(fragment.position() - block.position()); 0142 qreal right = line.cursorToX((fragment.position() - block.position()) + fragment.length()); 0143 QRectF fragmentPosition(QPointF(left, top), QPointF(right, bottom)); 0144 return fragmentPosition.adjusted(layout->position().x(), layout->position().y(), 0, 0); 0145 } 0146 0147 static const float wiggleFactor; 0148 }; 0149 0150 const float Calligra::Components::SpreadsheetImpl::Private::wiggleFactor{ 4.f }; 0151 0152 SpreadsheetImpl::SpreadsheetImpl(QObject* parent) 0153 : DocumentImpl{parent}, d{new Private} 0154 { 0155 setDocumentType(DocumentType::Spreadsheet); 0156 } 0157 0158 SpreadsheetImpl::~SpreadsheetImpl() 0159 { 0160 delete d; 0161 } 0162 0163 bool SpreadsheetImpl::load(const QUrl& url) 0164 { 0165 delete d->part; 0166 delete d->document; 0167 0168 d->part = new Calligra::Sheets::Part{this}; 0169 d->document = new Calligra::Sheets::Doc{d->part}; 0170 setKoDocument(d->document); 0171 d->part->setDocument(d->document); 0172 0173 bool retval = d->document->openUrl(url); 0174 0175 d->canvas = static_cast<Calligra::Sheets::CanvasItem*>(d->part->canvasItem(d->document)); 0176 0177 createAndSetCanvasController(d->canvas); 0178 createAndSetZoomController(d->canvas); 0179 connect(d->canvas, &Calligra::Sheets::CanvasItem::documentSizeChanged, this, &SpreadsheetImpl::updateDocumentSize); 0180 0181 auto sheet = d->document->map()->sheet(0); 0182 if(sheet) { 0183 updateDocumentSize(sheet->documentSize().toSize()); 0184 } 0185 0186 setCanvas(d->canvas); 0187 0188 d->updateLinkTargets(); 0189 0190 return retval; 0191 } 0192 0193 int SpreadsheetImpl::currentIndex() 0194 { 0195 if (d->document && d->document->map() && d->canvas->activeSheet()) { 0196 return d->document->map()->indexOf(d->canvas->activeSheet()); 0197 } else { 0198 return -1; 0199 } 0200 } 0201 0202 void SpreadsheetImpl::setCurrentIndex(int newValue) 0203 { 0204 if(newValue != currentIndex()) { 0205 d->canvas->setActiveSheet(d->document->map()->sheet(newValue)); 0206 d->updateLinkTargets(); 0207 emit currentIndexChanged(); 0208 } 0209 } 0210 0211 void SpreadsheetImpl::updateDocumentSize(const QSize& size) 0212 { 0213 QRectF activeRect = d->canvas->viewConverter()->documentToView(d->canvas->activeSheet()->cellCoordinatesToDocument(d->canvas->activeSheet()->usedArea(true))); 0214 zoomController()->setDocumentSize(activeRect.size(), false); 0215 setDocumentSize(activeRect.size().toSize()); 0216 } 0217 0218 int SpreadsheetImpl::indexCount() const 0219 { 0220 return d->document->map()->count(); 0221 } 0222 0223 QUrl SpreadsheetImpl::urlAtPoint(QPoint point) 0224 { 0225 for( const QPair< QRectF, QUrl >& link : d->links ) 0226 { 0227 QRectF hitTarget{ 0228 link.first.x() - Private::wiggleFactor, 0229 link.first.y() - Private::wiggleFactor, 0230 link.first.width() + Private::wiggleFactor * 2, 0231 link.first.height() + Private::wiggleFactor * 2 0232 }; 0233 0234 if( hitTarget.contains( point ) ) 0235 { 0236 return link.second; 0237 } 0238 } 0239 return QUrl(); 0240 } 0241 0242 QObject* SpreadsheetImpl::part() const 0243 { 0244 return d->part; 0245 }