File indexing completed on 2025-01-26 03:34:10

0001 /*
0002     File                 : CustomPoint.cpp
0003     Project              : LabPlot
0004     Description          : Custom user-defined point on the plot
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
0007     SPDX-FileCopyrightText: 2015-2021 Alexander Semke <alexander.semke@web.de>
0008     SPDX-FileCopyrightText: 2020 Martin Marmsoler <martin.marmsoler@gmail.com>
0009     SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn>
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include "CustomPoint.h"
0014 #include "CustomPointPrivate.h"
0015 #include "backend/core/Project.h"
0016 #include "backend/lib/XmlStreamReader.h"
0017 #include "backend/lib/commandtemplates.h"
0018 #include "backend/lib/macros.h"
0019 #include "backend/worksheet/Worksheet.h"
0020 #include "backend/worksheet/plots/PlotArea.h"
0021 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h"
0022 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0023 #include "backend/worksheet/plots/cartesian/Symbol.h"
0024 
0025 #include <QGraphicsSceneMouseEvent>
0026 #include <QMenu>
0027 #include <QPainter>
0028 
0029 #include <KConfig>
0030 #include <KConfigGroup>
0031 #include <KLocalizedString>
0032 
0033 using Dimension = CartesianCoordinateSystem::Dimension;
0034 
0035 /**
0036  * \class CustomPoint
0037  * \brief A customizable point.
0038  *
0039  * The position can be either specified by mouse events or by providing the
0040  * x- and y- coordinates in parent's coordinate system
0041  */
0042 
0043 CustomPoint::CustomPoint(CartesianPlot* plot, const QString& name)
0044     : WorksheetElement(name, new CustomPointPrivate(this), AspectType::CustomPoint) {
0045     m_plot = plot;
0046     DEBUG(Q_FUNC_INFO << ", cSystem index = " << m_cSystemIndex)
0047     DEBUG(Q_FUNC_INFO << ", plot cSystem count = " << m_plot->coordinateSystemCount())
0048     cSystem = dynamic_cast<const CartesianCoordinateSystem*>(m_plot->coordinateSystem(m_cSystemIndex));
0049 
0050     init();
0051 }
0052 
0053 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0054 // and is deleted during the cleanup in QGraphicsScene
0055 CustomPoint::~CustomPoint() = default;
0056 
0057 void CustomPoint::init() {
0058     Q_D(CustomPoint);
0059 
0060     // default position
0061     if (plot()) {
0062         d->coordinateBindingEnabled = true; // By default on
0063         auto cs = plot()->coordinateSystem(plot()->defaultCoordinateSystemIndex());
0064         const auto x = m_plot->range(Dimension::X, cs->index(Dimension::X)).center();
0065         const auto y = m_plot->range(Dimension::Y, cs->index(Dimension::Y)).center();
0066         DEBUG(Q_FUNC_INFO << ", x/y pos = " << x << " / " << y)
0067         d->positionLogical = QPointF(x, y);
0068     } else
0069         d->position.point = QPointF(0, 0);
0070     d->updatePosition(); // To update also scene coordinates
0071 
0072     // initialize the symbol
0073     d->symbol = new Symbol(QString());
0074     addChild(d->symbol);
0075     d->symbol->setHidden(true);
0076     connect(d->symbol, &Symbol::updateRequested, [=] {
0077         d->recalcShapeAndBoundingRect();
0078     });
0079     connect(d->symbol, &Symbol::updatePixmapRequested, [=] {
0080         d->update();
0081     });
0082     KConfig config;
0083     d->symbol->init(config.group(QStringLiteral("CustomPoint")));
0084 
0085     initActions();
0086 }
0087 
0088 void CustomPoint::initActions() {
0089 }
0090 
0091 /*!
0092     Returns an icon to be used in the project explorer.
0093 */
0094 QIcon CustomPoint::icon() const {
0095     return QIcon::fromTheme(QStringLiteral("draw-cross"));
0096 }
0097 
0098 QMenu* CustomPoint::createContextMenu() {
0099     // no context menu if the custom point is a child of an InfoElement,
0100     // everything is controlled by the parent
0101     if (parentAspect()->type() == AspectType::InfoElement)
0102         return nullptr;
0103 
0104     return WorksheetElement::createContextMenu();
0105     ;
0106 }
0107 
0108 void CustomPoint::retransform() {
0109     DEBUG(Q_FUNC_INFO)
0110     Q_D(CustomPoint);
0111     d->retransform();
0112 }
0113 
0114 void CustomPoint::handleResize(double /*horizontalRatio*/, double /*verticalRatio*/, bool /*pageResize*/) {
0115 }
0116 
0117 Symbol* CustomPoint::symbol() const {
0118     Q_D(const CustomPoint);
0119     return d->symbol;
0120 }
0121 
0122 // ##############################################################################
0123 // ####################### Private implementation ###############################
0124 // ##############################################################################
0125 CustomPointPrivate::CustomPointPrivate(CustomPoint* owner)
0126     : WorksheetElementPrivate(owner)
0127     , q(owner) {
0128     setFlag(QGraphicsItem::ItemSendsGeometryChanges);
0129     setFlag(QGraphicsItem::ItemIsMovable);
0130     setFlag(QGraphicsItem::ItemIsSelectable);
0131     setFlag(QGraphicsItem::ItemIsFocusable);
0132     setAcceptHoverEvents(true);
0133 }
0134 
0135 const CartesianPlot* CustomPointPrivate::plot() {
0136     return q->m_plot;
0137 }
0138 
0139 /*!
0140     calculates the position and the bounding box of the item/point. Called on geometry or properties changes.
0141  */
0142 void CustomPointPrivate::retransform() {
0143     DEBUG(Q_FUNC_INFO)
0144     if (suppressRetransform || q->isLoading())
0145         return;
0146 
0147     updatePosition(); // needed, because CartesianPlot calls retransform if some operations are done
0148     recalcShapeAndBoundingRect();
0149 }
0150 
0151 /*!
0152   recalculates the outer bounds and the shape of the item.
0153 */
0154 void CustomPointPrivate::recalcShapeAndBoundingRect() {
0155     prepareGeometryChange();
0156 
0157     m_shape = QPainterPath();
0158     if (insidePlot && symbol->style() != Symbol::Style::NoSymbols) {
0159         QPainterPath path = Symbol::stylePath(symbol->style());
0160 
0161         QTransform trafo;
0162         trafo.scale(symbol->size(), symbol->size());
0163         path = trafo.map(path);
0164         trafo.reset();
0165 
0166         if (symbol->rotationAngle() != 0.) {
0167             trafo.rotate(symbol->rotationAngle());
0168             path = trafo.map(path);
0169         }
0170 
0171         m_shape.addPath(WorksheetElement::shapeFromPath(trafo.map(path), symbol->pen()));
0172         m_boundingRectangle = m_shape.boundingRect();
0173     }
0174 }
0175 
0176 void CustomPointPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) {
0177     if (!insidePlot)
0178         return;
0179 
0180     if (symbol->style() != Symbol::Style::NoSymbols) {
0181         painter->setOpacity(symbol->opacity());
0182         painter->setPen(symbol->pen());
0183         painter->setBrush(symbol->brush());
0184         painter->drawPath(m_shape);
0185     }
0186 
0187     if (m_hovered && !isSelected() && !q->isPrinting()) {
0188         painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine));
0189         painter->drawPath(m_shape);
0190     }
0191 
0192     if (isSelected() && !q->isPrinting()) {
0193         painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine));
0194         painter->drawPath(m_shape);
0195     }
0196 }
0197 
0198 void CustomPointPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
0199     // don't move when the parent is a InfoElement, because there
0200     // the custompoint position changes by the mouse are not allowed
0201     if (q->parentAspect()->type() == AspectType::InfoElement)
0202         return;
0203 
0204     WorksheetElementPrivate::mouseReleaseEvent(event);
0205 }
0206 
0207 // ##############################################################################
0208 // ##################  Serialization/Deserialization  ###########################
0209 // ##############################################################################
0210 //! Save as XML
0211 void CustomPoint::save(QXmlStreamWriter* writer) const {
0212     Q_D(const CustomPoint);
0213 
0214     writer->writeStartElement(QStringLiteral("customPoint"));
0215     writeBasicAttributes(writer);
0216     writeCommentElement(writer);
0217 
0218     // geometry
0219     writer->writeStartElement(QStringLiteral("geometry"));
0220     WorksheetElement::save(writer);
0221     writer->writeEndElement();
0222 
0223     d->symbol->save(writer);
0224 
0225     writer->writeEndElement(); // close "CustomPoint" section
0226 }
0227 
0228 //! Load from XML
0229 bool CustomPoint::load(XmlStreamReader* reader, bool preview) {
0230     Q_D(CustomPoint);
0231 
0232     if (!readBasicAttributes(reader))
0233         return false;
0234 
0235     QXmlStreamAttributes attribs;
0236     QString str;
0237 
0238     while (!reader->atEnd()) {
0239         reader->readNext();
0240         if (reader->isEndElement() && reader->name() == QLatin1String("customPoint"))
0241             break;
0242 
0243         if (!reader->isStartElement())
0244             continue;
0245 
0246         if (!preview && reader->name() == QLatin1String("comment")) {
0247             if (!readCommentElement(reader))
0248                 return false;
0249         } else if (!preview && reader->name() == QLatin1String("geometry")) {
0250             WorksheetElement::load(reader, preview);
0251             if (project()->xmlVersion() < 6) {
0252                 // Before version 6 the position in the file was always a logical position
0253                 d->positionLogical = d->position.point;
0254                 d->position.point = QPointF(0, 0);
0255                 d->coordinateBindingEnabled = true;
0256             }
0257         } else if (!preview && reader->name() == QLatin1String("symbol")) {
0258             d->symbol->load(reader, preview);
0259         } else { // unknown element
0260             reader->raiseUnknownElementWarning();
0261             if (!reader->skipToEndElement())
0262                 return false;
0263         }
0264     }
0265     return true;
0266 }