diff --git a/src/backend/datapicker/DatapickerImage.cpp b/src/backend/datapicker/DatapickerImage.cpp index a4e62d2c2..d6ae0dd0f 100644 --- a/src/backend/datapicker/DatapickerImage.cpp +++ b/src/backend/datapicker/DatapickerImage.cpp @@ -1,712 +1,717 @@ /*************************************************************************** File : DatapickerImage.cpp Project : LabPlot Description : Worksheet for Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2019 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DatapickerImage.h" #include "DatapickerImagePrivate.h" #include "backend/datapicker/ImageEditor.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/datapicker/Segments.h" #include "backend/worksheet/Worksheet.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "kdefrontend/worksheet/ExportWorksheetDialog.h" #include #include #include #include #include #include #include #include #include /** * \class DatapickerImage * \brief container to open image/plot. * * Top-level container for DatapickerPoint. * * * \ingroup datapicker */ DatapickerImage::DatapickerImage(const QString& name, bool loading) : AbstractPart(name, AspectType::DatapickerImage), foregroundBins( new int[ImageEditor::colorAttributeMax(Foreground) + 1]), hueBins( new int[ImageEditor::colorAttributeMax(Hue) + 1]), saturationBins( new int[ImageEditor::colorAttributeMax(Saturation) + 1]), valueBins( new int[ImageEditor::colorAttributeMax(Value) + 1]), intensityBins( new int[ImageEditor::colorAttributeMax(Intensity) + 1]), d(new DatapickerImagePrivate(this)), m_segments(new Segments(this)) { if (!loading) init(); } DatapickerImage::~DatapickerImage() { delete [] hueBins; delete [] saturationBins; delete [] valueBins; delete [] intensityBins; delete [] foregroundBins; delete m_segments; delete d; } void DatapickerImage::init() { KConfig config; KConfigGroup group = config.group( "DatapickerImage" ); + + //general properties d->fileName = group.readEntry("FileName", QString()); d->rotationAngle = group.readEntry("RotationAngle", 0.0); d->minSegmentLength = group.readEntry("MinSegmentLength", 30); d->pointSeparation = group.readEntry("PointSeparation", 30); d->axisPoints.type = (DatapickerImage::GraphType) group.readEntry("GraphType", (int) DatapickerImage::Cartesian); d->axisPoints.ternaryScale = group.readEntry("TernaryScale", 1); + d->plotPointsType = (DatapickerImage::PointsType) group.readEntry("PlotPointsType", (int) DatapickerImage::AxisPoints); + + //edit image settings + d->plotImageType = DatapickerImage::OriginalImage; d->settings.foregroundThresholdHigh = group.readEntry("ForegroundThresholdHigh", 90); d->settings.foregroundThresholdLow = group.readEntry("ForegroundThresholdLow", 30); d->settings.hueThresholdHigh = group.readEntry("HueThresholdHigh", 360); d->settings.hueThresholdLow = group.readEntry("HueThresholdLow", 0); d->settings.intensityThresholdHigh = group.readEntry("IntensityThresholdHigh", 100); d->settings.intensityThresholdLow = group.readEntry("IntensityThresholdLow", 20); d->settings.saturationThresholdHigh = group.readEntry("SaturationThresholdHigh", 100); d->settings.saturationThresholdLow = group.readEntry("SaturationThresholdLow", 30); d->settings.valueThresholdHigh = group.readEntry("ValueThresholdHigh", 90); d->settings.valueThresholdLow = group.readEntry("ValueThresholdLow", 30); - d->plotPointsType = (DatapickerImage::PointsType) group.readEntry("PlotPointsType", (int) DatapickerImage::AxisPoints); - d->plotImageType = DatapickerImage::OriginalImage; - // point properties + + // reference point symbol properties d->pointStyle = (Symbol::Style)group.readEntry("PointStyle", (int)Symbol::Cross); d->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Point)); d->pointRotationAngle = group.readEntry("Rotation", 0.0); d->pointOpacity = group.readEntry("Opacity", 1.0); d->pointBrush.setStyle( (Qt::BrushStyle)group.readEntry("FillingStyle", (int)Qt::NoBrush) ); d->pointBrush.setColor( group.readEntry("FillingColor", QColor(Qt::black)) ); d->pointPen.setStyle( (Qt::PenStyle)group.readEntry("BorderStyle", (int)Qt::SolidLine) ); d->pointPen.setColor( group.readEntry("BorderColor", QColor(Qt::red)) ); d->pointPen.setWidthF( group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Point)) ); d->pointVisibility = group.readEntry("PointVisibility", true); } /*! Returns an icon to be used in the project explorer. */ QIcon DatapickerImage::icon() const { return QIcon::fromTheme("image-x-generic"); } /*! Return a new context menu */ QMenu* DatapickerImage::createContextMenu() { QMenu* menu = new QMenu(nullptr); emit requestProjectContextMenu(menu); return menu; } void DatapickerImage::createContextMenu(QMenu* menu) { emit requestProjectContextMenu(menu); } //! Construct a primary view on me. /** * This method may be called multiple times during the life time of an Aspect, or it might not get * called at all. Aspects must not depend on the existence of a view for their operation. */ QWidget* DatapickerImage::view() const { if (!m_partView) { m_view = new DatapickerImageView(const_cast(this)); m_partView = m_view; connect(m_view, &DatapickerImageView::statusInfo, this, &DatapickerImage::statusInfo); } return m_partView; } bool DatapickerImage::exportView() const { auto* dlg = new ExportWorksheetDialog(m_view); dlg->setFileName(name()); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const QString path = dlg->path(); const WorksheetView::ExportFormat format = dlg->exportFormat(); const int resolution = dlg->exportResolution(); WAIT_CURSOR; m_view->exportToFile(path, format, resolution); RESET_CURSOR; } delete dlg; return ret; } bool DatapickerImage::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, m_view); bool ret; dlg->setWindowTitle(i18nc("@title:window", "Print Datapicker Image")); if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool DatapickerImage::printPreview() const { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &DatapickerImageView::print); return dlg->exec(); } /*! Selects or deselects the Datapicker/DatapickerImage in the project explorer. This function is called in \c DatapickerImageView. The DatapickerImage gets deselected if there are selected items in the view, and selected if there are no selected items in the view. */ void DatapickerImage::setSelectedInView(const bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } void DatapickerImage::setSegmentsHoverEvent(const bool on) { m_segments->setAcceptHoverEvents(on); } QGraphicsScene* DatapickerImage::scene() const { return d->m_scene; } QRectF DatapickerImage::pageRect() const { return d->m_scene->sceneRect(); } void DatapickerImage::setPlotImageType(const DatapickerImage::PlotImageType type) { d->plotImageType = type; if (d->plotImageType == DatapickerImage::ProcessedImage) d->discretize(); emit requestUpdate(); } DatapickerImage::PlotImageType DatapickerImage::plotImageType() { return d->plotImageType; } void DatapickerImage::initSceneParameters() { setRotationAngle(0.0); setminSegmentLength(30); setPointSeparation(30); ReferencePoints axisPoints = d->axisPoints; axisPoints.ternaryScale = 1; axisPoints.type = DatapickerImage::Cartesian; setAxisPoints(axisPoints); EditorSettings settings; settings.foregroundThresholdHigh = 90; settings.foregroundThresholdLow = 30; settings.hueThresholdHigh = 360; settings.hueThresholdLow = 0; settings.intensityThresholdHigh = 100; settings.intensityThresholdLow = 20; settings.saturationThresholdHigh = 100; settings.saturationThresholdLow = 30; settings.valueThresholdHigh = 90; settings.valueThresholdLow = 30; setSettings(settings); DatapickerImage::PointsType plotPointsType = DatapickerImage::AxisPoints; setPlotPointsType(plotPointsType); } /* =============================== getter methods for background options ================================= */ CLASS_D_READER_IMPL(DatapickerImage, QString, fileName, fileName) CLASS_D_READER_IMPL(DatapickerImage, DatapickerImage::ReferencePoints, axisPoints, axisPoints) CLASS_D_READER_IMPL(DatapickerImage, DatapickerImage::EditorSettings, settings, settings) BASIC_D_READER_IMPL(DatapickerImage, float, rotationAngle, rotationAngle) BASIC_D_READER_IMPL(DatapickerImage, DatapickerImage::PointsType, plotPointsType, plotPointsType) BASIC_D_READER_IMPL(DatapickerImage, int, pointSeparation, pointSeparation) BASIC_D_READER_IMPL(DatapickerImage, int, minSegmentLength, minSegmentLength) BASIC_D_READER_IMPL(DatapickerImage, Symbol::Style, pointStyle, pointStyle) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointOpacity, pointOpacity) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointRotationAngle, pointRotationAngle) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointSize, pointSize) CLASS_D_READER_IMPL(DatapickerImage, QBrush, pointBrush, pointBrush) CLASS_D_READER_IMPL(DatapickerImage, QPen, pointPen, pointPen) BASIC_D_READER_IMPL(DatapickerImage, bool, pointVisibility, pointVisibility) /* ============================ setter methods and undo commands for background options ================= */ STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetFileName, QString, fileName, updateFileName) void DatapickerImage::setFileName(const QString& fileName) { if (fileName!= d->fileName) { beginMacro(i18n("%1: upload new image", name())); exec(new DatapickerImageSetFileNameCmd(d, fileName, ki18n("%1: upload image"))); endMacro(); } } STD_SETTER_CMD_IMPL_S(DatapickerImage, SetRotationAngle, float, rotationAngle) void DatapickerImage::setRotationAngle(float angle) { if (angle != d->rotationAngle) exec(new DatapickerImageSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_S(DatapickerImage, SetAxisPoints, DatapickerImage::ReferencePoints, axisPoints) void DatapickerImage::setAxisPoints(const DatapickerImage::ReferencePoints& points) { if (memcmp(&points, &d->axisPoints, sizeof(points)) != 0) exec(new DatapickerImageSetAxisPointsCmd(d, points, ki18n("%1: set Axis points"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetSettings, DatapickerImage::EditorSettings, settings, discretize) void DatapickerImage::setSettings(const DatapickerImage::EditorSettings& editorSettings) { if (memcmp(&editorSettings, &d->settings, sizeof(editorSettings)) != 0) exec(new DatapickerImageSetSettingsCmd(d, editorSettings, ki18n("%1: set editor settings"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetMinSegmentLength, int, minSegmentLength, makeSegments) void DatapickerImage::setminSegmentLength(const int value) { if (d->minSegmentLength != value) exec(new DatapickerImageSetMinSegmentLengthCmd(d, value, ki18n("%1: set minimum segment length"))); ; } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointStyle, Symbol::Style, pointStyle, retransform) void DatapickerImage::setPointStyle(Symbol::Style newStyle) { if (newStyle != d->pointStyle) exec(new DatapickerImageSetPointStyleCmd(d, newStyle, ki18n("%1: set point's style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointSize, qreal, pointSize, retransform) void DatapickerImage::setPointSize(qreal value) { if (!qFuzzyCompare(1 + value, 1 + d->pointSize)) exec(new DatapickerImageSetPointSizeCmd(d, value, ki18n("%1: set point's size"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointRotationAngle, qreal, pointRotationAngle, retransform) void DatapickerImage::setPointRotationAngle(qreal angle) { if (!qFuzzyCompare(1 + angle, 1 + d->pointRotationAngle)) exec(new DatapickerImageSetPointRotationAngleCmd(d, angle, ki18n("%1: rotate point"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointBrush, QBrush, pointBrush, retransform) void DatapickerImage::setPointBrush(const QBrush& newBrush) { if (newBrush != d->pointBrush) exec(new DatapickerImageSetPointBrushCmd(d, newBrush, ki18n("%1: set point's filling"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointPen, QPen, pointPen, retransform) void DatapickerImage::setPointPen(const QPen &newPen) { if (newPen != d->pointPen) exec(new DatapickerImageSetPointPenCmd(d, newPen, ki18n("%1: set outline style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointOpacity, qreal, pointOpacity, retransform) void DatapickerImage::setPointOpacity(qreal newOpacity) { if (newOpacity != d->pointOpacity) exec(new DatapickerImageSetPointOpacityCmd(d, newOpacity, ki18n("%1: set point's opacity"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointVisibility, bool, pointVisibility, retransform) void DatapickerImage::setPointVisibility(const bool on) { if (on != d->pointVisibility) exec(new DatapickerImageSetPointVisibilityCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } void DatapickerImage::setPrinting(bool on) const { QVector childPoints = parentAspect()->children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* point : childPoints) point->setPrinting(on); } void DatapickerImage::setPlotPointsType(const PointsType pointsType) { d->plotPointsType = pointsType; if (pointsType == DatapickerImage::AxisPoints) { //clear image int childCount = this->childCount(AbstractAspect::IncludeHidden); if (childCount) { beginMacro(i18n("%1: remove all axis points", name())); QVector childrenPoints = children(AbstractAspect::IncludeHidden); for (auto* point : childrenPoints) point->remove(); endMacro(); } m_segments->setSegmentsVisible(false); } else if (pointsType == DatapickerImage::CurvePoints) m_segments->setSegmentsVisible(false); else if (pointsType == DatapickerImage::SegmentPoints) { d->makeSegments(); m_segments->setSegmentsVisible(true); } } void DatapickerImage::setPointSeparation(const int value) { d->pointSeparation = value; } //############################################################################## //###################### Private implementation ############################### //############################################################################## DatapickerImagePrivate::DatapickerImagePrivate(DatapickerImage *owner) : q(owner), - pageRect(0, 0, 1500, 1500), + pageRect(0, 0, 1000, 1000), m_scene(new QGraphicsScene(pageRect)) { } QString DatapickerImagePrivate::name() const { return q->name(); } void DatapickerImagePrivate::retransform() { QVector childrenPoints = q->children(AbstractAspect::IncludeHidden); for (auto* point : childrenPoints) point->retransform(); } bool DatapickerImagePrivate::uploadImage(const QString& address) { bool rc = q->originalPlotImage.load(address); if (rc) { //convert the image to 32bit-format if this is not the case yet QImage::Format format = q->originalPlotImage.format(); if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_ARGB32_Premultiplied) q->originalPlotImage = q->originalPlotImage.convertToFormat(QImage::Format_RGB32); q->processedPlotImage = q->originalPlotImage; q->background = ImageEditor::findBackgroundColor(&q->originalPlotImage); //upload Histogram ImageEditor::uploadHistogram(q->intensityBins, &q->originalPlotImage, q->background, DatapickerImage::Intensity); ImageEditor::uploadHistogram(q->foregroundBins, &q->originalPlotImage, q->background, DatapickerImage::Foreground); ImageEditor::uploadHistogram(q->hueBins, &q->originalPlotImage, q->background, DatapickerImage::Hue); ImageEditor::uploadHistogram(q->saturationBins, &q->originalPlotImage, q->background, DatapickerImage::Saturation); ImageEditor::uploadHistogram(q->valueBins, &q->originalPlotImage, q->background, DatapickerImage::Value); discretize(); //resize the screen double w = Worksheet::convertToSceneUnits(q->originalPlotImage.width(), Worksheet::Inch)/QApplication::desktop()->physicalDpiX(); double h = Worksheet::convertToSceneUnits(q->originalPlotImage.height(), Worksheet::Inch)/QApplication::desktop()->physicalDpiX(); m_scene->setSceneRect(0, 0, w, h); q->isLoaded = true; } return rc; } void DatapickerImagePrivate::discretize() { if (plotImageType != DatapickerImage::ProcessedImage) return; ImageEditor::discretize(&q->processedPlotImage, &q->originalPlotImage, settings, q->background); if (plotPointsType != DatapickerImage::SegmentPoints) emit q->requestUpdate(); else makeSegments(); } void DatapickerImagePrivate::makeSegments() { if (plotPointsType != DatapickerImage::SegmentPoints) return; q->m_segments->makeSegments(q->processedPlotImage); q->m_segments->setSegmentsVisible(true); emit q->requestUpdate(); } DatapickerImagePrivate::~DatapickerImagePrivate() { delete m_scene; } void DatapickerImagePrivate::updateFileName() { WAIT_CURSOR; q->isLoaded = false; const QString& address = fileName.trimmed(); if (!address.isEmpty()) { if (uploadImage(address)) { q->initSceneParameters(); fileName = address; } } else { //hide segments if they are visible q->m_segments->setSegmentsVisible(false); } QVector childPoints = q->parentAspect()->children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); if (childPoints.count()) { for (auto* point : childPoints) point->remove(); } emit q->requestUpdate(); emit q->requestUpdateActions(); RESET_CURSOR; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void DatapickerImage::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "datapickerImage" ); writeBasicAttributes(writer); writeCommentElement(writer); //general properties writer->writeStartElement( "general" ); writer->writeAttribute( "fileName", d->fileName ); writer->writeAttribute( "plotPointsType", QString::number(d->plotPointsType) ); writer->writeEndElement(); writer->writeStartElement( "axisPoint" ); writer->writeAttribute( "graphType", QString::number(d->axisPoints.type) ); writer->writeAttribute( "ternaryScale", QString::number(d->axisPoints.ternaryScale) ); writer->writeAttribute( "axisPointLogicalX1", QString::number(d->axisPoints.logicalPos[0].x()) ); writer->writeAttribute( "axisPointLogicalY1", QString::number(d->axisPoints.logicalPos[0].y()) ); writer->writeAttribute( "axisPointLogicalX2", QString::number(d->axisPoints.logicalPos[1].x()) ); writer->writeAttribute( "axisPointLogicalY2", QString::number(d->axisPoints.logicalPos[1].y()) ); writer->writeAttribute( "axisPointLogicalX3", QString::number(d->axisPoints.logicalPos[2].x()) ); writer->writeAttribute( "axisPointLogicalY3", QString::number(d->axisPoints.logicalPos[2].y()) ); writer->writeAttribute( "axisPointLogicalZ1", QString::number(d->axisPoints.logicalPos[0].z()) ); writer->writeAttribute( "axisPointLogicalZ2", QString::number(d->axisPoints.logicalPos[1].z()) ); writer->writeAttribute( "axisPointLogicalZ3", QString::number(d->axisPoints.logicalPos[2].z()) ); writer->writeAttribute( "axisPointSceneX1", QString::number(d->axisPoints.scenePos[0].x()) ); writer->writeAttribute( "axisPointSceneY1", QString::number(d->axisPoints.scenePos[0].y()) ); writer->writeAttribute( "axisPointSceneX2", QString::number(d->axisPoints.scenePos[1].x()) ); writer->writeAttribute( "axisPointSceneY2", QString::number(d->axisPoints.scenePos[1].y()) ); writer->writeAttribute( "axisPointSceneX3", QString::number(d->axisPoints.scenePos[2].x()) ); writer->writeAttribute( "axisPointSceneY3", QString::number(d->axisPoints.scenePos[2].y()) ); writer->writeEndElement(); //editor and segment settings writer->writeStartElement( "editorSettings" ); writer->writeAttribute( "plotImageType", QString::number(d->plotImageType) ); writer->writeAttribute( "rotationAngle", QString::number(d->rotationAngle) ); writer->writeAttribute( "minSegmentLength", QString::number(d->minSegmentLength) ); writer->writeAttribute( "pointSeparation", QString::number(d->pointSeparation) ); writer->writeAttribute( "foregroundThresholdHigh", QString::number(d->settings.foregroundThresholdHigh) ); writer->writeAttribute( "foregroundThresholdLow", QString::number(d->settings.foregroundThresholdLow) ); writer->writeAttribute( "hueThresholdHigh", QString::number(d->settings.hueThresholdHigh) ); writer->writeAttribute( "hueThresholdLow", QString::number(d->settings.hueThresholdLow) ); writer->writeAttribute( "intensityThresholdHigh", QString::number(d->settings.intensityThresholdHigh) ); writer->writeAttribute( "intensityThresholdLow", QString::number(d->settings.intensityThresholdLow) ); writer->writeAttribute( "saturationThresholdHigh", QString::number(d->settings.saturationThresholdHigh) ); writer->writeAttribute( "saturationThresholdLow", QString::number(d->settings.saturationThresholdLow) ); writer->writeAttribute( "valueThresholdHigh", QString::number(d->settings.valueThresholdHigh) ); writer->writeAttribute( "valueThresholdLow", QString::number(d->settings.valueThresholdLow) ); writer->writeEndElement(); //symbol properties writer->writeStartElement( "symbolProperties" ); writer->writeAttribute( "pointRotationAngle", QString::number(d->pointRotationAngle) ); writer->writeAttribute( "pointOpacity", QString::number(d->pointOpacity) ); writer->writeAttribute( "pointSize", QString::number(d->pointSize) ); writer->writeAttribute( "pointStyle", QString::number(d->pointStyle) ); writer->writeAttribute( "pointVisibility", QString::number(d->pointVisibility) ); WRITE_QBRUSH(d->pointBrush); WRITE_QPEN(d->pointPen); writer->writeEndElement(); //serialize all children for (auto* child : children(IncludeHidden)) child->save(writer); writer->writeEndElement(); } //! Load from XML bool DatapickerImage::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "datapickerImage") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); d->fileName = str; str = attribs.value("plotPointsType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("plotPointsType").toString()); else d->plotPointsType = DatapickerImage::PointsType(str.toInt()); } else if (!preview && reader->name() == "axisPoint") { attribs = reader->attributes(); READ_INT_VALUE("graphType", axisPoints.type, DatapickerImage::GraphType); READ_INT_VALUE("ternaryScale", axisPoints.ternaryScale, int); str = attribs.value("axisPointLogicalX1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalX1").toString()); else d->axisPoints.logicalPos[0].setX(str.toDouble()); str = attribs.value("axisPointLogicalY1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalY1").toString()); else d->axisPoints.logicalPos[0].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalZ1").toString()); else d->axisPoints.logicalPos[0].setZ(str.toDouble()); str = attribs.value("axisPointLogicalX2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalX2").toString()); else d->axisPoints.logicalPos[1].setX(str.toDouble()); str = attribs.value("axisPointLogicalY2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalY2").toString()); else d->axisPoints.logicalPos[1].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalZ2").toString()); else d->axisPoints.logicalPos[1].setZ(str.toDouble()); str = attribs.value("axisPointLogicalX3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalX3").toString()); else d->axisPoints.logicalPos[2].setX(str.toDouble()); str = attribs.value("axisPointLogicalY3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalY3").toString()); else d->axisPoints.logicalPos[2].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointLogicalZ3").toString()); else d->axisPoints.logicalPos[2].setZ(str.toDouble()); str = attribs.value("axisPointSceneX1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneX1").toString()); else d->axisPoints.scenePos[0].setX(str.toDouble()); str = attribs.value("axisPointSceneY1").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneY1").toString()); else d->axisPoints.scenePos[0].setY(str.toDouble()); str = attribs.value("axisPointSceneX2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneX2").toString()); else d->axisPoints.scenePos[1].setX(str.toDouble()); str = attribs.value("axisPointSceneY2").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneY2").toString()); else d->axisPoints.scenePos[1].setY(str.toDouble()); str = attribs.value("axisPointSceneX3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneX3").toString()); else d->axisPoints.scenePos[2].setX(str.toDouble()); str = attribs.value("axisPointSceneY3").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("axisPointSceneY3").toString()); else d->axisPoints.scenePos[2].setY(str.toDouble()); } else if (!preview && reader->name() == "editorSettings") { attribs = reader->attributes(); READ_INT_VALUE("plotImageType", plotImageType, DatapickerImage::PlotImageType); READ_DOUBLE_VALUE("rotationAngle", rotationAngle); READ_INT_VALUE("minSegmentLength", minSegmentLength, int); READ_INT_VALUE("pointSeparation", pointSeparation, int); READ_INT_VALUE("foregroundThresholdHigh", settings.foregroundThresholdHigh, int); READ_INT_VALUE("foregroundThresholdLow", settings.foregroundThresholdLow, int); READ_INT_VALUE("hueThresholdHigh", settings.hueThresholdHigh, int); READ_INT_VALUE("hueThresholdLow", settings.hueThresholdLow, int); READ_INT_VALUE("intensityThresholdHigh", settings.intensityThresholdHigh, int); READ_INT_VALUE("intensityThresholdLow", settings.intensityThresholdLow, int); READ_INT_VALUE("saturationThresholdHigh", settings.saturationThresholdHigh, int); READ_INT_VALUE("saturationThresholdLow", settings.saturationThresholdLow, int); READ_INT_VALUE("valueThresholdHigh", settings.valueThresholdHigh, int); READ_INT_VALUE("valueThresholdLow", settings.valueThresholdLow, int); } else if (!preview && reader->name() == "symbolProperties") { attribs = reader->attributes(); READ_DOUBLE_VALUE("pointRotationAngle", pointRotationAngle); READ_DOUBLE_VALUE("pointOpacity", pointOpacity); READ_DOUBLE_VALUE("pointSize", pointSize); READ_INT_VALUE("pointStyle", pointStyle, Symbol::Style); READ_INT_VALUE("pointVisibility", pointVisibility, bool); READ_QBRUSH(d->pointBrush); READ_QPEN(d->pointPen); } else if (reader->name() == "datapickerPoint") { DatapickerPoint* datapickerPoint = new DatapickerPoint(QString()); datapickerPoint->setHidden(true); if (!datapickerPoint->load(reader, preview)) { delete datapickerPoint; return false; } else addChild(datapickerPoint); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } d->uploadImage(d->fileName); d->retransform(); return true; } diff --git a/src/commonfrontend/datapicker/DatapickerImageView.cpp b/src/commonfrontend/datapicker/DatapickerImageView.cpp index aa0fda324..5528895a6 100644 --- a/src/commonfrontend/datapicker/DatapickerImageView.cpp +++ b/src/commonfrontend/datapicker/DatapickerImageView.cpp @@ -1,815 +1,831 @@ /*************************************************************************** File : DatapickerImageView.cpp Project : LabPlot Description : DatapickerImage view for datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "commonfrontend/datapicker/DatapickerImageView.h" #include "backend/worksheet/Worksheet.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/datapicker/Datapicker.h" #include "backend/datapicker/Transform.h" #include "backend/datapicker/DatapickerCurve.h" #include "backend/datapicker/DatapickerImage.h" #include #include #include #include #include #include #include #include #include #include #include /** * \class DatapickerImageView * \brief Datapicker/DatapickerImage view */ /*! Constructur of the class. Creates a view for the DatapickerImage \c image and initializes the internal model. */ DatapickerImageView::DatapickerImageView(DatapickerImage* image) : QGraphicsView(), m_image(image), m_datapicker(dynamic_cast(m_image->parentAspect())), m_transform(new Transform()) { setScene(m_image->scene()); setRenderHint(QPainter::Antialiasing); setRubberBandSelectionMode(Qt::ContainsItemBoundingRect); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorViewCenter); setMinimumSize(16, 16); setFocusPolicy(Qt::StrongFocus); viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); viewport()->setAttribute( Qt::WA_NoSystemBackground ); setCacheMode(QGraphicsView::CacheBackground); initActions(); initMenus(); selectAndEditModeAction->setChecked(true); m_image->setSegmentsHoverEvent(true); setInteractive(true); changeZoom(zoomOriginAction); currentZoomAction = zoomInViewAction; if (m_image->plotPointsType() == DatapickerImage::AxisPoints) setAxisPointsAction->setChecked(true); else if (m_image->plotPointsType() == DatapickerImage::CurvePoints) setCurvePointsAction->setChecked(true); else selectSegmentAction->setChecked(true); handleImageActions(); changeRotationAngle(); //signal/slot connections //for general actions connect( m_image, SIGNAL(requestProjectContextMenu(QMenu*)), this, SLOT(createContextMenu(QMenu*)) ); connect( m_image, SIGNAL(requestUpdate()), this, SLOT(updateBackground()) ); connect( m_image, SIGNAL(requestUpdateActions()), this, SLOT(handleImageActions()) ); connect( m_datapicker, SIGNAL(requestUpdateActions()), this, SLOT(handleImageActions()) ); connect( m_image, SIGNAL(rotationAngleChanged(float)), this, SLOT(changeRotationAngle()) ); + + //resize the view to make the complete scene visible. + //no need to resize the view when the project is being opened, + //all views will be resized to the stored values at the end + if (!m_image->isLoading()) { + float w = Worksheet::convertFromSceneUnits(sceneRect().width(), Worksheet::Inch); + float h = Worksheet::convertFromSceneUnits(sceneRect().height(), Worksheet::Inch); + w *= QApplication::desktop()->physicalDpiX(); + h *= QApplication::desktop()->physicalDpiY(); + resize(w*1.1, h*1.1); + } + + //rescale to the original size + static const float hscale = QApplication::desktop()->physicalDpiX()/(Worksheet::convertToSceneUnits(1,Worksheet::Inch)); + static const float vscale = QApplication::desktop()->physicalDpiY()/(Worksheet::convertToSceneUnits(1,Worksheet::Inch)); + setTransform(QTransform::fromScale(hscale, vscale)); } DatapickerImageView::~DatapickerImageView() { delete m_transform; } void DatapickerImageView::initActions() { auto* zoomActionGroup = new QActionGroup(this); auto* mouseModeActionGroup = new QActionGroup(this); auto* plotPointsTypeActionGroup = new QActionGroup(this); navigationActionGroup = new QActionGroup(this); magnificationActionGroup = new QActionGroup(this); //Zoom actions zoomInViewAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), zoomActionGroup); zoomInViewAction->setShortcut(Qt::CTRL+Qt::Key_Plus); zoomOutViewAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), zoomActionGroup); zoomOutViewAction->setShortcut(Qt::CTRL+Qt::Key_Minus); zoomOriginAction = new QAction(QIcon::fromTheme("zoom-original"), i18n("Original Size"), zoomActionGroup); zoomOriginAction->setShortcut(Qt::CTRL+Qt::Key_1); zoomFitPageHeightAction = new QAction(QIcon::fromTheme("zoom-fit-height"), i18n("Fit to Height"), zoomActionGroup); zoomFitPageWidthAction = new QAction(QIcon::fromTheme("zoom-fit-width"), i18n("Fit to Width"), zoomActionGroup); // Mouse mode actions selectAndEditModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Edit"), mouseModeActionGroup); selectAndEditModeAction->setCheckable(true); navigationModeAction = new QAction(QIcon::fromTheme("input-mouse"), i18n("Navigate"), mouseModeActionGroup); navigationModeAction->setCheckable(true); zoomSelectionModeAction = new QAction(QIcon::fromTheme("page-zoom"), i18n("Select and Zoom"), mouseModeActionGroup); zoomSelectionModeAction->setCheckable(true); selectAndMoveModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Move"), mouseModeActionGroup); selectAndMoveModeAction->setCheckable(true); setAxisPointsAction = new QAction(QIcon::fromTheme("labplot-plot-axis-points"), i18n("Set Axis Points"), plotPointsTypeActionGroup); setAxisPointsAction->setCheckable(true); setCurvePointsAction = new QAction(QIcon::fromTheme("labplot-xy-curve-points"), i18n("Set Curve Points"), plotPointsTypeActionGroup); setCurvePointsAction->setCheckable(true); selectSegmentAction = new QAction(QIcon::fromTheme("labplot-xy-curve-segments"), i18n("Select Curve Segments"), plotPointsTypeActionGroup); selectSegmentAction->setCheckable(true); addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("New Curve"), this); shiftLeftAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left"), navigationActionGroup); shiftLeftAction->setShortcut(Qt::Key_Right); shiftRightAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right"), navigationActionGroup); shiftRightAction->setShortcut(Qt::Key_Left); shiftUpAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Up"), navigationActionGroup); shiftUpAction->setShortcut(Qt::Key_Up); shiftDownAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Down"), navigationActionGroup); shiftDownAction->setShortcut(Qt::Key_Down); noMagnificationAction = new QAction(QIcon::fromTheme("labplot-1x-zoom"), i18n("No Magnification"), magnificationActionGroup); noMagnificationAction->setCheckable(true); noMagnificationAction->setChecked(true); twoTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-2x-zoom"), i18n("2x Magnification"), magnificationActionGroup); twoTimesMagnificationAction->setCheckable(true); threeTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-3x-zoom"), i18n("3x Magnification"), magnificationActionGroup); threeTimesMagnificationAction->setCheckable(true); fourTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-4x-zoom"), i18n("4x Magnification"), magnificationActionGroup); fourTimesMagnificationAction->setCheckable(true); fiveTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-5x-zoom"), i18n("5x Magnification"), magnificationActionGroup); fiveTimesMagnificationAction->setCheckable(true); connect( mouseModeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(mouseModeChanged(QAction*)) ); connect( zoomActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeZoom(QAction*)) ); connect( plotPointsTypeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changePointsType(QAction*)) ); connect( addCurveAction, SIGNAL(triggered()), this, SLOT(addCurve()) ); connect( navigationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeSelectedItemsPosition(QAction*)) ); connect( magnificationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(magnificationChanged(QAction*)) ); } void DatapickerImageView::initMenus() { m_viewMouseModeMenu = new QMenu(i18n("Mouse Mode"), this); m_viewMouseModeMenu->setIcon(QIcon::fromTheme("input-mouse")); m_viewMouseModeMenu->addAction(selectAndEditModeAction); m_viewMouseModeMenu->addAction(navigationModeAction); m_viewMouseModeMenu->addAction(zoomSelectionModeAction); m_viewMouseModeMenu->addAction(selectAndMoveModeAction); m_viewImageMenu = new QMenu(i18n("Data Entry Mode"), this); m_viewImageMenu->addAction(setAxisPointsAction); m_viewImageMenu->addAction(setCurvePointsAction); m_viewImageMenu->addAction(selectSegmentAction); m_zoomMenu = new QMenu(i18n("Zoom View"), this); m_zoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); m_zoomMenu->addAction(zoomInViewAction); m_zoomMenu->addAction(zoomOutViewAction); m_zoomMenu->addAction(zoomOriginAction); m_zoomMenu->addAction(zoomFitPageHeightAction); m_zoomMenu->addAction(zoomFitPageWidthAction); m_navigationMenu = new QMenu(i18n("Move Last Point"), this); m_navigationMenu->addAction(shiftLeftAction); m_navigationMenu->addAction(shiftRightAction); m_navigationMenu->addAction(shiftUpAction); m_navigationMenu->addAction(shiftDownAction); m_magnificationMenu = new QMenu(i18n("Magnification"), this); m_magnificationMenu->setIcon(QIcon::fromTheme("labplot-zoom")); m_magnificationMenu->addAction(noMagnificationAction); m_magnificationMenu->addAction(twoTimesMagnificationAction); m_magnificationMenu->addAction(threeTimesMagnificationAction); m_magnificationMenu->addAction(fourTimesMagnificationAction); m_magnificationMenu->addAction(fiveTimesMagnificationAction); } /*! * Populates the menu \c menu with the image and image-view relevant actions. * The menu is used * - as the context menu in DatapickerImageView * - as the "datapicker menu" in the main menu-bar (called form MainWin) * - as a part of the image context menu in project explorer */ void DatapickerImageView::createContextMenu(QMenu* menu) const { Q_ASSERT(menu); QAction* firstAction = nullptr; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size()>1) firstAction = menu->actions().at(1); menu->insertMenu(firstAction, m_viewImageMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, addCurveAction); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_navigationMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_viewMouseModeMenu); menu->insertMenu(firstAction, m_zoomMenu); menu->insertMenu(firstAction, m_magnificationMenu); menu->insertSeparator(firstAction); } void DatapickerImageView::fillToolBar(QToolBar* toolBar) { toolBar->addSeparator(); toolBar->addAction(setAxisPointsAction); toolBar->addAction(setCurvePointsAction); toolBar->addAction(selectSegmentAction); toolBar->addSeparator(); toolBar->addAction(addCurveAction); toolBar->addSeparator(); toolBar->addAction(noMagnificationAction); toolBar->addAction(twoTimesMagnificationAction); toolBar->addAction(threeTimesMagnificationAction); toolBar->addAction(fourTimesMagnificationAction); toolBar->addAction(fiveTimesMagnificationAction); toolBar->addSeparator(); toolBar->addAction(shiftRightAction); toolBar->addAction(shiftLeftAction); toolBar->addAction(shiftUpAction); toolBar->addAction(shiftDownAction); toolBar->addSeparator(); toolBar->addAction(selectAndEditModeAction); toolBar->addAction(navigationModeAction); toolBar->addAction(zoomSelectionModeAction); toolBar->addAction(selectAndMoveModeAction); tbZoom = new QToolButton(toolBar); tbZoom->setPopupMode(QToolButton::MenuButtonPopup); tbZoom->setMenu(m_zoomMenu); tbZoom->setDefaultAction(currentZoomAction); toolBar->addWidget(tbZoom); } void DatapickerImageView::setScene(QGraphicsScene* scene) { QGraphicsView::setScene(scene); setTransform(QTransform()); } void DatapickerImageView::drawForeground(QPainter* painter, const QRectF& rect) { if (m_mouseMode == ZoomSelectionMode && m_selectionBandIsShown) { painter->save(); const QRectF& selRect = mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect(); painter->setPen(QPen(Qt::black, 5/transform().m11())); painter->drawRect(selRect); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(selRect); painter->restore(); } QGraphicsView::drawForeground(painter, rect); } void DatapickerImageView::drawBackground(QPainter* painter, const QRectF& rect) { painter->save(); QRectF scene_rect = sceneRect(); if (!scene_rect.contains(rect)) painter->fillRect(rect, Qt::lightGray); // canvas if (m_image->isLoaded) { if (m_image->plotImageType() == DatapickerImage::OriginalImage) { QImage todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(scene_rect.topLeft(), todraw); } else if (m_image->plotImageType() == DatapickerImage::ProcessedImage) { QImage todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(scene_rect.topLeft(), todraw); } else { painter->fillRect(scene_rect, Qt::white); } } else { painter->setBrush(QBrush(Qt::gray)); painter->drawRect(scene_rect); } invalidateScene(rect, QGraphicsScene::BackgroundLayer); painter->restore(); } //############################################################################## //#################################### Events ############################### //############################################################################## void DatapickerImageView::wheelEvent(QWheelEvent* event) { //https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView if (m_mouseMode == ZoomSelectionMode || (QApplication::keyboardModifiers() & Qt::ControlModifier)) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 15; // see QWheelEvent documentation zoom(numSteps); } else QGraphicsView::wheelEvent(event); } void DatapickerImageView::zoom(int numSteps) { m_numScheduledScalings += numSteps; if (m_numScheduledScalings * numSteps < 0) // if user moved the wheel in another direction, we reset previously scheduled scalings m_numScheduledScalings = numSteps; auto* anim = new QTimeLine(350, this); anim->setUpdateInterval(20); connect(anim, &QTimeLine::valueChanged, this, &DatapickerImageView::scalingTime); connect(anim, &QTimeLine::finished, this, &DatapickerImageView::animFinished); anim->start(); } void DatapickerImageView::scalingTime() { qreal factor = 1.0 + qreal(m_numScheduledScalings) / 300.0; scale(factor, factor); } void DatapickerImageView::animFinished() { if (m_numScheduledScalings > 0) m_numScheduledScalings--; else m_numScheduledScalings++; sender()->~QObject(); } void DatapickerImageView::mousePressEvent(QMouseEvent* event) { //prevent the deselection of items when context menu event //was triggered (right button click) if (event->button() == Qt::RightButton) { event->accept(); return; } if (event->button() == Qt::LeftButton && m_mouseMode == ZoomSelectionMode) { m_selectionStart = event->pos(); m_selectionBandIsShown = true; return; } QPointF eventPos = mapToScene(event->pos()); if ( m_mouseMode == SelectAndEditMode && m_image->isLoaded && sceneRect().contains(eventPos) ) { if ( m_image->plotPointsType() == DatapickerImage::AxisPoints ) { int childCount = m_image->childCount(AbstractAspect::IncludeHidden); if (childCount < 3) m_datapicker->addNewPoint(eventPos, m_image); } else if ( m_image->plotPointsType() == DatapickerImage::CurvePoints && m_datapicker->activeCurve() ) { m_datapicker->addNewPoint(eventPos, m_datapicker->activeCurve()); } } // make sure the datapicker (or its currently active curve) is selected in the project explorer if the view was clicked. // 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. if (m_datapicker->activeCurve() && m_image->plotPointsType() != DatapickerImage::AxisPoints) { m_datapicker->setSelectedInView(false); m_datapicker->activeCurve()->setSelectedInView(true); } else { if (m_datapicker->activeCurve()) m_datapicker->activeCurve()->setSelectedInView(false); m_datapicker->setSelectedInView(true); } QGraphicsView::mousePressEvent(event); } void DatapickerImageView::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && m_mouseMode == ZoomSelectionMode) { m_selectionBandIsShown = false; viewport()->repaint(QRect(m_selectionStart, m_selectionEnd).normalized()); //don't zoom if very small region was selected, avoid occasional/unwanted zooming m_selectionEnd = event->pos(); if ( abs(m_selectionEnd.x()-m_selectionStart.x())>20 && abs(m_selectionEnd.y()-m_selectionStart.y())>20 ) fitInView(mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect(), Qt::KeepAspectRatio); } QGraphicsView::mouseReleaseEvent(event); } void DatapickerImageView::mouseMoveEvent(QMouseEvent* event) { if ( m_mouseMode == SelectAndEditMode || m_mouseMode == ZoomSelectionMode ) { if (m_image->isLoaded) setCursor(Qt::CrossCursor); else setCursor(Qt::ArrowCursor); } else { setCursor(Qt::ArrowCursor); } //show the selection band if (m_selectionBandIsShown) { QRect rect = QRect(m_selectionStart, m_selectionEnd).normalized(); m_selectionEnd = event->pos(); rect = rect.united(QRect(m_selectionStart, m_selectionEnd).normalized()); int penWidth = 5/transform().m11(); rect.setX(rect.x()-penWidth); rect.setY(rect.y()-penWidth); rect.setHeight(rect.height()+2*penWidth); rect.setWidth(rect.width()+2*penWidth); viewport()->repaint(rect); return; } QPointF pos = mapToScene(event->pos()); //show the current coordinates under the mouse cursor in the status bar if (m_image->plotPointsType() == DatapickerImage::CurvePoints) { QVector3D logicalPos = m_transform->mapSceneToLogical(pos, m_image->axisPoints()); if (m_image->axisPoints().type == DatapickerImage::Ternary) { emit statusInfo( "a =" + QString::number(logicalPos.x()) + ", b =" + QString::number(logicalPos.y()) + ", c =" + QString::number(logicalPos.z())); } else { QString xLabel('x'); QString yLabel('y'); if (m_image->axisPoints().type == DatapickerImage::PolarInDegree) { xLabel = 'r'; yLabel = "y(deg)"; } else if (m_image->axisPoints().type == DatapickerImage::PolarInRadians) { xLabel = 'r'; yLabel = "y(rad)"; } if (m_datapicker->activeCurve()) { QString statusText = i18n("%1, active curve \"%2\": %3=%4, %5=%6", m_datapicker->name(), m_datapicker->activeCurve()->name(), xLabel, QString::number(logicalPos.x()), yLabel, QString::number(logicalPos.y())); emit statusInfo(statusText); } } } //show the magnification window if ( magnificationFactor && m_mouseMode == SelectAndEditMode && m_image->isLoaded && sceneRect().contains(pos) && m_image->plotPointsType() != DatapickerImage::SegmentPoints ) { if (!m_image->m_magnificationWindow) { // m_image->m_magnificationWindow = new QGraphicsPixmapItem(0, scene()); m_image->m_magnificationWindow = new QGraphicsPixmapItem; scene()->addItem(m_image->m_magnificationWindow); m_image->m_magnificationWindow->setZValue(std::numeric_limits::max()); } m_image->m_magnificationWindow->setVisible(false); //copy the part of the view to be shown magnified const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Centimeter)/transform().m11(); const QRectF copyRect(pos.x() - size/(2*magnificationFactor), pos.y() - size/(2*magnificationFactor), size/magnificationFactor, size/magnificationFactor); QPixmap px = grab(mapFromScene(copyRect).boundingRect()); px = px.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); //draw the bounding rect QPainter painter(&px); const QPen pen = QPen(Qt::lightGray, 2/transform().m11()); painter.setPen(pen); QRect rect = px.rect(); rect.setWidth(rect.width()-pen.widthF()/2); rect.setHeight(rect.height()-pen.widthF()/2); painter.drawRect(rect); //set the pixmap m_image->m_magnificationWindow->setPixmap(px); m_image->m_magnificationWindow->setPos(pos.x()- px.width()/2, pos.y()- px.height()/2); m_image->m_magnificationWindow->setVisible(true); } else if (m_image->m_magnificationWindow) { m_image->m_magnificationWindow->setVisible(false); } QGraphicsView::mouseMoveEvent(event); } void DatapickerImageView::contextMenuEvent(QContextMenuEvent* e) { Q_UNUSED(e); //no need to propagate the event to the scene and graphics items QMenu *menu = new QMenu(this); this->createContextMenu(menu); menu->exec(QCursor::pos()); } //############################################################################## //#################################### SLOTs ############################### //############################################################################## void DatapickerImageView::changePointsType(QAction* action) { if (action == setAxisPointsAction) m_image->setPlotPointsType(DatapickerImage::AxisPoints); else if (action == setCurvePointsAction) m_image->setPlotPointsType(DatapickerImage::CurvePoints); else if (action == selectSegmentAction) m_image->setPlotPointsType(DatapickerImage::SegmentPoints); } void DatapickerImageView::changeZoom(QAction* action) { if (action == zoomInViewAction) zoom(1); else if (action == zoomOutViewAction) zoom(-1); else if (action == zoomOriginAction) { static const float hscale = QApplication::desktop()->physicalDpiX()/(25.4*Worksheet::convertToSceneUnits(1,Worksheet::Millimeter)); static const float vscale = QApplication::desktop()->physicalDpiY()/(25.4*Worksheet::convertToSceneUnits(1,Worksheet::Millimeter)); setTransform(QTransform::fromScale(hscale, vscale)); m_rotationAngle = 0; } else if (action == zoomFitPageWidthAction) { float scaleFactor = viewport()->width()/scene()->sceneRect().width(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); m_rotationAngle = 0; } else if (action == zoomFitPageHeightAction) { float scaleFactor = viewport()->height()/scene()->sceneRect().height(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); m_rotationAngle = 0; } currentZoomAction = action; if (tbZoom) tbZoom->setDefaultAction(action); //change and set angle if tranform reset changeRotationAngle(); } void DatapickerImageView::changeSelectedItemsPosition(QAction* action) { if (scene()->selectedItems().isEmpty()) return; QPointF shift(0, 0); if (action == shiftLeftAction) shift.setX(1); else if (action == shiftRightAction) shift.setX(-1); else if (action == shiftUpAction) shift.setY(-1); else if (action == shiftDownAction) shift.setY(1); m_image->beginMacro(i18n("%1: change position of selected DatapickerPoints.", m_image->name())); const QVector axisPoints = m_image->children(AbstractAspect::IncludeHidden); for (auto* point : axisPoints) { if (!point->graphicsItem()->isSelected()) continue; QPointF newPos = point->position(); newPos = newPos + shift; point->setPosition(newPos); int pointIndex = m_image->indexOfChild(point, AbstractAspect::IncludeHidden); if (pointIndex == -1) continue; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.scenePos[pointIndex].setX(point->position().x()); points.scenePos[pointIndex].setY(point->position().y()); m_image->setUndoAware(false); m_image->setAxisPoints(points); m_image->setUndoAware(true); } for (auto* curve : m_image->parentAspect()->children()) { for (auto* point : curve->children(AbstractAspect::IncludeHidden)) { if (!point->graphicsItem()->isSelected()) continue; QPointF newPos = point->position(); newPos = newPos + shift; point->setPosition(newPos); } } m_image->endMacro(); } void DatapickerImageView::mouseModeChanged(QAction* action) { if (action == selectAndEditModeAction) { m_mouseMode = SelectAndEditMode; setInteractive(true); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(true); } else if (action == navigationModeAction) { m_mouseMode = NavigationMode; setInteractive(false); setDragMode(QGraphicsView::ScrollHandDrag); m_image->setSegmentsHoverEvent(false); } else if (action == zoomSelectionModeAction) { m_mouseMode = ZoomSelectionMode; setInteractive(false); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(false); } else { m_mouseMode = SelectAndMoveMode; setInteractive(true); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(false); } } void DatapickerImageView::magnificationChanged(QAction* action) { if (action == noMagnificationAction) magnificationFactor = 0; else if (action == twoTimesMagnificationAction) magnificationFactor = 2; else if (action == threeTimesMagnificationAction) magnificationFactor = 3; else if (action == fourTimesMagnificationAction) magnificationFactor = 4; else if (action == fiveTimesMagnificationAction) magnificationFactor = 5; } void DatapickerImageView::addCurve() { m_datapicker->beginMacro(i18n("%1: add new curve.", m_datapicker->name())); DatapickerCurve* curve = new DatapickerCurve(i18n("Curve")); curve->addDatasheet(m_image->axisPoints().type); m_datapicker->addChild(curve); m_datapicker->endMacro(); } void DatapickerImageView::changeRotationAngle() { this->rotate(m_rotationAngle); this->rotate(-m_image->rotationAngle()); m_rotationAngle = m_image->rotationAngle(); updateBackground(); } void DatapickerImageView::handleImageActions() { if (m_image->isLoaded) { magnificationActionGroup->setEnabled(true); setAxisPointsAction->setEnabled(true); int pointsCount = m_image->childCount(AbstractAspect::IncludeHidden); if (pointsCount > 0) navigationActionGroup->setEnabled(true); else navigationActionGroup->setEnabled(false); if (pointsCount > 2) { addCurveAction->setEnabled(true); if (m_datapicker->activeCurve()) { setCurvePointsAction->setEnabled(true); selectSegmentAction->setEnabled(true); } else { setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); } } else { addCurveAction->setEnabled(false); setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); if (m_image->plotPointsType() != DatapickerImage::AxisPoints) { m_image->setUndoAware(false); m_image->setPlotPointsType(DatapickerImage::AxisPoints); m_image->setUndoAware(true); } } } else { navigationActionGroup->setEnabled(false); magnificationActionGroup->setEnabled(false); setAxisPointsAction->setEnabled(false); addCurveAction->setEnabled(false); setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); } } void DatapickerImageView::exportToFile(const QString& path, const WorksheetView::ExportFormat format, const int resolution) { QRectF sourceRect; sourceRect = scene()->sceneRect(); //print if (format == WorksheetView::Pdf) { QPrinter printer(QPrinter::HighResolution); printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName(path); int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); printer.setPaperSize( QSizeF(w, h), QPrinter::Millimeter); printer.setPageMargins(0,0,0,0, QPrinter::Millimeter); printer.setPrintRange(QPrinter::PageRange); printer.setCreator( QLatin1String("LabPlot ") + LVERSION ); QPainter painter(&printer); painter.setRenderHint(QPainter::Antialiasing); QRectF targetRect(0, 0, painter.device()->width(),painter.device()->height()); painter.begin(&printer); exportPaint(&painter, targetRect, sourceRect); painter.end(); } else if (format == WorksheetView::Svg) { QSvgGenerator generator; generator.setFileName(path); int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); w = w*QApplication::desktop()->physicalDpiX()/25.4; h = h*QApplication::desktop()->physicalDpiY()/25.4; generator.setSize(QSize(w, h)); QRectF targetRect(0, 0, w, h); generator.setViewBox(targetRect); QPainter painter; painter.begin(&generator); exportPaint(&painter, targetRect, sourceRect); painter.end(); } else { //PNG //TODO add all formats supported by Qt in QImage int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); w = w*resolution/25.4; h = h*resolution/25.4; QImage image(QSize(w, h), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); QRectF targetRect(0, 0, w, h); QPainter painter; painter.begin(&image); painter.setRenderHint(QPainter::Antialiasing); exportPaint(&painter, targetRect, sourceRect); painter.end(); image.save(path, "png"); } } void DatapickerImageView::exportPaint(QPainter* painter, const QRectF& targetRect, const QRectF& sourceRect) { painter->save(); painter->scale(targetRect.width()/sourceRect.width(), targetRect.height()/sourceRect.height()); drawBackground(painter, sourceRect); painter->restore(); m_image->setPrinting(true); scene()->render(painter, QRectF(), sourceRect); m_image->setPrinting(false); } void DatapickerImageView::print(QPrinter* printer) { const QRectF scene_rect = sceneRect(); int w = Worksheet::convertFromSceneUnits(scene_rect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(scene_rect.height(), Worksheet::Millimeter); printer->setPaperSize( QSizeF(w, h), QPrinter::Millimeter); printer->setPageMargins(0,0,0,0, QPrinter::Millimeter); printer->setPrintRange(QPrinter::PageRange); printer->setCreator( QString("LabPlot ") + LVERSION ); QPainter painter(printer); QRectF targetRect(0, 0, painter.device()->width(),painter.device()->height()); painter.setRenderHint(QPainter::Antialiasing); painter.begin(printer); painter.save(); painter.scale(targetRect.width()/scene_rect.width(), targetRect.height()/scene_rect.height()); // canvas if (m_image->isLoaded) { if (m_image->plotImageType() == DatapickerImage::OriginalImage) { QImage todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter.drawImage(scene_rect.topLeft(), todraw); } else if (m_image->plotImageType() == DatapickerImage::ProcessedImage) { QImage todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter.drawImage(scene_rect.topLeft(), todraw); } else { painter.fillRect(scene_rect, Qt::white); } } else { painter.setBrush(QBrush(Qt::gray)); painter.drawRect(scene_rect); } painter.restore(); m_image->setPrinting(true); scene()->render(&painter, QRectF(), scene_rect); m_image->setPrinting(false); painter.end(); } void DatapickerImageView::updateBackground() { invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); }