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 }