File indexing completed on 2024-05-12 16:33:29
0001 /* This file is part of the KDE project 0002 0003 Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net> 0004 Copyright 2007-2010 Inge Wallin <inge@lysator.liu.se> 0005 Copyright 2007-2008 Johannes Simon <johannes.simon@gmail.com> 0006 Copyright 2017 Dag Andersen <danders@get2net.dk> 0007 0008 This library is free software; you can redistribute it and/or 0009 modify it under the terms of the GNU Library General Public 0010 License as published by the Free Software Foundation; either 0011 version 2 of the License, or (at your option) any later version. 0012 0013 This library is distributed in the hope that it will be useful, 0014 but WITHOUT ANY WARRANTY; without even the implied warranty of 0015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0016 Library General Public License for more details. 0017 0018 You should have received a copy of the GNU Library General Public License 0019 along with this library; see the file COPYING.LIB. If not, write to 0020 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0021 Boston, MA 02110-1301, USA. 0022 */ 0023 0024 0025 // Own 0026 #include "ChartShape.h" 0027 0028 // Posix 0029 #include <float.h> // For basic data types characteristics. 0030 0031 // Qt 0032 #include <QPointF> 0033 #include <QPainter> 0034 #include <QPainterPath> 0035 #include <QSizeF> 0036 #include <QTextDocument> 0037 #include <QUrl> 0038 0039 // KF5 0040 #include <kmessagebox.h> 0041 0042 // KChart 0043 #include <KChartChart> 0044 #include <KChartAbstractDiagram> 0045 #include <KChartCartesianAxis> 0046 #include <KChartCartesianCoordinatePlane> 0047 #include <KChartPolarCoordinatePlane> 0048 #include "KChartConvertions.h" 0049 // Attribute Classes 0050 #include <KChartDataValueAttributes> 0051 #include <KChartGridAttributes> 0052 #include <KChartTextAttributes> 0053 #include <KChartMarkerAttributes> 0054 #include <KChartThreeDPieAttributes> 0055 #include <KChartThreeDBarAttributes> 0056 #include <KChartThreeDLineAttributes> 0057 // Diagram Classes 0058 #include <KChartBarDiagram> 0059 #include <KChartPieDiagram> 0060 #include <KChartLineDiagram> 0061 #include <KChartRingDiagram> 0062 #include <KChartPolarDiagram> 0063 0064 // Calligra 0065 #include <KoShapeLoadingContext.h> 0066 #include <KoOdfLoadingContext.h> 0067 #include <KoEmbeddedDocumentSaver.h> 0068 #include <KoStore.h> 0069 #include <KoDocument.h> 0070 #include <KoShapeSavingContext.h> 0071 #include <KoViewConverter.h> 0072 #include <KoXmlReader.h> 0073 #include <KoXmlWriter.h> 0074 #include <KoXmlNS.h> 0075 #include <KoGenStyles.h> 0076 #include <KoStyleStack.h> 0077 #include <KoShapeRegistry.h> 0078 #include <KoTextShapeData.h> 0079 #include <KoTextDocumentLayout.h> 0080 #include <KoCanvasBase.h> 0081 #include <KoShapeManager.h> 0082 #include <KoSelection.h> 0083 #include <KoShapeBackground.h> 0084 #include <KoInsets.h> 0085 #include <KoShapeStrokeModel.h> 0086 #include <KoColorBackground.h> 0087 #include <KoShapeStroke.h> 0088 #include <KoOdfWorkaround.h> 0089 #include <KoTextDocument.h> 0090 #include <KoUnit.h> 0091 #include <KoShapePaintingContext.h> 0092 #include <KoTextShapeDataBase.h> 0093 #include <KoCanvasResourceIdentities.h> 0094 0095 // KoChart 0096 #include "Axis.h" 0097 #include "DataSet.h" 0098 #include "Legend.h" 0099 #include "PlotArea.h" 0100 #include "Surface.h" 0101 #include "ChartProxyModel.h" 0102 #include "TextLabelDummy.h" 0103 #include "ChartDocument.h" 0104 #include "ChartTableModel.h" 0105 #include "ChartLayout.h" 0106 #include "TableSource.h" 0107 #include "OdfLoadingHelper.h" 0108 #include "SingleModelHelper.h" 0109 #include "OdfHelper.h" 0110 #include "ChartDebug.h" 0111 0112 0113 // Define the protocol used here for embedded documents' URL 0114 // This used to "store" but KUrl didn't like it, 0115 // so let's simply make it "tar" ! 0116 #define STORE_PROTOCOL "tar" 0117 #define INTERNAL_PROTOCOL "intern" 0118 0119 namespace KoChart { 0120 0121 /// @see ChartShape::setEnableUserInteraction() 0122 static bool ENABLE_USER_INTERACTION = true; 0123 0124 static const char * const ODF_CHARTTYPES[NUM_CHARTTYPES] = { 0125 "chart:bar", 0126 "chart:line", 0127 "chart:area", 0128 "chart:circle", 0129 "chart:ring", 0130 "chart:scatter", 0131 "chart:radar", 0132 "chart:filled-radar", 0133 "chart:stock", 0134 "chart:bubble", 0135 "chart:surface", 0136 "chart:gantt" 0137 }; 0138 0139 static const ChartSubtype defaultSubtypes[NUM_CHARTTYPES] = { 0140 NormalChartSubtype, // Bar 0141 NormalChartSubtype, // Line 0142 NormalChartSubtype, // Area 0143 NoChartSubtype, // Circle 0144 NoChartSubtype, // Ring 0145 NoChartSubtype, // Scatter 0146 NormalChartSubtype, // Radar 0147 NormalChartSubtype, // Filled Radar 0148 HighLowCloseChartSubtype, // Stock 0149 NoChartSubtype, // Bubble 0150 NoChartSubtype, // Surface 0151 NoChartSubtype // Gantt 0152 }; 0153 0154 const char * odfCharttype(int charttype) 0155 { 0156 Q_ASSERT(charttype < LastChartType); 0157 if (charttype >= LastChartType || charttype < 0) { 0158 charttype = 0; 0159 } 0160 return ODF_CHARTTYPES[charttype]; 0161 } 0162 0163 0164 static const int NUM_DEFAULT_DATASET_COLORS = 12; 0165 0166 static const char * const defaultDataSetColors[NUM_DEFAULT_DATASET_COLORS] = 0167 { 0168 "#004586", 0169 "#ff420e", 0170 "#ffd320", 0171 "#579d1c", 0172 "#7e0021", 0173 "#83caff", 0174 "#314004", 0175 "#aecf00", 0176 "#4b1f6f", 0177 "#ff950e", 0178 "#c5000b", 0179 "#0084d1", 0180 }; 0181 0182 QColor defaultDataSetColor(int dataSetNum) 0183 { 0184 dataSetNum %= NUM_DEFAULT_DATASET_COLORS; 0185 return QColor(defaultDataSetColors[dataSetNum]); 0186 } 0187 0188 // ================================================================ 0189 // The Private class 0190 0191 0192 class ChartShape::Private 0193 { 0194 public: 0195 Private(ChartShape *shape); 0196 ~Private(); 0197 0198 void setChildVisible(KoShape *label, bool doShow); 0199 0200 // The components of a chart 0201 KoShape *title; 0202 KoShape *subTitle; 0203 KoShape *footer; 0204 Legend *legend; 0205 PlotArea *plotArea; 0206 0207 // Data 0208 ChartProxyModel *proxyModel; /// What's presented to KChart 0209 ChartTableModel *internalModel; 0210 TableSource tableSource; 0211 SingleModelHelper *internalModelHelper; 0212 0213 bool usesInternalModelOnly; /// @see usesInternalModelOnly() 0214 0215 ChartDocument *document; 0216 0217 ChartShape *shape; // The chart that owns this ChartShape::Private 0218 0219 KoDocumentResourceManager *resourceManager; 0220 }; 0221 0222 0223 ChartShape::Private::Private(ChartShape *shape) 0224 : internalModel(0) 0225 , internalModelHelper(0) 0226 , resourceManager(0) 0227 0228 { 0229 // Register the owner. 0230 this->shape = shape; 0231 0232 // Components 0233 title = 0; 0234 subTitle = 0; 0235 footer = 0; 0236 legend = 0; 0237 plotArea = 0; 0238 0239 // Data 0240 proxyModel = 0; 0241 0242 // If not explicitly set otherwise, this chart provides its own data. 0243 usesInternalModelOnly = true; 0244 0245 document = 0; 0246 } 0247 0248 ChartShape::Private::~Private() 0249 { 0250 } 0251 0252 0253 // 0254 // Show a child, which means either the Title, Subtitle, Footer or Axis Title. 0255 // 0256 // If there is too little room, then make space by shrinking the Plotarea. 0257 // 0258 void ChartShape::Private::setChildVisible(KoShape *child, bool doShow) 0259 { 0260 Q_ASSERT(child); 0261 0262 if (child->isVisible() == doShow) 0263 return; 0264 0265 child->setVisible(doShow); 0266 // FIXME: Shouldn't there be a KoShape::VisibilityChanged for KoShape::shapeChanged()? 0267 shape->layout()->scheduleRelayout(); 0268 } 0269 0270 0271 // ================================================================ 0272 // Class ChartShape 0273 // ================================================================ 0274 0275 0276 ChartShape::ChartShape(KoDocumentResourceManager *resourceManager) 0277 : KoFrameShape(KoXmlNS::draw, "object") 0278 , KoShapeContainer(new ChartLayout) 0279 , d (new Private(this)) 0280 { 0281 d->resourceManager = resourceManager; 0282 setShapeId(ChartShapeId); 0283 0284 // Instantiated all children first 0285 d->proxyModel = new ChartProxyModel(this, &d->tableSource); 0286 0287 d->plotArea = new PlotArea(this); 0288 d->document = new ChartDocument(this); 0289 d->legend = new Legend(this); 0290 0291 // Configure the plotarea. 0292 // We need this as the very first step, because some methods 0293 // here rely on the d->plotArea pointer. 0294 addShape(d->plotArea); 0295 d->plotArea->plotAreaInit(); 0296 d->plotArea->setZIndex(0); 0297 setClipped(d->plotArea, true); 0298 setInheritsTransform(d->plotArea, true); 0299 d->plotArea->setDeletable(false); 0300 d->plotArea->setToolDelegates(QSet<KoShape*>()<<this); // Enable chart tool 0301 d->plotArea->setAllowedInteraction(KoShape::ShearingAllowed, false); 0302 0303 // Configure the legend. 0304 d->legend->setVisible(true); 0305 d->legend->setZIndex(1); 0306 setClipped(d->legend, true); 0307 setInheritsTransform(d->legend, true); 0308 d->legend->setDeletable(false); 0309 d->legend->setToolDelegates(QSet<KoShape*>()<<this); // Enable chart tool 0310 d->legend->setAllowedInteraction(KoShape::ShearingAllowed, false); 0311 0312 // A few simple defaults (chart type and subtype in this case) 0313 setChartType(BarChartType); 0314 setChartSubType(NormalChartSubtype); 0315 0316 // Create the Title, which is a standard TextShape. 0317 KoShapeFactoryBase *textShapeFactory = KoShapeRegistry::instance()->value(TextShapeId); 0318 if (textShapeFactory) 0319 d->title = textShapeFactory->createDefaultShape(resourceManager); 0320 // Potential problem 1) No TextShape installed 0321 if (!d->title) { 0322 d->title = new TextLabelDummy; 0323 if (ENABLE_USER_INTERACTION) 0324 KMessageBox::error(0, i18n("The plugin needed for displaying text labels in a chart is not available."), 0325 i18n("Plugin Missing")); 0326 // Potential problem 2) TextShape incompatible 0327 } else if (dynamic_cast<TextLabelData*>(d->title->userData()) == 0 && 0328 ENABLE_USER_INTERACTION) 0329 KMessageBox::error(0, i18n("The plugin needed for displaying text labels is not compatible with the current version of the chart Flake shape."), 0330 i18n("Plugin Incompatible")); 0331 0332 // In both cases we need a KoTextShapeData instance to function. This is 0333 // enough for unit tests, so there has to be no TextShape plugin doing the 0334 // actual text rendering, we just need KoTextShapeData which is in the libs. 0335 TextLabelData *labelData = dynamic_cast<TextLabelData*>(d->title->userData()); 0336 if (labelData == 0) { 0337 labelData = new TextLabelData; 0338 KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); 0339 labelData->document()->setDocumentLayout(documentLayout); 0340 d->title->setUserData(labelData); 0341 } 0342 0343 // Add the title to the shape 0344 addShape(d->title); 0345 QFont font = titleData()->document()->defaultFont(); 0346 font.setPointSizeF(12.0); 0347 titleData()->document()->setDefaultFont(font); 0348 titleData()->document()->setPlainText(i18n("Title")); 0349 // Set a reasonable size, it will be resized automatically 0350 d->title->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); 0351 d->title->setVisible(false); 0352 d->title->setZIndex(2); 0353 setClipped(d->title, true); 0354 setInheritsTransform(d->title, true); 0355 d->title->setDeletable(false); 0356 d->title->setToolDelegates(QSet<KoShape*>()<<this<<d->title); // Enable chart tool 0357 labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); 0358 d->title->setAdditionalStyleAttribute("chart:auto-position", "true"); 0359 d->title->setAllowedInteraction(KoShape::ShearingAllowed, false); 0360 0361 // Create the Subtitle and add it to the shape. 0362 if (textShapeFactory) 0363 d->subTitle = textShapeFactory->createDefaultShape(resourceManager); 0364 if (!d->subTitle) { 0365 d->subTitle = new TextLabelDummy; 0366 } 0367 labelData = dynamic_cast<TextLabelData*>(d->subTitle->userData()); 0368 if (labelData == 0) { 0369 labelData = new TextLabelData; 0370 KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); 0371 labelData->document()->setDocumentLayout(documentLayout); 0372 d->subTitle->setUserData(labelData); 0373 } 0374 addShape(d->subTitle); 0375 font = subTitleData()->document()->defaultFont(); 0376 font.setPointSizeF(10.0); 0377 subTitleData()->document()->setDefaultFont(font); 0378 subTitleData()->document()->setPlainText(i18n("Subtitle")); 0379 // Set a reasonable size, it will be resized automatically 0380 d->subTitle->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); 0381 d->subTitle->setVisible(false); 0382 d->subTitle->setZIndex(3); 0383 setClipped(d->subTitle, true); 0384 setInheritsTransform(d->subTitle, true); 0385 d->subTitle->setDeletable(false); 0386 d->subTitle->setToolDelegates(QSet<KoShape*>()<<this<<d->subTitle); // Enable chart tool 0387 labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); 0388 d->subTitle->setAdditionalStyleAttribute("chart:auto-position", "true"); 0389 d->subTitle->setAllowedInteraction(KoShape::ShearingAllowed, false); 0390 0391 // Create the Footer and add it to the shape. 0392 if (textShapeFactory) 0393 d->footer = textShapeFactory->createDefaultShape(resourceManager); 0394 if (!d->footer) { 0395 d->footer = new TextLabelDummy; 0396 } 0397 labelData = dynamic_cast<TextLabelData*>(d->footer->userData()); 0398 if (labelData == 0) { 0399 labelData = new TextLabelData; 0400 KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); 0401 labelData->document()->setDocumentLayout(documentLayout); 0402 d->footer->setUserData(labelData); 0403 } 0404 addShape(d->footer); 0405 font = footerData()->document()->defaultFont(); 0406 font.setPointSizeF(10.0); 0407 footerData()->document()->setDefaultFont(font); 0408 footerData()->document()->setPlainText(i18n("Footer")); 0409 // Set a reasonable size, it will be resized automatically 0410 d->footer->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); 0411 d->footer->setVisible(false); 0412 d->footer->setZIndex(4); 0413 setClipped(d->footer, true); 0414 setInheritsTransform(d->footer, true); 0415 d->footer->setDeletable(false); 0416 d->footer->setToolDelegates(QSet<KoShape*>()<<this<<d->footer); // Enable chart tool 0417 labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); 0418 d->footer->setAdditionalStyleAttribute("chart:auto-position", "true"); 0419 d->footer->setAllowedInteraction(KoShape::ShearingAllowed, false); 0420 0421 // Set default contour (for how text run around is done around this shape) 0422 // to prevent a crash in LO 0423 setTextRunAroundContour(KoShape::ContourBox); 0424 0425 QSharedPointer<KoColorBackground> background(new KoColorBackground(Qt::white)); 0426 setBackground(background); 0427 0428 KoShapeStroke *stroke = new KoShapeStroke(0, Qt::black); 0429 setStroke(stroke); 0430 0431 setSize(QSizeF(CM_TO_POINT(8), CM_TO_POINT(5))); 0432 0433 // Tell layout about item types 0434 ChartLayout *l = layout(); 0435 l->setItemType(d->plotArea, PlotAreaType); 0436 l->setItemType(d->title, TitleLabelType); 0437 l->setItemType(d->subTitle, SubTitleLabelType); 0438 l->setItemType(d->footer, FooterLabelType); 0439 l->setItemType(d->legend, LegendType); 0440 l->layout(); 0441 requestRepaint(); 0442 } 0443 0444 ChartShape::~ChartShape() 0445 { 0446 delete d->title; 0447 delete d->subTitle; 0448 delete d->footer; 0449 0450 delete d->legend; 0451 delete d->plotArea; 0452 0453 delete d->proxyModel; 0454 0455 delete d->document; 0456 0457 delete d; 0458 } 0459 0460 ChartProxyModel *ChartShape::proxyModel() const 0461 { 0462 return d->proxyModel; 0463 } 0464 0465 KoShape *ChartShape::title() const 0466 { 0467 return d->title; 0468 } 0469 0470 TextLabelData *ChartShape::titleData() const 0471 { 0472 TextLabelData *data = qobject_cast<TextLabelData*>(d->title->userData()); 0473 return data; 0474 } 0475 0476 0477 KoShape *ChartShape::subTitle() const 0478 { 0479 return d->subTitle; 0480 } 0481 0482 TextLabelData *ChartShape::subTitleData() const 0483 { 0484 TextLabelData *data = qobject_cast<TextLabelData*>(d->subTitle->userData()); 0485 return data; 0486 } 0487 0488 KoShape *ChartShape::footer() const 0489 { 0490 return d->footer; 0491 } 0492 0493 TextLabelData *ChartShape::footerData() const 0494 { 0495 TextLabelData *data = qobject_cast<TextLabelData*>(d->footer->userData()); 0496 return data; 0497 } 0498 0499 QList<KoShape*> ChartShape::labels() const 0500 { 0501 QList<KoShape*> labels; 0502 labels.append(d->title); 0503 labels.append(d->footer); 0504 labels.append(d->subTitle); 0505 foreach(Axis *axis, plotArea()->axes()) { 0506 labels.append(axis->title()); 0507 } 0508 return labels; 0509 } 0510 0511 Legend *ChartShape::legend() const 0512 { 0513 // There has to be a valid legend even, if it's hidden. 0514 Q_ASSERT(d->legend); 0515 return d->legend; 0516 } 0517 0518 PlotArea *ChartShape::plotArea() const 0519 { 0520 return d->plotArea; 0521 } 0522 0523 ChartLayout *ChartShape::layout() const 0524 { 0525 ChartLayout *l = dynamic_cast<ChartLayout*>(KoShapeContainer::model()); 0526 Q_ASSERT(l); 0527 return l; 0528 } 0529 0530 0531 void ChartShape::showTitle(bool doShow) 0532 { 0533 d->setChildVisible(d->title, doShow); 0534 } 0535 0536 void ChartShape::showSubTitle(bool doShow) 0537 { 0538 d->setChildVisible(d->subTitle, doShow); 0539 } 0540 0541 void ChartShape::showFooter(bool doShow) 0542 { 0543 d->setChildVisible(d->footer, doShow); 0544 } 0545 0546 ChartTableModel *ChartShape::internalModel() const 0547 { 0548 return d->internalModel; 0549 } 0550 0551 void ChartShape::setInternalModel(ChartTableModel *model) 0552 { 0553 Table *table = d->tableSource.get(model); 0554 Q_ASSERT(table); 0555 delete d->internalModelHelper; 0556 delete d->internalModel; 0557 d->internalModel = model; 0558 d->internalModelHelper = new SingleModelHelper(table, d->proxyModel); 0559 } 0560 0561 TableSource *ChartShape::tableSource() const 0562 { 0563 return &d->tableSource; 0564 } 0565 0566 bool ChartShape::usesInternalModelOnly() const 0567 { 0568 return d->usesInternalModelOnly; 0569 } 0570 0571 void ChartShape::setUsesInternalModelOnly(bool doesSo) 0572 { 0573 d->usesInternalModelOnly = doesSo; 0574 } 0575 0576 0577 // ---------------------------------------------------------------- 0578 // getters and setters 0579 0580 0581 ChartType ChartShape::chartType() const 0582 { 0583 Q_ASSERT(d->plotArea); 0584 return d->plotArea->chartType(); 0585 } 0586 0587 ChartSubtype ChartShape::chartSubType() const 0588 { 0589 Q_ASSERT(d->plotArea); 0590 return d->plotArea->chartSubType(); 0591 } 0592 0593 bool ChartShape::isThreeD() const 0594 { 0595 Q_ASSERT(d->plotArea); 0596 return d->plotArea->isThreeD(); 0597 } 0598 0599 void ChartShape::setSheetAccessModel(QAbstractItemModel *model) 0600 { 0601 d->tableSource.setSheetAccessModel(model); 0602 } 0603 0604 void ChartShape::reset(const QString ®ion, 0605 bool firstRowIsLabel, 0606 bool firstColumnIsLabel, 0607 Qt::Orientation dataDirection) 0608 { 0609 // This method is provided via KoChartInterface, which is 0610 // used by embedding applications. 0611 d->usesInternalModelOnly = false; 0612 d->proxyModel->setFirstRowIsLabel(firstRowIsLabel); 0613 d->proxyModel->setFirstColumnIsLabel(firstColumnIsLabel); 0614 d->proxyModel->setDataDirection(dataDirection); 0615 d->proxyModel->reset(CellRegion(&d->tableSource, region)); 0616 } 0617 0618 void ChartShape::setChartType(ChartType type) 0619 { 0620 Q_ASSERT(d->plotArea); 0621 ChartType prev = chartType(); 0622 d->proxyModel->setDataDimensions(numDimensions(type)); 0623 d->plotArea->setChartType(type); 0624 emit chartTypeChanged(type, prev); 0625 } 0626 0627 void ChartShape::setChartSubType(ChartSubtype subType, bool reset) 0628 { 0629 Q_ASSERT(d->plotArea); 0630 ChartSubtype prev = d->plotArea->chartSubType(); 0631 d->plotArea->setChartSubType(subType); 0632 if (reset && chartType() == StockChartType && prev != subType && d->internalModel && d->usesInternalModelOnly) { 0633 // HACK to get reasonable behaviour in most cases 0634 // Stock charts are special because subtypes interpretes data differently from another: 0635 // - HighLowCloseChartSubtype assumes High = row 0, Low = row 1 and Close = row 2 0636 // - The other types assumes Open = row 0, High = row 1, Low = row 2 and Close = row 3 0637 // This makes switching between them a bit unintuitive. 0638 if (subType == HighLowCloseChartSubtype && d->internalModel->rowCount() > 3) { 0639 d->proxyModel->removeRows(0, 1); // remove Open 0640 } else { 0641 // just reset and hope for the best 0642 CellRegion region(d->tableSource.get(d->internalModel), QRect(1, 1, d->internalModel->columnCount(), d->internalModel->rowCount())); 0643 d->proxyModel->reset(region); 0644 } 0645 } 0646 emit updateConfigWidget(); 0647 } 0648 0649 void ChartShape::setThreeD(bool threeD) 0650 { 0651 Q_ASSERT(d->plotArea); 0652 d->plotArea->setThreeD(threeD); 0653 } 0654 0655 0656 // ---------------------------------------------------------------- 0657 0658 0659 void ChartShape::paintComponent(QPainter &painter, 0660 const KoViewConverter &converter, KoShapePaintingContext &paintContext) 0661 { 0662 // Only does a relayout if scheduled 0663 layout()->layout(); 0664 0665 // Paint the background 0666 applyConversion(painter, converter); 0667 if (background()) { 0668 // Calculate the clipping rect 0669 QRectF paintRect = QRectF(QPointF(0, 0), size()); 0670 painter.setClipRect(paintRect, Qt::IntersectClip); 0671 0672 QPainterPath p; 0673 p.addRect(paintRect); 0674 background()->paint(painter, converter, paintContext, p); 0675 } 0676 // Paint border if showTextShapeOutlines is set 0677 // This means that it will be painted in words but not eg in sheets 0678 if (paintContext.showTextShapeOutlines) { 0679 if (qAbs(rotation()) > 1) { 0680 painter.setRenderHint(QPainter::Antialiasing); 0681 } 0682 QPen pen(QColor(210, 210, 210), 0); // use cosmetic pen 0683 QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0)); 0684 QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y())); 0685 painter.setPen(pen); 0686 painter.drawRect(rect); 0687 } 0688 } 0689 0690 void ChartShape::paintDecorations(QPainter &painter, 0691 const KoViewConverter &converter, 0692 const KoCanvasBase *canvas) 0693 { 0694 // This only is a helper decoration, do nothing if we're already 0695 // painting handles anyway. 0696 Q_ASSERT(canvas); 0697 if (canvas->shapeManager()->selection()->selectedShapes().contains(this)) 0698 return; 0699 0700 if (stroke()) 0701 return; 0702 0703 QRectF border = QRectF(QPointF(-1.5, -1.5), 0704 converter.documentToView(size()) + QSizeF(1.5, 1.5)); 0705 0706 painter.setPen(QPen(Qt::lightGray, 0)); 0707 painter.drawRect(border); 0708 } 0709 0710 0711 // ---------------------------------------------------------------- 0712 // Loading and Saving 0713 0714 0715 bool ChartShape::loadEmbeddedDocument(KoStore *store, 0716 const KoXmlElement &objectElement, 0717 const KoOdfLoadingContext &loadingContext) 0718 { 0719 if (!objectElement.hasAttributeNS(KoXmlNS::xlink, "href")) { 0720 errorChart << "Object element has no valid xlink:href attribute"; 0721 return false; 0722 } 0723 0724 QString url = objectElement.attributeNS(KoXmlNS::xlink, "href"); 0725 0726 // It can happen that the url is empty e.g. when it is a 0727 // presentation:placeholder. 0728 if (url.isEmpty()) { 0729 return true; 0730 } 0731 0732 QString tmpURL; 0733 if (url[0] == '#') 0734 url.remove(0, 1); 0735 0736 if (QUrl::fromUserInput(url).isRelative()) { 0737 if (url.startsWith(QLatin1String("./"))) 0738 tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url.mid(2); 0739 else 0740 tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url; 0741 } 0742 else 0743 tmpURL = url; 0744 0745 QString path = tmpURL; 0746 if (tmpURL.startsWith(INTERNAL_PROTOCOL)) { 0747 path = store->currentPath(); 0748 if (!path.isEmpty() && !path.endsWith('/')) 0749 path += '/'; 0750 QString relPath = QUrl::fromUserInput(tmpURL).path(); 0751 path += relPath.mid(1); // remove leading '/' 0752 } 0753 if (!path.endsWith('/')) 0754 path += '/'; 0755 0756 const QString mimeType = loadingContext.mimeTypeForPath(path); 0757 //debugChart << "path for manifest file=" << path << "mimeType=" << mimeType; 0758 if (mimeType.isEmpty()) { 0759 //debugChart << "Manifest doesn't have media-type for" << path; 0760 return false; 0761 } 0762 0763 const bool isOdf = mimeType.startsWith(QLatin1String("application/vnd.oasis.opendocument")); 0764 if (!isOdf) { 0765 tmpURL += "/maindoc.xml"; 0766 //debugChart << "tmpURL adjusted to" << tmpURL; 0767 } 0768 0769 //debugChart << "tmpURL=" << tmpURL; 0770 0771 bool res = true; 0772 if (tmpURL.startsWith(STORE_PROTOCOL) 0773 || tmpURL.startsWith(INTERNAL_PROTOCOL) 0774 || QUrl::fromUserInput(tmpURL).isRelative()) 0775 { 0776 if (isOdf) { 0777 store->pushDirectory(); 0778 Q_ASSERT(tmpURL.startsWith(INTERNAL_PROTOCOL)); 0779 QString relPath = QUrl::fromUserInput(tmpURL).path().mid(1); 0780 store->enterDirectory(relPath); 0781 res = d->document->loadOasisFromStore(store); 0782 store->popDirectory(); 0783 } else { 0784 if (tmpURL.startsWith(INTERNAL_PROTOCOL)) 0785 tmpURL = QUrl::fromUserInput(tmpURL).path().mid(1); 0786 res = d->document->loadFromStore(store, tmpURL); 0787 } 0788 d->document->setStoreInternal(true); 0789 } 0790 else { 0791 // Reference to an external document. Hmmm... 0792 d->document->setStoreInternal(false); 0793 QUrl url = QUrl::fromUserInput(tmpURL); 0794 if (!url.isLocalFile()) { 0795 //QApplication::restoreOverrideCursor(); 0796 0797 // For security reasons we need to ask confirmation if the 0798 // url is remote. 0799 int result = KMessageBox::warningYesNoCancel( 0800 0, i18n("This document contains an external link to a remote document\n%1", tmpURL), 0801 i18n("Confirmation Required"), KGuiItem(i18n("Download")), KGuiItem(i18n("Skip"))); 0802 0803 if (result == KMessageBox::Cancel) { 0804 //d->m_parent->setErrorMessage("USER_CANCELED"); 0805 return false; 0806 } 0807 if (result == KMessageBox::Yes) 0808 res = d->document->openUrl(url); 0809 // and if == No, res will still be false so we'll use a kounavail below 0810 } 0811 else 0812 res = d->document->openUrl(url); 0813 } 0814 0815 if (!res) { 0816 QString errorMessage = d->document->errorMessage(); 0817 return false; 0818 } 0819 // Still waiting... 0820 //QApplication::setOverrideCursor(Qt::WaitCursor); 0821 0822 tmpURL.clear(); 0823 0824 //QApplication::restoreOverrideCursor(); 0825 0826 return res; 0827 } 0828 0829 bool ChartShape::loadOdf(const KoXmlElement &element, 0830 KoShapeLoadingContext &context) 0831 { 0832 //struct Timer{QTime t;Timer(){t.start();} ~Timer(){debugChart<<">>>>>"<<t.elapsed();}} timer; 0833 0834 // Load common attributes of (frame) shapes. If you change here, 0835 // don't forget to also change in saveOdf(). 0836 loadOdfAttributes(element, context, OdfAllAttributes); 0837 bool r = loadOdfFrame(element, context); 0838 0839 return r; 0840 } 0841 0842 // Used to load the actual contents from the ODF frame that surrounds 0843 // the chart in the ODF file. 0844 bool ChartShape::loadOdfFrameElement(const KoXmlElement &element, 0845 KoShapeLoadingContext &context) 0846 { 0847 if (element.tagName() == "object") 0848 return loadEmbeddedDocument(context.odfLoadingContext().store(), 0849 element, 0850 context.odfLoadingContext()); 0851 0852 warnChart << "Unknown frame element <" << element.tagName() << ">"; 0853 return false; 0854 } 0855 0856 bool ChartShape::loadOdfChartElement(const KoXmlElement &chartElement, 0857 KoShapeLoadingContext &context) 0858 { 0859 // Use a helper-class created on the stack to be sure a we always leave 0860 // this method with a call to endLoading proxyModel()->endLoading() 0861 struct ProxyModelLoadState { 0862 ChartProxyModel *m; 0863 ChartLayout *l; 0864 ProxyModelLoadState(ChartProxyModel *m, ChartLayout *l) : m(m), l(l) { m->beginLoading(); l->setLayoutingEnabled(false); } 0865 ~ProxyModelLoadState() { m->endLoading(); l->setLayoutingEnabled(true); } 0866 }; 0867 ProxyModelLoadState proxyModelLoadState(proxyModel(), layout()); 0868 0869 // The shared data will automatically be deleted in the destructor 0870 // of KoShapeLoadingContext 0871 OdfLoadingHelper *helper = new OdfLoadingHelper; 0872 helper->tableSource = &d->tableSource; 0873 helper->chartUsesInternalModelOnly = d->usesInternalModelOnly; 0874 0875 // Get access to sheets in Calligra Sheets 0876 QAbstractItemModel *sheetAccessModel = 0; 0877 if (resourceManager() && resourceManager()->hasResource(Sheets::CanvasResource::AccessModel)) { 0878 QVariant var = resourceManager()->resource(Sheets::CanvasResource::AccessModel); 0879 sheetAccessModel = static_cast<QAbstractItemModel*>(var.value<void*>()); 0880 if (sheetAccessModel) { 0881 // We're embedded in Calligra Sheets, which means Calligra Sheets provides the data 0882 d->usesInternalModelOnly = false; 0883 d->tableSource.setSheetAccessModel(sheetAccessModel); 0884 helper->chartUsesInternalModelOnly = d->usesInternalModelOnly; 0885 } 0886 } 0887 context.addSharedData(OdfLoadingHelperId, helper); 0888 0889 KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); 0890 styleStack.clear(); 0891 if (chartElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { 0892 context.odfLoadingContext().fillStyleStack(chartElement, KoXmlNS::chart, "style-name", "chart"); 0893 0894 styleStack.setTypeProperties("graphic"); 0895 KoInsets padding = layout()->padding(); 0896 if (styleStack.hasProperty(KoXmlNS::fo, "padding")) { 0897 padding.left = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding")); 0898 padding.top = padding.left; 0899 padding.right = padding.left; 0900 padding.bottom = padding.left; 0901 debugChartOdf<<"load padding"<<padding.left; 0902 } 0903 if (styleStack.hasProperty(KoXmlNS::fo, "padding-left")) { 0904 padding.left = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-left")); 0905 debugChartOdf<<"load padding-left"<<padding.left; 0906 } 0907 if (styleStack.hasProperty(KoXmlNS::fo, "padding-top")) { 0908 padding.top = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-top")); 0909 debugChartOdf<<"load padding-top"<<padding.top; 0910 } 0911 if (styleStack.hasProperty(KoXmlNS::fo, "padding-right")) { 0912 padding.right = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-right")); 0913 debugChartOdf<<"load padding-right"<<padding.right; 0914 } 0915 if (styleStack.hasProperty(KoXmlNS::fo, "padding-bottom")) { 0916 padding.bottom = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-bottom")); 0917 debugChartOdf<<"load padding-bottom"<<padding.bottom; 0918 } 0919 layout()->setPadding(padding); 0920 } 0921 // Also load the size here as it, if specified here, overwrites the frame's size, 0922 // See ODF specs for chart:chart element for more details. 0923 loadOdfAttributes(chartElement, context, 0924 OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements | OdfStyle | OdfSize); 0925 0926 #ifndef NWORKAROUND_ODF_BUGS 0927 if (!background()) { 0928 const QColor color = KoOdfWorkaround::fixMissingFillColor(chartElement, context); 0929 if (color.isValid()) // invalid color means do not set KoColorBackground but be transparent instead 0930 setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(color))); 0931 } 0932 #endif 0933 0934 // Check if we're loading an embedded document 0935 if (!chartElement.hasAttributeNS(KoXmlNS::chart, "class")) { 0936 debugChart << "Error: Embedded document has no chart:class attribute."; 0937 return false; 0938 } 0939 0940 Q_ASSERT(d->plotArea); 0941 0942 0943 // 1. Load the chart type. 0944 // NOTE: Chart type and -subtype is a bit tricky as stock charts and bubble charts 0945 // needs special treatment. 0946 // So we do not call the ChartShape::setChart... methods here in odf code, 0947 // but the plot area methods directly. 0948 const QString chartClass = chartElement.attributeNS(KoXmlNS::chart, "class", QString()); 0949 KoChart::ChartType chartType = KoChart::BarChartType; 0950 // Find out what charttype the chart class corresponds to. 0951 bool knownType = false; 0952 for (int type = 0; type < (int)LastChartType; ++type) { 0953 if (chartClass == ODF_CHARTTYPES[(ChartType)type]) { 0954 chartType = (ChartType)type; 0955 // Set the dimensionality of the data points, we can not call 0956 // setChartType here as bubble charts requires that the datasets already exist 0957 proxyModel()->setDataDimensions(numDimensions(chartType)); 0958 knownType = true; 0959 debugChartOdf <<"found chart of type" << chartClass<<chartType; 0960 break; 0961 } 0962 } 0963 0964 // If we can't find out what charttype it is, we might as well end here. 0965 if (!knownType) { 0966 // FIXME: Find out what the equivalent of 0967 // KoDocument::setErrorMessage() is for KoShape. 0968 //setErrorMessage(i18n("Unknown chart type %1" ,chartClass)); 0969 warnChartOdf<<"Unknown chart type:"<<chartClass; 0970 return false; 0971 } 0972 0973 // 2. Load the data 0974 // int dimensions = numDimensions(chartType); 0975 // debugChart << "DIMENSIONS" << dimensions; 0976 // d->proxyModel->setDataDimensions(dimensions); 0977 // debugChart << d->proxyModel->dataSets().count(); 0978 KoXmlElement dataElem = KoXml::namedItemNS(chartElement, KoXmlNS::table, "table"); 0979 if (!dataElem.isNull()) { 0980 if (!loadOdfData(dataElem, context)) 0981 return false; 0982 } 0983 0984 // 3. Load the plot area (this is where the meat is!). 0985 KoXmlElement plotareaElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "plot-area"); 0986 0987 if (!plotareaElem.isNull()) { 0988 d->plotArea->setChartType(chartType); 0989 d->plotArea->setChartSubType(chartSubType()); 0990 if (!d->plotArea->loadOdf(plotareaElem, context)) { 0991 return false; 0992 } 0993 // d->plotArea->setChartType(chartType); 0994 // d->plotArea->setChartSubType(chartSubType()); 0995 } 0996 0997 // 4. Load the title. 0998 KoXmlElement titleElem = KoXml::namedItemNS(chartElement, 0999 KoXmlNS::chart, "title"); 1000 d->setChildVisible(d->title, !titleElem.isNull()); 1001 if (!titleElem.isNull()) { 1002 if (!OdfHelper::loadOdfTitle(d->title, titleElem, context)) 1003 return false; 1004 } 1005 1006 // 5. Load the subtitle. 1007 KoXmlElement subTitleElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "subtitle"); 1008 d->setChildVisible(d->subTitle, !subTitleElem.isNull()); 1009 if (!subTitleElem.isNull()) { 1010 if (!OdfHelper::loadOdfTitle(d->subTitle, subTitleElem, context)) 1011 return false; 1012 } 1013 1014 // 6. Load the footer. 1015 KoXmlElement footerElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "footer"); 1016 d->setChildVisible(d->footer, !footerElem.isNull()); 1017 if (!footerElem.isNull()) { 1018 if (!OdfHelper::loadOdfTitle(d->footer, footerElem, context)) 1019 return false; 1020 } 1021 1022 // 7. Load the legend. 1023 KoXmlElement legendElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "legend"); 1024 d->setChildVisible(d->legend, !legendElem.isNull()); 1025 if (!legendElem.isNull()) { 1026 if (!d->legend->loadOdf(legendElem, context)) 1027 return false; 1028 } 1029 1030 // 8. Sets the chart type 1031 // since chart type in plot area is already set before axes were loaded, we need to do axes here 1032 for (Axis *a : d->plotArea->axes()) { 1033 a->plotAreaChartTypeChanged(chartType); 1034 } 1035 debugChartOdf<<"loaded:"<<this->chartType()<<chartSubType(); 1036 1037 updateAll(); 1038 requestRepaint(); 1039 1040 return true; 1041 } 1042 1043 bool ChartShape::loadOdfData(const KoXmlElement &tableElement, 1044 KoShapeLoadingContext &context) 1045 { 1046 // There is no table element to load 1047 if (tableElement.isNull() || !tableElement.isElement()) 1048 return true; 1049 1050 // An internal model might have been set before in ChartShapeFactory. 1051 if (d->internalModel) { 1052 Table *oldInternalTable = d->tableSource.get(d->internalModel); 1053 Q_ASSERT(oldInternalTable); 1054 d->tableSource.remove(oldInternalTable->name()); 1055 } 1056 1057 // FIXME: Make model->loadOdf() return a bool, and use it here. 1058 // Create a table with data from document, add it as table source 1059 // and reset the proxy only with data from this new table. 1060 ChartTableModel *internalModel = new ChartTableModel; 1061 internalModel->loadOdf(tableElement, context); 1062 1063 QString tableName = tableElement.attributeNS(KoXmlNS::table, "name"); 1064 debugChartOdf<<"Loaded table:"<<tableName; 1065 d->tableSource.add(tableName, internalModel); 1066 // TODO: d->tableSource.setAvoidNameClash(tableName) 1067 setInternalModel(internalModel); 1068 1069 return true; 1070 } 1071 1072 void ChartShape::saveOdf(KoShapeSavingContext & context) const 1073 { 1074 Q_ASSERT(d->plotArea); 1075 1076 KoXmlWriter& bodyWriter = context.xmlWriter(); 1077 1078 // Check if we're saving to a chart document. If not, embed a 1079 // chart document. ChartShape::saveOdf() will then be called 1080 // again later, when the current document saves the embedded 1081 // documents. 1082 // 1083 // FIXME: The check isEmpty() fixes a crash that happened when a 1084 // chart shape was saved from Words. There are two 1085 // problems with this fix: 1086 // 1. Checking the tag hierarchy is hardly the right way to do this 1087 // 2. The position doesn't seem to be saved yet. 1088 // 1089 // Also, I have to check with the other apps, e.g. Calligra Sheets, 1090 // if it works there too. 1091 // 1092 QList<const char*> tagHierarchy = bodyWriter.tagHierarchy(); 1093 if (tagHierarchy.isEmpty() 1094 || QString(tagHierarchy.last()) != "office:chart") 1095 { 1096 bodyWriter.startElement("draw:frame"); 1097 // See also loadOdf() in loadOdfAttributes. 1098 saveOdfAttributes(context, OdfAllAttributes); 1099 1100 bodyWriter.startElement("draw:object"); 1101 context.embeddedSaver().embedDocument(bodyWriter, d->document); 1102 bodyWriter.endElement(); // draw:object 1103 1104 bodyWriter.endElement(); // draw:frame 1105 return; 1106 } 1107 1108 bodyWriter.startElement("chart:chart"); 1109 1110 saveOdfAttributes(context, OdfSize); 1111 1112 context.setStyleFamily("ch"); 1113 KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart"); 1114 KoInsets padding = layout()->padding(); 1115 style.addPropertyPt("fo:padding-left", padding.left, KoGenStyle::GraphicType); 1116 style.addPropertyPt("fo:padding-top", padding.top, KoGenStyle::GraphicType); 1117 style.addPropertyPt("fo:padding-right", padding.right, KoGenStyle::GraphicType); 1118 style.addPropertyPt("fo:padding-bottom", padding.bottom, KoGenStyle::GraphicType); 1119 debugChartOdf<<"save padding:"<<padding; 1120 bodyWriter.addAttribute("chart:style-name", saveStyle(style, context)); 1121 1122 // 1. Write the chart type. 1123 bodyWriter.addAttribute("chart:class", ODF_CHARTTYPES[d->plotArea->chartType() ]); 1124 1125 // 2. Write the title. 1126 OdfHelper::saveOdfTitle(d->title, bodyWriter, "chart:title", context); 1127 1128 // 3. Write the subtitle. 1129 OdfHelper::saveOdfTitle(d->subTitle, bodyWriter, "chart:subtitle", context); 1130 1131 // 4. Write the footer. 1132 OdfHelper::saveOdfTitle(d->footer, bodyWriter, "chart:footer", context); 1133 1134 // 5. Write the legend. 1135 if (d->legend->isVisible()) 1136 d->legend->saveOdf(context); 1137 1138 // 6. Write the plot area (this is where the real action is!). 1139 d->plotArea->saveOdf(context); 1140 1141 // 7. Save the data 1142 saveOdfData(bodyWriter, context.mainStyles()); 1143 1144 bodyWriter.endElement(); // chart:chart 1145 } 1146 1147 static void saveOdfDataRow(KoXmlWriter &bodyWriter, QAbstractItemModel *table, int row) 1148 { 1149 bodyWriter.startElement("table:table-row"); 1150 const int cols = table->columnCount(); 1151 for (int col = 0; col < cols; ++col) { 1152 //QVariant value(internalModel.cellVal(row, col)); 1153 QModelIndex index = table->index(row, col); 1154 QVariant value = table->data(index); 1155 1156 bool ok; 1157 double val = value.toDouble(&ok); 1158 if (ok) { 1159 value = val; 1160 } 1161 1162 QString valType; 1163 QString valStr; 1164 1165 switch (value.type()) { 1166 case QVariant::Invalid: 1167 break; 1168 case QVariant::String: 1169 valType = "string"; 1170 valStr = value.toString(); 1171 break; 1172 case QVariant::Double: 1173 valType = "float"; 1174 valStr = QString::number(value.toDouble(), 'g', DBL_DIG); 1175 break; 1176 case QVariant::DateTime: 1177 1178 valType = "date"; 1179 valStr = ""; /* like in saveXML, but why? */ 1180 break; 1181 default: 1182 debugChart <<"ERROR: cell" << row <<"," << col 1183 << " has unknown type." << endl; 1184 } 1185 1186 // Add the value type and the string to the XML tree. 1187 bodyWriter.startElement("table:table-cell"); 1188 if (!valType.isEmpty()) { 1189 bodyWriter.addAttribute("office:value-type", valType); 1190 if (value.type() == QVariant::Double) 1191 bodyWriter.addAttribute("office:value", valStr); 1192 1193 bodyWriter.startElement("text:p"); 1194 bodyWriter.addTextNode(valStr); 1195 bodyWriter.endElement(); // text:p 1196 } 1197 1198 bodyWriter.endElement(); // table:table-cell 1199 } 1200 1201 bodyWriter.endElement(); // table:table-row 1202 } 1203 1204 void ChartShape::saveOdfData(KoXmlWriter &bodyWriter, KoGenStyles &mainStyles) const 1205 { 1206 Q_UNUSED(mainStyles); 1207 1208 // FIXME: Move this method to a sane place 1209 ChartTableModel *internalModel = d->internalModel; 1210 Table *internalTable = d->tableSource.get(internalModel); 1211 Q_ASSERT(internalTable); 1212 1213 // Only save the data if we actually have some. 1214 if (!internalModel) 1215 return; 1216 1217 const int rows = internalModel->rowCount(); 1218 const int cols = internalModel->columnCount(); 1219 1220 bodyWriter.startElement("table:table"); 1221 bodyWriter.addAttribute("table:name", internalTable->name()); 1222 1223 // Exactly one header column, always. 1224 bodyWriter.startElement("table:table-header-columns"); 1225 bodyWriter.startElement("table:table-column"); 1226 bodyWriter.endElement(); // table:table-column 1227 bodyWriter.endElement(); // table:table-header-columns 1228 1229 // Then "cols" columns 1230 bodyWriter.startElement("table:table-columns"); 1231 bodyWriter.startElement("table:table-column"); 1232 bodyWriter.addAttribute("table:number-columns-repeated", cols); 1233 bodyWriter.endElement(); // table:table-column 1234 bodyWriter.endElement(); // table:table-columns 1235 1236 int row = 0; 1237 1238 bodyWriter.startElement("table:table-header-rows"); 1239 if (rows > 0) 1240 saveOdfDataRow(bodyWriter, internalModel, row++); 1241 bodyWriter.endElement(); // table:table-header-rows 1242 1243 // Here start the actual data rows. 1244 bodyWriter.startElement("table:table-rows"); 1245 //QStringList::const_iterator rowLabelIt = m_rowLabels.begin(); 1246 for (; row < rows ; ++row) 1247 saveOdfDataRow(bodyWriter, internalModel, row); 1248 1249 bodyWriter.endElement(); // table:table-rows 1250 bodyWriter.endElement(); // table:table 1251 } 1252 1253 void ChartShape::updateAll() 1254 { 1255 d->legend->update(); 1256 d->plotArea->plotAreaUpdate(); 1257 relayout(); 1258 update(); 1259 } 1260 1261 void ChartShape::update() const 1262 { 1263 KoShape::update(); 1264 layout()->scheduleRelayout(); 1265 1266 emit updateConfigWidget(); 1267 } 1268 1269 void ChartShape::relayout() const 1270 { 1271 Q_ASSERT(d->plotArea); 1272 d->plotArea->relayout(); 1273 KoShape::update(); 1274 } 1275 1276 void ChartShape::requestRepaint() const 1277 { 1278 Q_ASSERT(d->plotArea); 1279 d->plotArea->requestRepaint(); 1280 } 1281 1282 KoDocumentResourceManager *ChartShape::resourceManager() const 1283 { 1284 return d->resourceManager; 1285 } 1286 1287 void ChartShape::setEnableUserInteraction(bool enable) 1288 { 1289 ENABLE_USER_INTERACTION = enable; 1290 } 1291 1292 void ChartShape::shapeChanged(ChangeType type, KoShape *shape) 1293 { 1294 Q_UNUSED(shape) 1295 layout()->containerChanged(this, type); 1296 } 1297 1298 ChartDocument *ChartShape::document() const 1299 { 1300 return d->document; 1301 } 1302 1303 } // Namespace KoChart