File indexing completed on 2024-05-12 16:33:28

0001 /* This file is part of the KDE project
0002 
0003    Copyright 2007 Johannes Simon <johannes.simon@gmail.com>
0004    Copyright 2009 Inge Wallin    <inge@lysator.liu.se>
0005 
0006    This library is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU Library General Public
0008    License as published by the Free Software Foundation; either
0009    version 2 of the License, or (at your option) any later version.
0010 
0011    This library is distributed in the hope that it will be useful,
0012    but WITHOUT ANY WARRANTY; without even the implied warranty of
0013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014    Library General Public License for more details.
0015 
0016    You should have received a copy of the GNU Library General Public License
0017    along with this library; see the file COPYING.LIB.  If not, write to
0018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019    Boston, MA 02110-1301, USA.
0020 */
0021 
0022 
0023 // Own
0024 #include "ChartProxyModel.h"
0025 #include "PlotArea.h"
0026 
0027 // Qt
0028 #include <QRegion>
0029 #include <QPoint>
0030 
0031 // Calligra
0032 #include <KoXmlReader.h>
0033 #include <KoShapeLoadingContext.h>
0034 #include <KoShapeSavingContext.h>
0035 #include <KoStyleStack.h>
0036 #include <KoXmlNS.h>
0037 #include <KoOdfLoadingContext.h>
0038 #include <KoOdfStylesReader.h>
0039 #include <KoOdfGraphicStyles.h>
0040 #include <interfaces/KoChartModel.h>
0041 
0042 // KoChart
0043 #include "Axis.h"
0044 #include "DataSet.h"
0045 #include "TableSource.h"
0046 #include "OdfLoadingHelper.h"
0047 #include "ChartTableModel.h"
0048 #include "ChartDebug.h"
0049 
0050 // Other
0051 #include <algorithm>
0052 
0053 using namespace KoChart;
0054 
0055 
0056 // ================================================================
0057 //                     Class ChartProxyModel::Private
0058 
0059 
0060 class ChartProxyModel::Private {
0061 public:
0062     Private(ChartProxyModel *parent, ChartShape *shape, TableSource *source);
0063     ~Private();
0064 
0065     ChartProxyModel *const q;
0066     ChartShape *const shape;
0067     TableSource *const tableSource;
0068 
0069     /// Set to true if we're in the process of loading data from ODF.
0070     /// Used to avoid repeatedly updating data.
0071     bool isLoading;
0072 
0073     bool manualControl;
0074 
0075     bool             firstRowIsLabel;
0076     bool             firstColumnIsLabel;
0077     Qt::Orientation  dataDirection;
0078     int              dataDimensions;
0079 
0080     CellRegion categoryDataRegion;
0081 
0082     QVector< CellRegion > dataSetRegions;
0083 
0084     QList<DataSet*>  dataSets;
0085     QList<DataSet*>  removedDataSets;
0086 
0087     CellRegion       selection;
0088 
0089     /**
0090      * Discards old and creates new data sets from the current region selection.
0091      */
0092     void rebuildDataMap();
0093 
0094     /**
0095      * Extracts a list of data sets (with x data region, y data region, etc.
0096      * assigned) from the current d->selection.
0097      *
0098      * Unless the list *dataSetsToRecycle is empty, it will reuse as many
0099      * DataSet instances from there as possible and remove them from the list.
0100      *
0101      * As a side effect, this method sets d->categoryDataRegion if
0102      * overrideCategories is true.
0103      */
0104     QList<DataSet*> createDataSetsFromRegion(QList<DataSet*> *dataSetsToRecycle,
0105                                               bool overrideCategories = true);
0106 };
0107 
0108 ChartProxyModel::Private::Private(ChartProxyModel *parent, ChartShape *shape, TableSource *source)
0109     : q(parent)
0110     , shape(shape)
0111     , tableSource(source)
0112     , isLoading(false)
0113     , manualControl(false)
0114 {
0115     firstRowIsLabel    = false;
0116     firstColumnIsLabel = false;
0117     dataDimensions     = 1;
0118 
0119     // Determines what orientation the data points in a data series
0120     // have when multiple data sets are created from one source
0121     // region. For example, vertical means that each column in the source
0122     // region is assigned to one data series.
0123     // Default to Qt::Vertical, as that's what OOo does also.
0124     dataDirection      = Qt::Vertical;
0125 }
0126 
0127 ChartProxyModel::Private::~Private()
0128 {
0129     qDeleteAll(dataSets);
0130     qDeleteAll(removedDataSets);
0131 }
0132 
0133 
0134 // ================================================================
0135 //                          Class ChartProxyModel
0136 
0137 
0138 ChartProxyModel::ChartProxyModel(ChartShape *shape, TableSource *source)
0139     : QAbstractTableModel(),
0140       d(new Private(this, shape, source))
0141 {
0142     connect(source, SIGNAL(tableAdded(Table*)),
0143             this,   SLOT(addTable(Table*)));
0144     connect(source, SIGNAL(tableRemoved(Table*)),
0145             this,   SLOT(removeTable(Table*)));
0146 }
0147 
0148 ChartProxyModel::~ChartProxyModel()
0149 {
0150     delete d;
0151 }
0152 
0153 void ChartProxyModel::reset(const CellRegion& region)
0154 {
0155     d->selection = region;
0156     d->rebuildDataMap();
0157 }
0158 
0159 CellRegion ChartProxyModel::cellRangeAddress() const
0160 {
0161     return d->selection;
0162 }
0163 
0164 void ChartProxyModel::Private::rebuildDataMap()
0165 {
0166     if (manualControl) {
0167         return;
0168     }
0169     // This was intended to speed up the loading process, by executing this
0170     // method only once in endLoading(), however the approach is actually
0171     // incorrect as it would potentially override a data set's regions
0172     // set by "somebody" else in the meantime.
0173     // if (isLoading)
0174     //     return;
0175     q->beginResetModel();
0176     q->invalidateDataSets();
0177     dataSets = createDataSetsFromRegion(&removedDataSets);
0178     q->endResetModel();
0179 }
0180 
0181 void ChartProxyModel::addTable(Table *table)
0182 {
0183     QAbstractItemModel *model = table->model();
0184     connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0185             this,  SLOT(dataChanged(QModelIndex,QModelIndex)));
0186 }
0187 
0188 void ChartProxyModel::removeTable(Table *table)
0189 {
0190     QAbstractItemModel *model = table->model();
0191     model->disconnect(this);
0192 }
0193 
0194 /**
0195  * Returns a row of a given region (i.e. a part of it with height 1), cutting
0196  * off the first @a colOffset cells in that row.
0197  *
0198  * Examples: extractRow(A1:C2, 0, 0) --> A1:C1
0199  *           extractRow(A1:C2, 1, 0) --> A2:C2
0200  *           extractRow(A1:C2, 0, 1) --> B1:C1
0201  *
0202  * See notes in createDataSetsFromRegion() for further details.
0203  *
0204  * @param region The region to extract the row from
0205  * @param row The number of the row, starting with 0
0206  * @param colOffset How many of the first columns to cut from the resulting row
0207  */
0208 static QVector<QRect> extractRow(const QVector<QRect> &rects, int colOffset, bool extractLabel)
0209 {
0210     if (colOffset == 0)
0211         return extractLabel ? QVector<QRect>() : rects;
0212     QVector<QRect> result;
0213     foreach(const QRect &rect, rects) {
0214         if (extractLabel) {
0215             QRect r(rect.topLeft(), QSize(colOffset, rect.height()));
0216             result.append(r);
0217         }
0218         else {
0219 //Q_ASSERT(rect.width() > colOffset);
0220             if (rect.width() > colOffset) {
0221                 QPoint topLeft = rect.topLeft() + QPoint(colOffset, 0);
0222                 QRect r(topLeft, QSize(rect.width() - colOffset, rect.height()));
0223                 result.append(r);
0224             }
0225         }
0226     }
0227     return result;
0228 }
0229 
0230 /**
0231  * Returns a column of a given region, cutting off the first @a rowOffset
0232  * rows in that column.
0233  *
0234  * Examples: extractColumn(A1:C2, 0, 0)       --> A1:A2
0235  *           extractColumn(A1:C2;D1;F2, 0, 0) --> A1:A2;D1:D2
0236  *
0237  * See notes in createDataSetsFromRegion() for further details.
0238  *
0239  * @param region The region to extract the row from
0240  * @param col The number of the column, starting with 0
0241  * @param rowOffset How many of the first rows to cut from the resulting column
0242  */
0243 static QVector<QRect> extractColumn(const QVector<QRect> &rects, int rowOffset, bool extractLabel)
0244 {
0245     if (rowOffset == 0)
0246         return extractLabel ? QVector<QRect>() : rects;
0247     QVector<QRect> result;
0248     foreach(const QRect &rect, rects) {
0249         if (extractLabel) {
0250             QRect r(rect.topLeft(), QSize(rect.width(), rowOffset));
0251             result.append(r);
0252         }
0253         else {
0254 //Q_ASSERT(rect.height() > rowOffset);
0255             if (rect.height() > rowOffset) {
0256                 QPoint topLeft = rect.topLeft() + QPoint(0, rowOffset);
0257                 QRect r(topLeft, QSize(rect.width(), rect.height() - rowOffset));
0258                 result.append(r);
0259             }
0260         }
0261     }
0262     return result;
0263 }
0264 
0265 /**
0266  * Returns a according to the dataDirection sorted data-region.
0267  *
0268  * First prepare the dataRegions to have them
0269  * - proper sorted either from left-to-right if the dataDirection is
0270  *   Qt::Horizontal or from top-to-bottom when Qt::Vertical.
0271  * - have multiple dimensions proper split-up into single rows/columns
0272  *   to produce one DataSet per row/column.
0273  *
0274  * The resulting map is build up depending on the dataDirection;
0275  * - Qt::Horizontal: key is the row and values are the columns in the row.
0276  * - Qt::Vertical: key is the column and values are the rows in the column.
0277  *
0278  * @param dataDirection The direction of the data. This could be either
0279  * dataDirection == Qt::Horizontal or dataDirection == Qt::Vertical.
0280  * @param dataRegions The unsorted list of data-regions.
0281  * @return Compared to the QVector that goes in the resulting map makes
0282  * following sure;
0283  * - sorted according to the dataDirection either by row or column
0284  * - duplicate definitions are removed/compressed.
0285  * - definitions or multiple rows and columns in one QRect are split
0286  *   in rows/columns what makes sure we earn one dataRegion per
0287  *   row/column. This is needed so for example the labelRegion and
0288  *   all other things operate on exactly one row/column.
0289  */
0290 QMap<int, QVector<QRect> > sortDataRegions(Qt::Orientation dataDirection, QVector<QRect> dataRegions)
0291 {
0292     QMap<int, QVector<QRect> >  sortedDataRegions;
0293     if (dataDirection == Qt::Horizontal) {
0294         // Split up region in horizontal rectangles that are sorted from top to bottom
0295         QMap<int, QVector<QRect> >  rows;
0296         foreach (const QRect &rect, dataRegions) {
0297             int x = rect.topLeft().x();
0298             for (int y = rect.topLeft().y(); y <= rect.bottomLeft().y(); y++) {
0299                 QRect dataRect = QRect(QPoint(x, y), QSize(rect.width(), 1));
0300                 if (!rows.contains(y))
0301                     rows.insert(y, QVector<QRect>());
0302                 rows[y].append(dataRect);
0303             }
0304         }
0305 
0306         // Sort rectangles in each row from left to right.
0307         QMapIterator<int, QVector<QRect> >  i(rows);
0308         while (i.hasNext()) {
0309             i.next();
0310             int             row = i.key();
0311             QVector<QRect>  unsortedRects = i.value();
0312             QVector<QRect>  sortedRects;
0313             foreach (const QRect &rect, unsortedRects) {
0314                 int index;
0315                 for (index = 0; index < sortedRects.size(); index++)
0316                     if (rect.topLeft().x() <= sortedRects[index].topLeft().x())
0317                         break;
0318                 sortedRects.insert(index, rect);
0319             }
0320             sortedDataRegions.insert(row, sortedRects);
0321         }
0322     } else {
0323         // Split up region in horizontal rectangles that are sorted from top to bottom
0324         QMap<int, QVector<QRect> >  columns;
0325         foreach (const QRect &rect, dataRegions) {
0326             int y = rect.topLeft().y();
0327             for (int x = rect.topLeft().x(); x <= rect.topRight().x(); ++x) {
0328                 QRect dataRect = QRect(QPoint(x, y), QSize(1, rect.height()));
0329                 if (!columns.contains(x))
0330                     columns.insert(x, QVector<QRect>());
0331                 columns[x].append(dataRect);
0332             }
0333         }
0334 
0335         // Sort rectangles in each column from top to bottom
0336         QMapIterator<int, QVector<QRect> >  i(columns);
0337         while (i.hasNext()) {
0338             i.next();
0339             int             col = i.key();
0340             QVector<QRect>  unsortedRects = i.value();
0341             QVector<QRect>  sortedRects;
0342             foreach (const QRect &rect, unsortedRects) {
0343                 int index;
0344                 for (index = 0; index < sortedRects.size(); ++index)
0345                     if (rect.topLeft().y() <= sortedRects[index].topLeft().y())
0346                         break;
0347                 sortedRects.insert(index, rect);
0348             }
0349             sortedDataRegions.insert(col, sortedRects);
0350         }
0351     }
0352     return sortedDataRegions;
0353 }
0354 
0355 QList<DataSet*> ChartProxyModel::Private::createDataSetsFromRegion(QList<DataSet*> *dataSetsToRecycle,
0356                                                                     bool overrideCategories)
0357 {
0358     if (manualControl) {
0359         return dataSets;
0360     }
0361     if (!selection.isValid())
0362         return QList<DataSet*>();
0363 
0364     // First prepare the dataRegions to have them
0365     // 1) proper sorted either from left-to-right if the dataDirection is
0366     //    Qt::Horizontal or from top-to-bottom when Qt::Vertical.
0367     // 2) have multiple dimensions proper split-up into single rows/columns
0368     //    to produce one DataSet per row/column.
0369     //
0370     // The resulting map is build up depending on the dataDirection;
0371     // - Qt::Horizontal: key is the row and values are the columns in the row.
0372     // - Qt::Vertical: key is the column and values are the rows in the column.
0373     QMap<int, QVector<QRect> > sortedDataRegions = sortDataRegions(dataDirection, selection.rects());
0374 
0375     // In the end, the contents of this list will look something like this:
0376     // (Category-Data, X-Data, Y-Data, Y-Data, Y-Data)
0377     // Semantic separation of the regions will follow later.
0378     QList<CellRegion> dataRegions;
0379     // This region exclusively contains (global) data set labels, i.e.
0380     // one label per data set (thus in opposite data direction)
0381     QList<CellRegion> labelRegions;
0382 
0383     // Fill dataRegions and set categoryRegion.
0384     // Note that here, we don't exactly know yet what region will be used for
0385     // what data set, we also don't know yet what data these regions contain.
0386     int rowOffset = firstRowIsLabel ? 1 : 0;
0387     int colOffset = firstColumnIsLabel ? 1 : 0;
0388 
0389     // Determines how many individual rows/columns will be assigned per data set.
0390     // It is at least one, but if there's more than one data dimension, the x
0391     // data is shared among all data sets, thus - 1.
0392     int regionsPerDataSet = qMax(1, dataDimensions - 1);
0393 
0394     // Determinate the number of rows and maximum number of columns used.
0395     // FIXME this logic is rather buggy. We better should use our sortedDataRegions
0396     //       here but that would need some testing for regression in e.g.
0397     //       bubble-charts which I don't had the time to do. So, leaving the logic as
0398     //       it was before for now. If you run into problems with bubble charts and
0399     //       there x-values then this foreach-logic here is very likely the reason.
0400     int rows = 0;
0401     int cols = 0;
0402     foreach(const QRect &rect, selection.rects()) {
0403         rows += rect.height();
0404         cols = qMax(cols, rect.width());
0405     }
0406 
0407     bool extractXData = dataDimensions > 1 &&
0408                         // Checks if the remaining data regions would fit exactly to the
0409                         // remaining data sets. If not, skip x data. This is only the case
0410                         // for bubble charts, (the only case of regionsPerDataSet == 2), so
0411                         // skipping x data will allow the last data set to also be assigned
0412                         // a bubble width region. This is exactly what OOo does.
0413                         (dataDirection == Qt::Horizontal ? rows - 1 - rowOffset
0414                                                          : cols - 1 -colOffset) % regionsPerDataSet == 0;
0415 
0416     // When x data is present, it occupies the first non-header row/column
0417     if (extractXData && dataDirection == Qt::Horizontal)
0418         ++rowOffset;
0419     if (extractXData && dataDirection == Qt::Vertical)
0420         ++colOffset;
0421 
0422     // This is the logic that extracts all the subregions from selection
0423     // that are later used for the data sets
0424     Table *internalTable = shape ? shape->tableSource()->get(shape->internalModel()) : 0;
0425     QList<int> sortedDataKeys = sortedDataRegions.keys();
0426     std::sort(sortedDataKeys.begin(), sortedDataKeys.end());
0427     foreach(int key, sortedDataKeys) {
0428         QVector<QRect> rects = sortedDataRegions[key];
0429         QVector<QRect> dataRects;
0430         CellRegion labelRegion;
0431         if (dataDirection == Qt::Horizontal) {
0432             if (firstColumnIsLabel) {
0433                 QVector<QRect> labelRects = extractRow(rects, colOffset, true);
0434                 labelRegion = labelRects.isEmpty() ? CellRegion() : CellRegion(selection.table(), labelRects);
0435             }
0436             else {
0437                 labelRegion = internalTable ? CellRegion(internalTable, QPoint(1, key)) : CellRegion();
0438             }
0439             dataRects = extractRow(rects, colOffset, false);
0440         }
0441         else {
0442             if (firstRowIsLabel) {
0443                 QVector<QRect> labelRects = extractColumn(rects, rowOffset, true);
0444                 labelRegion = labelRects.isEmpty() ? CellRegion() : CellRegion(selection.table(), labelRects);
0445             }
0446             else {
0447                 labelRegion = internalTable ? CellRegion(internalTable, QPoint(key, 1)) : CellRegion();
0448             }
0449             dataRects = extractColumn(rects, rowOffset, false);
0450         }
0451 
0452         labelRegions.append(labelRegion);
0453         dataRegions.append(dataRects.isEmpty() ? CellRegion() : CellRegion(selection.table(), dataRects));
0454     }
0455 
0456     bool useCategories =
0457             (dataDirection == Qt::Horizontal && firstRowIsLabel) ||
0458             (dataDirection == Qt::Vertical && firstColumnIsLabel);
0459 
0460     /*
0461     debugChart << "selection=" << selection.toString();
0462     debugChart << "dataDirection=" << (dataDirection == Qt::Horizontal ? "Horizontal" : "Vertical");
0463     debugChart << "firstRowIsLabel=" << firstRowIsLabel;
0464     debugChart << "firstColumnIsLabel=" << firstColumnIsLabel;
0465     debugChart << "overrideCategories=" << overrideCategories;
0466     debugChart << "useCategories=" << useCategories;
0467     debugChart << "dataRegions.count()="<<dataRegions.count();
0468     */
0469 
0470     // Regions shared by all data sets: categories and x-data
0471 
0472     //FIXME don't use overrideCategories for the categoryDataRegion's but for
0473     //the categoryLabelRegion (which we need to introduce and use).
0474 if(overrideCategories) categoryDataRegion = CellRegion();
0475 
0476     if (!dataRegions.isEmpty()) {
0477 // Q_ASSERT(label.isValid());
0478 // Q_ASSERT(data.isValid());
0479         if (useCategories) {
0480             labelRegions.takeFirst();
0481             categoryDataRegion = dataRegions.takeFirst();
0482         }
0483     }
0484 
0485     CellRegion xData;
0486     if (!dataRegions.isEmpty() && extractXData) {
0487         labelRegions.removeFirst();
0488         xData = dataRegions.takeFirst();
0489     }
0490 
0491     QList<DataSet*> createdDataSets;
0492     int dataSetNumber = 0;
0493     // Now assign all dataRegions to a number of data sets.
0494     // Here they're semantically separated into x data, y data, etc.
0495     Q_ASSERT(dataRegions.count() == labelRegions.count());
0496     while (!dataRegions.isEmpty()) {
0497         // Get a data set instance we can use
0498         DataSet *dataSet;
0499         if (!dataSetsToRecycle->isEmpty())
0500             dataSet = dataSetsToRecycle->takeFirst();
0501         else
0502             dataSet = new DataSet(dataSetNumber);
0503 
0504         // category and x data are "global" regions shared among all data sets
0505         dataSet->setCategoryDataRegion(categoryDataRegion);
0506         dataSet->setXDataRegion(xData);
0507 
0508         // the name-value used for e.g. the legend label
0509         dataSet->setLabelDataRegion(labelRegions.takeFirst());
0510 
0511         // the range for y-values
0512         dataSet->setYDataRegion(dataRegions.takeFirst());
0513 
0514         // the custom data (e.g. bubble width)
0515         if (!dataRegions.isEmpty() && dataDimensions > 2)
0516             dataSet->setCustomDataRegion(dataRegions.takeFirst());
0517         else
0518             dataSet->setCustomDataRegion(CellRegion());
0519 
0520         /*
0521         debugChart << "xDataRegion=" << dataSet->xDataRegion().toString();
0522         debugChart << "yDataRegion=" << dataSet->yDataRegion().toString();
0523         debugChart << "categoryDataRegion=" << dataSet->categoryDataRegion().toString();
0524         debugChart << "labelDataRegion=" << dataSet->labelDataRegion().toString();
0525         debugChart << "customDataRegion=" << dataSet->customDataRegion().toString();
0526         */
0527 
0528         createdDataSets.append(dataSet);
0529 
0530         // Increment number at the very end!
0531         dataSetNumber++;
0532     }
0533     return createdDataSets;
0534 }
0535 
0536 void ChartProxyModel::saveOdf(KoShapeSavingContext &context) const
0537 {
0538     int dataSetCount = rowCount();
0539     if (d->shape->chartType() == StockChartType && d->shape->chartSubType() == HighLowCloseChartSubtype && dataSetCount > 3) {
0540         // When loading, stock chart subtype is determined by number of datasets (sigh)
0541         dataSetCount = 3;
0542     }
0543     for (int i = 0; i < dataSetCount; ++i) {
0544         d->dataSets.at(i)->saveOdf(context);
0545     }
0546 }
0547 
0548 // This loads the properties of the datasets (chart:series).
0549 // FIXME: This is a strange place to load them (the proxy model)
0550 bool ChartProxyModel::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context, ChartType type)
0551 {
0552     Q_ASSERT(d->isLoading);
0553 
0554 //     PlotArea* plotArea = dynamic_cast< PlotArea* >(QObject::parent());
0555 
0556 //     const bool stockChart = element.attributeNS(KoXmlNS::chart, "class", QString()) == "chart:stock";
0557 
0558     OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId);
0559     bool ignoreCellRanges = helper->chartUsesInternalModelOnly;
0560 // Some OOo documents save incorrect cell ranges. For those this fix was intended.
0561 // Find out which documents exactly and only use fix for as few cases as possible.
0562 #if 0
0563     // If we exclusively use the chart's internal model then all data
0564     // is taken from there and each data set is automatically assigned
0565     // the rows it belongs to.
0566     bool ignoreCellRanges = helper->chartUsesInternalModelOnly;
0567 #endif
0568 
0569     beginResetModel();
0570 
0571     if (element.hasAttributeNS(KoXmlNS::chart, "style-name")) {
0572         KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0573         styleStack.clear();
0574         context.odfLoadingContext().fillStyleStack(element, KoXmlNS::chart, "style-name", "chart");
0575 
0576         // Data direction: It's in the plotarea style.
0577         if (styleStack.hasProperty(KoXmlNS::chart, "series-source")) {
0578             QString seriesSource = styleStack.property(KoXmlNS::chart, "series-source");
0579             // Check if the direction for data series is vertical or horizontal.
0580             if (seriesSource == "rows")
0581                 d->dataDirection = Qt::Horizontal;
0582             else if (seriesSource == "columns")
0583                 d->dataDirection = Qt::Vertical;
0584             // Otherwise leave our default value
0585         }
0586     }
0587 
0588     // Find out if the data table contains labels as first row and/or column.
0589     // This is in the plot-area element itself.
0590 
0591     // Do not ignore the data-source-has-labels in any case, even if a
0592     // category data region is specified for an axis, as the first
0593     // column still has to be excluded from the actual data region if
0594     // e.g. data-source-has-labels is set to "column" If an axis
0595     // contains the chart:categories element, the category data region
0596     // will automatically be set on every data set attached to that
0597     // axis. See Axis::attachDataSet().
0598     const QString dataSourceHasLabels
0599         = element.attributeNS(KoXmlNS::chart, "data-source-has-labels");
0600     if (dataSourceHasLabels == "both") {
0601         d->firstRowIsLabel = true;
0602         d->firstColumnIsLabel = true;
0603     } else if (dataSourceHasLabels == "row") {
0604         d->firstRowIsLabel = true;
0605         d->firstColumnIsLabel = false;
0606     } else if (dataSourceHasLabels == "column") {
0607         d->firstRowIsLabel = false;
0608         d->firstColumnIsLabel = true;
0609     } else {
0610         // dataSourceHasLabels == "none" or wrong value
0611         d->firstRowIsLabel = false;
0612         d->firstColumnIsLabel = false;
0613     }
0614 
0615     // For every dataset, there must be an explicit <chart:series> element,
0616     // which we will load later.
0617     d->dataSets.clear();
0618     d->removedDataSets.clear();
0619 
0620     // A cell range for all data is optional.
0621     // If cell ranges are in addition specified for one or more of these
0622     // data series, they'll be overwritten by the data loaded from the series
0623     if (!ignoreCellRanges
0624         && element.hasAttributeNS(KoXmlNS::table, "cell-range-address"))
0625     {
0626         QString cellRangeAddress = element.attributeNS(KoXmlNS::table, "cell-range-address");
0627         d->selection = CellRegion(d->tableSource, cellRangeAddress);
0628     // Otherwise use all data from internal table
0629     } else if (helper->chartUsesInternalModelOnly) {
0630         QList<Table*> tables = helper->tableSource->tableMap().values();
0631         Q_ASSERT(tables.count() == 1);
0632         Table *internalTable = tables.first();
0633         Q_ASSERT(internalTable->model());
0634         int rowCount = internalTable->model()->rowCount();
0635         int colCount = internalTable->model()->columnCount();
0636         d->selection = CellRegion(internalTable, QRect(1, 1, colCount, rowCount));
0637     }
0638 
0639     // This is what we'll use as basis for the data sets we "produce" from ODF.
0640     // This might be data sets that were "instantiated" from the internal
0641     // table or from an arbitrary selection of other tables as specified
0642     // in the PlotArea's table:cell-range-address attribute (parsed above).
0643     QList<DataSet*> createdDataSets = d->createDataSetsFromRegion(&d->removedDataSets,
0644                                                                    !helper->categoryRegionSpecifiedInXAxis);
0645 
0646     bool isBubble = d->shape->plotArea()->chartType() == BubbleChartType;
0647     bool isScatter = d->shape->plotArea()->chartType() == ScatterChartType;
0648     CellRegion prevXData, prevYData;
0649 
0650     int loadedDataSetCount = 0;
0651     KoXmlElement n;
0652     forEachElement (n, element) {
0653         if (n.namespaceURI() != KoXmlNS::chart) {
0654             continue;
0655         }
0656         if (n.localName() == "series") {
0657             DataSet *dataSet;
0658             if (loadedDataSetCount < createdDataSets.size()) {
0659                 dataSet = createdDataSets[loadedDataSetCount];
0660             } else {
0661                 // the datasetnumber needs to be known at construction time, to ensure
0662                 // default colors are set correctly
0663                 dataSet = new DataSet(d->dataSets.size());
0664             }
0665             dataSet->setChartType(type);
0666             d->dataSets.append(dataSet);
0667             if (d->categoryDataRegion.isValid()) {
0668                 dataSet->setCategoryDataRegion(d->categoryDataRegion);
0669             }
0670             dataSet->loadOdf(n, context);
0671 
0672             if (isBubble || isScatter) {
0673                 // bubble- and scatter-charts have chart:domain's that define the
0674                 // x- and y-data. But if they are not defined in the series then
0675                 // a previous defined one needs to be used.
0676                 // NOTE: This is not quite what odf specifies, but imho better (danders)
0677                 if (dataSet->xDataRegion().isValid()) {
0678                     prevXData = dataSet->xDataRegion();
0679                 }
0680                 else {
0681                     dataSet->setXDataRegion(prevXData);
0682                 }
0683                 if (isBubble) {
0684                     if (dataSet->yDataRegion().isValid()) {
0685                         prevYData = dataSet->yDataRegion();
0686                     }
0687                     else {
0688                         dataSet->setYDataRegion(prevYData);
0689                     }
0690                 }
0691             }
0692             ++loadedDataSetCount;
0693         }
0694     }
0695 
0696     //rebuildDataMap();
0697     endResetModel();
0698 
0699     return true;
0700 }
0701 
0702 
0703 QVariant ChartProxyModel::data(const QModelIndex &index,
0704                                 int role) const
0705 {
0706     Q_UNUSED(index);
0707     Q_UNUSED(role);
0708     Q_ASSERT("To be implemented");
0709     return QVariant();
0710 }
0711 
0712 void ChartProxyModel::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
0713 {
0714     QPoint topLeftPoint(topLeft.column() + 1, topLeft.row() + 1);
0715 
0716     // Excerpt from the Qt reference for QRect::bottomRight() which is
0717     // used for calculating bottomRight.  Note that for historical
0718     // reasons this function returns
0719     //   QPoint(left() + width() -1, top() + height() - 1).
0720     QPoint bottomRightPoint(bottomRight.column() + 1, bottomRight.row() + 1);
0721     QRect dataChangedRect = QRect(topLeftPoint,
0722                                    QSize(bottomRightPoint.x() - topLeftPoint.x() + 1,
0723                                           bottomRightPoint.y() - topLeftPoint.y() + 1));
0724     // Precisely determine what data in what table changed so that we don't
0725     // do unnecessary, expensive updates.
0726     Table *table = d->tableSource->get(topLeft.model());
0727     CellRegion dataChangedRegion(table, dataChangedRect);
0728 
0729     foreach (DataSet *dataSet, d->dataSets) {
0730         if (dataSet->xDataRegion().intersects(dataChangedRegion))
0731             dataSet->xDataChanged(QRect());
0732 
0733         if (dataSet->yDataRegion().intersects(dataChangedRegion))
0734             dataSet->yDataChanged(QRect());
0735 
0736         if (dataSet->categoryDataRegion().intersects(dataChangedRegion))
0737             dataSet->categoryDataChanged(QRect());
0738 
0739         if (dataSet->labelDataRegion().intersects(dataChangedRegion))
0740             dataSet->labelDataChanged(QRect());
0741 
0742         if (dataSet->customDataRegion().intersects(dataChangedRegion))
0743             dataSet->customDataChanged(QRect());
0744     }
0745 
0746     emit dataChanged();
0747 }
0748 
0749 
0750 QVariant ChartProxyModel::headerData(int section,
0751                                      Qt::Orientation orientation,
0752                                      int role /* = Qt::DisplayRole */) const
0753 {
0754     Q_UNUSED(section);
0755     Q_UNUSED(orientation);
0756     Q_UNUSED(role);
0757     Q_ASSERT("To be implemented");
0758     return QVariant();
0759 }
0760 
0761 
0762 QModelIndex ChartProxyModel::parent(const QModelIndex &index) const
0763 {
0764     Q_UNUSED(index);
0765 
0766     return QModelIndex();
0767 }
0768 
0769 int ChartProxyModel::rowCount(const QModelIndex &/*parent  = QModelIndex() */) const
0770 {
0771     return d->dataSets.count();
0772 }
0773 
0774 
0775 int ChartProxyModel::columnCount(const QModelIndex &/*parent = QModelIndex() */) const
0776 {
0777     // FIXME: Replace this by the actual column count once the proxy is properly being used.
0778     int cols = 0;
0779     for (int i = 0; i < d->dataSets.count(); ++i) {
0780         cols = qMax(cols, d->dataSets.at(i)->size());
0781     }
0782     return cols;
0783 }
0784 
0785 void ChartProxyModel::setFirstRowIsLabel(bool b)
0786 {
0787     if (b == d->firstRowIsLabel)
0788         return;
0789 
0790     d->firstRowIsLabel = b;
0791     d->rebuildDataMap();
0792 }
0793 
0794 
0795 void ChartProxyModel::setFirstColumnIsLabel(bool b)
0796 {
0797     if (b == d->firstColumnIsLabel)
0798         return;
0799 
0800     d->firstColumnIsLabel = b;
0801     d->rebuildDataMap();
0802 }
0803 
0804 Qt::Orientation ChartProxyModel::dataDirection()
0805 {
0806     return d->dataDirection;
0807 }
0808 
0809 void ChartProxyModel::invalidateDataSets()
0810 {
0811     d->removedDataSets = d->dataSets;
0812     d->dataSets.clear();
0813 }
0814 
0815 void ChartProxyModel::beginLoading()
0816 {
0817     Q_ASSERT(!d->isLoading);
0818 
0819     beginResetModel();
0820 
0821     // FIXME: invalidateDataSets() used to be called explicitly at the beginning
0822     // of ChartShape::loadOdf(). Now beginLoading() is called instead.
0823     // So, is invalidateDataSets() still necessary here?
0824     invalidateDataSets();
0825     d->isLoading = true;
0826 }
0827 
0828 void ChartProxyModel::endLoading()
0829 {
0830     Q_ASSERT(d->isLoading);
0831     d->isLoading = false;
0832 
0833     // Doing this here is wrong, the data set's cell regions set in
0834     // DataSet::loadOdf() would get overridden.
0835     // d->rebuildDataMap();
0836 
0837     endResetModel();
0838 }
0839 
0840 bool ChartProxyModel::isLoading() const
0841 {
0842     return d->isLoading;
0843 }
0844 
0845 void ChartProxyModel::setDataDirection(Qt::Orientation orientation)
0846 {
0847     if (d->dataDirection == orientation)
0848         return;
0849 
0850     d->dataDirection = orientation;
0851     d->rebuildDataMap();
0852 }
0853 
0854 void ChartProxyModel::setDataDimensions(int dimensions)
0855 {
0856     if (d->dataDimensions == dimensions)
0857         return;
0858 
0859     d->dataDimensions = dimensions;
0860     d->rebuildDataMap();
0861 }
0862 
0863 bool ChartProxyModel::firstRowIsLabel() const
0864 {
0865     return d->firstRowIsLabel;
0866 }
0867 
0868 bool ChartProxyModel::firstColumnIsLabel() const
0869 {
0870     return d->firstColumnIsLabel;
0871 }
0872 
0873 CellRegion ChartProxyModel::categoryDataRegion() const
0874 {
0875     return d->categoryDataRegion;
0876 }
0877 
0878 void ChartProxyModel::setCategoryDataRegion(const CellRegion &region)
0879 {
0880     d->categoryDataRegion = region;
0881 }
0882 
0883 QList<DataSet*> ChartProxyModel::dataSets() const
0884 {
0885     return d->dataSets;
0886 }
0887 
0888 void ChartProxyModel::addDataSet(int pos)
0889 {
0890     int dataSetNr = pos;
0891     QMap<int, int> numbers;
0892     for (int i = 0; i < d->dataSets.count(); ++i) {
0893         numbers[d->dataSets.at(i)->number()] = i;
0894     }
0895     if (numbers.contains(pos)) {
0896         // find first free one
0897         for (int i = 1; i < numbers.count(); ++ i) {
0898             if (!numbers.contains(i)) {
0899                 dataSetNr = i;
0900                 break;
0901             }
0902         }
0903     }
0904     DataSet *ds = new DataSet(dataSetNr);
0905     if (!d->dataSets.isEmpty()) {
0906         DataSet *copy = d->dataSets.first();
0907         ds->setXDataRegion(copy->xDataRegion());
0908         ds->setYDataRegion(copy->yDataRegion());
0909         ds->setCustomDataRegion(copy->customDataRegion());
0910         ds->setCategoryDataRegion(copy->categoryDataRegion());
0911     }
0912     d->dataSets.insert(pos, ds);
0913 }
0914 
0915 bool ChartProxyModel::insertRows(int row, int count, const QModelIndex &/*parent*/)
0916 {
0917     if (count < 1 || row < 0 || row > d->dataSets.count()) {
0918         return false;
0919     }
0920     beginResetModel();
0921     for (int i = 0; i < count; ++i) {
0922         addDataSet(row + i);
0923     }
0924     endResetModel();
0925     dataChanged(QModelIndex(), QModelIndex());
0926     return true;
0927 }
0928 
0929 bool ChartProxyModel::removeRows(int row, int count, const QModelIndex &/*parent*/)
0930 {
0931     if (count < 1 || row < 0 || row >= d->dataSets.count()) {
0932         return false;
0933     }
0934     beginResetModel();
0935     QList<DataSet*> remove;
0936     for (int i = 0;  i < count; ++i) {
0937         remove <<  d->dataSets.at(row + i);
0938     }
0939     for (DataSet *ds : remove) {
0940         d->dataSets.removeAll(ds);
0941         delete ds;
0942     }
0943     endResetModel();
0944     dataChanged(QModelIndex(), QModelIndex());
0945     return true;
0946 }
0947 
0948 void ChartProxyModel::setManualControl(bool value)
0949 {
0950     d->manualControl = value;
0951 }
0952 
0953 bool ChartProxyModel::manualControl()
0954 {
0955     return d->manualControl;
0956 }