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 &region,
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