diff --git a/plugins/chartshape/Axis.cpp b/plugins/chartshape/Axis.cpp index 944ccb0a0b0..2f1fe5f34b2 100644 --- a/plugins/chartshape/Axis.cpp +++ b/plugins/chartshape/Axis.cpp @@ -1,2352 +1,2356 @@ /* This file is part of the KDE project Copyright 2007 Johannes Simon Copyright 2009 Inge Wallin Copyright 2017 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "Axis.h" // Qt #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KoChart #include "PlotArea.h" #include "KChartModel.h" #include "DataSet.h" #include "Legend.h" #include "KChartConvertions.h" #include "ChartProxyModel.h" #include "TextLabelDummy.h" #include "ChartLayout.h" #include "OdfLoadingHelper.h" #include "OdfHelper.h" #include "ChartDebug.h" using namespace KoChart; class Axis::Private { public: Private(Axis *axis, AxisDimension dim); ~Private(); void adjustAllDiagrams(); /// Updates the axis position in the chart's layout /// FIXME: We should instead implement a generic layout position method /// and have the layout find out about our position when it changes. void updatePosition(); void registerDiagram(KChart::AbstractDiagram *diagram); KChart::AbstractDiagram *getDiagramAndCreateIfNeeded(ChartType chartType); KChart::AbstractDiagram *getDiagram(ChartType chartType); void deleteDiagram(ChartType chartType); void deleteDiagram(KChart::AbstractDiagram *diagram); void restoreDiagrams(); void createBarDiagram(); void createLineDiagram(); void createAreaDiagram(); void createCircleDiagram(); void createRingDiagram(); void createRadarDiagram(bool filled); void createScatterDiagram(); void createStockDiagram(); void createBubbleDiagram(); void createSurfaceDiagram(); void createGanttDiagram(); void applyAttributesToDataSet(DataSet* set, ChartType newCharttype); // Pointer to Axis that owns this Private instance Axis * const q; PlotArea *plotArea; const AxisDimension dimension; KoShape *title; TextLabelData *titleData; /// FIXME: Unused variable 'id', including id() getter QString id; QList dataSets; qreal majorInterval; int minorIntervalDivisor; bool showInnerMinorTicks; bool showOuterMinorTicks; bool showInnerMajorTicks; bool showOuterMajorTicks; bool logarithmicScaling; bool showMajorGrid; bool showMinorGrid; bool useAutomaticMajorInterval; bool useAutomaticMinorInterval; bool useAutomaticMinimumRange; bool useAutomaticMaximumRange; KChart::CartesianAxis *const kdAxis; KChart::CartesianCoordinatePlane *kdPlane; KChart::PolarCoordinatePlane *kdPolarPlane; KChart::RadarCoordinatePlane *kdRadarPlane; KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat; QList > diagrams; QPointer kdBarDiagram; QPointer kdLineDiagram; QPointer kdAreaDiagram; QPointer kdCircleDiagram; QPointer kdRingDiagram; QPointer kdRadarDiagram; QPointer kdScatterDiagram; QPointer kdStockDiagram; QPointer kdBubbleDiagram; // FIXME BUG: Somehow we need to visualize something for these // missing chart types. We have some alternatives: // 1. Show an empty area // 2. Show a text "unsupported chart type" // 3. Exchange for something else, e.g. a bar chart. // ... More? // // NOTE: Whatever we do, we should always store the data so that // it can be saved back into the file for a perfect // roundtrip. QPointer kdSurfaceDiagram; QPointer kdGanttDiagram; ChartType plotAreaChartType; ChartSubtype plotAreaChartSubType; // If KChart::LineDiagram::centerDataPoints() property is set to true, // the data points drawn in a line (i.e., also an area) diagram start at // an offset of 0.5, that is, in the middle of a column in the diagram. // Set flag to true if at least one dataset is attached to this axis // that belongs to a horizontal bar chart bool centerDataPoints; int gapBetweenBars; int gapBetweenSets; // TODO: Save // See ODF v1.2 $19.12 (chart:display-label) bool showLabels; bool showOverlappingDataLabels; bool isVisible; QString name; QString axisPosition; QString axisLabelsPosition; }; class CartesianAxis : public KChart::CartesianAxis { public: CartesianAxis(KoChart::Axis *_axis) : KChart::CartesianAxis(), axis(_axis) {} virtual ~CartesianAxis() {} const QString customizedLabel(const QString& label) const override { if (KoOdfNumberStyles::NumericStyleFormat *n = axis->numericStyleFormat()) return KoOdfNumberStyles::format(label, *n); return label; } private: KoChart::Axis *axis; }; Axis::Private::Private(Axis *axis, AxisDimension dim) : q(axis) , dimension(dim) , kdAxis(new CartesianAxis(axis)) , kdPlane(0) , kdPolarPlane(0) , kdRadarPlane(0) , numericStyleFormat(0) { centerDataPoints = false; gapBetweenBars = 0; gapBetweenSets = 100; isVisible = true; useAutomaticMajorInterval = true; useAutomaticMinorInterval = true; useAutomaticMinimumRange = true; useAutomaticMaximumRange = true; majorInterval = 2; minorIntervalDivisor = 1; showMajorGrid = false; showMinorGrid = false; logarithmicScaling = false; showInnerMinorTicks = false; showOuterMinorTicks = false; showInnerMajorTicks = false; showOuterMajorTicks = true; showOverlappingDataLabels = false; showLabels = true; title = 0; titleData = 0; KChart::RulerAttributes attr = kdAxis->rulerAttributes(); attr.setShowRulerLine(true); #if KCHART_VERSION >= ((2<<16)|(6<<8)|(89)) attr.setRulerLinePen(QPen()); #endif kdAxis->setRulerAttributes(attr); } Axis::Private::~Private() { Q_ASSERT(plotArea); q->removeAxisFromDiagrams(); delete kdBarDiagram; delete kdLineDiagram; delete kdAreaDiagram; delete kdCircleDiagram; delete kdRingDiagram; delete kdRadarDiagram; delete kdScatterDiagram; delete kdStockDiagram; delete kdBubbleDiagram; delete kdSurfaceDiagram; delete kdGanttDiagram; delete numericStyleFormat; delete kdAxis; foreach(DataSet *dataSet, dataSets) dataSet->setAttachedAxis(0); } void Axis::Private::registerDiagram(KChart::AbstractDiagram *diagram) { QObject::connect(plotArea->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), diagram->model(), SLOT(slotColumnsInserted(QModelIndex,int,int))); QObject::connect(diagram, SIGNAL(propertiesChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(layoutChanged(AbstractDiagram*)), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(modelsChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(dataHidden()), plotArea, SLOT(plotAreaUpdate())); } KChart::AbstractDiagram *Axis::Private::getDiagramAndCreateIfNeeded(ChartType chartType) { KChart::AbstractDiagram *diagram = 0; switch (chartType) { case BarChartType: if (!kdBarDiagram) createBarDiagram(); diagram = kdBarDiagram; break; case LineChartType: if (!kdLineDiagram) createLineDiagram(); diagram = kdLineDiagram; break; case AreaChartType: if (!kdAreaDiagram) createAreaDiagram(); diagram = kdAreaDiagram; break; case CircleChartType: if (!kdCircleDiagram) createCircleDiagram(); diagram = kdCircleDiagram; break; case RingChartType: if (!kdRingDiagram) createRingDiagram(); diagram = kdRingDiagram; break; case RadarChartType: case FilledRadarChartType: if (!kdRadarDiagram) createRadarDiagram(chartType == FilledRadarChartType); diagram = kdRadarDiagram; break; case ScatterChartType: if (!kdScatterDiagram) createScatterDiagram(); diagram = kdScatterDiagram; break; case StockChartType: if (!kdStockDiagram) createStockDiagram(); diagram = kdStockDiagram; break; case BubbleChartType: if (!kdBubbleDiagram) createBubbleDiagram(); diagram = kdBubbleDiagram; break; case SurfaceChartType: if (!kdSurfaceDiagram) createSurfaceDiagram(); diagram = kdSurfaceDiagram; break; case GanttChartType: if (!kdGanttDiagram) createGanttDiagram(); diagram = kdGanttDiagram; break; default: ; } diagram->setObjectName(q->name()); // for debug adjustAllDiagrams(); debugChartAxis<name()<<"diagram"<coordinatePlane()) { diagram->coordinatePlane()->takeDiagram(diagram); } delete diagram; adjustAllDiagrams(); } void Axis::Private::deleteDiagram(ChartType chartType) { KChart::AbstractDiagram *diagram = getDiagram(chartType); if (diagram) { deleteDiagram(diagram); } } void Axis::Private::createBarDiagram() { Q_ASSERT(kdBarDiagram == 0); kdBarDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdBarDiagram); kdBarDiagram->setModel(model); registerDiagram(kdBarDiagram); // By 'vertical', KChart means the orientation of a chart's bars, // not the orientation of the x axis. kdBarDiagram->setOrientation(plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical); kdBarDiagram->setPen(QPen(Qt::black, 0.0)); kdBarDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdBarDiagram->setType(KChart::BarDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) { kdBarDiagram->setType(KChart::BarDiagram::Percent); kdBarDiagram->setUnitSuffix("%", kdBarDiagram->orientation()); } if (isVisible) { kdBarDiagram->addAxis(kdAxis); q->registerDiagram(kdBarDiagram); } kdPlane->addDiagram(kdBarDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdBarDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdBarDiagram); } } // Set default bar diagram attributes q->setGapBetweenBars(gapBetweenBars); q->setGapBetweenSets(gapBetweenSets); // Propagate existing settings KChart::ThreeDBarAttributes attributes(kdBarDiagram->threeDBarAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdBarDiagram->setThreeDBarAttributes(attributes); q->plotAreaIsVerticalChanged(); plotArea->parent()->legend()->kdLegend()->addDiagram(kdBarDiagram); } void Axis::Private::createLineDiagram() { Q_ASSERT(kdLineDiagram == 0); kdLineDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdLineDiagram); kdLineDiagram->setModel(model); registerDiagram(kdLineDiagram); kdLineDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdLineDiagram->setType(KChart::LineDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdLineDiagram->setType(KChart::LineDiagram::Percent); if (isVisible) { kdLineDiagram->addAxis(kdAxis); q->registerDiagram(kdLineDiagram); } kdPlane->addDiagram(kdLineDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdLineDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdLineDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdLineDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdLineDiagram->setThreeDLineAttributes(attributes); KChart::LineAttributes lineAttr = kdLineDiagram->lineAttributes(); lineAttr.setMissingValuesPolicy(KChart::LineAttributes::MissingValuesHideSegments); kdLineDiagram->setLineAttributes(lineAttr); plotArea->parent()->legend()->kdLegend()->addDiagram(kdLineDiagram); } void Axis::Private::createAreaDiagram() { Q_ASSERT(kdAreaDiagram == 0); kdAreaDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdAreaDiagram); kdAreaDiagram->setModel(model); registerDiagram(kdAreaDiagram); KChart::LineAttributes attr = kdAreaDiagram->lineAttributes(); // Draw the area under the lines. This makes this diagram an area chart. attr.setDisplayArea(true); kdAreaDiagram->setLineAttributes(attr); kdAreaDiagram->setPen(QPen(Qt::black, 0.0)); // KD Chart by default draws the first data set as last line in a normal // line diagram, we however want the first series to appear in front. kdAreaDiagram->setReverseDatasetOrder(true); kdAreaDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdAreaDiagram->setType(KChart::LineDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) { kdAreaDiagram->setType(KChart::LineDiagram::Percent); kdAreaDiagram->setUnitSuffix("%", Qt::Vertical); } if (isVisible) { kdAreaDiagram->addAxis(kdAxis); q->registerDiagram(kdAreaDiagram); } kdPlane->addDiagram(kdAreaDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdAreaDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdAreaDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdAreaDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdAreaDiagram->setThreeDLineAttributes(attributes); plotArea->parent()->legend()->kdLegend()->addDiagram(kdAreaDiagram); } void Axis::Private::createCircleDiagram() { Q_ASSERT(kdCircleDiagram == 0); kdCircleDiagram = new KChart::PieDiagram(plotArea->kdChart(), kdPolarPlane); KChartModel *model = new KChartModel(plotArea, kdCircleDiagram); kdCircleDiagram->setModel(model); registerDiagram(kdCircleDiagram); model->setDataDirection(Qt::Horizontal); plotArea->parent()->legend()->kdLegend()->addDiagram(kdCircleDiagram); kdPolarPlane->addDiagram(kdCircleDiagram); // Propagate existing settings KChart::ThreeDPieAttributes attributes(kdCircleDiagram->threeDPieAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdCircleDiagram->setThreeDPieAttributes(attributes); // Initialize with default values that are specified in PlotArea // Note: KChart takes an int here, though ODF defines the offset to be a double. kdPolarPlane->setStartPosition((int)plotArea->angleOffset()); } void Axis::Private::createRingDiagram() { Q_ASSERT(kdRingDiagram == 0); kdRingDiagram = new KChart::RingDiagram(plotArea->kdChart(), kdPolarPlane); KChartModel *model = new KChartModel(plotArea, kdRingDiagram); kdRingDiagram->setModel(model); registerDiagram(kdRingDiagram); model->setDataDirection(Qt::Horizontal); plotArea->parent()->legend()->kdLegend()->addDiagram(kdRingDiagram); kdPolarPlane->addDiagram(kdRingDiagram); // Propagate existing settings KChart::ThreeDPieAttributes attributes(kdRingDiagram->threeDPieAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdRingDiagram->setThreeDPieAttributes(attributes); // Initialize with default values that are specified in PlotArea // Note: KChart takes an int here, though ODF defines the offset to be a double. kdPolarPlane->setStartPosition((int)plotArea->angleOffset()); } void Axis::Private::createRadarDiagram(bool filled) { Q_ASSERT(kdRadarDiagram == 0); //kdRadarDiagramModel->setDataDimensions(2); //kdRadarDiagramModel->setDataDirection(Qt::Horizontal); kdRadarDiagram = new KChart::RadarDiagram(plotArea->kdChart(), kdRadarPlane); KChartModel *model = new KChartModel(plotArea, kdRadarDiagram); kdRadarDiagram->setModel(model); registerDiagram(kdRadarDiagram); kdRadarDiagram->setCloseDatasets(true); if (filled) { // Don't use a solid fill of 1.0 but a more transparent one so the // grid and the data-value-labels are still visible plus it provides // a better look (other areas can still be seen) even if it's slightly // different from what OO.org does. kdRadarDiagram->setFillAlpha(0.4); } #if 0 // Stacked and Percent not supported by KChart. if (plotAreaChartSubType == StackedChartSubtype) kdRadarDiagram->setType(KChart::PolarDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdRadarDiagram->setType(KChart::PolarDiagram::Percent); #endif plotArea->parent()->legend()->kdLegend()->addDiagram(kdRadarDiagram); kdRadarPlane->addDiagram(kdRadarDiagram); } void Axis::Private::createScatterDiagram() { Q_ASSERT(kdScatterDiagram == 0); Q_ASSERT(plotArea); kdScatterDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdScatterDiagram); kdScatterDiagram->setModel(model); registerDiagram(kdScatterDiagram); model->setDataDimensions(2); kdScatterDiagram->setPen(Qt::NoPen); if (isVisible) { kdScatterDiagram->addAxis(kdAxis); q->registerDiagram(kdScatterDiagram); } kdPlane->addDiagram(kdScatterDiagram); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdScatterDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdScatterDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdScatterDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdScatterDiagram->setThreeDLineAttributes(attributes); plotArea->parent()->legend()->kdLegend()->addDiagram(kdScatterDiagram); } void Axis::Private::createStockDiagram() { Q_ASSERT(kdStockDiagram == 0); kdStockDiagram = new KChart::StockDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdStockDiagram); kdStockDiagram->setModel(model); switch (plotAreaChartSubType) { case HighLowCloseChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::HighLowClose); break; case OpenHighLowCloseChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::OpenHighLowClose); break; case CandlestickChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::Candlestick); break; } registerDiagram(kdStockDiagram); model->setDataDimensions(numDimensions(StockChartType)); #if 0 // Stacked and Percent not supported by KChart. if (plotAreaChartSubType == StackedChartSubtype) kdStockDiagram->setType(KChart::StockDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdStockDiagram->setType(KChart::StockDiagram::Percent); #endif if (isVisible) { kdStockDiagram->addAxis(kdAxis); q->registerDiagram(kdStockDiagram); } kdPlane->addDiagram(kdStockDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdStockDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdStockDiagram); } } plotArea->parent()->legend()->kdLegend()->addDiagram(kdStockDiagram); q->updateKChartStockAttributes(); } void Axis::Private::createBubbleDiagram() { Q_ASSERT(kdBubbleDiagram == 0); Q_ASSERT(plotArea); kdBubbleDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdBubbleDiagram); kdBubbleDiagram->setModel(model); registerDiagram(kdBubbleDiagram); model->setDataDimensions(2); kdPlane->addDiagram(kdBubbleDiagram); foreach (Axis *axis, plotArea->axes()) { //if (axis->dimension() == XAxisDimension) if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdBubbleDiagram->addAxis(axis->kdAxis()); q->registerDiagram(kdBubbleDiagram); } } // disable the connecting line KChart::LineAttributes la = kdBubbleDiagram->lineAttributes(); la.setVisible(false); kdBubbleDiagram->setLineAttributes(la); plotArea->parent()->legend()->kdLegend()->addDiagram(kdBubbleDiagram); } void Axis::Private::createSurfaceDiagram() { Q_ASSERT(!kdSurfaceDiagram); // This is a so far a by KChart unsupported chart type. // Fall back to bar diagram for now. kdSurfaceDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdSurfaceDiagram); kdSurfaceDiagram->setModel(model); registerDiagram(kdSurfaceDiagram); plotArea->parent()->legend()->kdLegend()->addDiagram(kdSurfaceDiagram); kdPlane->addDiagram(kdSurfaceDiagram); } void Axis::Private::createGanttDiagram() { // This is a so far a by KChart unsupported chart type (through KDGantt got merged into KChart with 2.3) Q_ASSERT(!kdGanttDiagram); // This is a so far a by KChart unsupported chart type. // Fall back to bar diagram for now. kdGanttDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdGanttDiagram); kdGanttDiagram->setModel(model); registerDiagram(kdGanttDiagram); plotArea->parent()->legend()->kdLegend()->addDiagram(kdGanttDiagram); kdPlane->addDiagram(kdGanttDiagram); } /** * Automatically adjusts the diagram so that all currently displayed * diagram types fit together. */ void Axis::Private::adjustAllDiagrams() { // If at least one dataset is attached that belongs to a // horizontal bar chart, set centerDataPoints to true. centerDataPoints = kdBarDiagram != 0; if (kdLineDiagram) kdLineDiagram->setCenterDataPoints(centerDataPoints); if (kdAreaDiagram) kdAreaDiagram->setCenterDataPoints(centerDataPoints); } // ================================================================ // class Axis // FIXME: make it possible to create an axis without parent, and // when it is removed, actually remove it from parent (signals and all) Axis::Axis(PlotArea *parent, AxisDimension dimension) : d(new Private(this, dimension)) { Q_ASSERT(parent); parent->addAxis(this); d->plotArea = parent; d->kdAxis->setObjectName(name()); KChart::BackgroundAttributes batt(d->kdAxis->backgroundAttributes()); batt.setBrush(QBrush(Qt::white)); d->kdAxis->setBackgroundAttributes(batt); setFontSize(8.0); // also sets MeasureCalculationModeAbsolute d->kdPlane = parent->kdCartesianPlane(this); d->kdPolarPlane = parent->kdPolarPlane(); d->kdRadarPlane = parent->kdRadarPlane(); d->plotAreaChartType = d->plotArea->chartType(); d->plotAreaChartSubType = d->plotArea->chartSubType(); setOdfAxisPosition("start"); setOdfAxisLabelsPosition("near-axis"); KoShapeFactoryBase *textShapeFactory = KoShapeRegistry::instance()->value(TextShapeId); if (textShapeFactory) d->title = textShapeFactory->createDefaultShape(parent->parent()->resourceManager()); if (d->title) { d->titleData = qobject_cast(d->title->userData()); if (d->titleData == 0) { d->titleData = new TextLabelData; d->title->setUserData(d->titleData); } QFont font = d->titleData->document()->defaultFont(); font.setPointSizeF(9); d->titleData->document()->setDefaultFont(font); } else { d->title = new TextLabelDummy; d->titleData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(d->titleData->document()); d->titleData->document()->setDocumentLayout(documentLayout); d->title->setUserData(d->titleData); } d->title->setSize(QSizeF(CM_TO_POINT(3), CM_TO_POINT(0.75))); d->plotArea->parent()->addShape(d->title); d->plotArea->parent()->setClipped(d->title, true); d->plotArea->parent()->setInheritsTransform(d->title, true); d->title->setDeletable(false); d->title->setZIndex(5); d->title->setToolDelegates(QSet()<parent()<title); // Enable chart tool d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->title->setAdditionalStyleAttribute("chart:auto-position", "true"); d->title->setAllowedInteraction(KoShape::ShearingAllowed, false); d->title->setVisible(false); // Needed to avoid problems when creating secondary axes (Axis creation needs review/refactoring) connect(d->plotArea, SIGNAL(angleOffsetChanged(qreal)), this, SLOT(setAngleOffset(qreal))); connect(d->plotArea, SIGNAL(holeSizeChanged(qreal)), this, SLOT(setHoleSize(qreal))); d->updatePosition(); } Axis::~Axis() { Q_ASSERT(d->plotArea); d->plotArea->parent()->KoShapeContainer::removeShape(d->title); Q_ASSERT(d->title); delete d->title; delete d; } PlotArea* Axis::plotArea() const { return d->plotArea; } KoShape *Axis::title() const { return d->title; } QString Axis::titleText() const { return d->titleData->document()->toPlainText(); } bool Axis::showLabels() const { return d->showLabels; } bool Axis::showOverlappingDataLabels() const { return d->showOverlappingDataLabels; } QString Axis::id() const { return d->id; } AxisDimension Axis::dimension() const { return d->dimension; } QList Axis::dataSets() const { return d->dataSets; } bool Axis::attachDataSet(DataSet *dataSet) { Q_ASSERT(!d->dataSets.contains(dataSet)); if (d->dataSets.contains(dataSet)) return false; d->dataSets.append(dataSet); if (dimension() == YAxisDimension) { dataSet->setAttachedAxis(this); ChartType chartType = dataSet->chartType(); if (chartType == LastChartType) chartType = d->plotAreaChartType; KChart::AbstractDiagram *diagram = d->getDiagramAndCreateIfNeeded(chartType); Q_ASSERT(diagram); KChartModel *model = dynamic_cast(diagram->model()); Q_ASSERT(model); model->addDataSet(dataSet); layoutPlanes(); requestRepaint(); } return true; } bool Axis::detachDataSet(DataSet *dataSet, bool silent) { Q_ASSERT(d->dataSets.contains(dataSet)); if (!d->dataSets.contains(dataSet)) return false; d->dataSets.removeAll(dataSet); if (dimension() == YAxisDimension) { ChartType chartType = dataSet->chartType(); if (chartType == LastChartType) chartType = d->plotAreaChartType; KChart::AbstractDiagram *oldDiagram = d->getDiagram(chartType); Q_ASSERT(oldDiagram); KChartModel *oldModel = dynamic_cast(oldDiagram->model()); Q_ASSERT(oldModel); const int rowCount = oldModel->dataDirection() == Qt::Vertical ? oldModel->columnCount() : oldModel->rowCount(); // If there's only as many rows as needed for *one* // dataset, that means that the dataset we're removing is // the last one in the model --> delete model if (rowCount == oldModel->dataDimensions()) d->deleteDiagram(chartType); else oldModel->removeDataSet(dataSet, silent); dataSet->setKdChartModel(0); dataSet->setAttachedAxis(0); if (!silent) { layoutPlanes(); requestRepaint(); } } return true; } void Axis::clearDataSets() { QList list = d->dataSets; foreach(DataSet *dataSet, list) detachDataSet(dataSet, true); } bool Axis::showRuler() const { return d->kdAxis->rulerAttributes().showRulerLine(); } void Axis::setShowRuler(bool show) { KChart::RulerAttributes attr = d->kdAxis->rulerAttributes(); attr.setShowRulerLine(!attr.showRulerLine()); d->kdAxis->setRulerAttributes(attr); } qreal Axis::majorInterval() const { return d->majorInterval; } void Axis::setMajorInterval(qreal interval) { // Don't overwrite if automatic interval is being requested (for // interval = 0) if (interval != 0.0) { d->majorInterval = interval; d->useAutomaticMajorInterval = false; } else d->useAutomaticMajorInterval = true; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridStepWidth(interval); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridStepWidth(interval); d->kdPolarPlane->setGridAttributes(true, attributes); // FIXME: Hide minor tick marks more appropriately if (!d->showMinorGrid && interval != 0.0) setMinorInterval(interval); requestRepaint(); } qreal Axis::minorInterval() const { return (d->majorInterval / (qreal)d->minorIntervalDivisor); } void Axis::setMinorInterval(qreal interval) { if (interval == 0.0) setMinorIntervalDivisor(0); else setMinorIntervalDivisor(int(qRound(d->majorInterval / interval))); } int Axis::minorIntervalDivisor() const { return d->minorIntervalDivisor; } void Axis::setMinorIntervalDivisor(int divisor) { // A divisor of 0.0 means automatic minor interval calculation if (divisor != 0) { d->minorIntervalDivisor = divisor; d->useAutomaticMinorInterval = false; } else d->useAutomaticMinorInterval = true; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } bool Axis::useAutomaticMajorInterval() const { return d->useAutomaticMajorInterval; } bool Axis::useAutomaticMinorInterval() const { return d->useAutomaticMinorInterval; } void Axis::setUseAutomaticMajorInterval(bool automatic) { d->useAutomaticMajorInterval = automatic; // A value of 0.0 will activate automatic intervals, // but not change d->majorInterval setMajorInterval(automatic ? 0.0 : majorInterval()); } void Axis::setUseAutomaticMinorInterval(bool automatic) { d->useAutomaticMinorInterval = automatic; // A value of 0.0 will activate automatic intervals, // but not change d->minorIntervalDivisor setMinorInterval(automatic ? 0.0 : minorInterval()); } bool Axis::showInnerMinorTicks() const { return d->showInnerMinorTicks; } bool Axis::showOuterMinorTicks() const { return d->showOuterMinorTicks; } bool Axis::showInnerMajorTicks() const { return d->showInnerMinorTicks; } bool Axis::showOuterMajorTicks() const { return d->showOuterMajorTicks; } void Axis::setShowInnerMinorTicks(bool showTicks) { d->showInnerMinorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowOuterMinorTicks(bool showTicks) { d->showOuterMinorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowInnerMajorTicks(bool showTicks) { d->showInnerMajorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowOuterMajorTicks(bool showTicks) { d->showOuterMajorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setScalingLogarithmic(bool logarithmicScaling) { d->logarithmicScaling = logarithmicScaling; if (dimension() != YAxisDimension) return; d->kdPlane->setAxesCalcModeY(d->logarithmicScaling ? KChart::AbstractCoordinatePlane::Logarithmic : KChart::AbstractCoordinatePlane::Linear); d->kdPlane->layoutPlanes(); requestRepaint(); } bool Axis::scalingIsLogarithmic() const { return d->logarithmicScaling; } bool Axis::showMajorGrid() const { return d->showMajorGrid; } void Axis::setShowMajorGrid(bool showGrid) { d->showMajorGrid = showGrid; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridVisible(d->showMajorGrid); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridVisible(d->showMajorGrid); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } bool Axis::showMinorGrid() const { return d->showMinorGrid; } void Axis::setShowMinorGrid(bool showGrid) { d->showMinorGrid = showGrid; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setSubGridVisible(d->showMinorGrid); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setSubGridVisible(d->showMinorGrid); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } void Axis::setTitleText(const QString &text) { d->titleData->document()->setPlainText(text); } void Axis::setShowLabels(bool show) { d->showLabels = show; KChart::TextAttributes textAttr = d->kdAxis->textAttributes(); textAttr.setVisible(show); d->kdAxis->setTextAttributes(textAttr); } void Axis::setShowOverlappingDataLabels(bool show) { d->showOverlappingDataLabels = show; } Qt::Orientation Axis::orientation() const { bool chartIsVertical = d->plotArea->isVertical(); bool horizontal = d->dimension == (chartIsVertical ? YAxisDimension : XAxisDimension); return horizontal ? Qt::Horizontal : Qt::Vertical; } bool Axis::loadOdf(const KoXmlElement &axisElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId); bool reverseAxis = false; d->title->setVisible(false); QPen gridPen(Qt::NoPen); QPen subGridPen(Qt::NoPen); d->showMajorGrid = false; d->showMinorGrid = false; d->showInnerMinorTicks = false; d->showOuterMinorTicks = false; d->showInnerMajorTicks = false; d->showOuterMajorTicks = true; // Use automatic interval calculation by default setMajorInterval(0.0); setMinorInterval(0.0); if (!axisElement.isNull()) { QString styleName = axisElement.attributeNS(KoXmlNS::chart, "style-name", QString()); const KoXmlElement *stylElement = stylesReader.findStyle(styleName, "chart"); if (stylElement) { const QString dataStyleName = stylElement->attributeNS(KoXmlNS::style, "data-style-name", QString()); if (!dataStyleName.isEmpty() && stylesReader.dataFormats().contains(dataStyleName)) { delete d->numericStyleFormat; d->numericStyleFormat = new KoOdfNumberStyles::NumericStyleFormat(stylesReader.dataFormats()[dataStyleName].first); } } KoXmlElement n; forEachElement (n, axisElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "title") { OdfHelper::loadOdfTitle(d->title, n, context); // title shall *always* have AutoResize d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize); } else if (n.localName() == "grid") { bool major = false; if (n.hasAttributeNS(KoXmlNS::chart, "class")) { const QString className = n.attributeNS(KoXmlNS::chart, "class"); if (className == "major") major = true; } else { warnChart << "Error: Axis' element contains no valid class. It must be either \"major\" or \"minor\"."; continue; } if (major) { d->showMajorGrid = true; } else { d->showMinorGrid = true; } if (n.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::style, "style-name", "chart"); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "stroke-color")) { const QString strokeColor = styleStack.property(KoXmlNS::svg, "stroke-color"); //d->showMajorGrid = true; if (major) gridPen = QPen(QColor(strokeColor)); else subGridPen = QPen(QColor(strokeColor)); } } } else if (n.localName() == "categories") { if (n.hasAttributeNS(KoXmlNS::table, "cell-range-address")) { const CellRegion region = CellRegion(helper->tableSource, n.attributeNS(KoXmlNS::table, "cell-range-address")); helper->categoryRegionSpecifiedInXAxis = true; plotArea()->proxyModel()->setCategoryDataRegion(region); } } } if (axisElement.hasAttributeNS(KoXmlNS::chart, "axis-name")) { const QString name = axisElement.attributeNS(KoXmlNS::chart, "name", QString()); setName(name); } // NOTE: chart:dimension already handled by PlotArea before and passed // explicitly in the constructor. + } else { + warnChartOdf<<"No axis element"; } if (axisElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(axisElement, KoXmlNS::chart, "style-name", "chart"); KoCharacterStyle charStyle; charStyle.loadOdf(&axisElement, context); setFont(charStyle.font()); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "logarithmic") && styleStack.property(KoXmlNS::chart, "logarithmic") == "true") { setScalingLogarithmic(true); } if (styleStack.hasProperty(KoXmlNS::chart, "reverse-direction") && styleStack.property(KoXmlNS::chart, "reverse-direction") == "true") { reverseAxis = true; } if (styleStack.hasProperty(KoXmlNS::chart, "interval-major")) setMajorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-major"))); if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor-divisor")) setMinorIntervalDivisor(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor-divisor"))); else if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor")) setMinorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor"))); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-inner")) setShowInnerMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-inner") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-outer")) setShowOuterMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-outer") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-inner")) setShowInnerMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-inner") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-outer")) setShowOuterMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-outer") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "display-label")) setShowLabels(styleStack.property(KoXmlNS::chart, "display-label") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "text-overlap")) setShowOverlappingDataLabels(styleStack.property(KoXmlNS::chart, "text-overlap") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "visible")) setVisible(styleStack.property(KoXmlNS::chart, "visible") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "minimum")) { const qreal minimum = styleStack.property(KoXmlNS::chart, "minimum").toDouble(); const qreal maximum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().second : d->kdPlane->horizontalRange().second; if (orientation() == Qt::Vertical) d->kdPlane->setVerticalRange(qMakePair(minimum, maximum)); else d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum)); d->useAutomaticMinimumRange = false; } if (styleStack.hasProperty(KoXmlNS::chart, "maximum")) { const qreal minimum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().first : d->kdPlane->horizontalRange().first; const qreal maximum = styleStack.property(KoXmlNS::chart, "maximum").toDouble(); if (orientation() == Qt::Vertical) d->kdPlane->setVerticalRange(qMakePair(minimum, maximum)); else d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum)); d->useAutomaticMaximumRange = false; } /*if (styleStack.hasProperty(KoXmlNS::chart, "origin")) { const qreal origin = KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "origin")); }*/ styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "axis-position")) { d->axisPosition = styleStack.property(KoXmlNS::chart, "axis-position"); } if (styleStack.hasProperty(KoXmlNS::chart, "axis-label-position")) { d->axisLabelsPosition = styleStack.property(KoXmlNS::chart, "axis-label-position"); } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); setFontSize(fontSize); } if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) { QString fontColorString = styleStack.property(KoXmlNS::fo, "font-color"); QColor color(fontColorString); if (color.isValid()) { KChart::TextAttributes tatt = kdAxis()->textAttributes(); QPen pen = tatt.pen(); pen.setColor(color); tatt.setPen(pen); kdAxis()->setTextAttributes(tatt); } } if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { QString fontFamilyString = styleStack.property(KoXmlNS::fo, "font-family"); if (!fontFamilyString.isEmpty()) { QFont f = this->font(); f.setFamily(fontFamilyString); setFont(f); } } if (styleStack.hasProperty(KoXmlNS::fo, "font-style")) { QString fontStyle = styleStack.property(KoXmlNS::fo, "font-style"); if (fontStyle == "italic") { QFont f = this->font(); f.setItalic(true); setFont(f); } else if (fontStyle == "oblique") { // TODO } } if (styleStack.hasProperty(KoXmlNS::fo, "font-weight")) { QString fontWeight = styleStack.property(KoXmlNS::fo, "font-weight"); //fo:font-weight attribute are normal, bold, 100, 200, 300, 400, 500, 600, 700, 800 or 900. if (fontWeight == "bold") { QFont f = this->font(); f.setBold(true); setFont(f); } else { // TODO } } } else { + warnChartOdf<<"Axis element has no style information"; setShowLabels(KoOdfWorkaround::fixMissingStyle_DisplayLabel(axisElement, context)); } KChart::GridAttributes gridAttr = d->kdPlane->gridAttributes(orientation()); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); d->kdPlane->setGridAttributes(orientation(), gridAttr); gridAttr = d->kdPolarPlane->gridAttributes(orientation()); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); // if (plotArea()->chartType() == RadarChartType || plotArea()->chartType() == FilledRadarChartType) // d->kdPolarPlane->setGridAttributes(false, gridAttr); // else d->kdPolarPlane->setGridAttributes(true, gridAttr); gridAttr = d->kdRadarPlane->globalGridAttributes(); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); d->kdRadarPlane->setGlobalGridAttributes(gridAttr); KChart::TextAttributes ta(d->kdRadarPlane->textAttributes()); ta.setVisible(helper->categoryRegionSpecifiedInXAxis); ta.setFont(font()); ta.setFontSize(50); d->kdRadarPlane->setTextAttributes(ta); if (reverseAxis) { KChart::CartesianCoordinatePlane *plane = dynamic_cast(kdPlane()); if (plane) { if (orientation() == Qt::Horizontal) plane->setHorizontalRangeReversed(reverseAxis); else // Qt::Vertical plane->setVerticalRangeReversed(reverseAxis); } } // Style of axis is still in styleStack if (!loadOdfChartSubtypeProperties(axisElement, context)) { return false; } if (titleText().isEmpty()) { // do not allow visible empty text d->title->setVisible(false); } requestRepaint(); + debugChartOdf<<"Loaded axis:"<name = name; } // NOTE: only used during save/load to enable attaching axis to datasets QString Axis::name() const { if (!d->name.isEmpty()) { return d->name; } QString name; switch(dimension()) { case XAxisDimension: name = QLatin1Char('x'); break; case YAxisDimension: name = QLatin1Char('y'); break; case ZAxisDimension: name = QLatin1Char('z'); break; } int i = 1; foreach (Axis *axis, d->plotArea->axes()) { if (axis == this) break; if (axis->dimension() == dimension()) i++; } if (i == 1) name = "primary-" + name; else if (i == 2) name = "secondary-" + name; // Usually, there's not more than two axes of the same dimension. // But use a fallback name here nevertheless. else name = QString::number(i) + '-' + name; return name; } void Axis::saveOdf(KoShapeSavingContext &context) { KoXmlWriter &bodyWriter = context.xmlWriter(); KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:axis"); KoGenStyle axisStyle(KoGenStyle::ChartAutoStyle, "chart"); axisStyle.addProperty("chart:logarithmic", scalingIsLogarithmic()); axisStyle.addProperty("chart:reverse-direction", axisDirectionReversed()); if (!d->axisPosition.isEmpty()) { axisStyle.addProperty("chart:axis-position", d->axisPosition); } if (!d->axisLabelsPosition.isEmpty()) { axisStyle.addProperty("chart:axis-label-position", d->axisLabelsPosition); } axisStyle.addProperty("chart:tick-marks-minor-inner", showInnerMinorTicks()); axisStyle.addProperty("chart:tick-marks-minor-outer", showOuterMinorTicks()); axisStyle.addProperty("chart:tick-marks-major-inner", showInnerMajorTicks()); axisStyle.addProperty("chart:tick-marks-major-outer", showOuterMajorTicks()); axisStyle.addProperty("chart:display-label", showLabels()); axisStyle.addProperty("chart:text-overlap", showOverlappingDataLabels()); axisStyle.addProperty("chart:visible", isVisible()); if (dimension() == YAxisDimension) { axisStyle.addProperty("chart:gap-width", d->gapBetweenSets); axisStyle.addProperty("chart:overlap", -d->gapBetweenBars); } if (!d->useAutomaticMinimumRange) { const qreal minimum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().first : d->kdPlane->horizontalRange().first; axisStyle.addProperty("chart:minimum", (int)minimum); } if (!d->useAutomaticMaximumRange) { const qreal maximum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().second : d->kdPlane->horizontalRange().second; axisStyle.addProperty("chart:maximum", (int)maximum); } //axisStyle.addPropertyPt("chart:origin", origin); KChart::TextAttributes tatt = kdAxis()->textAttributes(); QPen pen = tatt.pen(); axisStyle.addProperty("fo:font-color", pen.color().name(), KoGenStyle::TextType); axisStyle.addProperty("fo:font-family", tatt.font().family(), KoGenStyle::TextType); axisStyle.addPropertyPt("fo:font-size", fontSize(), KoGenStyle::TextType); if (font().bold()) { axisStyle.addProperty("fo:font-weight", "bold" , KoGenStyle::TextType); // TODO support other weights } if (font().italic()) { axisStyle.addProperty("fo:font-style", "italic" , KoGenStyle::TextType); // TODO oblique } const QString styleName = mainStyles.insert(axisStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); // TODO scale: logarithmic/linear // TODO visibility if (dimension() == XAxisDimension) bodyWriter.addAttribute("chart:dimension", "x"); else if (dimension() == YAxisDimension) bodyWriter.addAttribute("chart:dimension", "y"); bodyWriter.addAttribute("chart:name", name()); OdfHelper::saveOdfTitle(d->title, bodyWriter, "chart:title", context); if (plotArea()->proxyModel()->categoryDataRegion().isValid()) { bodyWriter.startElement("chart:categories"); bodyWriter.addAttribute("table:cell-range-address", plotArea()->proxyModel()->categoryDataRegion().toString()); bodyWriter.endElement(); } if (showMajorGrid()) saveOdfGrid(context, OdfMajorGrid); if (showMinorGrid()) saveOdfGrid(context, OdfMinorGrid); bodyWriter.endElement(); // chart:axis } void Axis::saveOdfGrid(KoShapeSavingContext &context, OdfGridClass gridClass) { KoXmlWriter &bodyWriter = context.xmlWriter(); KoGenStyles &mainStyles = context.mainStyles(); KoGenStyle gridStyle(KoGenStyle::GraphicAutoStyle, "chart"); KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); QPen gridPen = (gridClass == OdfMinorGrid ? attributes.subGridPen() : attributes.gridPen()); KoOdfGraphicStyles::saveOdfStrokeStyle(gridStyle, mainStyles, gridPen); bodyWriter.startElement("chart:grid"); bodyWriter.addAttribute("chart:class", gridClass == OdfMinorGrid ? "minor" : "major"); bodyWriter.addAttribute("chart:style-name", mainStyles.insert(gridStyle, "ch")); bodyWriter.endElement(); // chart:grid } void Axis::update() const { if (d->kdBarDiagram) { d->kdBarDiagram->doItemsLayout(); d->kdBarDiagram->update(); } if (d->kdLineDiagram) { d->kdLineDiagram->doItemsLayout(); d->kdLineDiagram->update(); } if (d->kdStockDiagram) { d->kdStockDiagram->doItemsLayout(); d->kdStockDiagram->update(); } d->plotArea->parent()->requestRepaint(); } KChart::CartesianAxis *Axis::kdAxis() const { return d->kdAxis; } KChart::AbstractCoordinatePlane *Axis::kdPlane() const { return d->kdPlane; } void Axis::plotAreaChartTypeChanged(ChartType newChartType) { if (dimension() != YAxisDimension) return; // Return if there's nothing to do if (newChartType == d->plotAreaChartType) return; if (d->dataSets.isEmpty()) { d->plotAreaChartType = newChartType; return; } //qDebug() << "changed ChartType"; ChartType oldChartType = d->plotAreaChartType; debugChartAxis<"<kdRadarDiagram->setFillAlpha(0); } else if (newChartType == FilledRadarChartType && oldChartType == RadarChartType) { d->kdRadarDiagram->setFillAlpha(0.4); } else { KChart::AbstractDiagram *newDiagram = d->getDiagram(newChartType); if (newDiagram) { debugChartAxis<<"already exists:"<deleteDiagram(newDiagram); } newDiagram = d->getDiagramAndCreateIfNeeded(newChartType); KChartModel *newModel = dynamic_cast(newDiagram->model()); // FIXME: This causes a crash on unimplemented types. We should // handle that in some other way. Q_ASSERT(newModel); foreach (DataSet *dataSet, d->dataSets) { //if (dataSet->chartType() != LastChartType) { dataSet->setChartType(LastChartType); dataSet->setChartSubType(NoChartSubtype); //} } KChart::AbstractDiagram *oldDiagram = d->getDiagram(oldChartType); Q_ASSERT(oldDiagram); // We need to know the old model so that we can remove the data sets // from the old model that we added to the new model. KChartModel *oldModel = dynamic_cast(oldDiagram->model()); Q_ASSERT(oldModel); foreach (DataSet *dataSet, d->dataSets) { if (dataSet->chartType() != LastChartType) { continue; } newModel->addDataSet(dataSet); const int dataSetCount = oldModel->dataDirection() == Qt::Vertical ? oldModel->columnCount() : oldModel->rowCount(); if (dataSetCount == oldModel->dataDimensions()) { d->deleteDiagram(oldChartType); } else { oldModel->removeDataSet(dataSet); } } } d->plotAreaChartType = newChartType; layoutPlanes(); requestRepaint(); } void Axis::plotAreaChartSubTypeChanged(ChartSubtype subType) { d->plotAreaChartSubType = subType; if (d->kdBarDiagram) { d->kdBarDiagram->setUnitSuffix("", d->kdBarDiagram->orientation()); } switch (d->plotAreaChartType) { case BarChartType: if (d->kdBarDiagram) { KChart::BarDiagram::BarType type; switch (subType) { case StackedChartSubtype: type = KChart::BarDiagram::Stacked; break; case PercentChartSubtype: type = KChart::BarDiagram::Percent; d->kdBarDiagram->setUnitSuffix("%", d->kdBarDiagram->orientation()); break; default: type = KChart::BarDiagram::Normal; } d->kdBarDiagram->setType(type); } break; case LineChartType: if (d->kdLineDiagram) { KChart::LineDiagram::LineType type; switch (subType) { case StackedChartSubtype: type = KChart::LineDiagram::Stacked; break; case PercentChartSubtype: type = KChart::LineDiagram::Percent; d->kdLineDiagram->setUnitSuffix("%", Qt::Vertical); break; default: type = KChart::LineDiagram::Normal; } d->kdLineDiagram->setType(type); } break; case AreaChartType: if (d->kdAreaDiagram) { KChart::LineDiagram::LineType type; switch (subType) { case StackedChartSubtype: type = KChart::LineDiagram::Stacked; break; case PercentChartSubtype: type = KChart::LineDiagram::Percent; d->kdAreaDiagram->setUnitSuffix("%", Qt::Vertical); break; default: type = KChart::LineDiagram::Normal; } d->kdAreaDiagram->setType(type); } break; case RadarChartType: case FilledRadarChartType: #if 0 // FIXME: Stacked and Percent not supported by KChart if (d->kdRadarDiagram) { KChart::PolarDiagram::PolarType type; switch (subType) { case StackedChartSubtype: type = KChart::PolarDiagram::Stacked; break; case PercentChartSubtype: type = KChart::PolarDiagram::Percent; break; default: type = KChart::PolarDiagram::Normal; } d->kdRadarDiagram->setType(type); } #endif break; case StockChartType: if (d->kdStockDiagram) { KChart::StockDiagram::Type type; switch (subType) { case CandlestickChartSubtype: type = KChart::StockDiagram::Candlestick; break; case OpenHighLowCloseChartSubtype: type = KChart::StockDiagram::OpenHighLowClose; break; default: type = KChart::StockDiagram::HighLowClose; break; } d->kdStockDiagram->setType(type); } break; default:; // FIXME: Implement more chart types } Q_FOREACH(DataSet* set, d->dataSets) { set->setChartType(d->plotAreaChartType); set->setChartSubType(subType); } } void Axis::plotAreaIsVerticalChanged() { if (d->kdBarDiagram) { d->kdBarDiagram->setOrientation(d->plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical); } updateKChartAxisPosition(); } void Axis::Private::updatePosition() { // // Is the first x or y axis? // bool first = (dimension == XAxisDimension) ? plotArea->xAxis() == q // : plotArea->yAxis() == q; // // Position position; // ItemType type = GenericItemType; // if (q->orientation() == Qt::Horizontal) { // position = first ? BottomPosition : TopPosition; // type = first ? XAxisTitleType : SecondaryXAxisTitleType; // } else { // position = first ? StartPosition : EndPosition; // type = first ? YAxisTitleType : SecondaryYAxisTitleType; // } // // KChart // kdAxis->setPosition(PositionToKChartAxisPosition(position)); // ChartLayout *layout = plotArea->parent()->layout(); // layout->setPosition(title, position, type); // layout->layout(); // // q->requestRepaint(); } void Axis::registerAxis(Axis *axis) { if (d->kdBarDiagram) { d->kdBarDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdBarDiagram); } if (d->kdLineDiagram) { d->kdLineDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdLineDiagram); } if (d->kdAreaDiagram) { d->kdAreaDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdAreaDiagram); } if (d->kdScatterDiagram) { d->kdScatterDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdScatterDiagram); } if (d->kdStockDiagram) { d->kdStockDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdStockDiagram); } if (d->kdBubbleDiagram) { d->kdBubbleDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdBubbleDiagram); } // FIXME: Add all diagrams here } void Axis::registerDiagram(KChart::AbstractCartesianDiagram *diagram) { if (!d->diagrams.contains(diagram)) { d->diagrams << diagram; } } void Axis::Private::restoreDiagrams() { diagrams.removeAll(nullptr); for (KChart::AbstractCartesianDiagram *diag : diagrams) { diag->addAxis(kdAxis); } } void Axis::removeAxisFromDiagrams(bool clear) { // HACK to remove an x-axis from a y-axis diagram d->diagrams.removeAll(nullptr); for (KChart::AbstractCartesianDiagram *diag : d->diagrams) { diag->takeAxis(d->kdAxis); } if (clear) { d->diagrams.clear(); } } void Axis::setThreeD(bool threeD) { // FIXME: Setting KD Chart attributes does not belong here. They should be // determined dynamically somehow. // KChart if (d->kdBarDiagram) { KChart::ThreeDBarAttributes attributes(d->kdBarDiagram->threeDBarAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdBarDiagram->setThreeDBarAttributes(attributes); } if (d->kdLineDiagram) { KChart::ThreeDLineAttributes attributes(d->kdLineDiagram->threeDLineAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdLineDiagram->setThreeDLineAttributes(attributes); } if (d->kdAreaDiagram) { KChart::ThreeDLineAttributes attributes(d->kdAreaDiagram->threeDLineAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdAreaDiagram->setThreeDLineAttributes(attributes); } if (d->kdCircleDiagram) { KChart::ThreeDPieAttributes attributes(d->kdCircleDiagram->threeDPieAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdCircleDiagram->setThreeDPieAttributes(attributes); } if (d->kdRingDiagram) { KChart::ThreeDPieAttributes attributes(d->kdRingDiagram->threeDPieAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdRingDiagram->setThreeDPieAttributes(attributes); } // The following types don't support 3D, at least not in KChart: // scatter, radar, stock, bubble, surface, gantt requestRepaint(); } void Axis::requestRepaint() const { d->plotArea->requestRepaint(); } void Axis::layoutPlanes() { d->kdPlane->layoutPlanes(); d->kdPolarPlane->layoutPlanes(); d->kdRadarPlane->layoutPlanes(); } int Axis::gapBetweenBars() const { return d->gapBetweenBars; } void Axis::setGapBetweenBars(int percent) { // This method is also used to override KChart's default attributes. // Do not just return and do nothing if value doesn't differ from stored one. d->gapBetweenBars = percent; if (d->kdBarDiagram) { KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes(); attributes.setBarGapFactor((float)percent / 100.0); d->kdBarDiagram->setBarAttributes(attributes); } requestRepaint(); } int Axis::gapBetweenSets() const { return d->gapBetweenSets; } void Axis::setGapBetweenSets(int percent) { // This method is also used to override KChart's default attributes. // Do not just return and do nothing if value doesn't differ from stored one. d->gapBetweenSets = percent; if (d->kdBarDiagram) { KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes(); attributes.setGroupGapFactor((float)percent / 100.0); d->kdBarDiagram->setBarAttributes(attributes); } requestRepaint(); } void Axis::setAngleOffset(qreal angle) { // only set if we already have a diagram else the value will be picked up on creating the diagram if (d->kdPolarPlane->diagram()) { d->kdPolarPlane->setStartPosition(angle); requestRepaint(); } } void Axis::setHoleSize(qreal value) { //TODO KChart does not support } QFont Axis::font() const { return d->kdAxis->textAttributes().font(); } void Axis::setFont(const QFont &font) { // Set the KChart axis to use this font KChart::TextAttributes attr = d->kdAxis->textAttributes(); attr.setFont(font); d->kdAxis->setTextAttributes(attr); } qreal Axis::fontSize() const { return d->kdAxis->textAttributes().fontSize().value(); } void Axis::setFontSize(qreal size) { // KChart has its own fontsize storage, it does not use QFont KChart::TextAttributes attributes = d->kdAxis->textAttributes(); attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); d->kdAxis->setTextAttributes(attributes); // Keep font in sync QFont f = font(); f.setPointSizeF(size); setFont(f); } bool Axis::isVisible() const { return d->isVisible; } void Axis::setVisible(bool visible) { debugChartAxis<isVisible<<"->"<kdBarDiagram; d->isVisible = visible; if (visible) { d->restoreDiagrams(); } else { removeAxisFromDiagrams(); } } KoOdfNumberStyles::NumericStyleFormat *Axis::numericStyleFormat() const { return d->numericStyleFormat; } void Axis::SetNumericStyleFormat(KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat) const { delete d->numericStyleFormat; d->numericStyleFormat = numericStyleFormat; } void Axis::setOdfAxisPosition(const QString &odfpos) { d->axisPosition = odfpos; } QString Axis::odfAxisPosition() const { return d->axisPosition; } void Axis::updateKChartAxisPosition() { if (!isCartesian(d->plotArea->chartType())) { debugChartAxis<plotArea->chartType(); return; } KChart::CartesianAxis::Position pos; if (d->plotArea->xAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Left; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Right; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } else { pos = KChart::CartesianAxis::Bottom; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Top; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->yAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Bottom; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Top; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } else { pos = KChart::CartesianAxis::Left; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Right; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->secondaryXAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Right; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Left; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } else { pos = KChart::CartesianAxis::Top; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Bottom; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Top ? KChart::CartesianAxis::Bottom : KChart::CartesianAxis::Top; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->secondaryYAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Top; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Bottom; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } else { pos = KChart::CartesianAxis::Right; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Left; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Right ? KChart::CartesianAxis::Left : KChart::CartesianAxis::Right; } } d->kdAxis->setPosition(pos); } debugChartAxis<kdAxis<kdAxis->isAbscissa(); d->plotArea->plotAreaUpdate(); } CartesianAxis::Position Axis::kchartAxisPosition() const { return d->kdAxis->position(); } CartesianAxis::Position Axis::actualAxisPosition() const { CartesianAxis::Position pos = d->kdAxis->position(); if (d->plotArea->isVertical()) { switch (pos) { case KChart::CartesianAxis::Bottom: pos = KChart::CartesianAxis::Left; break; case KChart::CartesianAxis::Top: pos = KChart::CartesianAxis::Right; break; case KChart::CartesianAxis::Left: pos = KChart::CartesianAxis::Bottom; break; case KChart::CartesianAxis::Right: pos = KChart::CartesianAxis::Top; break; } } return pos; } bool Axis::axisDirectionReversed() const { bool reversed = false; KChart::CartesianCoordinatePlane *plane = dynamic_cast(kdPlane()); if (plane) { if (orientation() == Qt::Horizontal) reversed = plane->isHorizontalRangeReversed(); else // Qt::Vertical reversed = plane->isVerticalRangeReversed(); } return reversed; } void Axis::setOdfAxisLabelsPosition(const QString &odfpos) { d->axisLabelsPosition = odfpos; } QString Axis::odfAxisLabelsPosition() const { return d->axisLabelsPosition; } void Axis::updateKChartStockAttributes() { if (d->kdStockDiagram) { d->kdStockDiagram->setLowHighLinePen(d->plotArea->stockRangeLinePen()); d->kdStockDiagram->setUpTrendCandlestickBrush(d->plotArea->stockGainBrush()); d->kdStockDiagram->setDownTrendCandlestickBrush(d->plotArea->stockLossBrush()); d->kdStockDiagram->setUpTrendCandlestickPen(d->plotArea->stockRangeLinePen()); d->kdStockDiagram->setDownTrendCandlestickPen(d->plotArea->stockRangeLinePen()); } } QDebug operator<<(QDebug dbg, KoChart::Axis *a) { dbg.nospace().noquote() <<"Axis["<name()<<']'; return dbg.space().quote(); } diff --git a/plugins/chartshape/CellRegion.cpp b/plugins/chartshape/CellRegion.cpp index 1fbca922a82..9b009f2e1bb 100644 --- a/plugins/chartshape/CellRegion.cpp +++ b/plugins/chartshape/CellRegion.cpp @@ -1,804 +1,825 @@ /* This file is part of the KDE project Copyright 2008 Johannes Simon Copyright 2008 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "CellRegion.h" // C #include // Qt #include #include #include #include // KoChart #include "TableSource.h" #include "ChartDebug.h" using std::pow; using namespace KoChart; /************************RegionParser*******************************/ class Parser { public: Parser(const QString & input) - : m_input(input) - , m_pos(m_input.constEnd()) + : m_pos(m_input.constEnd()) { + m_input = input; + if (m_input.contains(":.")) { + // FIXME + warnChartParse<<"Handle 'DotDoubleDot' in input string"; + m_input.replace(QStringLiteral(":."), QChar(':')); + } m_delimiter.append(QChar::fromLatin1('.')); m_delimiter.append(QChar::fromLatin1(':')); m_delimiter.append(QChar::fromLatin1(';')); m_delimiter.append(QChar::fromLatin1(' ')); } bool parse(); QList< QRect > getResult() const { return m_result; } QString tableName() const { return m_tableName; } private: struct Token { enum TokenType{ Dot = 0, DoubleDot = 1, Space = 2, Spacer = 3, Identifier = 4, End }; Token(TokenType type, const QString & identifier): m_type(type), m_identifier(identifier){} Token(): m_type(End) {} TokenType m_type; QString m_identifier; }; inline Token parseToken(); inline void eatWhitespaces(); inline bool parseRegionList(); inline bool parseRegion(); inline bool parseName(); inline bool parseName2(); inline bool parsePoint(); inline bool parseRegion2(); inline void setTableName(const QString &name); +public: + QString toString(Token &token) + { + QString types = "Dot,DoubleDot,Space,Spacer,Identifier,End"; + QString s = QString("Token[%1").arg(types.split(',').value(token.m_type)); + if (token.m_type == Token::Identifier) { + s += ", " + token.m_identifier; + } + s += ']'; + return s; + } + private: - const QString m_input; + QString m_input; QString::ConstIterator m_pos; QList< QRect > m_result; Token m_currentToken; QRect m_currentRect; QPoint m_currentPoint; QString m_tableName; int m_index; QVector< QChar > m_delimiter; }; void Parser::setTableName(const QString &name) { QString strippedName = name; if (name.startsWith(QChar::fromLatin1('$'))) strippedName.remove(0, 1); if (m_tableName.isEmpty()) m_tableName = strippedName; else if (strippedName != m_tableName) - debugChart << "More than one sheet referenced, this is currently not supported"; + debugChartParse << "More than one sheet referenced, this is currently not supported"; } bool Parser::parse() { - //debugChart << "Input " << m_input; + debugChartParse << "Input " << m_input; m_pos = m_input.constBegin(); m_index = 0; m_currentToken = parseToken(); return parseRegionList(); } Parser::Token Parser::parseToken() { Token::TokenType type = Token::End; if (m_pos != m_input.constEnd()) { switch(m_delimiter.indexOf(*m_pos)) { case(0): type = Token::Dot; break; case(1): type = Token::DoubleDot; break; case(2): case(3): type = Token::Space; break; default: type = Token::Identifier; } } if (m_index >= m_input.size()) type = Token::End; else if (*m_pos == QChar::fromLatin1('$')) { ++m_pos; ++m_index; } QString identifier; if (m_pos != m_input.constEnd() && *m_pos == QChar::fromLatin1('\'')) { ++m_pos; ++m_index; int startPos = m_index; for (; m_pos != m_input.constEnd() && *m_pos != QChar::fromLatin1('\''); ++m_pos, ++m_index) ; if (type == Token::Identifier) identifier = m_input.mid(startPos, m_index - startPos); if (m_pos != m_input.constEnd()) { ++m_pos; ++m_index; } } else { int startPos = m_index; for (; m_pos != m_input.constEnd() && !m_delimiter.contains(*m_pos); ++m_pos, ++m_index) ; if (m_pos != m_input.constEnd() && startPos == m_index) { ++m_index; ++m_pos; } if (type == Token::Identifier) identifier = m_input.mid(startPos, m_index - startPos); } - return Token(type, identifier); + Token t(type, identifier); + debugChartParse< specialChars = QList() << ' ' << '\t' << '-' << '\''; bool containsSpecialChars = false; foreach(QChar c, specialChars) containsSpecialChars = containsSpecialChars || name.contains(c); if(containsSpecialChars) return QLatin1Char('\'') + name + QLatin1Char('\''); return name; } /** * Reverts any operation done by formatTableName(), so that ideally * unformatTableName(formatTableName(name)) == name */ /* currently not used in CellRegion(TableSource *source, const QString& regions) static QString unformatTableName(QString name) { if (name.startsWith(QLatin1Char('\'')) && name.endsWith(QLatin1Char('\''))) { name.remove(0, 1).chop(1); } return name; } */ class CellRegion::Private { public: Private(); ~Private(); QString pointToString(const QPoint &point) const; // These are actually one-dimensional, but can have different // orientations (hor / vert). QVector rects; QRect boundingRect; // NOTE: Don't forget to extend operator=() if you add new members /// Table this region is in (name/model pair provided by TableSource) Table *table; }; CellRegion::Private::Private() { table = 0; } CellRegion::Private::~Private() { } // ================================================================ // Class CellRegion CellRegion::CellRegion() : d(new Private()) { } CellRegion::CellRegion(const CellRegion ®ion) : d(new Private()) { // Use operator=(); *this = region; } CellRegion::CellRegion(TableSource *source, const QString& regions) : d(new Private()) { // A dollar sign before a part of the address means that this part // is absolute. This is irrelevant for us, however, thus we can remove // all occurrences of '$', and handle relative and absolute addresses in // the same way. // See ODF specs $8.3.1 "Referencing Table Cells" Parser parser(regions); const bool success = parser.parse(); if (!success) warnChart << "Parsing cell region failed:"< rects = parser.getResult().toVector(); for (int i = 0; i < rects.count(); ++i) { add(rects.at(i)); } d->table = source->get(parser.tableName()); // QStringList regionsList = regions.split(' ', QString::SkipEmptyParts); // Q_FOREACH(const QString& region, regionsList) { // QString searchStr = QString(region).remove('$'); // QRegExp regEx; // // QStringList regionList = searchStr.split(';'); // Q_FOREACH(const QString ®ion, regionList) { // const bool isPoint = !region.contains(':'); // if (isPoint) // regEx = QRegExp("(|.*\\.)([A-Z]+)([0-9]+)"); // else // support range-notations like Sheet1.D2:Sheet1.F2 Sheet1.D2:F2 D2:F2 // regEx = QRegExp ("(|.*\\.)([A-Z]+)([0-9]+)\\:(|.*\\.)([A-Z]+)([0-9]+)"); // // // Check if region string is valid (e.g. not empty) // if (regEx.indexIn(region) >= 0) { // // It is possible for a cell-range-address as defined in ODF to contain // // refernces to cells of more than one sheet. This, however, we ignore // // here. We do not support more than one table in a cell region. // // Also we do not support regions spanned over different sheets. For us // // everything is either on no sheet or on the same sheet. // QString sheetName = regEx.cap(1); // if (sheetName.endsWith(".")) // sheetName = sheetName.left(sheetName.length() - 1); // // TODO: Support for multiple tables in one region // d->table = source->get(unformatTableName(sheetName)); // // QPoint topLeft(rangeStringToInt(regEx.cap(2)), regEx.cap(3).toInt()); // if (isPoint) { // d->rects.append(QRect(topLeft, QSize(1, 1))); // } else { // QPoint bottomRight(rangeStringToInt(regEx.cap(5)), regEx.cap(6).toInt()); // d->rects.append(QRect(topLeft, bottomRight)); // } // } // } // } } CellRegion::CellRegion(Table *table, const QPoint &point) : d(new Private()) { d->table = table; add(point); } CellRegion::CellRegion(Table *table, const QRect &rect) : d(new Private()) { d->table = table; add(rect); } CellRegion::CellRegion(Table *table, const QVector &rects) : d(new Private()) { d->table = table; foreach(const QRect& rect, rects) add(rect); } CellRegion::CellRegion(Table *table) : d(new Private()) { d->table = table; } CellRegion::~CellRegion() { delete d; } CellRegion& CellRegion::operator = (const CellRegion& region) { d->rects = region.d->rects; d->boundingRect = region.d->boundingRect; d->table = region.d->table; return *this; } bool CellRegion::operator == (const CellRegion &other) const { return d->rects == other.d->rects; } Table *CellRegion::table() const { return d->table; } QVector CellRegion::rects() const { return d->rects; } int CellRegion::rectCount() const { return d->rects.size(); } QString CellRegion::sheetName() const { return d->table->name(); } bool CellRegion::isValid() const { return d->rects.size() > 0 && d->table ; } QString CellRegion::Private::pointToString(const QPoint &point) const { QString result; result.append('$' + columnName(point.x())); result.append('$' + QString::number(point.y())); return result; } QString CellRegion::toString() const { if (!isValid()) return QString(); QString result; for (int i = 0; i < d->rects.count(); ++i) { const QRect range = d->rects[i]; // Top-left corner if (table()) result.append('$' + formatTableName(table()->name()) + '.'); result.append(d->pointToString(range.topLeft())); // If it is not a point, append rect's bottom-right corner if (range.topLeft() != range.bottomRight()) { result.append(':'); result.append(d->pointToString(range.bottomRight())); } // Separate ranges by a space // See odf 18.3.6cellRangeAddressList if (i < d->rects.count() - 1) result.append(' '); } return result; } bool CellRegion::contains(const QPoint &point, bool proper) const { foreach (const QRect &rect, d->rects) { if (rect.contains(point, proper)) return true; } return false; } bool CellRegion::contains(const QRect &rect, bool proper) const { foreach (const QRect &r, d->rects) { if (r.contains(rect, proper)) return true; } return false; } bool CellRegion::intersects(const CellRegion &other) const { // If both regions lie within only one table and these tables // are different, they trivially do not intersect. if (table() && other.table() && table() != other.table()) return false; foreach (const QRect &r, d->rects) { foreach(const QRect &_r, other.d->rects) { if (r.intersects(_r)) return true; } } return false; } CellRegion CellRegion::intersected(const QRect &rect) const { CellRegion intersections; foreach (const QRect &r, d->rects) { if (r.intersects(rect)) intersections.add(r.intersected(rect)); } return intersections; } Qt::Orientation CellRegion::orientation() const { foreach (const QRect &rect, d->rects) { if (rect.width() > 1) return Qt::Horizontal; if (rect.height() > 1) return Qt::Vertical; } // Default if region is only one cell return Qt::Vertical; } int CellRegion::cellCount() const { int count = 0; /*FIXME the following would be more correct cause it * would also cover multi-dimensional ranges (means * where rect.width()>1 *and* rect.height()>1). But * for that kchart needs lot of fixing (e.g. in * the CellRegion to proper handle multi-dimension * ranges too). * foreach (const QRect &rect, d->rects) count += (rect.width() * rect.height()); */ if (orientation() == Qt::Horizontal) { foreach (const QRect &rect, d->rects) count += rect.width(); } else { foreach(const QRect &rect, d->rects) count += rect.height(); } return count; } void CellRegion::add(const CellRegion &other) { add(other.rects()); } void CellRegion::add(const QPoint &point) { add(QRect(point, QSize(1, 1))); } void CellRegion::add(const QRect &rect) { // These checks are obsolete, a CellRegion can be used otherwise as well #if 0 if (!rect.isValid()) { warnChart << "CellRegion::add() Attempt to add invalid rectangle"; warnChart << "CellRegion::add():" << rect; return; } if (rect.width() > 1 && rect.height() > 1) { warnChart << "CellRegion::add() Attempt to add rectangle with height AND width > 1"; warnChart << "CellRegion::add():" << rect; return; } #endif d->rects.append(rect); d->boundingRect |= rect; } void CellRegion::add(const QVector &rects) { foreach (const QRect &rect, rects) add(rect); } QRect CellRegion::boundingRect() const { return d->boundingRect; } bool CellRegion::hasPointAtIndex(int index) const { return pointAtIndex(index) != QPoint(-1, -1); } QPoint CellRegion::pointAtIndex(int index) const { // sum of all previous rectangle indices int i = 0; foreach (const QRect &rect, d->rects) { // Rectangle is horizontal if (rect.width() > 1) { // Found it! // Index refers to point in current rectangle if (i + rect.width() > index) { // Local index of point in this rectangle int j = index - i; return QPoint(rect.x() + j, rect.y()); } // add number of indices in current rectangle to total index count i += rect.width(); } else { // Found it! // Index refers to point in current rectangle if (i + rect.height() > index) { // Local index of point in this rectangle int j = index - i; return QPoint(rect.x(), rect.y() + j); } // add number of indices in current rectangle to total index count i += rect.height(); } } // Invalid index! return QPoint(-1, -1); } int CellRegion::indexAtPoint(const QPoint &point) const { int indicesLeftToPoint = 0; bool found = false; foreach (const QRect &rect, d->rects) { if (!rect.contains(point)) { indicesLeftToPoint += rect.width() > 1 ? rect.width() : rect.height(); continue; } found = true; if (rect.width() > 1) indicesLeftToPoint += point.x() - rect.topLeft().x(); else indicesLeftToPoint += point.y() - rect.topLeft().y(); } return found ? indicesLeftToPoint : -1; } #if 0 // Unused? static int rangeCharToInt(char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A' + 1) : -1; } static int rangeStringToInt(const QString &string) { int result = 0; const int size = string.size(); for (int i = 0; i < size; i++) { //debugChart << "---" << float(rangeCharToInt(string[i].toLatin1()) * pow(10.0, (size - i - 1))); result += rangeCharToInt(string[i].toLatin1()) * pow(10.0, (size - i - 1)); } //debugChart << "+++++ result=" << result; return result; } static QString rangeIntToString(int i) { QString tmp = QString::number(i); for (int j = 0; j < tmp.size(); j++) { tmp[j] = 'A' + tmp[j].toLatin1() - '1'; } //debugChart << "tmp=" << tmp; return tmp; } #endif int CellRegion::rangeCharToInt(char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A' + 1) : -1; } int CellRegion::rangeStringToInt(const QString &string) { int result = 0; const int size = string.size(); for (int i = 0; i < size; i++) { result += rangeCharToInt(string[i].toLatin1()) * pow(10.0, (size - i - 1)); } return result; } QString CellRegion::rangeIntToString(int i) { QString tmp = QString::number(i); for (int j = 0; j < tmp.size(); j++) { tmp[j] = 'A' + tmp[j].toLatin1() - '1'; } return tmp; } // Return the symbolic name of any column. QString CellRegion::columnName(uint column) { if (column < 1 || column > 32767) return QString("@@@"); QString str; unsigned digits = 1; unsigned offset = 0; column--; for (unsigned limit = 26; column >= limit + offset; limit *= 26, ++digits) offset += limit; for (unsigned col = column - offset; digits; --digits, col /= 26) str.prepend(QChar('A' + (col % 26))); return str; } QDebug operator<<(QDebug dbg, const KoChart::CellRegion &r) { dbg << "CellRegion[" << r.toString() << ']'; return dbg; } diff --git a/plugins/chartshape/ChartDebug.cpp b/plugins/chartshape/ChartDebug.cpp index 536bb45804a..8ec3e21c24f 100644 --- a/plugins/chartshape/ChartDebug.cpp +++ b/plugins/chartshape/ChartDebug.cpp @@ -1,105 +1,111 @@ /* Copyright (c) 2017 Dag Andersen Copyright (c) 2015 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ChartDebug.h" const QLoggingCategory &CHART_LOG() { static const QLoggingCategory category("calligra.plugin.chart.shape"); return category; } +const QLoggingCategory &CHARTPARSE_LOG() +{ + static const QLoggingCategory category("calligra.plugin.chart.parse"); + return category; +} + const QLoggingCategory &CHARTLAYOUT_LOG() { static const QLoggingCategory category("calligra.plugin.chart.layout"); return category; } const QLoggingCategory &CHARTODF_LOG() { static const QLoggingCategory category("calligra.plugin.chart.odf"); return category; } const QLoggingCategory &CHARTTOOL_LOG() { static const QLoggingCategory category("calligra.plugin.chart.tool"); return category; } const QLoggingCategory &CHARTAXIS_LOG() { static const QLoggingCategory category("calligra.plugin.chart.axis"); return category; } const QLoggingCategory &CHARTDADASET_LOG() { static const QLoggingCategory category("calligra.plugin.chart.dataset"); return category; } const QLoggingCategory &CHARTUI_TITLES_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.titles"); return category; } const QLoggingCategory &CHARTUI_LEGEND_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.legend"); return category; } const QLoggingCategory &CHARTUI_PLOTAREA_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.plotarea"); return category; } const QLoggingCategory &CHARTUI_AXES_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.axes"); return category; } const QLoggingCategory &CHARTUI_DATASET_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.dataset"); return category; } const QLoggingCategory &CHARTUI_BUBBLE_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.bubble"); return category; } const QLoggingCategory &CHARTUI_SCATTER_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.scatter"); return category; } const QLoggingCategory &CHARTUI_STOCK_LOG() { static const QLoggingCategory category("calligra.plugin.chart.ui.stock"); return category; } diff --git a/plugins/chartshape/ChartDebug.h b/plugins/chartshape/ChartDebug.h index e35b23410c9..d814e99f207 100644 --- a/plugins/chartshape/ChartDebug.h +++ b/plugins/chartshape/ChartDebug.h @@ -1,112 +1,118 @@ /* Copyright (c) 2017 Dag Andersen * Copyright (c) 2015 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CHART_DEBUG_H #define CHART_DEBUG_H #include #include extern const QLoggingCategory &CHART_LOG(); #define debugChart qCDebug(CHART_LOG) #define warnChart qCWarning(CHART_LOG) #define errorChart qCCritical(CHART_LOG) +extern const QLoggingCategory &CHARTPARSE_LOG(); + +#define debugChartParse qCDebug(CHARTPARSE_LOG) +#define warnChartParse qCWarning(CHARTPARSE_LOG) +#define errorChartParse qCCritical(CHARTPARSE_LOG) + extern const QLoggingCategory &CHARTLAYOUT_LOG(); #define debugChartLayout qCDebug(CHARTLAYOUT_LOG)< Copyright 2007-2010 Inge Wallin Copyright 2007-2008 Johannes Simon Copyright 2017 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "ChartShape.h" // Posix #include // For basic data types characteristics. // Qt #include #include #include #include #include #include // KF5 #include // KChart #include #include #include #include #include #include "KChartConvertions.h" // Attribute Classes #include #include #include #include #include #include #include // Diagram Classes #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KoChart #include "Axis.h" #include "DataSet.h" #include "Legend.h" #include "PlotArea.h" #include "Surface.h" #include "ChartProxyModel.h" #include "TextLabelDummy.h" #include "ChartDocument.h" #include "ChartTableModel.h" #include "ChartLayout.h" #include "TableSource.h" #include "OdfLoadingHelper.h" #include "SingleModelHelper.h" #include "OdfHelper.h" #include "ChartDebug.h" // Define the protocol used here for embedded documents' URL // This used to "store" but KUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" #define INTERNAL_PROTOCOL "intern" namespace KoChart { /// @see ChartShape::setEnableUserInteraction() static bool ENABLE_USER_INTERACTION = true; static const char * const ODF_CHARTTYPES[NUM_CHARTTYPES] = { "chart:bar", "chart:line", "chart:area", "chart:circle", "chart:ring", "chart:scatter", "chart:radar", "chart:filled-radar", "chart:stock", "chart:bubble", "chart:surface", "chart:gantt" }; static const ChartSubtype defaultSubtypes[NUM_CHARTTYPES] = { NormalChartSubtype, // Bar NormalChartSubtype, // Line NormalChartSubtype, // Area NoChartSubtype, // Circle NoChartSubtype, // Ring NoChartSubtype, // Scatter NormalChartSubtype, // Radar NormalChartSubtype, // Filled Radar HighLowCloseChartSubtype, // Stock NoChartSubtype, // Bubble NoChartSubtype, // Surface NoChartSubtype // Gantt }; const char * odfCharttype(int charttype) { Q_ASSERT(charttype < LastChartType); if (charttype >= LastChartType || charttype < 0) { charttype = 0; } return ODF_CHARTTYPES[charttype]; } static const int NUM_DEFAULT_DATASET_COLORS = 12; static const char * const defaultDataSetColors[NUM_DEFAULT_DATASET_COLORS] = { "#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff", "#314004", "#aecf00", "#4b1f6f", "#ff950e", "#c5000b", "#0084d1", }; QColor defaultDataSetColor(int dataSetNum) { dataSetNum %= NUM_DEFAULT_DATASET_COLORS; return QColor(defaultDataSetColors[dataSetNum]); } // ================================================================ // The Private class class ChartShape::Private { public: Private(ChartShape *shape); ~Private(); void setChildVisible(KoShape *label, bool doShow); // The components of a chart KoShape *title; KoShape *subTitle; KoShape *footer; Legend *legend; PlotArea *plotArea; // Data ChartProxyModel *proxyModel; /// What's presented to KChart ChartTableModel *internalModel; TableSource tableSource; SingleModelHelper *internalModelHelper; bool usesInternalModelOnly; /// @see usesInternalModelOnly() ChartDocument *document; ChartShape *shape; // The chart that owns this ChartShape::Private KoDocumentResourceManager *resourceManager; }; ChartShape::Private::Private(ChartShape *shape) : internalModel(0) , internalModelHelper(0) , resourceManager(0) { // Register the owner. this->shape = shape; // Components title = 0; subTitle = 0; footer = 0; legend = 0; plotArea = 0; // Data proxyModel = 0; // If not explicitly set otherwise, this chart provides its own data. usesInternalModelOnly = true; document = 0; } ChartShape::Private::~Private() { } // // Show a child, which means either the Title, Subtitle, Footer or Axis Title. // // If there is too little room, then make space by shrinking the Plotarea. // void ChartShape::Private::setChildVisible(KoShape *child, bool doShow) { Q_ASSERT(child); if (child->isVisible() == doShow) return; child->setVisible(doShow); // FIXME: Shouldn't there be a KoShape::VisibilityChanged for KoShape::shapeChanged()? shape->layout()->scheduleRelayout(); } // ================================================================ // Class ChartShape // ================================================================ ChartShape::ChartShape(KoDocumentResourceManager *resourceManager) : KoFrameShape(KoXmlNS::draw, "object") , KoShapeContainer(new ChartLayout) , d (new Private(this)) { d->resourceManager = resourceManager; setShapeId(ChartShapeId); // Instantiated all children first d->proxyModel = new ChartProxyModel(this, &d->tableSource); d->plotArea = new PlotArea(this); d->document = new ChartDocument(this); d->legend = new Legend(this); // Configure the plotarea. // We need this as the very first step, because some methods // here rely on the d->plotArea pointer. addShape(d->plotArea); d->plotArea->plotAreaInit(); d->plotArea->setZIndex(0); setClipped(d->plotArea, true); setInheritsTransform(d->plotArea, true); d->plotArea->setDeletable(false); d->plotArea->setToolDelegates(QSet()<plotArea->setAllowedInteraction(KoShape::ShearingAllowed, false); // Configure the legend. d->legend->setVisible(true); d->legend->setZIndex(1); setClipped(d->legend, true); setInheritsTransform(d->legend, true); d->legend->setDeletable(false); d->legend->setToolDelegates(QSet()<legend->setAllowedInteraction(KoShape::ShearingAllowed, false); // A few simple defaults (chart type and subtype in this case) setChartType(BarChartType); setChartSubType(NormalChartSubtype); // Create the Title, which is a standard TextShape. KoShapeFactoryBase *textShapeFactory = KoShapeRegistry::instance()->value(TextShapeId); if (textShapeFactory) d->title = textShapeFactory->createDefaultShape(resourceManager); // Potential problem 1) No TextShape installed if (!d->title) { d->title = new TextLabelDummy; if (ENABLE_USER_INTERACTION) KMessageBox::error(0, i18n("The plugin needed for displaying text labels in a chart is not available."), i18n("Plugin Missing")); // Potential problem 2) TextShape incompatible } else if (dynamic_cast(d->title->userData()) == 0 && ENABLE_USER_INTERACTION) KMessageBox::error(0, i18n("The plugin needed for displaying text labels is not compatible with the current version of the chart Flake shape."), i18n("Plugin Incompatible")); // In both cases we need a KoTextShapeData instance to function. This is // enough for unit tests, so there has to be no TextShape plugin doing the // actual text rendering, we just need KoTextShapeData which is in the libs. TextLabelData *labelData = dynamic_cast(d->title->userData()); if (labelData == 0) { labelData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); labelData->document()->setDocumentLayout(documentLayout); d->title->setUserData(labelData); } // Add the title to the shape addShape(d->title); QFont font = titleData()->document()->defaultFont(); font.setPointSizeF(12.0); titleData()->document()->setDefaultFont(font); titleData()->document()->setPlainText(i18n("Title")); // Set a reasonable size, it will be resized automatically d->title->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); d->title->setVisible(false); d->title->setZIndex(2); setClipped(d->title, true); setInheritsTransform(d->title, true); d->title->setDeletable(false); d->title->setToolDelegates(QSet()<title); // Enable chart tool labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->title->setAdditionalStyleAttribute("chart:auto-position", "true"); d->title->setAllowedInteraction(KoShape::ShearingAllowed, false); // Create the Subtitle and add it to the shape. if (textShapeFactory) d->subTitle = textShapeFactory->createDefaultShape(resourceManager); if (!d->subTitle) { d->subTitle = new TextLabelDummy; } labelData = dynamic_cast(d->subTitle->userData()); if (labelData == 0) { labelData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); labelData->document()->setDocumentLayout(documentLayout); d->subTitle->setUserData(labelData); } addShape(d->subTitle); font = subTitleData()->document()->defaultFont(); font.setPointSizeF(10.0); subTitleData()->document()->setDefaultFont(font); subTitleData()->document()->setPlainText(i18n("Subtitle")); // Set a reasonable size, it will be resized automatically d->subTitle->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); d->subTitle->setVisible(false); d->subTitle->setZIndex(3); setClipped(d->subTitle, true); setInheritsTransform(d->subTitle, true); d->subTitle->setDeletable(false); d->subTitle->setToolDelegates(QSet()<subTitle); // Enable chart tool labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->subTitle->setAdditionalStyleAttribute("chart:auto-position", "true"); d->subTitle->setAllowedInteraction(KoShape::ShearingAllowed, false); // Create the Footer and add it to the shape. if (textShapeFactory) d->footer = textShapeFactory->createDefaultShape(resourceManager); if (!d->footer) { d->footer = new TextLabelDummy; } labelData = dynamic_cast(d->footer->userData()); if (labelData == 0) { labelData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); labelData->document()->setDocumentLayout(documentLayout); d->footer->setUserData(labelData); } addShape(d->footer); font = footerData()->document()->defaultFont(); font.setPointSizeF(10.0); footerData()->document()->setDefaultFont(font); footerData()->document()->setPlainText(i18n("Footer")); // Set a reasonable size, it will be resized automatically d->footer->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); d->footer->setVisible(false); d->footer->setZIndex(4); setClipped(d->footer, true); setInheritsTransform(d->footer, true); d->footer->setDeletable(false); d->footer->setToolDelegates(QSet()<footer); // Enable chart tool labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->footer->setAdditionalStyleAttribute("chart:auto-position", "true"); d->footer->setAllowedInteraction(KoShape::ShearingAllowed, false); // Set default contour (for how text run around is done around this shape) // to prevent a crash in LO setTextRunAroundContour(KoShape::ContourBox); QSharedPointer background(new KoColorBackground(Qt::white)); setBackground(background); KoShapeStroke *stroke = new KoShapeStroke(0, Qt::black); setStroke(stroke); setSize(QSizeF(CM_TO_POINT(8), CM_TO_POINT(5))); // Tell layout about item types ChartLayout *l = layout(); l->setItemType(d->plotArea, PlotAreaType); l->setItemType(d->title, TitleLabelType); l->setItemType(d->subTitle, SubTitleLabelType); l->setItemType(d->footer, FooterLabelType); l->setItemType(d->legend, LegendType); l->layout(); requestRepaint(); } ChartShape::~ChartShape() { delete d->title; delete d->subTitle; delete d->footer; delete d->legend; delete d->plotArea; delete d->proxyModel; delete d->document; delete d; } ChartProxyModel *ChartShape::proxyModel() const { return d->proxyModel; } KoShape *ChartShape::title() const { return d->title; } TextLabelData *ChartShape::titleData() const { TextLabelData *data = qobject_cast(d->title->userData()); return data; } KoShape *ChartShape::subTitle() const { return d->subTitle; } TextLabelData *ChartShape::subTitleData() const { TextLabelData *data = qobject_cast(d->subTitle->userData()); return data; } KoShape *ChartShape::footer() const { return d->footer; } TextLabelData *ChartShape::footerData() const { TextLabelData *data = qobject_cast(d->footer->userData()); return data; } QList ChartShape::labels() const { QList labels; labels.append(d->title); labels.append(d->footer); labels.append(d->subTitle); foreach(Axis *axis, plotArea()->axes()) { labels.append(axis->title()); } return labels; } Legend *ChartShape::legend() const { // There has to be a valid legend even, if it's hidden. Q_ASSERT(d->legend); return d->legend; } PlotArea *ChartShape::plotArea() const { return d->plotArea; } ChartLayout *ChartShape::layout() const { ChartLayout *l = dynamic_cast(KoShapeContainer::model()); Q_ASSERT(l); return l; } void ChartShape::showTitle(bool doShow) { d->setChildVisible(d->title, doShow); } void ChartShape::showSubTitle(bool doShow) { d->setChildVisible(d->subTitle, doShow); } void ChartShape::showFooter(bool doShow) { d->setChildVisible(d->footer, doShow); } ChartTableModel *ChartShape::internalModel() const { return d->internalModel; } void ChartShape::setInternalModel(ChartTableModel *model) { Table *table = d->tableSource.get(model); Q_ASSERT(table); delete d->internalModelHelper; delete d->internalModel; d->internalModel = model; d->internalModelHelper = new SingleModelHelper(table, d->proxyModel); } TableSource *ChartShape::tableSource() const { return &d->tableSource; } bool ChartShape::usesInternalModelOnly() const { return d->usesInternalModelOnly; } void ChartShape::setUsesInternalModelOnly(bool doesSo) { d->usesInternalModelOnly = doesSo; } // ---------------------------------------------------------------- // getters and setters ChartType ChartShape::chartType() const { Q_ASSERT(d->plotArea); return d->plotArea->chartType(); } ChartSubtype ChartShape::chartSubType() const { Q_ASSERT(d->plotArea); return d->plotArea->chartSubType(); } bool ChartShape::isThreeD() const { Q_ASSERT(d->plotArea); return d->plotArea->isThreeD(); } void ChartShape::setSheetAccessModel(QAbstractItemModel *model) { d->tableSource.setSheetAccessModel(model); } void ChartShape::reset(const QString ®ion, bool firstRowIsLabel, bool firstColumnIsLabel, Qt::Orientation dataDirection) { // This method is provided via KoChartInterface, which is // used by embedding applications. d->usesInternalModelOnly = false; d->proxyModel->setFirstRowIsLabel(firstRowIsLabel); d->proxyModel->setFirstColumnIsLabel(firstColumnIsLabel); d->proxyModel->setDataDirection(dataDirection); d->proxyModel->reset(CellRegion(&d->tableSource, region)); } void ChartShape::setChartType(ChartType type) { Q_ASSERT(d->plotArea); ChartType prev = chartType(); d->proxyModel->setDataDimensions(numDimensions(type)); d->plotArea->setChartType(type); emit chartTypeChanged(type, prev); } void ChartShape::setChartSubType(ChartSubtype subType, bool reset) { Q_ASSERT(d->plotArea); ChartSubtype prev = d->plotArea->chartSubType(); d->plotArea->setChartSubType(subType); if (reset && chartType() == StockChartType && prev != subType && d->internalModel && d->usesInternalModelOnly) { // HACK to get reasonable behaviour in most cases // Stock charts are special because subtypes interpretes data differently from another: // - HighLowCloseChartSubtype assumes High = row 0, Low = row 1 and Close = row 2 // - The other types assumes Open = row 0, High = row 1, Low = row 2 and Close = row 3 // This makes switching between them a bit unintuitive. if (subType == HighLowCloseChartSubtype && d->internalModel->rowCount() > 3) { d->proxyModel->removeRows(0, 1); // remove Open } else { // just reset and hope for the best CellRegion region(d->tableSource.get(d->internalModel), QRect(1, 1, d->internalModel->columnCount(), d->internalModel->rowCount())); d->proxyModel->reset(region); } } emit updateConfigWidget(); } void ChartShape::setThreeD(bool threeD) { Q_ASSERT(d->plotArea); d->plotArea->setThreeD(threeD); } // ---------------------------------------------------------------- void ChartShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { // Only does a relayout if scheduled layout()->layout(); // Paint the background applyConversion(painter, converter); if (background()) { // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); painter.setClipRect(paintRect, Qt::IntersectClip); QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // Paint border if showTextShapeOutlines is set // This means that it will be painted in words but not eg in sheets if (paintContext.showTextShapeOutlines) { if (qAbs(rotation()) > 1) { painter.setRenderHint(QPainter::Antialiasing); } QPen pen(QColor(210, 210, 210), 0); // use cosmetic pen QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0)); QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y())); painter.setPen(pen); painter.drawRect(rect); } } void ChartShape::paintDecorations(QPainter &painter, const KoViewConverter &converter, const KoCanvasBase *canvas) { // This only is a helper decoration, do nothing if we're already // painting handles anyway. Q_ASSERT(canvas); if (canvas->shapeManager()->selection()->selectedShapes().contains(this)) return; if (stroke()) return; QRectF border = QRectF(QPointF(-1.5, -1.5), converter.documentToView(size()) + QSizeF(1.5, 1.5)); painter.setPen(QPen(Qt::lightGray, 0)); painter.drawRect(border); } // ---------------------------------------------------------------- // Loading and Saving bool ChartShape::loadEmbeddedDocument(KoStore *store, const KoXmlElement &objectElement, const KoOdfLoadingContext &loadingContext) { if (!objectElement.hasAttributeNS(KoXmlNS::xlink, "href")) { errorChart << "Object element has no valid xlink:href attribute"; return false; } QString url = objectElement.attributeNS(KoXmlNS::xlink, "href"); // It can happen that the url is empty e.g. when it is a // presentation:placeholder. if (url.isEmpty()) { return true; } QString tmpURL; if (url[0] == '#') url.remove(0, 1); if (QUrl::fromUserInput(url).isRelative()) { if (url.startsWith(QLatin1String("./"))) tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url.mid(2); else tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url; } else tmpURL = url; QString path = tmpURL; if (tmpURL.startsWith(INTERNAL_PROTOCOL)) { path = store->currentPath(); if (!path.isEmpty() && !path.endsWith('/')) path += '/'; QString relPath = QUrl::fromUserInput(tmpURL).path(); path += relPath.mid(1); // remove leading '/' } if (!path.endsWith('/')) path += '/'; const QString mimeType = loadingContext.mimeTypeForPath(path); //debugChart << "path for manifest file=" << path << "mimeType=" << mimeType; if (mimeType.isEmpty()) { //debugChart << "Manifest doesn't have media-type for" << path; return false; } const bool isOdf = mimeType.startsWith(QLatin1String("application/vnd.oasis.opendocument")); if (!isOdf) { tmpURL += "/maindoc.xml"; //debugChart << "tmpURL adjusted to" << tmpURL; } //debugChart << "tmpURL=" << tmpURL; bool res = true; if (tmpURL.startsWith(STORE_PROTOCOL) || tmpURL.startsWith(INTERNAL_PROTOCOL) || QUrl::fromUserInput(tmpURL).isRelative()) { if (isOdf) { store->pushDirectory(); Q_ASSERT(tmpURL.startsWith(INTERNAL_PROTOCOL)); QString relPath = QUrl::fromUserInput(tmpURL).path().mid(1); store->enterDirectory(relPath); res = d->document->loadOasisFromStore(store); store->popDirectory(); } else { if (tmpURL.startsWith(INTERNAL_PROTOCOL)) tmpURL = QUrl::fromUserInput(tmpURL).path().mid(1); res = d->document->loadFromStore(store, tmpURL); } d->document->setStoreInternal(true); } else { // Reference to an external document. Hmmm... d->document->setStoreInternal(false); QUrl url = QUrl::fromUserInput(tmpURL); if (!url.isLocalFile()) { //QApplication::restoreOverrideCursor(); // For security reasons we need to ask confirmation if the // url is remote. int result = KMessageBox::warningYesNoCancel( 0, i18n("This document contains an external link to a remote document\n%1", tmpURL), i18n("Confirmation Required"), KGuiItem(i18n("Download")), KGuiItem(i18n("Skip"))); if (result == KMessageBox::Cancel) { //d->m_parent->setErrorMessage("USER_CANCELED"); return false; } if (result == KMessageBox::Yes) res = d->document->openUrl(url); // and if == No, res will still be false so we'll use a kounavail below } else res = d->document->openUrl(url); } if (!res) { QString errorMessage = d->document->errorMessage(); return false; } // Still waiting... //QApplication::setOverrideCursor(Qt::WaitCursor); tmpURL.clear(); //QApplication::restoreOverrideCursor(); return res; } bool ChartShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { //struct Timer{QTime t;Timer(){t.start();} ~Timer(){debugChart<<">>>>>"<"; return false; } bool ChartShape::loadOdfChartElement(const KoXmlElement &chartElement, KoShapeLoadingContext &context) { // Use a helper-class created on the stack to be sure a we always leave // this method with a call to endLoading proxyModel()->endLoading() struct ProxyModelLoadState { ChartProxyModel *m; ChartLayout *l; ProxyModelLoadState(ChartProxyModel *m, ChartLayout *l) : m(m), l(l) { m->beginLoading(); l->setLayoutingEnabled(false); } ~ProxyModelLoadState() { m->endLoading(); l->setLayoutingEnabled(true); } }; ProxyModelLoadState proxyModelLoadState(proxyModel(), layout()); // The shared data will automatically be deleted in the destructor // of KoShapeLoadingContext OdfLoadingHelper *helper = new OdfLoadingHelper; helper->tableSource = &d->tableSource; helper->chartUsesInternalModelOnly = d->usesInternalModelOnly; // Get access to sheets in Calligra Sheets QAbstractItemModel *sheetAccessModel = 0; if (resourceManager() && resourceManager()->hasResource(Sheets::CanvasResource::AccessModel)) { QVariant var = resourceManager()->resource(Sheets::CanvasResource::AccessModel); sheetAccessModel = static_cast(var.value()); if (sheetAccessModel) { // We're embedded in Calligra Sheets, which means Calligra Sheets provides the data d->usesInternalModelOnly = false; d->tableSource.setSheetAccessModel(sheetAccessModel); helper->chartUsesInternalModelOnly = d->usesInternalModelOnly; } } context.addSharedData(OdfLoadingHelperId, helper); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.clear(); if (chartElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { context.odfLoadingContext().fillStyleStack(chartElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); KoInsets padding = layout()->padding(); if (styleStack.hasProperty(KoXmlNS::fo, "padding")) { padding.left = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding")); padding.top = padding.left; padding.right = padding.left; padding.bottom = padding.left; debugChartOdf<<"load padding"<setPadding(padding); } // Also load the size here as it, if specified here, overwrites the frame's size, // See ODF specs for chart:chart element for more details. loadOdfAttributes(chartElement, context, OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements | OdfStyle | OdfSize); #ifndef NWORKAROUND_ODF_BUGS if (!background()) { const QColor color = KoOdfWorkaround::fixMissingFillColor(chartElement, context); if (color.isValid()) // invalid color means do not set KoColorBackground but be transparent instead setBackground(QSharedPointer(new KoColorBackground(color))); } #endif // Check if we're loading an embedded document if (!chartElement.hasAttributeNS(KoXmlNS::chart, "class")) { debugChart << "Error: Embedded document has no chart:class attribute."; return false; } Q_ASSERT(d->plotArea); // 1. Load the chart type. // NOTE: Chart type and -subtype is a bit tricky as stock charts and bubble charts // needs special treatment. // So we do not call the ChartShape::setChart... methods here in odf code, // but the plot area methods directly. const QString chartClass = chartElement.attributeNS(KoXmlNS::chart, "class", QString()); KoChart::ChartType chartType = KoChart::BarChartType; // Find out what charttype the chart class corresponds to. bool knownType = false; for (int type = 0; type < (int)LastChartType; ++type) { if (chartClass == ODF_CHARTTYPES[(ChartType)type]) { chartType = (ChartType)type; // Set the dimensionality of the data points, we can not call // setChartType here as bubble charts requires that the datasets already exist proxyModel()->setDataDimensions(numDimensions(chartType)); knownType = true; debugChartOdf <<"found chart of type" << chartClass<proxyModel->setDataDimensions(dimensions); // debugChart << d->proxyModel->dataSets().count(); KoXmlElement dataElem = KoXml::namedItemNS(chartElement, KoXmlNS::table, "table"); if (!dataElem.isNull()) { if (!loadOdfData(dataElem, context)) return false; } // 3. Load the plot area (this is where the meat is!). KoXmlElement plotareaElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "plot-area"); if (!plotareaElem.isNull()) { d->plotArea->setChartType(chartType); d->plotArea->setChartSubType(chartSubType()); if (!d->plotArea->loadOdf(plotareaElem, context)) { return false; } // d->plotArea->setChartType(chartType); // d->plotArea->setChartSubType(chartSubType()); } // 4. Load the title. KoXmlElement titleElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "title"); d->setChildVisible(d->title, !titleElem.isNull()); if (!titleElem.isNull()) { if (!OdfHelper::loadOdfTitle(d->title, titleElem, context)) return false; } // 5. Load the subtitle. KoXmlElement subTitleElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "subtitle"); d->setChildVisible(d->subTitle, !subTitleElem.isNull()); if (!subTitleElem.isNull()) { if (!OdfHelper::loadOdfTitle(d->subTitle, subTitleElem, context)) return false; } // 6. Load the footer. KoXmlElement footerElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "footer"); d->setChildVisible(d->footer, !footerElem.isNull()); if (!footerElem.isNull()) { if (!OdfHelper::loadOdfTitle(d->footer, footerElem, context)) return false; } // 7. Load the legend. KoXmlElement legendElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "legend"); d->setChildVisible(d->legend, !legendElem.isNull()); if (!legendElem.isNull()) { if (!d->legend->loadOdf(legendElem, context)) return false; } // 8. Sets the chart type // since chart type in plot area is already set before axes were loaded, we need to do axes here for (Axis *a : d->plotArea->axes()) { a->plotAreaChartTypeChanged(chartType); } debugChartOdf<<"loaded:"<chartType()<internalModel) { Table *oldInternalTable = d->tableSource.get(d->internalModel); Q_ASSERT(oldInternalTable); d->tableSource.remove(oldInternalTable->name()); } // FIXME: Make model->loadOdf() return a bool, and use it here. // Create a table with data from document, add it as table source // and reset the proxy only with data from this new table. ChartTableModel *internalModel = new ChartTableModel; internalModel->loadOdf(tableElement, context); QString tableName = tableElement.attributeNS(KoXmlNS::table, "name"); + debugChartOdf<<"Loaded table:"<tableSource.add(tableName, internalModel); // TODO: d->tableSource.setAvoidNameClash(tableName) setInternalModel(internalModel); return true; } void ChartShape::saveOdf(KoShapeSavingContext & context) const { Q_ASSERT(d->plotArea); KoXmlWriter& bodyWriter = context.xmlWriter(); // Check if we're saving to a chart document. If not, embed a // chart document. ChartShape::saveOdf() will then be called // again later, when the current document saves the embedded // documents. // // FIXME: The check isEmpty() fixes a crash that happened when a // chart shape was saved from Words. There are two // problems with this fix: // 1. Checking the tag hierarchy is hardly the right way to do this // 2. The position doesn't seem to be saved yet. // // Also, I have to check with the other apps, e.g. Calligra Sheets, // if it works there too. // QList tagHierarchy = bodyWriter.tagHierarchy(); if (tagHierarchy.isEmpty() || QString(tagHierarchy.last()) != "office:chart") { bodyWriter.startElement("draw:frame"); // See also loadOdf() in loadOdfAttributes. saveOdfAttributes(context, OdfAllAttributes); bodyWriter.startElement("draw:object"); context.embeddedSaver().embedDocument(bodyWriter, d->document); bodyWriter.endElement(); // draw:object bodyWriter.endElement(); // draw:frame return; } bodyWriter.startElement("chart:chart"); saveOdfAttributes(context, OdfSize); context.setStyleFamily("ch"); KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart"); KoInsets padding = layout()->padding(); style.addPropertyPt("fo:padding-left", padding.left, KoGenStyle::GraphicType); style.addPropertyPt("fo:padding-top", padding.top, KoGenStyle::GraphicType); style.addPropertyPt("fo:padding-right", padding.right, KoGenStyle::GraphicType); style.addPropertyPt("fo:padding-bottom", padding.bottom, KoGenStyle::GraphicType); debugChartOdf<<"save padding:"<plotArea->chartType() ]); // 2. Write the title. OdfHelper::saveOdfTitle(d->title, bodyWriter, "chart:title", context); // 3. Write the subtitle. OdfHelper::saveOdfTitle(d->subTitle, bodyWriter, "chart:subtitle", context); // 4. Write the footer. OdfHelper::saveOdfTitle(d->footer, bodyWriter, "chart:footer", context); // 5. Write the legend. if (d->legend->isVisible()) d->legend->saveOdf(context); // 6. Write the plot area (this is where the real action is!). d->plotArea->saveOdf(context); // 7. Save the data saveOdfData(bodyWriter, context.mainStyles()); bodyWriter.endElement(); // chart:chart } static void saveOdfDataRow(KoXmlWriter &bodyWriter, QAbstractItemModel *table, int row) { bodyWriter.startElement("table:table-row"); const int cols = table->columnCount(); for (int col = 0; col < cols; ++col) { //QVariant value(internalModel.cellVal(row, col)); QModelIndex index = table->index(row, col); QVariant value = table->data(index); bool ok; double val = value.toDouble(&ok); if (ok) { value = val; } QString valType; QString valStr; switch (value.type()) { case QVariant::Invalid: break; case QVariant::String: valType = "string"; valStr = value.toString(); break; case QVariant::Double: valType = "float"; valStr = QString::number(value.toDouble(), 'g', DBL_DIG); break; case QVariant::DateTime: valType = "date"; valStr = ""; /* like in saveXML, but why? */ break; default: debugChart <<"ERROR: cell" << row <<"," << col << " has unknown type." << endl; } // Add the value type and the string to the XML tree. bodyWriter.startElement("table:table-cell"); if (!valType.isEmpty()) { bodyWriter.addAttribute("office:value-type", valType); if (value.type() == QVariant::Double) bodyWriter.addAttribute("office:value", valStr); bodyWriter.startElement("text:p"); bodyWriter.addTextNode(valStr); bodyWriter.endElement(); // text:p } bodyWriter.endElement(); // table:table-cell } bodyWriter.endElement(); // table:table-row } void ChartShape::saveOdfData(KoXmlWriter &bodyWriter, KoGenStyles &mainStyles) const { Q_UNUSED(mainStyles); // FIXME: Move this method to a sane place ChartTableModel *internalModel = d->internalModel; Table *internalTable = d->tableSource.get(internalModel); Q_ASSERT(internalTable); // Only save the data if we actually have some. if (!internalModel) return; const int rows = internalModel->rowCount(); const int cols = internalModel->columnCount(); bodyWriter.startElement("table:table"); bodyWriter.addAttribute("table:name", internalTable->name()); // Exactly one header column, always. bodyWriter.startElement("table:table-header-columns"); bodyWriter.startElement("table:table-column"); bodyWriter.endElement(); // table:table-column bodyWriter.endElement(); // table:table-header-columns // Then "cols" columns bodyWriter.startElement("table:table-columns"); bodyWriter.startElement("table:table-column"); bodyWriter.addAttribute("table:number-columns-repeated", cols); bodyWriter.endElement(); // table:table-column bodyWriter.endElement(); // table:table-columns int row = 0; bodyWriter.startElement("table:table-header-rows"); if (rows > 0) saveOdfDataRow(bodyWriter, internalModel, row++); bodyWriter.endElement(); // table:table-header-rows // Here start the actual data rows. bodyWriter.startElement("table:table-rows"); //QStringList::const_iterator rowLabelIt = m_rowLabels.begin(); for (; row < rows ; ++row) saveOdfDataRow(bodyWriter, internalModel, row); bodyWriter.endElement(); // table:table-rows bodyWriter.endElement(); // table:table } void ChartShape::updateAll() { d->legend->update(); d->plotArea->plotAreaUpdate(); relayout(); update(); } void ChartShape::update() const { KoShape::update(); layout()->scheduleRelayout(); emit updateConfigWidget(); } void ChartShape::relayout() const { Q_ASSERT(d->plotArea); d->plotArea->relayout(); KoShape::update(); } void ChartShape::requestRepaint() const { Q_ASSERT(d->plotArea); d->plotArea->requestRepaint(); } KoDocumentResourceManager *ChartShape::resourceManager() const { return d->resourceManager; } void ChartShape::setEnableUserInteraction(bool enable) { ENABLE_USER_INTERACTION = enable; } void ChartShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape) layout()->containerChanged(this, type); } ChartDocument *ChartShape::document() const { return d->document; } } // Namespace KoChart diff --git a/plugins/chartshape/ChartTableModel.cpp b/plugins/chartshape/ChartTableModel.cpp index 60809f4df12..dcd4f553ada 100644 --- a/plugins/chartshape/ChartTableModel.cpp +++ b/plugins/chartshape/ChartTableModel.cpp @@ -1,197 +1,198 @@ /* This file is part of the KDE project Copyright 2008 Johannes Simon Copyright 2009 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "ChartTableModel.h" // C #include // Qt #include #include // Calligra #include #include #include #include #include #include // KoChart #include "CellRegion.h" #include "OdfLoadingHelper.h" #include "ChartDebug.h" namespace KoChart { ChartTableModel::ChartTableModel(QObject *parent /* = 0 */) : QStandardItemModel(parent) { } ChartTableModel::~ChartTableModel() { } QVariant ChartTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { return CellRegion::columnName(section+1); } } return QStandardItemModel::headerData(section, orientation, role); } QHash > ChartTableModel::cellRegion() const { // FIXME: Unimplemented? return QHash >(); } bool ChartTableModel::setCellRegion(const QString& /*regionName*/) { #if 0 // FIXME: What does this code do? int result = 0; const int size = regionName.size(); for (int i = 0; i < size; i++) { result += (CellRegion::rangeCharToInt(regionName[i].toLatin1()) * std::pow(10.0, (size - i - 1))); } return result; #endif return true; } bool ChartTableModel::isCellRegionValid(const QString& regionName) const { Q_UNUSED(regionName); return true; } bool ChartTableModel::loadOdf(const KoXmlElement &tableElement, KoShapeLoadingContext &context) { Q_UNUSED(context); + debugChartOdf<<"Load table"; setRowCount(0); setColumnCount(0); //QDomDocument doc; //KoXml::asQDomElement(doc, tableElement); //QTextStream stream(stdout); //stream << doc.documentElement(); int row = 0; KoXmlElement n; forEachElement (n, tableElement) { if (n.namespaceURI() != KoXmlNS::table) continue; if (n.localName() == "table-columns" || n.localName() == "table-header-columns") { int column = 0; KoXmlElement _n; forEachElement (_n, n) { if (_n.namespaceURI() != KoXmlNS::table || _n.localName() != "table-column") continue; column += qMax(1, _n.attributeNS(KoXmlNS::table, "number-columns-repeated").toInt()); if (column > columnCount()) setColumnCount(column); } } else if (n.localName() == "table-rows" || n.localName() == "table-header-rows") { if (n.localName() == "table-header-rows") { if (row >= 1) { // There can only be one header-row and only at the very beginning. // So, ignore all following header-rows to be sure our internal // table doesn't start at the wrong offset or something like that. continue; } } KoXmlElement _n; forEachElement (_n, n) { if (_n.namespaceURI() != KoXmlNS::table || _n.localName() != "table-row") continue; // Add a row to the internal representation. setRowCount(row + 1); // Loop through all cells in a table row. int column = 0; KoXmlElement __n; forEachElement (__n, _n) { if (__n.namespaceURI() != KoXmlNS::table || __n.localName() != "table-cell") continue; // We have a cell so be sure our column-counter is increased right now so // any 'continue' coming now will leave with the correct value for the next // cell we deal with. ++column; // If this row is wider than any previous one, then add another column. if (column > columnCount()) setColumnCount(column); const QString valueType = __n.attributeNS(KoXmlNS::office, "value-type"); QString valueString = __n.attributeNS(KoXmlNS::office, "value"); const KoXmlElement valueElement = __n.namedItemNS(KoXmlNS::text, "p").toElement(); if ((valueElement.isNull() || !valueElement.isElement()) && valueString.isEmpty()) continue; // Read the actual value in the cell. QVariant value; if (valueString.isEmpty()) valueString = valueElement.text().trimmed(); if (valueType == "float") value = valueString.toDouble(); else if (valueType == "boolean") value = (bool)valueString.toInt(); else // if (valueType == "string") value = valueString; setData(index(row, column - 1), value); } // foreach table:table-cell ++row; } // foreach table:table-row } } - + debugChartOdf<<"Loaded table:"< Copyright 2009-2010 Inge Wallin Copyright 2018 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "PlotArea.h" // Qt #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include #include #include // Attribute Classes #include #include #include #include #include // Diagram Classes #include #include #include #include #include // KoChart #include "Legend.h" #include "Surface.h" #include "Axis.h" #include "DataSet.h" #include "ChartProxyModel.h" #include "ScreenConversions.h" #include "ChartLayout.h" #include "ChartDebug.h" using namespace KoChart; const int MAX_PIXMAP_SIZE = 1000; Q_DECLARE_METATYPE(QPointer) typedef QList CoordinatePlaneList; class PlotArea::Private { public: Private(PlotArea *q, ChartShape *parent); ~Private(); void initAxes(); void updateAxesPosition(); CoordinatePlaneList coordinatePlanesForChartType(ChartType type); void autoHideAxisTitles(); PlotArea *q; // The parent chart shape ChartShape *shape; // ---------------------------------------------------------------- // Parts and properties of the chart ChartType chartType; ChartSubtype chartSubtype; Surface *wall; Surface *floor; // Only used in 3D charts // The axes QList axes; QList automaticallyHiddenAxisTitles; // 3D properties bool threeD; Ko3dScene *threeDScene; // ---------------------------------------------------------------- // Data specific to each chart type // 1. Bar charts // FIXME: OpenOffice stores these attributes in the axes' elements. // The specs don't say anything at all about what elements can have // these style attributes. // chart:vertical attribute: see ODF v1.2,19.63 bool vertical; // 2. Polar charts (pie/ring) qreal angleOffset; // in degrees qreal holeSize; // ---------------------------------------------------------------- // The embedded KD Chart // The KD Chart parts KChart::Chart *const kdChart; KChart::CartesianCoordinatePlane *const kdCartesianPlanePrimary; KChart::CartesianCoordinatePlane *const kdCartesianPlaneSecondary; KChart::PolarCoordinatePlane *const kdPolarPlane; KChart::RadarCoordinatePlane *const kdRadarPlane; QList kdDiagrams; // Caching: We can rerender faster if we cache KChart's output QImage image; bool paintPixmap; QPointF lastZoomLevel; QSizeF lastSize; mutable bool pixmapRepaintRequested; QPen stockRangeLinePen; QBrush stockGainBrush; QBrush stockLossBrush; QString symbolType; QString symbolName; DataSet::ValueLabelType valueLabelType; }; PlotArea::Private::Private(PlotArea *q, ChartShape *parent) : q(q) , shape(parent) // Default type: normal bar chart , chartType(BarChartType) , chartSubtype(NormalChartSubtype) , wall(0) , floor(0) , threeD(false) , threeDScene(0) // By default, x and y axes are not swapped. , vertical(false) // OpenOffice.org's default. It means the first pie slice starts at the // very top (and then going counter-clockwise). , angleOffset(90.0) , holeSize(50.0) // KCharts approx default // KD Chart stuff , kdChart(new KChart::Chart()) , kdCartesianPlanePrimary(new KChart::CartesianCoordinatePlane(kdChart)) , kdCartesianPlaneSecondary(new KChart::CartesianCoordinatePlane(kdChart)) , kdPolarPlane(new KChart::PolarCoordinatePlane(kdChart)) , kdRadarPlane(new KChart::RadarCoordinatePlane(kdChart)) // Cache , paintPixmap(true) , pixmapRepaintRequested(true) , symbolType("automatic") { kdCartesianPlanePrimary->setObjectName("primary"); kdCartesianPlaneSecondary->setObjectName("secondary"); // --- Prepare Primary Cartesian Coordinate Plane --- KChart::GridAttributes gridAttributes; gridAttributes.setGridVisible(false); gridAttributes.setGridGranularitySequence(KChartEnums::GranularitySequence_10_50); kdCartesianPlanePrimary->setGlobalGridAttributes(gridAttributes); // --- Prepare Secondary Cartesian Coordinate Plane --- kdCartesianPlaneSecondary->setGlobalGridAttributes(gridAttributes); // --- Prepare Polar Coordinate Plane --- KChart::GridAttributes polarGridAttributes; polarGridAttributes.setGridVisible(false); kdPolarPlane->setGlobalGridAttributes(polarGridAttributes); // --- Prepare Radar Coordinate Plane --- KChart::GridAttributes radarGridAttributes; polarGridAttributes.setGridVisible(true); kdRadarPlane->setGlobalGridAttributes(radarGridAttributes); // By default we use a cartesian chart (bar chart), so the polar planes // are not needed yet. They will be added on demand in setChartType(). kdChart->takeCoordinatePlane(kdPolarPlane); kdChart->takeCoordinatePlane(kdRadarPlane); shape->proxyModel()->setDataDimensions(1); stockRangeLinePen.setWidthF(2.0); stockGainBrush = QBrush(QColor(Qt::white)); stockLossBrush = QBrush(QColor(Qt::black)); } PlotArea::Private::~Private() { // remove first to avoid crash while (!kdChart->coordinatePlanes().isEmpty()) { kdChart->takeCoordinatePlane(kdChart->coordinatePlanes().last()); } qDeleteAll(axes); delete kdCartesianPlanePrimary; delete kdCartesianPlaneSecondary; delete kdPolarPlane; delete kdRadarPlane; delete kdChart; delete wall; delete floor; delete threeDScene; } void PlotArea::Private::initAxes() { // The category data region is anchored to an axis and will be set on addAxis if the // axis defines the Axis::categoryDataRegion(). So, clear it now. q->proxyModel()->setCategoryDataRegion(CellRegion()); // Remove all old axes while(!axes.isEmpty()) { Axis *axis = axes.takeLast(); Q_ASSERT(axis); if (axis->title()) automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // There need to be at least these two axes. Their constructor will // automatically add them to the plot area as child shape. new Axis(q, XAxisDimension); Axis *yAxis = new Axis(q, YAxisDimension); yAxis->setShowMajorGrid(true); updateAxesPosition(); } void PlotArea::Private::updateAxesPosition() { debugChartAxis<updateKChartAxisPosition(); } } PlotArea::PlotArea(ChartShape *parent) : QObject() , KoShape() , d(new Private(this, parent)) { setShapeId("ChartShapePlotArea"); // NB! used by defaulttool/ChartResizeStrategy.cpp Q_ASSERT(d->shape); Q_ASSERT(d->shape->proxyModel()); setAdditionalStyleAttribute("chart:auto-position", "true"); setAdditionalStyleAttribute("chart:auto-size", "true"); connect(d->shape->proxyModel(), SIGNAL(modelReset()), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(dataChanged()), this, SLOT(plotAreaUpdate())); } PlotArea::~PlotArea() { delete d; } void PlotArea::plotAreaInit() { d->kdChart->resize(size().toSize()); d->kdChart->replaceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdCartesianPlaneSecondary->setReferenceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdChart->addCoordinatePlane(d->kdCartesianPlaneSecondary); KChart::FrameAttributes attr = d->kdChart->frameAttributes(); attr.setVisible(false); d->kdChart->setFrameAttributes(attr); d->wall = new Surface(this); //d->floor = new Surface(this); d->initAxes(); addAxesTitlesToLayout(); } void PlotArea::proxyModelStructureChanged() { if (proxyModel()->isLoading()) return; Q_ASSERT(xAxis()); Q_ASSERT(yAxis()); QMap attachedAxes; QList dataSets = proxyModel()->dataSets(); // Remember to what y axis each data set belongs foreach(DataSet *dataSet, dataSets) attachedAxes.insert(dataSet, dataSet->attachedAxis()); // Proxy structure and thus data sets changed, drop old state and // clear all axes of data sets foreach(Axis *axis, axes()) axis->clearDataSets(); // Now add the new list of data sets to the axis they belong to foreach(DataSet *dataSet, dataSets) { xAxis()->attachDataSet(dataSet); // If they weren't assigned to a y axis before, use default y axis if (attachedAxes[dataSet]) attachedAxes[dataSet]->attachDataSet(dataSet); else yAxis()->attachDataSet(dataSet); } } ChartProxyModel *PlotArea::proxyModel() const { return d->shape->proxyModel(); } QList PlotArea::axes() const { return d->axes; } QList PlotArea::dataSets() const { return proxyModel()->dataSets(); } Axis *PlotArea::xAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) return axis; } return 0; } Axis *PlotArea::yAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) return axis; } return 0; } Axis *PlotArea::secondaryXAxis() const { bool firstXAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) { if (firstXAxisFound) return axis; else firstXAxisFound = true; } } return 0; } Axis *PlotArea::secondaryYAxis() const { bool firstYAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) { if (firstYAxisFound) return axis; else firstYAxisFound = true; } } return 0; } ChartType PlotArea::chartType() const { return d->chartType; } ChartSubtype PlotArea::chartSubType() const { return d->chartSubtype; } bool PlotArea::isThreeD() const { return d->threeD; } bool PlotArea::isVertical() const { return d->chartType == BarChartType && d->vertical; } Ko3dScene *PlotArea::threeDScene() const { return d->threeDScene; } qreal PlotArea::angleOffset() const { return d->angleOffset; } qreal PlotArea::holeSize() const { return d->holeSize; } void PlotArea::setHoleSize(qreal value) { d->holeSize = value; } // FIXME: this should add the axxis as a child (set axis->parent()) bool PlotArea::addAxis(Axis *axis) { if (d->axes.contains(axis)) { warnChart << "PlotArea::addAxis(): Trying to add already added axis."; return false; } if (!axis) { warnChart << "PlotArea::addAxis(): Pointer to axis is NULL!"; return false; } d->axes.append(axis); if (axis->dimension() == XAxisDimension) { // let each axis know about the other axis foreach (Axis *_axis, d->axes) { if (_axis->isVisible()) _axis->registerAxis(axis); } } requestRepaint(); return true; } bool PlotArea::removeAxis(Axis *axis) { bool removed = takeAxis(axis); if (removed) { // This also removes the axis' title, which is a shape as well delete axis; } return removed; } // FIXME: this should remove the axis as a child (set axis->parent()) bool PlotArea::takeAxis(Axis *axis) { if (!d->axes.contains(axis)) { warnChart << "PlotArea::takeAxis(): Trying to remove non-added axis."; return false; } if (!axis) { warnChart << "PlotArea::takeAxis(): Pointer to axis is NULL!"; return false; } if (axis->title()) { d->automaticallyHiddenAxisTitles.removeAll(axis->title()); } d->axes.removeAll(axis); axis->removeAxisFromDiagrams(true); requestRepaint(); return true; } CoordinatePlaneList PlotArea::Private::coordinatePlanesForChartType(ChartType type) { CoordinatePlaneList result; switch (type) { case BarChartType: case LineChartType: case AreaChartType: case ScatterChartType: case GanttChartType: case SurfaceChartType: case StockChartType: case BubbleChartType: result.append(kdCartesianPlanePrimary); result.append(kdCartesianPlaneSecondary); break; case CircleChartType: case RingChartType: result.append(kdPolarPlane); break; case RadarChartType: case FilledRadarChartType: result.append(kdRadarPlane); break; case LastChartType: Q_ASSERT("There's no coordinate plane for LastChartType"); break; } Q_ASSERT(!result.isEmpty()); return result; } void PlotArea::Private::autoHideAxisTitles() { automaticallyHiddenAxisTitles.clear(); foreach (Axis *axis, axes) { if (axis->title()->isVisible()) { axis->title()->setVisible(false); automaticallyHiddenAxisTitles.append(axis->title()); } } } void PlotArea::setChartType(ChartType type) { if (d->chartType == type) return; // Lots of things to do if the old and new types of coordinate // systems don't match. if (!isPolar(d->chartType) && isPolar(type)) { d->autoHideAxisTitles(); } else if (isPolar(d->chartType) && !isPolar(type)) { foreach (KoShape *title, d->automaticallyHiddenAxisTitles) { title->setVisible(true); } d->automaticallyHiddenAxisTitles.clear(); } CellRegion region = d->shape->proxyModel()->cellRangeAddress(); if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->setManualControl(false); xAxis()->clearDataSets(); yAxis()->clearDataSets(); if (secondaryYAxis()) { secondaryYAxis()->clearDataSets(); } if (secondaryXAxis()) { secondaryXAxis()->clearDataSets(); } } CoordinatePlaneList planesToRemove; // First remove secondary cartesian plane as it references the primary // plane, otherwise KChart will come down crashing on us. Note that // removing a plane that's not in the chart is not a problem. planesToRemove << d->kdCartesianPlaneSecondary << d->kdCartesianPlanePrimary << d->kdPolarPlane << d->kdRadarPlane; foreach(KChart::AbstractCoordinatePlane *plane, planesToRemove) d->kdChart->takeCoordinatePlane(plane); CoordinatePlaneList newPlanes = d->coordinatePlanesForChartType(type); foreach(KChart::AbstractCoordinatePlane *plane, newPlanes) d->kdChart->addCoordinatePlane(plane); Q_ASSERT(d->kdChart->coordinatePlanes() == newPlanes); d->chartType = type; foreach (Axis *axis, d->axes) { axis->plotAreaChartTypeChanged(type); } if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->reset(region); } if (type != BarChartType) { setVertical(false); // Only supported by bar charts } requestRepaint(); } void PlotArea::setChartSubType(ChartSubtype subType) { d->chartSubtype = subType; foreach (Axis *axis, d->axes) { axis->plotAreaChartSubTypeChanged(subType); } } void PlotArea::setThreeD(bool threeD) { d->threeD = threeD; foreach(Axis *axis, d->axes) axis->setThreeD(threeD); requestRepaint(); } void PlotArea::setVertical(bool vertical) { d->vertical = vertical; foreach(Axis *axis, d->axes) axis->plotAreaIsVerticalChanged(); } // ---------------------------------------------------------------- // loading and saving bool PlotArea::loadOdf(const KoXmlElement &plotAreaElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); // The exact position defined in ODF overwrites the default layout position // NOTE: Do not do this as it means functionallity changes just because you save and load. // I don't think odf has an element/attribute that can hold this type of info. // Also afaics libreoffice do not do this. // if (plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")) // { // parent()->layout()->setPosition(this, FloatingPosition); // } bool autoPosition = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y")); bool autoSize = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); loadOdfAttributes(plotAreaElement, context, OdfAllAttributes); // First step is to clear all old axis instances. while (!d->axes.isEmpty()) { Axis *axis = d->axes.takeLast(); Q_ASSERT(axis); // Clear this axis of all data sets, deleting any diagram associated with it. axis->clearDataSets(); if (axis->title()) d->automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // Now find out about things that are in the plotarea style. // // These things include chart subtype, special things for some // chart types like line charts, stock charts, etc. // // Note that this has to happen BEFORE we create a axis and call // there loadOdf method cause the axis will evaluate settings // like the PlotArea::isVertical boolean. bool candleStick = false; if (plotAreaElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "auto-position")) { autoPosition |= styleStack.property(KoXmlNS::chart, "auto-position") == "true"; } else { // To be backwards compatible we set auto-position to true as this was the original behaviour // and is the way LO works autoPosition = true; } if (styleStack.hasProperty(KoXmlNS::chart, "auto-size")) { autoSize |= styleStack.property(KoXmlNS::chart, "auto-size") == "true" ; } else { // To be backwards compatible we set auto-size to true as this was the original behaviour // and is the way LO works autoSize = true; } // ring and pie if (styleStack.hasProperty(KoXmlNS::chart, "angle-offset")) { bool ok; const qreal angleOffset = styleStack.property(KoXmlNS::chart, "angle-offset").toDouble(&ok); if (ok) { setAngleOffset(angleOffset); } } // ring if (styleStack.hasProperty(KoXmlNS::chart, "hole-size")) { bool ok; const qreal value = styleStack.property(KoXmlNS::chart, "hole-size").toDouble(&ok); if (ok) { setHoleSize(value); } } // Check for 3D. if (styleStack.hasProperty(KoXmlNS::chart, "three-dimensional")) setThreeD(styleStack.property(KoXmlNS::chart, "three-dimensional") == "true"); d->threeDScene = load3dScene(plotAreaElement); // Set subtypes stacked or percent. // These are valid for Bar, Line, Area and Radar types. if (styleStack.hasProperty(KoXmlNS::chart, "percentage") && styleStack.property(KoXmlNS::chart, "percentage") == "true") { setChartSubType(PercentChartSubtype); } else if (styleStack.hasProperty(KoXmlNS::chart, "stacked") && styleStack.property(KoXmlNS::chart, "stacked") == "true") { setChartSubType(StackedChartSubtype); } // Data specific to bar charts if (styleStack.hasProperty(KoXmlNS::chart, "vertical")) setVertical(styleStack.property(KoXmlNS::chart, "vertical") == "true"); // Data specific to stock charts if (styleStack.hasProperty(KoXmlNS::chart, "japanese-candle-stick")) { candleStick = styleStack.property(KoXmlNS::chart, "japanese-candle-stick") == "true"; } // Special properties for various chart types #if 0 switch () { case BarChartType: if (styleStack) ; } #endif styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); } setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false"); setAdditionalStyleAttribute("chart:auto-size", autoSize ? "true" : "false"); // Now create and load the axis from the ODF. This needs to happen // AFTER we did set some of the basic settings above so the axis // can use those basic settings to evaluate it's own settings // depending on them. This is especially required for the // PlotArea::isVertical() boolean flag else things will go wrong. KoXmlElement n; forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "axis") { - if (!n.hasAttributeNS(KoXmlNS::chart, "dimension")) + if (!n.hasAttributeNS(KoXmlNS::chart, "dimension")) { // We have to know what dimension the axis is supposed to be.. + qInfo()<loadOdf(n, context); } } // Two axes are mandatory, check that we have them. if (!xAxis()) { Axis *xAxis = new Axis(this, XAxisDimension); xAxis->setVisible(false); } if (!yAxis()) { Axis *yAxis = new Axis(this, YAxisDimension); yAxis->setVisible(false); } // Now, after the axes, load the datasets. // Note that this only contains properties of the datasets, the // actual data is not stored here. // // FIXME: Isn't the proxy model a strange place to store this data? proxyModel()->loadOdf(plotAreaElement, context, d->chartType); // Now load the surfaces (wall and possibly floor) // FIXME: Use named tags instead of looping? forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "wall") { d->wall->loadOdf(n, context); } else if (n.localName() == "floor") { // The floor is not always present, so allocate it if needed. // FIXME: Load floor, even if we don't really support it yet // and save it back to ODF. //if (!d->floor) // d->floor = new Surface(this); //d->floor->loadOdf(n, context); } else if (n.localName() == "stock-gain-marker") { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::draw, "fill")) { d->stockGainBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockGainBrush; } else { warnChartOdf<stockLossBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockLossBrush; } else { warnChartOdf<stockRangeLinePen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, styleStack.property(KoXmlNS::draw, "stroke"), stylesReader); debugChartOdf<stockRangeLinePen; } else { warnChartOdf<chartType == StockChartType) { // The number of data sets determines stock chart subtype if (proxyModel()->rowCount() > 3) { if (candleStick) { setChartSubType(CandlestickChartSubtype); } else { setChartSubType(OpenHighLowCloseChartSubtype); } } } // Connect axes to datasets and cleanup foreach(DataSet *ds, d->shape->proxyModel()->dataSets()) { foreach(Axis *axis, d->axes) { if (axis->name() == ds->axisName()) { axis->attachDataSet(ds); } } } debugChartOdf<chartType<chartSubtype<axes; if (isPolar(d->chartType)) { d->autoHideAxisTitles(); } foreach(Axis *axis, d->axes) { axis->setName(QString()); } // update kchart axis position for all axes d->updateAxesPosition(); // add axes titles to layout addAxesTitlesToLayout(); return true; } void PlotArea::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); //KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:plot-area"); KoGenStyle plotAreaStyle(KoGenStyle::ChartAutoStyle, "chart"); // Data direction const Qt::Orientation direction = proxyModel()->dataDirection(); plotAreaStyle.addProperty("chart:series-source", (direction == Qt::Horizontal) ? "rows" : "columns"); // Save chart subtype saveOdfSubType(bodyWriter, plotAreaStyle); // Save extra stuff (like auto-position) QMap::const_iterator it(additionalStyleAttributes().constBegin()); for (; it != additionalStyleAttributes().constEnd(); ++it) { plotAreaStyle.addProperty(it.key(), it.value(), KoGenStyle::ChartType); } // save graphic-properties and insert style bodyWriter.addAttribute("chart:style-name", saveStyle(plotAreaStyle, context)); const QSizeF s(size()); const QPointF p(position()); bodyWriter.addAttributePt("svg:width", s.width()); bodyWriter.addAttributePt("svg:height", s.height()); bodyWriter.addAttributePt("svg:x", p.x()); bodyWriter.addAttributePt("svg:y", p.y()); CellRegion cellRangeAddress = d->shape->proxyModel()->cellRangeAddress(); bodyWriter.addAttribute("table:cell-range-address", cellRangeAddress.toString()); // About the data: // Save if the first row / column contain headers. QString dataSourceHasLabels; if (proxyModel()->firstRowIsLabel()) { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "both"; else dataSourceHasLabels = "row"; } else { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "column"; else dataSourceHasLabels = "none"; } // Note: this is saved in the plotarea attributes and not the style. bodyWriter.addAttribute("chart:data-source-has-labels", dataSourceHasLabels); if (d->threeDScene) { d->threeDScene->saveOdfAttributes(bodyWriter); } if (d->chartType == StockChartType) { QString styleName; bodyWriter.startElement("chart:stock-gain-marker"); KoGenStyle stockGainStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockGainStyle, context.mainStyles(), d->stockGainBrush); styleName = context.mainStyles().insert(stockGainStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-gain-marker bodyWriter.startElement("chart:stock-loss-marker"); KoGenStyle stockLossStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockLossStyle, context.mainStyles(), d->stockLossBrush); styleName = context.mainStyles().insert(stockLossStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-loss-marker bodyWriter.startElement("chart:stock-range-line"); KoGenStyle stockRangeStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfStrokeStyle(stockRangeStyle, context.mainStyles(), d->stockRangeLinePen); styleName = context.mainStyles().insert(stockRangeStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-range-line } // Done with the attributes, start writing the children. // Save the axes. foreach(Axis *axis, d->axes) { axis->saveOdf(context); } if (d->threeDScene) { d->threeDScene->saveOdfChildren(bodyWriter); } // Save data series d->shape->proxyModel()->saveOdf(context); // Save the floor and wall of the plotarea. d->wall->saveOdf(context, "chart:wall"); //if (d->floor) // d->floor->saveOdf(context, "chart:floor"); bodyWriter.endElement(); // chart:plot-area } void PlotArea::saveOdfSubType(KoXmlWriter& xmlWriter, KoGenStyle& plotAreaStyle) const { Q_UNUSED(xmlWriter); switch (d->chartType) { case BarChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); } // Data specific to bar charts if (d->vertical) plotAreaStyle.addProperty("chart:vertical", "true"); // Don't save this if zero, because that's the default. //plotAreaStyle.addProperty("chart:lines-used", 0); // FIXME: for now break; case LineChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } // FIXME: What does this mean? plotAreaStyle.addProperty("chart:symbol-type", "automatic"); break; case AreaChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } break; case CircleChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); break; case RingChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); plotAreaStyle.addProperty("chart:hole-size", QString::number(d->holeSize)); break; case ScatterChartType: // FIXME break; case RadarChartType: case FilledRadarChartType: // Save subtype of the Radar chart. switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } break; case StockChartType: { switch(d->chartSubtype) { case NoChartSubtype: case HighLowCloseChartSubtype: case OpenHighLowCloseChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "false"); break; case CandlestickChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "true"); break; } } case BubbleChartType: case SurfaceChartType: case GanttChartType: // FIXME break; // This is not a valid type, but needs to be handled to avoid // a warning from gcc. case LastChartType: default: // FIXME break; } } void PlotArea::setAngleOffset(qreal angle) { d->angleOffset = angle; emit angleOffsetChanged(angle); } ChartShape *PlotArea::parent() const { // There has to be a valid parent Q_ASSERT(d->shape); return d->shape; } KChart::CartesianCoordinatePlane *PlotArea::kdCartesianPlane(Axis *axis) const { if (axis) { Q_ASSERT(d->axes.contains(axis)); // Only a secondary y axis gets the secondary plane if (axis->dimension() == YAxisDimension && axis != yAxis()) return d->kdCartesianPlaneSecondary; } return d->kdCartesianPlanePrimary; } KChart::PolarCoordinatePlane *PlotArea::kdPolarPlane() const { return d->kdPolarPlane; } KChart::RadarCoordinatePlane *PlotArea::kdRadarPlane() const { return d->kdRadarPlane; } KChart::Chart *PlotArea::kdChart() const { return d->kdChart; } bool PlotArea::registerKdDiagram(KChart::AbstractDiagram *diagram) { if (d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.append(diagram); return true; } bool PlotArea::deregisterKdDiagram(KChart::AbstractDiagram *diagram) { if (!d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.removeAll(diagram); return true; } // HACK to get kdChart to recognize secondary planes void PlotArea::registerKdPlane(KChart::AbstractCoordinatePlane *plane) { int pos = d->kdChart->coordinatePlanes().indexOf(plane); if (pos >= 1) { // secondary plane d->kdChart->takeCoordinatePlane(plane); d->kdChart->insertCoordinatePlane(pos, plane); } else if (pos < 0) { d->kdChart->addCoordinatePlane(plane); } } void PlotArea::plotAreaUpdate() { parent()->legend()->update(); if (d->chartType == StockChartType) { updateKChartStockAttributes(); } requestRepaint(); foreach(Axis* axis, d->axes) axis->update(); KoShape::update(); } void PlotArea::requestRepaint() const { d->pixmapRepaintRequested = true; } void PlotArea::paintPixmap(QPainter &painter, const KoViewConverter &converter) { // Adjust the size of the painting area to the current zoom level const QSize paintRectSize = converter.documentToView(size()).toSize(); const QSize plotAreaSize = size().toSize(); const int borderX = 4; const int borderY = 4; // Only use a pixmap with sane sizes d->paintPixmap = false;//paintRectSize.width() < MAX_PIXMAP_SIZE || paintRectSize.height() < MAX_PIXMAP_SIZE; if (d->paintPixmap) { d->image = QImage(paintRectSize, QImage::Format_RGB32); // Copy the painter's render hints, such as antialiasing QPainter pixmapPainter(&d->image); pixmapPainter.setRenderHints(painter.renderHints()); pixmapPainter.setRenderHint(QPainter::Antialiasing, false); // scale the painter's coordinate system to fit the current zoom level applyConversion(pixmapPainter, converter); d->kdChart->paint(&pixmapPainter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } else { d->kdChart->paint(&painter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } } void PlotArea::paint(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintContext) { //painter.save(); // First of all, scale the painter's coordinate system to fit the current zoom level applyConversion(painter, converter); // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); painter.setClipRect(paintRect, Qt::IntersectClip); // Paint the background if (background()) { QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // Get the current zoom level QPointF zoomLevel; converter.zoom(&zoomLevel.rx(), &zoomLevel.ry()); // Only repaint the pixmap if it is scheduled, the zoom level // changed or the shape was resized. /*if ( d->pixmapRepaintRequested || d->lastZoomLevel != zoomLevel || d->lastSize != size() || !d->paintPixmap) { // TODO (js): What if two zoom levels are constantly being // requested? At the moment, this *is* the case, // due to the fact that the shape is also rendered // in the page overview in Stage. Every time // the window is hidden and shown again, a repaint // is requested --> laggy performance, especially // when quickly switching through windows. // // ANSWER (iw): what about having a small mapping between size // in pixels and pixmaps? The size could be 2 or // at most 3. We could manage the replacing // using LRU. paintPixmap(painter, converter); d->pixmapRepaintRequested = false; d->lastZoomLevel = zoomLevel; d->lastSize = size(); }*/ painter.setRenderHint(QPainter::Antialiasing, false); // KChart thinks in pixels, Calligra in pt ScreenConversions::scaleFromPtToPx(painter); // Only paint the actual chart if there is a certain minimal size, // because otherwise kdchart will crash. QRect kdchartRect = ScreenConversions::scaleFromPtToPx(paintRect, painter); // Turn off clipping so that border (or "frame") drawn by KChart::Chart // is not not cut off. painter.setClipping(false); if (kdchartRect.width() > 10 && kdchartRect.height() > 10) { #if KCHART_VERSION < 0x020689 painter.setPen(QPen()); // ruler line needs a pen to be shown #endif d->kdChart->paint(&painter, kdchartRect); } //painter.restore(); // Paint the cached pixmap if we got a GO from paintPixmap() //if (d->paintPixmap) // painter.drawImage(0, 0, d->image); } void PlotArea::relayout() const { d->kdCartesianPlanePrimary->relayout(); d->kdCartesianPlaneSecondary->relayout(); d->kdPolarPlane->relayout(); d->kdRadarPlane->relayout(); update(); } void PlotArea::addTitleToLayout() { addAxesTitlesToLayout(); // for now } void PlotArea::addAxesTitlesToLayout() { ChartLayout *layout = d->shape->layout(); Axis *axis = xAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), XAxisTitleType); } axis = yAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), YAxisTitleType); } axis = secondaryXAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryXAxisTitleType); } axis = secondaryYAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryYAxisTitleType); } } void PlotArea::setStockRangeLinePen(const QPen &pen) { d->stockRangeLinePen = pen; } QPen PlotArea::stockRangeLinePen() const { return d->stockRangeLinePen; } void PlotArea::setStockGainBrush(const QBrush &brush) { d->stockGainBrush = brush; } QBrush PlotArea::stockGainBrush() const { return d->stockGainBrush; } void PlotArea::setStockLossBrush(const QBrush &brush) { d->stockLossBrush = brush; } QBrush PlotArea::stockLossBrush() const { return d->stockLossBrush; } void PlotArea::updateKChartStockAttributes() { for (Axis *a : d->axes) { a->updateKChartStockAttributes(); } } DataSet::ValueLabelType PlotArea::valueLabelType() const { return d->valueLabelType; } QString PlotArea::symbolType() const { return d->symbolType; } void PlotArea::setSymbolType(const QString &type) { d->symbolType = type; } QString PlotArea::symbolName() const { return d->symbolName; } void PlotArea::setSymbolName(const QString &name) { d->symbolName = name; } void PlotArea::setValueLabelType(const DataSet::ValueLabelType &type) { d->valueLabelType = type; }