File indexing completed on 2024-05-12 03:48:19

0001 /*
0002     File                 : InfoElement.cpp
0003     Project              : LabPlot
0004     Description          : Marker which can highlight points of curves and
0005                            show their values
0006     --------------------------------------------------------------------
0007     SPDX-FileCopyrightText: 2020 Martin Marmsoler <martin.marmsoler@gmail.com>
0008     SPDX-FileCopyrightText: 2020-2023 Alexander Semke <alexander.semke@web.de>
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include "InfoElement.h"
0014 
0015 #include "backend/core/AbstractColumn.h"
0016 #include "backend/core/Project.h"
0017 #include "backend/lib/XmlStreamReader.h"
0018 #include "backend/lib/commandtemplates.h"
0019 #include "backend/worksheet/InfoElementPrivate.h"
0020 #include "backend/worksheet/Line.h"
0021 #include "backend/worksheet/Worksheet.h"
0022 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h"
0023 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0024 #include "backend/worksheet/plots/cartesian/CustomPoint.h"
0025 #include "backend/worksheet/plots/cartesian/XYCurve.h"
0026 
0027 #include <KConfig>
0028 #include <KConfigGroup>
0029 
0030 #include <QAction>
0031 #include <QDateTime>
0032 #include <QGraphicsSceneMouseEvent>
0033 #include <QKeyEvent>
0034 #include <QMenu>
0035 #include <QPainter>
0036 #include <QTextEdit>
0037 
0038 InfoElement::InfoElement(const QString& name, CartesianPlot* plot)
0039     : WorksheetElement(name, new InfoElementPrivate(this), AspectType::InfoElement) {
0040     m_plot = plot;
0041 
0042     init();
0043     setVisible(false);
0044 }
0045 
0046 InfoElement::InfoElement(const QString& name, CartesianPlot* p, const XYCurve* curve, double logicalPos)
0047     : WorksheetElement(name, new InfoElementPrivate(this, curve), AspectType::InfoElement) {
0048     Q_D(InfoElement);
0049     m_plot = p;
0050 
0051     init();
0052 
0053     m_suppressChildPositionChanged = true;
0054 
0055     if (curve) {
0056         d->connectionLineCurveName = curve->name();
0057         auto* custompoint = new CustomPoint(m_plot, curve->name());
0058         custompoint->setFixed(true);
0059         custompoint->setCoordinateBindingEnabled(true);
0060         custompoint->setCoordinateSystemIndex(curve->coordinateSystemIndex());
0061         addChild(custompoint);
0062         InfoElement::MarkerPoints_T markerpoint(custompoint, curve, curve->path());
0063         markerpoints.append(markerpoint);
0064 
0065         // setpos after label was created
0066         if (curve->xColumn() && curve->yColumn()) {
0067             bool valueFound;
0068             double xpos;
0069             double y = curve->y(logicalPos, xpos, valueFound);
0070             if (valueFound) {
0071                 d->positionLogical = xpos;
0072                 d->m_index = curve->xColumn()->indexForValue(xpos);
0073                 custompoint->setPositionLogical(QPointF(xpos, y));
0074                 DEBUG("Value found")
0075             }
0076         } else {
0077             d->positionLogical = 0;
0078             custompoint->setPositionLogical(cSystem->mapSceneToLogical(QPointF(0, 0)));
0079             DEBUG("Value not found")
0080         }
0081 
0082         TextLabel::TextWrapper text;
0083         text.allowPlaceholder = true;
0084 
0085         QString textString = QString::number(markerpoints[0].customPoint->positionLogical().x()) + QStringLiteral(", ");
0086         textString.append(QString(QString(markerpoints[0].curve->name() + QStringLiteral(":"))));
0087         textString.append(QString::number(markerpoints[0].customPoint->positionLogical().y()));
0088         text.text = textString;
0089 
0090         // TODO: Find better solution than using textedit
0091         QString str = QStringLiteral("&(x), ") + markerpoints[0].curve->name() + QLatin1Char(':') + QLatin1String("&(") + markerpoints[0].curve->name()
0092             + QLatin1Char(')');
0093         QTextEdit textedit(str);
0094         text.textPlaceholder = textedit.toHtml();
0095 
0096         m_setTextLabelText = true;
0097         m_title->setUndoAware(false);
0098         m_title->setText(text);
0099         m_title->setUndoAware(true);
0100         m_setTextLabelText = false;
0101 
0102         initCurveConnections(curve);
0103         custompoint->setVisible(curve->isVisible());
0104 
0105         setVisible(true);
0106     } else
0107         setVisible(false);
0108 
0109     m_suppressChildPositionChanged = false;
0110 }
0111 
0112 InfoElement::~InfoElement() {
0113     //  m_suppressChildRemoved = true;
0114     //  // this function is not called, when deleting marker
0115     //  // don't understand why I have to remove them manually
0116     //  // I think it is because of the graphicsitem, which exists
0117     //  for (auto markerpoint : markerpoints) {
0118     //      removeChild(markerpoint.customPoint);
0119     //  }
0120 
0121     //  removeChild(m_title);
0122 
0123     //  m_suppressChildRemoved = false;
0124 }
0125 
0126 void InfoElement::init() {
0127     cSystem = dynamic_cast<const CartesianCoordinateSystem*>(m_plot->coordinateSystem(m_cSystemIndex));
0128 
0129     initActions();
0130     initMenus();
0131 
0132     connect(this, &InfoElement::childAspectRemoved, this, &InfoElement::childRemoved);
0133     connect(this, &InfoElement::childAspectAdded, this, &InfoElement::childAdded);
0134 
0135     m_title = new TextLabel(i18n("Label"), m_plot);
0136     m_title->setHidden(true);
0137     TextLabel::TextWrapper text;
0138     text.allowPlaceholder = true;
0139     m_setTextLabelText = true;
0140     m_title->setUndoAware(false);
0141     m_title->setText(text); // set placeholder to true
0142     m_title->setUndoAware(true);
0143     m_setTextLabelText = false;
0144     addChild(m_title);
0145 
0146     // use the line properties of axis line also for the info element lines
0147     KConfig config;
0148     const auto& group = config.group(QStringLiteral("Axis"));
0149 
0150     // lines
0151     Q_D(InfoElement);
0152     d->verticalLine = new Line(QString());
0153     d->verticalLine->setHidden(true);
0154     d->verticalLine->setPrefix(QStringLiteral("VerticalLine"));
0155     addChild(d->verticalLine);
0156     d->verticalLine->init(group);
0157     connect(d->verticalLine, &Line::updatePixmapRequested, [=] {
0158         d->update();
0159     });
0160     connect(d->verticalLine, &Line::updateRequested, [=] {
0161         d->updateVerticalLine();
0162     });
0163 
0164     d->connectionLine = new Line(QString());
0165     d->connectionLine->setHidden(true);
0166     d->connectionLine->setPrefix(QStringLiteral("ConnectionLine"));
0167     addChild(d->connectionLine);
0168     d->connectionLine->init(group);
0169     connect(d->connectionLine, &Line::updatePixmapRequested, [=] {
0170         d->update();
0171     });
0172     connect(d->connectionLine, &Line::updateRequested, [=] {
0173         d->updateConnectionLine();
0174     });
0175 }
0176 
0177 void InfoElement::initActions() {
0178 }
0179 
0180 void InfoElement::initMenus() {
0181     m_menusInitialized = true;
0182 }
0183 
0184 void InfoElement::initCurveConnections(const XYCurve* curve) {
0185     connect(curve, &XYCurve::visibleChanged, this, &InfoElement::curveVisibilityChanged);
0186     connect(curve, &XYCurve::coordinateSystemIndexChanged, this, &InfoElement::curveCoordinateSystemIndexChanged);
0187     connect(curve, &XYCurve::dataChanged, this, &InfoElement::curveDataChanged);
0188     connect(curve, &XYCurve::moveBegin, this, [this]() {
0189         m_curveGetsMoved = true;
0190     });
0191     connect(curve, &XYCurve::moveEnd, this, [this]() {
0192         m_curveGetsMoved = false;
0193     });
0194 }
0195 
0196 QMenu* InfoElement::createContextMenu() {
0197     if (!m_menusInitialized)
0198         initMenus();
0199 
0200     return WorksheetElement::createContextMenu();
0201 }
0202 
0203 /*!
0204  * @brief InfoElement::addCurve
0205  * Adds a new markerpoint to the plot which is placed on the curve curve
0206  * @param curve Curve on which the markerpoints sits
0207  * @param custompoint Use existing point, if the project was loaded the custompoint can have different settings
0208  */
0209 void InfoElement::addCurve(const XYCurve* curve, CustomPoint* custompoint) {
0210     Q_D(InfoElement);
0211 
0212     for (auto& markerpoint : markerpoints) {
0213         if (curve == markerpoint.curve)
0214             return;
0215     }
0216 
0217     project()->setSuppressAspectAddedSignal(true);
0218 
0219     if (!custompoint) {
0220         m_suppressChildPositionChanged = true;
0221         custompoint = new CustomPoint(m_plot, curve->name());
0222         custompoint->setCoordinateBindingEnabled(true);
0223         custompoint->setCoordinateSystemIndex(curve->coordinateSystemIndex());
0224         addChild(custompoint);
0225 
0226         if (curve->xColumn() && curve->yColumn()) {
0227             bool valueFound;
0228             double x_new, y;
0229             y = curve->y(d->positionLogical, x_new, valueFound);
0230 
0231             custompoint->setUndoAware(false);
0232             custompoint->setPositionLogical(QPointF(x_new, y));
0233             custompoint->setUndoAware(true);
0234         }
0235         m_suppressChildPositionChanged = false;
0236     } else
0237         addChild(custompoint);
0238 
0239     project()->setSuppressAspectAddedSignal(true);
0240 
0241     initCurveConnections(curve);
0242 
0243     custompoint->setUndoAware(false);
0244     custompoint->setVisible(curve->isVisible());
0245     custompoint->setUndoAware(true);
0246 
0247     if (d->m_index < 0 && curve->xColumn())
0248         d->m_index = curve->xColumn()->indexForValue(custompoint->positionLogical().x());
0249 
0250     struct MarkerPoints_T markerpoint = {custompoint, curve, curve->path()};
0251     markerpoints.append(markerpoint);
0252 
0253     if (markerpoints.count() == 1) // first point
0254         setConnectionLineCurveName(curve->name());
0255 
0256     m_title->setUndoAware(false);
0257     m_title->setText(createTextLabelText());
0258 
0259     if (markerpoints.length() == 1) {
0260         // Do a retransform, because when the first markerpoint
0261         // was added, after a curve was removed and added, the
0262         // position of the connection line must be recalculated
0263         retransform();
0264     }
0265 
0266     m_title->setVisible(true); // show in the worksheet view
0267     m_title->setUndoAware(true);
0268 }
0269 
0270 /*!
0271  * \brief InfoElement::addCurvePath
0272  * When loading infoelement from xml file, there is no information available, which curves are loaded.
0273  * So only the path will be stored and after all curves where loaded the curves will be assigned to the InfoElement
0274  * with the function assignCurve
0275  * Assumption: if custompoint!=nullptr then the custompoint was already added to the InfoElement previously. Here
0276  * only new created CustomPoints will be added to the InfoElement
0277  * @param curvePath path from the curve
0278  * @param custompoint adding already created custom point
0279  */
0280 void InfoElement::addCurvePath(QString& curvePath, CustomPoint* custompoint) {
0281     for (auto& markerpoint : markerpoints) {
0282         if (curvePath == markerpoint.curvePath)
0283             return;
0284     }
0285 
0286     if (!custompoint) {
0287         custompoint = new CustomPoint(m_plot, i18n("Symbol"));
0288         custompoint->setVisible(false);
0289         m_suppressChildPositionChanged = true;
0290         custompoint->setCoordinateBindingEnabled(true);
0291         m_suppressChildPositionChanged = false;
0292         addChild(custompoint);
0293     }
0294 
0295     struct MarkerPoints_T markerpoint = {custompoint, nullptr, curvePath};
0296     markerpoints.append(markerpoint);
0297 }
0298 
0299 /*!
0300  * \brief assignCurve
0301  * Finds the curve with the path stored in the markerpoints and assigns the pointer to markerpoints
0302  * @param curves
0303  * \return true if all markerpoints are assigned with a curve, false if one or more markerpoints don't have a curve assigned
0304  */
0305 bool InfoElement::assignCurve(const QVector<XYCurve*>& curves) {
0306     bool success = true;
0307     for (auto& mp : markerpoints) {
0308         for (auto curve : curves) {
0309             if (mp.curvePath == curve->path()) {
0310                 mp.curve = curve;
0311                 initCurveConnections(curve);
0312                 mp.customPoint->setVisible(curve->isVisible()); // initial visibility
0313                 mp.customPoint->setCoordinateSystemIndex(curve->coordinateSystemIndex());
0314                 break;
0315             }
0316         }
0317     }
0318 
0319     // check if all markerpoints have a valid curve
0320     // otherwise delete customPoint with no valid curve
0321     for (int i = markerpoints.count() - 1; i >= 0; i--) {
0322         if (markerpoints[i].curve == nullptr) {
0323             removeChild(markerpoints[i].customPoint);
0324             success = false;
0325         }
0326     }
0327 
0328     return success;
0329 }
0330 
0331 /*!
0332  * Remove markerpoint from a curve
0333  * @param curve
0334  */
0335 void InfoElement::removeCurve(const XYCurve* curve) {
0336     if (m_curveGetsMoved)
0337         return;
0338 
0339     for (const auto& mp : markerpoints) {
0340         if (mp.curve == curve) {
0341             disconnect(curve, nullptr, this, nullptr);
0342             removeChild(mp.customPoint);
0343         }
0344     }
0345 
0346     setUndoAware(false);
0347     if (curve->name() == connectionLineCurveName())
0348         setConnectionLineCurveName(markerpoints.count() > 0 ? markerpoints.at(0).curve->name() : QStringLiteral());
0349     setUndoAware(true);
0350 
0351     m_title->setUndoAware(false);
0352     m_title->setText(createTextLabelText());
0353 
0354     // hide the label if now curves are selected
0355     if (markerpoints.isEmpty()) {
0356         m_title->setUndoAware(false);
0357         m_title->setVisible(false); // hide in the worksheet view
0358         m_title->setUndoAware(true);
0359         Q_D(InfoElement);
0360         d->update(); // redraw to remove all children graphic items belonging to InfoElement
0361     }
0362 
0363     m_title->setUndoAware(true);
0364 }
0365 
0366 /*!
0367  * Set the z value of the m_title and the custompoints higher than the infoelement
0368  * @param value
0369  */
0370 void InfoElement::setZValue(qreal value) {
0371     graphicsItem()->setZValue(value);
0372 
0373     m_title->setZValue(value + 1);
0374 
0375     for (auto& markerpoint : markerpoints)
0376         markerpoint.customPoint->setZValue(value + 1);
0377 }
0378 
0379 /*!
0380  * Returns the amount of markerpoints. Used in the InfoElementDock to fill listWidget.
0381  */
0382 int InfoElement::markerPointsCount() {
0383     return markerpoints.length();
0384 }
0385 
0386 TextLabel::GluePoint InfoElement::gluePoint(int index) {
0387     return m_title->gluePointAt(index);
0388 }
0389 
0390 int InfoElement::gluePointsCount() {
0391     return m_title->gluePointCount();
0392 }
0393 
0394 /*!
0395  * Returns the Markerpoint at index \p index. Used in the InfoElementDock to fill listWidget
0396  * @param index
0397  */
0398 InfoElement::MarkerPoints_T InfoElement::markerPointAt(int index) {
0399     return markerpoints.at(index);
0400 }
0401 
0402 /*!
0403  * create Text which will be shown in the TextLabel
0404  * Called when:
0405  *  - The position of the infoelement was changed
0406  *  - a curve was removed
0407  *  - a curve was added
0408  * @return Text
0409  */
0410 TextLabel::TextWrapper InfoElement::createTextLabelText() {
0411     // TODO: save positions of the variables in extra variables to replace faster, because replace takes long time
0412     TextLabel::TextWrapper wrapper = m_title->text();
0413     if (markerPointsCount() < 1) {
0414         DEBUG(STDSTRING(wrapper.text))
0415         DEBUG(STDSTRING(wrapper.textPlaceholder))
0416         wrapper.text = wrapper.textPlaceholder;
0417         return wrapper;
0418     }
0419 
0420     if (!(markerpoints.at(0).curve && markerpoints.at(0).curve->xColumn()))
0421         return wrapper; // no data is set in the curve yet, nothing to do
0422 
0423     Q_D(const InfoElement);
0424 
0425     QString text = wrapper.textPlaceholder;
0426 
0427     // replace the placeholder for the x-value
0428     QString xValueStr;
0429     auto columnMode = markerpoints.at(0).curve->xColumn()->columnMode();
0430     if (columnMode == AbstractColumn::ColumnMode::Double || columnMode == AbstractColumn::ColumnMode::Integer
0431         || columnMode == AbstractColumn::ColumnMode::BigInt)
0432         xValueStr = QString::number(d->positionLogical);
0433     else if (columnMode == AbstractColumn::ColumnMode::Day || columnMode == AbstractColumn::ColumnMode::Month
0434              || columnMode == AbstractColumn::ColumnMode::DateTime) {
0435         const auto& dateTime = QDateTime::fromMSecsSinceEpoch(d->positionLogical, Qt::UTC);
0436         xValueStr = dateTime.toString(m_plot->rangeDateTimeFormat(Dimension::X));
0437     }
0438 
0439     if (wrapper.mode == TextLabel::Mode::Text)
0440         text.replace(QStringLiteral("&amp;(x)"), xValueStr);
0441     else
0442         text.replace(QStringLiteral("&(x)"), xValueStr);
0443 
0444     // replace the placeholders for curve's y-values
0445     for (const auto& markerpoint : qAsConst(markerpoints)) {
0446         QString replace;
0447         if (wrapper.mode == TextLabel::Mode::Text)
0448             replace = QStringLiteral("&amp;(");
0449         else
0450             replace = QStringLiteral("&(");
0451 
0452         replace += markerpoint.curve->name() + QLatin1Char(')');
0453         text.replace(replace, QString::number(markerpoint.customPoint->positionLogical().y()));
0454     }
0455 
0456     wrapper.text = text;
0457     return wrapper;
0458 }
0459 
0460 TextLabel* InfoElement::title() {
0461     return m_title;
0462 }
0463 
0464 bool InfoElement::isTextLabel() const {
0465     return m_title != nullptr;
0466 }
0467 
0468 /*!
0469  * Will be called, when the label changes his position
0470  * @param position
0471  */
0472 void InfoElement::labelPositionChanged(TextLabel::PositionWrapper /*position*/) {
0473     if (m_suppressChildPositionChanged)
0474         return;
0475 
0476     Q_D(InfoElement);
0477     d->retransform();
0478 }
0479 
0480 void InfoElement::labelVisibleChanged(bool /*visible*/) {
0481     Q_D(InfoElement);
0482     d->update();
0483 }
0484 
0485 void InfoElement::labelTextWrapperChanged(TextLabel::TextWrapper) {
0486     if (m_setTextLabelText)
0487         return;
0488 
0489     m_setTextLabelText = true;
0490     m_title->setUndoAware(false);
0491     m_title->setText(createTextLabelText());
0492     m_title->setUndoAware(true);
0493     m_setTextLabelText = false;
0494 
0495     Q_D(InfoElement);
0496     d->retransform();
0497 }
0498 
0499 /*!
0500  * \brief InfoElement::moveElementBegin
0501  * Called, when a child is moved in front or behind another element.
0502  * Needed, because the child calls child removed, when moving and then
0503  * everything will be deleted
0504  */
0505 void InfoElement::moveElementBegin() {
0506     m_suppressChildRemoved = true;
0507 }
0508 
0509 /*!
0510  * \brief InfoElement::moveElementEnd
0511  * Called, when a child is moved in front or behind another element.
0512  * Needed, because the child calls child removed, when moving and then
0513  * everything will be deleted
0514  */
0515 void InfoElement::moveElementEnd() {
0516     m_suppressChildRemoved = false;
0517 }
0518 
0519 void InfoElement::curveCoordinateSystemIndexChanged(int /*index*/) {
0520     auto* curve = static_cast<XYCurve*>(QObject::sender());
0521     auto cSystemIndex = curve->coordinateSystemIndex();
0522 
0523     for (auto& custompoint : markerpoints) {
0524         if (custompoint.curve == curve) {
0525             custompoint.customPoint->setCoordinateSystemIndex(cSystemIndex);
0526             break;
0527         }
0528     }
0529 
0530     retransform();
0531 }
0532 
0533 void InfoElement::curveVisibilityChanged() {
0534     XYCurve* curve = static_cast<XYCurve*>(QObject::sender());
0535     bool visible = curve->isVisible();
0536 
0537     bool oneMarkerpointVisible = false;
0538     for (auto& custompoint : markerpoints) {
0539         if (custompoint.curve == curve)
0540             custompoint.customPoint->setVisible(visible);
0541 
0542         if (custompoint.customPoint->isVisible())
0543             oneMarkerpointVisible = true;
0544     }
0545 
0546     // if curve was set to hidden, set InfoElement to first visible curve
0547     if (!visible) {
0548         for (auto& custompoint : markerpoints) {
0549             if (custompoint.curve->isVisible()) {
0550                 setConnectionLineCurveName(custompoint.curve->name());
0551                 break;
0552             }
0553         }
0554     }
0555 
0556     // if no markerpoints are visible, hide the title label
0557     m_title->setUndoAware(false);
0558     if ((!visible && markerpoints.count() == 0) || !oneMarkerpointVisible)
0559         m_title->setVisible(false);
0560     else
0561         m_title->setVisible(true);
0562     m_title->setUndoAware(true);
0563 }
0564 
0565 void InfoElement::curveDataChanged() {
0566     Q_D(InfoElement);
0567     setMarkerpointPosition(d->positionLogical);
0568     m_setTextLabelText = true;
0569     m_title->setUndoAware(false);
0570     m_title->setText(createTextLabelText());
0571     m_title->setUndoAware(true);
0572     m_setTextLabelText = false;
0573     retransform();
0574 }
0575 
0576 void InfoElement::labelBorderShapeChanged() {
0577     Q_D(InfoElement);
0578     Q_EMIT labelBorderShapeChangedSignal();
0579     d->retransform();
0580 }
0581 
0582 /*!
0583  * Delete child and remove from markerpoint list if it is a markerpoint. If it is a textlabel delete complete InfoElement
0584  */
0585 void InfoElement::childRemoved(const AbstractAspect* parent, const AbstractAspect* /*before*/, const AbstractAspect* child) {
0586     Q_D(InfoElement);
0587 
0588     // when childs are reordered, don't remove them
0589     // problem: when the order was changed the elements are deleted for a short time and recreated. This function will called then
0590     if (m_suppressChildRemoved)
0591         return;
0592 
0593     if (parent != this) // why do I need this?
0594         return;
0595 
0596     // point removed
0597     const auto* point = dynamic_cast<const CustomPoint*>(child);
0598     if (point) {
0599         for (int i = 0; i < markerpoints.length(); i++) {
0600             if (point == markerpoints[i].customPoint)
0601                 markerpoints.removeAt(i);
0602             // no point->remove() needed, because it was already deleted
0603         }
0604         // recreate text, because when marker was deleted,
0605         // the placeholder should not be replaced anymore by a value
0606         m_title->setUndoAware(false);
0607         m_title->setText(createTextLabelText());
0608         m_title->setUndoAware(true);
0609     }
0610 
0611     // textlabel was deleted
0612     const auto* textlabel = dynamic_cast<const TextLabel*>(child);
0613     if (textlabel) {
0614         Q_ASSERT(m_title == textlabel);
0615         m_title = nullptr;
0616         for (int i = 0; i < markerpoints.length(); i++) { // why it's not working without?
0617             m_suppressChildRemoved = true;
0618             markerpoints[i].customPoint->remove();
0619             markerpoints.removeAt(i);
0620             m_suppressChildRemoved = false;
0621         }
0622         remove(); // delete marker if textlabel was deleted, because there is no use case of this
0623     }
0624 
0625     d->retransform();
0626 }
0627 
0628 void InfoElement::childAdded(const AbstractAspect* child) {
0629     const auto* point = dynamic_cast<const CustomPoint*>(child);
0630     if (point) {
0631         auto* p = const_cast<CustomPoint*>(point);
0632         // otherwise Custom point must be patched to handle discrete curve points.
0633         // This makes it much easier
0634         p->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
0635         p->setParentGraphicsItem(graphicsItem());
0636         // Must be done after setCoordinateBindingEnabled, otherwise positionChanged will be called and
0637         // then the InfoElement position will be set incorrectly
0638         connect(point, &CustomPoint::positionChanged, this, &InfoElement::pointPositionChanged);
0639         connect(point, &CustomPoint::moveBegin, this, &InfoElement::moveElementBegin);
0640         connect(point, &CustomPoint::moveEnd, this, &InfoElement::moveElementEnd);
0641         return;
0642     }
0643 
0644     const auto* m_titleChild = dynamic_cast<const TextLabel*>(child);
0645     if (m_titleChild) {
0646         connect(m_title, &TextLabel::positionChanged, this, &InfoElement::labelPositionChanged);
0647         connect(m_title, &TextLabel::visibleChanged, this, &InfoElement::labelVisibleChanged);
0648         connect(m_title, &TextLabel::textWrapperChanged, this, &InfoElement::labelTextWrapperChanged);
0649         connect(m_title, &TextLabel::borderShapeChanged, this, &InfoElement::labelBorderShapeChanged);
0650         connect(m_title, &TextLabel::moveBegin, this, &InfoElement::moveElementBegin);
0651         connect(m_title, &TextLabel::moveEnd, this, &InfoElement::moveElementEnd);
0652         connect(m_title, &TextLabel::rotationAngleChanged, this, &InfoElement::retransform);
0653 
0654         auto* l = const_cast<TextLabel*>(m_titleChild);
0655         l->setParentGraphicsItem(graphicsItem());
0656     }
0657 }
0658 
0659 /*!
0660  * \brief InfoElement::currentValue
0661  * Calculates the new x position from
0662  * \param new_x
0663  * \return
0664  */
0665 int InfoElement::currentIndex(double x, double* found_x) {
0666     for (auto& markerpoint : markerpoints) {
0667         if (markerpoint.curve->name() == connectionLineCurveName()) {
0668             int index = markerpoint.curve->xColumn()->indexForValue(x);
0669 
0670             if (found_x && index >= 0) {
0671                 auto mode = markerpoint.curve->xColumn()->columnMode();
0672                 switch (mode) {
0673                 case AbstractColumn::ColumnMode::Double:
0674                 case AbstractColumn::ColumnMode::Integer:
0675                 case AbstractColumn::ColumnMode::BigInt:
0676                     *found_x = markerpoint.curve->xColumn()->valueAt(index);
0677                     break;
0678                 case AbstractColumn::ColumnMode::Text:
0679                     break;
0680                 case AbstractColumn::ColumnMode::DateTime:
0681                 case AbstractColumn::ColumnMode::Month:
0682                 case AbstractColumn::ColumnMode::Day:
0683                     *found_x = markerpoint.curve->xColumn()->dateTimeAt(index).toMSecsSinceEpoch();
0684                     break;
0685                 }
0686 
0687                 return index;
0688             }
0689         }
0690     }
0691 
0692     return -1;
0693 }
0694 
0695 double InfoElement::setMarkerpointPosition(double x) {
0696     // TODO: can be optimized when it will be checked if all markerpoints have the same xColumn, then the index m_index is the same
0697     Q_D(InfoElement);
0698     double x_new;
0699     double x_new_first = 0;
0700     for (int i = 0; i < markerpoints.length(); i++) {
0701         bool valueFound;
0702         double y = markerpoints[i].curve->y(x, x_new, valueFound);
0703         d->positionLogical = x_new;
0704         if (i == 0)
0705             x_new_first = x_new;
0706 
0707         if (valueFound) {
0708             m_suppressChildPositionChanged = true;
0709             auto* point = markerpoints[i].customPoint;
0710             point->graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, false);
0711             point->setUndoAware(false);
0712             point->setPositionLogical(QPointF(x_new, y));
0713             point->setUndoAware(false);
0714             point->graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
0715             m_suppressChildPositionChanged = false;
0716         }
0717     }
0718     return x_new_first;
0719 }
0720 
0721 /*!
0722  * Will be called, when the customPoint changes his position
0723  * @param pos
0724  */
0725 void InfoElement::pointPositionChanged(const WorksheetElement::PositionWrapper&) {
0726     if (m_suppressChildPositionChanged)
0727         return;
0728 
0729     const auto* point = dynamic_cast<CustomPoint*>(QObject::sender());
0730     if (!point)
0731         return;
0732 
0733     setPositionLogical(point->positionLogical().x());
0734 }
0735 
0736 void InfoElement::setParentGraphicsItem(QGraphicsItem* item) {
0737     Q_D(InfoElement);
0738     d->setParentItem(item);
0739     d->updatePosition();
0740 }
0741 
0742 void InfoElement::retransform() {
0743     Q_D(InfoElement);
0744     d->retransform();
0745 }
0746 
0747 void InfoElement::handleResize(double /*horizontalRatio*/, double /*verticalRatio*/, bool /*pageResize*/) {
0748 }
0749 
0750 // ##############################################################################
0751 // ######  Getter and setter methods ############################################
0752 // ##############################################################################
0753 
0754 /* ============================ getter methods ================= */
0755 BASIC_SHARED_D_READER_IMPL(InfoElement, double, positionLogical, positionLogical)
0756 BASIC_SHARED_D_READER_IMPL(InfoElement, int, gluePointIndex, gluePointIndex)
0757 BASIC_SHARED_D_READER_IMPL(InfoElement, QString, connectionLineCurveName, connectionLineCurveName)
0758 
0759 Line* InfoElement::verticalLine() const {
0760     Q_D(const InfoElement);
0761     return d->verticalLine;
0762 }
0763 
0764 Line* InfoElement::connectionLine() const {
0765     Q_D(const InfoElement);
0766     return d->connectionLine;
0767 }
0768 
0769 /* ============================ setter methods ================= */
0770 STD_SETTER_CMD_IMPL(InfoElement, SetPositionLogical, double, positionLogical)
0771 void InfoElement::setPositionLogical(double pos) {
0772     Q_D(InfoElement);
0773     double value = 0.;
0774     int index = currentIndex(pos, &value);
0775     if (index < 0)
0776         return;
0777 
0778     if (value != d->positionLogical) {
0779         d->m_index = index;
0780         exec(new InfoElementSetPositionLogicalCmd(d, pos, ki18n("%1: set position")));
0781         setMarkerpointPosition(value);
0782         m_setTextLabelText = true;
0783         m_title->setUndoAware(false);
0784         m_title->setText(createTextLabelText());
0785         m_title->setUndoAware(true);
0786         m_setTextLabelText = false;
0787         retransform();
0788         positionLogicalChanged(d->positionLogical);
0789     }
0790 }
0791 
0792 STD_SETTER_CMD_IMPL_F_S(InfoElement, SetGluePointIndex, int, gluePointIndex, retransform)
0793 void InfoElement::setGluePointIndex(int value) {
0794     Q_D(InfoElement);
0795     if (value != d->gluePointIndex)
0796         exec(new InfoElementSetGluePointIndexCmd(d, value, ki18n("%1: set gluepoint index")));
0797 }
0798 
0799 STD_SETTER_CMD_IMPL_F_S(InfoElement, SetConnectionLineCurveName, QString, connectionLineCurveName, retransform)
0800 void InfoElement::setConnectionLineCurveName(const QString& name) {
0801     Q_D(InfoElement);
0802     if (name.compare(d->connectionLineCurveName) != 0)
0803         exec(new InfoElementSetConnectionLineCurveNameCmd(d, name, ki18n("%1: set connectionline curve name")));
0804 }
0805 
0806 STD_SWAP_METHOD_SETTER_CMD_IMPL(InfoElement, SetVisible, bool, changeVisibility)
0807 void InfoElement::setVisible(bool on) {
0808     Q_D(InfoElement);
0809     if (on != isVisible())
0810         exec(new InfoElementSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible")));
0811 }
0812 
0813 // ##############################################################################
0814 // ######  SLOTs for changes triggered via QActions in the context menu  ########
0815 // ##############################################################################
0816 
0817 // ##############################################################################
0818 // ####################### Private implementation ###############################
0819 // ##############################################################################
0820 
0821 InfoElementPrivate::InfoElementPrivate(InfoElement* owner)
0822     : WorksheetElementPrivate(owner)
0823     , q(owner) {
0824     init();
0825 }
0826 
0827 InfoElementPrivate::InfoElementPrivate(InfoElement* owner, const XYCurve*)
0828     : WorksheetElementPrivate(owner)
0829     , q(owner) {
0830     init();
0831 }
0832 
0833 void InfoElementPrivate::init() {
0834     setFlag(QGraphicsItem::ItemIsMovable, false);
0835     setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
0836     setFlag(QGraphicsItem::ItemIsSelectable, true);
0837     setFlag(QGraphicsItem::ItemIsFocusable, true);
0838 
0839     setFlag(QGraphicsItem::ItemSendsGeometryChanges, false);
0840     setPos(QPointF(0, 0));
0841     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
0842 }
0843 
0844 /*!
0845     calculates the position and the bounding box of the InfoElement. Called on geometry changes.
0846     Or when the label or the point where moved
0847  */
0848 void InfoElementPrivate::retransform() {
0849     DEBUG(Q_FUNC_INFO)
0850     if (!q->m_title || q->markerpoints.isEmpty() || q->isLoading() || !parentItem())
0851         return;
0852 
0853     q->m_suppressChildPositionChanged = true;
0854 
0855     // new bounding rectangle
0856     const QRectF& rect = parentItem()->mapRectFromScene(q->plot()->rect());
0857     const QRectF& newBoundingRect = mapFromParent(rect).boundingRect();
0858     QDEBUG(Q_FUNC_INFO << ", rect = " << rect)
0859     QDEBUG(Q_FUNC_INFO << ", bounding rect = " << newBoundingRect)
0860 
0861     // TODO: why do I need to retransform the label and the custompoints?
0862     q->m_title->retransform();
0863     for (auto& markerpoint : q->markerpoints)
0864         markerpoint.customPoint->retransform();
0865 
0866     // determine the position to connect the line to
0867     QPointF pointPos;
0868     for (int i = 0; i < q->markerPointsCount(); ++i) {
0869         const auto* curve = q->markerpoints.at(i).curve;
0870         if (curve && curve->name() == connectionLineCurveName) {
0871             const auto& point = q->markerpoints.at(i).customPoint;
0872             const auto* cSystem = q->plot()->coordinateSystem(point->coordinateSystemIndex());
0873             pointPos = cSystem->mapLogicalToScene(point->positionLogical(), insidePlot, AbstractCoordinateSystem::MappingFlag::SuppressPageClippingVisible);
0874             break;
0875         }
0876     }
0877 
0878     if (!insidePlot)
0879         return;
0880 
0881     // use limit function like in the cursor! So the line will be drawn only till the border of the cartesian Plot
0882     QPointF m_titlePos;
0883     if (gluePointIndex < 0)
0884         m_titlePos = q->m_title->findNearestGluePoint(pointPos);
0885     else
0886         m_titlePos = q->m_title->gluePointAt(gluePointIndex).point;
0887 
0888     // connection line
0889     const QPointF m_titlePosItemCoords = mapFromParent(m_titlePos); // calculate item coords from scene coords
0890     const QPointF pointPosItemCoords = mapFromParent(mapPlotAreaToParent(pointPos)); // calculate item coords from scene coords
0891     if (newBoundingRect.contains(m_titlePosItemCoords) && newBoundingRect.contains(pointPosItemCoords))
0892         m_connectionLine = QLineF(m_titlePosItemCoords.x(), m_titlePosItemCoords.y(), pointPosItemCoords.x(), pointPosItemCoords.y());
0893     else
0894         m_connectionLine = QLineF();
0895     QDEBUG(Q_FUNC_INFO << ", connection line = " << m_connectionLine)
0896 
0897     // vertical line
0898     const QRectF& dataRect = mapFromParent(q->m_plot->dataRect()).boundingRect();
0899     xposLine = QLineF(pointPosItemCoords.x(), dataRect.bottom(), pointPosItemCoords.x(), dataRect.top());
0900     QDEBUG(Q_FUNC_INFO << ", vertical line " << xposLine)
0901 
0902     recalcShapeAndBoundingRect(newBoundingRect);
0903 
0904     q->m_suppressChildPositionChanged = false;
0905 }
0906 
0907 void InfoElementPrivate::updatePosition() {
0908     // TODO?
0909 }
0910 
0911 /*!
0912  * Repainting to update xposLine
0913  */
0914 void InfoElementPrivate::updateVerticalLine() {
0915     recalcShapeAndBoundingRect();
0916 }
0917 
0918 /*!
0919  * Repainting to updateConnectionLine
0920  */
0921 void InfoElementPrivate::updateConnectionLine() {
0922     recalcShapeAndBoundingRect();
0923 }
0924 
0925 bool InfoElementPrivate::changeVisibility(bool on) {
0926     bool oldValue = isVisible();
0927     setVisible(on);
0928     for (auto& markerpoint : q->markerpoints)
0929         markerpoint.customPoint->setVisible(on);
0930     if (q->m_title) {
0931         q->m_title->setUndoAware(false);
0932         q->m_title->setVisible(on);
0933         q->m_title->setUndoAware(true);
0934     }
0935     update(boundingRect());
0936     return oldValue;
0937 }
0938 
0939 void InfoElementPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) {
0940     if (!insidePlot)
0941         return;
0942 
0943     if (q->markerpoints.isEmpty())
0944         return;
0945 
0946     // do not draw connection line when the label is not visible
0947     if (connectionLine->style() != Qt::NoPen && q->m_title->isVisible()) {
0948         painter->setOpacity(connectionLine->opacity());
0949         painter->setPen(connectionLine->pen());
0950         painter->drawLine(m_connectionLine);
0951     }
0952 
0953     // draw vertical line, which connects all points together
0954     if (verticalLine->style() != Qt::NoPen) {
0955         painter->setOpacity(verticalLine->opacity());
0956         painter->setPen(verticalLine->pen());
0957         painter->drawLine(xposLine);
0958     }
0959 }
0960 
0961 void InfoElementPrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) {
0962     if (event->button() == Qt::MouseButton::LeftButton) {
0963         if (verticalLine->style() != Qt::NoPen) {
0964             const double width = verticalLine->pen().widthF();
0965             if (abs(xposLine.x1() - event->pos().x()) < ((width < 3) ? 3 : width)) {
0966                 if (!isSelected())
0967                     setSelected(true);
0968                 m_suppressKeyPressEvents = false;
0969                 oldMousePos = mapToParent(event->pos());
0970                 event->accept();
0971                 setFocus();
0972                 return;
0973             }
0974         } /* else {
0975              for (int i=0; i< q->markerPointsCount(); i++) {
0976                  InfoElement::MarkerPoints_T markerpoint =  q->markerPointAt(i);
0977                  //if (markerpoint.customPoint->symbolSize())
0978              }
0979          }*/
0980 
0981         // https://stackoverflow.com/questions/11604680/point-laying-near-line
0982         double dx12 = m_connectionLine.x2() - m_connectionLine.x1();
0983         double dy12 = m_connectionLine.y2() - m_connectionLine.y1();
0984         double vecLenght = sqrt(pow(dx12, 2) + pow(dy12, 2));
0985         QPointF unitvec(dx12 / vecLenght, dy12 / vecLenght);
0986 
0987         double dx1m = event->pos().x() - m_connectionLine.x1();
0988         double dy1m = event->pos().y() - m_connectionLine.y1();
0989 
0990         double dist_segm = abs(dx1m * unitvec.y() - dy1m * unitvec.x());
0991         double scalar_product = dx1m * unitvec.x() + dy1m * unitvec.y();
0992         // DEBUG("DIST_SEGMENT   " << dist_segm << "SCALAR_PRODUCT: " << scalar_product << "VEC_LENGTH: " << vecLenght);
0993 
0994         if (scalar_product > 0) {
0995             const double width = connectionLine->width();
0996             if (scalar_product < vecLenght && dist_segm < ((width < 3) ? 3 : width)) {
0997                 event->accept();
0998                 if (!isSelected())
0999                     setSelected(true);
1000                 oldMousePos = mapToParent(event->pos());
1001                 m_suppressKeyPressEvents = false;
1002                 event->accept();
1003                 setFocus();
1004                 return;
1005             }
1006         }
1007 
1008         m_suppressKeyPressEvents = true;
1009         event->ignore();
1010         if (isSelected())
1011             setSelected(false);
1012     }
1013     QGraphicsItem::mousePressEvent(event);
1014 }
1015 
1016 void InfoElementPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
1017     QPointF eventPos = mapToParent(event->pos());
1018     //  DEBUG("EventPos: " << eventPos.x() << " Y: " << eventPos.y());
1019     QPointF delta = eventPos - oldMousePos;
1020     if (delta == QPointF(0, 0))
1021         return;
1022 
1023     if (!q->cSystem->isValid())
1024         return;
1025     QPointF eventLogicPos = q->cSystem->mapSceneToLogical(eventPos, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1026     QPointF delta_logic = eventLogicPos - q->cSystem->mapSceneToLogical(oldMousePos);
1027 
1028     if (!q->m_title)
1029         return;
1030     if (q->markerpoints.isEmpty())
1031         return;
1032 
1033     // TODO: find better method to do this. It's inefficient.
1034     // Finding which curve should be used to find the new values
1035     double x = positionLogical;
1036     int activeIndex = 0;
1037     for (int i = 1; i < q->markerPointsCount(); i++) {
1038         if (q->markerpoints[i].curve->name().compare(connectionLineCurveName) == 0) {
1039             // not possible to use index, because when the number of elements in the columns of the curves are not the same there is a problem
1040             x = q->markerpoints[i].customPoint->positionLogical().x(); // q->markerpoints[i].curve->xColumn()->valueAt(m_index)
1041             activeIndex = i;
1042             break;
1043         }
1044     }
1045     x += delta_logic.x();
1046     auto xColumn = q->markerpoints[activeIndex].curve->xColumn();
1047     int xindex = xColumn->indexForValue(x);
1048     double x_new = NAN;
1049     if (xColumn->isNumeric())
1050         x_new = xColumn->valueAt(xindex);
1051     else
1052         x_new = xColumn->dateTimeAt(xindex).toMSecsSinceEpoch();
1053 
1054     auto pointPos = q->markerpoints[activeIndex].customPoint->positionLogical().x();
1055     if (abs(x_new - pointPos) > 0) {
1056         if ((xColumn->rowCount() - 1 == xindex && pointPos > x_new) || (xindex == 0 && pointPos < x_new)) {
1057             q->setPositionLogical(x_new);
1058         } else {
1059             oldMousePos = eventPos;
1060             q->setPositionLogical(x);
1061         }
1062     }
1063 }
1064 
1065 void InfoElementPrivate::keyPressEvent(QKeyEvent* event) {
1066     if (m_suppressKeyPressEvents) {
1067         event->ignore();
1068         return QGraphicsItem::keyPressEvent(event);
1069     }
1070 
1071     TextLabel::TextWrapper text;
1072     if (event->key() == Qt::Key_Right || event->key() == Qt::Key_Left) {
1073         int index;
1074         if (event->key() == Qt::Key_Right)
1075             index = 1;
1076         else
1077             index = -1;
1078 
1079         // problem: when curves have different number of samples, the points are anymore aligned
1080         // with the vertical line
1081         // find markerpoint to which the values matches (curvename is stored in connectionLineCurveName)
1082         for (int i = 0; i < q->markerPointsCount(); i++) {
1083             const auto* curve = q->markerpoints[i].curve;
1084             if (curve->name().compare(connectionLineCurveName) == 0) {
1085                 auto rowCount = curve->xColumn()->rowCount();
1086                 m_index += index;
1087                 if (m_index > rowCount - 1)
1088                     m_index = rowCount - 1;
1089                 if (m_index < 0)
1090                     m_index = 0;
1091                 if (curve->xColumn()->isNumeric())
1092                     q->setPositionLogical(curve->xColumn()->valueAt(m_index));
1093                 else
1094                     q->setPositionLogical(curve->xColumn()->dateTimeAt(m_index).toMSecsSinceEpoch());
1095                 break;
1096             }
1097         }
1098     }
1099 }
1100 
1101 bool InfoElementPrivate::activate(QPointF mouseScenePos, double /*maxDist*/) {
1102     if (!isVisible())
1103         return false;
1104 
1105     return m_shape.contains(mouseScenePos);
1106 }
1107 
1108 void InfoElementPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
1109     if (activate(event->pos())) {
1110         q->createContextMenu()->exec(event->screenPos());
1111         return;
1112     }
1113     QGraphicsItem::contextMenuEvent(event);
1114 }
1115 
1116 void InfoElementPrivate::recalcShape() {
1117     m_shape = QPainterPath();
1118 
1119     if (verticalLine->style() != Qt::PenStyle::NoPen) {
1120         QPainterPath path;
1121         path.moveTo(xposLine.p1());
1122         path.lineTo(xposLine.p2());
1123         m_shape.addPath(WorksheetElement::shapeFromPath(path, verticalLine->pen()));
1124     }
1125 
1126     if (connectionLine->style() != Qt::PenStyle::NoPen) {
1127         QPainterPath path;
1128         path.moveTo(m_connectionLine.p1());
1129         path.lineTo(m_connectionLine.p2());
1130         m_shape.addPath(WorksheetElement::shapeFromPath(path, connectionLine->pen()));
1131     }
1132 }
1133 
1134 void InfoElementPrivate::recalcShapeAndBoundingRect(const QRectF& rect) {
1135     prepareGeometryChange();
1136     m_boundingRectangle = rect;
1137     recalcShape();
1138     update(m_boundingRectangle);
1139 }
1140 
1141 void InfoElementPrivate::recalcShapeAndBoundingRect() {
1142     prepareGeometryChange();
1143     recalcShape();
1144     update(m_boundingRectangle);
1145 }
1146 
1147 // ##############################################################################
1148 // ##################  Serialization/Deserialization  ###########################
1149 // ##############################################################################
1150 void InfoElement::save(QXmlStreamWriter* writer) const {
1151     Q_D(const InfoElement);
1152 
1153     writer->writeStartElement(QStringLiteral("infoElement"));
1154     writeBasicAttributes(writer);
1155     writeCommentElement(writer);
1156 
1157     // general
1158     writer->writeStartElement(QStringLiteral("general"));
1159     writer->writeAttribute(QStringLiteral("position"), QString::number(d->positionLogical));
1160     writer->writeAttribute(QStringLiteral("curve"), d->connectionLineCurveName);
1161     writer->writeAttribute(QStringLiteral("gluePointIndex"), QString::number(d->gluePointIndex));
1162     writer->writeAttribute(QStringLiteral("markerIndex"), QString::number(d->m_index));
1163     writer->writeAttribute(QStringLiteral("plotRangeIndex"), QString::number(m_cSystemIndex));
1164     writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible()));
1165     writer->writeEndElement();
1166 
1167     // lines
1168     d->verticalLine->save(writer);
1169     d->connectionLine->save(writer);
1170 
1171     // text label
1172     m_title->save(writer);
1173 
1174     // custom points
1175     if (!markerpoints.isEmpty()) {
1176         writer->writeStartElement(QStringLiteral("points"));
1177         for (const auto& custompoint : markerpoints) {
1178             writer->writeStartElement(QStringLiteral("point"));
1179             writer->writeAttribute(QLatin1String("curvepath"), custompoint.curve->path());
1180             custompoint.customPoint->save(writer);
1181             writer->writeEndElement(); // close "point"
1182         }
1183         writer->writeEndElement(); // close "points"
1184     }
1185 
1186     writer->writeEndElement(); // close "infoElement"
1187 }
1188 
1189 bool InfoElement::load(XmlStreamReader* reader, bool preview) {
1190     if (!readBasicAttributes(reader))
1191         return false;
1192 
1193     Q_D(InfoElement);
1194 
1195     QXmlStreamAttributes attribs;
1196     QString str;
1197     QString curvePath;
1198 
1199     while (!reader->atEnd()) {
1200         reader->readNext();
1201         if (reader->isEndElement() && reader->name() == QLatin1String("infoElement"))
1202             break;
1203 
1204         if (!reader->isStartElement())
1205             continue;
1206 
1207         if (!preview && reader->name() == QLatin1String("comment")) {
1208             if (!readCommentElement(reader))
1209                 return false;
1210         } else if (reader->name() == QLatin1String("general")) {
1211             attribs = reader->attributes();
1212 
1213             READ_INT_VALUE_DIRECT("plotRangeIndex", m_cSystemIndex, int);
1214 
1215             str = attribs.value(QStringLiteral("visible")).toString();
1216             if (str.isEmpty())
1217                 reader->raiseMissingAttributeWarning(QStringLiteral("x"));
1218             else
1219                 setVisible(str.toInt());
1220 
1221             READ_DOUBLE_VALUE("position", positionLogical);
1222             READ_INT_VALUE("gluePointIndex", gluePointIndex, int);
1223             READ_INT_VALUE("markerIndex", m_index, int);
1224             READ_STRING_VALUE("curve", connectionLineCurveName);
1225         } else if (reader->name() == QLatin1String("verticalLine")) {
1226             d->verticalLine->load(reader, preview);
1227         } else if (reader->name() == QLatin1String("connectionLine")) {
1228             d->connectionLine->load(reader, preview);
1229         } else if (reader->name() == QLatin1String("textLabel")) {
1230             if (!m_title) {
1231                 m_title = new TextLabel(i18n("Label"), m_plot);
1232                 m_title->setIsLoading(true);
1233                 this->addChild(m_title);
1234             }
1235             if (!m_title->load(reader, preview))
1236                 return false;
1237         } else if (reader->name() == QLatin1String("customPoint")) {
1238             if (curvePath.isEmpty()) // safety check in case the xml is broken
1239                 continue;
1240 
1241             auto* point = new CustomPoint(m_plot, QString());
1242             point->setIsLoading(true);
1243             if (!point->load(reader, preview)) {
1244                 delete point;
1245                 return false;
1246             }
1247             this->addChild(point);
1248             addCurvePath(curvePath, point);
1249             curvePath.clear();
1250         } else if (reader->name() == QLatin1String("point")) {
1251             attribs = reader->attributes();
1252             curvePath = attribs.value(QStringLiteral("curvepath")).toString();
1253         } else { // unknown element
1254             reader->raiseUnknownElementWarning();
1255             if (!reader->skipToEndElement())
1256                 return false;
1257         }
1258     }
1259 
1260     return true;
1261 }
1262 
1263 // ##############################################################################
1264 // #########################  Theme management ##################################
1265 // ##############################################################################
1266 void InfoElement::loadThemeConfig(const KConfig& config) {
1267     // use the color for the axis line from the theme also for info element's lines
1268     const KConfigGroup& group = config.group(QStringLiteral("Axis"));
1269 
1270     const QColor& themeColor = group.readEntry(QStringLiteral("LineColor"), QColor(Qt::black));
1271     Q_D(InfoElement);
1272     d->verticalLine->loadThemeConfig(group, themeColor);
1273     d->connectionLine->loadThemeConfig(group, themeColor);
1274 
1275     // load the theme for all the children
1276     const auto& children = this->children<WorksheetElement>(ChildIndexFlag::IncludeHidden);
1277     for (auto* child : children)
1278         child->loadThemeConfig(config);
1279 }