File indexing completed on 2025-01-26 03:34:14
0001 /* 0002 File : ReferenceRange.cpp 0003 Project : LabPlot 0004 Description : Reference range on the plot 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2022-2023 Alexander Semke <alexander.semke@web.de> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "ReferenceRange.h" 0012 #include "ReferenceRangePrivate.h" 0013 #include "backend/lib/XmlStreamReader.h" 0014 #include "backend/lib/commandtemplates.h" 0015 #include "backend/worksheet/Background.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 /** 0032 * \class ReferenceRange 0033 * \brief A customizable point. 0034 * 0035 * The position can be either specified by mouse events or by providing the 0036 * x- and y- coordinates in parent's coordinate system 0037 */ 0038 0039 ReferenceRange::ReferenceRange(CartesianPlot* plot, const QString& name) 0040 : WorksheetElement(name, new ReferenceRangePrivate(this), AspectType::ReferenceRange) { 0041 m_plot = plot; 0042 init(); 0043 } 0044 0045 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0046 // and is deleted during the cleanup in QGraphicsScene 0047 ReferenceRange::~ReferenceRange() = default; 0048 0049 void ReferenceRange::init() { 0050 Q_D(ReferenceRange); 0051 0052 KConfig config; 0053 KConfigGroup group = config.group(QStringLiteral("ReferenceRange")); 0054 0055 d->orientation = (Orientation)group.readEntry(QStringLiteral("Orientation"), static_cast<int>(Orientation::Vertical)); 0056 switch (d->orientation) { 0057 case WorksheetElement::Orientation::Horizontal: 0058 d->position.positionLimit = WorksheetElement::PositionLimit::Y; 0059 break; 0060 case WorksheetElement::Orientation::Vertical: 0061 d->position.positionLimit = WorksheetElement::PositionLimit::X; 0062 break; 0063 case WorksheetElement::Orientation::Both: 0064 d->position.positionLimit = WorksheetElement::PositionLimit::None; 0065 break; 0066 } 0067 0068 if (plot()) { 0069 m_cSystemIndex = plot()->defaultCoordinateSystemIndex(); 0070 cSystem = plot()->coordinateSystem(m_cSystemIndex); 0071 d->coordinateBindingEnabled = true; 0072 // default position - 10% of the plot width/height positioned around the center 0073 auto cs = plot()->coordinateSystem(coordinateSystemIndex()); 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 const auto w = m_plot->range(Dimension::X, cs->index(Dimension::X)).length() * 0.1; 0077 const auto h = m_plot->range(Dimension::Y, cs->index(Dimension::Y)).length() * 0.1; 0078 d->positionLogical = QPointF(x, y); 0079 d->positionLogicalStart = QPointF(x - w / 2, y - h / 2); 0080 d->positionLogicalEnd = QPointF(x + w / 2, y + h / 2); 0081 } else 0082 d->position.point = QPointF(0, 0); // center of parent 0083 d->updatePosition(); // to update also scene coordinates 0084 0085 // background 0086 d->background = new Background(QString()); 0087 d->background->setEnabledAvailable(true); 0088 addChild(d->background); 0089 d->background->setHidden(true); 0090 d->background->init(group); 0091 connect(d->background, &Background::updateRequested, [=] { 0092 d->update(); 0093 }); 0094 0095 // border 0096 d->line = new Line(QString()); 0097 d->line->setHidden(true); 0098 addChild(d->line); 0099 d->line->init(group); 0100 connect(d->line, &Line::updatePixmapRequested, [=] { 0101 d->update(); 0102 }); 0103 connect(d->line, &Line::updateRequested, [=] { 0104 d->recalcShapeAndBoundingRect(); 0105 }); 0106 0107 connect(this, &WorksheetElement::objectPositionChanged, this, &ReferenceRange::updateStartEndPositions); 0108 retransform(); 0109 } 0110 0111 /*! 0112 Returns an icon to be used in the project explorer. 0113 */ 0114 QIcon ReferenceRange::icon() const { 0115 return QIcon::fromTheme(QStringLiteral("draw-rectangle")); 0116 } 0117 0118 void ReferenceRange::initActions() { 0119 // Orientation 0120 auto* orientationActionGroup = new QActionGroup(this); 0121 orientationActionGroup->setExclusive(true); 0122 connect(orientationActionGroup, &QActionGroup::triggered, this, &ReferenceRange::orientationChangedSlot); 0123 0124 orientationHorizontalAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal")), i18n("Horizontal"), orientationActionGroup); 0125 orientationHorizontalAction->setCheckable(true); 0126 0127 orientationVerticalAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-axis-vertical")), i18n("Vertical"), orientationActionGroup); 0128 orientationVerticalAction->setCheckable(true); 0129 0130 // Line 0131 lineStyleActionGroup = new QActionGroup(this); 0132 lineStyleActionGroup->setExclusive(true); 0133 connect(lineStyleActionGroup, &QActionGroup::triggered, this, &ReferenceRange::lineStyleChanged); 0134 0135 lineColorActionGroup = new QActionGroup(this); 0136 lineColorActionGroup->setExclusive(true); 0137 connect(lineColorActionGroup, &QActionGroup::triggered, this, &ReferenceRange::lineColorChanged); 0138 } 0139 0140 void ReferenceRange::initMenus() { 0141 this->initActions(); 0142 0143 // Orientation 0144 orientationMenu = new QMenu(i18n("Orientation")); 0145 orientationMenu->setIcon(QIcon::fromTheme(QStringLiteral("labplot-axis-horizontal"))); 0146 orientationMenu->addAction(orientationHorizontalAction); 0147 orientationMenu->addAction(orientationVerticalAction); 0148 0149 // Line 0150 lineMenu = new QMenu(i18n("Border Line")); 0151 lineMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-line"))); 0152 lineStyleMenu = new QMenu(i18n("Style"), lineMenu); 0153 lineStyleMenu->setIcon(QIcon::fromTheme(QStringLiteral("object-stroke-style"))); 0154 lineMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-line"))); 0155 lineMenu->addMenu(lineStyleMenu); 0156 0157 lineColorMenu = new QMenu(i18n("Color"), lineMenu); 0158 lineColorMenu->setIcon(QIcon::fromTheme(QStringLiteral("fill-color"))); 0159 GuiTools::fillColorMenu(lineColorMenu, lineColorActionGroup); 0160 lineMenu->addMenu(lineColorMenu); 0161 } 0162 0163 QMenu* ReferenceRange::createContextMenu() { 0164 if (!orientationMenu) 0165 initMenus(); 0166 0167 QMenu* menu = WorksheetElement::createContextMenu(); 0168 QAction* visibilityAction = this->visibilityAction(); 0169 0170 Q_D(const ReferenceRange); 0171 0172 // Orientation 0173 if (d->orientation == Orientation::Horizontal) 0174 orientationHorizontalAction->setChecked(true); 0175 else 0176 orientationVerticalAction->setChecked(true); 0177 menu->insertMenu(visibilityAction, orientationMenu); 0178 0179 // Border line styles 0180 const auto& pen = d->line->pen(); 0181 GuiTools::updatePenStyles(lineStyleMenu, lineStyleActionGroup, pen.color()); 0182 GuiTools::selectPenStyleAction(lineStyleActionGroup, pen.style()); 0183 GuiTools::selectColorAction(lineColorActionGroup, pen.color()); 0184 0185 menu->insertMenu(visibilityAction, lineMenu); 0186 menu->insertSeparator(visibilityAction); 0187 0188 return menu; 0189 } 0190 0191 void ReferenceRange::retransform() { 0192 Q_D(ReferenceRange); 0193 d->retransform(); 0194 } 0195 0196 void ReferenceRange::handleResize(double /*horizontalRatio*/, double /*verticalRatio*/, bool /*pageResize*/) { 0197 } 0198 0199 /* ============================ getter methods ================= */ 0200 BASIC_SHARED_D_READER_IMPL(ReferenceRange, ReferenceRange::Orientation, orientation, orientation) 0201 BASIC_SHARED_D_READER_IMPL(ReferenceRange, QPointF, positionLogicalStart, positionLogicalStart) 0202 BASIC_SHARED_D_READER_IMPL(ReferenceRange, QPointF, positionLogicalEnd, positionLogicalEnd) 0203 0204 Line* ReferenceRange::line() const { 0205 Q_D(const ReferenceRange); 0206 return d->line; 0207 } 0208 0209 Background* ReferenceRange::background() const { 0210 Q_D(const ReferenceRange); 0211 return d->background; 0212 } 0213 0214 /* ============================ setter methods and undo commands ================= */ 0215 STD_SETTER_CMD_IMPL_F_S(ReferenceRange, SetOrientation, ReferenceRange::Orientation, orientation, updateOrientation) 0216 void ReferenceRange::setOrientation(Orientation orientation) { 0217 Q_D(ReferenceRange); 0218 if (orientation != d->orientation) 0219 exec(new ReferenceRangeSetOrientationCmd(d, orientation, ki18n("%1: set orientation"))); 0220 } 0221 0222 STD_SETTER_CMD_IMPL_F_S(ReferenceRange, SetPositionLogicalStart, QPointF, positionLogicalStart, retransform) 0223 void ReferenceRange::setPositionLogicalStart(QPointF pos) { 0224 Q_D(ReferenceRange); 0225 if (pos != d->positionLogicalStart) 0226 exec(new ReferenceRangeSetPositionLogicalStartCmd(d, pos, ki18n("%1: set start logical position"))); 0227 } 0228 0229 STD_SETTER_CMD_IMPL_F_S(ReferenceRange, SetPositionLogicalEnd, QPointF, positionLogicalEnd, retransform) 0230 void ReferenceRange::setPositionLogicalEnd(QPointF pos) { 0231 Q_D(ReferenceRange); 0232 if (pos != d->positionLogicalEnd) 0233 exec(new ReferenceRangeSetPositionLogicalEndCmd(d, pos, ki18n("%1: set end logical position"))); 0234 } 0235 0236 // ############################################################################## 0237 // ###### SLOTs for changes triggered via QActions in the context menu ######## 0238 // ############################################################################## 0239 void ReferenceRange::orientationChangedSlot(QAction* action) { 0240 if (action == orientationHorizontalAction) 0241 this->setOrientation(Orientation::Horizontal); 0242 else 0243 this->setOrientation(Orientation::Vertical); 0244 } 0245 0246 void ReferenceRange::lineStyleChanged(QAction* action) { 0247 Q_D(const ReferenceRange); 0248 d->line->setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action)); 0249 } 0250 0251 void ReferenceRange::lineColorChanged(QAction* action) { 0252 Q_D(const ReferenceRange); 0253 d->line->setColor(GuiTools::colorFromAction(lineColorActionGroup, action)); 0254 } 0255 0256 // ############################################################################## 0257 // ####################### Private implementation ############################### 0258 // ############################################################################## 0259 ReferenceRangePrivate::ReferenceRangePrivate(ReferenceRange* owner) 0260 : WorksheetElementPrivate(owner) 0261 , q(owner) { 0262 setFlag(QGraphicsItem::ItemSendsGeometryChanges); 0263 setFlag(QGraphicsItem::ItemIsMovable); 0264 setFlag(QGraphicsItem::ItemIsSelectable); 0265 setFlag(QGraphicsItem::ItemIsFocusable); 0266 setAcceptHoverEvents(true); 0267 } 0268 0269 QPointF ReferenceRangePrivate::recalculateRect() { 0270 auto cs = q->plot()->coordinateSystem(q->coordinateSystemIndex()); 0271 if (!cs->isValid()) 0272 return QPointF(); 0273 0274 // calculate rect in logical coordinates 0275 QPointF p1, p2; 0276 switch (orientation) { 0277 case ReferenceRange::Orientation::Vertical: { 0278 const auto yRange{q->m_plot->range(Dimension::Y, cs->index(Dimension::Y))}; 0279 p1 = QPointF(positionLogicalStart.x(), yRange.start()); 0280 p2 = QPointF(positionLogicalEnd.x(), yRange.end()); 0281 break; 0282 } 0283 case ReferenceRange::Orientation::Horizontal: { 0284 const auto xRange{q->m_plot->range(Dimension::X, cs->index(Dimension::X))}; 0285 p1 = QPointF(xRange.start(), positionLogicalStart.y()); 0286 p2 = QPointF(xRange.end(), positionLogicalEnd.y()); 0287 break; 0288 } 0289 case ReferenceRange::Orientation::Both: { 0290 p1 = QPointF(positionLogicalStart.x(), positionLogicalStart.y()); 0291 p2 = QPointF(positionLogicalEnd.x(), positionLogicalEnd.y()); 0292 break; 0293 } 0294 } 0295 const auto pointsSceneUnclipped = cs->mapLogicalToScene({p1, p2}, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 0296 // New position without considering clipping! 0297 const auto newPosScene = 0298 QPointF((pointsSceneUnclipped.at(0).x() + pointsSceneUnclipped.at(1).x()) / 2, (pointsSceneUnclipped.at(0).y() + pointsSceneUnclipped.at(1).y()) / 2); 0299 0300 auto point0Clipped = pointsSceneUnclipped.at(0); 0301 auto point1Clipped = pointsSceneUnclipped.at(1); 0302 0303 const auto diffXUnclipped = qAbs(point0Clipped.x() - point1Clipped.x()); 0304 const auto diffYUnclipped = qAbs(point0Clipped.y() - point1Clipped.y()); 0305 0306 // Clipping 0307 const QRectF& dataRect = static_cast<const CartesianPlot*>(q->plot())->dataRect(); 0308 if (point0Clipped.x() < point1Clipped.x()) { 0309 if (point0Clipped.x() < dataRect.left()) { 0310 m_leftClipped = true; 0311 point0Clipped.setX(dataRect.left()); 0312 } else 0313 m_leftClipped = false; 0314 0315 if (point1Clipped.x() > dataRect.right()) { 0316 m_rightClipped = true; 0317 point1Clipped.setX(dataRect.right()); 0318 } else 0319 m_rightClipped = false; 0320 const auto diffX = point1Clipped.x() - point0Clipped.x(); 0321 rect.setX(-diffXUnclipped / 2 + point0Clipped.x() 0322 - pointsSceneUnclipped.at(0).x()); // -diffXUnclipped/2 is the value it would be shifted if no clipping happens 0323 if (diffX >= 0) 0324 rect.setWidth(diffX); 0325 else 0326 rect.setWidth(0); 0327 } else { 0328 if (point1Clipped.x() < dataRect.left()) { 0329 m_leftClipped = true; 0330 point1Clipped.setX(dataRect.left()); 0331 } else 0332 m_leftClipped = false; 0333 0334 if (point0Clipped.x() > dataRect.right()) { 0335 m_rightClipped = true; 0336 point0Clipped.setX(dataRect.right()); 0337 } else 0338 m_rightClipped = false; 0339 const auto diffX = point0Clipped.x() - point1Clipped.x(); 0340 rect.setX(-diffXUnclipped / 2 + point1Clipped.x() - pointsSceneUnclipped.at(1).x()); 0341 if (diffX >= 0) 0342 rect.setWidth(diffX); 0343 else 0344 rect.setWidth(0); 0345 } 0346 0347 if (point0Clipped.y() < point1Clipped.y()) { 0348 if (point0Clipped.y() < dataRect.top()) { 0349 m_topClipped = true; 0350 point0Clipped.setY(dataRect.top()); 0351 } else 0352 m_topClipped = false; 0353 0354 if (point1Clipped.y() > dataRect.bottom()) { 0355 m_bottomClipped = true; 0356 point1Clipped.setY(dataRect.bottom()); 0357 } else 0358 m_bottomClipped = false; 0359 const auto diff = point1Clipped.y() - point0Clipped.y(); 0360 rect.setY(-diffYUnclipped / 2 + point0Clipped.y() - pointsSceneUnclipped.at(0).y()); 0361 if (diff >= 0) 0362 rect.setHeight(diff); 0363 else 0364 rect.setHeight(0); 0365 } else { 0366 if (point1Clipped.y() < dataRect.top()) { 0367 m_topClipped = true; 0368 point1Clipped.setY(dataRect.top()); 0369 } else 0370 m_topClipped = false; 0371 0372 if (point0Clipped.y() > dataRect.bottom()) { 0373 m_bottomClipped = true; 0374 point0Clipped.setY(dataRect.bottom()); 0375 } else 0376 m_bottomClipped = false; 0377 const auto diff = point0Clipped.y() - point1Clipped.y(); 0378 rect.setY(-diffYUnclipped / 2 + point1Clipped.y() - pointsSceneUnclipped.at(1).y()); 0379 if (diff >= 0) 0380 rect.setHeight(diff); 0381 else 0382 rect.setHeight(0); 0383 } 0384 0385 recalcShapeAndBoundingRect(); 0386 return newPosScene; 0387 } 0388 0389 /*! 0390 calculates the position and the bounding box of the item/point. Called on geometry or properties changes. 0391 */ 0392 void ReferenceRangePrivate::retransform() { 0393 if (suppressRetransform || !q->cSystem || q->isLoading()) 0394 return; 0395 0396 const QPointF newPosScene = recalculateRect(); 0397 0398 auto cs = q->plot()->coordinateSystem(q->coordinateSystemIndex()); 0399 0400 positionLogical = cs->mapSceneToLogical(newPosScene, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); 0401 updatePosition(); 0402 } 0403 0404 void ReferenceRangePrivate::updateOrientation() { 0405 switch (orientation) { 0406 case WorksheetElement::Orientation::Horizontal: 0407 position.positionLimit = WorksheetElement::PositionLimit::Y; 0408 break; 0409 case WorksheetElement::Orientation::Vertical: 0410 position.positionLimit = WorksheetElement::PositionLimit::X; 0411 break; 0412 case WorksheetElement::Orientation::Both: 0413 position.positionLimit = WorksheetElement::PositionLimit::None; 0414 break; 0415 } 0416 retransform(); 0417 } 0418 0419 /*! 0420 * called when the user moves the graphics item with the mouse and the scene position of the item is changed. 0421 * Here we update the logical coordinates for the start and end points based on the new valud for the logical 0422 * position \c newPosition of the item's center and notify the dock widget. 0423 */ 0424 // TODO: make this undo/redo-able 0425 void ReferenceRange::updateStartEndPositions() { 0426 Q_D(ReferenceRange); 0427 if (d->orientation == WorksheetElement::Orientation::Horizontal) { 0428 const double width = (d->positionLogicalEnd.y() - d->positionLogicalStart.y()) / 2; 0429 d->positionLogicalStart.setY(d->positionLogical.y() - width); 0430 d->positionLogicalEnd.setY(d->positionLogical.y() + width); 0431 } else { 0432 const double width = (d->positionLogicalEnd.x() - d->positionLogicalStart.x()) / 2; 0433 d->positionLogicalStart.setX(d->positionLogical.x() - width); 0434 d->positionLogicalEnd.setX(d->positionLogical.x() + width); 0435 } 0436 0437 // Update boundingrect 0438 d->recalculateRect(); 0439 0440 Q_EMIT positionLogicalStartChanged(d->positionLogicalStart); 0441 Q_EMIT positionLogicalEndChanged(d->positionLogicalEnd); 0442 } 0443 0444 /*! 0445 recalculates the outer bounds and the shape of the item. 0446 */ 0447 void ReferenceRangePrivate::recalcShapeAndBoundingRect() { 0448 prepareGeometryChange(); 0449 0450 m_shape = QPainterPath(); 0451 if (m_visible) { 0452 QPainterPath path; 0453 0454 if (!m_topClipped && !m_rightClipped && !m_bottomClipped && !m_leftClipped) { 0455 path.addRect(rect); 0456 } else { 0457 if (!m_topClipped) { 0458 path.moveTo(rect.topLeft()); 0459 path.lineTo(rect.topRight()); 0460 } 0461 if (!m_rightClipped) { 0462 if (m_topClipped) 0463 path.moveTo(rect.topRight()); 0464 path.lineTo(rect.bottomRight()); 0465 } 0466 if (!m_bottomClipped) { 0467 if (m_rightClipped) 0468 path.moveTo(rect.bottomRight()); 0469 path.lineTo(rect.bottomLeft()); 0470 } 0471 if (!m_leftClipped) { 0472 if (m_bottomClipped) 0473 path.moveTo(rect.bottomLeft()); 0474 path.lineTo(rect.topLeft()); 0475 } 0476 } 0477 0478 m_shape.addPath(WorksheetElement::shapeFromPath(path, line->pen())); 0479 m_boundingRectangle = m_shape.boundingRect(); 0480 } 0481 } 0482 0483 void ReferenceRangePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) { 0484 if (!m_visible) 0485 return; 0486 0487 if (rect.width() == 0 || rect.height() == 0) 0488 return; 0489 0490 // draw filling 0491 if (background->enabled()) { 0492 painter->setOpacity(background->opacity()); 0493 painter->setPen(Qt::NoPen); 0494 drawFilling(painter); 0495 } 0496 0497 // draw the background 0498 painter->drawRect(rect); 0499 0500 // draw the border 0501 if (line->style() != Qt::NoPen) { 0502 painter->setPen(line->pen()); 0503 painter->setBrush(Qt::NoBrush); 0504 painter->setOpacity(line->opacity()); 0505 } 0506 0507 painter->drawPath(m_shape); 0508 0509 if (m_hovered && !isSelected() && !q->isPrinting()) { 0510 painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); 0511 painter->drawPath(m_shape); 0512 } 0513 0514 if (isSelected() && !q->isPrinting()) { 0515 painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); 0516 painter->drawPath(m_shape); 0517 } 0518 } 0519 0520 void ReferenceRangePrivate::drawFilling(QPainter* painter) const { 0521 if (background->type() == Background::Type::Color) { 0522 switch (background->colorStyle()) { 0523 case Background::ColorStyle::SingleColor: { 0524 painter->setBrush(QBrush(background->firstColor())); 0525 break; 0526 } 0527 case Background::ColorStyle::HorizontalLinearGradient: { 0528 QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); 0529 linearGrad.setColorAt(0, background->firstColor()); 0530 linearGrad.setColorAt(1, background->secondColor()); 0531 painter->setBrush(QBrush(linearGrad)); 0532 break; 0533 } 0534 case Background::ColorStyle::VerticalLinearGradient: { 0535 QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); 0536 linearGrad.setColorAt(0, background->firstColor()); 0537 linearGrad.setColorAt(1, background->secondColor()); 0538 painter->setBrush(QBrush(linearGrad)); 0539 break; 0540 } 0541 case Background::ColorStyle::TopLeftDiagonalLinearGradient: { 0542 QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); 0543 linearGrad.setColorAt(0, background->firstColor()); 0544 linearGrad.setColorAt(1, background->secondColor()); 0545 painter->setBrush(QBrush(linearGrad)); 0546 break; 0547 } 0548 case Background::ColorStyle::BottomLeftDiagonalLinearGradient: { 0549 QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); 0550 linearGrad.setColorAt(0, background->firstColor()); 0551 linearGrad.setColorAt(1, background->secondColor()); 0552 painter->setBrush(QBrush(linearGrad)); 0553 break; 0554 } 0555 case Background::ColorStyle::RadialGradient: { 0556 QRadialGradient radialGrad(rect.center(), rect.width() / 2); 0557 radialGrad.setColorAt(0, background->firstColor()); 0558 radialGrad.setColorAt(1, background->secondColor()); 0559 painter->setBrush(QBrush(radialGrad)); 0560 break; 0561 } 0562 } 0563 } else if (background->type() == Background::Type::Image) { 0564 if (!background->fileName().trimmed().isEmpty()) { 0565 QPixmap pix(background->fileName()); 0566 switch (background->imageStyle()) { 0567 case Background::ImageStyle::ScaledCropped: 0568 pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); 0569 painter->setBrush(QBrush(pix)); 0570 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2); 0571 break; 0572 case Background::ImageStyle::Scaled: 0573 pix = pix.scaled(rect.size().toSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 0574 painter->setBrush(QBrush(pix)); 0575 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2); 0576 break; 0577 case Background::ImageStyle::ScaledAspectRatio: 0578 pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); 0579 painter->setBrush(QBrush(pix)); 0580 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2); 0581 break; 0582 case Background::ImageStyle::Centered: 0583 painter->drawPixmap(QPointF(rect.center().x() - pix.size().width() / 2, rect.center().y() - pix.size().height() / 2), pix); 0584 break; 0585 case Background::ImageStyle::Tiled: 0586 painter->setBrush(QBrush(pix)); 0587 break; 0588 case Background::ImageStyle::CenterTiled: 0589 painter->setBrush(QBrush(pix)); 0590 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2); 0591 } 0592 } 0593 } else if (background->type() == Background::Type::Pattern) { 0594 painter->setBrush(QBrush(background->firstColor(), background->brushStyle())); 0595 } 0596 } 0597 0598 // ############################################################################## 0599 // ################## Serialization/Deserialization ########################### 0600 // ############################################################################## 0601 //! Save as XML 0602 void ReferenceRange::save(QXmlStreamWriter* writer) const { 0603 Q_D(const ReferenceRange); 0604 0605 writer->writeStartElement(QStringLiteral("referenceRange")); 0606 writeBasicAttributes(writer); 0607 writeCommentElement(writer); 0608 0609 // position and orientation 0610 writer->writeStartElement(QStringLiteral("geometry")); 0611 WorksheetElement::save(writer); 0612 writer->writeAttribute(QStringLiteral("logicalPosStartX"), QString::number(d->positionLogicalStart.x())); 0613 writer->writeAttribute(QStringLiteral("logicalPosStartY"), QString::number(d->positionLogicalStart.y())); 0614 writer->writeAttribute(QStringLiteral("logicalPosEndX"), QString::number(d->positionLogicalEnd.x())); 0615 writer->writeAttribute(QStringLiteral("logicalPosEndY"), QString::number(d->positionLogicalEnd.y())); 0616 writer->writeAttribute(QStringLiteral("orientation"), QString::number(static_cast<int>(d->orientation))); 0617 writer->writeEndElement(); 0618 0619 d->background->save(writer); 0620 d->line->save(writer); 0621 0622 writer->writeEndElement(); // close "ReferenceRange" section 0623 } 0624 0625 //! Load from XML 0626 bool ReferenceRange::load(XmlStreamReader* reader, bool preview) { 0627 Q_D(ReferenceRange); 0628 0629 if (!readBasicAttributes(reader)) 0630 return false; 0631 0632 QXmlStreamAttributes attribs; 0633 QString str; 0634 0635 while (!reader->atEnd()) { 0636 reader->readNext(); 0637 if (reader->isEndElement() && reader->name() == QStringLiteral("referenceRange")) 0638 break; 0639 0640 if (!reader->isStartElement()) 0641 continue; 0642 0643 if (!preview && reader->name() == QStringLiteral("comment")) { 0644 if (!readCommentElement(reader)) 0645 return false; 0646 } else if (!preview && reader->name() == QStringLiteral("geometry")) { 0647 attribs = reader->attributes(); 0648 READ_INT_VALUE("orientation", orientation, Orientation); 0649 WorksheetElement::load(reader, preview); 0650 0651 str = attribs.value(QStringLiteral("logicalPosStartX")).toString(); 0652 if (str.isEmpty()) 0653 reader->raiseMissingAttributeWarning(QStringLiteral("logicalPosStartX")); 0654 else 0655 d->positionLogicalStart.setX(str.toDouble()); 0656 0657 str = attribs.value(QStringLiteral("logicalPosStartY")).toString(); 0658 if (str.isEmpty()) 0659 reader->raiseMissingAttributeWarning(QStringLiteral("logicalPosStartY")); 0660 else 0661 d->positionLogicalStart.setY(str.toDouble()); 0662 0663 str = attribs.value(QStringLiteral("logicalPosEndX")).toString(); 0664 if (str.isEmpty()) 0665 reader->raiseMissingAttributeWarning(QStringLiteral("logicalPosEndX")); 0666 else 0667 d->positionLogicalEnd.setX(str.toDouble()); 0668 0669 str = attribs.value(QStringLiteral("logicalPosEndY")).toString(); 0670 if (str.isEmpty()) 0671 reader->raiseMissingAttributeWarning(QStringLiteral("logicalPosEndY")); 0672 else 0673 d->positionLogicalEnd.setY(str.toDouble()); 0674 } else if (!preview && reader->name() == QStringLiteral("background")) 0675 d->background->load(reader, preview); 0676 else if (!preview && reader->name() == QStringLiteral("line")) 0677 d->line->load(reader, preview); 0678 else { // unknown element 0679 reader->raiseUnknownElementWarning(); 0680 if (!reader->skipToEndElement()) 0681 return false; 0682 } 0683 } 0684 return true; 0685 } 0686 0687 // ############################################################################## 0688 // ######################### Theme management ################################## 0689 // ############################################################################## 0690 void ReferenceRange::loadThemeConfig(const KConfig& config) { 0691 // determine the index of the current range in the list of all range children 0692 // and apply the theme color for this index 0693 const auto* plot = dynamic_cast<const CartesianPlot*>(parentAspect()); 0694 if (!plot) 0695 return; 0696 0697 int index = 0; 0698 const auto& children = plot->children<WorksheetElement>(); 0699 for (auto* child : children) { 0700 if (child == this) 0701 break; 0702 0703 if (child->inherits(AspectType::ReferenceRange)) 0704 ++index; 0705 } 0706 0707 const auto& themeColor = plot->themeColorPalette(index); 0708 0709 KConfigGroup group; 0710 if (config.hasGroup(QStringLiteral("Theme"))) 0711 group = config.group(QStringLiteral("Axis")); // when loading from the theme config, use the same properties as for Axis 0712 else 0713 group = config.group(QStringLiteral("ReferenceRange")); 0714 0715 Q_D(ReferenceRange); 0716 d->line->loadThemeConfig(group); 0717 d->background->loadThemeConfig(group, themeColor); 0718 }