File indexing completed on 2024-05-12 03:48:23
0001 /* 0002 File : WorksheetElement.cpp 0003 Project : LabPlot 0004 Description : Base class for all Worksheet children. 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2009 Tilman Benkert <thzs@gmx.net> 0007 SPDX-FileCopyrightText: 2012-2023 Alexander Semke <alexander.semke@web.de> 0008 SPDX-FileCopyrightText: 2024 Stefan Gerlach <stefan.gerlach@uni.kn> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 0013 #include "backend/worksheet/WorksheetElement.h" 0014 #include "backend/core/AspectPrivate.h" 0015 #include "backend/core/Project.h" 0016 #include "backend/lib/XmlStreamReader.h" 0017 #include "backend/lib/commandtemplates.h" 0018 #include "backend/worksheet/Worksheet.h" 0019 #include "backend/worksheet/WorksheetElementPrivate.h" 0020 #include "plots/AbstractPlot.h" 0021 #include "plots/PlotArea.h" 0022 #include "plots/cartesian/CartesianCoordinateSystem.h" 0023 #include "plots/cartesian/Plot.h" 0024 0025 #include <KLocalizedString> 0026 #include <QGraphicsItem> 0027 #include <QGraphicsScene> 0028 #include <QGraphicsSceneContextMenuEvent> 0029 #include <QKeyEvent> 0030 #include <QMenu> 0031 #include <QPen> 0032 0033 /** 0034 * \class WorksheetElement 0035 * \brief Base class for all Worksheet children. 0036 * 0037 */ 0038 // WorksheetElement::WorksheetElement(const QString& name, AspectType type) 0039 //: AbstractAspect(name, type), d_ptr(new WorksheetElementPrivate(this)) { 0040 // init(); 0041 //} 0042 0043 WorksheetElement::WorksheetElement(const QString& name, WorksheetElementPrivate* dd, AspectType type) 0044 : AbstractAspect(name, type) 0045 , d_ptr(dd) { 0046 init(); 0047 } 0048 0049 void WorksheetElement::init() { 0050 Q_D(WorksheetElement); 0051 d->setData(0, static_cast<quint64>(type())); 0052 } 0053 0054 WorksheetElement::~WorksheetElement() { 0055 delete m_moveBehindMenu; 0056 delete m_moveInFrontOfMenu; 0057 delete m_drawingOrderMenu; 0058 } 0059 0060 void WorksheetElement::finalizeAdd() { 0061 DEBUG(Q_FUNC_INFO) 0062 if (!m_plot) { 0063 // determine the plot parent which is not neccessarily the parent aspect like for 0064 // * child CustomPoint in InfoeElement 0065 // * child XYCurves in QQPlot 0066 // * etc. 0067 m_plot = dynamic_cast<CartesianPlot*>(parent(AspectType::CartesianPlot)); 0068 } 0069 0070 if (m_plot) { 0071 cSystem = dynamic_cast<const CartesianCoordinateSystem*>(m_plot->coordinateSystem(m_cSystemIndex)); 0072 Q_EMIT plotRangeListChanged(); 0073 } else 0074 DEBUG(Q_FUNC_INFO << ", WARNING: no plot available.") 0075 } 0076 0077 /** 0078 * \fn QGraphicsItem *WorksheetElement::graphicsItem() const 0079 * \brief Return the graphics item representing this element. 0080 * 0081 */ 0082 QGraphicsItem* WorksheetElement::graphicsItem() const { 0083 return d_ptr; 0084 } 0085 0086 /*! 0087 * \brief WorksheetElement::setParentGraphicsItem 0088 * Sets the parent graphicsitem, needed for binding to coord 0089 * \param item parent graphicsitem 0090 */ 0091 void WorksheetElement::setParentGraphicsItem(QGraphicsItem* item) { 0092 Q_D(WorksheetElement); 0093 d->setParentItem(item); 0094 } 0095 0096 /** 0097 * \fn void WorksheetElement::setVisible(bool on) 0098 * \brief Show/hide the element. 0099 * 0100 */ 0101 0102 /** 0103 * \fn bool WorksheetElement::isVisible() const 0104 * \brief Return whether the element is (at least) partially visible. 0105 * 0106 */ 0107 0108 /** 0109 * \brief Return whether the element is fully visible (i.e., including all child elements). 0110 * 0111 * The standard implementation returns isVisible(). 0112 */ 0113 bool WorksheetElement::isFullyVisible() const { 0114 return isVisible(); 0115 } 0116 0117 void WorksheetElement::setSuppressRetransform(bool value) { 0118 Q_D(WorksheetElement); 0119 d->suppressRetransform = value; 0120 } 0121 0122 /** 0123 * \fn void WorksheetElement::setPrinting(bool on) 0124 * \brief Switches the printing mode on/off 0125 * 0126 */ 0127 void WorksheetElement::setPrinting(bool printing) { 0128 m_printing = printing; 0129 } 0130 0131 bool WorksheetElement::isPrinting() const { 0132 return m_printing; 0133 } 0134 0135 void WorksheetElement::setZValue(qreal value) { 0136 graphicsItem()->setZValue(value); 0137 } 0138 0139 void WorksheetElement::changeVisibility() { 0140 Q_D(const WorksheetElement); 0141 this->setVisible(!d->isVisible()); 0142 } 0143 0144 void WorksheetElement::changeLocking() { 0145 this->setLock(!isLocked()); 0146 } 0147 0148 STD_SETTER_CMD_IMPL_S(WorksheetElement, SetLock, bool, lock) 0149 void WorksheetElement::setLock(bool lock) { 0150 Q_D(WorksheetElement); 0151 if (lock != d->lock) { 0152 if (!lock && isHovered()) 0153 setHover(false); 0154 exec(new WorksheetElementSetLockCmd(d, lock, lock ? ki18n("%1: lock") : ki18n("%1: unlock"))); 0155 } 0156 } 0157 0158 STD_SWAP_METHOD_SETTER_CMD_IMPL_F(WorksheetElement, SetVisible, bool, swapVisible, update) 0159 void WorksheetElement::setVisible(bool on) { 0160 Q_D(WorksheetElement); 0161 exec(new WorksheetElementSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); 0162 } 0163 0164 bool WorksheetElementPrivate::swapVisible(bool on) { 0165 bool oldValue = isVisible(); 0166 0167 // When making a graphics item invisible, it gets deselected in the scene. 0168 // In this case we don't want to deselect the item in the project explorer. 0169 // We need to supress the deselection in the view. 0170 auto* worksheet = static_cast<Worksheet*>(q->parent(AspectType::Worksheet)); 0171 if (worksheet) { 0172 worksheet->suppressSelectionChangedEvent(true); 0173 setVisible(on); 0174 worksheet->suppressSelectionChangedEvent(false); 0175 } else 0176 setVisible(on); 0177 0178 Q_EMIT q->changed(); 0179 Q_EMIT q->visibleChanged(on); 0180 return oldValue; 0181 } 0182 0183 bool WorksheetElement::isVisible() const { 0184 Q_D(const WorksheetElement); 0185 return d->isVisible(); 0186 } 0187 0188 /** 0189 This does exactly what Qt internally does to creates a shape from a painter path. 0190 */ 0191 QPainterPath WorksheetElement::shapeFromPath(const QPainterPath& path, const QPen& pen) { 0192 if (path == QPainterPath()) 0193 return path; 0194 0195 // PERFTRACE("WorksheetElement::shapeFromPath()"); 0196 0197 // TODO: We unfortunately need this hack as QPainterPathStroker will set a width of 1.0 0198 // if we pass a value of 0.0 to QPainterPathStroker::setWidth() 0199 const qreal penWidthZero = qreal(1.e-8); 0200 0201 QPainterPathStroker ps; 0202 ps.setCapStyle(pen.capStyle()); 0203 if (pen.widthF() <= 0.0) 0204 ps.setWidth(penWidthZero); 0205 else 0206 ps.setWidth(pen.widthF()); 0207 ps.setJoinStyle(pen.joinStyle()); 0208 ps.setMiterLimit(pen.miterLimit()); 0209 0210 QPainterPath p = ps.createStroke(path); 0211 p.addPath(path); 0212 0213 return p; 0214 } 0215 0216 QAction* WorksheetElement::visibilityAction() { 0217 if (!m_visibilityAction) { 0218 m_visibilityAction = new QAction(QIcon::fromTheme(QStringLiteral("view-visible")), i18n("Visible"), this); 0219 m_visibilityAction->setCheckable(true); 0220 connect(m_visibilityAction, &QAction::triggered, this, &WorksheetElement::changeVisibility); 0221 } 0222 return m_visibilityAction; 0223 } 0224 0225 QAction* WorksheetElement::lockingAction() { 0226 if (!m_lockingAction) { 0227 m_lockingAction = new QAction(QIcon::fromTheme(QStringLiteral("hidemouse")), i18n("Lock"), this); 0228 m_lockingAction->setCheckable(true); 0229 connect(m_lockingAction, &QAction::triggered, this, &WorksheetElement::changeLocking); 0230 } 0231 return m_lockingAction; 0232 } 0233 0234 QMenu* WorksheetElement::createContextMenu() { 0235 if (!m_drawingOrderMenu) { 0236 m_drawingOrderMenu = new QMenu(i18n("Drawing &order")); 0237 m_drawingOrderMenu->setIcon(QIcon::fromTheme(QStringLiteral("layer-bottom"))); 0238 0239 m_moveBehindMenu = new QMenu(i18n("Move &behind")); 0240 m_moveBehindMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-arrow-down"))); 0241 m_drawingOrderMenu->addMenu(m_moveBehindMenu); 0242 0243 m_moveInFrontOfMenu = new QMenu(i18n("Move in &front of")); 0244 m_moveInFrontOfMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-arrow-up"))); 0245 m_drawingOrderMenu->addMenu(m_moveInFrontOfMenu); 0246 0247 connect(m_drawingOrderMenu, &QMenu::aboutToShow, this, &WorksheetElement::prepareDrawingOrderMenu); 0248 connect(m_moveBehindMenu, &QMenu::triggered, this, &WorksheetElement::execMoveBehind); 0249 connect(m_moveInFrontOfMenu, &QMenu::triggered, this, &WorksheetElement::execMoveInFrontOf); 0250 } 0251 0252 QMenu* menu = AbstractAspect::createContextMenu(); 0253 QAction* firstAction = menu->actions().at(1); // skip the first action because of the "title-action" 0254 0255 auto* visibilityAction = this->visibilityAction(); 0256 visibilityAction->setChecked(isVisible()); 0257 menu->insertAction(firstAction, visibilityAction); 0258 menu->insertSeparator(firstAction); 0259 0260 // don't add the lock action for elements which cannot be freely moved on the worksheet (like axis and curves/plots) 0261 if (!dynamic_cast<Axis*>(this) && !dynamic_cast<Plot*>(this)) { 0262 auto* lockingAction = this->lockingAction(); 0263 lockingAction->setChecked(isLocked()); 0264 menu->insertAction(firstAction, lockingAction); 0265 menu->insertSeparator(firstAction); 0266 } 0267 0268 // add the sub-menu for the drawing order 0269 // don't add the drawing order menu for axes and legends, they're always drawn on top of each other elements 0270 if (type() == AspectType::Axis || type() == AspectType::CartesianPlotLegend) 0271 return menu; 0272 0273 // for plots in a worksheet with an active layout the Z-factor is not relevant but we still 0274 // want to use the "Drawing order" menu to be able to change the position/order of the plot in the layout. 0275 // Since the order of the child in the list of children is opposite to the Z-factor, we change 0276 // the names of the menus to adapt to the reversed logic. 0277 if (dynamic_cast<AbstractPlot*>(this)) { 0278 const auto* w = dynamic_cast<const Worksheet*>(this->parentAspect()); 0279 if (!w) 0280 return menu; 0281 0282 if (w->layout() != Worksheet::Layout::NoLayout) { 0283 m_moveBehindMenu->setTitle(i18n("Move in &front of")); 0284 m_moveBehindMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-arrow-up"))); 0285 m_moveInFrontOfMenu->setTitle(i18n("Move &behind")); 0286 m_moveInFrontOfMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-arrow-down"))); 0287 } else { 0288 m_moveBehindMenu->setTitle(i18n("Move &behind")); 0289 m_moveBehindMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-arrow-down"))); 0290 m_moveInFrontOfMenu->setTitle(i18n("Move in &front of")); 0291 m_moveInFrontOfMenu->setIcon(QIcon::fromTheme(QStringLiteral("draw-arrow-up"))); 0292 } 0293 } 0294 0295 // don't add the drawing order menu if the parent element has no other children 0296 int children = 0; 0297 for (auto* child : parentAspect()->children<WorksheetElement>()) { 0298 if (child->type() != AspectType::Axis && child->type() != AspectType::CartesianPlotLegend) 0299 children++; 0300 } 0301 0302 if (children > 1) { 0303 menu->addSeparator(); 0304 menu->addMenu(m_drawingOrderMenu); 0305 } 0306 0307 return menu; 0308 } 0309 0310 void WorksheetElement::prepareDrawingOrderMenu() { 0311 const auto* parent = parentAspect(); 0312 const int index = parent->indexOfChild<WorksheetElement>(this); 0313 const auto& children = parent->children<WorksheetElement>(); 0314 0315 //"move behind" sub-menu 0316 m_moveBehindMenu->clear(); 0317 for (int i = 0; i < index; ++i) { 0318 const auto* elem = children.at(i); 0319 // axes and legends are always drawn on top of other elements, don't add them to the menu 0320 if (elem->type() != AspectType::Axis && elem->type() != AspectType::CartesianPlotLegend) { 0321 auto* action = m_moveBehindMenu->addAction(elem->icon(), elem->name()); 0322 action->setData(i); 0323 } 0324 } 0325 0326 //"move in front of" sub-menu 0327 m_moveInFrontOfMenu->clear(); 0328 for (int i = index + 1; i < children.size(); ++i) { 0329 const auto* elem = children.at(i); 0330 // axes and legends are always drawn on top of other elements, don't add them to the menu 0331 if (elem->type() != AspectType::Axis && elem->type() != AspectType::CartesianPlotLegend) { 0332 auto* action = m_moveInFrontOfMenu->addAction(elem->icon(), elem->name()); 0333 action->setData(i); 0334 } 0335 } 0336 0337 // hide the sub-menus if they don't have any entries 0338 m_moveInFrontOfMenu->menuAction()->setVisible(!m_moveInFrontOfMenu->isEmpty()); 0339 m_moveBehindMenu->menuAction()->setVisible(!m_moveBehindMenu->isEmpty()); 0340 } 0341 0342 void WorksheetElement::execMoveInFrontOf(QAction* action) { 0343 Q_EMIT moveBegin(); 0344 auto* parent = parentAspect(); 0345 int index = action->data().toInt(); 0346 auto* sibling1 = parent->child<WorksheetElement>(index); 0347 auto* sibling2 = parent->child<WorksheetElement>(index + 1); 0348 beginMacro(i18n("%1: move behind %2.", name(), sibling1->name())); 0349 setMoved(true); 0350 remove(); 0351 parent->insertChildBefore(this, sibling2); 0352 setMoved(false); 0353 endMacro(); 0354 Q_EMIT moveEnd(); 0355 } 0356 0357 void WorksheetElement::execMoveBehind(QAction* action) { 0358 Q_EMIT moveBegin(); 0359 auto* parent = parentAspect(); 0360 int index = action->data().toInt(); 0361 auto* sibling = parent->child<WorksheetElement>(index); 0362 beginMacro(i18n("%1: move in front of %2.", name(), sibling->name())); 0363 setMoved(true); 0364 remove(); 0365 parent->insertChildBefore(this, sibling); 0366 setMoved(false); 0367 endMacro(); 0368 Q_EMIT moveEnd(); 0369 } 0370 0371 QPointF WorksheetElement::align(QPointF pos, QRectF rect, HorizontalAlignment horAlign, VerticalAlignment vertAlign, bool positive) const { 0372 // positive is right 0373 double xAlign = 0.; 0374 switch (horAlign) { 0375 case HorizontalAlignment::Left: 0376 xAlign = rect.width() / 2.; 0377 break; 0378 case HorizontalAlignment::Right: 0379 xAlign = -rect.width() / 2.; 0380 break; 0381 case HorizontalAlignment::Center: 0382 break; 0383 } 0384 0385 // positive is to top 0386 double yAlign = 0.; 0387 switch (vertAlign) { 0388 case VerticalAlignment::Bottom: 0389 yAlign = -rect.height() / 2.; 0390 break; 0391 case VerticalAlignment::Top: 0392 yAlign = rect.height() / 2.; 0393 break; 0394 case VerticalAlignment::Center: 0395 break; 0396 } 0397 0398 // For yAlign it must be two times plus. 0399 if (positive) 0400 return {pos.x() + xAlign, pos.y() + yAlign}; 0401 else 0402 return {pos.x() - xAlign, pos.y() + yAlign}; 0403 } 0404 0405 QRectF WorksheetElement::parentRect() const { 0406 QRectF rect; 0407 auto* parent = parentAspect(); 0408 if (parent && parent->type() == AspectType::CartesianPlot && plot()) { 0409 if (type() != AspectType::Axis) 0410 rect = plot()->graphicsItem()->mapRectFromScene(plot()->rect()); 0411 else 0412 rect = plot()->dataRect(); // axes are positioned relative to the data rect and not to the whole plot rect 0413 } else { 0414 const auto* parent = graphicsItem()->parentItem(); 0415 if (parent) { 0416 rect = parent->boundingRect(); 0417 } else { 0418 if (graphicsItem()->scene()) 0419 rect = graphicsItem()->scene()->sceneRect(); 0420 } 0421 } 0422 0423 return rect; 0424 } 0425 0426 /*! 0427 * \brief parentPosToRelativePos 0428 * Converts the absolute position of the element in parent coordinates into the distance between the 0429 * alignment point of the parent and the element 0430 * \param parentPos Element position in parent coordinates 0431 * \param parentRect Parent data rect 0432 * \param rect element's rect 0433 * \param position contains the alignement of the element to the parent 0434 * \return distance between the parent position to the element 0435 */ 0436 QPointF WorksheetElement::parentPosToRelativePos(QPointF parentPos, PositionWrapper position) const { 0437 // increasing relative pos hor --> right 0438 // increasing relative pos vert --> top 0439 // increasing parent pos hor --> right 0440 // increasing parent pos vert --> bottom 0441 0442 QRectF parentRect = this->parentRect(); 0443 QPointF relPos; 0444 0445 double percentage = 0.; 0446 switch (position.horizontalPosition) { 0447 case HorizontalPosition::Left: 0448 break; 0449 case HorizontalPosition::Center: 0450 percentage = 0.5; 0451 break; 0452 case HorizontalPosition::Right: 0453 percentage = 1.0; 0454 break; 0455 case HorizontalPosition::Relative: 0456 percentage = position.point.x(); 0457 } 0458 0459 relPos.setX(parentPos.x() - (parentRect.x() + parentRect.width() * percentage)); 0460 0461 switch (position.verticalPosition) { 0462 case VerticalPosition::Top: 0463 percentage = 0.; 0464 break; 0465 case VerticalPosition::Center: 0466 percentage = 0.5; 0467 break; 0468 case VerticalPosition::Bottom: 0469 percentage = 1.0; 0470 break; 0471 case VerticalPosition::Relative: 0472 percentage = position.point.y(); 0473 } 0474 0475 relPos.setY(parentRect.y() + parentRect.height() * percentage - parentPos.y()); 0476 0477 return relPos; 0478 } 0479 0480 /*! 0481 * \brief relativePosToParentPos 0482 * \param parentRect 0483 * \param rect element's rect 0484 * \param position contains the alignment of the element to the parent 0485 * \return parent position 0486 */ 0487 QPointF WorksheetElement::relativePosToParentPos(PositionWrapper position) const { 0488 // increasing relative pos hor --> right 0489 // increasing relative pos vert --> top 0490 // increasing parent pos hor --> right 0491 // increasing parent pos vert --> bottom 0492 0493 QRectF parentRect = this->parentRect(); 0494 QPointF parentPos; 0495 0496 double percentage = 0.; 0497 switch (position.horizontalPosition) { 0498 case HorizontalPosition::Left: 0499 case HorizontalPosition::Relative: 0500 break; 0501 case HorizontalPosition::Center: 0502 percentage = 0.5; 0503 break; 0504 case HorizontalPosition::Right: 0505 percentage = 1.0; 0506 break; 0507 } 0508 0509 if (position.horizontalPosition == HorizontalPosition::Relative) 0510 parentPos.setX(parentRect.x() + parentRect.width() * position.point.x()); 0511 else 0512 parentPos.setX(parentRect.x() + parentRect.width() * percentage + position.point.x()); 0513 0514 switch (position.verticalPosition) { 0515 case VerticalPosition::Top: 0516 percentage = 0.; 0517 break; 0518 case VerticalPosition::Center: 0519 percentage = 0.5; 0520 break; 0521 case VerticalPosition::Bottom: 0522 percentage = 1.0; 0523 break; 0524 case VerticalPosition::Relative: 0525 break; 0526 } 0527 0528 if (position.verticalPosition == VerticalPosition::Relative) 0529 parentPos.setY(parentRect.y() + parentRect.height() * position.point.y()); 0530 else 0531 parentPos.setY(parentRect.y() + parentRect.height() * percentage - position.point.y()); 0532 0533 return parentPos; 0534 } 0535 0536 void WorksheetElement::save(QXmlStreamWriter* writer) const { 0537 Q_D(const WorksheetElement); 0538 writer->writeAttribute(QStringLiteral("x"), QString::number(d->position.point.x())); 0539 writer->writeAttribute(QStringLiteral("y"), QString::number(d->position.point.y())); 0540 writer->writeAttribute(QStringLiteral("horizontalPosition"), QString::number(static_cast<int>(d->position.horizontalPosition))); 0541 writer->writeAttribute(QStringLiteral("verticalPosition"), QString::number(static_cast<int>(d->position.verticalPosition))); 0542 writer->writeAttribute(QStringLiteral("horizontalAlignment"), QString::number(static_cast<int>(d->horizontalAlignment))); 0543 writer->writeAttribute(QStringLiteral("verticalAlignment"), QString::number(static_cast<int>(d->verticalAlignment))); 0544 writer->writeAttribute(QStringLiteral("rotationAngle"), QString::number(d->rotation())); 0545 writer->writeAttribute(QStringLiteral("plotRangeIndex"), QString::number(m_cSystemIndex)); 0546 writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible())); 0547 writer->writeAttribute(QStringLiteral("coordinateBinding"), QString::number(d->coordinateBindingEnabled)); 0548 writer->writeAttribute(QStringLiteral("logicalPosX"), QString::number(d->positionLogical.x())); 0549 writer->writeAttribute(QStringLiteral("logicalPosY"), QString::number(d->positionLogical.y())); 0550 writer->writeAttribute(QStringLiteral("locked"), QString::number(d->lock)); 0551 } 0552 0553 bool WorksheetElement::load(XmlStreamReader* reader, bool preview) { 0554 if (preview) 0555 return true; 0556 0557 Q_D(WorksheetElement); 0558 auto attribs = reader->attributes(); 0559 0560 auto str = attribs.value(QStringLiteral("x")).toString(); 0561 if (str.isEmpty()) 0562 reader->raiseMissingAttributeWarning(QStringLiteral("x")); 0563 else 0564 d->position.point.setX(str.toDouble()); 0565 0566 str = attribs.value(QStringLiteral("y")).toString(); 0567 if (str.isEmpty()) 0568 reader->raiseMissingAttributeWarning(QStringLiteral("y")); 0569 else 0570 d->position.point.setY(str.toDouble()); 0571 0572 READ_INT_VALUE("horizontalPosition", position.horizontalPosition, HorizontalPosition); 0573 READ_INT_VALUE("verticalPosition", position.verticalPosition, VerticalPosition); 0574 if (Project::xmlVersion() < 1) { 0575 // Before 2.9.0 the position.point is only used when horizontalPosition or 0576 // vertical position was set to Custom, otherwise the label was attached to the 0577 // "position" and it was not possible to arrange relative to this anchor point 0578 // From 2.9.0, the horizontalPosition and verticalPosition indicate the anchor 0579 // point and position.point indicates the distance to them 0580 if (d->position.horizontalPosition != HorizontalPosition::Relative) { 0581 d->position.point.setX(0); 0582 if (d->position.horizontalPosition == HorizontalPosition::Left) 0583 d->horizontalAlignment = HorizontalAlignment::Left; 0584 else if (d->position.horizontalPosition == HorizontalPosition::Right) 0585 d->horizontalAlignment = HorizontalAlignment::Right; 0586 } else // TODO 0587 d->position.horizontalPosition = HorizontalPosition::Center; 0588 0589 if (d->position.verticalPosition != VerticalPosition::Relative) { 0590 d->position.point.setY(0); 0591 if (d->position.verticalPosition == VerticalPosition::Top) 0592 d->verticalAlignment = VerticalAlignment::Top; 0593 else if (d->position.verticalPosition == VerticalPosition::Bottom) 0594 d->verticalAlignment = VerticalAlignment::Bottom; 0595 } else // TODO 0596 d->position.verticalPosition = VerticalPosition::Center; 0597 0598 // in the old format the order was reversed, multiply by -1 here 0599 d->position.point.setY(-d->position.point.y()); 0600 } else { 0601 READ_INT_VALUE("horizontalAlignment", horizontalAlignment, HorizontalAlignment); 0602 READ_INT_VALUE("verticalAlignment", verticalAlignment, VerticalAlignment); 0603 } 0604 if (project()->xmlVersion() >= 8) { 0605 QGRAPHICSITEM_READ_DOUBLE_VALUE("rotationAngle", Rotation); 0606 } else { 0607 str = attribs.value(QStringLiteral("rotationAngle")).toString(); 0608 if (str.isEmpty()) 0609 reader->raiseMissingAttributeWarning(QStringLiteral("rotationAngle")); 0610 else 0611 d->setRotation(-1 * str.toDouble()); 0612 } 0613 READ_INT_VALUE_DIRECT("plotRangeIndex", m_cSystemIndex, int); 0614 0615 str = attribs.value(QStringLiteral("visible")).toString(); 0616 if (str.isEmpty()) 0617 reader->raiseMissingAttributeWarning(QStringLiteral("visible")); 0618 else 0619 d->setVisible(str.toInt()); 0620 0621 READ_INT_VALUE("coordinateBinding", coordinateBindingEnabled, bool); 0622 0623 str = attribs.value(QStringLiteral("logicalPosX")).toString(); 0624 if (str.isEmpty()) 0625 reader->raiseMissingAttributeWarning(QStringLiteral("logicalPosX")); 0626 else 0627 d->positionLogical.setX(str.toDouble()); 0628 0629 str = attribs.value(QStringLiteral("logicalPosY")).toString(); 0630 if (str.isEmpty()) 0631 reader->raiseMissingAttributeWarning(QStringLiteral("logicalPosY")); 0632 else 0633 d->positionLogical.setY(str.toDouble()); 0634 0635 str = attribs.value(QStringLiteral("locked")).toString(); 0636 if (str.isEmpty()) 0637 reader->raiseMissingAttributeWarning(QStringLiteral("locked")); 0638 else 0639 d->lock = static_cast<bool>(str.toInt()); 0640 0641 return true; 0642 } 0643 0644 void WorksheetElement::loadThemeConfig(const KConfig&) { 0645 } 0646 0647 void WorksheetElement::saveThemeConfig(const KConfig&) { 0648 } 0649 0650 // coordinate system 0651 0652 class SetCoordinateSystemIndexCmd : public QUndoCommand { 0653 public: 0654 SetCoordinateSystemIndexCmd(WorksheetElement* element, int index, QUndoCommand* parent = nullptr) 0655 : QUndoCommand(parent) 0656 , m_element(element) 0657 , m_index(index) { 0658 } 0659 0660 virtual void redo() override { 0661 const auto oldIndex = m_element->m_cSystemIndex; 0662 m_element->m_cSystemIndex = m_index; 0663 if (m_element->plot()) 0664 m_element->cSystem = dynamic_cast<const CartesianCoordinateSystem*>(m_element->plot()->coordinateSystem(m_index)); 0665 else 0666 DEBUG(Q_FUNC_INFO << ", WARNING: No plot found. Failed setting csystem index.") 0667 0668 m_index = oldIndex; 0669 m_element->retransform(); 0670 Q_EMIT m_element->coordinateSystemIndexChanged(m_element->m_cSystemIndex); 0671 } 0672 0673 virtual void undo() override { 0674 redo(); 0675 } 0676 0677 private: 0678 WorksheetElement* m_element; 0679 int m_index; 0680 }; 0681 0682 void WorksheetElement::setCoordinateSystemIndex(int index, QUndoCommand* parent) { 0683 if (index != m_cSystemIndex) { 0684 auto* command = new SetCoordinateSystemIndexCmd(this, index, parent); 0685 if (!parent) 0686 exec(command); 0687 } else if (!cSystem) { 0688 // during load the index will be set, 0689 // but the element might not have yet a plot assigned 0690 if (plot()) 0691 cSystem = dynamic_cast<const CartesianCoordinateSystem*>(plot()->coordinateSystem(index)); 0692 retransform(); 0693 } 0694 } 0695 0696 int WorksheetElement::coordinateSystemCount() const { 0697 if (m_plot) 0698 return m_plot->coordinateSystemCount(); 0699 DEBUG(Q_FUNC_INFO << ", WARNING: no plot set!") 0700 0701 return 0; 0702 } 0703 0704 QString WorksheetElement::coordinateSystemInfo(const int index) const { 0705 if (m_plot) 0706 return m_plot->coordinateSystem(index)->info(); 0707 0708 return {}; 0709 } 0710 0711 bool WorksheetElement::isHovered() const { 0712 Q_D(const WorksheetElement); 0713 return d->isHovered(); 0714 } 0715 0716 void WorksheetElement::setHover(bool on) { 0717 Q_D(WorksheetElement); 0718 d->setHover(on); 0719 } 0720 0721 /* ============================ getter methods ================= */ 0722 BASIC_SHARED_D_READER_IMPL(WorksheetElement, WorksheetElement::PositionWrapper, position, position) 0723 BASIC_SHARED_D_READER_IMPL(WorksheetElement, WorksheetElement::HorizontalAlignment, horizontalAlignment, horizontalAlignment) 0724 BASIC_SHARED_D_READER_IMPL(WorksheetElement, WorksheetElement::VerticalAlignment, verticalAlignment, verticalAlignment) 0725 BASIC_SHARED_D_READER_IMPL(WorksheetElement, QPointF, positionLogical, positionLogical) 0726 BASIC_SHARED_D_READER_IMPL(WorksheetElement, 0727 qreal, 0728 rotationAngle, 0729 rotation() * -1) // the rotation is in qgraphicsitem different to the convention used in labplot 0730 BASIC_SHARED_D_READER_IMPL(WorksheetElement, bool, coordinateBindingEnabled, coordinateBindingEnabled) 0731 BASIC_SHARED_D_READER_IMPL(WorksheetElement, qreal, scale, scale()) 0732 BASIC_SHARED_D_READER_IMPL(WorksheetElement, bool, isLocked, lock) 0733 0734 /* ============================ setter methods and undo commands ================= */ 0735 STD_SETTER_CMD_IMPL_F_S_SC(WorksheetElement, SetPosition, WorksheetElement::PositionWrapper, position, updatePosition, objectPositionChanged) 0736 void WorksheetElement::setPosition(const PositionWrapper& pos) { 0737 Q_D(WorksheetElement); 0738 if (pos.point != d->position.point || pos.horizontalPosition != d->position.horizontalPosition || pos.verticalPosition != d->position.verticalPosition 0739 || pos.positionLimit != d->position.positionLimit) 0740 exec(new WorksheetElementSetPositionCmd(d, pos, ki18n("%1: set position"))); 0741 } 0742 0743 STD_SETTER_CMD_IMPL_F_S_SC(WorksheetElement, 0744 SetHorizontalAlignment, 0745 WorksheetElement::HorizontalAlignment, 0746 horizontalAlignment, 0747 updatePosition, 0748 objectPositionChanged) 0749 void WorksheetElement::setHorizontalAlignment(const WorksheetElement::HorizontalAlignment hAlign) { 0750 Q_D(WorksheetElement); 0751 if (hAlign != d->horizontalAlignment) 0752 exec(new WorksheetElementSetHorizontalAlignmentCmd(d, hAlign, ki18n("%1: set horizontal alignment"))); 0753 } 0754 0755 STD_SETTER_CMD_IMPL_F_S_SC(WorksheetElement, 0756 SetVerticalAlignment, 0757 WorksheetElement::VerticalAlignment, 0758 verticalAlignment, 0759 updatePosition, 0760 objectPositionChanged) 0761 void WorksheetElement::setVerticalAlignment(const WorksheetElement::VerticalAlignment vAlign) { 0762 Q_D(WorksheetElement); 0763 if (vAlign != d->verticalAlignment) 0764 exec(new WorksheetElementSetVerticalAlignmentCmd(d, vAlign, ki18n("%1: set vertical alignment"))); 0765 } 0766 0767 STD_SETTER_CMD_IMPL_S(WorksheetElement, SetCoordinateBindingEnabled, bool, coordinateBindingEnabled) // do I need a final method? 0768 bool WorksheetElement::setCoordinateBindingEnabled(bool on) { 0769 Q_D(WorksheetElement); 0770 if (on && !cSystem) 0771 return false; 0772 if (on != d->coordinateBindingEnabled) { 0773 // Must not be in the Undo Command, 0774 // because if done once, logical and 0775 // scene pos are synched and therefore 0776 // when changing it does not have any visual effect 0777 d->updatePosition(); 0778 exec(new WorksheetElementSetCoordinateBindingEnabledCmd(d, on, on ? ki18n("%1: use logical coordinates") : ki18n("%1: set invisible"))); 0779 return true; 0780 } 0781 return true; 0782 } 0783 0784 STD_SETTER_CMD_IMPL_F_S_SC(WorksheetElement, SetPositionLogical, QPointF, positionLogical, updatePosition, objectPositionChanged) 0785 void WorksheetElement::setPositionLogical(QPointF pos) { 0786 Q_D(WorksheetElement); 0787 if (pos != d->positionLogical) 0788 exec(new WorksheetElementSetPositionLogicalCmd(d, pos, ki18n("%1: set logical position"))); 0789 } 0790 0791 /*! 0792 * \brief WorksheetElement::setPosition 0793 * sets the position without undo/redo-stuff 0794 * \param point point in scene coordinates 0795 */ 0796 void WorksheetElement::setPosition(QPointF point) { 0797 Q_D(WorksheetElement); 0798 if (point != d->position.point) { 0799 d->position.point = point; 0800 retransform(); 0801 } 0802 } 0803 0804 /*! 0805 * position is set to invalid if the parent item is not drawn on the scene 0806 * (e.g. axis is not drawn because it's outside plot ranges -> don't draw axis' title label) 0807 */ 0808 void WorksheetElement::setPositionInvalid(bool invalid) { 0809 Q_D(WorksheetElement); 0810 if (invalid != d->positionInvalid) 0811 d->positionInvalid = invalid; 0812 } 0813 0814 GRAPHICSITEM_SETTER_CMD_IMPL_F_S(WorksheetElement, SetRotationAngle, qreal, rotation, setRotation, recalcShapeAndBoundingRect) 0815 void WorksheetElement::setRotationAngle(qreal angle) { 0816 const qreal angle_graphicsItem = -angle; 0817 Q_D(WorksheetElement); 0818 if (angle_graphicsItem != d->rotation()) 0819 exec(new WorksheetElementSetRotationAngleCmd(d, angle_graphicsItem, ki18n("%1: set rotation angle"))); 0820 } 0821 0822 // ############################################################################## 0823 // ####################### Private implementation ############################### 0824 // ############################################################################## 0825 WorksheetElementPrivate::WorksheetElementPrivate(WorksheetElement* owner) 0826 : q(owner) { 0827 } 0828 0829 QString WorksheetElementPrivate::name() const { 0830 return q->name(); 0831 } 0832 0833 QRectF WorksheetElementPrivate::boundingRect() const { 0834 return m_boundingRectangle; 0835 } 0836 0837 QPainterPath WorksheetElementPrivate::shape() const { 0838 return m_shape; 0839 } 0840 0841 void WorksheetElementPrivate::paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { 0842 } 0843 0844 void WorksheetElementPrivate::updatePosition() { 0845 QPointF p; 0846 if (coordinateBindingEnabled && q->cSystem) { 0847 // the position in logical coordinates was changed, calculate the position in scene coordinates 0848 // insidePlot will get false if the point lies outside of the datarect 0849 p = q->cSystem->mapLogicalToScene(positionLogical, insidePlot, AbstractCoordinateSystem::MappingFlag::SuppressPageClippingVisible); 0850 position.point = q->parentPosToRelativePos(mapPlotAreaToParent(p), position); 0851 Q_EMIT q->positionChanged(position); 0852 } else { 0853 insidePlot = true; // not important if within the datarect or not 0854 p = q->relativePosToParentPos(position); 0855 0856 // the position in scene coordinates was changed, calculate the position in logical coordinates 0857 if (q->cSystem && q->cSystem->isValid()) { 0858 positionLogical = q->cSystem->mapSceneToLogical(mapParentToPlotArea(p), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 0859 Q_EMIT q->positionLogicalChanged(positionLogical); 0860 } 0861 } 0862 0863 p = q->align(p, boundingRect(), horizontalAlignment, verticalAlignment, true); 0864 0865 suppressItemChangeEvent = true; 0866 setPos(p); 0867 suppressItemChangeEvent = false; 0868 0869 Q_EMIT q->changed(); 0870 } 0871 0872 bool WorksheetElementPrivate::sceneEvent(QEvent* event) { 0873 // don't allow to move the element with the mouse or with the cursor keys if it's locked, react on all other events 0874 if (lock && (event->type() == QEvent::GraphicsSceneMouseMove || event->type() == QEvent::KeyPress)) { 0875 event->ignore(); 0876 return true; 0877 } 0878 return QGraphicsItem::sceneEvent(event); 0879 } 0880 0881 void WorksheetElementPrivate::keyPressEvent(QKeyEvent* event) { 0882 const bool keyVertical = event->key() == Qt::Key_Up || event->key() == Qt::Key_Down; 0883 const bool keyHorizontal = event->key() == Qt::Key_Left || event->key() == Qt::Key_Right; 0884 if ((keyHorizontal && position.positionLimit != WorksheetElement::PositionLimit::Y) 0885 || (keyVertical && position.positionLimit != WorksheetElement::PositionLimit::X)) { 0886 const int delta = 5; // always in scene coordinates 0887 0888 WorksheetElement::PositionWrapper tempPosition = position; 0889 if (coordinateBindingEnabled && q->cSystem) { 0890 if (!q->cSystem->isValid()) 0891 return; 0892 // the position in logical coordinates was changed, calculate the position in scene coordinates 0893 bool visible; 0894 QPointF p = q->cSystem->mapLogicalToScene(positionLogical, visible, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 0895 if (event->key() == Qt::Key_Left) { 0896 p.setX(p.x() - delta); 0897 } else if (event->key() == Qt::Key_Right) { 0898 p.setX(p.x() + delta); 0899 } else if (event->key() == Qt::Key_Up) { 0900 p.setY(p.y() - delta); // y-axis is reversed, change the sign here 0901 } else if (event->key() == Qt::Key_Down) { 0902 p.setY(p.y() + delta); 0903 } 0904 auto pLogic = q->cSystem->mapSceneToLogical(p, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 0905 q->setPositionLogical(pLogic); // So it is undoable 0906 } else { 0907 QPointF point = q->parentPosToRelativePos(pos(), position); 0908 point = q->align(point, m_boundingRectangle, horizontalAlignment, verticalAlignment, false); 0909 0910 if (event->key() == Qt::Key_Left) { 0911 point.setX(point.x() - delta); 0912 } else if (event->key() == Qt::Key_Right) { 0913 point.setX(point.x() + delta); 0914 } else if (event->key() == Qt::Key_Up) { 0915 point.setY(point.y() + delta); 0916 } else if (event->key() == Qt::Key_Down) { 0917 point.setY(point.y() - delta); 0918 } 0919 tempPosition.point = point; 0920 q->setPosition(tempPosition); // So it is undoable 0921 } 0922 event->accept(); 0923 } else 0924 QGraphicsItem::keyPressEvent(event); 0925 } 0926 0927 void WorksheetElementPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { 0928 if (!m_moveStarted && event->button() == Qt::LeftButton) 0929 m_moveStarted = true; 0930 0931 QGraphicsItem::mouseMoveEvent(event); 0932 } 0933 0934 void WorksheetElementPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { 0935 if (!m_moveStarted) { 0936 QGraphicsItem::mouseReleaseEvent(event); 0937 return; 0938 } 0939 0940 // convert position of the item in parent coordinates to label's position 0941 QPointF point = q->parentPosToRelativePos(pos(), position); 0942 point = q->align(point, boundingRect(), horizontalAlignment, verticalAlignment, false); 0943 if (point != position.point) { 0944 // position was changed -> set the position related member variables 0945 suppressRetransform = true; 0946 WorksheetElement::PositionWrapper tempPosition = position; 0947 tempPosition.point = point; 0948 q->setPosition(tempPosition); 0949 updatePosition(); // to update the logical position if available 0950 suppressRetransform = false; 0951 } 0952 0953 m_moveStarted = false; 0954 QGraphicsItem::mouseReleaseEvent(event); 0955 } 0956 0957 QVariant WorksheetElementPrivate::itemChange(GraphicsItemChange change, const QVariant& value) { 0958 if (suppressItemChangeEvent) 0959 return value; 0960 0961 if (change == QGraphicsItem::ItemPositionChange) { 0962 auto currPos = pos(); 0963 auto newPos = value.toPointF(); 0964 switch (position.positionLimit) { 0965 case WorksheetElement::PositionLimit::X: 0966 newPos.setY(currPos.y()); 0967 break; 0968 case WorksheetElement::PositionLimit::Y: 0969 newPos.setX(currPos.x()); 0970 break; 0971 case WorksheetElement::PositionLimit::None: 0972 default: 0973 break; 0974 } 0975 0976 // don't use setPosition here, because then all small changes are on the undo stack 0977 // setPosition is used then in mouseReleaseEvent 0978 if (coordinateBindingEnabled) { 0979 if (!q->cSystem->isValid()) 0980 return QGraphicsItem::itemChange(change, value); 0981 QPointF pos = q->align(newPos, m_boundingRectangle, horizontalAlignment, verticalAlignment, false); 0982 0983 positionLogical = q->cSystem->mapSceneToLogical(mapParentToPlotArea(pos), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping); 0984 Q_EMIT q->positionLogicalChanged(positionLogical); 0985 Q_EMIT q->objectPositionChanged(); 0986 } else { 0987 // convert item's center point in parent's coordinates 0988 WorksheetElement::PositionWrapper tempPosition = position; 0989 tempPosition.point = q->parentPosToRelativePos(newPos, position); 0990 tempPosition.point = q->align(tempPosition.point, boundingRect(), horizontalAlignment, verticalAlignment, false); 0991 0992 // Q_EMIT the signals in order to notify the UI. 0993 Q_EMIT q->positionChanged(tempPosition); 0994 Q_EMIT q->objectPositionChanged(); 0995 } 0996 return QGraphicsItem::itemChange(change, newPos); 0997 } 0998 0999 return QGraphicsItem::itemChange(change, value); 1000 } 1001 1002 /*! 1003 * \brief TextLabelPrivate::mapParentToPlotArea 1004 * Mapping a point from parent coordinates to plotArea coordinates 1005 * Needed because in some cases the parent is not the PlotArea, but a child of it (Marker/InfoElement) 1006 * IMPORTANT: function is also used in Custompoint, so when changing anything, change it also there 1007 * \param point point in parent coordinates 1008 * \return point in PlotArea coordinates 1009 */ 1010 QPointF WorksheetElementPrivate::mapParentToPlotArea(QPointF point) const { 1011 auto* parent = q->parent(AspectType::CartesianPlot); 1012 if (parent) { 1013 auto* plot = static_cast<CartesianPlot*>(parent); 1014 // mapping from parent to item coordinates and them to plot area 1015 return mapToItem(plot->plotArea()->graphicsItem(), mapFromParent(point)); 1016 } 1017 1018 return point; // don't map if no parent set. Then it's during load 1019 } 1020 1021 /*! 1022 * \brief TextLabelPrivate::mapPlotAreaToParent 1023 * Mapping a point from the PlotArea (CartesianPlot::plotArea) coordinates to the parent 1024 * coordinates of this item 1025 * Needed because in some cases the parent is not the PlotArea, but a child of it (Marker/InfoElement) 1026 * IMPORTANT: function is also used in Custompoint, so when changing anything, change it also there 1027 * \param point point in plotArea coordinates 1028 * \return point in parent coordinates 1029 */ 1030 QPointF WorksheetElementPrivate::mapPlotAreaToParent(QPointF point) const { 1031 auto* parent = q->parent(AspectType::CartesianPlot); 1032 if (parent) { 1033 auto* plot = static_cast<CartesianPlot*>(parent); 1034 // first mapping to item coordinates and from there back to parent 1035 // WorksheetinfoElement: parentItem()->parentItem() == plot->graphicsItem() 1036 // plot->graphicsItem().pos() == plot->plotArea()->graphicsItem().pos() 1037 return mapToParent(mapFromItem(plot->plotArea()->graphicsItem(), point)); 1038 } 1039 1040 return point; // don't map if no parent set. Then it's during load 1041 } 1042 1043 void WorksheetElementPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { 1044 if (!isSelected()) 1045 setHover(true); 1046 } 1047 1048 void WorksheetElementPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { 1049 setHover(false); 1050 } 1051 1052 void WorksheetElementPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { 1053 // don't show any context menu if the element is hidden which is the case 1054 // for example for axis and plot title labels. For such objects the context menu 1055 // of their parents, i.e. of axis and plot, is used. 1056 if (!q->hidden()) { 1057 auto* menu = q->createContextMenu(); 1058 if (menu) 1059 menu->exec(event->screenPos()); 1060 } 1061 } 1062 1063 bool WorksheetElementPrivate::isHovered() const { 1064 return m_hovered; 1065 } 1066 1067 void WorksheetElementPrivate::setHover(bool on) { 1068 if (on == m_hovered) 1069 return; // don't update if state not changed 1070 1071 m_hovered = on; 1072 Q_EMIT q->hoveredChanged(on); 1073 update(); 1074 }