File indexing completed on 2024-05-12 16:34:59

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2008-2010 Thorsten Zachmann <zachmann@kde.org>
0004  * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org>
0005  * Copyright (C) 2010 KO GmbH <cbo@kogmbh.com>
0006  *
0007  * This library is free software; you can redistribute it and/or
0008  * modify it under the terms of the GNU Library General Public
0009  * License as published by the Free Software Foundation; either
0010  * version 2 of the License, or (at your option) any later version.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Library General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Library General Public License
0018  * along with this library; see the file COPYING.LIB.  If not, write to
0019  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020  * Boston, MA 02110-1301, USA.
0021  */
0022 #include "TextShape.h"
0023 #include "ShrinkToFitShapeContainer.h"
0024 #include <KoTextSharedLoadingData.h>
0025 #include "SimpleRootAreaProvider.h"
0026 
0027 #include <KoTextLayoutRootArea.h>
0028 #include <KoTextEditor.h>
0029 
0030 #include <KoCanvasBase.h>
0031 #include <KoCanvasResourceManager.h>
0032 #include <KoChangeTracker.h>
0033 #include <KoInlineTextObjectManager.h>
0034 #include <KoTextRangeManager.h>
0035 #include <KoOdfLoadingContext.h>
0036 #include <KoOdfWorkaround.h>
0037 #include <KoParagraphStyle.h>
0038 #include <KoPostscriptPaintDevice.h>
0039 #include <KoSelection.h>
0040 #include <KoShapeLoadingContext.h>
0041 #include <KoShapeBackground.h>
0042 #include <KoShapePaintingContext.h>
0043 #include <KoShapeSavingContext.h>
0044 #include <KoText.h>
0045 #include <KoTextDocument.h>
0046 #include <KoTextDocumentLayout.h>
0047 #include <KoTextPage.h>
0048 #include <KoTextShapeContainerModel.h>
0049 #include <KoPageProvider.h>
0050 #include <KoViewConverter.h>
0051 #include <KoXmlWriter.h>
0052 #include <KoXmlReader.h>
0053 #include <KoXmlNS.h>
0054 #include <KoStyleStack.h>
0055 
0056 #include <QAbstractTextDocumentLayout>
0057 #include <QApplication>
0058 #include <QFont>
0059 #include <QPainter>
0060 #include <QPainterPath>
0061 #include <QPen>
0062 #include <QTextLayout>
0063 
0064 #include <QDebug>
0065 
0066 TextShape::TextShape(KoInlineTextObjectManager *inlineTextObjectManager, KoTextRangeManager *textRangeManager)
0067         : KoShapeContainer(new KoTextShapeContainerModel())
0068         , KoFrameShape(KoXmlNS::draw, "text-box")
0069         , m_pageProvider(0)
0070         , m_imageCollection(0)
0071         , m_paragraphStyle(0)
0072         , m_clip(true)
0073 {
0074     setShapeId(TextShape_SHAPEID);
0075     m_textShapeData = new KoTextShapeData();
0076     setUserData(m_textShapeData);
0077     SimpleRootAreaProvider *provider = new SimpleRootAreaProvider(m_textShapeData, this);
0078 
0079     KoTextDocument(m_textShapeData->document()).setInlineTextObjectManager(inlineTextObjectManager);
0080     KoTextDocument(m_textShapeData->document()).setTextRangeManager(textRangeManager);
0081 
0082     m_layout = new KoTextDocumentLayout(m_textShapeData->document(), provider);
0083     m_textShapeData->document()->setDocumentLayout(m_layout);
0084 
0085     setCollisionDetection(true);
0086 
0087     QObject::connect(m_layout, SIGNAL(layoutIsDirty()), m_layout, SLOT(scheduleLayout()));
0088 }
0089 
0090 TextShape::~TextShape()
0091 {
0092 }
0093 
0094 void TextShape::paintComponent(QPainter &painter, const KoViewConverter &converter,
0095                                KoShapePaintingContext &paintContext)
0096 {
0097     painter.save();
0098     applyConversion(painter, converter);
0099     KoBorder *border = this->border();
0100 
0101     if (border) {
0102        paintBorder(painter, converter);
0103     }
0104     else if (paintContext.showTextShapeOutlines) {
0105         // No need to paint the outlines if there is a real border.
0106         if (qAbs(rotation()) > 1)
0107             painter.setRenderHint(QPainter::Antialiasing);
0108 
0109         QPen pen(QColor(210, 210, 210), 0); // use cosmetic pen
0110         QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0));
0111         QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y()));
0112         painter.setPen(pen);
0113         painter.drawRect(rect);
0114     }
0115     painter.restore();
0116 
0117     if (m_textShapeData->isDirty()) { // not layouted yet.
0118         return;
0119     }
0120 
0121     QTextDocument *doc = m_textShapeData->document();
0122     Q_ASSERT(doc);
0123     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(doc->documentLayout());
0124     Q_ASSERT(lay);
0125     lay->showInlineObjectVisualization(paintContext.showInlineObjectVisualization);
0126 
0127     applyConversion(painter, converter);
0128 
0129     if (background()) {
0130         QPainterPath p;
0131         p.addRect(QRectF(QPointF(), size()));
0132         background()->paint(painter, converter, paintContext, p);
0133     }
0134 
0135     // this enables to use the same shapes on different pages showing different page numbers
0136     if (m_pageProvider) {
0137         KoTextPage *page = m_pageProvider->page(this);
0138         if (page) {
0139             // this is used to not trigger repaints if layout during the painting is done
0140             m_paintRegion = painter.clipRegion();
0141             if (!m_textShapeData->rootArea()->page() || page->pageNumber() != m_textShapeData->rootArea()->page()->pageNumber()) {
0142                 m_textShapeData->rootArea()->setPage(page); // takes over ownership of the page
0143             } else {
0144                 delete page;
0145             }
0146         }
0147     }
0148 
0149     KoTextDocumentLayout::PaintContext pc;
0150 
0151     QAbstractTextDocumentLayout::Selection selection;
0152     KoTextEditor *textEditor = KoTextDocument(m_textShapeData->document()).textEditor();
0153     selection.cursor = *(textEditor->cursor());
0154     QPalette palette = pc.textContext.palette;
0155     selection.format.setBackground(palette.brush(QPalette::Highlight));
0156     selection.format.setForeground(palette.brush(QPalette::HighlightedText));
0157     pc.textContext.selections.append(selection);
0158 
0159     pc.textContext.selections += KoTextDocument(doc).selections();
0160     pc.viewConverter = &converter;
0161     pc.imageCollection = m_imageCollection;
0162     pc.showFormattingCharacters = paintContext.showFormattingCharacters;
0163     pc.showTableBorders = paintContext.showTableBorders;
0164     pc.showSectionBounds = paintContext.showSectionBounds;
0165     pc.showSpellChecking = paintContext.showSpellChecking;
0166     pc.showSelections = paintContext.showSelections;
0167 
0168     // When clipping the painter we need to make sure not to cutoff cosmetic pens which
0169     // may used to draw e.g. table-borders for user convenience when on screen (but not
0170     // on e.g. printing). Such cosmetic pens are special cause they will always have the
0171     // same pen-width (1 pixel) independent of zoom-factor or painter transformations and
0172     // are not taken into account in any border-calculations.
0173     QRectF clipRect = outlineRect();
0174     qreal cosmeticPenX = 1 * 72. / painter.device()->logicalDpiX();
0175     qreal cosmeticPenY = 1 * 72. / painter.device()->logicalDpiY();
0176     painter.setClipRect(clipRect.adjusted(-cosmeticPenX, -cosmeticPenY, cosmeticPenX, cosmeticPenY), Qt::IntersectClip);
0177 
0178     painter.save();
0179     painter.translate(0, -m_textShapeData->documentOffset());
0180     m_textShapeData->rootArea()->paint(&painter, pc); // only need to draw ourselves
0181     painter.restore();
0182 
0183     m_paintRegion = QRegion();
0184 }
0185 
0186 QPointF TextShape::convertScreenPos(const QPointF &point) const
0187 {
0188     QPointF p = absoluteTransformation(0).inverted().map(point);
0189     return p + QPointF(0.0, m_textShapeData->documentOffset());
0190 }
0191 
0192 
0193 QPainterPath TextShape::outline() const
0194 {
0195     QPainterPath path;
0196     path.addRect(QRectF(QPointF(0,0), size()));
0197     return path;
0198 }
0199 
0200 QRectF TextShape::outlineRect() const
0201 {
0202     if (m_textShapeData->rootArea()) {
0203         QRectF rect = m_textShapeData->rootArea()->boundingRect();
0204         rect.moveTop(rect.top() - m_textShapeData->rootArea()->top());
0205         if (m_clip) {
0206             rect.setHeight(size().height());
0207         }
0208         return rect | QRectF(QPointF(0, 0), size());
0209     }
0210     return QRectF(QPointF(0,0), size());
0211 }
0212 
0213 void TextShape::shapeChanged(ChangeType type, KoShape *shape)
0214 {
0215     Q_UNUSED(shape);
0216     KoShapeContainer::shapeChanged(type, shape);
0217     if (type == PositionChanged || type == SizeChanged || type == CollisionDetected) {
0218         m_textShapeData->setDirty();
0219     }
0220 }
0221 
0222 void TextShape::saveOdf(KoShapeSavingContext &context) const
0223 {
0224     KoXmlWriter & writer = context.xmlWriter();
0225 
0226     QString textHeight = additionalAttribute("fo:min-height");
0227     const_cast<TextShape*>(this)->removeAdditionalAttribute("fo:min-height");
0228     writer.startElement("draw:frame");
0229     // if the TextShape is wrapped in a shrink to fit container we need to save the geometry of the container as
0230     // the geometry of the shape might have been changed.
0231     if (ShrinkToFitShapeContainer *stf = dynamic_cast<ShrinkToFitShapeContainer *>(this->parent())) {
0232         stf->saveOdfAttributes(context, OdfSize | OdfPosition | OdfTransformation );
0233         saveOdfAttributes(context, OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements);
0234     }
0235     else {
0236         saveOdfAttributes(context, OdfAllAttributes);
0237     }
0238 
0239     writer.startElement("draw:text-box");
0240     if (! textHeight.isEmpty())
0241         writer.addAttribute("fo:min-height", textHeight);
0242     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(m_textShapeData->document()->documentLayout());
0243     int index = -1;
0244     if (lay) {
0245         int i = 0;
0246         foreach (KoShape *shape, lay->shapes()) {
0247             if (shape == this) {
0248                 index = i;
0249             } else if (index >= 0) {
0250                 writer.addAttribute("draw:chain-next-name", shape->name());
0251                 break;
0252             }
0253             ++i;
0254         }
0255     }
0256     const bool saveMyText = index == 0; // only save the text once.
0257 
0258     m_textShapeData->saveOdf(context, 0, 0, saveMyText ? -1 : 0);
0259     writer.endElement(); // draw:text-box
0260     saveOdfCommonChildElements(context);
0261     writer.endElement(); // draw:frame
0262 }
0263 
0264 QString TextShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
0265 {
0266     Qt::Alignment vAlign(m_textShapeData->verticalAlignment());
0267     QString verticalAlign = "top";
0268     if (vAlign == Qt::AlignBottom) {
0269         verticalAlign = "bottom";
0270     }
0271     else if ( vAlign == Qt::AlignVCenter ) {
0272         verticalAlign = "middle";
0273     }
0274     style.addProperty("draw:textarea-vertical-align", verticalAlign);
0275 
0276     KoTextShapeData::ResizeMethod resize = m_textShapeData->resizeMethod();
0277     if (resize == KoTextShapeData::AutoGrowWidth || resize == KoTextShapeData::AutoGrowWidthAndHeight)
0278         style.addProperty("draw:auto-grow-width", "true");
0279     if (resize != KoTextShapeData::AutoGrowHeight && resize != KoTextShapeData::AutoGrowWidthAndHeight)
0280         style.addProperty("draw:auto-grow-height", "false");
0281     if (resize == KoTextShapeData::ShrinkToFitResize)
0282         style.addProperty("draw:fit-to-size", "true");
0283 
0284     m_textShapeData->saveStyle(style, context);
0285 
0286     return KoShape::saveStyle(style, context);
0287 }
0288 
0289 void TextShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context)
0290 {
0291     KoShape::loadStyle(element, context);
0292     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0293     styleStack.setTypeProperties("graphic");
0294 
0295     QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align"));
0296     Qt::Alignment alignment(Qt::AlignTop);
0297     if (verticalAlign == "bottom") {
0298         alignment = Qt::AlignBottom;
0299     }
0300     else if (verticalAlign == "justify") {
0301         // not yet supported
0302         alignment = Qt::AlignVCenter;
0303     }
0304     else if (verticalAlign == "middle") {
0305         alignment = Qt::AlignVCenter;
0306     }
0307 
0308     m_textShapeData->setVerticalAlignment(alignment);
0309 
0310     const QString fitToSize = styleStack.property(KoXmlNS::draw, "fit-to-size");
0311     KoTextShapeData::ResizeMethod resize = KoTextShapeData::NoResize;
0312     if (fitToSize == "true" || fitToSize == "shrink-to-fit") { // second is buggy value from impress
0313         resize = KoTextShapeData::ShrinkToFitResize;
0314     }
0315     else {
0316         // An explicit svg:width or svg:height defined do change the default value (means those value
0317         // used if not explicit defined otherwise) for auto-grow-height and auto-grow-height. So
0318         // they are mutable exclusive.
0319         // It is not clear (means we did not test and took care of it) what happens if both are
0320         // defined and are in conflict with each other or how the fit-to-size is related to this.
0321 
0322         QString autoGrowWidth = styleStack.property(KoXmlNS::draw, "auto-grow-width");
0323         if (autoGrowWidth.isEmpty()) {
0324             autoGrowWidth = element.hasAttributeNS(KoXmlNS::svg, "width") ? "false" : "true";
0325         }
0326 
0327         QString autoGrowHeight = styleStack.property(KoXmlNS::draw, "auto-grow-height");
0328         if (autoGrowHeight.isEmpty()) {
0329             autoGrowHeight = element.hasAttributeNS(KoXmlNS::svg, "height") ? "false" : "true";
0330         }
0331 
0332         if (autoGrowWidth == "true") {
0333             resize = autoGrowHeight == "true" ? KoTextShapeData::AutoGrowWidthAndHeight : KoTextShapeData::AutoGrowWidth;
0334         }
0335         else if (autoGrowHeight == "true") {
0336             resize = KoTextShapeData::AutoGrowHeight;
0337         }
0338     }
0339 
0340     m_textShapeData->setResizeMethod(resize);
0341 }
0342 
0343 bool TextShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
0344 {
0345     m_textShapeData->document()->setUndoRedoEnabled(false);
0346     loadOdfAttributes(element, context, OdfAllAttributes);
0347 
0348     // this cannot be done in loadStyle as that fill the style stack wrongly and therefor it results
0349     // in wrong data to be loaded.
0350     m_textShapeData->loadStyle(element, context);
0351 
0352 #ifndef NWORKAROUND_ODF_BUGS
0353     KoTextShapeData::ResizeMethod method = m_textShapeData->resizeMethod();
0354     if (KoOdfWorkaround::fixAutoGrow(method, context)) {
0355         KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(m_textShapeData->document()->documentLayout());
0356         Q_ASSERT(lay);
0357         if (lay) {
0358             SimpleRootAreaProvider *provider = dynamic_cast<SimpleRootAreaProvider*>(lay->provider());
0359             if (provider) {
0360                 provider->m_fixAutogrow = true;
0361             }
0362         }
0363     }
0364 #endif
0365 
0366     bool answer = loadOdfFrame(element, context);
0367     m_textShapeData->document()->setUndoRedoEnabled(true);
0368     return answer;
0369 }
0370 
0371 bool TextShape::loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context)
0372 {
0373     // If the loadOdfFrame from the base class for draw:text-box fails, check
0374     // for table:table, because that is a legal child of draw:frame in ODF 1.2.
0375     if (!KoFrameShape::loadOdfFrame(element, context)) {
0376         const KoXmlElement &possibleTableElement(KoXml::namedItemNS(element, KoXmlNS::table, "table"));
0377         if (possibleTableElement.isNull()) {
0378             return false;
0379         }
0380         else {
0381             return loadOdfFrameElement(possibleTableElement, context);
0382         }
0383     }
0384 
0385     return true;
0386 }
0387 
0388 bool TextShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context)
0389 {
0390     bool ok = m_textShapeData->loadOdf(element, context, 0, this);
0391     if (ok)
0392         ShrinkToFitShapeContainer::tryWrapShape(this, element, context);
0393     return ok;
0394 }
0395 
0396 void TextShape::update() const
0397 {
0398     KoShapeContainer::update();
0399 }
0400 
0401 void TextShape::update(const QRectF &shape) const
0402 {
0403     // this is done to avoid updates which are called during the paint event and not needed.
0404     if (!m_paintRegion.contains(shape.toRect())) {
0405         KoShape::update(shape);
0406     }
0407 }
0408 
0409 void TextShape::waitUntilReady(const KoViewConverter &, bool asynchronous) const
0410 {
0411     Q_UNUSED(asynchronous);
0412     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(m_textShapeData->document()->documentLayout());
0413     Q_ASSERT(lay);
0414     if (m_textShapeData->isDirty()) {
0415         // Do a simple layout-call which will make sure to relayout till things are done. If more
0416         // layouts are scheduled then we don't need to wait for them here but can just continue.
0417         lay->layout();
0418     }
0419 }
0420 
0421 KoImageCollection *TextShape::imageCollection()
0422 {
0423     return m_imageCollection;
0424 }
0425 
0426 void TextShape::updateDocumentData()
0427 {
0428     if (m_layout) {
0429         KoTextDocument document(m_textShapeData->document());
0430         m_layout->setStyleManager(document.styleManager());
0431         m_layout->setInlineTextObjectManager(document.inlineTextObjectManager());
0432         m_layout->setTextRangeManager(document.textRangeManager());
0433         m_layout->setChangeTracker(document.changeTracker());
0434     }
0435 }