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 }