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 }