File indexing completed on 2024-05-12 03:47:28

0001 /*
0002     File                 : DatapickerCurve.cpp
0003     Project              : LabPlot
0004     Description          : container for Curve-Point and Datasheet/Spreadsheet
0005     of datapicker
0006     --------------------------------------------------------------------
0007     SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
0008     SPDX-FileCopyrightText: 2015-2022 Alexander Semke <alexander.semke@web.de>
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "DatapickerCurve.h"
0013 #include "backend/datapicker/Datapicker.h"
0014 #include "backend/datapicker/DatapickerCurvePrivate.h"
0015 #include "backend/datapicker/DatapickerPoint.h"
0016 #include "backend/lib/XmlStreamReader.h"
0017 #include "backend/lib/commandtemplates.h"
0018 #include "backend/spreadsheet/Spreadsheet.h"
0019 #include "backend/worksheet/Worksheet.h"
0020 #include "backend/worksheet/plots/cartesian/Symbol.h"
0021 
0022 #include <QIcon>
0023 #include <QVector3D>
0024 
0025 #include <KConfig>
0026 #include <KConfigGroup>
0027 #include <KLocalizedString>
0028 
0029 /**
0030  * \class DatapickerCurve
0031  * \brief Top-level container for Curve-Point and Datasheet/Spreadsheet of datapicker.
0032  * \ingroup backend
0033  */
0034 
0035 DatapickerCurve::DatapickerCurve(const QString& name)
0036     : AbstractAspect(name, AspectType::DatapickerCurve)
0037     , d_ptr(new DatapickerCurvePrivate(this)) {
0038     init();
0039 }
0040 
0041 DatapickerCurve::DatapickerCurve(const QString& name, DatapickerCurvePrivate* dd)
0042     : AbstractAspect(name, AspectType::DatapickerCurve)
0043     , d_ptr(dd) {
0044     init();
0045 }
0046 
0047 DatapickerCurve::~DatapickerCurve() {
0048     delete d_ptr;
0049 }
0050 
0051 void DatapickerCurve::init() {
0052     Q_D(DatapickerCurve);
0053 
0054     KConfig config;
0055     KConfigGroup group;
0056     group = config.group(QStringLiteral("DatapickerCurve"));
0057     d->pointVisibility = group.readEntry(QStringLiteral("PointVisibility"), true);
0058 
0059     // error bars
0060     d->curveErrorTypes.x = (ErrorType)group.readEntry(QStringLiteral("CurveErrorType_X"), static_cast<int>(ErrorType::NoError));
0061     d->curveErrorTypes.y = (ErrorType)group.readEntry(QStringLiteral("CurveErrorType_Y"), static_cast<int>(ErrorType::NoError));
0062     d->pointErrorBarSize = group.readEntry(QStringLiteral("ErrorBarSize"), Worksheet::convertToSceneUnits(8, Worksheet::Unit::Point));
0063     d->pointErrorBarBrush.setStyle((Qt::BrushStyle)group.readEntry(QStringLiteral("ErrorBarFillingStyle"), (int)Qt::NoBrush));
0064     d->pointErrorBarBrush.setColor(group.readEntry(QStringLiteral("ErrorBarFillingColor"), QColor(Qt::black)));
0065     d->pointErrorBarPen.setStyle((Qt::PenStyle)group.readEntry(QStringLiteral("ErrorBarBorderStyle"), (int)Qt::SolidLine));
0066     d->pointErrorBarPen.setColor(group.readEntry(QStringLiteral("ErrorBarBorderColor"), QColor(Qt::black)));
0067     d->pointErrorBarPen.setWidthF(group.readEntry(QStringLiteral("ErrorBarBorderWidth"), Worksheet::convertToSceneUnits(1, Worksheet::Unit::Point)));
0068 
0069     // initialize the symbol
0070     d->symbol = new Symbol(QString());
0071     addChild(d->symbol);
0072     d->symbol->setHidden(true);
0073     connect(d->symbol, &Symbol::updateRequested, [=] {
0074         d->retransform();
0075     });
0076     connect(d->symbol, &Symbol::updatePixmapRequested, [=] {
0077         d->retransform();
0078     });
0079     d->symbol->init(group);
0080 
0081     connect(this, &AbstractAspect::childAspectAdded, this, &DatapickerCurve::childAdded);
0082     connect(this, &AbstractAspect::childAspectAboutToBeRemoved, this, &DatapickerCurve::childRemoved);
0083 }
0084 
0085 void DatapickerCurve::childAdded(const AbstractAspect* child) {
0086     if (m_supressResizeDatasheet)
0087         return;
0088     const auto* p = dynamic_cast<const DatapickerPoint*>(child);
0089     if (!p)
0090         return;
0091     m_datasheet->setRowCount(m_datasheet->rowCount() + 1);
0092 }
0093 
0094 void DatapickerCurve::childRemoved(const AbstractAspect* child) {
0095     Q_UNUSED(child);
0096     const auto* point = dynamic_cast<const DatapickerPoint*>(child);
0097     if (!point)
0098         return;
0099 
0100     int row = indexOfChild<DatapickerPoint>(point, ChildIndexFlag::IncludeHidden);
0101     m_datasheet->removeRows(row, 1);
0102 }
0103 
0104 /*!
0105     Returns an icon to be used in the project explorer.
0106 */
0107 QIcon DatapickerCurve::icon() const {
0108     return QIcon::fromTheme(QStringLiteral("labplot-xy-curve"));
0109 }
0110 
0111 Column* DatapickerCurve::appendColumn(const QString& name) {
0112     auto* col = new Column(name);
0113     col->insertRows(0, m_datasheet->rowCount());
0114     col->setFixed(true);
0115     col->setUndoAware(false);
0116     m_datasheet->addChild(col);
0117 
0118     return col;
0119 }
0120 
0121 // ##############################################################################
0122 // ##########################  getter methods  ##################################
0123 // ##############################################################################
0124 Symbol* DatapickerCurve::symbol() const {
0125     Q_D(const DatapickerCurve);
0126     return d->symbol;
0127 }
0128 
0129 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, DatapickerCurve::Errors, curveErrorTypes, curveErrorTypes)
0130 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointErrorBarSize, pointErrorBarSize)
0131 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, QBrush, pointErrorBarBrush, pointErrorBarBrush)
0132 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, QPen, pointErrorBarPen, pointErrorBarPen)
0133 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, bool, pointVisibility, pointVisibility)
0134 
0135 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posXColumn, posXColumn)
0136 QString& DatapickerCurve::posXColumnPath() const {
0137     return d_ptr->posXColumnPath;
0138 }
0139 
0140 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posYColumn, posYColumn)
0141 QString& DatapickerCurve::posYColumnPath() const {
0142     return d_ptr->posYColumnPath;
0143 }
0144 
0145 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posZColumn, posZColumn)
0146 QString& DatapickerCurve::posZColumnPath() const {
0147     return d_ptr->posZColumnPath;
0148 }
0149 
0150 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, plusDeltaXColumn, plusDeltaXColumn)
0151 QString& DatapickerCurve::plusDeltaXColumnPath() const {
0152     return d_ptr->plusDeltaXColumnPath;
0153 }
0154 
0155 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, minusDeltaXColumn, minusDeltaXColumn)
0156 QString& DatapickerCurve::minusDeltaXColumnPath() const {
0157     return d_ptr->minusDeltaXColumnPath;
0158 }
0159 
0160 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, plusDeltaYColumn, plusDeltaYColumn)
0161 QString& DatapickerCurve::plusDeltaYColumnPath() const {
0162     return d_ptr->plusDeltaYColumnPath;
0163 }
0164 
0165 BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, minusDeltaYColumn, minusDeltaYColumn)
0166 QString& DatapickerCurve::minusDeltaYColumnPath() const {
0167     return d_ptr->minusDeltaYColumnPath;
0168 }
0169 
0170 // ##############################################################################
0171 // #########################  setter methods  ###################################
0172 // ##############################################################################
0173 void DatapickerCurve::addDatasheet(DatapickerImage::GraphType type) {
0174     Q_D(DatapickerCurve);
0175 
0176     m_datasheet = new Spreadsheet(i18n("Data"));
0177     m_datasheet->setFixed(true);
0178     m_datasheet->setRowCount(0);
0179     addChild(m_datasheet);
0180 
0181     QString xLabel;
0182     QString yLabel;
0183 
0184     switch (type) {
0185     case DatapickerImage::GraphType::Linear: {
0186         xLabel = QLatin1Char('x');
0187         yLabel = QLatin1Char('y');
0188         break;
0189     }
0190     case DatapickerImage::GraphType::PolarInDegree: {
0191         xLabel = QLatin1String("r");
0192         yLabel = QLatin1String("y(deg)");
0193         break;
0194     }
0195     case DatapickerImage::GraphType::PolarInRadians: {
0196         xLabel = QLatin1String("r");
0197         yLabel = QLatin1String("y(rad)");
0198         break;
0199     }
0200     case DatapickerImage::GraphType::LnXY: {
0201         xLabel = QLatin1String("ln(x)");
0202         yLabel = QLatin1String("ln(y)");
0203         break;
0204     }
0205     case DatapickerImage::GraphType::LnX: {
0206         xLabel = QLatin1String("ln(x)");
0207         yLabel = QLatin1String("y");
0208         break;
0209     }
0210     case DatapickerImage::GraphType::LnY: {
0211         xLabel = QLatin1String("x");
0212         yLabel = QLatin1String("ln(y)");
0213         break;
0214     }
0215     case DatapickerImage::GraphType::Log10XY: {
0216         xLabel = QLatin1String("log(x)");
0217         yLabel = QLatin1String("log(y)");
0218         break;
0219     }
0220     case DatapickerImage::GraphType::Log10X: {
0221         xLabel = QLatin1String("log(x)");
0222         yLabel = QLatin1String("y");
0223         break;
0224     }
0225     case DatapickerImage::GraphType::Log10Y: {
0226         xLabel = QLatin1String("x");
0227         yLabel = QLatin1String("log(y)");
0228         break;
0229     }
0230     case DatapickerImage::GraphType::Ternary: {
0231         xLabel = QLatin1Char('a');
0232         yLabel = QLatin1Char('b');
0233         break;
0234     }
0235     }
0236 
0237     // the default spreadsheet can have arbitrary number of colums as per user's default template.
0238     // make sure we have the columns for x and y only
0239     if (m_datasheet->columnCount() < 1)
0240         appendColumn(xLabel);
0241     if (m_datasheet->columnCount() < 2)
0242         appendColumn(yLabel);
0243     if (m_datasheet->columnCount() > 2)
0244         m_datasheet->setColumnCount(2);
0245 
0246     // add the third column for Ternary
0247     if (type == DatapickerImage::GraphType::Ternary)
0248         d->posZColumn = appendColumn(QLatin1String("c"));
0249 
0250     d->posXColumn = m_datasheet->column(0);
0251     d->posXColumn->setName(xLabel);
0252     d->posXColumn->setPlotDesignation(AbstractColumn::PlotDesignation::X);
0253     d->posXColumn->setFixed(true);
0254     d->posXColumn->setUndoAware(false);
0255 
0256     d->posYColumn = m_datasheet->column(1);
0257     d->posYColumn->setName(yLabel);
0258     d->posYColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0259     d->posYColumn->setFixed(true);
0260     d->posYColumn->setUndoAware(false);
0261 }
0262 
0263 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetCurveErrorTypes, DatapickerCurve::Errors, curveErrorTypes)
0264 void DatapickerCurve::setCurveErrorTypes(const DatapickerCurve::Errors errors) {
0265     Q_D(DatapickerCurve);
0266     if (d->curveErrorTypes.x != errors.x || d->curveErrorTypes.y != errors.y) {
0267         beginMacro(i18n("%1: set xy-error type", name()));
0268         exec(new DatapickerCurveSetCurveErrorTypesCmd(d, errors, ki18n("%1: set xy-error type")));
0269 
0270         if (errors.x != ErrorType::NoError && !d->plusDeltaXColumn)
0271             setPlusDeltaXColumn(appendColumn(QLatin1String("+delta_x")));
0272         else if (d->plusDeltaXColumn && errors.x == ErrorType::NoError) {
0273             d->plusDeltaXColumn->remove();
0274             d->plusDeltaXColumn = nullptr;
0275         }
0276 
0277         if (errors.x == ErrorType::AsymmetricError && !d->minusDeltaXColumn)
0278             setMinusDeltaXColumn(appendColumn(QLatin1String("-delta_x")));
0279         else if (d->minusDeltaXColumn && errors.x != ErrorType::AsymmetricError) {
0280             d->minusDeltaXColumn->remove();
0281             d->minusDeltaXColumn = nullptr;
0282         }
0283 
0284         if (errors.y != ErrorType::NoError && !d->plusDeltaYColumn)
0285             setPlusDeltaYColumn(appendColumn(QLatin1String("+delta_y")));
0286         else if (d->plusDeltaYColumn && errors.y == ErrorType::NoError) {
0287             d->plusDeltaYColumn->remove();
0288             d->plusDeltaYColumn = nullptr;
0289         }
0290 
0291         if (errors.y == ErrorType::AsymmetricError && !d->minusDeltaYColumn)
0292             setMinusDeltaYColumn(appendColumn(QLatin1String("-delta_y")));
0293         else if (d->minusDeltaYColumn && errors.y != ErrorType::AsymmetricError) {
0294             d->minusDeltaYColumn->remove();
0295             d->minusDeltaYColumn = nullptr;
0296         }
0297 
0298         endMacro();
0299     }
0300 }
0301 
0302 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosXColumn, AbstractColumn*, posXColumn)
0303 void DatapickerCurve::setPosXColumn(AbstractColumn* column) {
0304     Q_D(DatapickerCurve);
0305     if (d->posXColumn != column)
0306         exec(new DatapickerCurveSetPosXColumnCmd(d, column, ki18n("%1: set position X column")));
0307 }
0308 
0309 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosYColumn, AbstractColumn*, posYColumn)
0310 void DatapickerCurve::setPosYColumn(AbstractColumn* column) {
0311     Q_D(DatapickerCurve);
0312     if (d->posYColumn != column)
0313         exec(new DatapickerCurveSetPosYColumnCmd(d, column, ki18n("%1: set position Y column")));
0314 }
0315 
0316 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosZColumn, AbstractColumn*, posZColumn)
0317 void DatapickerCurve::setPosZColumn(AbstractColumn* column) {
0318     Q_D(DatapickerCurve);
0319     if (d->posZColumn != column)
0320         exec(new DatapickerCurveSetPosZColumnCmd(d, column, ki18n("%1: set position Z column")));
0321 }
0322 
0323 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPlusDeltaXColumn, AbstractColumn*, plusDeltaXColumn)
0324 void DatapickerCurve::setPlusDeltaXColumn(AbstractColumn* column) {
0325     Q_D(DatapickerCurve);
0326     if (d->plusDeltaXColumn != column)
0327         exec(new DatapickerCurveSetPlusDeltaXColumnCmd(d, column, ki18n("%1: set +delta_X column")));
0328 }
0329 
0330 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetMinusDeltaXColumn, AbstractColumn*, minusDeltaXColumn)
0331 void DatapickerCurve::setMinusDeltaXColumn(AbstractColumn* column) {
0332     Q_D(DatapickerCurve);
0333     if (d->minusDeltaXColumn != column)
0334         exec(new DatapickerCurveSetMinusDeltaXColumnCmd(d, column, ki18n("%1: set -delta_X column")));
0335 }
0336 
0337 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPlusDeltaYColumn, AbstractColumn*, plusDeltaYColumn)
0338 void DatapickerCurve::setPlusDeltaYColumn(AbstractColumn* column) {
0339     Q_D(DatapickerCurve);
0340     if (d->plusDeltaYColumn != column)
0341         exec(new DatapickerCurveSetPlusDeltaYColumnCmd(d, column, ki18n("%1: set +delta_Y column")));
0342 }
0343 
0344 STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetMinusDeltaYColumn, AbstractColumn*, minusDeltaYColumn)
0345 void DatapickerCurve::setMinusDeltaYColumn(AbstractColumn* column) {
0346     Q_D(DatapickerCurve);
0347     if (d->minusDeltaYColumn != column)
0348         exec(new DatapickerCurveSetMinusDeltaYColumnCmd(d, column, ki18n("%1: set -delta_Y column")));
0349 }
0350 
0351 STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarSize, qreal, pointErrorBarSize, retransform)
0352 void DatapickerCurve::setPointErrorBarSize(qreal size) {
0353     Q_D(DatapickerCurve);
0354     if (size != d->pointErrorBarSize)
0355         exec(new DatapickerCurveSetPointErrorBarSizeCmd(d, size, ki18n("%1: set error bar size")));
0356 }
0357 
0358 STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarBrush, QBrush, pointErrorBarBrush, retransform)
0359 void DatapickerCurve::setPointErrorBarBrush(const QBrush& brush) {
0360     Q_D(DatapickerCurve);
0361     if (brush != d->pointErrorBarBrush)
0362         exec(new DatapickerCurveSetPointErrorBarBrushCmd(d, brush, ki18n("%1: set error bar filling")));
0363 }
0364 
0365 STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarPen, QPen, pointErrorBarPen, retransform)
0366 void DatapickerCurve::setPointErrorBarPen(const QPen& pen) {
0367     Q_D(DatapickerCurve);
0368     if (pen != d->pointErrorBarPen)
0369         exec(new DatapickerCurveSetPointErrorBarPenCmd(d, pen, ki18n("%1: set error bar outline style")));
0370 }
0371 
0372 STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointVisibility, bool, pointVisibility, retransform)
0373 void DatapickerCurve::setPointVisibility(bool on) {
0374     Q_D(DatapickerCurve);
0375     if (on != d->pointVisibility)
0376         exec(new DatapickerCurveSetPointVisibilityCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible")));
0377 }
0378 
0379 void DatapickerCurve::setPrinting(bool on) {
0380     for (auto* point : children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden))
0381         point->setPrinting(on);
0382 }
0383 
0384 /*!
0385     Selects or deselects the Datapicker/Curve in the project explorer.
0386     This function is called in \c DatapickerImageView.
0387 */
0388 void DatapickerCurve::setSelectedInView(bool b) {
0389     if (b)
0390         Q_EMIT childAspectSelectedInView(this);
0391     else
0392         Q_EMIT childAspectDeselectedInView(this);
0393 }
0394 
0395 // ##############################################################################
0396 // ######  SLOTs for changes triggered via QActions in the context menu  ########
0397 // ##############################################################################
0398 void DatapickerCurve::suppressUpdatePoint(bool suppress) {
0399     m_supressResizeDatasheet = suppress;
0400 
0401     if (!suppress) {
0402         // update points
0403         auto points = children<DatapickerPoint>(ChildIndexFlag::IncludeHidden);
0404         m_datasheet->setRowCount(points.count());
0405         updatePoints();
0406     }
0407 }
0408 
0409 void DatapickerCurve::updatePoints() {
0410     for (auto* point : children<DatapickerPoint>(ChildIndexFlag::IncludeHidden))
0411         updatePoint(point);
0412 }
0413 
0414 /*!
0415     Update datasheet for corresponding curve-point,
0416     it is called every time whenever there is any change in position
0417     of curve-point or its error-bar so keep it undo unaware
0418     no need to create extra entry in undo stack
0419 */
0420 void DatapickerCurve::updatePoint(const DatapickerPoint* point) {
0421     Q_D(DatapickerCurve);
0422 
0423     if (m_supressResizeDatasheet)
0424         return;
0425 
0426     // TODO: this check shouldn't be required.
0427     // redesign the retransform()-call in load() to avoid it.
0428     if (!parentAspect())
0429         return;
0430 
0431     auto* datapicker = static_cast<Datapicker*>(parentAspect());
0432     int row = indexOfChild<DatapickerPoint>(point, ChildIndexFlag::IncludeHidden);
0433 
0434     const auto xDateTime = datapicker->xDateTime();
0435     if ((m_datetime && !xDateTime) || (!m_datetime && xDateTime))
0436         updateColumns(xDateTime);
0437 
0438     Vector3D data = datapicker->mapSceneToLogical(point->position());
0439 
0440     if (d->posXColumn) {
0441         if (xDateTime) {
0442             auto dt = QDateTime::fromMSecsSinceEpoch(data.x());
0443             dt.setTimeSpec(Qt::TimeSpec::UTC);
0444             d->posXColumn->setDateTimeAt(row, dt);
0445         } else
0446             d->posXColumn->setValueAt(row, data.x());
0447     }
0448 
0449     if (d->posYColumn)
0450         d->posYColumn->setValueAt(row, data.y());
0451 
0452     if (d->posZColumn)
0453         d->posZColumn->setValueAt(row, data.y());
0454 
0455     if (d->plusDeltaXColumn) {
0456         data = datapicker->mapSceneLengthToLogical(QPointF(point->plusDeltaXPos().x(), 0));
0457         d->plusDeltaXColumn->setValueAt(row, std::abs(data.x()));
0458     }
0459 
0460     if (d->minusDeltaXColumn) {
0461         data = datapicker->mapSceneLengthToLogical(QPointF(point->minusDeltaXPos().x(), 0));
0462         d->minusDeltaXColumn->setValueAt(row, std::abs(data.x()));
0463     }
0464 
0465     if (d->plusDeltaYColumn) {
0466         data = datapicker->mapSceneLengthToLogical(QPointF(0, point->plusDeltaYPos().y()));
0467         d->plusDeltaYColumn->setValueAt(row, std::abs(data.y()));
0468     }
0469 
0470     if (d->minusDeltaYColumn) {
0471         data = datapicker->mapSceneLengthToLogical(QPointF(0, point->minusDeltaYPos().y()));
0472         d->minusDeltaYColumn->setValueAt(row, std::abs(data.y()));
0473     }
0474 }
0475 
0476 void DatapickerCurve::updateColumns(bool datetime) {
0477     m_datetime = datetime;
0478     Q_D(DatapickerCurve);
0479     if (datetime)
0480         d->posXColumn->setColumnMode(AbstractColumn::ColumnMode::DateTime);
0481     else
0482         d->posXColumn->setColumnMode(AbstractColumn::ColumnMode::Double);
0483 }
0484 
0485 // ##############################################################################
0486 // ####################### Private implementation ###############################
0487 // ##############################################################################
0488 DatapickerCurvePrivate::DatapickerCurvePrivate(DatapickerCurve* curve)
0489     : q(curve) {
0490 }
0491 
0492 QString DatapickerCurvePrivate::name() const {
0493     return q->name();
0494 }
0495 
0496 void DatapickerCurvePrivate::retransform() {
0497     if (q->isLoading())
0498         return;
0499     const auto& points = q->children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0500     for (auto* point : points)
0501         point->retransform();
0502 }
0503 
0504 // ##############################################################################
0505 // ##################  Serialization/Deserialization  ###########################
0506 // ##############################################################################
0507 //! Save as XML
0508 void DatapickerCurve::save(QXmlStreamWriter* writer) const {
0509     Q_D(const DatapickerCurve);
0510 
0511     writer->writeStartElement(QStringLiteral("datapickerCurve"));
0512     writeBasicAttributes(writer);
0513     writeCommentElement(writer);
0514 
0515     // general
0516     writer->writeStartElement(QStringLiteral("general"));
0517     WRITE_COLUMN(d->posXColumn, posXColumn);
0518     WRITE_COLUMN(d->posYColumn, posYColumn);
0519     WRITE_COLUMN(d->posZColumn, posZColumn);
0520     WRITE_COLUMN(d->plusDeltaXColumn, plusDeltaXColumn);
0521     WRITE_COLUMN(d->minusDeltaXColumn, minusDeltaXColumn);
0522     WRITE_COLUMN(d->plusDeltaYColumn, plusDeltaYColumn);
0523     WRITE_COLUMN(d->minusDeltaYColumn, minusDeltaYColumn);
0524     writer->writeAttribute(QStringLiteral("curveErrorType_X"), QString::number(static_cast<int>(d->curveErrorTypes.x)));
0525     writer->writeAttribute(QStringLiteral("curveErrorType_Y"), QString::number(static_cast<int>(d->curveErrorTypes.y)));
0526     writer->writeAttribute(QStringLiteral("vibible"), QString::number(d->pointVisibility));
0527     writer->writeEndElement();
0528 
0529     // Symbols
0530     d->symbol->save(writer);
0531 
0532     // error bar properties
0533     writer->writeStartElement(QStringLiteral("errorBarProperties"));
0534     writer->writeAttribute(QStringLiteral("pointErrorBarSize"), QString::number(d->pointErrorBarSize));
0535     WRITE_QBRUSH(d->pointErrorBarBrush);
0536     WRITE_QPEN(d->pointErrorBarPen);
0537     writer->writeEndElement();
0538 
0539     // serialize all children
0540     for (auto* child : children<AbstractAspect>(ChildIndexFlag::IncludeHidden))
0541         child->save(writer);
0542 
0543     writer->writeEndElement(); // close section
0544 }
0545 
0546 //! Load from XML
0547 bool DatapickerCurve::load(XmlStreamReader* reader, bool preview) {
0548     Q_D(DatapickerCurve);
0549 
0550     if (!readBasicAttributes(reader))
0551         return false;
0552 
0553     QXmlStreamAttributes attribs;
0554     QString str;
0555 
0556     while (!reader->atEnd()) {
0557         reader->readNext();
0558         if (reader->isEndElement() && reader->name() == QLatin1String("datapickerCurve"))
0559             break;
0560 
0561         if (!reader->isStartElement())
0562             continue;
0563 
0564         if (reader->name() == QLatin1String("comment")) {
0565             if (!readCommentElement(reader))
0566                 return false;
0567         } else if (!preview && reader->name() == QLatin1String("general")) {
0568             attribs = reader->attributes();
0569 
0570             READ_INT_VALUE("visible", pointVisibility, bool);
0571             READ_INT_VALUE("curveErrorType_X", curveErrorTypes.x, ErrorType);
0572             READ_INT_VALUE("curveErrorType_Y", curveErrorTypes.y, ErrorType);
0573 
0574             READ_COLUMN(posXColumn);
0575             READ_COLUMN(posYColumn);
0576             READ_COLUMN(posZColumn);
0577             READ_COLUMN(plusDeltaXColumn);
0578             READ_COLUMN(minusDeltaXColumn);
0579             READ_COLUMN(plusDeltaYColumn);
0580             READ_COLUMN(minusDeltaYColumn);
0581         } else if (!preview && reader->name() == QLatin1String("symbolProperties")) {
0582             // old serialization that was used before the switch to Symbol::load().
0583             // in the old serialization the symbol properties and "point visibility" where saved
0584             // under "symbolProperties".
0585             attribs = reader->attributes();
0586 
0587             str = attribs.value(QStringLiteral("pointRotationAngle")).toString();
0588             if (str.isEmpty())
0589                 reader->raiseMissingAttributeWarning(QStringLiteral("pointRotationAngle"));
0590             else
0591                 d->symbol->setRotationAngle(str.toDouble());
0592 
0593             str = attribs.value(QStringLiteral("pointOpacity")).toString();
0594             if (str.isEmpty())
0595                 reader->raiseMissingAttributeWarning(QStringLiteral("pointOpacity"));
0596             else
0597                 d->symbol->setOpacity(str.toDouble());
0598 
0599             str = attribs.value(QStringLiteral("pointSize")).toString();
0600             if (str.isEmpty())
0601                 reader->raiseMissingAttributeWarning(QStringLiteral("pointSize"));
0602             else
0603                 d->symbol->setSize(str.toDouble());
0604 
0605             str = attribs.value(QStringLiteral("pointStyle")).toString();
0606             if (str.isEmpty())
0607                 reader->raiseMissingAttributeWarning(QStringLiteral("pointStyle"));
0608             else
0609                 d->symbol->setStyle(static_cast<Symbol::Style>(str.toInt()));
0610 
0611             // brush
0612             QBrush brush;
0613             str = attribs.value(QStringLiteral("brush_style")).toString();
0614             if (str.isEmpty())
0615                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_style"));
0616             else
0617                 brush.setStyle(static_cast<Qt::BrushStyle>(str.toInt()));
0618 
0619             QColor color;
0620             str = attribs.value(QStringLiteral("brush_color_r")).toString();
0621             if (str.isEmpty())
0622                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_color_r"));
0623             else
0624                 color.setRed(str.toInt());
0625 
0626             str = attribs.value(QStringLiteral("brush_color_g")).toString();
0627             if (str.isEmpty())
0628                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_color_g"));
0629             else
0630                 color.setGreen(str.toInt());
0631 
0632             str = attribs.value(QStringLiteral("brush_color_b")).toString();
0633             if (str.isEmpty())
0634                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_color_b"));
0635             else
0636                 color.setBlue(str.toInt());
0637 
0638             brush.setColor(color);
0639             d->symbol->setBrush(brush);
0640 
0641             // pen
0642             QPen pen;
0643             str = attribs.value(QStringLiteral("style")).toString();
0644             if (str.isEmpty())
0645                 reader->raiseMissingAttributeWarning(QStringLiteral("style"));
0646             else
0647                 pen.setStyle(static_cast<Qt::PenStyle>(str.toInt()));
0648 
0649             str = attribs.value(QStringLiteral("color_r")).toString();
0650             if (str.isEmpty())
0651                 reader->raiseMissingAttributeWarning(QStringLiteral("color_r"));
0652             else
0653                 color.setRed(str.toInt());
0654 
0655             str = attribs.value(QStringLiteral("color_g")).toString();
0656             if (str.isEmpty())
0657                 reader->raiseMissingAttributeWarning(QStringLiteral("color_g"));
0658             else
0659                 color.setGreen(str.toInt());
0660 
0661             str = attribs.value(QStringLiteral("color_b")).toString();
0662             if (str.isEmpty())
0663                 reader->raiseMissingAttributeWarning(QStringLiteral("color_b"));
0664             else
0665                 color.setBlue(str.toInt());
0666 
0667             pen.setColor(color);
0668 
0669             str = attribs.value(QStringLiteral("width")).toString();
0670             if (str.isEmpty())
0671                 reader->raiseMissingAttributeWarning(QStringLiteral("width"));
0672             else
0673                 pen.setWidthF(str.toDouble());
0674 
0675             d->symbol->setPen(pen);
0676 
0677             READ_INT_VALUE("pointVisibility", pointVisibility, bool);
0678         } else if (!preview && reader->name() == QLatin1String("symbols")) {
0679             d->symbol->load(reader, preview);
0680         } else if (!preview && reader->name() == QLatin1String("errorBarProperties")) {
0681             attribs = reader->attributes();
0682 
0683             READ_DOUBLE_VALUE("pointErrorBarSize", pointErrorBarSize);
0684             READ_QBRUSH(d->pointErrorBarBrush);
0685             READ_QPEN(d->pointErrorBarPen);
0686         } else if (reader->name() == QLatin1String("datapickerPoint")) {
0687             auto* curvePoint = new DatapickerPoint(QString());
0688             curvePoint->setHidden(true);
0689             if (!curvePoint->load(reader, preview)) {
0690                 delete curvePoint;
0691                 return false;
0692             } else {
0693                 addChild(curvePoint);
0694                 curvePoint->initErrorBar(curveErrorTypes());
0695             }
0696         } else if (reader->name() == QLatin1String("spreadsheet")) {
0697             auto* datasheet = new Spreadsheet(QStringLiteral("spreadsheet"), true);
0698             if (!datasheet->load(reader, preview)) {
0699                 delete datasheet;
0700                 return false;
0701             } else {
0702                 addChild(datasheet);
0703                 datasheet->setFixed(true);
0704                 const auto& columns = datasheet->children<Column>();
0705                 for (auto* col : columns)
0706                     col->setFixed(true);
0707                 m_datasheet = datasheet;
0708             }
0709         } else { // unknown element
0710             reader->raiseUnknownElementWarning();
0711             if (!reader->skipToEndElement())
0712                 return false;
0713         }
0714     }
0715 
0716     d->retransform();
0717     return true;
0718 }