File indexing completed on 2025-01-26 03:34:14
0001 /* 0002 File : ReferenceLine.cpp 0003 Project : LabPlot 0004 Description : Custom user-defined point on the plot 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2020-2022 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "ReferenceLine.h" 0013 #include "ReferenceLinePrivate.h" 0014 #include "backend/lib/XmlStreamReader.h" 0015 #include "backend/lib/commandtemplates.h" 0016 #include "backend/worksheet/Line.h" 0017 #include "backend/worksheet/Worksheet.h" 0018 #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" 0019 #include "backend/worksheet/plots/cartesian/CartesianPlot.h" 0020 #include "kdefrontend/GuiTools.h" 0021 0022 #include <QActionGroup> 0023 #include <QGraphicsSceneMouseEvent> 0024 #include <QMenu> 0025 #include <QPainter> 0026 0027 #include <KConfig> 0028 #include <KConfigGroup> 0029 #include <KLocalizedString> 0030 0031 using Dimension = CartesianCoordinateSystem::Dimension; 0032 0033 /** 0034 * \class ReferenceLine 0035 * \brief A customizable point. 0036 * 0037 * The position can be either specified by mouse events or by providing the 0038 * x- and y- coordinates in parent's coordinate system 0039 */ 0040 0041 ReferenceLine::ReferenceLine(CartesianPlot* plot, const QString& name) 0042 : WorksheetElement(name, new ReferenceLinePrivate(this), AspectType::ReferenceLine) { 0043 m_plot = plot; 0044 init(); 0045 } 0046 0047 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0048 // and is deleted during the cleanup in QGraphicsScene 0049 ReferenceLine::~ReferenceLine() = default; 0050 0051 void ReferenceLine::init() { 0052 Q_D(ReferenceLine); 0053 0054 KConfig config; 0055 KConfigGroup group = config.group(QStringLiteral("ReferenceLine")); 0056 0057 d->orientation = (Orientation)group.readEntry(QStringLiteral("Orientation"), static_cast<int>(Orientation::Vertical)); 0058 switch (d->orientation) { 0059 case WorksheetElement::Orientation::Horizontal: 0060 d->position.positionLimit = WorksheetElement::PositionLimit::Y; 0061 break; 0062 case WorksheetElement::Orientation::Vertical: 0063 d->position.positionLimit = WorksheetElement::PositionLimit::X; 0064 break; 0065 case WorksheetElement::Orientation::Both: 0066 d->position.positionLimit = WorksheetElement::PositionLimit::None; 0067 break; 0068 } 0069 0070 if (plot()) { 0071 d->coordinateBindingEnabled = true; 0072 // default position 0073 auto cs = plot()->coordinateSystem(plot()->defaultCoordinateSystemIndex()); 0074 const auto x = m_plot->range(Dimension::X, cs->index(Dimension::X)).center(); 0075 const auto y = m_plot->range(Dimension::Y, cs->index(Dimension::Y)).center(); 0076 DEBUG(Q_FUNC_INFO << ", x/y pos = " << x << " / " << y) 0077 d->positionLogical = QPointF(x, y); 0078 } else 0079 d->position.point = QPointF(0, 0); 0080 d->updatePosition(); // To update also scene coordinates 0081 0082 // line 0083 d->line = new Line(QString()); 0084 d->line->setHidden(true); 0085 addChild(d->line); 0086 d->line->init(group); 0087 connect(d->line, &Line::updatePixmapRequested, [=] { 0088 d->update(); 0089 }); 0090 connect(d->line, &Line::updateRequested, [=] { 0091 d->recalcShapeAndBoundingRect(); 0092 }); 0093 } 0094 0095 /*! 0096 Returns an icon to be used in the project explorer. 0097 */ 0098 QIcon ReferenceLine::icon() const { 0099 return QIcon::fromTheme(QStringLiteral("draw-line")); 0100 } 0101 0102 void ReferenceLine::initActions() { 0103 // Orientation 0104 auto* orientationActionGroup = new QActionGroup(this); 0105 orientationActionGroup->setExclusive(true); 0106 connect(orientationActionGroup, &QActionGroup::triggered, this, &ReferenceLine::orientationChangedSlot); 0107 0108 orientationHorizontalAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal")), i18n("Horizontal"), orientationActionGroup); 0109 orientationHorizontalAction->setCheckable(true); 0110 0111 orientationVerticalAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-vertical")), i18n("Vertical"), orientationActionGroup); 0112 orientationVerticalAction->setCheckable(true); 0113 0114 // Line 0115 lineStyleActionGroup = new QActionGroup(this); 0116 lineStyleActionGroup->setExclusive(true); 0117 connect(lineStyleActionGroup, &QActionGroup::triggered, this, &ReferenceLine::lineStyleChanged); 0118 0119 lineColorActionGroup = new QActionGroup(this); 0120 lineColorActionGroup->setExclusive(true); 0121 connect(lineColorActionGroup, &QActionGroup::triggered, this, &ReferenceLine::lineColorChanged); 0122 } 0123 0124 void ReferenceLine::initMenus() { 0125 this->initActions(); 0126 0127 // Orientation 0128 orientationMenu = new QMenu(i18n("Orientation")); 0129 orientationMenu->setIcon(QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal"))); 0130 orientationMenu->addAction(orientationHorizontalAction); 0131 orientationMenu->addAction(orientationVerticalAction); 0132 0133 // Line 0134 lineMenu = new QMenu(i18n("Line")); 0135 lineMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-line"))); 0136 lineStyleMenu = new QMenu(i18n("Style"), lineMenu); 0137 lineStyleMenu->setIcon(QIcon::fromTheme(QStringLiteral("object-stroke-style"))); 0138 lineMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-line"))); 0139 lineMenu->addMenu(lineStyleMenu); 0140 0141 lineColorMenu = new QMenu(i18n("Color"), lineMenu); 0142 lineColorMenu->setIcon(QIcon::fromTheme(QStringLiteral("fill-color"))); 0143 GuiTools::fillColorMenu(lineColorMenu, lineColorActionGroup); 0144 lineMenu->addMenu(lineColorMenu); 0145 } 0146 0147 QMenu* ReferenceLine::createContextMenu() { 0148 if (!orientationMenu) 0149 initMenus(); 0150 0151 QMenu* menu = WorksheetElement::createContextMenu(); 0152 QAction* visibilityAction = this->visibilityAction(); 0153 0154 Q_D(const ReferenceLine); 0155 0156 // Orientation 0157 if (d->orientation == Orientation::Horizontal) 0158 orientationHorizontalAction->setChecked(true); 0159 else 0160 orientationVerticalAction->setChecked(true); 0161 menu->insertMenu(visibilityAction, orientationMenu); 0162 0163 // Line styles 0164 const auto& pen = d->line->pen(); 0165 GuiTools::updatePenStyles(lineStyleMenu, lineStyleActionGroup, pen.color()); 0166 GuiTools::selectPenStyleAction(lineStyleActionGroup, pen.style()); 0167 GuiTools::selectColorAction(lineColorActionGroup, pen.color()); 0168 0169 menu->insertMenu(visibilityAction, lineMenu); 0170 menu->insertSeparator(visibilityAction); 0171 0172 return menu; 0173 } 0174 0175 void ReferenceLine::retransform() { 0176 Q_D(ReferenceLine); 0177 d->retransform(); 0178 } 0179 0180 void ReferenceLine::handleResize(double /*horizontalRatio*/, double /*verticalRatio*/, bool /*pageResize*/) { 0181 } 0182 0183 /* ============================ getter methods ================= */ 0184 BASIC_SHARED_D_READER_IMPL(ReferenceLine, ReferenceLine::Orientation, orientation, orientation) 0185 0186 Line* ReferenceLine::line() const { 0187 Q_D(const ReferenceLine); 0188 return d->line; 0189 } 0190 0191 /* ============================ setter methods and undo commands ================= */ 0192 STD_SETTER_CMD_IMPL_F_S(ReferenceLine, SetOrientation, ReferenceLine::Orientation, orientation, updateOrientation) 0193 void ReferenceLine::setOrientation(Orientation orientation) { 0194 Q_D(ReferenceLine); 0195 if (orientation != d->orientation) 0196 exec(new ReferenceLineSetOrientationCmd(d, orientation, ki18n("%1: set orientation"))); 0197 } 0198 0199 // ############################################################################## 0200 // ###### SLOTs for changes triggered via QActions in the context menu ######## 0201 // ############################################################################## 0202 void ReferenceLine::orientationChangedSlot(QAction* action) { 0203 if (action == orientationHorizontalAction) 0204 this->setOrientation(Orientation::Horizontal); 0205 else 0206 this->setOrientation(Orientation::Vertical); 0207 } 0208 0209 void ReferenceLine::lineStyleChanged(QAction* action) { 0210 Q_D(const ReferenceLine); 0211 d->line->setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action)); 0212 } 0213 0214 void ReferenceLine::lineColorChanged(QAction* action) { 0215 Q_D(const ReferenceLine); 0216 d->line->setColor(GuiTools::colorFromAction(lineColorActionGroup, action)); 0217 } 0218 0219 // ############################################################################## 0220 // ####################### Private implementation ############################### 0221 // ############################################################################## 0222 ReferenceLinePrivate::ReferenceLinePrivate(ReferenceLine* owner) 0223 : WorksheetElementPrivate(owner) 0224 , q(owner) { 0225 setFlag(QGraphicsItem::ItemSendsGeometryChanges); 0226 setFlag(QGraphicsItem::ItemIsMovable); 0227 setFlag(QGraphicsItem::ItemIsSelectable); 0228 setFlag(QGraphicsItem::ItemIsFocusable); 0229 setAcceptHoverEvents(true); 0230 } 0231 0232 /*! 0233 calculates the position and the bounding box of the item/point. Called on geometry or properties changes. 0234 */ 0235 void ReferenceLinePrivate::retransform() { 0236 if (suppressRetransform || !q->cSystem || q->isLoading()) 0237 return; 0238 0239 auto cs = q->plot()->coordinateSystem(q->coordinateSystemIndex()); 0240 const auto xRange{q->m_plot->range(Dimension::X, cs->index(Dimension::X))}; 0241 const auto yRange{q->m_plot->range(Dimension::Y, cs->index(Dimension::Y))}; 0242 0243 // calculate the position in the scene coordinates 0244 if (orientation == ReferenceLine::Orientation::Vertical) 0245 positionLogical = QPointF(positionLogical.x(), yRange.center()); 0246 else 0247 positionLogical = QPointF(xRange.center(), positionLogical.y()); 0248 updatePosition(); // To update position.point 0249 0250 // position.point contains already the scene position, but here it will be determined, 0251 // if the point lies outside of the datarect or not 0252 QVector<QPointF> listScene = q->cSystem->mapLogicalToScene(Points() << positionLogical); 0253 QDEBUG(Q_FUNC_INFO << ", scene list = " << listScene) 0254 0255 if (!listScene.isEmpty()) { 0256 insidePlot = true; 0257 0258 // determine the length of the line to be drawn 0259 QVector<QPointF> pointsLogical; 0260 if (orientation == ReferenceLine::Orientation::Vertical) 0261 pointsLogical << QPointF(positionLogical.x(), yRange.start()) << QPointF(positionLogical.x(), yRange.end()); 0262 else 0263 pointsLogical << QPointF(xRange.start(), positionLogical.y()) << QPointF(xRange.end(), positionLogical.y()); 0264 0265 QVector<QPointF> pointsScene = q->cSystem->mapLogicalToScene(pointsLogical); 0266 0267 if (pointsScene.size() > 1) { 0268 if (orientation == ReferenceLine::Orientation::Vertical) 0269 length = pointsScene.at(0).y() - pointsScene.at(1).y(); 0270 else 0271 length = pointsScene.at(0).x() - pointsScene.at(1).x(); 0272 } 0273 } else 0274 insidePlot = false; 0275 QDEBUG(Q_FUNC_INFO << ", scene list after = " << listScene) 0276 0277 recalcShapeAndBoundingRect(); 0278 } 0279 0280 void ReferenceLinePrivate::updateOrientation() { 0281 switch (orientation) { 0282 case WorksheetElement::Orientation::Horizontal: 0283 position.positionLimit = WorksheetElement::PositionLimit::Y; 0284 break; 0285 case WorksheetElement::Orientation::Vertical: 0286 position.positionLimit = WorksheetElement::PositionLimit::X; 0287 break; 0288 case WorksheetElement::Orientation::Both: 0289 position.positionLimit = WorksheetElement::PositionLimit::None; 0290 break; 0291 } 0292 retransform(); 0293 } 0294 0295 /*! 0296 recalculates the outer bounds and the shape of the item. 0297 */ 0298 void ReferenceLinePrivate::recalcShapeAndBoundingRect() { 0299 prepareGeometryChange(); 0300 0301 m_shape = QPainterPath(); 0302 if (insidePlot) { 0303 QPainterPath path; 0304 if (orientation == ReferenceLine::Orientation::Horizontal) { 0305 path.moveTo(-length / 2, 0); 0306 path.lineTo(length / 2, 0); 0307 } else { 0308 path.moveTo(0, length / 2); 0309 path.lineTo(0, -length / 2); 0310 } 0311 m_shape.addPath(WorksheetElement::shapeFromPath(path, line->pen())); 0312 m_boundingRectangle = m_shape.boundingRect(); 0313 } 0314 } 0315 0316 void ReferenceLinePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) { 0317 if (!insidePlot) 0318 return; 0319 0320 painter->setOpacity(line->opacity()); 0321 painter->setPen(line->pen()); 0322 if (orientation == ReferenceLine::Orientation::Horizontal) 0323 painter->drawLine(-length / 2, 0, length / 2, 0); 0324 else 0325 painter->drawLine(0, length / 2, 0, -length / 2); 0326 0327 if (m_hovered && !isSelected() && !q->isPrinting()) { 0328 painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); 0329 painter->drawPath(m_shape); 0330 } 0331 0332 if (isSelected() && !q->isPrinting()) { 0333 painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); 0334 painter->drawPath(m_shape); 0335 } 0336 } 0337 0338 // ############################################################################## 0339 // ################## Serialization/Deserialization ########################### 0340 // ############################################################################## 0341 //! Save as XML 0342 void ReferenceLine::save(QXmlStreamWriter* writer) const { 0343 Q_D(const ReferenceLine); 0344 0345 writer->writeStartElement(QStringLiteral("referenceLine")); 0346 writeBasicAttributes(writer); 0347 writeCommentElement(writer); 0348 0349 writer->writeStartElement(QStringLiteral("geometry")); 0350 WorksheetElement::save(writer); 0351 writer->writeAttribute(QStringLiteral("orientation"), QString::number(static_cast<int>(d->orientation))); 0352 writer->writeEndElement(); 0353 0354 d->line->save(writer); 0355 0356 writer->writeEndElement(); // close "ReferenceLine" section 0357 } 0358 0359 //! Load from XML 0360 bool ReferenceLine::load(XmlStreamReader* reader, bool preview) { 0361 Q_D(ReferenceLine); 0362 0363 if (!readBasicAttributes(reader)) 0364 return false; 0365 0366 QXmlStreamAttributes attribs; 0367 QString str; 0368 0369 while (!reader->atEnd()) { 0370 reader->readNext(); 0371 if (reader->isEndElement() && reader->name() == QLatin1String("referenceLine")) 0372 break; 0373 0374 if (!reader->isStartElement()) 0375 continue; 0376 0377 if (!preview && reader->name() == QLatin1String("comment")) { 0378 if (!readCommentElement(reader)) 0379 return false; 0380 } else if (!preview && reader->name() == QLatin1String("general")) { 0381 // old logic for the position for xml version < 6 0382 Q_D(ReferenceLine); 0383 attribs = reader->attributes(); 0384 auto str = attribs.value(QStringLiteral("position")).toString(); 0385 if (str.isEmpty()) 0386 reader->raiseMissingAttributeWarning(QStringLiteral("position")); 0387 else { 0388 d->positionLogical.setX(str.toDouble()); 0389 d->positionLogical.setY(str.toDouble()); 0390 } 0391 d->coordinateBindingEnabled = true; 0392 0393 READ_INT_VALUE("orientation", orientation, Orientation); 0394 READ_INT_VALUE_DIRECT("plotRangeIndex", m_cSystemIndex, int); 0395 0396 str = attribs.value(QStringLiteral("visible")).toString(); 0397 if (str.isEmpty()) 0398 reader->raiseMissingAttributeWarning(QStringLiteral("visible")); 0399 else 0400 d->setVisible(str.toInt()); 0401 } else if (!preview && reader->name() == QLatin1String("geometry")) { 0402 attribs = reader->attributes(); 0403 // new logic for the position for xmlVersion >= 6 0404 READ_INT_VALUE("orientation", orientation, Orientation); 0405 WorksheetElement::load(reader, preview); 0406 } else if (!preview && reader->name() == QLatin1String("line")) { 0407 d->line->load(reader, preview); 0408 } else { // unknown element 0409 reader->raiseUnknownElementWarning(); 0410 if (!reader->skipToEndElement()) 0411 return false; 0412 } 0413 } 0414 return true; 0415 } 0416 0417 // ############################################################################## 0418 // ######################### Theme management ################################## 0419 // ############################################################################## 0420 void ReferenceLine::loadThemeConfig(const KConfig& config) { 0421 Q_D(ReferenceLine); 0422 0423 // for the properties of the line read the properties of the axis line 0424 const KConfigGroup& group = config.group(QStringLiteral("Axis")); 0425 d->line->loadThemeConfig(group); 0426 }