diff --git a/plugins/chartshape/Axis.cpp b/plugins/chartshape/Axis.cpp index 65f2a7ae5a2..8e74c0a6b24 100644 --- a/plugins/chartshape/Axis.cpp +++ b/plugins/chartshape/Axis.cpp @@ -1,2175 +1,2179 @@ /* 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 // 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 // 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 "ChartDebug.h" using namespace KoChart; class Axis::Private { public: Private(Axis *axis, AxisDimension dim); ~Private(); void adjustAllDiagrams(); /// Updates the axis potition 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); void deregisterDiagram(KChart::AbstractDiagram *diagram); KChart::AbstractDiagram *getDiagramAndCreateIfNeeded(ChartType chartType); KChart::AbstractDiagram *getDiagram(ChartType chartType); void deleteDiagram(ChartType chartType); 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; /// Font used for axis labels /// TODO: Save to ODF QFont font; KChart::CartesianAxis *const kdAxis; KChart::CartesianCoordinatePlane *kdPlane; KChart::PolarCoordinatePlane *kdPolarPlane; KChart::RadarCoordinatePlane *kdRadarPlane; KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat; KChart::BarDiagram *kdBarDiagram; KChart::LineDiagram *kdLineDiagram; KChart::LineDiagram *kdAreaDiagram; KChart::PieDiagram *kdCircleDiagram; KChart::RingDiagram *kdRingDiagram; KChart::RadarDiagram *kdRadarDiagram; KChart::Plotter *kdScatterDiagram; KChart::StockDiagram *kdStockDiagram; KChart::Plotter *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. KChart::BarDiagram *kdSurfaceDiagram; KChart::BarDiagram *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; // TODO: Save to ODF int gapBetweenBars; // TODO: Save to ODF int gapBetweenSets; // TODO: Save // See ODF v1.2 $19.12 (chart:display-label) bool showLabels; bool showOverlappingDataLabels; bool isVisible; }; class CartesianAxis : public KChart::CartesianAxis { public: CartesianAxis(KoChart::Axis *_axis) : KChart::CartesianAxis(), axis(_axis) {} virtual ~CartesianAxis() {} virtual const QString customizedLabel(const QString& label) const { 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; kdBarDiagram = 0; kdLineDiagram = 0; kdAreaDiagram = 0; kdCircleDiagram = 0; kdRingDiagram = 0; kdRadarDiagram = 0; kdScatterDiagram = 0; kdStockDiagram = 0; kdBubbleDiagram = 0; kdSurfaceDiagram = 0; kdGanttDiagram = 0; title = 0; titleData = 0; KChart::RulerAttributes attr = kdAxis->rulerAttributes(); attr.setShowRulerLine(true); kdAxis->setRulerAttributes(attr); } Axis::Private::~Private() { Q_ASSERT(plotArea); 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) { KChartModel *model = new KChartModel(plotArea); diagram->setModel(model); QObject::connect(plotArea->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), 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())); } void Axis::Private::deregisterDiagram(KChart::AbstractDiagram *diagram) { KChartModel *model = dynamic_cast(diagram->model()); Q_ASSERT(model); QObject::disconnect(plotArea->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), model, SLOT(slotColumnsInserted(QModelIndex,int,int))); QObject::disconnect(diagram, SIGNAL(propertiesChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::disconnect(diagram, SIGNAL(layoutChanged(AbstractDiagram*)), plotArea, SLOT(plotAreaUpdate())); QObject::disconnect(diagram, SIGNAL(modelsChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::disconnect(diagram, SIGNAL(dataHidden()), plotArea, SLOT(plotAreaUpdate())); delete model; } 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: ; } adjustAllDiagrams(); return diagram; } /** * Returns currently used internal KChart diagram for the specified chart type */ KChart::AbstractDiagram *Axis::Private::getDiagram(ChartType chartType) { switch (chartType) { case BarChartType: return kdBarDiagram; case LineChartType: return kdLineDiagram; case AreaChartType: return kdAreaDiagram; case CircleChartType: return kdCircleDiagram; case RingChartType: return kdRingDiagram; case RadarChartType: case FilledRadarChartType: return kdRadarDiagram; case ScatterChartType: return kdScatterDiagram; case StockChartType: return kdStockDiagram; case BubbleChartType: return kdBubbleDiagram; case SurfaceChartType: return kdSurfaceDiagram; case GanttChartType: return kdGanttDiagram; case LastChartType: return 0; // Compiler warning for unhandled chart type is intentional. } Q_ASSERT(!"Unhandled chart type"); return 0; } void Axis::Private::deleteDiagram(ChartType chartType) { KChart::AbstractDiagram **diagram = 0; switch (chartType) { case BarChartType: diagram = (KChart::AbstractDiagram**)&kdBarDiagram; break; case LineChartType: diagram = (KChart::AbstractDiagram**)&kdLineDiagram; break; case AreaChartType: diagram = (KChart::AbstractDiagram**)&kdAreaDiagram; break; case CircleChartType: diagram = (KChart::AbstractDiagram**)&kdCircleDiagram; break; case RingChartType: diagram = (KChart::AbstractDiagram**)&kdRingDiagram; break; case RadarChartType: case FilledRadarChartType: diagram = (KChart::AbstractDiagram**)&kdRadarDiagram; break; case ScatterChartType: diagram = (KChart::AbstractDiagram**)&kdScatterDiagram; break; case StockChartType: diagram = (KChart::AbstractDiagram**)&kdStockDiagram; break; case BubbleChartType: diagram = (KChart::AbstractDiagram**)&kdBubbleDiagram; break; case SurfaceChartType: diagram = (KChart::AbstractDiagram**)&kdSurfaceDiagram; break; case GanttChartType: diagram = (KChart::AbstractDiagram**)&kdGanttDiagram; break; case LastChartType: Q_ASSERT("There is no diagram with type LastChartType"); // Compiler warning for unhandled chart type is intentional. } Q_ASSERT(diagram); Q_ASSERT(*diagram); deregisterDiagram(*diagram); // remove digram from the plane before we delete it or else KChart crashes if ((*diagram)->coordinatePlane()) { (*diagram)->coordinatePlane()->takeDiagram(*diagram); } delete *diagram; *diagram = 0; adjustAllDiagrams(); } void Axis::Private::createBarDiagram() { Q_ASSERT(kdBarDiagram == 0); kdBarDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); 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); kdPlane->addDiagram(kdBarDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) kdBarDiagram->addAxis(axis->kdAxis()); } // Set default bar diagram attributes q->setGapBetweenBars(0); q->setGapBetweenSets(100); // Propagate existing settings KChart::ThreeDBarAttributes attributes(kdBarDiagram->threeDBarAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdBarDiagram->setThreeDBarAttributes(attributes); plotArea->parent()->legend()->kdLegend()->addDiagram(kdBarDiagram); } void Axis::Private::createLineDiagram() { Q_ASSERT(kdLineDiagram == 0); kdLineDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane); 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); kdPlane->addDiagram(kdLineDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->dimension() == XAxisDimension) if (axis->isVisible()) kdLineDiagram->addAxis(axis->kdAxis()); } // 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); 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); kdPlane->addDiagram(kdAreaDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->dimension() == XAxisDimension) if (axis->isVisible()) kdAreaDiagram->addAxis(axis->kdAxis()); } // 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); registerDiagram(kdCircleDiagram); KChartModel *model = dynamic_cast(kdCircleDiagram->model()); Q_ASSERT(model); 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->pieAngleOffset()); } void Axis::Private::createRingDiagram() { Q_ASSERT(kdRingDiagram == 0); kdRingDiagram = new KChart::RingDiagram(plotArea->kdChart(), kdPolarPlane); registerDiagram(kdRingDiagram); KChartModel *model = dynamic_cast(kdRingDiagram->model()); Q_ASSERT(model); 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->pieAngleOffset()); } void Axis::Private::createRadarDiagram(bool filled) { Q_ASSERT(kdRadarDiagram == 0); //kdRadarDiagramModel->setDataDimensions(2); //kdRadarDiagramModel->setDataDirection(Qt::Horizontal); kdRadarDiagram = new KChart::RadarDiagram(plotArea->kdChart(), kdRadarPlane); 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); registerDiagram(kdScatterDiagram); KChartModel *model = dynamic_cast(kdScatterDiagram->model()); Q_ASSERT(model); model->setDataDimensions(2); kdScatterDiagram->setPen(Qt::NoPen); if (isVisible) kdScatterDiagram->addAxis(kdAxis); kdPlane->addDiagram(kdScatterDiagram); foreach (Axis *axis, plotArea->axes()) { if (axis->dimension() == XAxisDimension) if (axis->isVisible()) kdScatterDiagram->addAxis(axis->kdAxis()); } // 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); registerDiagram(kdStockDiagram); KChartModel *model = dynamic_cast(kdStockDiagram->model()); Q_ASSERT(model); model->setDataDimensions(3); #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); kdPlane->addDiagram(kdStockDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->dimension() == XAxisDimension) if (axis->isVisible()) kdStockDiagram->addAxis(axis->kdAxis()); } plotArea->parent()->legend()->kdLegend()->addDiagram(kdStockDiagram); } void Axis::Private::createBubbleDiagram() { Q_ASSERT(kdBubbleDiagram == 0); Q_ASSERT(plotArea); kdBubbleDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane); registerDiagram(kdBubbleDiagram); KChartModel *model = dynamic_cast(kdBubbleDiagram->model()); Q_ASSERT(model); model->setDataDimensions(2); kdPlane->addDiagram(kdBubbleDiagram); foreach (Axis *axis, plotArea->axes()) { //if (axis->dimension() == XAxisDimension) if (axis->isVisible()) kdBubbleDiagram->addAxis(axis->kdAxis()); } // 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); 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); 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 Axis::Axis(PlotArea *parent, AxisDimension dimension) : d(new Private(this, dimension)) { Q_ASSERT(parent); parent->addAxis(this); d->plotArea = parent; KChart::BackgroundAttributes batt(d->kdAxis->backgroundAttributes()); batt.setBrush(QBrush(Qt::white)); d->kdAxis->setBackgroundAttributes(batt); d->kdPlane = parent->kdCartesianPlane(this); d->kdPolarPlane = parent->kdPolarPlane(); d->kdRadarPlane = parent->kdRadarPlane(); d->plotAreaChartType = d->plotArea->chartType(); d->plotAreaChartSubType = d->plotArea->chartSubType(); 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); connect(d->plotArea, SIGNAL(gapBetweenBarsChanged(int)), this, SLOT(setGapBetweenBars(int))); connect(d->plotArea, SIGNAL(gapBetweenSetsChanged(int)), this, SLOT(setGapBetweenSets(int))); connect(d->plotArea, SIGNAL(pieAngleOffsetChanged(qreal)), this, SLOT(setPieAngleOffset(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); } 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() { 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") { if (n.hasAttributeNS(KoXmlNS::svg, "x") && n.hasAttributeNS(KoXmlNS::svg, "y")) { const qreal x = KoUnit::parseValue(n.attributeNS(KoXmlNS::svg, "x")); const qreal y = KoUnit::parseValue(n.attributeNS(KoXmlNS::svg, "y")); d->title->setPosition(QPointF(x, y)); } if (n.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) { qreal rotationAngle = 360 - KoUnit::parseValue(styleStack.property(KoXmlNS::style, "rotation-angle")); if (kdAxis()->position() == KChart::CartesianAxis::Left) rotationAngle = 90 + rotationAngle; else if (kdAxis()->position() == KChart::CartesianAxis::Right) rotationAngle = -90 + rotationAngle; d->title->rotate(rotationAngle); } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); QFont font = d->titleData->document()->defaultFont(); font.setPointSizeF(fontSize); d->titleData->document()->setDefaultFont(font); } if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { const QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family"); QFont font = d->titleData->document()->defaultFont(); font.setFamily(fontFamily); d->titleData->document()->setDefaultFont(font); } } const KoXmlElement textElement = KoXml::namedItemNS(n, KoXmlNS::text, "p"); if (!textElement.isNull()) { d->title->setVisible(true); setTitleText(textElement.text()); } else { // Note: Not an error, just mean do not show //warnChart << "Error: Axis' element contains no "; } if (n.hasAttributeNS(KoXmlNS::svg, "width") && n.hasAttributeNS(KoXmlNS::svg, "height")) { const qreal width = KoUnit::parseValue(n.attributeNS(KoXmlNS::svg, "width")); const qreal height = KoUnit::parseValue(n.attributeNS(KoXmlNS::svg, "height")); d->title->setSize(QSizeF(width, height)); } else { QTextDocument* doc = d->titleData->document(); QRect r = QFontMetrics(doc->defaultFont()).boundingRect(doc->toPlainText()); d->title->setSize(r.size()); } } 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, "axis-name", QString()); //setTitleText(name); } // NOTE: chart:dimension already handled by PlotArea before and passed // explicitly in the constructor. } 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("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { QString fontSizeString = styleStack.property(KoXmlNS::fo, "font-size"); const QString unitString = fontSizeString.right(2); fontSizeString.remove(unitString); bool ok = false; qreal fontSize = fontSizeString.toDouble(&ok); if (unitString == "cm") fontSize = CM_TO_POINT(fontSize); else if (unitString == "pc") fontSize = PI_TO_POINT(fontSize); else if (unitString == "mm") fontSize = MM_TO_POINT(fontSize); else if (unitString == "in") fontSize = INCH_TO_POINT(fontSize); if (ok) { KChart::TextAttributes tatt = kdAxis()->textAttributes(); tatt.setFontSize(KChart::Measure(fontSize, KChartEnums::MeasureCalculationModeAbsolute)); kdAxis()->setTextAttributes(tatt); } } 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); } } } else { 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; requestRepaint(); return true; } bool Axis::loadOdfChartSubtypeProperties(const KoXmlElement &axisElement, KoShapeLoadingContext &context) { Q_UNUSED(axisElement); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("chart"); // Load these attributes regardless of the actual chart type. They'll have // no effect if their respective chart type is not in use. // However, they'll be saved back to ODF that way. if (styleStack.hasProperty(KoXmlNS::chart, "gap-width")) setGapBetweenSets(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "gap-width"))); if (styleStack.hasProperty(KoXmlNS::chart, "overlap")) // The minus is intended! setGapBetweenBars(-KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "overlap"))); return true; } 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()); KChart::CartesianCoordinatePlane *plane = dynamic_cast(kdPlane()); bool reverseAxis = false; if (plane) { if (orientation() == Qt::Horizontal) reverseAxis = plane->isHorizontalRangeReversed(); else // Qt::Vertical reverseAxis = plane->isVerticalRangeReversed(); } axisStyle.addProperty("chart:reverse-direction", reverseAxis); 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()); axisStyle.addPropertyPt("chart:gap-width", d->gapBetweenSets); axisStyle.addPropertyPt("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", tatt.font().pointSize(), KoGenStyle::TextType); 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"); 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; bodyWriter.addAttribute("chart:name", name); bodyWriter.startElement("chart:title"); bodyWriter.addAttributePt("svg:x", d->title->position().x()); bodyWriter.addAttributePt("svg:y", d->title->position().y()); bodyWriter.addAttributePt("svg:width", d->title->size().width()); bodyWriter.addAttributePt("svg:height", d->title->size().height()); KoGenStyle axisTitleStyle(KoGenStyle::ChartAutoStyle, "chart"); axisTitleStyle.addPropertyPt("style:rotation-angle", 360 - d->title->rotation()); QTextCursor cursor(d->titleData->document()); QFont titleFont = cursor.charFormat().font(); axisTitleStyle.addProperty("fo:font-family", titleFont.family(), KoGenStyle::TextType); axisTitleStyle.addPropertyPt("fo:font-size", titleFont.pointSize(), KoGenStyle::TextType); const QString titleStyleName = mainStyles.insert(axisTitleStyle, "ch"); bodyWriter.addAttribute("chart:style-name", titleStyleName); if (d->title->isVisible()) { QString axisLabel = d->titleData->document()->toPlainText(); if (!axisLabel.isEmpty()) { bodyWriter.startElement("text:p"); bodyWriter.addTextNode(axisLabel); bodyWriter.endElement(); // text:p } } bodyWriter.endElement(); // chart:title 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; // Change only the fill in case of type change from RadarChartType to FilledRadarChartType // or viceversa as rest of the properties remain same if (newChartType == RadarChartType && oldChartType == FilledRadarChartType) { d->kdRadarDiagram->setFillAlpha(0); } else if (newChartType == FilledRadarChartType && oldChartType == RadarChartType) { d->kdRadarDiagram->setFillAlpha(0.4); } else { KChart::AbstractDiagram *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; // FIXME: What does this do? Only the user may set a data set's pen through // a proper UI, in any other case the pen falls back to a default // which depends on the chart type, so setting it here will break the default // for other chart types. #if 0 Qt::PenStyle newPenStyle = newDiagram->pen().style(); QPen newPen = dataSet->pen(); newPen.setStyle(newPenStyle); dataSet->setPen( newPen); #endif newModel->addDataSet(dataSet); const int dataSetCount = oldModel->dataDirection() == Qt::Vertical ? oldModel->columnCount() : oldModel->rowCount(); if (dataSetCount == oldModel->dataDimensions()) // We need to call this method so set it sets d->kd[TYPE]Diagram to NULL 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) { #if 0 case CandlestickChartSubtype: type = KChart::StockDiagram::Candlestick; break; case OpenHighLowCloseChartSubtype: type = KChart::StockDiagram::OpenHighLowClose; break; #endif default: type = KChart::StockDiagram::HighLowClose; } 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() { d->updatePosition(); } void Axis::Private::updatePosition() { // Is the first x or y axis? bool first = (dimension == XAxisDimension) ? plotArea->xAxis() == q : plotArea->yAxis() == q; Position position; - if (q->orientation() == Qt::Horizontal) + ItemType type = GenericItemType; + if (q->orientation() == Qt::Horizontal) { position = first ? BottomPosition : TopPosition; - else + type = first ? XAxisTitleType : SecondaryXAxisTitleType; + } else { position = first ? StartPosition : EndPosition; - + type = first ? YAxisTitleType : SecondaryYAxisTitleType; + } if (position == StartPosition) title->rotate(-90 - title->rotation()); else if (position == EndPosition) title->rotate(90 - title->rotation()); // KChart kdAxis->setPosition(PositionToKChartAxisPosition(position)); ChartLayout *layout = plotArea->parent()->layout(); - layout->setPosition(title, position, 10); + layout->setPosition(title, position, type); layout->layout(); q->requestRepaint(); } void Axis::registerKdAxis(KChart::CartesianAxis *axis) { if (d->kdBarDiagram) d->kdBarDiagram->addAxis(axis); if (d->kdLineDiagram) d->kdLineDiagram->addAxis(axis); if (d->kdAreaDiagram) d->kdAreaDiagram->addAxis(axis); if (d->kdScatterDiagram) d->kdScatterDiagram->addAxis(axis); if (d->kdStockDiagram) d->kdStockDiagram->addAxis(axis); if (d->kdBubbleDiagram) d->kdBubbleDiagram->addAxis(axis); // FIXME: Add all diagrams here } void Axis::deregisterKdAxis(KChart::CartesianAxis *axis) { if (d->kdBarDiagram) d->kdBarDiagram->takeAxis(axis); if (d->kdLineDiagram) d->kdLineDiagram->takeAxis(axis); if (d->kdAreaDiagram) d->kdAreaDiagram->takeAxis(axis); if (d->kdScatterDiagram) d->kdScatterDiagram->takeAxis(axis); if (d->kdStockDiagram) d->kdStockDiagram->takeAxis(axis); if (d->kdBubbleDiagram) d->kdBubbleDiagram->takeAxis(axis); // FIXME: Add all diagrams here } 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(); } 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(); } 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::setPieAngleOffset(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()) { // KChart takes an int here, though ODF defines it to be a double. d->kdPolarPlane->setStartPosition((int)angle); requestRepaint(); } } QFont Axis::font() const { return d->font; } void Axis::setFont(const QFont &font) { // Save the font for later retrieval d->font = font; // Set the KChart axis to use this font as well. KChart::TextAttributes attr = d->kdAxis->textAttributes(); attr.setFont(font); d->kdAxis->setTextAttributes(attr); } qreal Axis::fontSize() const { return d->font.pointSizeF(); } void Axis::setFontSize(qreal size) { d->font.setPointSizeF(size); // KChart KChart::TextAttributes attributes = d->kdAxis->textAttributes(); attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); d->kdAxis->setTextAttributes(attributes); } bool Axis::isVisible() const { return d->isVisible; } void Axis::setVisible(bool visible) { d->isVisible = visible; if (visible) registerKdAxis(d->kdAxis); else deregisterKdAxis(d->kdAxis); } KoOdfNumberStyles::NumericStyleFormat *Axis::numericStyleFormat() const { return d->numericStyleFormat; } void Axis::SetNumericStyleFormat(KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat) const { delete d->numericStyleFormat; d->numericStyleFormat = numericStyleFormat; } diff --git a/plugins/chartshape/ChartDebug.cpp b/plugins/chartshape/ChartDebug.cpp index eef354f1ac7..5923d632c7e 100644 --- a/plugins/chartshape/ChartDebug.cpp +++ b/plugins/chartshape/ChartDebug.cpp @@ -1,26 +1,33 @@ /* + 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.chartshape"); return category; } + +const QLoggingCategory &CHARTLAYOUT_LOG() +{ + static const QLoggingCategory category("calligra.plugin.chartlayout"); + return category; +} diff --git a/plugins/chartshape/ChartDebug.h b/plugins/chartshape/ChartDebug.h index 23f885779e2..071266985b4 100644 --- a/plugins/chartshape/ChartDebug.h +++ b/plugins/chartshape/ChartDebug.h @@ -1,33 +1,40 @@ /* + 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 &CHARTLAYOUT_LOG(); + +#define debugChartLayout qCDebug(CHARTLAYOUT_LOG) +#define warnChartLayout qCWarning(CHARTLAYOUT_LOG) +#define errorChartLayout qCCritical(CHARTLAYOUT_LOG) + #endif diff --git a/plugins/chartshape/ChartLayout.cpp b/plugins/chartshape/ChartLayout.cpp index 18798f56e4b..8c870b831c5 100644 --- a/plugins/chartshape/ChartLayout.cpp +++ b/plugins/chartshape/ChartLayout.cpp @@ -1,361 +1,1228 @@ /* This file is part of the KDE project + Copyright 2017 Dag Andersen Copyright 2010 Johannes Simon 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. */ // Qt #include #include // KoChart #include "ChartLayout.h" +#include "Legend.h" +#include "ChartDebug.h" +#include "PlotArea.h" // Calligra #include using namespace KoChart; -// Static helper methods (defined at end of file) -static QPointF itemPosition(KoShape *shape); -static QSizeF itemSize(KoShape *shape); -static void setItemPosition(KoShape *shape, const QPointF& pos); - class ChartLayout::LayoutData { public: + int itemType; Position pos; - int weight; bool clipped; bool inheritsTransform; - LayoutData(Position _pos, int _weight) - : pos(_pos) - , weight(_weight) + LayoutData(int _itemType, Position _pos) + : itemType(_itemType) + , pos(_pos) , clipped(true) , inheritsTransform(true) {} }; ChartLayout::ChartLayout() : m_doingLayout(false) , m_relayoutScheduled(false) , m_hMargin(5) , m_vMargin(5) + , m_autoLayoutEnabled(true) { + m_spacing = QPointF(m_hMargin, m_vMargin); } ChartLayout::~ChartLayout() { foreach(LayoutData *data, m_layoutItems.values()) delete data; } void ChartLayout::add(KoShape *shape) { Q_ASSERT(!m_layoutItems.contains(shape)); - m_layoutItems.insert(shape, new LayoutData(FloatingPosition, 0)); + m_layoutItems.insert(shape, new LayoutData(GenericItemType, FloatingPosition)); scheduleRelayout(); } -void ChartLayout::add(KoShape *shape, Position pos, int weight) +void ChartLayout::add(KoShape *shape, Position pos, int itemType) { Q_ASSERT(!m_layoutItems.contains(shape)); - m_layoutItems.insert(shape, new LayoutData(pos, weight)); + m_layoutItems.insert(shape, new LayoutData(itemType, pos)); scheduleRelayout(); } void ChartLayout::remove(KoShape *shape) { if (m_layoutItems.contains(shape)) { // delete LayoutData delete m_layoutItems.value(shape); m_layoutItems.remove(shape); scheduleRelayout(); } } void ChartLayout::setClipped(const KoShape *shape, bool clipping) { Q_ASSERT(m_layoutItems.contains(const_cast(shape))); m_layoutItems.value(const_cast(shape))->clipped = clipping; } bool ChartLayout::isClipped(const KoShape *shape) const { Q_ASSERT(m_layoutItems.contains(const_cast(shape))); return m_layoutItems.value(const_cast(shape))->clipped; } void ChartLayout::setInheritsTransform(const KoShape *shape, bool inherit) { m_layoutItems.value(const_cast(shape))->inheritsTransform = inherit; } bool ChartLayout::inheritsTransform(const KoShape *shape) const { return m_layoutItems.value(const_cast(shape))->inheritsTransform; } int ChartLayout::count() const { return m_layoutItems.size(); } QList ChartLayout::shapes() const { return m_layoutItems.keys(); } +void ChartLayout::setContainerSize(const QSizeF &size) +{ + if (size != m_containerSize) { + m_containerSize = size; + scheduleRelayout(); + } +} + void ChartLayout::containerChanged(KoShapeContainer *container, KoShape::ChangeType type) { switch(type) { case KoShape::SizeChanged: - m_containerSize = container->size(); - scheduleRelayout(); + setContainerSize(container->size()); break; default: break; } } bool ChartLayout::isChildLocked(const KoShape *shape) const { return shape->isGeometryProtected(); } -void ChartLayout::setPosition(const KoShape *shape, Position pos, int weight) +void ChartLayout::setPosition(const KoShape *shape, Position pos, int itemType) { Q_ASSERT(m_layoutItems.contains(const_cast(shape))); LayoutData *data = m_layoutItems.value(const_cast(shape)); data->pos = pos; - data->weight = weight; + data->itemType = itemType; scheduleRelayout(); } +void ChartLayout::setAutoLayoutEnabled(bool on) +{ + m_autoLayoutEnabled = on; +} + void ChartLayout::childChanged(KoShape *shape, KoShape::ChangeType type) { Q_UNUSED(shape); // Do not relayout again if we're currently in the process of a relayout. // Repositioning a layout item or resizing it will result in a cull of this method. if (m_doingLayout) return; // This can be fine-tuned, but right now, simply everything will be re-layouted. switch (type) { case KoShape::PositionChanged: case KoShape::SizeChanged: scheduleRelayout(); // FIXME: There's some cases that would require relayouting but that don't make sense // for chart items, e.g. ShearChanged. How should these changes be avoided or handled? default: break; } } void ChartLayout::scheduleRelayout() { m_relayoutScheduled = true; } void ChartLayout::layout() { Q_ASSERT(!m_doingLayout); if (!m_relayoutScheduled) return; m_doingLayout = true; QMap top, bottom, start, end; KoShape *topStart = 0; KoShape *bottomStart = 0; KoShape *topEnd = 0; KoShape *bottomEnd = 0; KoShape *p = 0; - QMapIterator it(m_layoutItems); - while (it.hasNext()) { - it.next(); - KoShape *shape = it.key(); - if (!shape->isVisible()) - continue; - LayoutData *data = it.value(); - switch (data->pos) { - case TopPosition: - top.insert(data->weight, shape); - break; - case BottomPosition: - bottom.insert(data->weight, shape); - break; - case StartPosition: - start.insert(data->weight, shape); - break; - case EndPosition: - end.insert(data->weight, shape); - break; - case TopStartPosition: - topStart = shape; - break; - case BottomStartPosition: - bottomStart = shape; - break; - case TopEndPosition: - topEnd = shape; - break; - case BottomEndPosition: - bottomEnd = shape; - break; - case CenterPosition: - p = shape; - break; - case FloatingPosition: - // Nothing to do - break; + if (m_autoLayoutEnabled) { + QMap::const_iterator it; + for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { + KoShape *shape = it.key(); + if (!shape->isVisible()) { + continue; // do nothing + } + LayoutData *data = it.value(); + switch (data->pos) { + case TopPosition: + top.insert(data->itemType, shape); + break; + case BottomPosition: + bottom.insert(data->itemType, shape); + break; + case StartPosition: + start.insert(data->itemType, shape); + break; + case EndPosition: + end.insert(data->itemType, shape); + break; + case TopStartPosition: + topStart = shape; + break; + case BottomStartPosition: + bottomStart = shape; + break; + case TopEndPosition: + topEnd = shape; + break; + case BottomEndPosition: + bottomEnd = shape; + break; + case CenterPosition: + p = shape; + break; + case FloatingPosition: + // Nothing to do + break; + } } } qreal topY = layoutTop(top); qreal bottomY = layoutBottom(bottom); qreal startX = layoutStart(start); qreal endX = layoutEnd(end); if (p) { setItemPosition(p, QPointF(startX, topY)); p->setSize(QSizeF(endX - startX, bottomY - topY)); } layoutTopStart(topStart); layoutBottomStart(bottomStart); layoutTopEnd(topEnd); layoutBottomEnd(bottomEnd); m_doingLayout = false; m_relayoutScheduled = false; } -/// Private Methods - - - qreal ChartLayout::layoutTop(const QMap& shapes) { qreal top = m_vMargin; qreal pX = m_containerSize.width() / 2.0; foreach(KoShape *shape, shapes) { QSizeF size = itemSize(shape); setItemPosition(shape, QPointF(pX - size.width() / 2.0, top)); - top += size.height() + m_vMargin; + top += size.height() + m_spacing.y(); } - return top + m_vMargin; + return top; } qreal ChartLayout::layoutBottom(const QMap& shapes) { - qreal bottom = m_containerSize.height(); + qreal bottom = m_containerSize.height() - m_vMargin; qreal pX = m_containerSize.width() / 2.0; foreach(KoShape *shape, shapes) { QSizeF size = itemSize(shape); - bottom -= size.height() + m_vMargin; + bottom -= size.height(); setItemPosition(shape, QPointF(pX - size.width() / 2.0, bottom)); + bottom -= m_spacing.y(); } - return bottom - m_vMargin; + return bottom; } qreal ChartLayout::layoutStart(const QMap& shapes) { qreal start = m_hMargin; qreal pY = m_containerSize.height() / 2.0; foreach(KoShape *shape, shapes) { QSizeF size = itemSize(shape); setItemPosition(shape, QPointF(start, pY - size.height() / 2.0)); - start += size.width() + m_hMargin; + start += size.width() + m_spacing.x(); } - return start + m_hMargin; + return start; } qreal ChartLayout::layoutEnd(const QMap& shapes) { - qreal end = m_containerSize.width(); + qreal end = m_containerSize.width() - m_hMargin; qreal pY = m_containerSize.height() / 2.0; foreach(KoShape *shape, shapes) { QSizeF size = itemSize(shape); - end -= size.width() + m_hMargin; + end -= size.width(); setItemPosition(shape, QPointF(end, pY - size.height() / 2.0)); + end -= m_spacing.x(); } - return end - m_hMargin; + return end; } void ChartLayout::layoutTopStart(KoShape *shape) { if (!shape) return; - setItemPosition(shape, QPointF(0, 0)); + setItemPosition(shape, QPointF(m_hMargin, m_vMargin)); } void ChartLayout::layoutBottomStart(KoShape *shape) { if (!shape) return; setItemPosition(shape, QPointF(0, m_containerSize.height() - itemSize(shape).height())); } void ChartLayout::layoutTopEnd(KoShape *shape) { if (!shape) return; - setItemPosition(shape, QPointF(m_containerSize.width() - itemSize(shape).width(), 0)); + setItemPosition(shape, QPointF(m_containerSize.width() - itemSize(shape).width() - m_hMargin, m_vMargin)); } void ChartLayout::layoutBottomEnd(KoShape *shape) { if (!shape) return; const QSizeF size = itemSize(shape); - setItemPosition(shape, QPointF(m_containerSize.width() - size.width(), - m_containerSize.height() - size.height())); + setItemPosition(shape, QPointF(m_containerSize.width() - size.width() - m_hMargin, + m_containerSize.height() - size.height() - m_vMargin)); } void ChartLayout::setMargins(qreal hMargin, qreal vMargin) { m_vMargin = vMargin; m_hMargin = hMargin; scheduleRelayout(); } +QPointF ChartLayout::margins() const +{ + return QPointF(m_vMargin, m_hMargin); +} + +void ChartLayout::setSpacing(qreal hSpacing, qreal vSpacing) +{ + m_spacing = QPointF(hSpacing, vSpacing); +} + +QPointF ChartLayout::spacing() const +{ + return m_spacing; +} + +QMap ChartLayout::calculateLayout(const KoShape *shape, bool show) const +{ + Q_ASSERT(m_layoutItems.contains(const_cast(shape))); + + QMap result; + + KoShape *plotArea = 0; + for (LayoutData *l : m_layoutItems) { + if (l->itemType == PlotAreaType) { + plotArea = m_layoutItems.key(l); + break; + } + } + Q_ASSERT(plotArea); + Q_ASSERT(shape != plotArea); + + // find which area the shape is/should be in + Position area = m_layoutItems[const_cast(shape)]->pos; + if (shape->isVisible()) { + // shape may have been moved into a different area than the default area + area = itemArea(shape, plotArea); + } + debugChartLayout<<(show?"Show:":"Hide:")<(shape), plotArea, !show); + break; + case TopPosition: + result = calculateLayoutTop(const_cast(shape), plotArea, !show); + break; + case EndPosition: + result = calculateLayoutEnd(const_cast(shape), plotArea, !show); + break; + case BottomPosition: + result = calculateLayoutBottom(const_cast(shape), plotArea, !show); + break; + case TopStartPosition: + result = calculateLayoutTopStart(const_cast(shape), plotArea, !show); + break; + case TopEndPosition: + result = calculateLayoutTopEnd(const_cast(shape), plotArea, !show); + break; + case BottomStartPosition: + result = calculateLayoutBottomStart(const_cast(shape), plotArea, !show); + break; + case BottomEndPosition: + result = calculateLayoutBottomEnd(const_cast(shape), plotArea, !show); + break; + case CenterPosition: + debugChartLayout<<"Center"; + break; // When shape intersects center area: do nothing + default: + break; + } + debugChartLayout< ChartLayout::calculateLayoutTop(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout< newlayout; + QMap oldlayout; + QMap::const_iterator it; + for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { + if (it.key()->isVisible() || it.key() == shape) { + oldlayout.insert(it.key(), itemRect(it.key())); + } + } + + if (hide) { + // This shape shall be hidden. + // If appropriate, move other shapes into freed space and + // if the freed space can be utilized: resize the plot area + + // sort visible shapes by type + QMap sortedShapes; + QMap::const_iterator it; + for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { + if (it.key()->isVisible() && it.key() != shape && it.value()->itemType != PlotAreaType) { + sortedShapes.insert(it.value()->itemType, it.key()); + } + } + QRectF itmRect(itemRect(shape)); + debugChartLayout<<"item"< sortedShapes; + QMap::const_iterator it; + for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { + if (it.key() != shape && it.value()->itemType != PlotAreaType) { + sortedShapes.insert(itemPosition(it.key()).y(), it.key()); + } + } + // First get all shapes in this area + QList shapesInArea; + for (KoShape *s : sortedShapes) { + if (s->isVisible()) { + QRectF sr = itemRect(s); + if (sr.intersects(area)) { + shapesInArea << s; + } + } + } + debugChartLayout<<"in area:"< it(shapesInArea); it.hasNext();) { + KoShape *s = it.next(); + if (m_layoutItems[s]->itemType < m_layoutItems[shape]->itemType) { + itmRect.moveTop(itemRect(s).bottom() + m_spacing.y()); + it.remove(); // this shall not be moved + } + } + if (itemRect(shape) != itmRect) { + newlayout.insert(shape, itmRect); + debugChartLayout<<"insert"<::const_iterator i; + for (i = oldlayout.constBegin(); i != oldlayout.constEnd(); ++i) { + LayoutData *data = m_layoutItems[i.key()]; + if (data->itemType == LegendType || data->itemType == YAxisTitleType || data->itemType == SecondaryYAxisTitleType) { + QRectF r = i.value(); + qreal ypos = relativePosition(oldPARect.top(), oldPARect.height(), newPARect.top(), newPARect.height(), r.top(), r.height()); + debugChartLayout<<"check:"< result; + QMap::const_iterator i; + for (i = newlayout.constBegin(); i != newlayout.constEnd(); ++i) { + if (i.value() != oldlayout[i.key()]) { + QPointF poffset = i.value().topLeft() - oldlayout[i.key()].topLeft(); + QSizeF soffset = i.value().size() - oldlayout[i.key()].size(); + result.insert(i.key(), QRectF(i.key()->position() + poffset, i.key()->size() + soffset)); + } else qWarning()<<"in newlayout, not moved?"< ChartLayout::calculateLayoutBottom(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout< newlayout; + QMap oldlayout; + QMap::const_iterator it; + for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { + if (it.key()->isVisible() || it.key() == shape) { + oldlayout.insert(it.key(), itemRect(it.key())); + } + } + if (hide) { + // This shape shall be hidden. + // If appropriate, move other shapes into freed space and + // if the freed space can be utilized: resize the plot area + + // sort visible shapes by type, descending + QMap sortedShapes; + QMap::const_iterator it; + for (it = m_layoutItems.constBegin(); it != m_layoutItems.constEnd(); ++it) { + if (it.key()->isVisible() && it.key() != shape && it.value()->itemType != PlotAreaType) { + sortedShapes.insert(-(it.value()->itemType), it.key()); + } + } + QRectF itmRect(itemRect(shape)); + debugChartLayout<<"Hide item:"< it(shapesInArea); it.hasNext();) { + KoShape *s = it.next(); + if (m_layoutItems[s]->itemType < m_layoutItems[shape]->itemType) { + itmRect.moveBottom(itemRect(s).top() - m_spacing.y()); + it.remove(); // this shall not be moved + } + } + if (itemRect(shape) != itmRect) { + newlayout.insert(shape, itmRect); + debugChartLayout<<"insert"<::const_iterator i; + for (i = oldlayout.constBegin(); i != oldlayout.constEnd(); ++i) { + LayoutData *data = m_layoutItems[i.key()]; + if (data->itemType == LegendType || data->itemType == YAxisTitleType || data->itemType == SecondaryYAxisTitleType) { + QRectF r = oldlayout[i.key()]; + qreal ypos = relativePosition(oldPARect.top(), oldPARect.height(), newPARect.top(), newPARect.height(), r.top(), r.height()); + debugChartLayout<<"check:"< result; + QMap::const_iterator i; + for (i = newlayout.constBegin(); i != newlayout.constEnd(); ++i) { + if (i.value() != oldlayout[i.key()]) { + QPointF poffset = i.value().topLeft() - oldlayout[i.key()].topLeft(); + QSizeF soffset = i.value().size() - oldlayout[i.key()].size(); + result.insert(i.key(), QRectF(i.key()->position() + poffset, i.key()->size() + soffset)); + } else qWarning()<<"in newlayout, not moved?"< ChartLayout::calculateLayoutStart(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout< it(shapesInArea); it.hasNext();) { + KoShape *s = it.next(); + if (m_layoutItems[s]->itemType < m_layoutItems[shape]->itemType) { + itmRect.moveLeft(itemRect(s).right() + m_spacing.x()); + it.remove(); // this shall not be moved + } + } + if (itemRect(shape) != itmRect) { + newlayout.insert(shape, itmRect); + debugChartLayout<<"insert"<::const_iterator it; + for (it = oldlayout.constBegin(); it != oldlayout.constEnd(); ++it) { + LayoutData *data = m_layoutItems[it.key()]; + if ((data->itemType == LegendType && !it.key()->isVisible()) || data->itemType == XAxisTitleType || data->itemType == SecondaryXAxisTitleType) { + QRectF r(it.value()); + debugChartLayout<<"move check:"< result; + {QMap::const_iterator it; + for (it = newlayout.constBegin(); it != newlayout.constEnd(); ++it) { + const QRectF oldrect = oldlayout.value(it.key()); + if (it.value() != oldrect) { + const QPointF poffset = it.value().topLeft() - oldrect.topLeft(); + const QSizeF soffset = it.value().size() - oldrect.size(); + result.insert(it.key(), QRectF(it.key()->position() + poffset, it.key()->size() + soffset)); + } + }} + return result; +} + +QMap ChartLayout::calculateLayoutEnd(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout< it(shapesInArea); it.hasNext();) { + KoShape *s = it.next(); + if (m_layoutItems[s]->itemType < m_layoutItems[shape]->itemType) { + itmRect.moveRight(itemRect(s).left() - m_spacing.x()); + it.remove(); // this shall not be moved + } + } + if (itemRect(shape) != itmRect) { + newlayout.insert(shape, itmRect); + debugChartLayout<<"insert"<::const_iterator it; + for (it = oldlayout.constBegin(); it != oldlayout.constEnd(); ++it) { + if (isShapeToBeMoved(it.key(), EndPosition, plotArea)) { + QRectF r(it.value()); + debugChartLayout<<"move check:"< result; + {QMap::const_iterator it; + for (it = newlayout.constBegin(); it != newlayout.constEnd(); ++it) { + const QRectF oldrect = oldlayout.value(it.key()); + if (it.value() != oldrect) { + const QPointF poffset = it.value().topLeft() - oldrect.topLeft(); + const QSizeF soffset = it.value().size() - oldrect.size(); + result.insert(it.key(), QRectF(it.key()->position() + poffset, it.key()->size() + soffset)); + } + }} + return result; +} + +QMap ChartLayout::calculateLayoutTopStart(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout<(); +} + +QMap ChartLayout::calculateLayoutBottomStart(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout<(); +} + +QMap ChartLayout::calculateLayoutTopEnd(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout<(); +} + +QMap ChartLayout::calculateLayoutBottomEnd(KoShape *shape, KoShape *plotArea, bool hide) const +{ + debugChartLayout<(); +} + +QRectF ChartLayout::layoutArea(Position area, const KoShape *plotArea) const +{ + QRectF center; + if (plotArea) { + center = itemRect(plotArea); + } else { + center = QRectF(m_containerSize.width() * 0.2, m_containerSize.height() * 0.2, m_containerSize.width() * 0.8, m_containerSize.height() * 0.8); + } + switch (area) { + case StartPosition: + return QRectF(0, center.left(), center.left(), center.height()); + break; + case TopPosition: + return QRectF(center.left(), 0, center.width(), center.top()); + break; + case EndPosition: + return QRectF(center.right(), center.top(), m_containerSize.width() - center.right(), center.height()); + break; + case BottomPosition: + return QRectF(center.left(), center.bottom(), center.width(), m_containerSize.height() - center.bottom()); + break; + case TopStartPosition: + return QRectF(0, 0, center.left(), center.top()); + break; + case TopEndPosition: + return QRectF(center.right(), 0, m_containerSize.width() - center.right(), center.top()); + break; + case BottomStartPosition: + return QRectF(0, center.bottom(), center.left(), m_containerSize.height() - center.bottom()); + break; + case BottomEndPosition: + return QRectF(center.right(), center.bottom(), m_containerSize.width() - center.right(), m_containerSize.height() - center.bottom()); + break; + case CenterPosition: + return center; + default: + break; + } + return QRectF(); +} + +Position ChartLayout::itemArea(const KoShape *item, const KoShape *plotArea) const +{ + QRectF center(QPointF(m_containerSize.width() * 0.2, m_containerSize.height() * 0.2), m_containerSize * 0.6); + if (plotArea) { + center = itemRect(plotArea); + } + QRectF r = itemRect(item); + if (r.intersects(center)) { + return CenterPosition; + } + QRectF area = layoutArea(TopStartPosition, plotArea); + if (r.intersects(area)) { + return TopStartPosition; + } + area = layoutArea(TopPosition, plotArea); + if (r.intersects(area)) { + return TopPosition; + } + area = layoutArea(TopEndPosition, plotArea); + if (r.intersects(area)) { + return TopEndPosition; + } + area = layoutArea(StartPosition, plotArea); + if (r.intersects(area)) { + return StartPosition; + } + area = layoutArea(EndPosition, plotArea); + if (r.intersects(area)) { + return EndPosition; + } + area = layoutArea(BottomStartPosition, plotArea); + if (r.intersects(area)) { + return BottomStartPosition; + } + area = layoutArea(BottomPosition, plotArea); + if (r.intersects(area)) { + return BottomPosition; + } + area = layoutArea(BottomEndPosition, plotArea); + if (r.intersects(area)) { + return BottomEndPosition; + } + return m_layoutItems[const_cast(item)]->pos; +} + +qreal ChartLayout::itemDefaultPosition(const KoShape *shape, qreal defaultValue, qreal itemstart, qreal itemend, qreal areastart, qreal areaend) const +{ + qreal result = defaultValue; + LayoutData *data = m_layoutItems[const_cast(shape)]; + if (data->pos != FloatingPosition) { + switch (data->itemType) { + case LegendType: + // Legend has alignment + switch (static_cast(shape)->alignment()) { + case Qt::AlignLeft: result = areastart; break; + case Qt::AlignRight: result = areaend - itemRect(shape).height(); break; + case Qt::AlignCenter: { + qreal w = itemRect(shape).height() * 0.5; + if (data->pos == TopPosition || data->pos == BottomPosition) { + w = itemRect(shape).width() * 0.5; + } + result = areastart + (0.5 * (areaend - areastart)) - w; break; + } + default: break; // default value + } + break; + case XAxisTitleType: + case YAxisTitleType: + case SecondaryXAxisTitleType: + case SecondaryYAxisTitleType: + if (!shape->isVisible()) { + // center + result = areastart + (0.5 * (areaend - areastart)) - (0.5 * (itemend - itemstart)); break; + } + break; + default: + // Leave these were they are, user might have moved them + break; + } + } + return result; +} + /// Static Helper Methods -static QPointF itemPosition(KoShape *shape) +/*static*/ qreal ChartLayout::relativePosition(qreal start1, qreal length1, qreal start2, qreal length2, qreal start, qreal length) +{ + qreal pos = start; + debugChartLayout<<"start1="<(shape)->alignment()) { + case Qt::AlignLeft: s += ":Left"; break; + case Qt::AlignCenter: s += ":Center"; break; + case Qt::AlignRight: s += ":Right"; break; + default: s += ":Float"; break; + } + s += ']'; + break; + case XAxisTitleType: s = "KoShape[XAxisTitle]"; break; + case YAxisTitleType: s = "KoShape[YAxisTitle]"; break; + case SecondaryXAxisTitleType: s = "KoShape[SXAxisTitle]"; break; + case SecondaryYAxisTitleType: s = "KoShape[SYAxisTitle]"; break; + default: s = "KoShape[Unknown]"; break; + } + return s; +} diff --git a/plugins/chartshape/ChartLayout.h b/plugins/chartshape/ChartLayout.h index 914f593d5d5..f8938790529 100644 --- a/plugins/chartshape/ChartLayout.h +++ b/plugins/chartshape/ChartLayout.h @@ -1,205 +1,266 @@ /* This file is part of the KDE project + Copyright 2017 Dag Andersen Copyright 2010 Johannes Simon 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 KCHART_LAYOUT_H #define KCHART_LAYOUT_H // Qt #include #include +#include // Calligra #include // KoChart #include "kochart_global.h" -class QSizeF; - namespace KoChart { /** * A generic chart-style layout with 10 possible positions: * * ---------------------- * | A | D | F | * |---|------------------| * | | | | * | B | I | G | (J) * | | | | * |---|------------------| * | C | E | H | * ---------------------- * * A - TopStartPosition * B - StartPosition * C - BottomStartPosition * D - TopPosition * E - BottomPosition * F - TopEndPosition * G - EndPosition * H - BottomEndPosition * I - CenterPosition * J - FloatingPosition * * Layout elements with the same position that are in one of the positions B, D, G or E * will be placed more towards the center based on a "weight". */ class ChartLayout : public KoShapeContainerModel { public: ChartLayout(); ~ChartLayout(); /** * Adds a floating shape to the layout. */ void add(KoShape *shape); /** * Adds a shape to the layout. * * @param pos position in the layout * @param weight priority of this shape in regard to its placement when * other shapes are in the same Position. * A shape with a higher weight will be placed more towards * the center (i.e., it "sinks" due to its higher weight) */ void add(KoShape *shape, Position pos, int weight = 0); /** * Removes a shape from the layout. */ void remove(KoShape *shape); /** * Turns clipping of a shape on or off. */ void setClipped(const KoShape *shape, bool clipping); /** * @see setClipping */ bool isClipped(const KoShape *shape) const; /// reimplemented virtual void setInheritsTransform(const KoShape *shape, bool inherit); /// reimplemented virtual bool inheritsTransform(const KoShape *shape) const; /** * Returns the number of shapes in this layout. */ int count() const; /** * Returns a list of shapes in this layout. */ QList shapes() const; /** * Called whenever a property of the container (i.e. the ChartShape) is changed. */ void containerChanged(KoShapeContainer *container, KoShape::ChangeType type); /** * Returns whether a shape is locked for user modifications. */ bool isChildLocked(const KoShape *shape) const; /** * Changes the layout position of a shape that is already contained * in this layout. */ void setPosition(const KoShape *shape, Position pos, int weight = 0); /** * Called whenever a property of a shape in this layout has changed. * * All layout items effected by this change will be re-layouted. */ void childChanged(KoShape *shape, KoShape::ChangeType type); /** * Does the layouting of shapes that have changed its size or position or * that were effected by one of these changes. * * Only does a relayout if one has been schedules previously through * scheduleRelayout(). * * \see scheduleRelayout */ void layout(); /** * Schedules a relayout that is to be done when layout() is called. * * \see layout */ void scheduleRelayout(); /** * Sets the horizontal and vertical margin that will be applied during layout */ void setMargins (qreal hMargin, qreal vMargin); + /// Returns the margins defined for this layout + QPointF margins() const; + /// Set spacing in points to @p hSpacing, @p vSpacing to be used for this layout + void setSpacing(qreal hSpacing, qreal vSpacing); + /// Returns the horizontal and vertical spacing in points defined for this layout + /// Default: uses the margins + QPointF spacing() const; + + /** + * Calculates the layouting of the shapes with @p shape visibility @p show, + * in accordance with the @p shapes default positioning set + * when the shapes where added to this layout. + * + * This will normally be used when @p shape changes visibility, but can also + * be used to trigger relayout due to other changes in shape (resize). + * + * Note that shape->isVisible() together with @p show is used to determine + * if the shape changes visibility, hence you should call this method + * before you call shape->setVisible() + * + * Returns the new position and size of shapes that must be moved and/or resized + */ + QMap calculateLayout(const KoShape *shape, bool show) const; + + /// Enable/disable automatic layout + void setAutoLayoutEnabled(bool on); private: + /// Set the chart size to @p size and schedule relayout + void setContainerSize(const QSizeF &size); + /** * Lays out all items in TopPosition, and returns the y value of * the bottom-most item's bottom. */ qreal layoutTop(const QMap& shapes); /** * Lays out all items in BottomPosition, and returns the y value of * the top-most item's top. */ qreal layoutBottom(const QMap& shapes); /** * Lays out all items in StartPosition, and returns the x value of * the right-most item's right. */ qreal layoutStart(const QMap& shapes); /** * Lays out all items in EndPosition, and returns the x value of * the left-most item's left. */ qreal layoutEnd(const QMap& shapes); void layoutTopStart(KoShape *shape); void layoutBottomStart(KoShape *shape); void layoutTopEnd(KoShape *shape); void layoutBottomEnd(KoShape *shape); - class LayoutData; + QMap calculateLayoutTop(KoShape *shape, KoShape *center, bool hide) const; + QMap calculateLayoutBottom(KoShape *shape, KoShape *center, bool hide) const; + QMap calculateLayoutStart(KoShape *shape, KoShape *center, bool hide) const; + QMap calculateLayoutEnd(KoShape *shape, KoShape *center, bool hide) const; + QMap calculateLayoutTopStart(KoShape *shape, KoShape *center, bool hide) const; + QMap calculateLayoutBottomStart(KoShape *shape, KoShape *center, bool hide) const; + QMap calculateLayoutTopEnd(KoShape *shape, KoShape *center, bool hide) const; + QMap calculateLayoutBottomEnd(KoShape *shape, KoShape *center, bool hide) const; + + QRectF layoutArea(Position area, const KoShape *plotArea) const; + Position itemArea(const KoShape *item, const KoShape *plotArea) const; + /// Returns the position where the @p shape shall be inserted. + /// @p defaultValue is used if position cannot/shall not be calculated (eg shape layout position is FloatingPosition) + /// @p itemstart and @p itemend the shapes start and end + /// @p areastart and @p areacend the start and end of the area in which the item shall be positioned + qreal itemDefaultPosition(const KoShape *shape, qreal defaultValue, qreal itemstart, qreal itemend, qreal areastart, qreal areaend) const; + + bool isShapeToBeMoved(const KoShape *shape, Position area, const KoShape *plotArea) const; + + QString dbg(const KoShape *shape) const; +#ifdef COMPILING_TESTS +public: +#endif + static qreal relativePosition(qreal start1, qreal length1, qreal start2, qreal length2, qreal start, qreal length); + static QPointF itemPosition(const KoShape *shape); + static QSizeF itemSize(const KoShape *shape); + static QRectF itemRect(const KoShape *shape); + static void setItemPosition(KoShape *shape, const QPointF& pos); + +private: bool m_doingLayout; bool m_relayoutScheduled; QSizeF m_containerSize; qreal m_hMargin; qreal m_vMargin; + QPointF m_spacing; + bool m_autoLayoutEnabled; + class LayoutData; QMap m_layoutItems; }; } // namespace KoChart #endif // KCHART_CHARTLAYOUT_H diff --git a/plugins/chartshape/ChartShape.cpp b/plugins/chartshape/ChartShape.cpp index ff527cea64c..23e34d7f2e4 100644 --- a/plugins/chartshape/ChartShape.cpp +++ b/plugins/chartshape/ChartShape.cpp @@ -1,1374 +1,1383 @@ /* This file is part of the KDE project Copyright 2007 Stefan Nikolaus 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 // 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 "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 }; void saveOdfFont(KoGenStyle &style, const QFont& font, const QColor& color) { style.addProperty("fo:font-family", font.family(), KoGenStyle::TextType); style.addPropertyPt("fo:font-size", font.pointSize(), KoGenStyle::TextType); style.addProperty("fo:color", color.isValid() ? color.name() : "#000000", KoGenStyle::TextType); int w = font.weight(); style.addProperty("fo:font-weight", w == 50 ? "normal" : w == 75 ? "bold" : QString::number(qRound(w / 10.0) * 100), KoGenStyle::TextType); style.addProperty("fo:font-style", font.italic() ? "italic" : "normal", KoGenStyle::TextType); } QString saveOdfFont(KoGenStyles& mainStyles, const QFont& font, const QColor& color) { KoGenStyle autoStyle(KoGenStyle::ParagraphAutoStyle, "chart", 0); saveOdfFont(autoStyle, font, color); return mainStyles.insert(autoStyle, "ch"); } void saveOdfLabel(KoShape *label, KoXmlWriter &bodyWriter, - KoGenStyles &mainStyles, LabelType labelType) + KoGenStyles &mainStyles, ItemType labelType) { // Don't save hidden labels, as that's the way of removing them // from a chart. if (!label->isVisible()) return; TextLabelData *labelData = qobject_cast(label->userData()); if (!labelData) return; if (labelType == FooterLabelType) bodyWriter.startElement("chart:footer"); else if (labelType == SubTitleLabelType) bodyWriter.startElement("chart:subtitle"); else // if (labelType == TitleLabelType) bodyWriter.startElement("chart:title"); bodyWriter.addAttributePt("svg:x", label->position().x()); bodyWriter.addAttributePt("svg:y", label->position().y()); bodyWriter.addAttributePt("svg:width", label->size().width()); bodyWriter.addAttributePt("svg:height", label->size().height()); // TODO: Save text label color QTextCursor cursor(labelData->document()); QFont labelFont = cursor.charFormat().font(); KoGenStyle autoStyle(KoGenStyle::ChartAutoStyle, "chart", 0); autoStyle.addPropertyPt("style:rotation-angle", 360 - label->rotation()); saveOdfFont(autoStyle, labelFont, QColor()); bodyWriter.addAttribute("chart:style-name", mainStyles.insert(autoStyle, "ch")); bodyWriter.startElement("text:p"); bodyWriter.addTextNode(labelData->document()->toPlainText()); bodyWriter.endElement(); // text:p bodyWriter.endElement(); // chart:title/subtitle/footer } 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(); bool loadOdfLabel(KoShape *label, KoXmlElement &labelElement, KoShapeLoadingContext &context); 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 QAbstractItemModel *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() { } bool ChartShape::Private::loadOdfLabel(KoShape *label, KoXmlElement &labelElement, KoShapeLoadingContext &context) { TextLabelData *labelData = qobject_cast(label->userData()); if (!labelData) return false; // Following will always return false cause KoTextShapeData::loadOdf will try to load // a frame while our text:p is not within a frame. So, let's just not call loadOdf then... //label->loadOdf(labelElement, context); // 1. set the text KoXmlElement pElement = KoXml::namedItemNS(labelElement, KoXmlNS::text, "p"); QTextDocument* doc = labelData->document(); doc->setPlainText(pElement.text()); // 2. set the position QPointF pos = label->position(); bool posChanged = false; if (labelElement.hasAttributeNS(KoXmlNS::svg, "x")) { pos.setX(KoUnit::parseValue(labelElement.attributeNS(KoXmlNS::svg, "x", QString()))); posChanged = true; } if (labelElement.hasAttributeNS(KoXmlNS::svg, "y")) { pos.setY(KoUnit::parseValue(labelElement.attributeNS(KoXmlNS::svg, "y", QString()))); posChanged = true; } if (posChanged) { label->setPosition(pos); } // 3. set the styles if (labelElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.clear(); context.odfLoadingContext().fillStyleStack(labelElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) { qreal rotationAngle = 360 - KoUnit::parseValue(styleStack.property(KoXmlNS::style, "rotation-angle")); label->rotate(rotationAngle); } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); QFont font = doc->defaultFont(); font.setPointSizeF(fontSize); doc->setDefaultFont(font); } if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { const QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family"); QFont font = doc->defaultFont(); font.setFamily(fontFamily); doc->setDefaultFont(font); } } // 4. set the size if (labelElement.hasAttributeNS(KoXmlNS::svg, "width") && labelElement.hasAttributeNS(KoXmlNS::svg, "height")) { const qreal width = KoUnit::parseValue(labelElement.attributeNS(KoXmlNS::svg, "width")); const qreal height = KoUnit::parseValue(labelElement.attributeNS(KoXmlNS::svg, "height")); label->setSize(QSizeF(width, height)); } else { QSizeF size = shape->size(); QRect r = QFontMetrics(doc->defaultFont()).boundingRect( labelData->shapeMargins().left, labelData->shapeMargins().top, qMax(CM_TO_POINT(5), qreal(size.width() - pos.x() * 2.0 - labelData->shapeMargins().right)), qMax(CM_TO_POINT(0.6), qreal(size.height() - labelData->shapeMargins().bottom)), Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, doc->toPlainText()); label->setSize(r.size()); } return true; } // // 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); // Configure the legend. d->legend->setVisible(true); d->legend->setZIndex(1); setClipped(d->legend, true); setInheritsTransform(d->legend, true); // 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. if (dynamic_cast(d->title->userData()) == 0) { TextLabelData *dataDummy = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(dataDummy->document()); dataDummy->document()->setDocumentLayout(documentLayout); d->title->setUserData(dataDummy); } // Start with a reasonable default size that we can base all following relative // positions of chart elements on. setSize(QSizeF(CM_TO_POINT(8), CM_TO_POINT(5))); // Add the title to the shape addShape(d->title); QFont font = titleData()->document()->defaultFont(); font.setPointSizeF(12.0); titleData()->document()->setDefaultFont(font); titleData()->document()->setHtml("
" + i18n("Title") + "
"); // Position the title center at the very top. d->title->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); d->title->setPosition(QPointF(size().width() / 2.0 - d->title->size().width() / 2.0, 0.0)); d->title->setVisible(false); d->title->setZIndex(2); setClipped(d->title, true); setInheritsTransform(d->title, true); // Create the Subtitle and add it to the shape. if (textShapeFactory) d->subTitle = textShapeFactory->createDefaultShape(resourceManager); if (!d->subTitle) { d->subTitle = new TextLabelDummy; } if (dynamic_cast(d->subTitle->userData()) == 0) { TextLabelData *dataDummy = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(dataDummy->document()); dataDummy->document()->setDocumentLayout(documentLayout); d->subTitle->setUserData(dataDummy); } addShape(d->subTitle); font = subTitleData()->document()->defaultFont(); font.setPointSizeF(10.0); subTitleData()->document()->setDefaultFont(font); subTitleData()->document()->setHtml("
" + i18n("Subtitle") + "
"); // Position it in the center, just below the title. d->subTitle->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.6))); d->subTitle->setPosition(QPointF(size().width() / 2.0 - d->title->size().width() / 2.0, d->title->size().height())); d->subTitle->setVisible(false); d->subTitle->setZIndex(3); setClipped(d->subTitle, true); setInheritsTransform(d->subTitle, true); // Create the Footer and add it to the shape. if (textShapeFactory) d->footer = textShapeFactory->createDefaultShape(resourceManager); if (!d->footer) { d->footer = new TextLabelDummy; } if (dynamic_cast(d->footer->userData()) == 0) { TextLabelData *dataDummy = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(dataDummy->document()); dataDummy->document()->setDocumentLayout(documentLayout); d->footer->setUserData(dataDummy); } addShape(d->footer); font = footerData()->document()->defaultFont(); font.setPointSizeF(10.0); footerData()->document()->setDefaultFont(font); footerData()->document()->setHtml("
" + i18n("Footer") + "
"); // Position the footer in the center, at the bottom. d->footer->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.6))); d->footer->setPosition(QPointF(size().width() / 2.0 - d->footer->size().width() / 2.0, size().height() - d->footer->size().height())); d->footer->setVisible(false); d->footer->setZIndex(4); setClipped(d->footer, true); setInheritsTransform(d->footer, true); // Set default contour (for how text run around is done around this shape) // to prevent a crash in LO setTextRunAroundContour(KoShape::ContourBox); // Enable auto-resizing of chart labels foreach(KoShape *label, labels()) { TextLabelData *labelData = qobject_cast(label->userData()); KoTextDocument doc(labelData->document()); //FIXME doc.setResizeMethod(KoTextDocument::AutoResize); } QSharedPointer background(new KoColorBackground(Qt::white)); setBackground(background); KoShapeStroke *stroke = new KoShapeStroke(0, Qt::black); setStroke(stroke); + // set the default positioning, and do initial layout ChartLayout *l = layout(); - l->setPosition(d->plotArea, CenterPosition); - l->setPosition(d->title, TopPosition, 0); - l->setPosition(d->subTitle, TopPosition, 1); - l->setPosition(d->footer, BottomPosition, 0); - l->setPosition(d->legend, d->legend->legendPosition()); + l->setPosition(d->plotArea, CenterPosition, PlotAreaType); + l->setPosition(d->title, TopPosition, TitleLabelType); + l->setPosition(d->subTitle, TopPosition, SubTitleLabelType); + l->setPosition(d->footer, BottomPosition, FooterLabelType); + l->setPosition(d->legend, d->legend->legendPosition(), LegendType); l->scheduleRelayout(); l->layout(); - l->setPosition(d->plotArea, FloatingPosition); - l->setPosition(d->title, FloatingPosition); - l->setPosition(d->subTitle, FloatingPosition); - l->setPosition(d->footer, FloatingPosition); - l->setPosition(d->legend, FloatingPosition); - for (Axis *a : d->plotArea->axes()) { - l->setPosition(a->title(), FloatingPosition); - } - l->layout(); + // Let user/odf control positioning + l->setAutoLayoutEnabled(false); 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); } QAbstractItemModel *ChartShape::internalModel() const { return d->internalModel; } void ChartShape::setInternalModel(QAbstractItemModel *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); d->proxyModel->setDataDimensions(numDimensions(type)); d->plotArea->setChartType(type); emit chartTypeChanged(type); } void ChartShape::setChartSubType(ChartSubtype subType) { Q_ASSERT(d->plotArea); d->plotArea->setChartSubType(subType); 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<<">>>>>"<legend->isVisible() && d->legend->alignment() != Qt::AlignJustify) { + // The legend is positioned into an area, not using the x,y coordinates, + // so we need to let ChartLayout ley it out accordingly + // This may move/resize other components, but maybe that is ok + d->legend->setVisible(false); + const QMap map = layout()->calculateLayout(d->legend, true); + qDebug()<<"loadOdf:"<legend<<"legend:"<legend->position()<::const_iterator it; + for (it = map.constBegin(); it != map.constEnd(); ++it) { + it.key()->setPosition(it.value().topLeft()); + it.key()->setSize(it.value().size()); + } + d->legend->setVisible(true); + layout()->scheduleRelayout(); + } + + return r; } // Used to load the actual contents from the ODF frame that surrounds // the chart in the ODF file. bool ChartShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) { if (element.tagName() == "object") return loadEmbeddedDocument(context.odfLoadingContext().store(), element, context.odfLoadingContext()); warnChart << "Unknown frame element <" << element.tagName() << ">"; 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; ProxyModelLoadState(ChartProxyModel *m) : m(m) { m->beginLoading(); } ~ProxyModelLoadState() { m->endLoading(); } }; ProxyModelLoadState proxyModelLoadState(proxyModel()); // 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(75751149)) { // duplicated from Calligra Sheets QVariant var = resourceManager()->resource(75751149); 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"); } // 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. 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]) { //debugChart <<"found chart of type" << chartClass; chartType = (ChartType)type; // Set the dimensionality of the data points, we can not call // setType here as we bubble charts requires that the datasets already exist proxyModel()->setDataDimensions(numDimensions(chartType)); knownType = true; break; } } // If we can't find out what charttype it is, we might as well end here. if (!knownType) { // FIXME: Find out what the equivalent of // KoDocument::setErrorMessage() is for KoShape. //setErrorMessage(i18n("Unknown chart type %1" ,chartClass)); return false; } // 2. Load the data // int dimensions = numDimensions(chartType); // debugChart << "DIMENSIONS" << dimensions; // d->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; } - // Make the axis titles movable - for (Axis *a : d->plotArea->axes()) { - layout()->setPosition(a->title(), FloatingPosition); - } // 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 (!d->loadOdfLabel(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 (!d->loadOdfLabel(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 (!d->loadOdfLabel(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 setChartType(chartType); d->legend->update(); requestRepaint(); return true; } bool ChartShape::loadOdfData(const KoXmlElement &tableElement, KoShapeLoadingContext &context) { // There is no table element to load if (tableElement.isNull() || !tableElement.isElement()) return true; // An internal model might have been set before in ChartShapeFactory. if (d->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"); d->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; } KoGenStyles& mainStyles(context.mainStyles()); bodyWriter.startElement("chart:chart"); saveOdfAttributes(context, OdfSize); KoGenStyle style(KoGenStyle::GraphicAutoStyle, "chart"); bodyWriter.addAttribute("chart:style-name", saveStyle(style, context)); // 1. Write the chart type. bodyWriter.addAttribute("chart:class", ODF_CHARTTYPES[d->plotArea->chartType() ]); // 2. Write the title. if (d->title->isVisible()) saveOdfLabel(d->title, bodyWriter, mainStyles, TitleLabelType); // 3. Write the subtitle. if (d->subTitle->isVisible()) saveOdfLabel(d->subTitle, bodyWriter, mainStyles, SubTitleLabelType); // 4. Write the footer. if (d->footer->isVisible()) saveOdfLabel(d->footer, bodyWriter, mainStyles, FooterLabelType); // 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, 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 QAbstractItemModel *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::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; } } // Namespace KoChart diff --git a/plugins/chartshape/ChartTool.cpp b/plugins/chartshape/ChartTool.cpp index a4784f05d52..949b862ea03 100644 --- a/plugins/chartshape/ChartTool.cpp +++ b/plugins/chartshape/ChartTool.cpp @@ -1,893 +1,917 @@ /* This file is part of the KDE project * * Copyright (C) 2007, 2010 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 "ChartTool.h" // Qt #include #include #include #include #include #include #include #include // KF5 #include // Calligra #include #include #include #include #include #include // KChart #include #include #include #include #include #include // KoChart #include "Surface.h" #include "PlotArea.h" +#include "ChartLayout.h" #include "Axis.h" #include "DataSet.h" #include "Legend.h" #include "ChartProxyModel.h" #include "ChartConfigWidget.h" #include "KChartConvertions.h" #include "commands/ChartTypeCommand.h" #include "commands/LegendCommand.h" #include "commands/AxisCommand.h" #include "commands/DatasetCommand.h" #include "commands/ChartTextShapeCommand.h" #include "ChartDebug.h" using namespace KoChart; class ChartTool::Private { public: Private(); ~Private(); ChartShape *shape; QModelIndex datasetSelection; QPen datasetSelectionPen; QBrush datasetSelectionBrush; void setDataSetShowLabel(DataSet *dataSet, bool *number, bool *percentage, bool *category, bool *symbol); }; ChartTool::Private::Private() : shape(0) { } ChartTool::Private::~Private() { } ChartTool::ChartTool(KoCanvasBase *canvas) : KoToolBase(canvas), d(new Private()) { // Create QActions here. #if 0 QActionGroup *group = new QActionGroup(this); m_foo = new QAction(koIcon("this-action"), i18n("Do something"), this); m_foo->setCheckable(true); group->addAction(m_foo); connect(m_foo, SIGNAL(toggled(bool)), this, SLOT(catchFoo(bool))); m_bar = new QAction(koIcon("that-action"), i18n("Do something else"), this); m_bar->setCheckable(true); group->addAction(m_bar); connect(m_foo, SIGNAL(toggled(bool)), this, SLOT(catchBar(bool))); #endif connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged())); } ChartTool::~ChartTool() { delete d; } void ChartTool::shapeSelectionChanged() { KoShape *selectedShape = 0; // Get the chart shape that the tool is working on. // Let d->shape point to it. d->shape = 0; // to be sure we don't deal with an old value if nothing is found KoSelection *selection = canvas()->shapeManager()->selection(); foreach (KoShape *shape, selection->selectedShapes()) { // Find out which type of shape that the user clicked on. // We support several here, since the chart shape is comprised // of several subshapes (plotarea, legend) d->shape = dynamic_cast(shape); if (!d->shape) { PlotArea *plotArea = dynamic_cast(shape); if (plotArea) { selectedShape = plotArea; d->shape = plotArea->parent(); } else { Legend *legend = dynamic_cast(shape); if (legend) { selectedShape = legend; d->shape = dynamic_cast(shape->parent()); } } // The selected shape is the chart } else selectedShape = shape; // Insert the values from the selected shape (note: not only // chart shape, but also plotarea or legend) into the tool // option widget. if (selectedShape) { foreach (QWidget *w, optionWidgets()) { KoShapeConfigWidgetBase *widget = dynamic_cast(w); Q_ASSERT(widget); if (widget) widget->open(selectedShape); } // We support only one selected chart at the time, so once // we found one, we don't need to search for any more // among the selected shapes. break; } } // If we couldn't determine a chart shape, then there is nothing to do. if (!d->shape) { // none found emit done(); return; } } void ChartTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } void ChartTool::mousePressEvent(KoPointerEvent *event) { #if 1 // disabled Q_UNUSED(event); return; #else // Select dataset if ( !d->shape || !d->shape->kdChart() || ! d->shape->kdChart()->coordinatePlane() || !d->shape->kdChart()->coordinatePlane()->diagram()) return; QPointF point = event->point - d->shape->position(); QModelIndex selection = d->shape->kdChart()->coordinatePlane()->diagram()->indexAt(point.toPoint()); // Note: the dataset will always stay column() due to the transformations being // done internally by the ChartProxyModel int dataset = selection.column(); if (d->datasetSelection.isValid()) { d->shape->kdChart()->coordinatePlane()->diagram()->setPen(d->datasetSelection.column(), d->datasetSelectionPen); //d->shape->kdChart()->coordinatePlane()->diagram()->setBrush(d->datasetSelection, d->datasetSelectionBrush); } if (selection.isValid()) { d->datasetSelection = selection; QPen pen(Qt::DotLine); pen.setColor(Qt::darkGray); pen.setWidth(1); d->datasetSelectionBrush = d->shape->kdChart()->coordinatePlane()->diagram()->brush(selection); d->datasetSelectionPen = d->shape->kdChart()->coordinatePlane()->diagram()->pen(dataset); d->shape->kdChart()->coordinatePlane()->diagram()->setPen(dataset, pen); //d->shape->kdChart()->coordinatePlane()->diagram()->setBrush(selection, QBrush(Qt::lightGray)); } ((ChartConfigWidget*)optionWidget())->selectDataset(dataset); d->shape->update(); #endif } void ChartTool::mouseMoveEvent(KoPointerEvent *event) { event->ignore(); } void ChartTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); } void ChartTool::activate(ToolActivation, const QSet &) { useCursor(Qt::ArrowCursor); // cause on ChartTool::deactivate we set d->shape to NULL it is needed // to call shapeSelectionChanged() even if the selection did not change // to be sure d->shape is proper set again. shapeSelectionChanged(); } void ChartTool::deactivate() { d->shape = 0; // Tell the config widget to delete all open dialogs. // // The reason why we want to do that explicitly here is because // they are connected to the models, which may disappear when the // chart shape is destructed. foreach (QWidget *w, optionWidgets()) { ChartConfigWidget *configWidget = dynamic_cast(w); if (configWidget) configWidget->deleteSubDialogs(); } } QWidget *ChartTool::createOptionWidget() { ChartConfigWidget *widget = new ChartConfigWidget(); - + connect(widget, SIGNAL(dataSetXDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetXDataRegion(DataSet*,CellRegion))); connect(widget, SIGNAL(dataSetYDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetYDataRegion(DataSet*,CellRegion))); connect(widget, SIGNAL(dataSetCustomDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetCustomDataRegion(DataSet*,CellRegion))); connect(widget, SIGNAL(dataSetLabelDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetLabelDataRegion(DataSet*,CellRegion))); connect(widget, SIGNAL(dataSetCategoryDataRegionChanged(DataSet*,CellRegion)), this, SLOT(setDataSetCategoryDataRegion(DataSet*,CellRegion))); connect(widget, SIGNAL(dataSetChartTypeChanged(DataSet*,ChartType)), this, SLOT(setDataSetChartType(DataSet*,ChartType))); connect(widget, SIGNAL(dataSetChartSubTypeChanged(DataSet*,ChartSubtype)), this, SLOT(setDataSetChartSubType(DataSet*,ChartSubtype))); connect(widget, SIGNAL(datasetBrushChanged(DataSet*,QColor)), this, SLOT(setDataSetBrush(DataSet*,QColor))); connect(widget, SIGNAL(dataSetMarkerChanged(DataSet*,OdfMarkerStyle)), this, SLOT(setDataSetMarker(DataSet*,OdfMarkerStyle))); connect(widget, SIGNAL(datasetPenChanged(DataSet*,QColor)), this, SLOT(setDataSetPen(DataSet*,QColor))); connect(widget, SIGNAL(datasetShowCategoryChanged(DataSet*,bool)), this, SLOT(setDataSetShowCategory(DataSet*,bool))); connect(widget, SIGNAL(dataSetShowNumberChanged(DataSet*,bool)), this, SLOT(setDataSetShowNumber(DataSet*,bool))); connect(widget, SIGNAL(datasetShowPercentChanged(DataSet*,bool)), this, SLOT(setDataSetShowPercent(DataSet*,bool))); connect(widget, SIGNAL(datasetShowSymbolChanged(DataSet*,bool)), this, SLOT(setDataSetShowSymbol(DataSet*,bool))); connect(widget, SIGNAL(dataSetAxisChanged(DataSet*,Axis*)), this, SLOT(setDataSetAxis(DataSet*,Axis*))); connect(widget, SIGNAL(gapBetweenBarsChanged(int)), this, SLOT(setGapBetweenBars(int))); connect(widget, SIGNAL(gapBetweenSetsChanged(int)), this, SLOT(setGapBetweenSets(int))); connect(widget, SIGNAL(pieExplodeFactorChanged(DataSet*,int)), this, SLOT(setPieExplodeFactor(DataSet*,int))); connect(widget, SIGNAL(showLegendChanged(bool)), this, SLOT(setShowLegend(bool))); connect(widget, SIGNAL(chartTypeChanged(ChartType,ChartSubtype)), this, SLOT(setChartType(ChartType,ChartSubtype))); connect(widget, SIGNAL(chartSubTypeChanged(ChartSubtype)), this, SLOT(setChartSubType(ChartSubtype))); connect(widget, SIGNAL(threeDModeToggled(bool)), this, SLOT(setThreeDMode(bool))); connect(widget, SIGNAL(showTitleChanged(bool)), this, SLOT(setShowTitle(bool))); connect(widget, SIGNAL(showSubTitleChanged(bool)), this, SLOT(setShowSubTitle(bool))); connect(widget, SIGNAL(showFooterChanged(bool)), this, SLOT(setShowFooter(bool))); connect(widget, SIGNAL(axisAdded(AxisDimension,QString)), this, SLOT(addAxis(AxisDimension,QString))); connect(widget, SIGNAL(axisRemoved(Axis*)), this, SLOT(removeAxis(Axis*))); connect(widget, SIGNAL(axisTitleChanged(Axis*,QString)), this, SLOT(setAxisTitle(Axis*,QString))); connect(widget, SIGNAL(axisShowTitleChanged(Axis*,bool)), this, SLOT(setAxisShowTitle(Axis*,bool))); connect(widget, SIGNAL(axisShowGridLinesChanged(Axis*,bool)), this, SLOT(setAxisShowGridLines(Axis*,bool))); connect(widget, SIGNAL(axisUseLogarithmicScalingChanged(Axis*,bool)), this, SLOT(setAxisUseLogarithmicScaling(Axis*,bool))); connect(widget, SIGNAL(axisStepWidthChanged(Axis*,qreal)), this, SLOT(setAxisStepWidth(Axis*,qreal))); connect(widget, SIGNAL(axisSubStepWidthChanged(Axis*,qreal)), this, SLOT(setAxisSubStepWidth(Axis*,qreal))); connect(widget, SIGNAL(axisUseAutomaticStepWidthChanged(Axis*,bool)), this, SLOT(setAxisUseAutomaticStepWidth(Axis*,bool))); connect(widget, SIGNAL(axisUseAutomaticSubStepWidthChanged(Axis*,bool)), this, SLOT(setAxisUseAutomaticSubStepWidth(Axis*,bool))); connect(widget, SIGNAL(axisLabelsFontChanged(Axis*,QFont)), this, SLOT(setAxisLabelsFont(Axis*,QFont))); connect(widget, SIGNAL(legendTitleChanged(QString)), this, SLOT(setLegendTitle(QString))); connect(widget, SIGNAL(legendFontChanged(QFont)), this, SLOT(setLegendFont(QFont))); connect(widget, SIGNAL(legendFontSizeChanged(int)), this, SLOT(setLegendFontSize(int))); connect(widget, SIGNAL(legendOrientationChanged(Qt::Orientation)), this, SLOT(setLegendOrientation(Qt::Orientation))); connect(widget, SIGNAL(legendAlignmentChanged(Qt::Alignment)), this, SLOT(setLegendAlignment(Qt::Alignment))); connect(widget, SIGNAL(legendFixedPositionChanged(Position)), this, SLOT(setLegendFixedPosition(Position))); connect(widget, SIGNAL(legendBackgroundColorChanged(QColor)) , this, SLOT(setLegendBackgroundColor(QColor))); connect(widget, SIGNAL(legendFrameColorChanged(QColor)) , this, SLOT(setLegendFrameColor(QColor))); connect(widget, SIGNAL(legendShowFrameChanged(bool)) , this, SLOT(setLegendShowFrame(bool))); connect(d->shape, SIGNAL(updateConfigWidget()), widget, SLOT(update())); connect(d->shape->legend(), SIGNAL(updateConfigWidget()), widget, SLOT(update())); return widget; } void ChartTool::setChartType(ChartType type, ChartSubtype subtype) { Q_ASSERT(d->shape); if (!d->shape) return; ChartTypeCommand *command = new ChartTypeCommand(d->shape); if (command!=0) { command->setChartType(type, subtype); canvas()->addCommand(command); } foreach (QWidget *w, optionWidgets()) w->update(); } void ChartTool::setChartSubType(ChartSubtype subtype) { Q_ASSERT(d->shape); if (!d->shape) return; d->shape->setChartSubType(subtype); d->shape->update(); } void ChartTool::setDataSetXDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setXDataRegion(region); } void ChartTool::setDataSetYDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setYDataRegion(region); } void ChartTool::setDataSetCustomDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setCustomDataRegion(region); } void ChartTool::setDataSetLabelDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setLabelDataRegion(region); } void ChartTool::setDataSetCategoryDataRegion(DataSet *dataSet, const CellRegion ®ion) { if (!dataSet) return; dataSet->setCategoryDataRegion(region); } void ChartTool::setDataSetChartType(DataSet *dataSet, ChartType type) { Q_ASSERT(d->shape); Q_ASSERT(dataSet); if (dataSet) dataSet->setChartType(type); d->shape->update(); d->shape->legend()->update(); } void ChartTool::setDataSetChartSubType(DataSet *dataSet, ChartSubtype subType) { Q_ASSERT(dataSet); if (dataSet) dataSet->setChartSubType(subType); d->shape->update(); } void ChartTool::setDataSetBrush(DataSet *dataSet, const QColor& color) { Q_ASSERT(d->shape); if (!dataSet) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetBrush(color); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setDataSetPen(DataSet *dataSet, const QColor& color) { Q_ASSERT(d->shape); if (!dataSet) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetPen(color); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setDataSetMarker(DataSet *dataSet, OdfMarkerStyle style) { Q_ASSERT(d->shape); if (!dataSet) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetMarker(style); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setDataSetAxis(DataSet *dataSet, Axis *axis) { Q_ASSERT(d->shape); if (!dataSet || !axis) return; dataSet->attachedAxis()->detachDataSet(dataSet); axis->attachDataSet(dataSet); d->shape->update(); } void ChartTool::Private::setDataSetShowLabel(DataSet *dataSet, bool *number, bool *percentage, bool *category, bool *symbol) { Q_ASSERT(shape); if (!dataSet) return; DataSet::ValueLabelType type = dataSet->valueLabelType(); if (number) type.number = *number; if (percentage) type.percentage = *percentage; if (category) type.category = *category; if (symbol) type.symbol = *symbol; dataSet->setValueLabelType(type); // its necessary to set this for all data value //TODO we need to allow to differ in the UI between the datasets vs // the global setting and then allow to edit them separatly. for (int i = 0; i < dataSet->size(); ++i) { DataSet::ValueLabelType type = dataSet->valueLabelType(i); if (number) type.number = *number; if (percentage) type.percentage = *percentage; if (category) type.category = *category; if (symbol) type.symbol = *symbol; dataSet->setValueLabelType(type, i); } shape->update(); } void ChartTool::setDataSetShowCategory(DataSet *dataSet, bool b) { //d->setDataSetShowLabel(dataSet, 0, 0, &b, 0); Q_ASSERT(d->shape); if (!dataSet) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetShowCategory(b); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setDataSetShowNumber(DataSet *dataSet, bool b) { //d->setDataSetShowLabel(dataSet, &b, 0, 0, 0); Q_ASSERT(d->shape); if (!dataSet) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetShowNumber(b); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setDataSetShowPercent(DataSet *dataSet, bool b) { //d->setDataSetShowLabel(dataSet, 0, &b, 0, 0); Q_ASSERT(d->shape); if (!dataSet) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetShowPercent(b); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setDataSetShowSymbol(DataSet *dataSet, bool b) { //d->setDataSetShowLabel(dataSet, 0, 0, 0, &b); Q_ASSERT(d->shape); if (!dataSet) return; DatasetCommand *command = new DatasetCommand(dataSet, d->shape); command->setDataSetShowSymbol(b); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setThreeDMode(bool threeD) { Q_ASSERT(d->shape); if (!d->shape) return; d->shape->setThreeD(threeD); d->shape->update(); } void ChartTool::setShowTitle(bool show) { Q_ASSERT(d->shape); if (!d->shape) return; ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->title(), d->shape, show); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setShowSubTitle(bool show) { Q_ASSERT(d->shape); if (!d->shape) return; ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->subTitle(), d->shape, show); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setShowFooter(bool show) { Q_ASSERT(d->shape); if (!d->shape) return; ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->footer(), d->shape, show); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setDataDirection(Qt::Orientation direction) { Q_ASSERT(d->shape); if (!d->shape) return; d->shape->proxyModel()->setDataDirection(direction); d->shape->relayout(); } void ChartTool::setLegendTitle(const QString &title) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendTitle(title); canvas()->addCommand(command); } void ChartTool::setLegendFont(const QFont &font) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); // There only is a general font, for the legend items and the legend title LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendFont(font); canvas()->addCommand(command); } void ChartTool::setLegendFontSize(int size) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendFontSize(size); canvas()->addCommand(command); } void ChartTool::setLegendOrientation(Qt::Orientation orientation) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendExpansion(QtOrientationToLegendExpansion(orientation)); canvas()->addCommand(command); } void ChartTool::setLegendAlignment(Qt::Alignment alignment) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); d->shape->legend()->setAlignment(alignment); d->shape->legend()->update(); } void ChartTool::setLegendFixedPosition(Position position) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); d->shape->legend()->setLegendPosition(position); foreach (QWidget *w, optionWidgets()) { ((ChartConfigWidget*) w)->updateFixedPosition(position); } d->shape->legend()->update(); } void ChartTool::setLegendBackgroundColor(const QColor& color) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); d->shape->legend()->setBackgroundColor(color); d->shape->legend()->update(); } void ChartTool::setLegendFrameColor(const QColor& color) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); d->shape->legend()->setFrameColor(color); d->shape->legend()->update(); } void ChartTool::setLegendShowFrame(bool show) { Q_ASSERT(d->shape); Q_ASSERT(d->shape->legend()); LegendCommand *command = new LegendCommand(d->shape->legend()); command->setLegendShowFrame(show); canvas()->addCommand(command); } void ChartTool::addAxis(AxisDimension dimension, const QString& title) { Q_ASSERT(d->shape); Axis *axis = new Axis(d->shape->plotArea(), dimension); axis->setTitleText(title); d->shape->update(); + // TODO: undo command + axis->title()->setVisible(false); + QMap map = d->shape->layout()->calculateLayout(axis->title(), true); + QMap::const_iterator it; + for (it = map.constBegin(); it != map.constEnd(); ++it) { + it.key()->setPosition(it.value().topLeft()); + it.key()->setSize(it.value().size()); + } + axis->title()->setVisible(true); + d->shape->update(); } void ChartTool::removeAxis(Axis *axis) { Q_ASSERT(d->shape); - + // TODO: undo command + if (axis->title()->isVisible()) { + QMap map = d->shape->layout()->calculateLayout(axis->title(), false); + QMap::const_iterator it; + for (it = map.constBegin(); it != map.constEnd(); ++it) { + it.key()->setPosition(it.value().topLeft()); + it.key()->setSize(it.value().size()); + } + } d->shape->plotArea()->removeAxis(axis); d->shape->update(); } void ChartTool::setAxisTitle(Axis *axis, const QString& title) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisTitle(title); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisShowTitle(Axis *axis, bool show) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisShowTitle(show); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisShowGridLines(Axis *axis, bool b) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisShowGridLines(b); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisUseLogarithmicScaling(Axis *axis, bool b) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisUseLogarithmicScaling(b); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisStepWidth(Axis *axis, qreal width) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisStepWidth(width); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisSubStepWidth(Axis *axis, qreal width) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisSubStepWidth(width); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisUseAutomaticStepWidth(Axis *axis, bool automatic) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisUseAutomaticStepWidth(automatic); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisUseAutomaticSubStepWidth(Axis *axis, bool automatic) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisUseAutomaticSubStepWidth(automatic); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setAxisLabelsFont(Axis *axis, const QFont &font) { Q_ASSERT(d->shape); AxisCommand *command = new AxisCommand(axis, d->shape); command->setAxisLabelsFont(font); canvas()->addCommand(command); d->shape->update(); } void ChartTool::setGapBetweenBars(int percent) { Q_ASSERT(d->shape); d->shape->plotArea()->setGapBetweenBars(percent); d->shape->update(); } void ChartTool::setGapBetweenSets(int percent) { Q_ASSERT(d->shape); d->shape->plotArea()->setGapBetweenSets(percent); d->shape->update(); } void ChartTool::setPieExplodeFactor(DataSet *dataSet, int percent) { Q_ASSERT(d->shape); dataSet->setPieExplodeFactor(percent); d->shape->update(); } void ChartTool::setShowLegend(bool show) { Q_ASSERT(d->shape); ChartTextShapeCommand *command = new ChartTextShapeCommand(d->shape->legend(), d->shape, show); + if (show) { + command->setText(kundo2_i18n("Show Legend")); + } else { + command->setText(kundo2_i18n("Hide Legend")); + } canvas()->addCommand(command); d->shape->legend()->update(); } diff --git a/plugins/chartshape/Legend.cpp b/plugins/chartshape/Legend.cpp index b0349d2dd61..01aae09724a 100644 --- a/plugins/chartshape/Legend.cpp +++ b/plugins/chartshape/Legend.cpp @@ -1,671 +1,664 @@ /* This file is part of the KDE project Copyright 2007 Johannes Simon Copyright 2010 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 "Legend.h" // Qt #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include "KChartConvertions.h" // KoChart #include "PlotArea.h" #include "ScreenConversions.h" #include "ChartLayout.h" #include "OdfLoadingHelper.h" using namespace KoChart; class Legend::Private { public: Private(); ~Private(); ChartShape *shape; // Properties of the Legend QString title; bool showFrame; QPen framePen; QBrush backgroundBrush; LegendExpansion expansion; Position position; QFont font; QFont titleFont; QColor fontColor; Qt::Alignment alignment; KoShapeStroke *lineBorder; // The connection to KChart KChart::Legend *kdLegend; QImage image; mutable bool pixmapRepaintRequested; QSizeF lastSize; QPointF lastZoomLevel; }; Legend::Private::Private() { lineBorder = new KoShapeStroke(0.5, Qt::black); showFrame = false; framePen = QPen(); backgroundBrush = QBrush(); expansion = HighLegendExpansion; - alignment = Qt::AlignRight; + alignment = Qt::AlignCenter; pixmapRepaintRequested = true; position = EndPosition; } Legend::Private::~Private() { delete lineBorder; } Legend::Legend(ChartShape *parent) : QObject(parent) , d(new Private()) { Q_ASSERT(parent); setShapeId("ChartShapeLegend"); d->shape = parent; d->kdLegend = new KChart::Legend(); d->kdLegend->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); setTitleFontSize(10); setTitle(QString()); setFontSize(8); update(); parent->addShape(this); connect (d->kdLegend, SIGNAL(propertiesChanged()), this, SLOT(slotKdLegendChanged())); connect (parent, SIGNAL(chartTypeChanged(ChartType)), this, SLOT(slotChartTypeChanged(ChartType))); } Legend::~Legend() { delete d->kdLegend; delete d; } QString Legend::title() const { return d->title; } void Legend::setTitle(const QString &title) { d->title = title; d->kdLegend->setTitleText(title); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } bool Legend::showFrame() const { return d->showFrame; } void Legend::setShowFrame(bool show) { d->showFrame = show; setStroke(show ? d->lineBorder : 0); emit updateConfigWidget(); } QPen Legend::framePen() const { return d->framePen; } void Legend::setFramePen(const QPen &pen) { d->framePen = pen; // KChart KChart::FrameAttributes attributes = d->kdLegend->frameAttributes(); attributes.setPen(pen); d->kdLegend->setFrameAttributes(attributes); d->pixmapRepaintRequested = true; } QColor Legend::frameColor() const { return d->framePen.color(); } void Legend::setFrameColor(const QColor &color) { d->framePen.setColor(color); // KChart KChart::FrameAttributes attributes = d->kdLegend->frameAttributes(); attributes.setVisible(true); QPen pen = attributes.pen(); pen.setColor(color); attributes.setPen(pen); d->kdLegend->setFrameAttributes(attributes); d->pixmapRepaintRequested = true; } QBrush Legend::backgroundBrush() const { return d->backgroundBrush; } void Legend::setBackgroundBrush(const QBrush &brush) { d->backgroundBrush = brush; // KChart KChart::BackgroundAttributes attributes = d->kdLegend->backgroundAttributes(); attributes.setVisible(true); attributes.setBrush(brush); d->kdLegend->setBackgroundAttributes(attributes); d->pixmapRepaintRequested = true; } QColor Legend::backgroundColor() const { return d->backgroundBrush.color(); } void Legend::setBackgroundColor(const QColor &color) { d->backgroundBrush.setColor(color); // KChart KChart::BackgroundAttributes attributes = d->kdLegend->backgroundAttributes(); attributes.setVisible(true); QBrush brush = attributes.brush(); brush.setColor(color); attributes.setBrush(brush); d->kdLegend->setBackgroundAttributes(attributes); d->pixmapRepaintRequested = true; } QFont Legend::font() const { return d->font; } void Legend::setFont(const QFont &font) { d->font = font; // KChart KChart::TextAttributes attributes = d->kdLegend->textAttributes(); attributes.setFont(font); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } qreal Legend::fontSize() const { return d->font.pointSizeF(); } void Legend::setFontSize(qreal size) { d->font.setPointSizeF(size); // KChart KChart::TextAttributes attributes = d->kdLegend->textAttributes(); KChart::Measure m = attributes.fontSize(); m.setValue(size); attributes.setFontSize(m); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } void Legend::setFontColor(const QColor &color) { KChart::TextAttributes attributes = d->kdLegend->textAttributes(); QPen pen = attributes.pen(); pen.setColor(color); attributes.setPen(pen); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; } QColor Legend::fontColor() const { KChart::TextAttributes attributes = d->kdLegend->textAttributes(); QPen pen = attributes.pen(); return pen.color(); } QFont Legend::titleFont() const { return d->titleFont; } void Legend::setTitleFont(const QFont &font) { d->titleFont = font; // KChart KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); attributes.setFont(font); d->kdLegend->setTitleTextAttributes(attributes); d->pixmapRepaintRequested = true; } qreal Legend::titleFontSize() const { return d->titleFont.pointSizeF(); } void Legend::setTitleFontSize(qreal size) { d->titleFont.setPointSizeF(size); // KChart KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); d->kdLegend->setTitleTextAttributes(attributes); d->pixmapRepaintRequested = true; } LegendExpansion Legend::expansion() const { return d->expansion; } void Legend::setExpansion(LegendExpansion expansion) { d->expansion = expansion; d->kdLegend->setOrientation(LegendExpansionToQtOrientation(expansion)); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } Qt::Alignment Legend::alignment() const { return d->alignment; } void Legend::setAlignment(Qt::Alignment alignment) { d->alignment = alignment; } Position Legend::legendPosition() const { return d->position; } void Legend::setLegendPosition(Position position) { d->position = position; d->pixmapRepaintRequested = true; - d->shape->layout()->setPosition(this, position); + d->shape->layout()->setPosition(this, position, LegendType); } void Legend::setSize(const QSizeF &newSize) { QSize newSizePx = ScreenConversions::scaleFromPtToPx(newSize); d->kdLegend->resize(newSizePx); d->kdLegend->resizeLayout(newSizePx); KoShape::setSize(newSize); } void Legend::paintPixmap(QPainter &painter, const KoViewConverter &converter) { // Adjust the size of the painting area to the current zoom level const QSize paintRectSize = converter.documentToView(d->lastSize).toSize(); d->image = QImage(paintRectSize, QImage::Format_ARGB32); 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->kdLegend->paint(&pixmapPainter); } void Legend::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()); //clipRect.intersect(paintRect); painter.setClipRect(paintRect, Qt::IntersectClip); // 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()) { // TODO: 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 d->pixmapRepaintRequested = false; d->lastZoomLevel = zoomLevel; d->lastSize = size(); paintPixmap(painter, converter); }*/ // Paint the background if (background()) { QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // KChart thinks in pixels, Calligra in pt // KChart also for non-QWidget painting devices cares for the logicalDpi // Other than PlotArea we do not control the output size via the paint method, // so here have to resize the legend temporarily. // Printing should only result in 1 paint call, so this should not happen too often. // TODO: something in KChart seems broken in general on printer output, also seen in kchart examples // so legend in print is still broken :/ const QSize sizePx = d->kdLegend->size(); const QSize newSizePx = ScreenConversions::scaleFromPtToPx(size(), painter); const bool isPainterDifferentDpi = (sizePx != newSizePx); if (isPainterDifferentDpi) { // temporarily set a size matching the painterdevice d->kdLegend->resize(newSizePx); d->kdLegend->resizeLayout(newSizePx); } ScreenConversions::scaleFromPtToPx(painter); d->kdLegend->paint(&painter); if (isPainterDifferentDpi) { // restore screen-dpi size d->kdLegend->resize(sizePx); d->kdLegend->resizeLayout(sizePx); } //painter.restore(); // Paint the cached pixmap //painter.drawImage(0, 0, d->image); } // ---------------------------------------------------------------- // loading and saving bool Legend::loadOdf(const KoXmlElement &legendElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.clear(); // FIXME: If the style isn't present we shouldn't care about it at all // and move everything related to the legend style in this if clause if (legendElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { context.odfLoadingContext().fillStyleStack(legendElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); } if (!legendElement.isNull()) { int attributesToLoad = OdfAllAttributes; QString lp = legendElement.attributeNS(KoXmlNS::chart, "legend-position", QString()); - if (!lp.isEmpty()) { - attributesToLoad ^= OdfPosition; - } - - // FIXME according to odf if legend-position is provided the x - // and y value should not be used. - // - // FIXME also width and height are not supported at this place - if (legendElement.hasAttributeNS(KoXmlNS::svg, "x") || - legendElement.hasAttributeNS(KoXmlNS::svg, "y") || - legendElement.hasAttributeNS(KoXmlNS::svg, "width") || - legendElement.hasAttributeNS(KoXmlNS::svg, "height")) - { - d->shape->layout()->setPosition(this, FloatingPosition); - } + // Note: load position even if it might not be used (if alignment is specified) loadOdfAttributes(legendElement, context, attributesToLoad); QString lalign = legendElement.attributeNS(KoXmlNS::chart, "legend-align", QString()); if (legendElement.hasAttributeNS(KoXmlNS::style, "legend-expansion")) { QString lexpansion = legendElement.attributeNS(KoXmlNS::style, "legend-expansion", QString()); if (lexpansion == "wide") setExpansion(WideLegendExpansion); else if (lexpansion == "high") setExpansion(HighLegendExpansion); else setExpansion(BalancedLegendExpansion); } if (lalign == "start") { setAlignment(Qt::AlignLeft); } else if (lalign == "end") { setAlignment(Qt::AlignRight); } - else { + else if (lalign == "center") { setAlignment(Qt::AlignCenter); + } else { + setAlignment(Qt::AlignJustify); // Means: use shapes position } if (lp == "start") { setLegendPosition(StartPosition); } else if (lp == "top") { setLegendPosition(TopPosition); } else if (lp == "bottom") { setLegendPosition(BottomPosition); } else if (lp == "top-start") { setLegendPosition(TopStartPosition); } else if (lp == "bottom-start") { setLegendPosition(BottomStartPosition); } else if (lp == "top-end") { setLegendPosition(TopEndPosition); } else if (lp == "bottom-end") { setLegendPosition(BottomEndPosition); } else { setLegendPosition(EndPosition); } if (legendElement.hasAttributeNS(KoXmlNS::office, "title")) { setTitle(legendElement.attributeNS(KoXmlNS::office, "title", QString())); } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family"); QFont font = d->font; font.setFamily(fontFamily); setFont(font); } if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); setFontSize(fontSize); } if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) { QColor color = styleStack.property(KoXmlNS::fo, "font-color"); if (color.isValid()) { setFontColor(color); } } } else { // No legend element, use default legend. - // FIXME: North?? Isn't that a bit strange as default? /IW - setLegendPosition(TopPosition); + setLegendPosition(EndPosition); setAlignment(Qt::AlignCenter); } d->pixmapRepaintRequested = true; return true; } void Legend::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); bodyWriter.startElement("chart:legend"); saveOdfAttributes(context, OdfPosition); // Legend specific attributes QString lp = PositionToString(d->position); if (!lp.isEmpty()) { bodyWriter.addAttribute("chart:legend-position", lp); } - QString lalign; // FIXME: This string is always empty. What gives? + QString lalign; + switch (d->alignment) { + case Qt::AlignLeft: lalign = "start"; break; + case Qt::AlignRight: lalign = "end"; break; + case Qt::AlignCenter: lalign = "center"; break; + default: break; + } if (!lalign.isEmpty()) { bodyWriter.addAttribute("chart:legend-align", lalign); } // Legend style FIXME: Check if more styling then just the font goes here. KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart", 0); saveOdfFont(style, d->font, d->fontColor); bodyWriter.addAttribute("chart:style-name", saveStyle(style, context)); QString lexpansion; switch (expansion()) { case WideLegendExpansion: lexpansion = "wide"; break; case HighLegendExpansion: lexpansion = "high"; break; case BalancedLegendExpansion: lexpansion = "balanced"; break; }; bodyWriter.addAttribute("style:legend-expansion", lexpansion); if (!title().isEmpty()) bodyWriter.addAttribute("office:title", title()); bodyWriter.endElement(); // chart:legend } KChart::Legend *Legend::kdLegend() const { // There has to be a valid KChart instance of this legend Q_ASSERT(d->kdLegend); return d->kdLegend; } void Legend::rebuild() { d->kdLegend->forceRebuild(); update(); } void Legend::update() const { d->pixmapRepaintRequested = true; KoShape::update(); } void Legend::slotKdLegendChanged() { // FIXME: Update legend properly by implementing all *DataChanged() slots // in KChartModel. Right now, only yDataChanged() is implemented. //d->kdLegend->forceRebuild(); QSize size = d->kdLegend->sizeHint(); setSize(ScreenConversions::scaleFromPxToPt(size)); update(); } void Legend::slotChartTypeChanged(ChartType chartType) { // TODO: Once we support markers, this switch will have to be // more clever. switch (chartType) { case LineChartType: case ScatterChartType: d->kdLegend->setLegendStyle(KChart::Legend::MarkersAndLines); break; default: d->kdLegend->setLegendStyle(KChart::Legend::MarkersOnly); break; } } diff --git a/plugins/chartshape/PlotArea.cpp b/plugins/chartshape/PlotArea.cpp index 636ec417ca9..efb6e58c577 100644 --- a/plugins/chartshape/PlotArea.cpp +++ b/plugins/chartshape/PlotArea.cpp @@ -1,1165 +1,1165 @@ /* This file is part of the KDE project Copyright 2007-2008 Johannes Simon Copyright 2009-2010 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 "PlotArea.h" // Qt #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 // 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(); CoordinatePlaneList coordinatePlanesForChartType(ChartType type); 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; int gapBetweenBars; int gapBetweenSets; // 2. Pie charts // TODO: Load+Save qreal pieAngleOffset; // in degrees // ---------------------------------------------------------------- // 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; }; 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) // Data specific for bar charts , gapBetweenBars(0) , gapBetweenSets(100) // OpenOffice.org's default. It means the first pie slice starts at the // very top (and then going counter-clockwise). , pieAngleOffset(90.0) // 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) { // --- Prepare Primary Cartesian Coordinate Plane --- KChart::GridAttributes gridAttributes; gridAttributes.setGridVisible(false); gridAttributes.setGridGranularitySequence(KChartEnums::GranularitySequence_10_50); kdCartesianPlanePrimary->setGlobalGridAttributes(gridAttributes); // Old workaround for broken behaviour in CartesianCoordinatePlane::drawingArea() // with a custom patch to the local KChart copy: // Disable odd default of (1, 1, -3, -3) which only produces weird offsets // between axes and plot area frame. // kdCartesianPlanePrimary->setDrawingAreaMargins(0, 0, 0, 0); // TODO: Needs proper fixing in KChart in general // --- Prepare Secondary Cartesian Coordinate Plane --- kdCartesianPlaneSecondary->setGlobalGridAttributes(gridAttributes); // TODO: Needs proper fixing in KChart in general // see similar call a few lines above // kdCartesianPlaneSecondary->setDrawingAreaMargins(0, 0, 0, 0); // --- 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); } PlotArea::Private::~Private() { 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); } PlotArea::PlotArea(ChartShape *parent) : QObject() , KoShape() , d(new Private(this, parent)) { - setShapeId("ChartShapePlotArea"); + setShapeId("ChartShapePlotArea"); // NB! used by defaulttool/ChartResizeStrategy.cpp Q_ASSERT(d->shape); Q_ASSERT(d->shape->proxyModel()); 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); 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(); } 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->orientation() == Qt::Horizontal) { if (firstXAxisFound) return axis; else firstXAxisFound = true; } } return 0; } Axis *PlotArea::secondaryYAxis() const { bool firstYAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->orientation() == Qt::Vertical) { 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->vertical; } Ko3dScene *PlotArea::threeDScene() const { return d->threeDScene; } int PlotArea::gapBetweenBars() const { return d->gapBetweenBars; } int PlotArea::gapBetweenSets() const { return d->gapBetweenSets; } qreal PlotArea::pieAngleOffset() const { return d->pieAngleOffset; } 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->registerKdAxis(axis->kdAxis()); } } requestRepaint(); return true; } bool PlotArea::removeAxis(Axis *axis) { if (!d->axes.contains(axis)) { warnChart << "PlotArea::removeAxis(): Trying to remove non-added axis."; return false; } if (!axis) { warnChart << "PlotArea::removeAxis(): Pointer to axis is NULL!"; return false; } if (axis->title()) d->automaticallyHiddenAxisTitles.removeAll(axis->title()); d->axes.removeAll(axis); if (axis->dimension() == XAxisDimension) { foreach (Axis *_axis, d->axes) _axis->deregisterKdAxis(axis->kdAxis()); } // This also removes the axis' title, which is a shape as well delete axis; 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::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)) { foreach (Axis *axis, d->axes) { if (!axis->title()->isVisible()) continue; axis->title()->setVisible(false); d->automaticallyHiddenAxisTitles.append(axis->title()); } } else if (isPolar(d->chartType) && !isPolar(type)) { foreach (KoShape *title, d->automaticallyHiddenAxisTitles) { title->setVisible(true); } d->automaticallyHiddenAxisTitles.clear(); } CoordinatePlaneList planesToRemove; // First remove secondary cartesian plane as it references the primary // plane, otherwise KD Chart 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); } requestRepaint(); } void PlotArea::setChartSubType(ChartSubtype subType) { d->chartSubtype = subType; foreach (Axis *axis, d->axes) { axis->plotAreaChartSubTypeChanged(subType); } requestRepaint(); } 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(); // 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); // } 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. 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, "angle-offset")) { bool ok; const int angleOffset = styleStack.property(KoXmlNS::chart, "angle-offset").toInt(&ok); if (ok) setPieAngleOffset(angleOffset); } // 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"); // 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"); } // 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")) // We have to know what dimension the axis is supposed to be.. continue; const QString dimension = n.attributeNS(KoXmlNS::chart, "dimension", QString()); AxisDimension dim; if (dimension == "x") dim = XAxisDimension; else if (dimension == "y") dim = YAxisDimension; else if (dimension == "z") dim = ZAxisDimension; else continue; Axis *axis = new Axis(this, dim); axis->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 == StockChartType ? 3 : 1, 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 (d->chartType == StockChartType && n.localName() == "stock-gain-marker") { // FIXME } else if (d->chartType == StockChartType && n.localName() == "stock-loss-marker") { // FIXME } else if (d->chartType == StockChartType && n.localName() == "stock-range-line") { if (n.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); // stroke-color const QString strokeColor = styleStack.property(KoXmlNS::svg, "stroke-color"); // FIXME: There seem to be no way to set this for the StockChart in KChart. :-/ //QPen(QColor(strokeColor)); // FIXME: svg:stroke-width } } else if (n.localName() != "axis" && n.localName() != "series") { warnChart << "PlotArea::loadOdf(): Unknown tag name " << n.localName(); } } requestRepaint(); return true; } void PlotArea::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); //KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:plot-area"); // FIXME: Somehow this style gets the name gr2 instead of ch2. // Fix that as well. 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); 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); } // 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: // FIXME break; case RingChartType: // FIXME 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: 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::setGapBetweenBars(int percent) { d->gapBetweenBars = percent; emit gapBetweenBarsChanged(percent); } void PlotArea::setGapBetweenSets(int percent) { d->gapBetweenSets = percent; emit gapBetweenSetsChanged(percent); } void PlotArea::setPieAngleOffset(qreal angle) { d->pieAngleOffset = angle; emit pieAngleOffsetChanged(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; } void PlotArea::plotAreaUpdate() const { parent()->legend()->update(); 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) { 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(); } diff --git a/plugins/chartshape/commands/AxisCommand.cpp b/plugins/chartshape/commands/AxisCommand.cpp index 36cc01b5583..e9adf1bdc4a 100644 --- a/plugins/chartshape/commands/AxisCommand.cpp +++ b/plugins/chartshape/commands/AxisCommand.cpp @@ -1,182 +1,186 @@ /* This file is part of the KDE project Copyright 2012 Brijesh Patel 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 "AxisCommand.h" // KF5 #include // KoChart #include "Axis.h" #include "ChartDebug.h" +#include "ChartTextShapeCommand.h" using namespace KoChart; using namespace KChart; AxisCommand::AxisCommand(Axis* axis, ChartShape* chart) : m_chart(chart) , m_axis(axis) { m_newShowTitle = m_axis->title()->isVisible(); m_newTitleText = m_axis->titleText(); m_newShowGridLines = m_axis->showMajorGrid(); m_newUseLogarithmicScaling = m_axis->scalingIsLogarithmic(); m_newLabelsFont = m_axis->font(); } AxisCommand::~AxisCommand() { } void AxisCommand::redo() { // save the old type m_oldShowTitle = m_axis->title()->isVisible(); m_oldTitleText = m_axis->titleText(); m_oldShowGridLines = m_axis->showMajorGrid(); m_oldUseLogarithmicScaling = m_axis->scalingIsLogarithmic(); m_oldLabelsFont = m_axis->font(); /*m_oldStepWidth = m_axis->majorInterval(); m_oldSubStepWidth = m_axis->minorInterval(); m_oldUseAutomaticStepWidth = m_axis->useAutomaticMajorInterval(); m_oldUseAutomaticSubStepWidth = m_axis->useAutomaticMinorInterval();*/ if (m_oldShowTitle == m_newShowTitle && m_oldTitleText == m_newTitleText && m_oldShowGridLines == m_newShowGridLines && m_oldUseLogarithmicScaling == m_newUseLogarithmicScaling && m_oldLabelsFont == m_newLabelsFont) return; // Actually do the work - m_axis->title()->setVisible(m_newShowTitle); m_axis->setTitleText(m_newTitleText); m_axis->setShowMajorGrid(m_newShowGridLines); m_axis->setShowMinorGrid(m_newShowGridLines); m_axis->setScalingLogarithmic(m_oldUseLogarithmicScaling); m_axis->setFont(m_newLabelsFont); m_axis->setFontSize(m_newLabelsFont.pointSize()); /*m_axis->setMajorInterval(m_newStepWidth); m_axis->setMinorInterval(m_newSubStepWidth); m_axis->setUseAutomaticMajorInterval(m_newUseAutomaticStepWidth); m_axis->setUseAutomaticMinorInterval(m_newUseAutomaticSubStepWidth);*/ + + KUndo2Command::redo(); m_chart->update(); } void AxisCommand::undo() { if (m_oldShowTitle == m_newShowTitle && m_oldTitleText == m_newTitleText && m_oldShowGridLines == m_newShowGridLines && m_oldUseLogarithmicScaling == m_newUseLogarithmicScaling && m_oldLabelsFont == m_newLabelsFont) return; - m_axis->title()->setVisible(m_oldShowTitle); m_axis->setTitleText(m_oldTitleText); m_axis->setShowMajorGrid(m_oldShowGridLines); m_axis->setShowMinorGrid(m_oldShowGridLines); m_axis->setScalingLogarithmic(m_oldUseLogarithmicScaling); m_axis->setFont(m_oldLabelsFont); m_axis->setFontSize(m_oldLabelsFont.pointSize()); /*m_axis->setMajorInterval(m_oldStepWidth); m_axis->setMinorInterval(m_oldSubStepWidth); m_axis->setUseAutomaticMajorInterval(m_oldUseAutomaticStepWidth); m_axis->setUseAutomaticMinorInterval(m_oldUseAutomaticSubStepWidth);*/ + + KUndo2Command::undo(); m_chart->update(); } void AxisCommand::setAxisShowTitle(bool show) { m_newShowTitle = show; if (show) { setText(kundo2_i18n("Show Axis Title")); } else { setText(kundo2_i18n("Hide Axis Title")); } + new ChartTextShapeCommand(m_axis->title(), m_chart, show, this); } void AxisCommand::setAxisTitle(const QString &title) { m_newTitleText = title; setText(kundo2_i18n("Set Axis Title")); } void AxisCommand::setAxisShowGridLines(bool show) { m_newShowGridLines = show; if (show) { setText(kundo2_i18n("Show Axis Gridlines")); } else { setText(kundo2_i18n("Hide Axis Gridlines")); } } void AxisCommand::setAxisUseLogarithmicScaling(bool b) { m_newUseLogarithmicScaling = b; if (b) { setText(kundo2_i18n("Logarithmic Scaling")); } else { setText(kundo2_i18n("Linear Scaling")); } } void AxisCommand::setAxisStepWidth(qreal width) { m_newStepWidth = width; setText(kundo2_i18n("Set Axis Step Width")); } void AxisCommand::setAxisSubStepWidth(qreal width) { m_newSubStepWidth = width; setText(kundo2_i18n("Set Axis Substep Width")); } void AxisCommand::setAxisUseAutomaticStepWidth(bool automatic) { m_newShowGridLines = automatic; if (automatic) { setText(kundo2_i18n("Set Automatic Step Width")); } else { setText(kundo2_i18n("Set Manual Step Width")); } } void AxisCommand::setAxisUseAutomaticSubStepWidth(bool automatic) { m_newShowGridLines = automatic; if (automatic) { setText(kundo2_i18n("Automatic Substep Width")); } else { setText(kundo2_i18n("Manual Substep Width")); } } void AxisCommand::setAxisLabelsFont(const QFont &font) { m_newLabelsFont = font; setText(kundo2_i18n("Set Axis Label Font")); } diff --git a/plugins/chartshape/commands/ChartTextShapeCommand.cpp b/plugins/chartshape/commands/ChartTextShapeCommand.cpp index bc2a292d4be..cb38a34cb59 100644 --- a/plugins/chartshape/commands/ChartTextShapeCommand.cpp +++ b/plugins/chartshape/commands/ChartTextShapeCommand.cpp @@ -1,71 +1,110 @@ /* This file is part of the KDE project - Copyright 2012 Brijesh Patel - - 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. -*/ + * Copyright 2017 Dag Andersen + * Copyright 2012 Brijesh Patel + * + * 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 "ChartTextShapeCommand.h" // KF5 #include // Calligra #include "KoShape.h" +#include "KoShapeMoveCommand.h" +#include "KoShapeSizeCommand.h" // KoChart #include "ChartShape.h" #include "ChartLayout.h" +#include "PlotArea.h" #include "ChartDebug.h" using namespace KoChart; -ChartTextShapeCommand::ChartTextShapeCommand(KoShape* textShape, ChartShape *chart, bool isVisible) - : m_textShape(textShape) +ChartTextShapeCommand::ChartTextShapeCommand(KoShape* textShape, ChartShape *chart, bool isVisible, KUndo2Command *parent) + : KUndo2Command(parent) + , m_textShape(textShape) , m_chart(chart) + , m_oldIsVisible(textShape->isVisible()) , m_newIsVisible(isVisible) { + Q_ASSERT(m_oldIsVisible != m_newIsVisible); + + init(); + if (m_newIsVisible) { setText(kundo2_i18n("Show Text Shape")); } else { setText(kundo2_i18n("Hide Text Shape")); } } ChartTextShapeCommand::~ChartTextShapeCommand() { } void ChartTextShapeCommand::redo() { - // save the old type - m_oldIsVisible = m_textShape->isVisible(); - - if (m_oldIsVisible == m_newIsVisible) + if (m_oldIsVisible == m_newIsVisible) { return; - + } // Actually do the work - m_textShape->setVisible(m_newIsVisible); + KUndo2Command::redo(); + m_textShape->setVisible(m_newIsVisible); // after redo() m_chart->update(); } void ChartTextShapeCommand::undo() { - if (m_oldIsVisible == m_newIsVisible) + if (m_oldIsVisible == m_newIsVisible) { return; - - m_textShape->setVisible(m_oldIsVisible); + } + KUndo2Command::undo(); + m_textShape->setVisible(m_oldIsVisible); // after undo() m_chart->update(); } + +void ChartTextShapeCommand::init() +{ + const QMap map = m_chart->layout()->calculateLayout(m_textShape, m_newIsVisible); + QVector oldpositions; + QVector newpositions; + QVector oldsizes; + QVector newsizes; + QList positionshapes; + QList sizeshapes; + QMap::const_iterator it = map.constBegin(); + for (; it != map.constEnd(); ++it) { + if (it.key()->position() != it.value().topLeft()) { + positionshapes << it.key(); + oldpositions << it.key()->position(); + newpositions << it.value().topLeft(); + } + if (it.key()->size() != it.value().size()) { + sizeshapes << it.key(); + oldsizes << it.key()->size(); + newsizes << it.value().size(); + } + } + if (!positionshapes.isEmpty()) { + new KoShapeMoveCommand(positionshapes, oldpositions, newpositions, this); + } + if (!sizeshapes.isEmpty()) { + new KoShapeSizeCommand(sizeshapes, oldsizes, newsizes, this); + } +} diff --git a/plugins/chartshape/commands/ChartTextShapeCommand.h b/plugins/chartshape/commands/ChartTextShapeCommand.h index e5f254410cf..9e66b9bcc04 100644 --- a/plugins/chartshape/commands/ChartTextShapeCommand.h +++ b/plugins/chartshape/commands/ChartTextShapeCommand.h @@ -1,81 +1,85 @@ /* This file is part of the KDE project - Copyright 2012 Brijesh Patel - - 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. -*/ + * Copyright 2017 Dag Andersen + * Copyright 2012 Brijesh Patel + * + * 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 KCHART_CHART_TEXTSHAPE_COMMAND #define KCHART_CHART_TEXTSHAPE_COMMAND // Qt #include // KoChart #include "kochart_global.h" class KoShape; #if 0 namespace KChart { class AbstractCoordinatePlane; class AbstractDiagram; class Chart; } #endif namespace KoChart { class ChartShape; /** * Chart type replacement command. */ class ChartTextShapeCommand : public KUndo2Command { public: /** * Constructor. */ - ChartTextShapeCommand(KoShape* textShape, ChartShape* chart, bool isVisible); + ChartTextShapeCommand(KoShape* textShape, ChartShape* chart, bool isVisible, KUndo2Command *parent = 0); /** * Destructor. */ virtual ~ChartTextShapeCommand(); /** * Executes the actual operation. */ virtual void redo(); /** * Executes the actual operation in reverse order. */ virtual void undo(); +private: + void init(); + private: KoShape *m_textShape; ChartShape *m_chart; bool m_oldIsVisible; bool m_newIsVisible; }; } // namespace KoChart #endif // KCHART_CHART_TEXTSHAPE_COMMAND diff --git a/plugins/chartshape/commands/LegendCommand.cpp b/plugins/chartshape/commands/LegendCommand.cpp index f91a47a42f0..5b0b0cb7949 100644 --- a/plugins/chartshape/commands/LegendCommand.cpp +++ b/plugins/chartshape/commands/LegendCommand.cpp @@ -1,120 +1,141 @@ /* This file is part of the KDE project + Copyright 2017 Dag Andersen Copyright 2012 Brijesh Patel 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 "LegendCommand.h" // KF5 #include // KoChart #include "Legend.h" +#include "ChartShape.h" +#include "ChartLayout.h" #include "ChartDebug.h" using namespace KoChart; using namespace KChart; LegendCommand::LegendCommand(KoChart::Legend* legend) : m_legend(legend) { + QObject *l = qobject_cast(legend); // legend is both KoShape and QObject, both with parent() method + m_chart = dynamic_cast(l->parent()); + Q_ASSERT(m_chart); + m_newFont = legend->font(); m_newTitle = legend->title(); m_newFontSize = legend->fontSize(); m_newExpansion = legend->expansion(); m_newShowFrame = legend->showFrame(); } LegendCommand::~LegendCommand() { } void LegendCommand::redo() { // save the old type m_oldTitle = m_legend->title(); m_oldFont = m_legend->font(); m_oldFontSize = m_legend->fontSize(); m_oldExpansion = m_legend->expansion(); m_oldShowFrame = m_legend->showFrame(); if (m_oldTitle == m_newTitle && m_oldFont == m_newFont && m_oldFontSize == m_newFontSize && m_oldExpansion == m_newExpansion && m_oldShowFrame == m_newShowFrame) return; // Actually do the work m_legend->setTitle(m_newTitle); m_legend->setFont(m_newFont); m_legend->setFontSize(m_newFontSize); m_legend->setExpansion(m_newExpansion); m_legend->setShowFrame(m_newShowFrame); + + // FIXME: temporary hack, this needs proper undo command + QMap map = m_chart->layout()->calculateLayout(m_legend, m_legend->isVisible()); + for (KoShape *s : map.keys()) { + s->setPosition(map[s].topLeft()); + s->setSize(map[s].size()); + } m_legend->update(); } void LegendCommand::undo() { if (m_oldTitle == m_newTitle && m_oldFont == m_newFont && m_oldFontSize == m_newFontSize && m_oldExpansion == m_newExpansion && m_oldShowFrame == m_newShowFrame) return; m_legend->setTitle(m_oldTitle); m_legend->setFont(m_oldFont); m_legend->setFontSize(m_oldFontSize); m_legend->setExpansion(m_oldExpansion); m_legend->setShowFrame(m_oldShowFrame); + + // FIXME: temporary hack, this needs proper undo command + QMap map = m_chart->layout()->calculateLayout(m_legend, m_legend->isVisible()); + for (KoShape *s : map.keys()) { + s->setPosition(map[s].topLeft()); + s->setSize(map[s].size()); + } m_legend->update(); } void LegendCommand::setLegendTitle(const QString &title) { m_newTitle = title; setText(kundo2_i18n("Set Legend Title")); } void LegendCommand::setLegendFont(const QFont &font) { m_newFont = font; m_newFontSize = font.pointSize(); setText(kundo2_i18n("Set Legend Font")); } void LegendCommand::setLegendFontSize(int size) { m_newFontSize = size; setText(kundo2_i18n("Set Legend Font size")); } void LegendCommand::setLegendExpansion(LegendExpansion expansion) { m_newExpansion = expansion; setText(kundo2_i18n("Set Legend Orientation")); } void LegendCommand::setLegendShowFrame(bool show) { m_newShowFrame = show; if (show) { setText(kundo2_i18n("Show Legend Frame")); } else { setText(kundo2_i18n("Hide Legend Frame")); } } diff --git a/plugins/chartshape/commands/LegendCommand.h b/plugins/chartshape/commands/LegendCommand.h index e2e90226a65..8dcf63c1fd7 100644 --- a/plugins/chartshape/commands/LegendCommand.h +++ b/plugins/chartshape/commands/LegendCommand.h @@ -1,94 +1,96 @@ /* This file is part of the KDE project Copyright 2012 Brijesh Patel 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 KCHART_LEGEND_COMMAND #define KCHART_LEGEND_COMMAND // Qt #include // KoChart #include "kochart_global.h" #if 0 namespace KChart { class AbstractCoordinatePlane; class AbstractDiagram; class Chart; } #endif namespace KoChart { class Legend; +class ChartShape; class LegendCommand : public KUndo2Command { public: /** * Constructor. */ explicit LegendCommand(Legend *legend); /** * Destructor. */ virtual ~LegendCommand(); /** * Executes the actual operation. */ virtual void redo(); /** * Executes the actual operation in reverse order. */ virtual void undo(); void setLegendTitle(const QString &title); void setLegendFont(const QFont &font); void setLegendFontSize(int size); void setLegendExpansion(LegendExpansion expansion); /*void setLegendAlignment(Qt::Alignment); void setLegendFixedPosition(Position); void setLegendBackgroundColor(QColor &color); void setLegendFrameColor(QColor &color);*/ void setLegendShowFrame(bool show); private: Legend *m_legend; QString m_oldTitle; QString m_newTitle; QFont m_oldFont; QFont m_newFont; int m_oldFontSize; int m_newFontSize; LegendExpansion m_oldExpansion; LegendExpansion m_newExpansion; bool m_oldShowFrame; bool m_newShowFrame; + ChartShape *m_chart; }; } // namespace KoChart #endif // KCHART_LEGEND_COMMAND diff --git a/plugins/chartshape/kochart_global.cpp b/plugins/chartshape/kochart_global.cpp index 9c264912cd6..ef5126c690f 100644 --- a/plugins/chartshape/kochart_global.cpp +++ b/plugins/chartshape/kochart_global.cpp @@ -1,82 +1,101 @@ /* This file is part of the KDE project Copyright 2010 Johannes Simon 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 "kochart_global.h" namespace KoChart { bool isPolar(ChartType type) { switch (type) { case CircleChartType: case RingChartType: case RadarChartType: case FilledRadarChartType: return true; default: return false; } return false; } bool isCartesian(ChartType type) { return !isPolar(type); } int numDimensions(ChartType type) { int dimensions = 1; switch (type) { case BarChartType: case LineChartType: case AreaChartType: case CircleChartType: case RingChartType: case RadarChartType: case FilledRadarChartType: dimensions = 1; break; case ScatterChartType: case SurfaceChartType: dimensions = 2; break; case BubbleChartType: dimensions = 3; break; case StockChartType: // High, Low, Close. Also supported by KD Chart are Open, High, Low, // Close, but we only use the first so far. dimensions = 3; break; case GanttChartType: // FIXME: Figure out correct number of dimensions dimensions = 1; break; case LastChartType: dimensions = 1; } return dimensions; } } // namespace KoChart + +QDebug operator<<(QDebug dbg, KoChart::Position p) +{ + switch (p) { + case KoChart::StartPosition: dbg << "(StartPosition)"; break; + case KoChart::TopPosition: dbg << "(TopPosition)"; break; + case KoChart::EndPosition: dbg << "(EndPosition)"; break; + case KoChart::BottomPosition: dbg << "(BottomPosition)"; break; + case KoChart::TopStartPosition: dbg << "(BottomPosition)"; break; + case KoChart::TopEndPosition: dbg << "(TopEndPosition)"; break; + case KoChart::BottomStartPosition: dbg << "(BottomStartPosition)"; break; + case KoChart::BottomEndPosition: dbg << "(BottomEndPosition)"; break; + case KoChart::CenterPosition: dbg << "(CenterPosition)"; break; + case KoChart::FloatingPosition: dbg << "(FloatingPosition)"; break; + default: break; + Q_ASSERT(false); // Unknown position + } + return dbg; +} diff --git a/plugins/chartshape/kochart_global.h b/plugins/chartshape/kochart_global.h index e821e3ffd19..f4e0ab4bf3a 100644 --- a/plugins/chartshape/kochart_global.h +++ b/plugins/chartshape/kochart_global.h @@ -1,132 +1,144 @@ /* This file is part of the KDE project + Copyright 2017 Dag Andersen Copyright 2007 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. */ #ifndef KCHART_GLOBAL_H #define KCHART_GLOBAL_H +#include + namespace KoChart { // Chart types for OpenDocument enum ChartType { BarChartType, LineChartType, AreaChartType, CircleChartType, // Pie in KChart RingChartType, ScatterChartType, RadarChartType, // Polar in KChart FilledRadarChartType, // Polar in KChart StockChartType, BubbleChartType, SurfaceChartType, GanttChartType, LastChartType // Not an actual type, just a place holder }; const int NUM_CHARTTYPES = int (LastChartType); bool isPolar(ChartType type); bool isCartesian(ChartType type); int numDimensions(ChartType type); // Chart subtypes, applicable to Bar, Line, Area, and Radar enum ChartSubtype { NoChartSubtype, // for charts with no subtypes NormalChartSubtype, // For bar, line, area and radar charts StackedChartSubtype, PercentChartSubtype, HighLowCloseChartSubtype, // For stock charts OpenHighLowCloseChartSubtype, CandlestickChartSubtype }; enum AxisDimension { XAxisDimension, YAxisDimension, ZAxisDimension }; struct ChartTypeOptions { ChartSubtype subtype; }; enum Position { StartPosition, TopPosition, EndPosition, BottomPosition, TopStartPosition, TopEndPosition, BottomStartPosition, BottomEndPosition, CenterPosition, FloatingPosition }; enum LegendExpansion { WideLegendExpansion, HighLegendExpansion, BalancedLegendExpansion }; enum ErrorCategory { NoErrorCategory, VarianceErrorCategory, StandardDeviationErrorCategory, StandardErrorErrorCategory, PercentageErrorCategory, ErrorMarginErrorCategory, ConstantErrorCategory }; -enum LabelType { - TitleLabelType, - SubTitleLabelType, - FooterLabelType +enum ItemType { + GenericItemType = 0, + TitleLabelType = 1, + SubTitleLabelType = 3, + FooterLabelType = 5, + PlotAreaType = 10, + LegendType = 11, + XAxisTitleType = 20, + YAxisTitleType = 21, + SecondaryXAxisTitleType = 22, + SecondaryYAxisTitleType = 23 }; enum OdfMarkerStyle { MarkerSquare = 0, MarkerDiamond = 1, MarkerArrowDown = 2, MarkerArrowUp = 3, MarkerArrowRight = 4, MarkerArrowLeft = 5, MarkerBowTie = 6, MarkerHourGlass = 7, MarkerCircle = 8, MarkerStar = 9, MarkerX = 10, MarkerCross = 11, MarkerAsterisk = 12, MarkerHorizontalBar = 13, MarkerVerticalBar = 14, MarkerRing = 15, MarkerFastCross = 16, Marker1Pixel = 17, Marker4Pixels = 18, NoMarker = 19 }; } // Namespace KoChart +QDebug operator<<(QDebug dbg, KoChart::Position p); + #endif diff --git a/plugins/chartshape/tests/CMakeLists.txt b/plugins/chartshape/tests/CMakeLists.txt index 9c42e110ffc..d9db5b0ac91 100644 --- a/plugins/chartshape/tests/CMakeLists.txt +++ b/plugins/chartshape/tests/CMakeLists.txt @@ -1,48 +1,52 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_DIRECTORY} ) include_directories( ${CMAKE_SOURCE_DIR}/plugins/chartshape #${CMAKE_SOURCE_DIR}/interfaces #${CMAKE_SOURCE_DIR}/kchart/shape/dialogs ${KOMAIN_INCLUDES} ) # call: chartshape_add_unit_test( LINK_LIBRARIES [ [...]] [GUI]) macro(CHARTSHAPE_ADD_UNIT_TEST _TEST_NAME) ecm_add_test( ${ARGN} TEST_NAME "${_TEST_NAME}" NAME_PREFIX "shapes-chart-" ) endmacro() +chartshape_add_unit_test(TestChartLayout + TestChartLayout.cpp + LINK_LIBRARIES chartshapecore Qt5::Test +) chartshape_add_unit_test(TestProxyModel TestProxyModel.cpp LINK_LIBRARIES chartshapecore Qt5::Test ) #FIXME: Too many dependencies in DataSet, there should be no need to link to flake and the entire chart shape here chartshape_add_unit_test(TestDataSet TestDataSet.cpp LINK_LIBRARIES chartshapecore Qt5::Test ) chartshape_add_unit_test(TestKChartModel TestKChartModel.cpp ModelObserver.cpp LINK_LIBRARIES chartshapecore Qt5::Test ) chartshape_add_unit_test(TestTableSource TestTableSource.cpp LINK_LIBRARIES chartshapecore Qt5::Test ) chartshape_add_unit_test(TestCellRegion TestCellRegion.cpp LINK_LIBRARIES chartshapecore Qt5::Test ) # call: chartshape_loading_add_unit_test( LINK_LIBRARIES [ [...]] [GUI]) macro(CHARTSHAPE_LOADING_ADD_UNIT_TEST _TEST_NAME) ecm_add_test( ${ARGN} TEST_NAME "${_TEST_NAME}" NAME_PREFIX "shapes-chart-loading-" ) endmacro() add_subdirectory( odf ) diff --git a/plugins/chartshape/tests/TestChartLayout.cpp b/plugins/chartshape/tests/TestChartLayout.cpp new file mode 100644 index 00000000000..74c04d33f56 --- /dev/null +++ b/plugins/chartshape/tests/TestChartLayout.cpp @@ -0,0 +1,580 @@ +/* This file is part of the KDE project + * + * 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. + */ + +#include "TestChartLayout.h" +#include "ChartShape.h" +#include "ChartLayout.h" +#include "PlotArea.h" +#include "Axis.h" +#include "Legend.h" + +#include "KoDocumentResourceManager.h" +#include "KoShape.h" + +#include +#include +#include +#include + +using namespace KoChart; + +static void filterMessages(QtMsgType type, const QMessageLogContext &/*context*/, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + switch (type) { + case QtDebugMsg: +// fprintf(stderr, "%s\n%s\n%s\n%d\n%d\n", context.category, context.file, context.function, context.version, context.line); + break; + default: + fprintf(stderr, "%s\n", localMsg.constData()); + break; + } +} + +static void updateItems(const QMap &map) +{ + QMap::const_iterator it; + for (it = map.constBegin(); it != map.constEnd(); ++it) { + it.key()->setPosition(it.value().topLeft()); + it.key()->setSize(it.value().size()); + } +} + +static QRectF itemRect(const KoShape *shape) +{ + return ChartLayout::itemRect(shape); +} + +static void setItemPosition(KoShape *shape, const QPointF& pos) +{ + ChartLayout::setItemPosition(shape, pos); +} + + +TestChartLayout::TestChartLayout() +{ +} + +void TestChartLayout::initTestCase() +{ + msgHandler = qInstallMessageHandler(filterMessages); + + drm = new KoDocumentResourceManager(); // used throughout the test + + // Just used to check default layout + // If changed tests may have to be changed too + init(); + + QVERIFY(plotArea->isVisible()); + QVERIFY(legend->isVisible()); + QVERIFY(!chartTitle->isVisible()); + QVERIFY(!chartSubTitle->isVisible()); + QVERIFY(!chartFooter->isVisible()); + QVERIFY(plotArea->xAxis() != 0 && xAxisTitle != 0); + QVERIFY(plotArea->yAxis() != 0 && yAxisTitle != 0); + QVERIFY(xAxisTitle->isVisible()); + QVERIFY(yAxisTitle->isVisible()); + QVERIFY(secondaryXAxisTitle == 0); + QVERIFY(secondaryYAxisTitle == 0); + + qInstallMessageHandler(msgHandler); + + QCOMPARE(xAxisTitleRect.bottom(), chart->size().height() - layout->margins().y()); + QCOMPARE(yAxisTitleRect.left(), layout->margins().x()); + QCOMPARE(legendRect.right(), chart->size().width() - layout->margins().x()); + QCOMPARE(plotAreaRect.top(), layout->margins().y()); + QCOMPARE(plotAreaRect.left(), yAxisTitleRect.right() + layout->spacing().x()); + QCOMPARE(plotAreaRect.bottom(), xAxisTitleRect.top() - layout->spacing().x()); + QCOMPARE(plotAreaRect.right(), legendRect.left() - layout->spacing().x()); + + cleanup(); +} + +void TestChartLayout::cleanupTestCase() +{ + delete drm; +} + +void TestChartLayout::init() +{ + qInstallMessageHandler(filterMessages); + + chart = new ChartShape(drm); + layout = chart->layout(); + layout->setAutoLayoutEnabled(true); + chart->setSize(QSizeF(300., 200.)); + layout->layout(); + layout->setAutoLayoutEnabled(false); + +// qInfo()<<(KoShape*)chart<size(); +// debug(chart); + + chartTitle = chart->title(); + chartTitleRect = itemRect(chartTitle); + chartSubTitle = chart->subTitle(); + chartSubTitleRect = itemRect(chartSubTitle); + chartFooter = chart->footer(); + chartFooterRect = itemRect(chartFooter); + plotArea = chart->plotArea(); + plotAreaRect = itemRect(plotArea); + legend = chart->legend(); + legendRect = itemRect(legend); + xAxisTitle = plotArea->xAxis()->title(); + xAxisTitleRect = itemRect(xAxisTitle); + yAxisTitle = plotArea->yAxis()->title(); + yAxisTitleRect = itemRect(yAxisTitle); + + secondaryXAxisTitle = plotArea->secondaryXAxis() ? plotArea->secondaryXAxis()->title() : 0; + secondaryXAxisTitleRect = secondaryXAxisTitle ? itemRect(secondaryXAxisTitle) : QRectF(); + secondaryYAxisTitle = plotArea->secondaryYAxis() ? plotArea->secondaryYAxis()->title() : 0; + secondaryYAxisTitleRect = secondaryYAxisTitle ? itemRect(secondaryYAxisTitle) : QRectF(); + + qInstallMessageHandler(msgHandler); +} + +void TestChartLayout::cleanup() +{ + delete chart; +} + +void TestChartLayout::testRelativePositioning() { + qreal start1 = 10.0; + qreal length1 = 40.0; + qreal start2 = 10.0; + qreal length2 = 60.0; + qreal start = 5.0; + qreal length = 10.0; + qreal pos; + + qInfo()<<"Test resize only (start1 == start2)"; + + qInfo()<<"Test start before start1"; + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, 5.0); + + qInfo()<<"Test start at start1"; + start = start1; + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, 10.0); + + qInfo()<<"Test centered"; + start = start1 + (0.5 * (length1 - length)); + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + (0.5 * (length2 - length))); + + qInfo()<<"Test end at end"; + start = start1 + length1 - length; + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + length2 - length); + + qInfo()<<"Test end after end"; + start = start1 + length1 - length + 1.0; + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + length2 - length + 1.0); + + qInfo()<<"Test both increase size and move"; + start2 = 20.0; + + qInfo()<<"Test item at start"; + start = start1; + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2); + + qInfo()<<"Test item before start"; + start = start1 - 1.0; + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 - 1.0); + + qInfo()<<"Test item at end"; + start = start1 + length1 - (0.5 * length); + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + length2 - (0.5 * length)); + + qInfo()<<"Test item after end"; + start = start1 + length1 - (0.5 * length) + 1.0; + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + length2 - (0.5 * length) + 1.0); + + qInfo()<<"Test item centered"; + start = start1 + (0.5 * (length1 - length)); + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + (0.5 * (length2 - length))); + + qInfo()<<"Test both decrease size and move"; + qSwap(length1, length2); + + qInfo()<<"Test item centered"; + start = start1 + (length1 / 2.) - (0.5 * length); + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + (length2 / 2.) - (0.5 * length)); + + qInfo()<<"Test item 1/3"; + start = start1 + (length1 / 3.0) - (0.5 * length); + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + (length2 / 3.0) - (0.5 * length)); + + qInfo()<<"Test item 2/3"; + start = start1 + (2.0 * length1 / 3.0) - (0.5 * length); + pos = ChartLayout::relativePosition(start1, length1, start2, length2, start, length); + QCOMPARE(pos, start2 + (2.0 * length2 / 3.0) - (0.5 * length)); +} + + +void TestChartLayout::testLayoutTop() +{ + QMap map; + + QVERIFY(!chartTitle->isVisible()); + map = layout->calculateLayout(chartTitle, true); + chartTitle->setVisible(true); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(chartTitle).top(), layout->margins().y()); + QCOMPARE(itemRect(plotArea).top(), itemRect(chartTitle).bottom() + layout->spacing().y()); + + map = layout->calculateLayout(chartTitle, false); + chartTitle->setVisible(false); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QCOMPARE(itemRect(plotArea), plotAreaRect); + QCOMPARE(itemRect(plotArea).top(), layout->margins().y()); + + map = layout->calculateLayout(chartSubTitle, true); + chartSubTitle->setVisible(true); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(chartSubTitle).top(), layout->margins().y()); + QCOMPARE(itemRect(plotArea).top(), itemRect(chartSubTitle).bottom() + layout->spacing().y()); + + map = layout->calculateLayout(chartSubTitle, false); + chartSubTitle->setVisible(false); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QCOMPARE(itemRect(plotArea), plotAreaRect); + QCOMPARE(itemRect(plotArea).top(), layout->margins().y()); + + map = layout->calculateLayout(chartTitle, true); + chartTitle->setVisible(true); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(chartTitle).top(), layout->margins().y()); + QCOMPARE(itemRect(plotArea).top(), itemRect(chartTitle).bottom() + layout->spacing().y()); + + map = layout->calculateLayout(chartSubTitle, true); + chartSubTitle->setVisible(true); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(chartSubTitle).top(), itemRect(chartTitle).bottom() + layout->spacing().y()); + QCOMPARE(itemRect(plotArea).top(), itemRect(chartSubTitle).bottom() + layout->spacing().y()); + + map = layout->calculateLayout(chartTitle, false); + chartTitle->setVisible(false); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(chartSubTitle).top(), layout->margins().y()); + QCOMPARE(itemRect(plotArea).top(), itemRect(chartSubTitle).bottom() + layout->spacing().y()); + + map = layout->calculateLayout(chartSubTitle, false); + chartSubTitle->setVisible(false); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(!chartSubTitle->isVisible()); + QCOMPARE(itemRect(plotArea), plotAreaRect); + QCOMPARE(itemRect(plotArea).top(), layout->margins().y()); +} + +void TestChartLayout::testLayoutBottom() +{ + QMap map; + + // hide xAxisTitle + map = layout->calculateLayout(xAxisTitle, false); + xAxisTitle->setVisible(false); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(plotArea).height(), plotAreaRect.height() + xAxisTitleRect.height() + layout->spacing().y()); + + // show xAxisTitle + map = layout->calculateLayout(xAxisTitle, true); + xAxisTitle->setVisible(true); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QCOMPARE(itemRect(plotArea), plotAreaRect); + QCOMPARE(itemRect(plotArea).bottom(), itemRect(xAxisTitle).top() - layout->spacing().y()); + + // show both chartFooter and xAxisTitle + QVERIFY(!chartFooter->isVisible()); + map = layout->calculateLayout(chartFooter, true); + chartFooter->setVisible(true); + updateItems(map); + QVERIFY(map.contains(plotArea)); + QVERIFY(map.contains(xAxisTitle)); + QVERIFY(map.contains(yAxisTitle)); + QVERIFY(map.contains(legend)); + QCOMPARE(itemRect(chartFooter).bottom(), chart->size().height() - layout->margins().y()); + QCOMPARE(itemRect(xAxisTitle).bottom(), itemRect(chartFooter).top() - layout->spacing().y()); + QCOMPARE(itemRect(plotArea).bottom(), itemRect(xAxisTitle).top() - layout->spacing().y()); + + // hide xAxisTitle + map = layout->calculateLayout(xAxisTitle, false); + xAxisTitle->setVisible(false); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(chartFooter).bottom(), chart->size().height() - layout->margins().y()); + QCOMPARE(itemRect(plotArea).bottom(), itemRect(chartFooter).top() - layout->spacing().y()); + + // set footer visible to give space to yAxis tests below + if (!chartFooter->isVisible()) { + map = layout->calculateLayout(chartFooter, true); + chartFooter->setVisible(true); + updateItems(map); + } + if (!xAxisTitle->isVisible()) { + map = layout->calculateLayout(xAxisTitle, true); + xAxisTitle->setVisible(true); + updateItems(map); + } + QCOMPARE(itemRect(chartFooter).bottom(), chart->size().height() - layout->margins().y()); + QCOMPARE(itemRect(xAxisTitle).bottom(), itemRect(chartFooter).top() - layout->spacing().y()); + QCOMPARE(itemRect(plotArea).bottom(), itemRect(xAxisTitle).top() - layout->spacing().y()); + + plotAreaRect = itemRect(plotArea); + + // align y title bottom edge with top edge of plot area + yAxisTitle->setSize(QSizeF(15., yAxisTitle->size().height())); // to make it fit left of plot area + QPointF pos = xAxisTitleRect.topLeft(); + pos.setY(plotAreaRect.top() - itemRect(yAxisTitle).height()); + setItemPosition(yAxisTitle, pos); + QCOMPARE(itemRect(yAxisTitle).bottom(), plotAreaRect.top()); + + // remove chart xAxisTitle to get plot area resized (but not moved) + map = layout->calculateLayout(xAxisTitle, false); + QVERIFY(map.contains(plotArea)); + QVERIFY2(!map.contains(yAxisTitle), "yAxis title shall not be moved"); + QVERIFY(!map.contains(xAxisTitle)); + + // move y title bottom edge above top of plot area + pos = yAxisTitleRect.topLeft(); + pos.setY(plotAreaRect.top() - itemRect(yAxisTitle).height() - 1.); + setItemPosition(yAxisTitle, pos); + QCOMPARE(itemRect(yAxisTitle).bottom(), plotAreaRect.top() - 1.); + + // remove chart xAxisTitle to get plot area resized (but not moved) + map = layout->calculateLayout(xAxisTitle, false); + QVERIFY(map.contains(plotArea)); + QVERIFY2(!map.contains(yAxisTitle), "yAxis title shall not be moved"); + QVERIFY(!map.contains(xAxisTitle)); + + // align y title bottom edge with bottom edge of plot area + pos = yAxisTitleRect.topLeft(); + pos.setY(plotAreaRect.bottom() - itemRect(yAxisTitle).height()); + setItemPosition(yAxisTitle, pos); + QCOMPARE(itemRect(yAxisTitle).bottom(), plotAreaRect.bottom()); + + // remove chart xAxisTitle to get plot area resized (but not moved) + map = layout->calculateLayout(xAxisTitle, false); + QVERIFY(map.contains(plotArea)); + QVERIFY2(map.contains(yAxisTitle), "yAxis title shall be moved"); + updateItems(map); + QCOMPARE(itemRect(yAxisTitle).bottom(), itemRect(plotArea).bottom()); + QVERIFY(!map.contains(xAxisTitle)); + + // move y title bottom edge below bottom edge of plot area + pos = itemRect(yAxisTitle).topLeft(); + pos.setY(itemRect(plotArea).bottom() - itemRect(yAxisTitle).height() + 1.); + setItemPosition(yAxisTitle, pos); + QCOMPARE(itemRect(yAxisTitle).bottom(), itemRect(plotArea).bottom() + 1.); + + // insert chart xAxisTitle to get plot area resized (but not moved) + xAxisTitle->setVisible(false); + map = layout->calculateLayout(xAxisTitle, true); + xAxisTitle->setVisible(true); + updateItems(map); + QVERIFY(map.contains(plotArea)); + QVERIFY2(map.contains(yAxisTitle), "yAxis title shall be moved"); + QCOMPARE(itemRect(yAxisTitle).bottom(), itemRect(plotArea).bottom() + 1.); +} + +void TestChartLayout::testLayoutStart() +{ + QMap map; + + map = layout->calculateLayout(yAxisTitle, false); + yAxisTitle->setVisible(false); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(plotArea).left(), layout->margins().x()); + QCOMPARE(itemRect(plotArea).left(), layout->margins().x()); + + map = layout->calculateLayout(yAxisTitle, true); + yAxisTitle->setVisible(true); + updateItems(map); + QCOMPARE(itemRect(plotArea), plotAreaRect); + QCOMPARE(itemRect(plotArea).left(), itemRect(yAxisTitle).right() + layout->spacing().x()); + QCOMPARE(itemRect(yAxisTitle).left(), layout->margins().x()); + QCOMPARE(itemRect(plotArea).left(), itemRect(yAxisTitle).right() + layout->spacing().x()); +} + +void TestChartLayout::testLayoutEnd() +{ + QMap map; + + map = layout->calculateLayout(legend, false); + legend->setVisible(false); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QVERIFY(itemRect(plotArea) != plotAreaRect); + QCOMPARE(itemRect(plotArea).right(), chart->size().width() - layout->margins().x()); + + map = layout->calculateLayout(legend, true); + legend->setVisible(true); + QVERIFY(map.contains(plotArea)); + updateItems(map); + QCOMPARE(itemRect(legend).right(), chart->size().width() - layout->margins().x()); + QCOMPARE(itemRect(plotArea).right(), itemRect(legend).left() - layout->spacing().x()); + switch(legend->alignment()) { + case Qt::AlignLeft: + QCOMPARE(itemRect(legend).top(), itemRect(plotArea).top()); + break; + case Qt::AlignRight: + QCOMPARE(itemRect(legend).bottom(), itemRect(plotArea).bottom()); + break; + case Qt::AlignCenter: + // TODO + break; + default: + QCOMPARE(itemRect(legend), legendRect); // should not have moved + break; + } + + // align x title right edge with left edge of plot area + xAxisTitle->setSize(QSizeF(15., xAxisTitle->size().height())); // to make it fit left of plot area + QPointF pos = xAxisTitleRect.topLeft(); + pos.setX(plotAreaRect.left() - itemRect(xAxisTitle).width()); + setItemPosition(xAxisTitle, pos); + QCOMPARE(itemRect(xAxisTitle).right(), plotAreaRect.left()); + + // remove chart legend to get plot area resized (but not moved) + map = layout->calculateLayout(legend, false); + QVERIFY(map.contains(plotArea)); + QVERIFY2(!map.contains(xAxisTitle), "xAxis title shall not be moved"); + QVERIFY(!map.contains(legend)); + + // move x title right edge left of left edge of plot area + pos = xAxisTitleRect.topLeft(); + pos.setX(plotAreaRect.left() - itemRect(xAxisTitle).width() - 1.); + setItemPosition(xAxisTitle, pos); + QCOMPARE(itemRect(xAxisTitle).right(), plotAreaRect.left() - 1.); + + // remove chart legend to get plot area resized (but not moved) + map = layout->calculateLayout(legend, false); + QVERIFY(map.contains(plotArea)); + QVERIFY2(!map.contains(xAxisTitle), "xAxis title shall not be moved"); + QVERIFY(!map.contains(legend)); + + // align x title left edge with right edge of plot area + pos = xAxisTitleRect.topLeft(); + pos.setX(plotAreaRect.right()); + setItemPosition(xAxisTitle, pos); + QCOMPARE(itemRect(xAxisTitle).left(), plotAreaRect.right()); + + // remove chart legend to get plot area resized (but not moved) + map = layout->calculateLayout(legend, false); + QVERIFY(map.contains(plotArea)); + QVERIFY2(map.contains(xAxisTitle), "xAxis title shall be moved"); + QCOMPARE(map[xAxisTitle].left(), map[plotArea].right()); + QVERIFY(!map.contains(legend)); + + // move x title left edge to the right of right edge of plot area + pos = xAxisTitleRect.topLeft(); + pos.setX(plotAreaRect.right() + 1.); + setItemPosition(xAxisTitle, pos); + QCOMPARE(itemRect(xAxisTitle).left(), plotAreaRect.right() + 1.); + + // remove chart legend to get plot area resized (but not moved) + map = layout->calculateLayout(legend, false); + QVERIFY(map.contains(plotArea)); + QVERIFY2(map.contains(xAxisTitle), "xAxis title shall be moved"); + QCOMPARE(map[xAxisTitle].left(), map[plotArea].right() + 1.); + QVERIFY(!map.contains(legend)); + + QVERIFY(legend->isVisible()); + QCOMPARE(legend->alignment(), Qt::AlignCenter); + + map = layout->calculateLayout(legend, false); + QVERIFY(map.contains(plotArea)); + QVERIFY(map.contains(xAxisTitle)); + QCOMPARE(map.count(), 2); + + // test alignment + legend->setVisible(false); + layout->setAutoLayoutEnabled(true); + layout->scheduleRelayout(); + layout->layout(); + layout->setAutoLayoutEnabled(true); + + legend->setAlignment(Qt::AlignLeft); + map = layout->calculateLayout(legend, true); + updateItems(map); + QCOMPARE(itemRect(plotArea).top(), itemRect(legend).top()); + + legend->setAlignment(Qt::AlignCenter); + map = layout->calculateLayout(legend, true); + updateItems(map); + QCOMPARE(itemRect(plotArea).top() + (0.5 * itemRect(plotArea).height()), itemRect(legend).top() + (0.5 * itemRect(legend).height())); + + legend->setAlignment(Qt::AlignRight); + map = layout->calculateLayout(legend, true); + updateItems(map); + QCOMPARE(itemRect(plotArea).bottom(), itemRect(legend).bottom()); +} + +void TestChartLayout::testLayoutTopStart() +{ + QEXPECT_FAIL("", "Re-layout in TopStart area not implemented", Continue); + QVERIFY(false); +} + +void TestChartLayout::testLayoutTopEnd() +{ + QEXPECT_FAIL("", "Re-layout in TopEnd area not implemented", Continue); + QVERIFY(false); +} + +void TestChartLayout::testLayoutBottomStart() +{ + QEXPECT_FAIL("", "Re-layout in BottomStart area not implemented", Continue); + QVERIFY(false); +} + +void TestChartLayout::testLayoutBottomEnd() +{ + QEXPECT_FAIL("", "Re-layout in BottomEnd area not implemented", Continue); + QVERIFY(false); +} + +QTEST_MAIN(TestChartLayout) diff --git a/plugins/chartshape/tests/TestChartLayout.h b/plugins/chartshape/tests/TestChartLayout.h new file mode 100644 index 00000000000..90a4ff3d67e --- /dev/null +++ b/plugins/chartshape/tests/TestChartLayout.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + * + * 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. + */ + +#ifndef KCHART_TESTCHARTLAYOUT_H +#define KCHART_TESTCHARTLAYOUT_H + +#include +#include + +class KoShape; + +class KoDocumentResourceManager; + +namespace KoChart { + class ChartShape; + class ChartLayout; + class PlotArea; + class Legend; +} + +class TestChartLayout : public QObject +{ + Q_OBJECT +public: + TestChartLayout(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testRelativePositioning(); + + void testLayoutTop(); + void testLayoutBottom(); + void testLayoutStart(); + void testLayoutEnd(); + + void testLayoutTopStart(); + void testLayoutTopEnd(); + void testLayoutBottomStart(); + void testLayoutBottomEnd(); + +private: + QtMessageHandler msgHandler; + KoDocumentResourceManager *drm; + KoChart::ChartShape *chart; + KoChart::ChartLayout *layout; + KoShape *chartTitle; + QRectF chartTitleRect; + KoShape *chartSubTitle; + QRectF chartSubTitleRect; + KoShape *chartFooter; + QRectF chartFooterRect; + KoChart::PlotArea *plotArea; + QRectF plotAreaRect; + KoChart::Legend *legend; + QRectF legendRect; + KoShape *xAxisTitle; + QRectF xAxisTitleRect; + KoShape *yAxisTitle; + QRectF yAxisTitleRect; + KoShape *secondaryXAxisTitle; + QRectF secondaryXAxisTitleRect; + KoShape *secondaryYAxisTitle; + QRectF secondaryYAxisTitleRect; +}; + +#endif // KCHART_TESTCHARTLAYOUT_H