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 }