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

0001 /*
0002     File                 : Datapicker.cpp
0003     Project              : LabPlot
0004     Description          : Datapicker
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
0007     SPDX-FileCopyrightText: 2015-2022 Alexander Semke <alexander.semke@web.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "Datapicker.h"
0013 #include "backend/datapicker/DatapickerCurve.h"
0014 #include "backend/datapicker/DatapickerImage.h"
0015 #include "backend/datapicker/DatapickerPoint.h"
0016 #include "backend/datapicker/Transform.h"
0017 #include "backend/lib/XmlStreamReader.h"
0018 #include "backend/spreadsheet/Spreadsheet.h"
0019 #include "commonfrontend/datapicker/DatapickerView.h"
0020 
0021 #include "QIcon"
0022 #include <KLocalizedString>
0023 #include <QGraphicsScene>
0024 
0025 /**
0026  * \class Datapicker
0027  * \brief Top-level container for DatapickerCurve and DatapickerImage.
0028  * \ingroup backend
0029  */
0030 Datapicker::Datapicker(const QString& name, const bool loading)
0031     : AbstractPart(name, AspectType::Datapicker)
0032     , m_transform(new Transform()) {
0033     connect(this, &Datapicker::childAspectAdded, this, &Datapicker::handleAspectAdded);
0034     connect(this, &Datapicker::childAspectAboutToBeRemoved, this, &Datapicker::handleAspectAboutToBeRemoved);
0035 
0036     if (!loading)
0037         init();
0038 }
0039 
0040 Datapicker::~Datapicker() {
0041     delete m_transform;
0042 }
0043 
0044 void Datapicker::init() {
0045     m_image = new DatapickerImage(i18n("Plot"));
0046     m_image->setHidden(true);
0047     setUndoAware(false);
0048     addChild(m_image);
0049     setUndoAware(true);
0050 
0051     connect(m_image, &DatapickerImage::statusInfo, this, &Datapicker::statusInfo);
0052 }
0053 
0054 /*!
0055     Returns an icon to be used in the project explorer.
0056 */
0057 QIcon Datapicker::icon() const {
0058     return QIcon::fromTheme(QLatin1String("color-picker-black"));
0059 }
0060 
0061 /*!
0062  * Returns a new context menu. The caller takes ownership of the menu.
0063  */
0064 QMenu* Datapicker::createContextMenu() {
0065     QMenu* menu = AbstractPart::createContextMenu();
0066     m_image->createContextMenu(menu);
0067     return menu;
0068 }
0069 
0070 QWidget* Datapicker::view() const {
0071     if (!m_partView) {
0072         m_view = new DatapickerView(const_cast<Datapicker*>(this));
0073         m_partView = m_view;
0074         connect(this, &Datapicker::viewAboutToBeDeleted, [this]() {
0075             m_view = nullptr;
0076         });
0077     }
0078     return m_partView;
0079 }
0080 
0081 bool Datapicker::exportView() const {
0082     auto* s = currentSpreadsheet();
0083     if (s)
0084         return s->exportView();
0085     else
0086         return m_image->exportView();
0087 }
0088 
0089 bool Datapicker::printView() {
0090     auto* s = currentSpreadsheet();
0091     if (s)
0092         return s->printView();
0093     else
0094         return m_image->printView();
0095 }
0096 
0097 bool Datapicker::printPreview() const {
0098     auto* s = currentSpreadsheet();
0099     if (s)
0100         return s->printPreview();
0101     else
0102         return m_image->printPreview();
0103 }
0104 
0105 DatapickerCurve* Datapicker::activeCurve() {
0106     return m_activeCurve;
0107 }
0108 
0109 Spreadsheet* Datapicker::currentSpreadsheet() const {
0110     if (!m_view)
0111         return nullptr;
0112 
0113     const int index = m_view->currentIndex();
0114     if (index > 0) {
0115         auto* curve = child<DatapickerCurve>(index - 1);
0116         return curve->child<Spreadsheet>(0);
0117     }
0118     return nullptr;
0119 }
0120 
0121 DatapickerImage* Datapicker::image() const {
0122     return m_image;
0123 }
0124 
0125 /*!
0126     this slot is called when a datapicker child is selected in the project explorer.
0127     emits \c datapickerItemSelected() to forward this event to the \c DatapickerView
0128     in order to select the corresponding tab.
0129  */
0130 void Datapicker::childSelected(const AbstractAspect* aspect) {
0131     Q_ASSERT(aspect);
0132     m_activeCurve = dynamic_cast<DatapickerCurve*>(const_cast<AbstractAspect*>(aspect));
0133 
0134     int index = -1;
0135     if (m_activeCurve) {
0136         // if one of the curves is currently selected, select the image with the plot (the very first child)
0137         index = 0;
0138         Q_EMIT statusInfo(i18n("%1, active curve \"%2\"", this->name(), m_activeCurve->name()));
0139         Q_EMIT requestUpdateActions();
0140     } else if (aspect) {
0141         const auto* curve = aspect->ancestor<const DatapickerCurve>();
0142         index = indexOfChild<AbstractAspect>(curve);
0143         ++index; //+1 because of the hidden plot image being the first child and shown in the first tab in the view
0144     }
0145 
0146     Q_EMIT datapickerItemSelected(index);
0147 }
0148 
0149 /*!
0150     this slot is called when a worksheet element is deselected in the project explorer.
0151  */
0152 void Datapicker::childDeselected(const AbstractAspect* aspect) {
0153     Q_UNUSED(aspect);
0154 }
0155 
0156 /*!
0157  *  Emits the signal to select or to deselect the datapicker item (spreadsheet or image) with the index \c index
0158  *  in the project explorer, if \c selected=true or \c selected=false, respectively.
0159  *  The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer.
0160  *  This function is called in \c DatapickerView when the current tab was changed
0161  */
0162 void Datapicker::setChildSelectedInView(int index, bool selected) {
0163     // select/deselect the datapicker itself if the first tab "representing" the plot image and the curves was selected in the view
0164     if (index == 0) {
0165         if (selected)
0166             Q_EMIT childAspectSelectedInView(this);
0167         else {
0168             Q_EMIT childAspectDeselectedInView(this);
0169 
0170             // deselect also all curves (they don't have any tab index in the view) that were potentially selected before
0171             for (const auto* curve : children<const DatapickerCurve>())
0172                 Q_EMIT childAspectDeselectedInView(curve);
0173         }
0174 
0175         return;
0176     }
0177 
0178     --index; //-1 because of the first tab in the view being reserved for the plot image and curves
0179 
0180     // select/deselect the data spreadhseets
0181     auto spreadsheets = children<const Spreadsheet>(ChildIndexFlag::Recursive);
0182     const AbstractAspect* aspect = spreadsheets.at(index);
0183     if (selected) {
0184         Q_EMIT childAspectSelectedInView(aspect);
0185 
0186         // deselect the datapicker in the project explorer, if a child (spreadsheet or image) was selected.
0187         // prevents unwanted multiple selection with datapicker if it was selected before.
0188         Q_EMIT childAspectDeselectedInView(this);
0189     } else {
0190         Q_EMIT childAspectDeselectedInView(aspect);
0191 
0192         // deselect also all children that were potentially selected before (columns of a spreadsheet)
0193         for (const auto* child : aspect->children<const AbstractAspect>())
0194             Q_EMIT childAspectDeselectedInView(child);
0195     }
0196 }
0197 
0198 /*!
0199     Selects or deselects the datapicker or its current active curve in the project explorer.
0200     This function is called in \c DatapickerImageView.
0201 */
0202 void Datapicker::setSelectedInView(const bool b) {
0203     if (b)
0204         Q_EMIT childAspectSelectedInView(this);
0205     else
0206         Q_EMIT childAspectDeselectedInView(this);
0207 }
0208 
0209 /*!
0210  * \brief Datapicker::addNewPoint
0211  * \param pos position in scene coordinates
0212  * \param parentAspect
0213  */
0214 void Datapicker::addNewPoint(QPointF pos, AbstractAspect* parentAspect) {
0215     auto points = parentAspect->children<DatapickerPoint>(ChildIndexFlag::IncludeHidden);
0216 
0217     auto* newPoint = new DatapickerPoint(i18n("Point %1", points.count() + 1));
0218     newPoint->setHidden(true);
0219 
0220     beginMacro(i18n("%1: add %2", parentAspect->name(), newPoint->name()));
0221     parentAspect->addChild(newPoint);
0222     const QPointF oldPos = newPoint->position();
0223     newPoint->setPosition(pos);
0224     if (oldPos == pos) {
0225         // Just if pos == oldPos, setPosition will not trigger retransform() then
0226         newPoint->retransform();
0227     }
0228 
0229     auto* datapickerCurve = static_cast<DatapickerCurve*>(parentAspect);
0230     if (m_image == parentAspect) {
0231         auto axisPoints = m_image->axisPoints();
0232         axisPoints.scenePos[points.count()].setX(pos.x());
0233         axisPoints.scenePos[points.count()].setY(pos.y());
0234         m_image->setAxisPoints(axisPoints);
0235         newPoint->setIsReferencePoint(true);
0236         connect(newPoint, &DatapickerPoint::pointSelected, m_image, QOverload<const DatapickerPoint*>::of(&DatapickerImage::referencePointSelected));
0237     } else if (datapickerCurve) {
0238         newPoint->initErrorBar(datapickerCurve->curveErrorTypes());
0239         // datapickerCurve->updatePoint(newPoint);
0240     }
0241 
0242     endMacro();
0243     Q_EMIT requestUpdateActions();
0244 }
0245 
0246 bool Datapicker::xDateTime() const {
0247     return m_image->axisPoints().datetime;
0248 }
0249 
0250 Vector3D Datapicker::mapSceneToLogical(QPointF point) const {
0251     return m_transform->mapSceneToLogical(point, m_image->axisPoints());
0252 }
0253 
0254 Vector3D Datapicker::mapSceneLengthToLogical(QPointF point) const {
0255     return m_transform->mapSceneLengthToLogical(point, m_image->axisPoints());
0256 }
0257 
0258 void Datapicker::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) {
0259     const auto* curve = qobject_cast<const DatapickerCurve*>(aspect);
0260     if (curve) {
0261         // clear scene
0262         const auto& points = curve->children<DatapickerPoint>(ChildIndexFlag::IncludeHidden);
0263         for (auto* point : points)
0264             handleChildAspectAboutToBeRemoved(point);
0265 
0266         if (curve == m_activeCurve) {
0267             m_activeCurve = nullptr;
0268             Q_EMIT statusInfo(QString());
0269         }
0270     } else
0271         handleChildAspectAboutToBeRemoved(aspect);
0272 
0273     Q_EMIT requestUpdateActions();
0274 }
0275 
0276 void Datapicker::handleAspectAdded(const AbstractAspect* aspect) {
0277     const auto* addedPoint = qobject_cast<const DatapickerPoint*>(aspect);
0278     const auto* curve = qobject_cast<const DatapickerCurve*>(aspect);
0279     if (addedPoint)
0280         handleChildAspectAdded(addedPoint);
0281     else if (curve) {
0282         connect(m_image, &DatapickerImage::axisPointsChanged, curve, &DatapickerCurve::updatePoints);
0283         auto points = curve->children<DatapickerPoint>(ChildIndexFlag::IncludeHidden);
0284         for (auto* point : points)
0285             handleChildAspectAdded(point);
0286     } else
0287         return;
0288 
0289     qreal zVal = 0;
0290     const auto& points = m_image->children<DatapickerPoint>(ChildIndexFlag::IncludeHidden);
0291     for (auto* point : points)
0292         point->graphicsItem()->setZValue(zVal++);
0293 
0294     for (const auto* curve : children<DatapickerCurve>()) {
0295         for (auto* point : curve->children<DatapickerPoint>(ChildIndexFlag::IncludeHidden))
0296             point->graphicsItem()->setZValue(zVal++);
0297     }
0298 
0299     Q_EMIT requestUpdateActions();
0300 }
0301 
0302 void Datapicker::handleChildAspectAboutToBeRemoved(const AbstractAspect* aspect) {
0303     const auto* removedPoint = qobject_cast<const DatapickerPoint*>(aspect);
0304     if (removedPoint) {
0305         QGraphicsItem* item = removedPoint->graphicsItem();
0306         Q_ASSERT(item != nullptr);
0307         Q_ASSERT(m_image != nullptr);
0308         m_image->scene()->removeItem(item);
0309     }
0310 }
0311 
0312 void Datapicker::handleChildAspectAdded(const AbstractAspect* aspect) {
0313     const auto* addedPoint = qobject_cast<const DatapickerPoint*>(aspect);
0314     if (addedPoint) {
0315         QGraphicsItem* item = addedPoint->graphicsItem();
0316         Q_ASSERT(item != nullptr);
0317         Q_ASSERT(m_image != nullptr);
0318         m_image->scene()->addItem(item);
0319     }
0320 }
0321 
0322 // ##############################################################################
0323 // ##################  Serialization/Deserialization  ###########################
0324 // ##############################################################################
0325 
0326 //! Save as XML
0327 void Datapicker::save(QXmlStreamWriter* writer) const {
0328     writer->writeStartElement(QStringLiteral("datapicker"));
0329     writeBasicAttributes(writer);
0330     writeCommentElement(writer);
0331 
0332     // serialize all children
0333     for (auto* child : children<AbstractAspect>(ChildIndexFlag::IncludeHidden))
0334         child->save(writer);
0335 
0336     writer->writeEndElement(); // close "datapicker" section
0337 }
0338 
0339 //! Load from XML
0340 bool Datapicker::load(XmlStreamReader* reader, bool preview) {
0341     if (!readBasicAttributes(reader))
0342         return false;
0343 
0344     while (!reader->atEnd()) {
0345         reader->readNext();
0346         if (reader->isEndElement() && reader->name() == QLatin1String("datapicker"))
0347             break;
0348 
0349         if (!reader->isStartElement())
0350             continue;
0351 
0352         if (reader->name() == QLatin1String("comment")) {
0353             if (!readCommentElement(reader))
0354                 return false;
0355         } else if (reader->name() == QLatin1String("datapickerImage")) {
0356             auto* plot = new DatapickerImage(i18n("Plot"), true);
0357             if (!plot->load(reader, preview)) {
0358                 delete plot;
0359                 return false;
0360             } else {
0361                 plot->setHidden(true);
0362                 addChild(plot);
0363                 m_image = plot;
0364             }
0365         } else if (reader->name() == QLatin1String("datapickerCurve")) {
0366             auto* curve = new DatapickerCurve(QString());
0367             if (!curve->load(reader, preview)) {
0368                 delete curve;
0369                 return false;
0370             } else
0371                 addChild(curve);
0372         } else { // unknown element
0373             reader->raiseWarning(i18n("unknown datapicker element '%1'", reader->name().toString()));
0374             if (!reader->skipToEndElement())
0375                 return false;
0376         }
0377     }
0378 
0379     for (auto* aspect : children<AbstractAspect>(ChildIndexFlag::IncludeHidden)) {
0380         for (auto* point : aspect->children<DatapickerPoint>(ChildIndexFlag::IncludeHidden))
0381             handleAspectAdded(point);
0382     }
0383 
0384     return true;
0385 }