File indexing completed on 2024-05-12 03:48:22

0001 /*
0002     File                 : Worksheet.cpp
0003     Project              : LabPlot
0004     Description          : Worksheet
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2009 Tilman Benkert <thzs@gmx.net>
0007     SPDX-FileCopyrightText: 2011-2022 Alexander Semke <alexander.semke@web.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "Worksheet.h"
0013 #include "Background.h"
0014 #include "WorksheetElement.h"
0015 #include "WorksheetPrivate.h"
0016 #include "backend/core/Project.h"
0017 #include "backend/core/Settings.h"
0018 #include "backend/lib/XmlStreamReader.h"
0019 #include "backend/lib/commandtemplates.h"
0020 #include "backend/worksheet/Image.h"
0021 #include "backend/worksheet/Line.h"
0022 #include "backend/worksheet/TextLabel.h"
0023 #include "backend/worksheet/TreeModel.h"
0024 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0025 #include "backend/worksheet/plots/cartesian/XYCurve.h"
0026 #include "commonfrontend/worksheet/WorksheetView.h"
0027 #include "kdefrontend/ThemeHandler.h"
0028 #include "kdefrontend/worksheet/ExportWorksheetDialog.h"
0029 
0030 #ifndef SDK
0031 #include <QPrintDialog>
0032 #include <QPrintPreviewDialog>
0033 #include <QPrinter>
0034 #endif
0035 
0036 #include <QDir>
0037 #include <QGraphicsItem>
0038 #include <QIcon>
0039 #include <QMenu>
0040 
0041 #include <KConfig>
0042 #include <KConfigGroup>
0043 #include <KLocalizedString>
0044 
0045 /**
0046  * \class Worksheet
0047  * \brief Top-level container for worksheet elements like plot, labels, etc.
0048  *
0049  * The worksheet is, besides the data containers \c Spreadsheet and \c Matrix, another central part of the application
0050  * and provides an area for showing and grouping together different kinds of worksheet objects - plots, labels &etc;
0051  *
0052  * * \ingroup worksheet
0053  */
0054 Worksheet::Worksheet(const QString& name, bool loading)
0055     : AbstractPart(name, AspectType::Worksheet)
0056     , d_ptr(new WorksheetPrivate(this)) {
0057     Q_D(Worksheet);
0058     d->background = new Background(QString());
0059     addChild(d->background);
0060     d->background->setHidden(true);
0061     connect(d->background, &Background::updateRequested, [=] {
0062         d->update();
0063     });
0064 
0065     connect(this, &Worksheet::childAspectAdded, this, &Worksheet::handleAspectAdded);
0066     connect(this, &Worksheet::childAspectAboutToBeRemoved, this, &Worksheet::handleAspectAboutToBeRemoved);
0067     connect(this, &Worksheet::childAspectRemoved, this, &Worksheet::handleAspectRemoved);
0068 
0069     if (!loading)
0070         init();
0071 }
0072 
0073 Worksheet::~Worksheet() {
0074     delete d_ptr;
0075 }
0076 
0077 void Worksheet::init() {
0078     Q_D(Worksheet);
0079     KConfig config;
0080     auto group = config.group(QStringLiteral("Worksheet"));
0081 
0082     // size
0083     d->scaleContent = group.readEntry(QStringLiteral("ScaleContent"), false);
0084     d->useViewSize = group.readEntry(QStringLiteral("UseViewSize"), false);
0085     d->pageRect.setX(0);
0086     d->pageRect.setY(0);
0087     d->pageRect.setWidth(group.readEntry(QStringLiteral("Width"), 1000));
0088     d->pageRect.setHeight(group.readEntry(QStringLiteral("Height"), 1000));
0089     d->m_scene->setSceneRect(d->pageRect);
0090 
0091     // background
0092     d->background->init(group);
0093 
0094     // layout
0095     d->layout = (Layout)group.readEntry(QStringLiteral("Layout"), static_cast<int>(Layout::VerticalLayout));
0096     d->layoutTopMargin = group.readEntry(QStringLiteral("LayoutTopMargin"), convertToSceneUnits(0.5, Unit::Centimeter));
0097     d->layoutBottomMargin = group.readEntry(QStringLiteral("LayoutBottomMargin"), convertToSceneUnits(0.5, Unit::Centimeter));
0098     d->layoutLeftMargin = group.readEntry(QStringLiteral("LayoutLeftMargin"), convertToSceneUnits(0.5, Unit::Centimeter));
0099     d->layoutRightMargin = group.readEntry(QStringLiteral("LayoutRightMargin"), convertToSceneUnits(0.5, Unit::Centimeter));
0100     d->layoutVerticalSpacing = group.readEntry(QStringLiteral("LayoutVerticalSpacing"), convertToSceneUnits(0.5, Unit::Centimeter));
0101     d->layoutHorizontalSpacing = group.readEntry(QStringLiteral("LayoutHorizontalSpacing"), convertToSceneUnits(0.5, Unit::Centimeter));
0102     d->layoutRowCount = group.readEntry(QStringLiteral("LayoutRowCount"), 2);
0103     d->layoutColumnCount = group.readEntry(QStringLiteral("LayoutColumnCount"), 2);
0104 
0105     // default theme
0106     auto settings = Settings::group(QStringLiteral("Settings_Worksheet"));
0107     d->theme = settings.readEntry(QStringLiteral("Theme"), QString());
0108     loadTheme(d->theme);
0109 }
0110 
0111 /*!
0112     converts from \c unit to the scene units. At the moment, 1 scene unit corresponds to 1/10 mm.
0113  */
0114 double Worksheet::convertToSceneUnits(const double value, const Worksheet::Unit unit) {
0115     switch (unit) {
0116     case Unit::Millimeter:
0117         return value * 10.0;
0118     case Unit::Centimeter:
0119         return value * 100.0;
0120     case Unit::Inch:
0121         return value * 25.4 * 10.;
0122     case Unit::Point:
0123         return value * 25.4 / 72. * 10.;
0124     }
0125 
0126     return 0;
0127 }
0128 
0129 /*!
0130     converts from the scene units to \c unit . At the moment, 1 scene unit corresponds to 1/10 mm.
0131  */
0132 double Worksheet::convertFromSceneUnits(const double value, const Worksheet::Unit unit) {
0133     switch (unit) {
0134     case Unit::Millimeter:
0135         return value / 10.0;
0136     case Unit::Centimeter:
0137         return value / 100.0;
0138     case Unit::Inch:
0139         return value / 25.4 / 10.;
0140     case Unit::Point:
0141         return value / 25.4 / 10. * 72.;
0142     }
0143 
0144     return 0;
0145 }
0146 
0147 QIcon Worksheet::icon() const {
0148     return QIcon::fromTheme(QStringLiteral("labplot-worksheet"));
0149 }
0150 
0151 /**
0152  * Return a new context menu. The caller takes ownership of the menu.
0153  */
0154 QMenu* Worksheet::createContextMenu() {
0155     QMenu* menu = AbstractPart::createContextMenu();
0156     Q_ASSERT(menu);
0157     Q_EMIT requestProjectContextMenu(menu);
0158     return menu;
0159 }
0160 
0161 //! Construct a primary view on me.
0162 /**
0163  * This method may be called multiple times during the life time of an Aspect, or it might not get
0164  * called at all. Aspects must not depend on the existence of a view for their operation.
0165  */
0166 QWidget* Worksheet::view() const {
0167     DEBUG(Q_FUNC_INFO)
0168     if (!m_partView) {
0169         m_view = new WorksheetView(const_cast<Worksheet*>(this));
0170         m_partView = m_view;
0171         connect(m_view, &WorksheetView::statusInfo, this, &Worksheet::statusInfo);
0172         connect(m_view, &WorksheetView::propertiesExplorerRequested, this, &Worksheet::propertiesExplorerRequested);
0173         connect(this, &Worksheet::cartesianPlotMouseModeChanged, m_view, &WorksheetView::cartesianPlotMouseModeChangedSlot);
0174         connect(this, &Worksheet::childContextMenuRequested, m_view, &WorksheetView::childContextMenuRequested);
0175         connect(this, &Worksheet::viewAboutToBeDeleted, [this]() {
0176             m_view = nullptr;
0177         });
0178         Q_EMIT const_cast<Worksheet*>(this)->changed();
0179     }
0180     return m_partView;
0181 }
0182 
0183 /*!
0184  * returns the list of all parent aspects (folders and sub-folders)
0185  * together with all the data containers required to plot the data in the worksheet
0186  */
0187 QVector<AbstractAspect*> Worksheet::dependsOn() const {
0188     // add all parent aspects (folders and sub-folders)
0189     auto aspects = AbstractAspect::dependsOn();
0190 
0191     // traverse all plots and add all data containers they depend on
0192     for (const auto* plot : children<AbstractPlot>())
0193         aspects << plot->dependsOn();
0194 
0195     return aspects;
0196 }
0197 
0198 QVector<AspectType> Worksheet::pasteTypes() const {
0199     return QVector<AspectType>{AspectType::CartesianPlot, AspectType::TextLabel, AspectType::Image};
0200 }
0201 
0202 bool Worksheet::exportView() const {
0203 #ifndef SDK
0204     auto* dlg = new ExportWorksheetDialog(m_view);
0205     dlg->setProjectFileName(const_cast<Worksheet*>(this)->project()->fileName());
0206     dlg->setFileName(name());
0207     bool ret;
0208     if ((ret = (dlg->exec() == QDialog::Accepted))) {
0209         QString path = dlg->path();
0210         const WorksheetView::ExportFormat format = dlg->exportFormat();
0211         const WorksheetView::ExportArea area = dlg->exportArea();
0212         const bool background = dlg->exportBackground();
0213         const int resolution = dlg->exportResolution();
0214 
0215         WAIT_CURSOR;
0216         m_view->exportToFile(path, format, area, background, resolution);
0217         RESET_CURSOR;
0218     }
0219     delete dlg;
0220     return ret;
0221 #else
0222     return true;
0223 #endif
0224 }
0225 
0226 bool Worksheet::exportView(QPixmap& pixmap) const {
0227     if (!m_view)
0228         return false;
0229 
0230     m_view->exportToPixmap(pixmap);
0231     return true;
0232 }
0233 
0234 bool Worksheet::printView() {
0235 #ifndef SDK
0236     QPrinter printer;
0237     auto* dlg = new QPrintDialog(&printer, m_view);
0238     dlg->setWindowTitle(i18nc("@title:window", "Print Worksheet"));
0239     bool ret;
0240     if ((ret = (dlg->exec() == QDialog::Accepted)))
0241         m_view->print(&printer);
0242 
0243     delete dlg;
0244     return ret;
0245 #else
0246     return true;
0247 #endif
0248 }
0249 
0250 bool Worksheet::printPreview() const {
0251 #ifndef SDK
0252     auto* dlg = new QPrintPreviewDialog(m_view);
0253     connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &WorksheetView::print);
0254     return dlg->exec();
0255 #else
0256     return true;
0257 #endif
0258 }
0259 
0260 void Worksheet::handleAspectAdded(const AbstractAspect* aspect) {
0261     DEBUG(Q_FUNC_INFO)
0262     Q_D(Worksheet);
0263     const auto* addedElement = dynamic_cast<const WorksheetElement*>(aspect);
0264     if (!addedElement)
0265         return;
0266 
0267     if (aspect->parentAspect() != this)
0268         return;
0269 
0270     // add the GraphicsItem of the added child to the scene
0271     DEBUG(Q_FUNC_INFO << ", ADDING child to SCENE")
0272     auto* item = addedElement->graphicsItem();
0273     d->m_scene->addItem(item);
0274 
0275     connect(aspect, &AbstractAspect::contextMenuRequested, this, &Worksheet::childContextMenuRequested);
0276     connect(addedElement, &WorksheetElement::changed, this, &Worksheet::changed);
0277 
0278     // for containers, connect to visilibity changes and update the layout accordingly
0279     if (dynamic_cast<const WorksheetElementContainer*>(addedElement))
0280         connect(addedElement, &WorksheetElement::visibleChanged, this, [=]() {
0281             if (layout() != Worksheet::Layout::NoLayout)
0282                 updateLayout();
0283         });
0284 
0285     const auto* plot = dynamic_cast<const CartesianPlot*>(aspect);
0286     if (plot) {
0287         connect(plot, &CartesianPlot::axisShiftSignal, this, &Worksheet::cartesianPlotAxisShift);
0288         connect(plot, &CartesianPlot::wheelEventSignal, this, &Worksheet::cartesianPlotWheelEvent);
0289         connect(plot, &CartesianPlot::mouseMoveCursorModeSignal, this, &Worksheet::cartesianPlotMouseMoveCursorMode);
0290         connect(plot, &CartesianPlot::mouseMoveSelectionModeSignal, this, &Worksheet::cartesianPlotMouseMoveSelectionMode);
0291         connect(plot, &CartesianPlot::mouseMoveZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseMoveZoomSelectionMode);
0292         connect(plot, &CartesianPlot::mousePressCursorModeSignal, this, &Worksheet::cartesianPlotMousePressCursorMode);
0293         connect(plot, &CartesianPlot::mousePressZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMousePressZoomSelectionMode);
0294         connect(plot, &CartesianPlot::mouseReleaseZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseReleaseZoomSelectionMode);
0295         connect(plot, &CartesianPlot::mouseHoverZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseHoverZoomSelectionMode);
0296         connect(plot, &CartesianPlot::mouseHoverOutsideDataRectSignal, this, &Worksheet::cartesianPlotMouseHoverOutsideDataRect);
0297         connect(plot, &CartesianPlot::aspectDescriptionChanged, this, &Worksheet::updateCompleteCursorTreeModel);
0298         connect(plot, &CartesianPlot::curveNameChanged, this, &Worksheet::updateCompleteCursorTreeModel);
0299         connect(plot, &CartesianPlot::curveRemoved, this, &Worksheet::curveRemoved);
0300         connect(plot, &CartesianPlot::curveAdded, this, &Worksheet::curveAdded);
0301         connect(plot, &CartesianPlot::visibleChanged, this, &Worksheet::updateCompleteCursorTreeModel);
0302         connect(plot, &CartesianPlot::curveVisibilityChangedSignal, this, &Worksheet::updateCompleteCursorTreeModel);
0303         connect(plot, &CartesianPlot::curveDataChanged, this, &Worksheet::curveDataChanged);
0304         connect(plot,
0305                 static_cast<void (CartesianPlot::*)(const QColor&, const QString&)>(&CartesianPlot::plotColorChanged),
0306                 this,
0307                 &Worksheet::updateCurveBackground);
0308         connect(plot, &CartesianPlot::mouseModeChanged, this, &Worksheet::cartesianPlotMouseModeChangedSlot);
0309         auto* p = const_cast<CartesianPlot*>(plot);
0310         p->setInteractive(d->plotsInteractive);
0311 
0312         cursorModelPlotAdded(p->name());
0313     }
0314     qreal zVal = 0;
0315     for (auto* child : children<WorksheetElement>(ChildIndexFlag::IncludeHidden))
0316         child->graphicsItem()->setZValue(zVal++);
0317 
0318     // if a theme was selected in the worksheet, apply this theme for newly added children
0319     if (!d->theme.isEmpty() && !isLoading() && !pasted() && !aspect->pasted()) {
0320         KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig);
0321         const_cast<WorksheetElement*>(addedElement)->loadThemeConfig(config);
0322     }
0323 
0324     // recalculate the layout if enabled, set the currently added plot resizable otherwise
0325     if (!isLoading()) {
0326         if (d->layout != Worksheet::Layout::NoLayout)
0327             d->updateLayout(false);
0328         else {
0329             if (plot) {
0330                 // make other plots non-resizable
0331                 const auto& containers = children<WorksheetElementContainer>();
0332                 for (auto* container : containers)
0333                     container->setResizeEnabled(false);
0334 
0335                 // make the newly added plot resizable
0336                 const_cast<CartesianPlot*>(plot)->setResizeEnabled(true);
0337             }
0338         }
0339     }
0340 }
0341 
0342 void Worksheet::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) {
0343     Q_D(Worksheet);
0344     const auto* removedElement = qobject_cast<const WorksheetElement*>(aspect);
0345     if (removedElement) {
0346         QGraphicsItem* item = removedElement->graphicsItem();
0347         // TODO: disabled until Origin project import is fixed
0348         if (item->scene() == d->m_scene)
0349             d->m_scene->removeItem(item);
0350     }
0351 }
0352 
0353 void Worksheet::handleAspectRemoved(const AbstractAspect* /*parent*/, const AbstractAspect* /*before*/, const AbstractAspect* child) {
0354     Q_D(Worksheet);
0355     if (d->layout != Worksheet::Layout::NoLayout)
0356         d->updateLayout(false);
0357     auto* plot = dynamic_cast<const CartesianPlot*>(child);
0358     if (plot)
0359         cursorModelPlotRemoved(plot->name());
0360 }
0361 
0362 QGraphicsScene* Worksheet::scene() const {
0363     Q_D(const Worksheet);
0364     return d->m_scene;
0365 }
0366 
0367 QRectF Worksheet::pageRect() const {
0368     Q_D(const Worksheet);
0369     return d->m_scene->sceneRect();
0370 }
0371 
0372 double Worksheet::zoomFactor() const {
0373     return m_view->zoomFactor();
0374 }
0375 
0376 /*!
0377     this slot is called when a worksheet element is selected in the project explorer.
0378     emits \c itemSelected() which forwards this event to the \c WorksheetView
0379     in order to select the corresponding \c QGraphicsItem.
0380  */
0381 void Worksheet::childSelected(const AbstractAspect* aspect) {
0382     auto* element = qobject_cast<WorksheetElement*>(const_cast<AbstractAspect*>(aspect));
0383     if (element)
0384         Q_EMIT itemSelected(element->graphicsItem());
0385 }
0386 
0387 /*!
0388     this slot is called when a worksheet element is deselected in the project explorer.
0389     emits \c itemDeselected() which forwards this event to \c WorksheetView
0390     in order to deselect the corresponding \c QGraphicsItem.
0391  */
0392 void Worksheet::childDeselected(const AbstractAspect* aspect) {
0393     auto* element = qobject_cast<WorksheetElement*>(const_cast<AbstractAspect*>(aspect));
0394     if (element)
0395         Q_EMIT itemDeselected(element->graphicsItem());
0396 }
0397 
0398 /*!
0399  *  Emits the signal to select or to deselect the aspect corresponding to \c QGraphicsItem \c item in the project explorer,
0400  *  if \c selected=true or \c selected=false, respectively.
0401  *  The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer.
0402  * This function is called in \c WorksheetView upon selection changes.
0403  */
0404 void Worksheet::setItemSelectedInView(const QGraphicsItem* item, const bool b) {
0405     // determine the corresponding aspect
0406     AbstractAspect* aspect(nullptr);
0407     for (const auto* child : children<WorksheetElement>(ChildIndexFlag::IncludeHidden)) {
0408         aspect = this->aspectFromGraphicsItem(child, item);
0409         if (aspect)
0410             break;
0411     }
0412 
0413     if (!aspect)
0414         return;
0415 
0416     // forward selection/deselection to AbstractTreeModel
0417     if (b)
0418         Q_EMIT childAspectSelectedInView(aspect);
0419     else
0420         Q_EMIT childAspectDeselectedInView(aspect);
0421 
0422     // handle the resize items on selection changes
0423     if (layout() == Worksheet::Layout::NoLayout) {
0424         // only one selected plot can be made resizable
0425         if (b) {
0426             const auto& items = m_view->selectedItems();
0427             if (items.size() == 1) {
0428                 // only one object is selected.
0429                 // make it resiable if its a container
0430                 auto* container = dynamic_cast<WorksheetElementContainer*>(aspect);
0431                 if (container)
0432                     container->setResizeEnabled(true);
0433             } else if (items.size() > 1) {
0434                 // multiple objects are selected, make all containers non-resizable
0435                 const auto& elements = children<WorksheetElement>();
0436                 for (auto* element : elements) {
0437                     auto* container = dynamic_cast<WorksheetElementContainer*>(element);
0438                     if (container)
0439                         container->setResizeEnabled(false);
0440                 }
0441             }
0442         } else {
0443             auto* container = dynamic_cast<WorksheetElementContainer*>(aspect);
0444             if (container)
0445                 container->setResizeEnabled(false);
0446         }
0447     }
0448 }
0449 
0450 /*!
0451  * helper function:  checks whether \c aspect or one of its children has the \c GraphicsItem \c item
0452  * Returns a pointer to \c WorksheetElement having this item.
0453  */
0454 WorksheetElement* Worksheet::aspectFromGraphicsItem(const WorksheetElement* parent, const QGraphicsItem* item) const {
0455     if (parent->graphicsItem() == item)
0456         return const_cast<WorksheetElement*>(parent);
0457     else {
0458         for (const auto* child : parent->children<WorksheetElement>(AbstractAspect::ChildIndexFlag::IncludeHidden)) {
0459             WorksheetElement* a = this->aspectFromGraphicsItem(child, item);
0460             if (a)
0461                 return a;
0462         }
0463         return nullptr;
0464     }
0465 }
0466 
0467 /*!
0468     Selects or deselects the worksheet in the project explorer.
0469     This function is called in \c WorksheetView.
0470     The worksheet gets deselected if there are selected items in the view,
0471     and selected if there are no selected items in the view.
0472 */
0473 void Worksheet::setSelectedInView(const bool b) {
0474     if (b)
0475         Q_EMIT childAspectSelectedInView(this);
0476     else
0477         Q_EMIT childAspectDeselectedInView(this);
0478 }
0479 
0480 void Worksheet::deleteAspectFromGraphicsItem(const QGraphicsItem* item) {
0481     Q_ASSERT(item);
0482     // determine the corresponding aspect
0483     AbstractAspect* aspect(nullptr);
0484     for (const auto* child : children<WorksheetElement>(ChildIndexFlag::IncludeHidden)) {
0485         aspect = this->aspectFromGraphicsItem(child, item);
0486         if (aspect)
0487             break;
0488     }
0489 
0490     if (!aspect)
0491         return;
0492 
0493     if (aspect->parentAspect())
0494         aspect->parentAspect()->removeChild(aspect);
0495     else
0496         this->removeChild(aspect);
0497 }
0498 
0499 void Worksheet::setIsClosing() {
0500     if (m_view)
0501         m_view->setIsClosing();
0502 }
0503 
0504 void Worksheet::suppressSelectionChangedEvent(bool value) {
0505     if (m_view)
0506         m_view->suppressSelectionChangedEvent(value);
0507 }
0508 
0509 /*!
0510  * \brief Worksheet::plotCount
0511  * \return number of CartesianPlot's in the Worksheet
0512  */
0513 int Worksheet::plotCount() {
0514     return children<CartesianPlot>().length();
0515 }
0516 
0517 /*!
0518  * \brief Worksheet::plot
0519  * \param index Number of plot which should be returned
0520  * \return Pointer to the CartesianPlot which was searched with index
0521  */
0522 CartesianPlot* Worksheet::plot(int index) {
0523     auto plots = children<CartesianPlot>();
0524     if (plots.length() - 1 >= index)
0525         return plots.at(index);
0526     return nullptr;
0527 }
0528 
0529 TreeModel* Worksheet::cursorModel() {
0530     Q_D(const Worksheet);
0531     return d->cursorData;
0532 }
0533 
0534 void Worksheet::update() {
0535     Q_EMIT requestUpdate();
0536     Q_EMIT changed();
0537 }
0538 
0539 void Worksheet::setSuppressLayoutUpdate(bool value) {
0540     Q_D(Worksheet);
0541     d->suppressLayoutUpdate = value;
0542 }
0543 
0544 void Worksheet::updateLayout() {
0545     Q_D(Worksheet);
0546     d->updateLayout();
0547 }
0548 
0549 Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotActionMode() const {
0550     Q_D(const Worksheet);
0551     return d->cartesianPlotActionMode;
0552 }
0553 
0554 Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotCursorMode() const {
0555     Q_D(const Worksheet);
0556     return d->cartesianPlotCursorMode;
0557 }
0558 
0559 bool Worksheet::plotsInteractive() const {
0560     Q_D(const Worksheet);
0561     return d->plotsInteractive;
0562 }
0563 
0564 void Worksheet::setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode mode) {
0565     Q_D(Worksheet);
0566     if (d->cartesianPlotActionMode == mode)
0567         return;
0568 
0569     d->cartesianPlotActionMode = mode;
0570     project()->setChanged(true);
0571 }
0572 
0573 void Worksheet::setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode mode) {
0574     Q_D(Worksheet);
0575     if (d->cartesianPlotCursorMode == mode)
0576         return;
0577 
0578     d->cartesianPlotCursorMode = mode;
0579 
0580     if (mode == CartesianPlotActionMode::ApplyActionToAll) {
0581         d->suppressCursorPosChanged = true;
0582         const auto& plots = children<CartesianPlot>();
0583         QPointF logicPos;
0584         if (!plots.isEmpty()) {
0585             for (int i = 0; i < 2; i++) {
0586                 logicPos = QPointF(plots[0]->cursorPos(i), 0); // y value does not matter
0587                 cartesianPlotMousePressCursorMode(i, logicPos);
0588             }
0589         }
0590         d->suppressCursorPosChanged = false;
0591     }
0592     updateCompleteCursorTreeModel();
0593     project()->setChanged(true);
0594 }
0595 
0596 void Worksheet::setInteractive(bool value) {
0597     if (!m_view)
0598         view();
0599     m_view->setInteractive(value);
0600 }
0601 
0602 void Worksheet::setPlotsInteractive(bool interactive) {
0603     Q_D(Worksheet);
0604     if (d->plotsInteractive == interactive)
0605         return;
0606 
0607     d->plotsInteractive = interactive;
0608 
0609     for (auto* plot : children<CartesianPlot>())
0610         plot->setInteractive(interactive);
0611 
0612     project()->setChanged(true);
0613 }
0614 
0615 void Worksheet::registerShortcuts() {
0616     m_view->registerShortcuts();
0617 }
0618 
0619 WorksheetElement* Worksheet::currentSelection() {
0620     if (!m_view) {
0621         view();
0622         return nullptr;
0623     }
0624 
0625     return m_view->selectedElement();
0626 }
0627 
0628 void Worksheet::unregisterShortcuts() {
0629     m_view->unregisterShortcuts();
0630 }
0631 
0632 /* =============================== getter methods for general options ==================================== */
0633 BASIC_D_READER_IMPL(Worksheet, bool, scaleContent, scaleContent)
0634 BASIC_D_READER_IMPL(Worksheet, bool, useViewSize, useViewSize)
0635 BASIC_D_READER_IMPL(Worksheet, Worksheet::ZoomFit, zoomFit, zoomFit)
0636 
0637 // background
0638 Background* Worksheet::background() const {
0639     Q_D(const Worksheet);
0640     return d->background;
0641 }
0642 
0643 /* =============================== getter methods for layout options ====================================== */
0644 BASIC_D_READER_IMPL(Worksheet, Worksheet::Layout, layout, layout)
0645 BASIC_D_READER_IMPL(Worksheet, double, layoutTopMargin, layoutTopMargin)
0646 BASIC_D_READER_IMPL(Worksheet, double, layoutBottomMargin, layoutBottomMargin)
0647 BASIC_D_READER_IMPL(Worksheet, double, layoutLeftMargin, layoutLeftMargin)
0648 BASIC_D_READER_IMPL(Worksheet, double, layoutRightMargin, layoutRightMargin)
0649 BASIC_D_READER_IMPL(Worksheet, double, layoutHorizontalSpacing, layoutHorizontalSpacing)
0650 BASIC_D_READER_IMPL(Worksheet, double, layoutVerticalSpacing, layoutVerticalSpacing)
0651 BASIC_D_READER_IMPL(Worksheet, int, layoutRowCount, layoutRowCount)
0652 BASIC_D_READER_IMPL(Worksheet, int, layoutColumnCount, layoutColumnCount)
0653 
0654 BASIC_D_READER_IMPL(Worksheet, QString, theme, theme)
0655 
0656 /* ============================ setter methods and undo commands for general options  ===================== */
0657 STD_SETTER_CMD_IMPL_S(Worksheet, SetUseViewSize, bool, useViewSize)
0658 void Worksheet::setUseViewSize(bool useViewSize) {
0659     Q_D(Worksheet);
0660     if (useViewSize != d->useViewSize)
0661         exec(new WorksheetSetUseViewSizeCmd(d, useViewSize, ki18n("%1: change size type")));
0662 }
0663 
0664 void Worksheet::setZoomFit(ZoomFit zoomFit) {
0665     Q_D(Worksheet);
0666     d->zoomFit = zoomFit; // No need to undo
0667 }
0668 
0669 STD_SETTER_CMD_IMPL_S(Worksheet, SetScaleContent, bool, scaleContent)
0670 void Worksheet::setScaleContent(bool scaleContent) {
0671     Q_D(Worksheet);
0672     if (scaleContent != d->scaleContent)
0673         exec(new WorksheetSetScaleContentCmd(d, scaleContent, ki18n("%1: change \"rescale the content\" property")));
0674 }
0675 
0676 /* ============================ setter methods and undo commands  for layout options  ================= */
0677 STD_SETTER_CMD_IMPL_F_S(Worksheet, SetLayout, Worksheet::Layout, layout, updateLayout)
0678 void Worksheet::setLayout(Worksheet::Layout layout) {
0679     Q_D(Worksheet);
0680     if (layout != d->layout) {
0681         beginMacro(i18n("%1: set layout", name()));
0682         exec(new WorksheetSetLayoutCmd(d, layout, ki18n("%1: set layout")));
0683         endMacro();
0684     }
0685 }
0686 
0687 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutTopMargin, double, layoutTopMargin, updateLayout)
0688 void Worksheet::setLayoutTopMargin(double margin) {
0689     Q_D(Worksheet);
0690     if (margin != d->layoutTopMargin) {
0691         beginMacro(i18n("%1: set layout top margin", name()));
0692         exec(new WorksheetSetLayoutTopMarginCmd(d, margin, ki18n("%1: set layout top margin")));
0693         endMacro();
0694     }
0695 }
0696 
0697 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutBottomMargin, double, layoutBottomMargin, updateLayout)
0698 void Worksheet::setLayoutBottomMargin(double margin) {
0699     Q_D(Worksheet);
0700     if (margin != d->layoutBottomMargin) {
0701         beginMacro(i18n("%1: set layout bottom margin", name()));
0702         exec(new WorksheetSetLayoutBottomMarginCmd(d, margin, ki18n("%1: set layout bottom margin")));
0703         endMacro();
0704     }
0705 }
0706 
0707 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutLeftMargin, double, layoutLeftMargin, updateLayout)
0708 void Worksheet::setLayoutLeftMargin(double margin) {
0709     Q_D(Worksheet);
0710     if (margin != d->layoutLeftMargin) {
0711         beginMacro(i18n("%1: set layout left margin", name()));
0712         exec(new WorksheetSetLayoutLeftMarginCmd(d, margin, ki18n("%1: set layout left margin")));
0713         endMacro();
0714     }
0715 }
0716 
0717 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRightMargin, double, layoutRightMargin, updateLayout)
0718 void Worksheet::setLayoutRightMargin(double margin) {
0719     Q_D(Worksheet);
0720     if (margin != d->layoutRightMargin) {
0721         beginMacro(i18n("%1: set layout right margin", name()));
0722         exec(new WorksheetSetLayoutRightMarginCmd(d, margin, ki18n("%1: set layout right margin")));
0723         endMacro();
0724     }
0725 }
0726 
0727 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutVerticalSpacing, double, layoutVerticalSpacing, updateLayout)
0728 void Worksheet::setLayoutVerticalSpacing(double spacing) {
0729     Q_D(Worksheet);
0730     if (spacing != d->layoutVerticalSpacing) {
0731         beginMacro(i18n("%1: set layout vertical spacing", name()));
0732         exec(new WorksheetSetLayoutVerticalSpacingCmd(d, spacing, ki18n("%1: set layout vertical spacing")));
0733         endMacro();
0734     }
0735 }
0736 
0737 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutHorizontalSpacing, double, layoutHorizontalSpacing, updateLayout)
0738 void Worksheet::setLayoutHorizontalSpacing(double spacing) {
0739     Q_D(Worksheet);
0740     if (spacing != d->layoutHorizontalSpacing) {
0741         beginMacro(i18n("%1: set layout horizontal spacing", name()));
0742         exec(new WorksheetSetLayoutHorizontalSpacingCmd(d, spacing, ki18n("%1: set layout horizontal spacing")));
0743         endMacro();
0744     }
0745 }
0746 
0747 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRowCount, int, layoutRowCount, updateLayout)
0748 void Worksheet::setLayoutRowCount(int count) {
0749     Q_D(Worksheet);
0750     if (count != d->layoutRowCount) {
0751         beginMacro(i18n("%1: set layout row count", name()));
0752         exec(new WorksheetSetLayoutRowCountCmd(d, count, ki18n("%1: set layout row count")));
0753         endMacro();
0754     }
0755 }
0756 
0757 STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutColumnCount, int, layoutColumnCount, updateLayout)
0758 void Worksheet::setLayoutColumnCount(int count) {
0759     Q_D(Worksheet);
0760     if (count != d->layoutColumnCount) {
0761         beginMacro(i18n("%1: set layout column count", name()));
0762         exec(new WorksheetSetLayoutColumnCountCmd(d, count, ki18n("%1: set layout column count")));
0763         endMacro();
0764     }
0765 }
0766 
0767 class WorksheetSetPageRectCmd : public StandardMacroSetterCmd<Worksheet::Private, QRectF> {
0768 public:
0769     WorksheetSetPageRectCmd(Worksheet::Private* target, QRectF newValue, const KLocalizedString& description)
0770         : StandardMacroSetterCmd<Worksheet::Private, QRectF>(target, &Worksheet::Private::pageRect, newValue, description) {
0771     }
0772     void finalize() override {
0773         m_target->updatePageRect();
0774         Q_EMIT m_target->q->pageRectChanged(m_target->*m_field);
0775     }
0776     void finalizeUndo() override {
0777         m_target->m_scene->setSceneRect(m_target->*m_field);
0778         Q_EMIT m_target->q->pageRectChanged(m_target->*m_field);
0779     }
0780 };
0781 
0782 void Worksheet::setPageRect(const QRectF& rect) {
0783     Q_D(Worksheet);
0784     // don't allow any rectangulars of width/height equal to zero
0785     if (qFuzzyCompare(rect.width(), 0.) || qFuzzyCompare(rect.height(), 0.)) {
0786         Q_EMIT pageRectChanged(d->pageRect);
0787         return;
0788     }
0789 
0790     if (rect != d->pageRect) {
0791         if (!d->useViewSize) {
0792             beginMacro(i18n("%1: set page size", name()));
0793             exec(new WorksheetSetPageRectCmd(d, rect, ki18n("%1: set page size")));
0794             endMacro();
0795         } else {
0796             d->pageRect = rect;
0797             d->updatePageRect();
0798             Q_EMIT pageRectChanged(d->pageRect);
0799         }
0800     }
0801 }
0802 
0803 void Worksheet::setPrinting(bool on) const {
0804     const auto& elements = children<WorksheetElement>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0805     for (auto* elem : elements)
0806         elem->setPrinting(on);
0807 }
0808 
0809 STD_SETTER_CMD_IMPL_S(Worksheet, SetTheme, QString, theme)
0810 void Worksheet::setTheme(const QString& theme) {
0811     Q_D(Worksheet);
0812     QString info;
0813     if (!theme.isEmpty())
0814         info = i18n("%1: load theme %2", name(), theme);
0815     else
0816         info = i18n("%1: load default theme", name());
0817     beginMacro(info);
0818     exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: set theme")));
0819     loadTheme(theme);
0820     endMacro();
0821 }
0822 
0823 void Worksheet::cartesianPlotMousePressZoomSelectionMode(QPointF logicPos) {
0824     auto senderPlot = static_cast<CartesianPlot*>(QObject::sender());
0825     auto mouseMode = senderPlot->mouseMode();
0826     auto actionMode = cartesianPlotActionMode();
0827     if (actionMode == CartesianPlotActionMode::ApplyActionToAll) {
0828         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0829         for (auto* plot : plots)
0830             plot->mousePressZoomSelectionMode(logicPos, -1);
0831     } else if ((actionMode == CartesianPlotActionMode::ApplyActionToAllX && mouseMode != CartesianPlot::MouseMode::ZoomYSelection)
0832                || (actionMode == CartesianPlotActionMode::ApplyActionToAllY && mouseMode != CartesianPlot::MouseMode::ZoomXSelection)) {
0833         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0834         for (auto* plot : plots) {
0835             if (plot != senderPlot) {
0836                 if (actionMode == CartesianPlotActionMode::ApplyActionToAllX)
0837                     plot->setMouseMode(CartesianPlot::MouseMode::ZoomXSelection);
0838                 else if (actionMode == CartesianPlotActionMode::ApplyActionToAllY)
0839                     plot->setMouseMode(CartesianPlot::MouseMode::ZoomYSelection);
0840             }
0841             plot->mousePressZoomSelectionMode(logicPos, -1);
0842         }
0843     } else {
0844         int index = CartesianPlot::cSystemIndex(m_view->selectedElement());
0845         senderPlot->mousePressZoomSelectionMode(logicPos, index);
0846     }
0847 }
0848 
0849 void Worksheet::cartesianPlotMouseReleaseZoomSelectionMode() {
0850     auto senderPlot = static_cast<CartesianPlot*>(QObject::sender());
0851     auto mouseMode = senderPlot->mouseMode();
0852     auto actionMode = cartesianPlotActionMode();
0853     if (actionMode == CartesianPlotActionMode::ApplyActionToAll
0854         || (actionMode == CartesianPlotActionMode::ApplyActionToAllX && mouseMode != CartesianPlot::MouseMode::ZoomYSelection)
0855         || (actionMode == CartesianPlotActionMode::ApplyActionToAllY && mouseMode != CartesianPlot::MouseMode::ZoomXSelection)) {
0856         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0857         for (auto* plot : plots) {
0858             plot->mouseReleaseZoomSelectionMode(-1);
0859             plot->setMouseMode(mouseMode);
0860         }
0861     } else {
0862         int index = CartesianPlot::cSystemIndex(m_view->selectedElement());
0863         auto* plot = static_cast<CartesianPlot*>(QObject::sender());
0864         plot->mouseReleaseZoomSelectionMode(index);
0865     }
0866 }
0867 
0868 void Worksheet::cartesianPlotMousePressCursorMode(int cursorNumber, QPointF logicPos) {
0869     if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) {
0870         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0871         for (auto* plot : plots)
0872             plot->mousePressCursorMode(cursorNumber, logicPos);
0873     } else {
0874         auto* plot = static_cast<CartesianPlot*>(QObject::sender());
0875         plot->mousePressCursorMode(cursorNumber, logicPos);
0876     }
0877 
0878     cursorPosChanged(cursorNumber, logicPos.x());
0879 }
0880 
0881 void Worksheet::cartesianPlotMouseMoveZoomSelectionMode(QPointF logicPos) {
0882     auto senderPlot = static_cast<CartesianPlot*>(QObject::sender());
0883     auto actionMode = cartesianPlotActionMode();
0884     auto mouseMode = senderPlot->mouseMode();
0885     if (actionMode == CartesianPlotActionMode::ApplyActionToAll
0886         || (actionMode == CartesianPlotActionMode::ApplyActionToAllX && mouseMode != CartesianPlot::MouseMode::ZoomYSelection)
0887         || (actionMode == CartesianPlotActionMode::ApplyActionToAllY && mouseMode != CartesianPlot::MouseMode::ZoomXSelection)) {
0888         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0889         for (auto* plot : plots)
0890             plot->mouseMoveZoomSelectionMode(logicPos, -1);
0891     } else
0892         senderPlot->mouseMoveZoomSelectionMode(logicPos, CartesianPlot::cSystemIndex(m_view->selectedElement()));
0893 }
0894 
0895 void Worksheet::cartesianPlotMouseMoveSelectionMode(QPointF logicStart, QPointF logicEnd) {
0896     auto* senderPlot = static_cast<CartesianPlot*>(QObject::sender());
0897     auto actionMode = cartesianPlotActionMode();
0898     if (actionMode == CartesianPlotActionMode::ApplyActionToAll) {
0899         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0900         for (auto* plot : plots)
0901             plot->mouseMoveSelectionMode(logicStart, logicEnd);
0902     } else if (actionMode == CartesianPlotActionMode::ApplyActionToSelection) {
0903         senderPlot->mouseMoveSelectionMode(logicStart, logicEnd);
0904     } else {
0905         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0906         if (actionMode == CartesianPlotActionMode::ApplyActionToAllX) {
0907             // value does not matter, only difference
0908             logicStart.setY(0);
0909             logicEnd.setY(0);
0910             for (auto* plot : plots)
0911                 plot->mouseMoveSelectionMode(logicStart, logicEnd);
0912         } else if (actionMode == CartesianPlotActionMode::ApplyActionToAllY) {
0913             // value does not matter, only difference
0914             logicStart.setX(0);
0915             logicEnd.setX(0);
0916             for (auto* plot : plots)
0917                 plot->mouseMoveSelectionMode(logicStart, logicEnd);
0918         }
0919     }
0920 }
0921 
0922 void Worksheet::cartesianPlotMouseHoverZoomSelectionMode(QPointF logicPos) {
0923     auto senderPlot = static_cast<CartesianPlot*>(QObject::sender());
0924     auto actionMode = cartesianPlotActionMode();
0925     auto mouseMode = senderPlot->mouseMode();
0926     if (actionMode == CartesianPlotActionMode::ApplyActionToAll
0927         || (actionMode == CartesianPlotActionMode::ApplyActionToAllX && mouseMode != CartesianPlot::MouseMode::ZoomYSelection)
0928         || (actionMode == CartesianPlotActionMode::ApplyActionToAllY && mouseMode != CartesianPlot::MouseMode::ZoomXSelection)) {
0929         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0930         for (auto* plot : plots)
0931             plot->mouseHoverZoomSelectionMode(logicPos, -1);
0932     } else {
0933         if (m_view->selectedElement()->parent(AspectType::CartesianPlot) == senderPlot)
0934             senderPlot->mouseHoverZoomSelectionMode(logicPos, CartesianPlot::cSystemIndex(m_view->selectedElement()));
0935         else
0936             senderPlot->mouseHoverZoomSelectionMode(logicPos, -1);
0937     }
0938 }
0939 
0940 void Worksheet::cartesianPlotMouseHoverOutsideDataRect() {
0941     auto senderPlot = static_cast<CartesianPlot*>(QObject::sender());
0942     auto mode = cartesianPlotActionMode();
0943     auto mouseMode = senderPlot->mouseMode();
0944     if (cartesianPlotActionMode() == CartesianPlotActionMode::ApplyActionToAll
0945         || (mode == CartesianPlotActionMode::ApplyActionToAllX && mouseMode != CartesianPlot::MouseMode::ZoomYSelection)
0946         || (mode == CartesianPlotActionMode::ApplyActionToAllY && mouseMode != CartesianPlot::MouseMode::ZoomXSelection)) {
0947         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0948         for (auto* plot : plots)
0949             plot->mouseHoverOutsideDataRect();
0950     } else
0951         senderPlot->mouseHoverOutsideDataRect();
0952 }
0953 
0954 void Worksheet::cartesianPlotAxisShift(int delta, Dimension dim, int index) {
0955     const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0956     const auto cursorMode = cartesianPlotActionMode();
0957     bool leftOrDown = false;
0958     if (delta < 0)
0959         leftOrDown = true;
0960 
0961     switch (cursorMode) {
0962     case CartesianPlotActionMode::ApplyActionToAll: {
0963         for (auto* plot : plots)
0964             plot->shift(-1, dim, leftOrDown);
0965         break;
0966     }
0967     case CartesianPlotActionMode::ApplyActionToAllX: {
0968         switch (dim) {
0969         case Dimension::X: {
0970             for (auto* plot : plots)
0971                 plot->shift(-1, dim, leftOrDown);
0972             break;
0973         }
0974         case Dimension::Y: {
0975             auto* plot = static_cast<CartesianPlot*>(QObject::sender());
0976             plot->shift(index, dim, leftOrDown);
0977             break;
0978         }
0979         }
0980         break;
0981     }
0982     case CartesianPlotActionMode::ApplyActionToAllY: {
0983         switch (dim) {
0984         case Dimension::X: {
0985             for (auto* plot : plots)
0986                 plot->shift(index, dim, leftOrDown);
0987             break;
0988         }
0989         case Dimension::Y: {
0990             auto* plot = static_cast<CartesianPlot*>(QObject::sender());
0991             plot->shift(-1, dim, leftOrDown);
0992             break;
0993         }
0994         }
0995         break;
0996     }
0997     case CartesianPlotActionMode::ApplyActionToSelection: {
0998         auto* plot = static_cast<CartesianPlot*>(QObject::sender());
0999         plot->shift(index, dim, leftOrDown);
1000         break;
1001     }
1002     }
1003 }
1004 
1005 void Worksheet::cartesianPlotWheelEvent(const QPointF& sceneRelPos, int delta, int xIndex, int yIndex, bool considerDimension, Dimension dim) {
1006     const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
1007     const auto cursorMode = cartesianPlotActionMode();
1008     if (considerDimension) {
1009         if ((dim == Dimension::X && (cursorMode == CartesianPlotActionMode::ApplyActionToAllX || cursorMode == CartesianPlotActionMode::ApplyActionToAll))
1010             || (dim == Dimension::Y && (cursorMode == CartesianPlotActionMode::ApplyActionToAllY || cursorMode == CartesianPlotActionMode::ApplyActionToAll))) {
1011             for (auto* plot : plots)
1012                 plot->wheelEvent(sceneRelPos, delta, -1, -1, considerDimension, dim);
1013         } else {
1014             auto* plot = static_cast<CartesianPlot*>(QObject::sender());
1015             plot->wheelEvent(sceneRelPos, delta, xIndex, yIndex, considerDimension, dim);
1016         }
1017 
1018     } else {
1019         switch (cursorMode) {
1020         case CartesianPlotActionMode::ApplyActionToAll: {
1021             for (auto* plot : plots)
1022                 plot->wheelEvent(sceneRelPos, delta, -1, -1, considerDimension, dim);
1023             break;
1024         }
1025         case CartesianPlotActionMode::ApplyActionToAllX: {
1026             auto* plot = static_cast<CartesianPlot*>(QObject::sender());
1027             plot->wheelEvent(sceneRelPos, delta, -1, yIndex, considerDimension, dim);
1028             for (auto* p : plots) {
1029                 if (p != plot) {
1030                     // The yIndex must not be available in the other plots
1031                     // yIndex does not matter, because considerDimension is true
1032                     p->wheelEvent(sceneRelPos, delta, -1, -1, true, Dimension::X);
1033                 }
1034             }
1035             break;
1036         }
1037         case CartesianPlotActionMode::ApplyActionToAllY: {
1038             auto* plot = static_cast<CartesianPlot*>(QObject::sender());
1039             // wheelEvent on all y in that plot
1040             plot->wheelEvent(sceneRelPos, delta, xIndex, -1, considerDimension, dim);
1041             for (auto* p : plots) {
1042                 if (p != plot) {
1043                     // The xIndex must not be available in the other plots
1044                     // xIndex does not matter, because considerDimension is true
1045                     p->wheelEvent(sceneRelPos, delta, -1, -1, true, Dimension::Y);
1046                 }
1047             }
1048             break;
1049         }
1050         case CartesianPlotActionMode::ApplyActionToSelection: {
1051             auto* plot = static_cast<CartesianPlot*>(QObject::sender());
1052             plot->wheelEvent(sceneRelPos, delta, xIndex, yIndex, considerDimension, dim);
1053             break;
1054         }
1055         }
1056     }
1057 }
1058 
1059 void Worksheet::cartesianPlotMouseMoveCursorMode(int cursorNumber, QPointF logicPos) {
1060     if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) {
1061         const auto& plots = children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
1062         for (auto* plot : plots)
1063             plot->mouseMoveCursorMode(cursorNumber, logicPos);
1064     } else {
1065         auto* plot = static_cast<CartesianPlot*>(QObject::sender());
1066         plot->mouseMoveCursorMode(cursorNumber, logicPos);
1067     }
1068 
1069     cursorPosChanged(cursorNumber, logicPos.x());
1070 }
1071 
1072 QString dateTimeDiffToString(const QDateTime& dt0, const QDateTime& dt1) {
1073     // dt0 to dt1 is positive if dt0 is smaller than dt1
1074     QString result;
1075     qint64 diff;
1076     bool negative = false;
1077     ;
1078     if (dt0 < dt1)
1079         diff = dt0.msecsTo(dt1);
1080     else {
1081         diff = dt1.msecsTo(dt0);
1082         negative = true;
1083     }
1084     const qint64 dayToMsecs = 24 * 3600 * 1000;
1085     const qint64 hourToMsecs = 3600 * 1000;
1086     const qint64 minutesToMsecs = 60 * 1000;
1087 
1088     qint64 days = diff / dayToMsecs;
1089     diff -= days * dayToMsecs;
1090     qint64 hours = diff / hourToMsecs;
1091     diff -= hours * hourToMsecs;
1092     qint64 minutes = diff / minutesToMsecs;
1093     diff -= minutes * minutesToMsecs;
1094     qint64 seconds = diff / 1000;
1095     diff -= seconds * 1000;
1096     qint64 msecs = diff;
1097 
1098     if (negative)
1099         result += QStringLiteral("- ");
1100 
1101     if (days > 0)
1102         result += QString::number(days) + QStringLiteral(" ") + QObject::tr("days") + QStringLiteral(" ");
1103 
1104     if (hours > 0)
1105         result += QString::number(hours) + QStringLiteral(":");
1106     else
1107         result += QStringLiteral("00:");
1108 
1109     if (minutes > 0)
1110         result += QString::number(minutes) + QStringLiteral(":");
1111     else
1112         result += QStringLiteral("00:");
1113 
1114     if (seconds > 0)
1115         result += QString::number(seconds) + QStringLiteral(".");
1116     else
1117         result += QStringLiteral("00.");
1118 
1119     if (msecs > 0)
1120         result += QString::number(msecs);
1121     else
1122         result += QStringLiteral("000");
1123 
1124     return result;
1125 }
1126 
1127 /*!
1128  * \brief Worksheet::cursorPosChanged
1129  * Updates the cursor treemodel with the new data
1130  * \param xPos: new position of the cursor
1131  * It is assumed, that the plots/curves are in the same order than receiving from
1132  * the children() function. It's not checked if the names are the same
1133  */
1134 void Worksheet::cursorPosChanged(int cursorNumber, double xPos) {
1135     Q_D(const Worksheet);
1136     if (d->suppressCursorPosChanged)
1137         return;
1138 
1139     auto* sender = dynamic_cast<CartesianPlot*>(QObject::sender());
1140     if (!sender)
1141         return;
1142 
1143     TreeModel* treeModel = cursorModel();
1144 
1145     // if ApplyActionToSelection, each plot has it's own x value
1146     bool isDatetime = sender->xRangeFormatDefault() == RangeT::Format::DateTime;
1147     if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) {
1148         // x values
1149         int rowPlot = 1;
1150         QModelIndex xName = treeModel->index(0, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME));
1151         treeModel->setData(xName, QVariant(QStringLiteral("X")));
1152         double valueCursor[2];
1153         QDateTime datetime[2];
1154         for (int i = 0; i < 2; i++) { // need both cursors to calculate diff
1155             QVariant data;
1156             valueCursor[i] = sender->cursorPos(i);
1157             if (isDatetime) {
1158                 datetime[i] = QDateTime::fromMSecsSinceEpoch(valueCursor[i], Qt::UTC);
1159                 data = datetime[i].toString(sender->rangeDateTimeFormat(Dimension::X));
1160             } else
1161                 data = QVariant(valueCursor[i]);
1162             treeModel->setTreeData(data, 0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0) + i);
1163         }
1164         if (isDatetime)
1165             treeModel->setTreeData(dateTimeDiffToString(datetime[0], datetime[1]), 0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF));
1166         else
1167             treeModel->setTreeData(QVariant(valueCursor[1] - valueCursor[0]), 0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF));
1168 
1169         // y values
1170         for (int i = 0; i < plotCount(); i++) { // i=0 is the x Axis
1171 
1172             auto* p = plot(i);
1173             if (!p || !p->isVisible())
1174                 continue;
1175 
1176             QModelIndex plotIndex = treeModel->index(rowPlot, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1177 
1178             // curves
1179             int rowCurve = 0;
1180             for (int j = 0; j < p->curveCount(); j++) {
1181                 // assumption: index of signals in model is the same than the index of the signal in the plot
1182                 bool valueFound;
1183 
1184                 const XYCurve* curve = p->getCurve(j);
1185                 if (!curve->isVisible())
1186                     continue;
1187 
1188                 double value = curve->y(xPos, valueFound);
1189                 if (cursorNumber == 0) {
1190                     treeModel->setTreeData(QVariant(value), rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex);
1191                     double valueCursor1 = treeModel->treeData(rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex).toDouble();
1192                     treeModel->setTreeData(QVariant(valueCursor1 - value),
1193                                            rowCurve,
1194                                            static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF),
1195                                            plotIndex);
1196                 } else {
1197                     treeModel->setTreeData(QVariant(value), rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex);
1198                     double valueCursor0 = treeModel->treeData(rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex).toDouble();
1199                     treeModel->setTreeData(QVariant(value - valueCursor0),
1200                                            rowCurve,
1201                                            static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF),
1202                                            plotIndex);
1203                 }
1204                 rowCurve++;
1205             }
1206             rowPlot++;
1207         }
1208     } else { // apply to selection
1209         // assumption: plot is visible
1210         int rowCount = treeModel->rowCount();
1211         for (int i = 0; i < rowCount; i++) {
1212             QModelIndex plotIndex = treeModel->index(i, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1213             if (plotIndex.data().toString().compare(sender->name()) != 0)
1214                 continue;
1215 
1216             // x values (first row always exist)
1217             treeModel->setTreeData(QVariant(QStringLiteral("X")), 0, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex);
1218             double valueCursor[2];
1219             for (int i = 0; i < 2; i++) { // need both cursors to calculate diff
1220                 valueCursor[i] = sender->cursorPos(i);
1221                 treeModel->setTreeData(QVariant(valueCursor[i]), 0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0) + i, plotIndex);
1222             }
1223             treeModel->setTreeData(QVariant(valueCursor[1] - valueCursor[0]), 0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex);
1224 
1225             // y values
1226             int rowCurve = 1; // first is x value
1227             for (int j = 0; j < sender->curveCount(); j++) { // j=0 are the x values
1228 
1229                 const XYCurve* curve = sender->getCurve(j); // -1 because we start with 1 for the x axis
1230                 if (!curve->isVisible())
1231                     continue;
1232 
1233                 // assumption: index of signals in model is the same than the index of the signal in the plot
1234                 bool valueFound;
1235 
1236                 double value = curve->y(xPos, valueFound);
1237                 if (cursorNumber == 0) {
1238                     treeModel->setTreeData(QVariant(value), rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex);
1239                     double valueCursor1 = treeModel->treeData(rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex).toDouble();
1240                     treeModel->setTreeData(QVariant(valueCursor1 - value),
1241                                            rowCurve,
1242                                            static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF),
1243                                            plotIndex);
1244                 } else {
1245                     treeModel->setTreeData(QVariant(value), rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex);
1246                     double valueCursor0 = treeModel->treeData(rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex).toDouble();
1247                     treeModel->setTreeData(QVariant(value - valueCursor0),
1248                                            rowCurve,
1249                                            static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF),
1250                                            plotIndex);
1251                 }
1252                 rowCurve++;
1253             }
1254         }
1255     }
1256 }
1257 
1258 void Worksheet::cursorModelPlotAdded(const QString& /*name*/) {
1259     //  TreeModel* treeModel = cursorModel();
1260     //  int rowCount = treeModel->rowCount();
1261     //  // add plot at the end
1262     //  treeModel->insertRows(rowCount, 1); // add empty rows. Then they become filled
1263     //  treeModel->setTreeData(QVariant(name), rowCount, WorksheetPrivate::TreeModelColumn::PLOTNAME); // rowCount instead of rowCount -1 because first row is
1264     // the x value
1265     updateCompleteCursorTreeModel();
1266 }
1267 
1268 void Worksheet::cursorModelPlotRemoved(const QString& name) {
1269     TreeModel* treeModel = cursorModel();
1270     int rowCount = treeModel->rowCount();
1271 
1272     // first is x Axis
1273     for (int i = 1; i < rowCount; i++) {
1274         QModelIndex plotIndex = treeModel->index(i, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1275         if (plotIndex.data().toString().compare(name) != 0)
1276             continue;
1277         treeModel->removeRows(plotIndex.row(), 1);
1278         return;
1279     }
1280 }
1281 
1282 void Worksheet::cartesianPlotMouseModeChangedSlot(CartesianPlot::MouseMode mode) {
1283     Q_D(Worksheet);
1284     if (d->updateCompleteCursorModel) {
1285         updateCompleteCursorTreeModel();
1286         d->updateCompleteCursorModel = false;
1287     }
1288 
1289     Q_EMIT cartesianPlotMouseModeChanged(mode);
1290 }
1291 
1292 void Worksheet::curveDataChanged(const XYCurve* curve) {
1293     auto* plot = dynamic_cast<CartesianPlot*>(QObject::sender());
1294     if (!plot || !curve)
1295         return;
1296 
1297     TreeModel* treeModel = cursorModel();
1298     int rowCount = treeModel->rowCount();
1299 
1300     for (int i = 0; i < rowCount; i++) {
1301         QModelIndex plotIndex = treeModel->index(i, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1302         if (plotIndex.data().toString().compare(plot->name()) != 0)
1303             continue;
1304 
1305         for (int j = 0; j < plot->curveCount(); j++) {
1306             if (plot->getCurve(j)->name().compare(curve->name()) != 0)
1307                 continue;
1308 
1309             treeModel->setTreeData(QVariant(curve->name()), j, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex);
1310 
1311             bool valueFound;
1312             double valueCursor0 = curve->y(plot->cursorPos(0), valueFound);
1313             treeModel->setTreeData(QVariant(valueCursor0), j, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex);
1314 
1315             double valueCursor1 = curve->y(plot->cursorPos(1), valueFound);
1316             treeModel->setTreeData(QVariant(valueCursor1), j, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex);
1317 
1318             treeModel->setTreeData(QVariant(valueCursor1 - valueCursor0), j, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex);
1319             break;
1320         }
1321         break;
1322     }
1323 }
1324 
1325 void Worksheet::curveAdded(const XYCurve* curve) {
1326     Q_D(const Worksheet);
1327     auto* plot = dynamic_cast<CartesianPlot*>(QObject::sender());
1328     if (!plot)
1329         return;
1330 
1331     TreeModel* treeModel = cursorModel();
1332     int rowCount = treeModel->rowCount();
1333 
1334     int i = 0;
1335     // first row is the x axis when applied to all plots. Starting at the second row
1336     if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll)
1337         i = 1;
1338 
1339     for (; i < rowCount; i++) {
1340         QModelIndex plotIndex = treeModel->index(i, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1341         if (plotIndex.data().toString().compare(plot->name()) != 0)
1342             continue;
1343         int row = 0;
1344         for (int j = 0; j < plot->curveCount(); j++) {
1345             if (plot->getCurve(j)->name().compare(curve->name()) != 0) {
1346                 if (plot->getCurve(j)->isVisible())
1347                     row++;
1348                 continue;
1349             }
1350 
1351             treeModel->insertRow(row, plotIndex);
1352 
1353             treeModel->setTreeData(QVariant(curve->name()), row, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex);
1354             QColor curveColor = curve->line()->pen().color();
1355             curveColor.setAlpha(d->cursorTreeModelCurveBackgroundAlpha);
1356             treeModel->setTreeData(QVariant(curveColor), row, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex, Qt::BackgroundRole);
1357             bool valueFound;
1358             double valueCursor0 = curve->y(plot->cursorPos(0), valueFound);
1359             treeModel->setTreeData(QVariant(valueCursor0), row, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex);
1360 
1361             double valueCursor1 = curve->y(plot->cursorPos(1), valueFound);
1362             treeModel->setTreeData(QVariant(valueCursor1), row, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex);
1363 
1364             treeModel->setTreeData(QVariant(valueCursor1 - valueCursor0), row, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex);
1365             break;
1366         }
1367         break;
1368     }
1369 }
1370 
1371 void Worksheet::curveRemoved(const XYCurve* curve) {
1372     auto* plot = dynamic_cast<CartesianPlot*>(QObject::sender());
1373     if (!plot)
1374         return;
1375 
1376     TreeModel* treeModel = cursorModel();
1377     int rowCount = treeModel->rowCount();
1378 
1379     for (int i = 0; i < rowCount; i++) {
1380         QModelIndex plotIndex = treeModel->index(i, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1381         if (plotIndex.data().toString().compare(plot->name()) != 0)
1382             continue;
1383 
1384         int curveCount = treeModel->rowCount(plotIndex);
1385         for (int j = 0; j < curveCount; j++) {
1386             QModelIndex curveIndex = treeModel->index(j, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex);
1387 
1388             if (curveIndex.data().toString().compare(curve->name()) != 0)
1389                 continue;
1390             treeModel->removeRow(j, plotIndex);
1391             break;
1392         }
1393         break;
1394     }
1395 }
1396 
1397 /*!
1398  * Updates the background of the cuves entry in the treeview
1399  * @param pen Pen of the curve
1400  * @param curveName Curve name to find in treemodel
1401  */
1402 void Worksheet::updateCurveBackground(QColor color, const QString& curveName) {
1403     Q_D(const Worksheet);
1404     const auto* plot = static_cast<const CartesianPlot*>(QObject::sender());
1405     auto* treeModel = cursorModel();
1406     int rowCount = treeModel->rowCount();
1407 
1408     for (int i = 0; i < rowCount; i++) {
1409         auto plotIndex = treeModel->index(i, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1410         if (plotIndex.data().toString().compare(plot->name()) != 0)
1411             continue;
1412 
1413         int curveCount = treeModel->rowCount(plotIndex);
1414         for (int j = 0; j < curveCount; j++) {
1415             auto curveIndex = treeModel->index(j, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex);
1416 
1417             if (curveIndex.data().toString().compare(curveName) != 0)
1418                 continue;
1419 
1420             color.setAlpha(d->cursorTreeModelCurveBackgroundAlpha);
1421             treeModel->setTreeData(QVariant(color), j, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex, Qt::BackgroundRole);
1422             return;
1423         }
1424         return;
1425     }
1426 }
1427 
1428 /**
1429  * @brief Worksheet::updateCompleteCursorTreeModel
1430  * If the plot or the curve are not available, the plot/curve is not in the treemodel!
1431  */
1432 void Worksheet::updateCompleteCursorTreeModel() {
1433     Q_D(const Worksheet);
1434     if (isLoading())
1435         return;
1436 
1437     TreeModel* treeModel = cursorModel();
1438 
1439     if (treeModel->rowCount() > 0)
1440         treeModel->removeRows(0, treeModel->rowCount()); // remove all data
1441 
1442     int pc = plotCount();
1443     if (pc < 1)
1444         return;
1445 
1446     if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) {
1447         // 1 because of the X data
1448         treeModel->insertRows(0, 1); //, treeModel->index(0,0)); // add empty rows. Then they become filled
1449 
1450         // set X data
1451         QModelIndex xName = treeModel->index(0, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME));
1452         treeModel->setData(xName, QVariant(QStringLiteral("X")));
1453         auto* plot0 = plot(0);
1454         if (plot0) {
1455             double valueCursor[2];
1456             for (int i = 0; i < 2; i++) {
1457                 valueCursor[i] = plot0->cursorPos(i);
1458                 QModelIndex cursor = treeModel->index(0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0) + i);
1459 
1460                 treeModel->setData(cursor, QVariant(valueCursor[i]));
1461             }
1462             QModelIndex diff = treeModel->index(0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF));
1463             treeModel->setData(diff, QVariant(valueCursor[1] - valueCursor[0]));
1464         }
1465     } else {
1466         // treeModel->insertRows(0, plotCount, treeModel->index(0,0)); // add empty rows. Then they become filled
1467     }
1468 
1469     // set plot name, y value, background
1470     for (int i = 0; i < pc; i++) {
1471         auto* p = plot(i);
1472         QModelIndex plotName;
1473         int addOne = 0;
1474 
1475         if (!p || !p->isVisible())
1476             continue;
1477 
1478         // add new entry for the plot
1479         treeModel->insertRows(treeModel->rowCount(), 1); //, treeModel->index(0, 0));
1480 
1481         // add plot name and X row if needed
1482         if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) {
1483             plotName = treeModel->index(i + 1, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME)); // plus one because first row are the x values
1484             treeModel->setData(plotName, QVariant(p->name()));
1485         } else {
1486             addOne = 1;
1487             plotName = treeModel->index(i, static_cast<int>(WorksheetPrivate::TreeModelColumn::PLOTNAME));
1488             treeModel->setData(plotName, QVariant(p->name()));
1489             treeModel->insertRows(0, 1, plotName); // one, because the first row are the x values
1490 
1491             QModelIndex xName = treeModel->index(0, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotName);
1492             treeModel->setData(xName, QVariant(QStringLiteral("X")));
1493             double valueCursor[2];
1494             for (int i = 0; i < 2; i++) {
1495                 valueCursor[i] = p->cursorPos(i);
1496                 QModelIndex cursor = treeModel->index(0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0) + i, plotName);
1497 
1498                 treeModel->setData(cursor, QVariant(valueCursor[i]));
1499             }
1500             QModelIndex diff = treeModel->index(0, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotName);
1501             treeModel->setData(diff, QVariant(valueCursor[1] - valueCursor[0]));
1502         }
1503 
1504         int rowCurve = addOne;
1505         for (int j = 0; j < p->curveCount(); j++) {
1506             double cursorValue[2] = {NAN, NAN};
1507             const XYCurve* curve = p->getCurve(j);
1508 
1509             if (!curve->isVisible())
1510                 continue;
1511 
1512             for (int k = 0; k < 2; k++) {
1513                 double xPos = p->cursorPos(k);
1514                 bool valueFound;
1515                 cursorValue[k] = curve->y(xPos, valueFound);
1516             }
1517             treeModel->insertRows(rowCurve, 1, plotName);
1518             QColor curveColor = curve->line()->pen().color();
1519             curveColor.setAlpha(d->cursorTreeModelCurveBackgroundAlpha);
1520             treeModel->setTreeData(QVariant(curveColor), rowCurve, 0, plotName, Qt::BackgroundRole);
1521             treeModel->setTreeData(QVariant(curve->name()), rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotName);
1522             treeModel->setTreeData(QVariant(cursorValue[0]), rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR0), plotName);
1523             treeModel->setTreeData(QVariant(cursorValue[1]), rowCurve, static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSOR1), plotName);
1524             treeModel->setTreeData(QVariant(cursorValue[1] - cursorValue[0]),
1525                                    rowCurve,
1526                                    static_cast<int>(WorksheetPrivate::TreeModelColumn::CURSORDIFF),
1527                                    plotName);
1528             rowCurve++;
1529         }
1530     }
1531 }
1532 
1533 // ##############################################################################
1534 // ######################  Private implementation ###############################
1535 // ##############################################################################
1536 WorksheetPrivate::WorksheetPrivate(Worksheet* owner)
1537     : q(owner)
1538     , m_scene(new QGraphicsScene()) {
1539     QStringList headers = {i18n("Curves"), QStringLiteral("V1"), QStringLiteral("V2"), QStringLiteral("V2-V1")};
1540     cursorData = new TreeModel(headers, nullptr);
1541 }
1542 
1543 QString WorksheetPrivate::name() const {
1544     return q->name();
1545 }
1546 
1547 /*!
1548  * called if the worksheet page (the actual size of worksheet's rectangular) was changed.
1549  * if a layout is active, it is is updated - this adjusts the sizes of the elements in the layout to the new page size.
1550  * if no layout is active and the option "scale content" is active, \c handleResize() is called to adjust the properties.
1551  */
1552 void WorksheetPrivate::updatePageRect() {
1553     if (q->isLoading())
1554         return;
1555 
1556     QRectF oldRect = m_scene->sceneRect();
1557     m_scene->setSceneRect(pageRect);
1558 
1559     if (layout != Worksheet::Layout::NoLayout)
1560         updateLayout();
1561     else {
1562         if (scaleContent) {
1563             qreal horizontalRatio = pageRect.width() / oldRect.width();
1564             qreal verticalRatio = pageRect.height() / oldRect.height();
1565             const auto& children = q->children<WorksheetElement>(AbstractAspect::ChildIndexFlag::IncludeHidden);
1566             if (useViewSize) {
1567                 // don't make the change of the geometry undoable/redoable if the view size is used.
1568                 for (auto* elem : children) {
1569                     elem->setUndoAware(false);
1570                     elem->handleResize(horizontalRatio, verticalRatio, true);
1571                     elem->setUndoAware(true);
1572                 }
1573             } else {
1574                 //              for (auto* child : children)
1575                 //                  child->handleResize(horizontalRatio, verticalRatio, true);
1576             }
1577         }
1578     }
1579 }
1580 
1581 void WorksheetPrivate::update() {
1582     q->update();
1583 }
1584 
1585 WorksheetPrivate::~WorksheetPrivate() {
1586     delete m_scene;
1587     delete cursorData;
1588 }
1589 
1590 void WorksheetPrivate::updateLayout(bool undoable) {
1591     if (suppressLayoutUpdate)
1592         return;
1593 
1594     const auto& list = q->children<WorksheetElementContainer>();
1595     int count = 0;
1596     for (auto* elem : list)
1597         if (elem->isVisible())
1598             ++count;
1599 
1600     if (count == 0)
1601         return;
1602 
1603     // determine the currently selected plot/container and make it
1604     // resizable or not depending on the layout settings
1605     bool resizable = (layout == Worksheet::Layout::NoLayout);
1606     if (q->m_view) {
1607         const auto& items = q->m_view->selectedItems();
1608         if (items.size() == 1) {
1609             const auto& item = items.constFirst();
1610             const auto& containers = q->children<WorksheetElementContainer>();
1611             for (auto* container : containers) {
1612                 if (container->graphicsItem() == item) {
1613                     container->setResizeEnabled(resizable);
1614                     break;
1615                 }
1616             }
1617         }
1618     }
1619 
1620     if (layout == Worksheet::Layout::NoLayout) {
1621         for (auto* elem : list)
1622             elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true);
1623 
1624         return;
1625     }
1626 
1627     double x = layoutLeftMargin;
1628     double y = layoutTopMargin;
1629     double w, h;
1630     if (layout == Worksheet::Layout::VerticalLayout) {
1631         w = m_scene->sceneRect().width() - layoutLeftMargin - layoutRightMargin;
1632         h = (m_scene->sceneRect().height() - layoutTopMargin - layoutBottomMargin - (count - 1) * layoutVerticalSpacing) / count;
1633         for (auto* elem : list) {
1634             if (!elem->isVisible())
1635                 continue;
1636             setContainerRect(elem, x, y, h, w, undoable);
1637             y += h + layoutVerticalSpacing;
1638         }
1639     } else if (layout == Worksheet::Layout::HorizontalLayout) {
1640         w = (m_scene->sceneRect().width() - layoutLeftMargin - layoutRightMargin - (count - 1) * layoutHorizontalSpacing) / count;
1641         h = m_scene->sceneRect().height() - layoutTopMargin - layoutBottomMargin;
1642         for (auto* elem : list) {
1643             if (!elem->isVisible())
1644                 continue;
1645             setContainerRect(elem, x, y, h, w, undoable);
1646             x += w + layoutHorizontalSpacing;
1647         }
1648     } else { // GridLayout
1649         // add new rows, if not sufficient
1650         if (count > layoutRowCount * layoutColumnCount) {
1651             layoutRowCount = floor((double)count / layoutColumnCount + 0.5);
1652             Q_EMIT q->layoutRowCountChanged(layoutRowCount);
1653         }
1654 
1655         w = (m_scene->sceneRect().width() - layoutLeftMargin - layoutRightMargin - (layoutColumnCount - 1) * layoutHorizontalSpacing) / layoutColumnCount;
1656         h = (m_scene->sceneRect().height() - layoutTopMargin - layoutBottomMargin - (layoutRowCount - 1) * layoutVerticalSpacing) / layoutRowCount;
1657         int columnIndex = 0; // counts the columns in a row
1658         for (auto* elem : list) {
1659             if (!elem->isVisible())
1660                 continue;
1661             setContainerRect(elem, x, y, h, w, undoable);
1662             x += w + layoutHorizontalSpacing;
1663             columnIndex++;
1664             if (columnIndex == layoutColumnCount) {
1665                 columnIndex = 0;
1666                 x = layoutLeftMargin;
1667                 y += h + layoutVerticalSpacing;
1668             }
1669         }
1670     }
1671 
1672     Q_EMIT q->changed();
1673 }
1674 
1675 void WorksheetPrivate::setContainerRect(WorksheetElementContainer* elem, double x, double y, double h, double w, bool undoable) {
1676     if (useViewSize) {
1677         // when using the view size, no need to put rect changes onto the undo-stack
1678         elem->setUndoAware(false);
1679         elem->setRect(QRectF(x, y, w, h));
1680         elem->setUndoAware(true);
1681     } else {
1682         // don't put rect changed onto the undo-stack if undoable-flag is set to true,
1683         // e.g. when new child is added or removed (the layout and the childrend rects will be updated anyway)
1684         if (!undoable) {
1685             elem->setUndoAware(false);
1686             elem->setRect(QRectF(x, y, w, h));
1687             elem->setUndoAware(true);
1688         } else
1689             elem->setRect(QRectF(x, y, w, h));
1690     }
1691     elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
1692 }
1693 
1694 // ##############################################################################
1695 // ##################  Serialization/Deserialization  ###########################
1696 // ##############################################################################
1697 
1698 //! Save as XML
1699 void Worksheet::save(QXmlStreamWriter* writer) const {
1700     Q_D(const Worksheet);
1701     writer->writeStartElement(QStringLiteral("worksheet"));
1702     writeBasicAttributes(writer);
1703     writeCommentElement(writer);
1704 
1705     // applied theme
1706     if (!d->theme.isEmpty()) {
1707         writer->writeStartElement(QStringLiteral("theme"));
1708         writer->writeAttribute(QStringLiteral("name"), d->theme);
1709         writer->writeEndElement();
1710     }
1711 
1712     // geometry
1713     writer->writeStartElement(QStringLiteral("geometry"));
1714     QRectF rect = d->m_scene->sceneRect();
1715     writer->writeAttribute(QStringLiteral("x"), QString::number(rect.x()));
1716     writer->writeAttribute(QStringLiteral("y"), QString::number(rect.y()));
1717     writer->writeAttribute(QStringLiteral("width"), QString::number(rect.width()));
1718     writer->writeAttribute(QStringLiteral("height"), QString::number(rect.height()));
1719     writer->writeAttribute(QStringLiteral("useViewSize"), QString::number(d->useViewSize));
1720     writer->writeAttribute(QStringLiteral("zoomFit"), QString::number((int)d->zoomFit));
1721     writer->writeEndElement();
1722 
1723     // layout
1724     writer->writeStartElement(QStringLiteral("layout"));
1725     writer->writeAttribute(QStringLiteral("layout"), QString::number(static_cast<int>(d->layout)));
1726     writer->writeAttribute(QStringLiteral("topMargin"), QString::number(d->layoutTopMargin));
1727     writer->writeAttribute(QStringLiteral("bottomMargin"), QString::number(d->layoutBottomMargin));
1728     writer->writeAttribute(QStringLiteral("leftMargin"), QString::number(d->layoutLeftMargin));
1729     writer->writeAttribute(QStringLiteral("rightMargin"), QString::number(d->layoutRightMargin));
1730     writer->writeAttribute(QStringLiteral("verticalSpacing"), QString::number(d->layoutVerticalSpacing));
1731     writer->writeAttribute(QStringLiteral("horizontalSpacing"), QString::number(d->layoutHorizontalSpacing));
1732     writer->writeAttribute(QStringLiteral("columnCount"), QString::number(d->layoutColumnCount));
1733     writer->writeAttribute(QStringLiteral("rowCount"), QString::number(d->layoutRowCount));
1734     writer->writeEndElement();
1735 
1736     // background properties
1737     d->background->save(writer);
1738 
1739     // cartesian properties
1740     writer->writeStartElement(QStringLiteral("plotProperties"));
1741     writer->writeAttribute(QStringLiteral("plotInteractive"), QString::number(d->plotsInteractive));
1742     writer->writeAttribute(QStringLiteral("cartesianPlotActionMode"), QString::number(static_cast<int>(d->cartesianPlotActionMode)));
1743     writer->writeAttribute(QStringLiteral("cartesianPlotCursorMode"), QString::number(static_cast<int>(d->cartesianPlotCursorMode)));
1744     writer->writeEndElement();
1745 
1746     // serialize all children
1747     for (auto* child : children<WorksheetElement>(ChildIndexFlag::IncludeHidden))
1748         child->save(writer);
1749 
1750     writer->writeEndElement(); // close "worksheet" section
1751 }
1752 
1753 //! Load from XML
1754 bool Worksheet::load(XmlStreamReader* reader, bool preview) {
1755     if (!readBasicAttributes(reader))
1756         return false;
1757 
1758     Q_D(Worksheet);
1759 
1760     // clear the theme that was potentially set in init() in order to correctly load here the worksheets without any theme used
1761     d->theme.clear();
1762 
1763     QXmlStreamAttributes attribs;
1764     QString str;
1765 
1766     while (!reader->atEnd()) {
1767         reader->readNext();
1768         if (reader->isEndElement() && reader->name() == QLatin1String("worksheet"))
1769             break;
1770 
1771         if (!reader->isStartElement())
1772             continue;
1773 
1774         if (reader->name() == QLatin1String("comment")) {
1775             if (!readCommentElement(reader))
1776                 return false;
1777         } else if (!preview && reader->name() == QLatin1String("theme")) {
1778             attribs = reader->attributes();
1779             d->theme = attribs.value(QStringLiteral("name")).toString();
1780         } else if (!preview && reader->name() == QLatin1String("geometry")) {
1781             attribs = reader->attributes();
1782 
1783             str = attribs.value(QStringLiteral("x")).toString();
1784             if (str.isEmpty())
1785                 reader->raiseMissingAttributeWarning(QStringLiteral("x"));
1786             else
1787                 d->pageRect.setX(str.toDouble());
1788 
1789             str = attribs.value(QStringLiteral("y")).toString();
1790             if (str.isEmpty())
1791                 reader->raiseMissingAttributeWarning(QStringLiteral("y"));
1792             else
1793                 d->pageRect.setY(str.toDouble());
1794 
1795             str = attribs.value(QStringLiteral("width")).toString();
1796             if (str.isEmpty())
1797                 reader->raiseMissingAttributeWarning(QStringLiteral("width"));
1798             else
1799                 d->pageRect.setWidth(str.toDouble());
1800 
1801             str = attribs.value(QStringLiteral("height")).toString();
1802             if (str.isEmpty())
1803                 reader->raiseMissingAttributeWarning(QStringLiteral("height"));
1804             else
1805                 d->pageRect.setHeight(str.toDouble());
1806 
1807             READ_INT_VALUE("useViewSize", useViewSize, int);
1808             READ_INT_VALUE("zoomFit", zoomFit, ZoomFit);
1809         } else if (!preview && reader->name() == QLatin1String("layout")) {
1810             attribs = reader->attributes();
1811 
1812             READ_INT_VALUE("layout", layout, Worksheet::Layout);
1813             READ_DOUBLE_VALUE("topMargin", layoutTopMargin);
1814             READ_DOUBLE_VALUE("bottomMargin", layoutBottomMargin);
1815             READ_DOUBLE_VALUE("leftMargin", layoutLeftMargin);
1816             READ_DOUBLE_VALUE("rightMargin", layoutRightMargin);
1817             READ_DOUBLE_VALUE("verticalSpacing", layoutVerticalSpacing);
1818             READ_DOUBLE_VALUE("horizontalSpacing", layoutHorizontalSpacing);
1819             READ_INT_VALUE("columnCount", layoutColumnCount, int);
1820             READ_INT_VALUE("rowCount", layoutRowCount, int);
1821         } else if (!preview && reader->name() == QLatin1String("background"))
1822             d->background->load(reader, preview);
1823         else if (!preview && reader->name() == QLatin1String("plotProperties")) {
1824             attribs = reader->attributes();
1825 
1826             str = attribs.value(QStringLiteral("plotInteractive")).toString();
1827             if (str.isEmpty()) {
1828                 str = attribs.value(QStringLiteral("plotLocked")).toString();
1829                 if (str.isEmpty())
1830                     reader->raiseMissingAttributeWarning(QStringLiteral("plotLocked"));
1831                 else
1832                     d->plotsInteractive = !static_cast<bool>(str.toInt());
1833             } else
1834                 d->plotsInteractive = static_cast<bool>(str.toInt());
1835 
1836             READ_INT_VALUE("cartesianPlotActionMode", cartesianPlotActionMode, Worksheet::CartesianPlotActionMode);
1837             READ_INT_VALUE("cartesianPlotCursorMode", cartesianPlotCursorMode, Worksheet::CartesianPlotActionMode);
1838         } else if (reader->name() == QLatin1String("cartesianPlot")) {
1839             auto* plot = new CartesianPlot(QString());
1840             plot->setIsLoading(true);
1841             if (!plot->load(reader, preview)) {
1842                 delete plot;
1843                 return false;
1844             } else
1845                 addChildFast(plot);
1846         } else if (!preview && reader->name() == QLatin1String("textLabel")) {
1847             auto* label = new TextLabel(QString());
1848             label->setIsLoading(true);
1849             if (!label->load(reader, preview)) {
1850                 delete label;
1851                 return false;
1852             } else
1853                 addChildFast(label);
1854         } else if (!preview && reader->name() == QLatin1String("image")) {
1855             Image* image = new Image(QString());
1856             image->setIsLoading(true);
1857             if (!image->load(reader, preview)) {
1858                 delete image;
1859                 return false;
1860             } else
1861                 addChildFast(image);
1862         } else { // unknown element
1863             reader->raiseUnknownElementWarning();
1864             if (!reader->skipToEndElement())
1865                 return false;
1866         }
1867     }
1868 
1869     if (!preview) {
1870         d->m_scene->setSceneRect(d->pageRect);
1871         d->updateLayout();
1872         updateCompleteCursorTreeModel();
1873     }
1874 
1875     return true;
1876 }
1877 
1878 // ##############################################################################
1879 // #########################  Theme management ##################################
1880 // ##############################################################################
1881 void Worksheet::loadTheme(const QString& theme) {
1882     Q_D(Worksheet);
1883     KConfigGroup group;
1884     KConfig* config = nullptr;
1885     if (!theme.isEmpty()) {
1886         // load values from the theme config
1887         config = new KConfig(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig);
1888 
1889         // apply the same background color for Worksheet as for the CartesianPlot
1890         group = config->group(QStringLiteral("CartesianPlot"));
1891 
1892         // load the theme for all the children
1893         const auto& children = this->children<WorksheetElement>(ChildIndexFlag::IncludeHidden);
1894         for (auto* child : children)
1895             child->loadThemeConfig(*config);
1896     } else {
1897         // load default values
1898         config = new KConfig();
1899         group = config->group(QStringLiteral("Worksheet"));
1900     }
1901 
1902     // load background properties
1903     d->background->loadThemeConfig(group);
1904 
1905     // load the theme for all the children
1906     const auto& children = this->children<WorksheetElement>(ChildIndexFlag::IncludeHidden);
1907     for (auto* child : children)
1908         child->loadThemeConfig(*config);
1909 
1910     delete config;
1911 
1912     Q_EMIT changed();
1913 }