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

0001 /*
0002     File                 : DatapickerImageView.cpp
0003     Project              : LabPlot
0004     Description          : DatapickerImage view for datapicker
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
0007     SPDX-FileCopyrightText: 2015-2023 Alexander Semke <alexander.semke@web.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "commonfrontend/datapicker/DatapickerImageView.h"
0013 #include "backend/datapicker/Datapicker.h"
0014 #include "backend/datapicker/DatapickerCurve.h"
0015 #include "backend/datapicker/DatapickerImage.h"
0016 #include "backend/datapicker/DatapickerPoint.h"
0017 #include "backend/datapicker/Transform.h"
0018 #include "backend/worksheet/Worksheet.h"
0019 
0020 #include <limits>
0021 
0022 #include <QActionGroup>
0023 #include <QClipboard>
0024 #include <QFileInfo>
0025 #include <QImage>
0026 #include <QMenu>
0027 #include <QMessageBox>
0028 #include <QMimeData>
0029 #include <QPrinter>
0030 #include <QScreen>
0031 #include <QSvgGenerator>
0032 #include <QTimeLine>
0033 #include <QToolBar>
0034 #include <QToolButton>
0035 #include <QWheelEvent>
0036 
0037 #include <KLocalizedString>
0038 
0039 /**
0040  * \class DatapickerImageView
0041  * \brief Datapicker/DatapickerImage view
0042  */
0043 
0044 /*!
0045   Constructor of the class.
0046   Creates a view for the DatapickerImage \c image and initializes the internal model.
0047 */
0048 DatapickerImageView::DatapickerImageView(DatapickerImage* image)
0049     : QGraphicsView()
0050     , m_image(image)
0051     , m_datapicker(dynamic_cast<Datapicker*>(m_image->parentAspect()))
0052     , m_transform(new Transform()) {
0053     setScene(m_image->scene());
0054 
0055     setRenderHint(QPainter::Antialiasing);
0056     setRubberBandSelectionMode(Qt::ContainsItemBoundingRect);
0057     setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
0058     setResizeAnchor(QGraphicsView::AnchorViewCenter);
0059     setMinimumSize(16, 16);
0060     setFocusPolicy(Qt::StrongFocus);
0061 
0062     viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
0063     viewport()->setAttribute(Qt::WA_NoSystemBackground);
0064     setCacheMode(QGraphicsView::CacheBackground);
0065 
0066     initActions();
0067     initMenus();
0068     m_image->setSegmentsHoverEvent(true);
0069     setInteractive(true);
0070 
0071     changeZoom(zoomOriginAction);
0072     currentZoomAction = zoomInViewAction;
0073 
0074     if (m_image->plotPointsType() == DatapickerImage::PointsType::AxisPoints)
0075         setAxisPointsAction->setChecked(true);
0076     else if (m_image->plotPointsType() == DatapickerImage::PointsType::CurvePoints)
0077         setCurvePointsAction->setChecked(true);
0078     else
0079         selectSegmentAction->setChecked(true);
0080 
0081     handleImageActions();
0082     changeRotationAngle();
0083 
0084     // signal/slot connections
0085     // for general actions
0086     connect(m_image, &DatapickerImage::requestProjectContextMenu, this, &DatapickerImageView::createContextMenu);
0087     connect(m_image, &DatapickerImage::requestUpdate, this, &DatapickerImageView::updateBackground);
0088     connect(m_image, &DatapickerImage::requestUpdateActions, this, &DatapickerImageView::handleImageActions);
0089     connect(m_datapicker, &Datapicker::requestUpdateActions, this, &DatapickerImageView::handleImageActions);
0090     connect(m_image, &DatapickerImage::rotationAngleChanged, this, &DatapickerImageView::changeRotationAngle);
0091 
0092     // resize the view to make the complete scene visible.
0093     // no need to resize the view when the project is being opened,
0094     // all views will be resized to the stored values at the end
0095     if (!m_image->isLoading()) {
0096         float w = Worksheet::convertFromSceneUnits(sceneRect().width(), Worksheet::Unit::Inch);
0097         float h = Worksheet::convertFromSceneUnits(sceneRect().height(), Worksheet::Unit::Inch);
0098         w *= QApplication::primaryScreen()->physicalDotsPerInchX();
0099         h *= QApplication::primaryScreen()->physicalDotsPerInchY();
0100         resize(w * 1.1, h * 1.1);
0101     }
0102 
0103     // rescale to the original size
0104     static const float hscale = QApplication::primaryScreen()->physicalDotsPerInchX() / (Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch));
0105     static const float vscale = QApplication::primaryScreen()->physicalDotsPerInchY() / (Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch));
0106     setTransform(QTransform::fromScale(hscale, vscale));
0107 }
0108 
0109 DatapickerImageView::~DatapickerImageView() {
0110     delete m_transform;
0111 }
0112 
0113 void DatapickerImageView::initActions() {
0114     auto* zoomActionGroup = new QActionGroup(this);
0115     auto* mouseModeActionGroup = new QActionGroup(this);
0116     navigationActionGroup = new QActionGroup(this);
0117     magnificationActionGroup = new QActionGroup(this);
0118 
0119     // Zoom actions
0120     zoomInViewAction = new QAction(QIcon::fromTheme(QStringLiteral("zoom-in")), i18n("Zoom In"), zoomActionGroup);
0121     zoomInViewAction->setShortcut(Qt::CTRL | Qt::Key_Plus);
0122 
0123     zoomOutViewAction = new QAction(QIcon::fromTheme(QStringLiteral("zoom-out")), i18n("Zoom Out"), zoomActionGroup);
0124     zoomOutViewAction->setShortcut(Qt::CTRL | Qt::Key_Minus);
0125 
0126     zoomOriginAction = new QAction(QIcon::fromTheme(QStringLiteral("zoom-original")), i18n("Original Size"), zoomActionGroup);
0127     zoomOriginAction->setShortcut(Qt::CTRL | Qt::Key_1);
0128 
0129     zoomFitPageHeightAction = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-height")), i18n("Fit to Height"), zoomActionGroup);
0130     zoomFitPageWidthAction = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18n("Fit to Width"), zoomActionGroup);
0131 
0132     // Mouse mode actions
0133     navigationModeAction = new QAction(QIcon::fromTheme(QStringLiteral("input-mouse")), i18n("Navigate"), mouseModeActionGroup);
0134     navigationModeAction->setCheckable(true);
0135     navigationModeAction->setData(static_cast<int>(MouseMode::Navigation));
0136 
0137     zoomSelectionModeAction = new QAction(QIcon::fromTheme(QStringLiteral("page-zoom")), i18n("Select and Zoom"), mouseModeActionGroup);
0138     zoomSelectionModeAction->setCheckable(true);
0139     zoomSelectionModeAction->setData(static_cast<int>(MouseMode::ZoomSelection));
0140 
0141     setAxisPointsAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-plot-axis-points")), i18n("Set Axis Points"), mouseModeActionGroup);
0142     setAxisPointsAction->setCheckable(true);
0143     setAxisPointsAction->setData(static_cast<int>(MouseMode::ReferencePointsEntry));
0144 
0145     setCurvePointsAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve-points")), i18n("Set Curve Points"), mouseModeActionGroup);
0146     setCurvePointsAction->setCheckable(true);
0147     setCurvePointsAction->setData(static_cast<int>(MouseMode::CurvePointsEntry));
0148 
0149     selectSegmentAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve-segments")), i18n("Select Curve Segments"), mouseModeActionGroup);
0150     selectSegmentAction->setCheckable(true);
0151     selectSegmentAction->setData(static_cast<int>(MouseMode::CurveSegmentsEntry));
0152 
0153     addCurveAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("New Curve"), this);
0154 
0155     shiftLeftAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-shift-left-x")), i18n("Shift Left"), navigationActionGroup);
0156     shiftLeftAction->setShortcut(Qt::Key_Right);
0157 
0158     shiftRightAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-shift-right-x")), i18n("Shift Right"), navigationActionGroup);
0159     shiftRightAction->setShortcut(Qt::Key_Left);
0160 
0161     shiftUpAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-shift-down-y")), i18n("Shift Up"), navigationActionGroup);
0162     shiftUpAction->setShortcut(Qt::Key_Up);
0163 
0164     shiftDownAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-shift-up-y")), i18n("Shift Down"), navigationActionGroup);
0165     shiftDownAction->setShortcut(Qt::Key_Down);
0166 
0167     noMagnificationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-1x-zoom")), i18n("No Magnification"), magnificationActionGroup);
0168     noMagnificationAction->setCheckable(true);
0169     noMagnificationAction->setChecked(true);
0170 
0171     twoTimesMagnificationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-2x-zoom")), i18n("2x Magnification"), magnificationActionGroup);
0172     twoTimesMagnificationAction->setCheckable(true);
0173 
0174     threeTimesMagnificationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-3x-zoom")), i18n("3x Magnification"), magnificationActionGroup);
0175     threeTimesMagnificationAction->setCheckable(true);
0176 
0177     fourTimesMagnificationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-4x-zoom")), i18n("4x Magnification"), magnificationActionGroup);
0178     fourTimesMagnificationAction->setCheckable(true);
0179 
0180     fiveTimesMagnificationAction = new QAction(QIcon::fromTheme(QStringLiteral("labplot-5x-zoom")), i18n("5x Magnification"), magnificationActionGroup);
0181     fiveTimesMagnificationAction->setCheckable(true);
0182 
0183     // set some default values
0184     currentZoomAction = zoomInViewAction;
0185     currentMagnificationAction = noMagnificationAction;
0186 
0187     switch (m_image->plotPointsType()) {
0188     case DatapickerImage::PointsType::AxisPoints:
0189         currentPlotPointsTypeAction = setAxisPointsAction;
0190         setAxisPointsAction->setChecked(true);
0191         mouseModeChanged(setAxisPointsAction);
0192         break;
0193     case DatapickerImage::PointsType::CurvePoints:
0194         currentPlotPointsTypeAction = setCurvePointsAction;
0195         setCurvePointsAction->setChecked(true);
0196         mouseModeChanged(setCurvePointsAction);
0197         break;
0198     case DatapickerImage::PointsType::SegmentPoints:
0199         currentPlotPointsTypeAction = selectSegmentAction;
0200         selectSegmentAction->setChecked(true);
0201         mouseModeChanged(selectSegmentAction);
0202     }
0203 
0204     // signal-slot connections
0205     connect(mouseModeActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::mouseModeChanged);
0206     connect(zoomActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::changeZoom);
0207     connect(addCurveAction, &QAction::triggered, this, &DatapickerImageView::addCurve);
0208     connect(navigationActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::changeSelectedItemsPosition);
0209     connect(magnificationActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::magnificationChanged);
0210 }
0211 
0212 void DatapickerImageView::initMenus() {
0213     m_viewMouseModeMenu = new QMenu(i18n("Mouse Mode"), this);
0214     m_viewMouseModeMenu->setIcon(QIcon::fromTheme(QStringLiteral("input-mouse")));
0215     m_viewMouseModeMenu->addAction(setAxisPointsAction);
0216     m_viewMouseModeMenu->addAction(setCurvePointsAction);
0217     m_viewMouseModeMenu->addAction(selectSegmentAction);
0218     m_viewMouseModeMenu->addSeparator();
0219     m_viewMouseModeMenu->addAction(navigationModeAction);
0220     m_viewMouseModeMenu->addAction(zoomSelectionModeAction);
0221 
0222     m_zoomMenu = new QMenu(i18n("Zoom View"), this);
0223     m_zoomMenu->setIcon(QIcon::fromTheme(QStringLiteral("zoom-draw")));
0224     m_zoomMenu->addAction(zoomInViewAction);
0225     m_zoomMenu->addAction(zoomOutViewAction);
0226     m_zoomMenu->addAction(zoomOriginAction);
0227     m_zoomMenu->addAction(zoomFitPageHeightAction);
0228     m_zoomMenu->addAction(zoomFitPageWidthAction);
0229 
0230     m_navigationMenu = new QMenu(i18n("Move Last Point"), this);
0231     m_navigationMenu->addAction(shiftLeftAction);
0232     m_navigationMenu->addAction(shiftRightAction);
0233     m_navigationMenu->addAction(shiftUpAction);
0234     m_navigationMenu->addAction(shiftDownAction);
0235 
0236     m_magnificationMenu = new QMenu(i18n("Magnification"), this);
0237     m_magnificationMenu->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
0238     m_magnificationMenu->addAction(noMagnificationAction);
0239     m_magnificationMenu->addAction(twoTimesMagnificationAction);
0240     m_magnificationMenu->addAction(threeTimesMagnificationAction);
0241     m_magnificationMenu->addAction(fourTimesMagnificationAction);
0242     m_magnificationMenu->addAction(fiveTimesMagnificationAction);
0243 }
0244 
0245 /*!
0246  * Populates the menu \c menu with the image and image-view relevant actions.
0247  * The menu is used
0248  *   - as the context menu in DatapickerImageView
0249  *   - as the "datapicker menu" in the main menu-bar (called form MainWin)
0250  *   - as a part of the image context menu in project explorer
0251  */
0252 void DatapickerImageView::createContextMenu(QMenu* menu) const {
0253     Q_ASSERT(menu);
0254 
0255     QAction* firstAction = nullptr;
0256     // if we're populating the context menu for the project explorer, then
0257     // there're already actions available there. Skip the first title-action
0258     // and insert the action at the beginning of the menu.
0259     if (menu->actions().size() > 1)
0260         firstAction = menu->actions().at(1);
0261 
0262     menu->insertAction(firstAction, addCurveAction);
0263     menu->insertSeparator(firstAction);
0264     menu->insertMenu(firstAction, m_navigationMenu);
0265     menu->insertSeparator(firstAction);
0266     menu->insertMenu(firstAction, m_viewMouseModeMenu);
0267     menu->insertMenu(firstAction, m_zoomMenu);
0268     menu->insertMenu(firstAction, m_magnificationMenu);
0269     menu->insertSeparator(firstAction);
0270 }
0271 
0272 void DatapickerImageView::fillToolBar(QToolBar* toolBar) {
0273     toolBar->addSeparator();
0274     toolBar->addAction(setAxisPointsAction);
0275     toolBar->addAction(setCurvePointsAction);
0276     toolBar->addAction(selectSegmentAction);
0277     toolBar->addAction(navigationModeAction);
0278     toolBar->addAction(zoomSelectionModeAction);
0279 
0280     toolBar->addSeparator();
0281     toolBar->addAction(addCurveAction);
0282 
0283     toolBar->addSeparator();
0284     toolBar->addAction(shiftRightAction);
0285     toolBar->addAction(shiftLeftAction);
0286     toolBar->addAction(shiftUpAction);
0287     toolBar->addAction(shiftDownAction);
0288 
0289     tbZoom = new QToolButton(toolBar);
0290     tbZoom->setPopupMode(QToolButton::MenuButtonPopup);
0291     tbZoom->setMenu(m_zoomMenu);
0292     tbZoom->setDefaultAction(currentZoomAction);
0293     toolBar->addSeparator();
0294     toolBar->addWidget(tbZoom);
0295 
0296     tbMagnification = new QToolButton(toolBar);
0297     tbMagnification->setPopupMode(QToolButton::MenuButtonPopup);
0298     tbMagnification->setMenu(m_magnificationMenu);
0299     tbMagnification->setDefaultAction(currentMagnificationAction);
0300     toolBar->addWidget(tbMagnification);
0301 }
0302 
0303 void DatapickerImageView::setScene(QGraphicsScene* scene) {
0304     QGraphicsView::setScene(scene);
0305     setTransform(QTransform());
0306 }
0307 
0308 void DatapickerImageView::drawForeground(QPainter* painter, const QRectF& rect) {
0309     if (m_mouseMode == MouseMode::ZoomSelection && m_selectionBandIsShown) {
0310         painter->save();
0311         const QRectF& selRect = mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect();
0312         painter->setPen(QPen(Qt::black, 5 / transform().m11()));
0313         painter->drawRect(selRect);
0314         painter->setBrush(Qt::blue);
0315         painter->setOpacity(0.2);
0316         painter->drawRect(selRect);
0317         painter->restore();
0318     }
0319     QGraphicsView::drawForeground(painter, rect);
0320 }
0321 
0322 void DatapickerImageView::drawBackground(QPainter* painter, const QRectF& rect) {
0323     painter->save();
0324 
0325     QRectF scene_rect = sceneRect();
0326     if (!scene_rect.contains(rect))
0327         painter->fillRect(rect, Qt::lightGray);
0328 
0329     // canvas
0330     if (m_image->isLoaded) {
0331         if (m_image->plotImageType() == DatapickerImage::PlotImageType::OriginalImage) {
0332             const QImage& todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0333             painter->drawImage(scene_rect.topLeft(), todraw);
0334         } else if (m_image->plotImageType() == DatapickerImage::PlotImageType::ProcessedImage) {
0335             const QImage& todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0336             painter->drawImage(scene_rect.topLeft(), todraw);
0337         } else {
0338             painter->fillRect(scene_rect, Qt::white);
0339         }
0340     } else {
0341         painter->setBrush(QBrush(Qt::gray));
0342         painter->drawRect(scene_rect);
0343     }
0344 
0345     invalidateScene(rect, QGraphicsScene::BackgroundLayer);
0346     painter->restore();
0347 }
0348 
0349 // ##############################################################################
0350 // ####################################  Events   ###############################
0351 // ##############################################################################
0352 void DatapickerImageView::keyPressEvent(QKeyEvent* event) {
0353     if (event->matches(QKeySequence::Paste)) {
0354         const QClipboard* clipboard = QApplication::clipboard();
0355         const QMimeData* mimeData = clipboard->mimeData();
0356         if (mimeData->hasImage()) {
0357             m_image->setImage(qvariant_cast<QImage>(mimeData->imageData()), QStringLiteral(""), true);
0358             event->accept();
0359         } else if (mimeData->hasText()) {
0360             // Check if it is a filepath
0361             QString text = mimeData->text();
0362             if (text.startsWith(QStringLiteral("file://")))
0363                 text.replace(QStringLiteral("file://"), QStringLiteral(""));
0364             QFileInfo fi(text);
0365             if (fi.exists()) {
0366                 m_image->setImage(fi.absoluteFilePath(), true);
0367                 event->accept();
0368             }
0369         }
0370     }
0371     QGraphicsView::keyPressEvent(event);
0372 }
0373 
0374 void DatapickerImageView::wheelEvent(QWheelEvent* event) {
0375     // https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView
0376     if (m_mouseMode == MouseMode::ZoomSelection || (QApplication::keyboardModifiers() & Qt::ControlModifier)) {
0377         QPoint numDegrees = event->angleDelta() / 8;
0378         int numSteps = numDegrees.y() / 15; // see QWheelEvent documentation
0379         zoom(numSteps);
0380     } else
0381         QGraphicsView::wheelEvent(event);
0382 }
0383 
0384 void DatapickerImageView::zoom(int numSteps) {
0385     m_numScheduledScalings += numSteps;
0386     if (m_numScheduledScalings * numSteps < 0) // if user moved the wheel in another direction, we reset previously scheduled scalings
0387         m_numScheduledScalings = numSteps;
0388 
0389     auto* anim = new QTimeLine(350, this);
0390     anim->setUpdateInterval(20);
0391 
0392     connect(anim, &QTimeLine::valueChanged, this, &DatapickerImageView::scalingTime);
0393     connect(anim, &QTimeLine::finished, this, &DatapickerImageView::animFinished);
0394     anim->start();
0395 }
0396 
0397 void DatapickerImageView::scalingTime() {
0398     qreal factor = 1.0 + qreal(m_numScheduledScalings) / 300.0;
0399     scale(factor, factor);
0400 }
0401 
0402 void DatapickerImageView::animFinished() {
0403     if (m_numScheduledScalings > 0)
0404         m_numScheduledScalings--;
0405     else
0406         m_numScheduledScalings++;
0407     sender()->~QObject();
0408 }
0409 
0410 void DatapickerImageView::mousePressEvent(QMouseEvent* event) {
0411     // prevent the deselection of items when context menu event
0412     // was triggered (right button click)
0413     if (event->button() == Qt::RightButton) {
0414         event->accept();
0415         return;
0416     }
0417 
0418     if (event->button() == Qt::LeftButton && m_mouseMode == MouseMode::ZoomSelection) {
0419         m_selectionStart = event->pos();
0420         m_selectionBandIsShown = true;
0421         return;
0422     }
0423 
0424     const auto eventPos = mapToScene(event->pos());
0425     const auto type = m_image->plotPointsType();
0426     bool entryMode =
0427         (m_mouseMode == MouseMode::ReferencePointsEntry || m_mouseMode == MouseMode::CurvePointsEntry || m_mouseMode == MouseMode::CurveSegmentsEntry);
0428 
0429     // check whether there is a point item under the cursor
0430     bool pointsUnderCursor = false;
0431     const auto& items = this->items(event->pos());
0432     const auto& referencePoints = m_image->children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0433     for (auto* item : items) {
0434         if (item == m_image->m_magnificationWindow)
0435             continue;
0436 
0437         // when entering curve points, ignore the reference points under the cursor,
0438         // it should be possible to place curve points close to or over the reference points.
0439         if (type == DatapickerImage::PointsType::CurvePoints) {
0440             bool referenceItem = false;
0441             for (auto* point : referencePoints) {
0442                 if (point->graphicsItem() == item) {
0443                     referenceItem = true;
0444                     break;
0445                 }
0446             }
0447 
0448             if (referenceItem)
0449                 continue;
0450         }
0451 
0452         pointsUnderCursor = true;
0453         break;
0454     }
0455 
0456     if (entryMode && !pointsUnderCursor && m_image->isLoaded && sceneRect().contains(eventPos)) {
0457         if (type == DatapickerImage::PointsType::AxisPoints) {
0458             int childCount = m_image->childCount<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0459             if (childCount < 3)
0460                 m_datapicker->addNewPoint(eventPos, m_image);
0461         } else if (type == DatapickerImage::PointsType::CurvePoints && m_datapicker->activeCurve())
0462             m_datapicker->addNewPoint(eventPos, m_datapicker->activeCurve());
0463 
0464         if (m_image->m_magnificationWindow && m_image->m_magnificationWindow->isVisible())
0465             updateMagnificationWindow();
0466     }
0467 
0468     // make sure the datapicker (or its currently active curve) is selected in the project explorer if the view was clicked.
0469     // We need this for the case when we change from the project-node in the project explorer to the datapicker node by clicking the view.
0470     if (m_datapicker->activeCurve() && type != DatapickerImage::PointsType::AxisPoints) {
0471         m_datapicker->setSelectedInView(false);
0472         m_datapicker->activeCurve()->setSelectedInView(true);
0473     } else {
0474         if (m_datapicker->activeCurve())
0475             m_datapicker->activeCurve()->setSelectedInView(false);
0476         m_datapicker->setSelectedInView(true);
0477     }
0478 
0479     QGraphicsView::mousePressEvent(event);
0480 }
0481 
0482 void DatapickerImageView::mouseReleaseEvent(QMouseEvent* event) {
0483     if (event->button() == Qt::LeftButton && m_mouseMode == MouseMode::ZoomSelection) {
0484         m_selectionBandIsShown = false;
0485         viewport()->repaint(QRect(m_selectionStart, m_selectionEnd).normalized());
0486 
0487         // don't zoom if very small region was selected, avoid occasional/unwanted zooming
0488         m_selectionEnd = event->pos();
0489         if (abs(m_selectionEnd.x() - m_selectionStart.x()) > 20 && abs(m_selectionEnd.y() - m_selectionStart.y()) > 20)
0490             fitInView(mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect(), Qt::KeepAspectRatio);
0491     }
0492 
0493     QGraphicsView::mouseReleaseEvent(event);
0494 }
0495 
0496 void DatapickerImageView::mouseMoveEvent(QMouseEvent* event) {
0497     // show the selection band
0498     if (m_selectionBandIsShown) {
0499         QRect rect = QRect(m_selectionStart, m_selectionEnd).normalized();
0500         m_selectionEnd = event->pos();
0501         rect = rect.united(QRect(m_selectionStart, m_selectionEnd).normalized());
0502         int penWidth = 5 / transform().m11();
0503         rect.setX(rect.x() - penWidth);
0504         rect.setY(rect.y() - penWidth);
0505         rect.setHeight(rect.height() + 2 * penWidth);
0506         rect.setWidth(rect.width() + 2 * penWidth);
0507         viewport()->repaint(rect);
0508         return;
0509     }
0510 
0511     QPointF pos = mapToScene(event->pos());
0512 
0513     // show the current coordinates under the mouse cursor in the status bar
0514     if (m_image->plotPointsType() == DatapickerImage::PointsType::CurvePoints) {
0515         Vector3D logicalPos = m_transform->mapSceneToLogical(pos, m_image->axisPoints());
0516         if (m_image->axisPoints().type == DatapickerImage::GraphType::Ternary) {
0517             Q_EMIT statusInfo(QStringLiteral("a =") + QString::number(logicalPos.x()) + QStringLiteral(", b =") + QString::number(logicalPos.y())
0518                               + QStringLiteral(", c =") + QString::number(logicalPos.z()));
0519         } else {
0520             QString xLabel(QStringLiteral("x"));
0521             QString yLabel(QStringLiteral("y"));
0522             if (m_image->axisPoints().type == DatapickerImage::GraphType::PolarInDegree) {
0523                 xLabel = QStringLiteral("r");
0524                 yLabel = QStringLiteral("y(deg)");
0525             } else if (m_image->axisPoints().type == DatapickerImage::GraphType::PolarInRadians) {
0526                 xLabel = QStringLiteral("r");
0527                 yLabel = QStringLiteral("y(rad)");
0528             }
0529 
0530             if (m_datapicker->activeCurve()) {
0531                 QString statusText = i18n("%1, active curve \"%2\": %3=%4, %5=%6",
0532                                           m_datapicker->name(),
0533                                           m_datapicker->activeCurve()->name(),
0534                                           xLabel,
0535                                           QString::number(logicalPos.x()),
0536                                           yLabel,
0537                                           QString::number(logicalPos.y()));
0538                 Q_EMIT statusInfo(statusText);
0539             }
0540         }
0541     }
0542 
0543     // show the magnification window
0544     if (magnificationFactor && m_image->isLoaded && sceneRect().contains(pos)) {
0545         if (!m_image->m_magnificationWindow) {
0546             m_image->m_magnificationWindow = new QGraphicsPixmapItem;
0547             scene()->addItem(m_image->m_magnificationWindow);
0548             m_image->m_magnificationWindow->setZValue(std::numeric_limits<int>::max());
0549         }
0550 
0551         updateMagnificationWindow();
0552     } else if (m_image->m_magnificationWindow)
0553         m_image->m_magnificationWindow->setVisible(false);
0554 
0555     QGraphicsView::mouseMoveEvent(event);
0556 }
0557 
0558 void DatapickerImageView::updateMagnificationWindow() {
0559     m_image->m_magnificationWindow->setVisible(false);
0560     QPointF pos = mapToScene(mapFromGlobal(QCursor::pos()));
0561 
0562     // copy the part of the view to be shown magnified
0563     const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Unit::Centimeter) / transform().m11();
0564     const QRectF copyRect(pos.x() - size / (2 * magnificationFactor),
0565                           pos.y() - size / (2 * magnificationFactor),
0566                           size / magnificationFactor,
0567                           size / magnificationFactor);
0568     QPixmap px = grab(mapFromScene(copyRect).boundingRect());
0569     px = px.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0570 
0571     // draw the bounding rect
0572     QPainter painter(&px);
0573     const QPen pen = QPen(Qt::lightGray, 2 / transform().m11());
0574     painter.setPen(pen);
0575     QRect rect = px.rect();
0576     rect.setWidth(rect.width() - pen.widthF() / 2);
0577     rect.setHeight(rect.height() - pen.widthF() / 2);
0578     painter.drawRect(rect);
0579 
0580     // set the pixmap
0581     m_image->m_magnificationWindow->setPixmap(px);
0582     m_image->m_magnificationWindow->setPos(pos.x() - px.width() / 2, pos.y() - px.height() / 2);
0583 
0584     m_image->m_magnificationWindow->setVisible(true);
0585 }
0586 
0587 void DatapickerImageView::contextMenuEvent(QContextMenuEvent*) {
0588     // no need to propagate the event to the scene and graphics items
0589     QMenu* menu = new QMenu(this);
0590     this->createContextMenu(menu);
0591     menu->exec(QCursor::pos());
0592 }
0593 
0594 // ##############################################################################
0595 // ####################################  SLOTs   ###############################
0596 // ##############################################################################
0597 void DatapickerImageView::mouseModeChanged(QAction* action) {
0598     m_mouseMode = (DatapickerImageView::MouseMode)action->data().toInt();
0599 
0600     if (action == navigationModeAction) {
0601         setInteractive(false);
0602         setDragMode(QGraphicsView::ScrollHandDrag);
0603         m_image->setSegmentsHoverEvent(false);
0604     } else if (action == zoomSelectionModeAction) {
0605         setInteractive(false);
0606         setDragMode(QGraphicsView::NoDrag);
0607         m_image->setSegmentsHoverEvent(false);
0608         setCursor(Qt::ArrowCursor);
0609     } else {
0610         setInteractive(true);
0611         setDragMode(QGraphicsView::NoDrag);
0612         m_image->setSegmentsHoverEvent(true);
0613         setCursor(Qt::CrossCursor);
0614 
0615         if (currentPlotPointsTypeAction != action) {
0616             if (action == setAxisPointsAction) {
0617                 int count = m_image->childCount<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0618                 if (count) {
0619                     auto button = QMessageBox::question(this,
0620                                                         i18n("Remove existing reference points?"),
0621                                                         i18n("All available reference points will be removed. Do you want to continue?"));
0622                     if (button != QMessageBox::Yes) {
0623                         currentPlotPointsTypeAction->setChecked(true);
0624                         return;
0625                     }
0626                 }
0627 
0628                 m_image->setPlotPointsType(DatapickerImage::PointsType::AxisPoints);
0629             } else if (action == setCurvePointsAction)
0630                 m_image->setPlotPointsType(DatapickerImage::PointsType::CurvePoints);
0631             else if (action == selectSegmentAction)
0632                 m_image->setPlotPointsType(DatapickerImage::PointsType::SegmentPoints);
0633 
0634             currentPlotPointsTypeAction = action;
0635         }
0636     }
0637 }
0638 
0639 void DatapickerImageView::changeZoom(QAction* action) {
0640     if (action == zoomInViewAction)
0641         zoom(1);
0642     else if (action == zoomOutViewAction)
0643         zoom(-1);
0644     else if (action == zoomOriginAction) {
0645         static const float hscale =
0646             QApplication::primaryScreen()->physicalDotsPerInchX() / (25.4 * Worksheet::convertToSceneUnits(1, Worksheet::Unit::Millimeter));
0647         static const float vscale =
0648             QApplication::primaryScreen()->physicalDotsPerInchY() / (25.4 * Worksheet::convertToSceneUnits(1, Worksheet::Unit::Millimeter));
0649         setTransform(QTransform::fromScale(hscale, vscale));
0650         m_rotationAngle = 0;
0651     } else if (action == zoomFitPageWidthAction) {
0652         float scaleFactor = viewport()->width() / scene()->sceneRect().width();
0653         setTransform(QTransform::fromScale(scaleFactor, scaleFactor));
0654         m_rotationAngle = 0;
0655     } else if (action == zoomFitPageHeightAction) {
0656         float scaleFactor = viewport()->height() / scene()->sceneRect().height();
0657         setTransform(QTransform::fromScale(scaleFactor, scaleFactor));
0658         m_rotationAngle = 0;
0659     }
0660 
0661     currentZoomAction = action;
0662     if (tbZoom)
0663         tbZoom->setDefaultAction(action);
0664 
0665     // change and set angle if tranform reset
0666     changeRotationAngle();
0667 }
0668 
0669 void DatapickerImageView::changeSelectedItemsPosition(QAction* action) {
0670     if (scene()->selectedItems().isEmpty())
0671         return;
0672 
0673     QPointF shift(0, 0);
0674     if (action == shiftLeftAction)
0675         shift.setX(1);
0676     else if (action == shiftRightAction)
0677         shift.setX(-1);
0678     else if (action == shiftUpAction)
0679         shift.setY(-1);
0680     else if (action == shiftDownAction)
0681         shift.setY(1);
0682 
0683     m_image->beginMacro(i18n("%1: change position of selected DatapickerPoints.", m_image->name()));
0684     const QVector<DatapickerPoint*> axisPoints = m_image->children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0685     for (auto* point : axisPoints) {
0686         if (!point->graphicsItem()->isSelected())
0687             continue;
0688 
0689         QPointF newPos = point->position();
0690         newPos = newPos + shift;
0691         point->setPosition(newPos);
0692 
0693         int pointIndex = m_image->indexOfChild<DatapickerPoint>(point, AbstractAspect::ChildIndexFlag::IncludeHidden);
0694         if (pointIndex == -1)
0695             continue;
0696         DatapickerImage::ReferencePoints points = m_image->axisPoints();
0697         points.scenePos[pointIndex].setX(point->position().x());
0698         points.scenePos[pointIndex].setY(point->position().y());
0699         m_image->setUndoAware(false);
0700         m_image->setAxisPoints(points);
0701         m_image->setUndoAware(true);
0702     }
0703 
0704     for (auto* curve : m_image->parentAspect()->children<DatapickerCurve>()) {
0705         for (auto* point : curve->children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden)) {
0706             if (!point->graphicsItem()->isSelected())
0707                 continue;
0708 
0709             QPointF newPos = point->position();
0710             newPos = newPos + shift;
0711             point->setPosition(newPos);
0712         }
0713     }
0714 
0715     m_image->endMacro();
0716     if (m_image->m_magnificationWindow && m_image->m_magnificationWindow->isVisible())
0717         updateMagnificationWindow();
0718 }
0719 
0720 void DatapickerImageView::magnificationChanged(QAction* action) {
0721     if (action == noMagnificationAction) {
0722         magnificationFactor = 0;
0723         if (m_image->m_magnificationWindow)
0724             m_image->m_magnificationWindow->setVisible(false);
0725     } else if (action == twoTimesMagnificationAction)
0726         magnificationFactor = 2;
0727     else if (action == threeTimesMagnificationAction)
0728         magnificationFactor = 3;
0729     else if (action == fourTimesMagnificationAction)
0730         magnificationFactor = 4;
0731     else if (action == fiveTimesMagnificationAction)
0732         magnificationFactor = 5;
0733 }
0734 
0735 void DatapickerImageView::addCurve() {
0736     m_datapicker->beginMacro(i18n("%1: add new curve.", m_datapicker->name()));
0737     auto* curve = new DatapickerCurve(i18n("Curve"));
0738     curve->addDatasheet(m_image->axisPoints().type);
0739     m_datapicker->addChild(curve);
0740     m_datapicker->endMacro();
0741 
0742     setCurvePointsAction->setChecked(true);
0743     mouseModeChanged(setCurvePointsAction);
0744 }
0745 
0746 void DatapickerImageView::changeRotationAngle() {
0747     this->rotate(m_rotationAngle);
0748     this->rotate(-m_image->rotationAngle());
0749     m_rotationAngle = m_image->rotationAngle();
0750     updateBackground();
0751 }
0752 
0753 void DatapickerImageView::handleImageActions() {
0754     if (m_image->isLoaded) {
0755         magnificationActionGroup->setEnabled(true);
0756         setAxisPointsAction->setEnabled(true);
0757 
0758         int pointsCount = m_image->childCount<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0759         if (pointsCount > 0)
0760             navigationActionGroup->setEnabled(true);
0761         else
0762             navigationActionGroup->setEnabled(false);
0763 
0764         if (pointsCount > 2) {
0765             addCurveAction->setEnabled(true);
0766             if (m_datapicker->activeCurve()) {
0767                 setCurvePointsAction->setEnabled(true);
0768                 selectSegmentAction->setEnabled(true);
0769             } else {
0770                 setCurvePointsAction->setEnabled(false);
0771                 selectSegmentAction->setEnabled(false);
0772             }
0773         } else {
0774             addCurveAction->setEnabled(false);
0775             setCurvePointsAction->setEnabled(false);
0776             selectSegmentAction->setEnabled(false);
0777         }
0778     } else {
0779         navigationActionGroup->setEnabled(false);
0780         magnificationActionGroup->setEnabled(false);
0781         setAxisPointsAction->setEnabled(false);
0782         addCurveAction->setEnabled(false);
0783         setCurvePointsAction->setEnabled(false);
0784         selectSegmentAction->setEnabled(false);
0785     }
0786 }
0787 
0788 void DatapickerImageView::exportToFile(const QString& path, const WorksheetView::ExportFormat format, const int resolution) {
0789     QRectF sourceRect = scene()->sceneRect();
0790 
0791     // print
0792     if (format == WorksheetView::ExportFormat::PDF) {
0793         QPrinter printer(QPrinter::HighResolution);
0794         printer.setOutputFormat(QPrinter::PdfFormat);
0795         printer.setOutputFileName(path);
0796         int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter);
0797         int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::Millimeter);
0798         printer.setPageSize(QPageSize(QSizeF(w, h), QPageSize::Millimeter));
0799         printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPageLayout::Millimeter);
0800         printer.setPrintRange(QPrinter::PageRange);
0801         printer.setCreator(QLatin1String("LabPlot ") + QLatin1String(LVERSION));
0802 
0803         QPainter painter(&printer);
0804         painter.setRenderHint(QPainter::Antialiasing);
0805         QRectF targetRect(0, 0, painter.device()->width(), painter.device()->height());
0806         painter.begin(&printer);
0807         exportPaint(&painter, targetRect, sourceRect);
0808         painter.end();
0809     } else if (format == WorksheetView::ExportFormat::SVG) {
0810         QSvgGenerator generator;
0811         generator.setFileName(path);
0812         int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter);
0813         int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::Millimeter);
0814         w = w * QApplication::primaryScreen()->physicalDotsPerInchX() / 25.4;
0815         h = h * QApplication::primaryScreen()->physicalDotsPerInchY() / 25.4;
0816 
0817         generator.setSize(QSize(w, h));
0818         QRectF targetRect(0, 0, w, h);
0819         generator.setViewBox(targetRect);
0820 
0821         QPainter painter;
0822         painter.begin(&generator);
0823         exportPaint(&painter, targetRect, sourceRect);
0824         painter.end();
0825     } else {
0826         // PNG
0827         // TODO add all formats supported by Qt in QImage
0828         int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter);
0829         int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::Millimeter);
0830         w = w * resolution / 25.4;
0831         h = h * resolution / 25.4;
0832         QImage image(QSize(w, h), QImage::Format_ARGB32_Premultiplied);
0833         image.fill(Qt::transparent);
0834         QRectF targetRect(0, 0, w, h);
0835 
0836         QPainter painter;
0837         painter.begin(&image);
0838         painter.setRenderHint(QPainter::Antialiasing);
0839         exportPaint(&painter, targetRect, sourceRect);
0840         painter.end();
0841 
0842         if (!path.isEmpty()) {
0843             bool rc{false};
0844             switch (format) {
0845             case WorksheetView::ExportFormat::PNG:
0846                 rc = image.save(path, "PNG");
0847                 break;
0848             case WorksheetView::ExportFormat::JPG:
0849                 rc = image.save(path, "JPG");
0850                 break;
0851             case WorksheetView::ExportFormat::BMP:
0852                 rc = image.save(path, "BMP");
0853                 break;
0854             case WorksheetView::ExportFormat::PPM:
0855                 rc = image.save(path, "PPM");
0856                 break;
0857             case WorksheetView::ExportFormat::XBM:
0858                 rc = image.save(path, "XBM");
0859                 break;
0860             case WorksheetView::ExportFormat::XPM:
0861                 rc = image.save(path, "XPM");
0862                 break;
0863             case WorksheetView::ExportFormat::PDF:
0864             case WorksheetView::ExportFormat::SVG:
0865                 break;
0866             }
0867 
0868             if (!rc) {
0869                 RESET_CURSOR;
0870                 QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path));
0871             }
0872         }
0873     }
0874 }
0875 
0876 void DatapickerImageView::exportPaint(QPainter* painter, const QRectF& targetRect, const QRectF& sourceRect) {
0877     bool magnificationActive = false;
0878     if (m_image->m_magnificationWindow && m_image->m_magnificationWindow->isVisible()) {
0879         magnificationActive = true;
0880         m_image->m_magnificationWindow->setVisible(false);
0881     }
0882 
0883     painter->save();
0884     painter->scale(targetRect.width() / sourceRect.width(), targetRect.height() / sourceRect.height());
0885     drawBackground(painter, sourceRect);
0886     painter->restore();
0887     m_image->setPrinting(true);
0888     scene()->render(painter, QRectF(), sourceRect);
0889     m_image->setPrinting(false);
0890 
0891     if (magnificationActive)
0892         m_image->m_magnificationWindow->setVisible(true);
0893 }
0894 
0895 void DatapickerImageView::print(QPrinter* printer) {
0896     const QRectF scene_rect = sceneRect();
0897     int w = Worksheet::convertFromSceneUnits(scene_rect.width(), Worksheet::Unit::Millimeter);
0898     int h = Worksheet::convertFromSceneUnits(scene_rect.height(), Worksheet::Unit::Millimeter);
0899     printer->setPageSize(QPageSize(QSizeF(w, h), QPageSize::Millimeter));
0900     printer->setPageMargins(QMarginsF(0, 0, 0, 0), QPageLayout::Millimeter);
0901     printer->setPrintRange(QPrinter::PageRange);
0902     printer->setCreator(QStringLiteral("LabPlot ") + QLatin1String(LVERSION));
0903 
0904     QPainter painter(printer);
0905     QRectF targetRect(0, 0, painter.device()->width(), painter.device()->height());
0906     painter.setRenderHint(QPainter::Antialiasing);
0907     painter.begin(printer);
0908     painter.save();
0909     painter.scale(targetRect.width() / scene_rect.width(), targetRect.height() / scene_rect.height());
0910 
0911     // canvas
0912     if (m_image->isLoaded) {
0913         if (m_image->plotImageType() == DatapickerImage::PlotImageType::OriginalImage) {
0914             QImage todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0915             painter.drawImage(scene_rect.topLeft(), todraw);
0916         } else if (m_image->plotImageType() == DatapickerImage::PlotImageType::ProcessedImage) {
0917             QImage todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0918             painter.drawImage(scene_rect.topLeft(), todraw);
0919         } else
0920             painter.fillRect(scene_rect, Qt::white);
0921     } else {
0922         painter.setBrush(QBrush(Qt::gray));
0923         painter.drawRect(scene_rect);
0924     }
0925 
0926     painter.restore();
0927     m_image->setPrinting(true);
0928     bool magnificationActive = false;
0929     if (m_image->m_magnificationWindow && m_image->m_magnificationWindow->isVisible()) {
0930         magnificationActive = true;
0931         m_image->m_magnificationWindow->setVisible(false);
0932     }
0933     scene()->render(&painter, QRectF(), scene_rect);
0934     m_image->setPrinting(false);
0935     painter.end();
0936 
0937     if (magnificationActive)
0938         m_image->m_magnificationWindow->setVisible(true);
0939 }
0940 
0941 void DatapickerImageView::updateBackground() {
0942     invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer);
0943 }