File indexing completed on 2024-05-12 15:26:40

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