diff --git a/src/backend/datapicker/DatapickerCurve.cpp b/src/backend/datapicker/DatapickerCurve.cpp index 0acb0603c..524ad84da 100644 --- a/src/backend/datapicker/DatapickerCurve.cpp +++ b/src/backend/datapicker/DatapickerCurve.cpp @@ -1,568 +1,568 @@ /*************************************************************************** File : DatapickerCurve.cpp Project : LabPlot Description : container for Curve-Point and Datasheet/Spreadsheet of datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2019 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 "DatapickerCurve.h" #include "backend/datapicker/DatapickerCurvePrivate.h" #include "backend/datapicker/Datapicker.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include #include #include #include #include /** * \class DatapickerCurve * \brief Top-level container for Curve-Point and Datasheet/Spreadsheet of datapicker. * \ingroup backend */ DatapickerCurve::DatapickerCurve(const QString &name) : AbstractAspect(name, AspectType::DatapickerCurve), d_ptr(new DatapickerCurvePrivate(this)) { init(); } DatapickerCurve::DatapickerCurve(const QString &name, DatapickerCurvePrivate *dd) : AbstractAspect(name, AspectType::DatapickerCurve), d_ptr(dd) { init(); } DatapickerCurve::~DatapickerCurve() { delete d_ptr; } void DatapickerCurve::init() { Q_D(DatapickerCurve); KConfig config; KConfigGroup group; group = config.group("DatapickerCurve"); - d->curveErrorTypes.x = (ErrorType) group.readEntry("CurveErrorType_X", (int) NoError); - d->curveErrorTypes.y = (ErrorType) group.readEntry("CurveErrorType_X", (int) NoError); + d->curveErrorTypes.x = (ErrorType) group.readEntry("CurveErrorType_X", static_cast(ErrorType::NoError)); + d->curveErrorTypes.y = (ErrorType) group.readEntry("CurveErrorType_Y", static_cast(ErrorType::NoError)); // point properties d->pointStyle = (Symbol::Style)group.readEntry("PointStyle", (int)Symbol::Cross); - d->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Point)); + d->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Unit::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->pointErrorBarSize = group.readEntry("ErrorBarSize", Worksheet::convertToSceneUnits(8, Worksheet::Point)); + d->pointPen.setWidthF( group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Unit::Point)) ); + d->pointErrorBarSize = group.readEntry("ErrorBarSize", Worksheet::convertToSceneUnits(8, Worksheet::Unit::Point)); d->pointErrorBarBrush.setStyle( (Qt::BrushStyle)group.readEntry("ErrorBarFillingStyle", (int)Qt::NoBrush) ); d->pointErrorBarBrush.setColor( group.readEntry("ErrorBarFillingColor", QColor(Qt::black)) ); d->pointErrorBarPen.setStyle( (Qt::PenStyle)group.readEntry("ErrorBarBorderStyle", (int)Qt::SolidLine) ); d->pointErrorBarPen.setColor( group.readEntry("ErrorBarBorderColor", QColor(Qt::black)) ); - d->pointErrorBarPen.setWidthF( group.readEntry("ErrorBarBorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Point)) ); + d->pointErrorBarPen.setWidthF( group.readEntry("ErrorBarBorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Unit::Point)) ); d->pointVisibility = group.readEntry("PointVisibility", true); } /*! Returns an icon to be used in the project explorer. */ QIcon DatapickerCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } Column* DatapickerCurve::appendColumn(const QString& name) { Column* col = new Column(i18n("Column"), AbstractColumn::ColumnMode::Numeric); col->insertRows(0, m_datasheet->rowCount()); col->setName(name); m_datasheet->addChild(col); return col; } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(DatapickerCurve, DatapickerCurve::Errors, curveErrorTypes, curveErrorTypes) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, Symbol::Style, pointStyle, pointStyle) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointOpacity, pointOpacity) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointRotationAngle, pointRotationAngle) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointSize, pointSize) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QBrush, pointBrush, pointBrush) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QPen, pointPen, pointPen) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointErrorBarSize, pointErrorBarSize) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QBrush, pointErrorBarBrush, pointErrorBarBrush) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QPen, pointErrorBarPen, pointErrorBarPen) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, bool, pointVisibility, pointVisibility) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posXColumn, posXColumn) QString& DatapickerCurve::posXColumnPath() const { return d_ptr->posXColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posYColumn, posYColumn) QString& DatapickerCurve::posYColumnPath() const { return d_ptr->posYColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posZColumn, posZColumn) QString& DatapickerCurve::posZColumnPath() const { return d_ptr->posZColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, plusDeltaXColumn, plusDeltaXColumn) QString& DatapickerCurve::plusDeltaXColumnPath() const { return d_ptr->plusDeltaXColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, minusDeltaXColumn, minusDeltaXColumn) QString& DatapickerCurve::minusDeltaXColumnPath() const { return d_ptr->minusDeltaXColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, plusDeltaYColumn, plusDeltaYColumn) QString& DatapickerCurve::plusDeltaYColumnPath() const { return d_ptr->plusDeltaYColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, minusDeltaYColumn, minusDeltaYColumn) QString& DatapickerCurve::minusDeltaYColumnPath() const { return d_ptr->minusDeltaYColumnPath; } //############################################################################## //######################### setter methods ################################### //############################################################################## void DatapickerCurve::addDatasheet(DatapickerImage::GraphType type) { Q_D(DatapickerCurve); m_datasheet = new Spreadsheet(i18n("Data")); addChild(m_datasheet); QString xLabel('x'); QString yLabel('y'); - if (type == DatapickerImage::PolarInDegree) { + if (type == DatapickerImage::GraphType::PolarInDegree) { xLabel = QLatin1String("r"); yLabel = QLatin1String("y(deg)"); - } else if (type == DatapickerImage::PolarInRadians) { + } else if (type == DatapickerImage::GraphType::PolarInRadians) { xLabel = QLatin1String("r"); yLabel = QLatin1String("y(rad)"); - } else if (type == DatapickerImage::LogarithmicX) { + } else if (type == DatapickerImage::GraphType::LogarithmicX) { xLabel = QLatin1String("log(x)"); yLabel = QLatin1String("y"); - } else if (type == DatapickerImage::LogarithmicY) { + } else if (type == DatapickerImage::GraphType::LogarithmicY) { xLabel = QLatin1String("x"); yLabel = QLatin1String("log(y)"); } - if (type == DatapickerImage::Ternary) + if (type == DatapickerImage::GraphType::Ternary) d->posZColumn = appendColumn(i18n("c")); d->posXColumn = m_datasheet->column(0); d->posXColumn->setName(xLabel); d->posYColumn = m_datasheet->column(1); d->posYColumn->setName(yLabel); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetCurveErrorTypes, DatapickerCurve::Errors, curveErrorTypes) void DatapickerCurve::setCurveErrorTypes(const DatapickerCurve::Errors errors) { Q_D(DatapickerCurve); if (d->curveErrorTypes.x != errors.x || d->curveErrorTypes.y != errors.y) { beginMacro(i18n("%1: set xy-error type", name())); exec(new DatapickerCurveSetCurveErrorTypesCmd(d, errors, ki18n("%1: set xy-error type"))); - if ( errors.x != NoError && !d->plusDeltaXColumn ) + if ( errors.x != ErrorType::NoError && !d->plusDeltaXColumn ) setPlusDeltaXColumn(appendColumn(QLatin1String("+delta_x"))); - else if ( d->plusDeltaXColumn && errors.x == NoError ) { + else if ( d->plusDeltaXColumn && errors.x ==ErrorType:: NoError ) { d->plusDeltaXColumn->remove(); d->plusDeltaXColumn = nullptr; } - if ( errors.x == AsymmetricError && !d->minusDeltaXColumn ) + if ( errors.x == ErrorType::AsymmetricError && !d->minusDeltaXColumn ) setMinusDeltaXColumn(appendColumn(QLatin1String("-delta_x"))); - else if ( d->minusDeltaXColumn && errors.x != AsymmetricError ) { + else if ( d->minusDeltaXColumn && errors.x != ErrorType::AsymmetricError ) { d->minusDeltaXColumn->remove(); d->minusDeltaXColumn = nullptr; } - if ( errors.y != NoError && !d->plusDeltaYColumn ) + if ( errors.y != ErrorType::NoError && !d->plusDeltaYColumn ) setPlusDeltaYColumn(appendColumn(QLatin1String("+delta_y"))); - else if ( d->plusDeltaYColumn && errors.y == NoError ) { + else if ( d->plusDeltaYColumn && errors.y == ErrorType::NoError ) { d->plusDeltaYColumn->remove(); d->plusDeltaYColumn = nullptr; } - if ( errors.y == AsymmetricError && !d->minusDeltaYColumn ) + if ( errors.y == ErrorType::AsymmetricError && !d->minusDeltaYColumn ) setMinusDeltaYColumn(appendColumn(QLatin1String("-delta_y"))); - else if ( d->minusDeltaYColumn && errors.y != AsymmetricError ) { + else if ( d->minusDeltaYColumn && errors.y != ErrorType::AsymmetricError ) { d->minusDeltaYColumn->remove(); d->minusDeltaYColumn = nullptr; } endMacro(); } } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosXColumn, AbstractColumn*, posXColumn) void DatapickerCurve::setPosXColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->posXColumn != column) exec(new DatapickerCurveSetPosXColumnCmd(d, column, ki18n("%1: set position X column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosYColumn, AbstractColumn*, posYColumn) void DatapickerCurve::setPosYColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->posYColumn != column) exec(new DatapickerCurveSetPosYColumnCmd(d, column, ki18n("%1: set position Y column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosZColumn, AbstractColumn*, posZColumn) void DatapickerCurve::setPosZColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->posZColumn != column) exec(new DatapickerCurveSetPosZColumnCmd(d, column, ki18n("%1: set position Z column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPlusDeltaXColumn, AbstractColumn*, plusDeltaXColumn) void DatapickerCurve::setPlusDeltaXColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->plusDeltaXColumn != column) exec(new DatapickerCurveSetPlusDeltaXColumnCmd(d, column, ki18n("%1: set +delta_X column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetMinusDeltaXColumn, AbstractColumn*, minusDeltaXColumn) void DatapickerCurve::setMinusDeltaXColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->minusDeltaXColumn != column) exec(new DatapickerCurveSetMinusDeltaXColumnCmd(d, column, ki18n("%1: set -delta_X column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPlusDeltaYColumn, AbstractColumn*, plusDeltaYColumn) void DatapickerCurve::setPlusDeltaYColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->plusDeltaYColumn != column) exec(new DatapickerCurveSetPlusDeltaYColumnCmd(d, column, ki18n("%1: set +delta_Y column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetMinusDeltaYColumn, AbstractColumn*, minusDeltaYColumn) void DatapickerCurve::setMinusDeltaYColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->minusDeltaYColumn != column) exec(new DatapickerCurveSetMinusDeltaYColumnCmd(d, column, ki18n("%1: set -delta_Y column"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointStyle, Symbol::Style, pointStyle, retransform) void DatapickerCurve::setPointStyle(Symbol::Style newStyle) { Q_D(DatapickerCurve); if (newStyle != d->pointStyle) exec(new DatapickerCurveSetPointStyleCmd(d, newStyle, ki18n("%1: set point's style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointSize, qreal, pointSize, retransform) void DatapickerCurve::setPointSize(qreal value) { Q_D(DatapickerCurve); if (!qFuzzyCompare(1 + value, 1 + d->pointSize)) exec(new DatapickerCurveSetPointSizeCmd(d, value, ki18n("%1: set point's size"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointRotationAngle, qreal, pointRotationAngle, retransform) void DatapickerCurve::setPointRotationAngle(qreal angle) { Q_D(DatapickerCurve); if (!qFuzzyCompare(1 + angle, 1 + d->pointRotationAngle)) exec(new DatapickerCurveSetPointRotationAngleCmd(d, angle, ki18n("%1: rotate point"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointBrush, QBrush, pointBrush, retransform) void DatapickerCurve::setPointBrush(const QBrush& newBrush) { Q_D(DatapickerCurve); if (newBrush != d->pointBrush) exec(new DatapickerCurveSetPointBrushCmd(d, newBrush, ki18n("%1: set point's filling"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointPen, QPen, pointPen, retransform) void DatapickerCurve::setPointPen(const QPen &newPen) { Q_D(DatapickerCurve); if (newPen != d->pointPen) exec(new DatapickerCurveSetPointPenCmd(d, newPen, ki18n("%1: set outline style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointOpacity, qreal, pointOpacity, retransform) void DatapickerCurve::setPointOpacity(qreal newOpacity) { Q_D(DatapickerCurve); if (newOpacity != d->pointOpacity) exec(new DatapickerCurveSetPointOpacityCmd(d, newOpacity, ki18n("%1: set point's opacity"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarSize, qreal, pointErrorBarSize, retransform) void DatapickerCurve::setPointErrorBarSize(qreal size) { Q_D(DatapickerCurve); if (size != d->pointErrorBarSize) exec(new DatapickerCurveSetPointErrorBarSizeCmd(d, size, ki18n("%1: set error bar size"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarBrush, QBrush, pointErrorBarBrush, retransform) void DatapickerCurve::setPointErrorBarBrush(const QBrush &brush) { Q_D(DatapickerCurve); if (brush != d->pointErrorBarBrush) exec(new DatapickerCurveSetPointErrorBarBrushCmd(d, brush, ki18n("%1: set error bar filling"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarPen, QPen, pointErrorBarPen, retransform) void DatapickerCurve::setPointErrorBarPen(const QPen &pen) { Q_D(DatapickerCurve); if (pen != d->pointErrorBarPen) exec(new DatapickerCurveSetPointErrorBarPenCmd(d, pen, ki18n("%1: set error bar outline style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointVisibility, bool, pointVisibility, retransform) void DatapickerCurve::setPointVisibility(bool on) { Q_D(DatapickerCurve); if (on != d->pointVisibility) exec(new DatapickerCurveSetPointVisibilityCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } void DatapickerCurve::setPrinting(bool on) { for (auto* point : children(AbstractAspect::ChildIndexFlag::IncludeHidden)) point->setPrinting(on); } /*! Selects or deselects the Datapicker/Curve in the project explorer. This function is called in \c DatapickerImageView. */ void DatapickerCurve::setSelectedInView(bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void DatapickerCurve::updatePoints() { for (auto* point : children(ChildIndexFlag::IncludeHidden)) updatePoint(point); } /*! Update datasheet for corresponding curve-point, it is called every time whenever there is any change in position of curve-point or its error-bar so keep it undo unaware no need to create extra entry in undo stack */ void DatapickerCurve::updatePoint(const DatapickerPoint* point) { Q_D(DatapickerCurve); //TODO: this check shouldn't be required. //redesign the retransform()-call in load() to avoid it. if (!parentAspect()) return; auto* datapicker = static_cast(parentAspect()); int row = indexOfChild(point, ChildIndexFlag::IncludeHidden); QVector3D data = datapicker->mapSceneToLogical(point->position()); if (d->posXColumn) d->posXColumn->setValueAt(row, data.x()); if (d->posYColumn) d->posYColumn->setValueAt(row, data.y()); if (d->posZColumn) d->posZColumn->setValueAt(row, data.y()); if (d->plusDeltaXColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(point->plusDeltaXPos().x(), 0)); d->plusDeltaXColumn->setValueAt(row, qAbs(data.x())); } if (d->minusDeltaXColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(point->minusDeltaXPos().x(), 0)); d->minusDeltaXColumn->setValueAt(row, qAbs(data.x())); } if (d->plusDeltaYColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(0, point->plusDeltaYPos().y())); d->plusDeltaYColumn->setValueAt(row, qAbs(data.y())); } if (d->minusDeltaYColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(0, point->minusDeltaYPos().y())); d->minusDeltaYColumn->setValueAt(row, qAbs(data.y())); } } //############################################################################## //####################### Private implementation ############################### //############################################################################## DatapickerCurvePrivate::DatapickerCurvePrivate(DatapickerCurve *curve) : q(curve) { } QString DatapickerCurvePrivate::name() const { return q->name(); } void DatapickerCurvePrivate::retransform() { auto points = q->children(AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* point : points) point->retransform(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void DatapickerCurve::save(QXmlStreamWriter* writer) const { Q_D(const DatapickerCurve); writer->writeStartElement("datapickerCurve"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); WRITE_COLUMN(d->posXColumn, posXColumn); WRITE_COLUMN(d->posYColumn, posYColumn); WRITE_COLUMN(d->posZColumn, posZColumn); WRITE_COLUMN(d->plusDeltaXColumn, plusDeltaXColumn); WRITE_COLUMN(d->minusDeltaXColumn, minusDeltaXColumn); WRITE_COLUMN(d->plusDeltaYColumn, plusDeltaYColumn); WRITE_COLUMN(d->minusDeltaYColumn, minusDeltaYColumn); - writer->writeAttribute( "curveErrorType_X", QString::number(d->curveErrorTypes.x) ); - writer->writeAttribute( "curveErrorType_Y", QString::number(d->curveErrorTypes.y) ); + writer->writeAttribute( "curveErrorType_X", QString::number(static_cast(d->curveErrorTypes.x)) ); + writer->writeAttribute( "curveErrorType_Y", QString::number(static_cast(d->curveErrorTypes.y)) ); 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(); //error bar properties writer->writeStartElement("errorBarProperties"); writer->writeAttribute( "pointErrorBarSize", QString::number(d->pointErrorBarSize) ); WRITE_QBRUSH(d->pointErrorBarBrush); WRITE_QPEN(d->pointErrorBarPen); writer->writeEndElement(); //serialize all children for (auto* child : children(ChildIndexFlag::IncludeHidden)) child->save(writer); writer->writeEndElement(); // close section } //! Load from XML bool DatapickerCurve::load(XmlStreamReader* reader, bool preview) { Q_D(DatapickerCurve); 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() == "datapickerCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_INT_VALUE("curveErrorType_X", curveErrorTypes.x, ErrorType); READ_INT_VALUE("curveErrorType_Y", curveErrorTypes.y, ErrorType); READ_COLUMN(posXColumn); READ_COLUMN(posYColumn); READ_COLUMN(posZColumn); READ_COLUMN(plusDeltaXColumn); READ_COLUMN(minusDeltaXColumn); READ_COLUMN(plusDeltaYColumn); READ_COLUMN(minusDeltaYColumn); } 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 (!preview && reader->name() == "errorBarProperties") { attribs = reader->attributes(); READ_DOUBLE_VALUE("pointErrorBarSize", pointErrorBarSize); READ_QBRUSH(d->pointErrorBarBrush); READ_QPEN(d->pointErrorBarPen); } else if (reader->name() == "datapickerPoint") { DatapickerPoint* curvePoint = new DatapickerPoint(QString()); curvePoint->setHidden(true); if (!curvePoint->load(reader, preview)) { delete curvePoint; return false; } else { addChild(curvePoint); curvePoint->initErrorBar(curveErrorTypes()); } } else if (reader->name() == "spreadsheet") { Spreadsheet* datasheet = new Spreadsheet("spreadsheet", true); if (!datasheet->load(reader, preview)) { delete datasheet; return false; } else { addChild(datasheet); m_datasheet = datasheet; } } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } d->retransform(); return true; } diff --git a/src/backend/datapicker/DatapickerCurve.h b/src/backend/datapicker/DatapickerCurve.h index b6419fc59..7057fee40 100644 --- a/src/backend/datapicker/DatapickerCurve.h +++ b/src/backend/datapicker/DatapickerCurve.h @@ -1,128 +1,128 @@ /*************************************************************************** File : DatapickerCurve.h Project : LabPlot Description : container for Curve-Point and Datasheet/Spreadsheet of datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2019 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 * * * ***************************************************************************/ #ifndef DATAPICKERCURVE_H #define DATAPICKERCURVE_H #include "backend/core/AbstractAspect.h" #include "backend/lib/macros.h" #include "backend/datapicker/DatapickerImage.h" #include "backend/worksheet/plots/cartesian/Symbol.h" class DatapickerPoint; class QAction; class DatapickerCurvePrivate; class Column; class Spreadsheet; class AbstractColumn; class DatapickerCurve: public AbstractAspect { Q_OBJECT public: explicit DatapickerCurve(const QString&); ~DatapickerCurve() override; - enum ErrorType {NoError, SymmetricError, AsymmetricError}; + enum class ErrorType {NoError, SymmetricError, AsymmetricError}; struct Errors { ErrorType x; ErrorType y; }; QIcon icon() const override; void setPrinting(bool); void setSelectedInView(bool); void addDatasheet(DatapickerImage::GraphType); void updatePoints(); void updatePoint(const DatapickerPoint*); BASIC_D_ACCESSOR_DECL(Errors, curveErrorTypes, CurveErrorTypes) BASIC_D_ACCESSOR_DECL(Symbol::Style, pointStyle, PointStyle) BASIC_D_ACCESSOR_DECL(qreal, pointOpacity, PointOpacity) BASIC_D_ACCESSOR_DECL(qreal, pointRotationAngle, PointRotationAngle) BASIC_D_ACCESSOR_DECL(qreal, pointSize, PointSize) CLASS_D_ACCESSOR_DECL(QBrush, pointBrush, PointBrush) CLASS_D_ACCESSOR_DECL(QPen, pointPen, PointPen) BASIC_D_ACCESSOR_DECL(qreal, pointErrorBarSize, PointErrorBarSize) CLASS_D_ACCESSOR_DECL(QBrush, pointErrorBarBrush, PointErrorBarBrush) CLASS_D_ACCESSOR_DECL(QPen, pointErrorBarPen, PointErrorBarPen) BASIC_D_ACCESSOR_DECL(bool, pointVisibility, PointVisibility) POINTER_D_ACCESSOR_DECL(AbstractColumn, posXColumn, PosXColumn) QString& posXColumnPath() const; POINTER_D_ACCESSOR_DECL(AbstractColumn, posYColumn, PosYColumn) QString& posYColumnPath() const; POINTER_D_ACCESSOR_DECL(AbstractColumn, posZColumn, PosZColumn) QString& posZColumnPath() const; POINTER_D_ACCESSOR_DECL(AbstractColumn, plusDeltaXColumn, PlusDeltaXColumn) QString& plusDeltaXColumnPath() const; POINTER_D_ACCESSOR_DECL(AbstractColumn, minusDeltaXColumn, MinusDeltaXColumn) QString& minusDeltaXColumnPath() const; POINTER_D_ACCESSOR_DECL(AbstractColumn, plusDeltaYColumn, PlusDeltaYColumn) QString& plusDeltaYColumnPath() const; POINTER_D_ACCESSOR_DECL(AbstractColumn, minusDeltaYColumn, MinusDeltaYColumn) QString& minusDeltaYColumnPath() const; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; typedef DatapickerCurvePrivate Private; protected: DatapickerCurve(const QString& name, DatapickerCurvePrivate* dd); DatapickerCurvePrivate* const d_ptr; private: Q_DECLARE_PRIVATE(DatapickerCurve) void init(); void initAction(); Column* appendColumn(const QString&); Spreadsheet* m_datasheet{nullptr}; signals: void curveErrorTypesChanged(const DatapickerCurve::Errors&); void posXColumnChanged(const AbstractColumn*); void posYColumnChanged(const AbstractColumn*); void posZColumnChanged(const AbstractColumn*); void plusDeltaXColumnChanged(const AbstractColumn*); void minusDeltaXColumnChanged(const AbstractColumn*); void plusDeltaYColumnChanged(const AbstractColumn*); void minusDeltaYColumnChanged(const AbstractColumn*); void pointStyleChanged(Symbol::Style); void pointSizeChanged(qreal); void pointRotationAngleChanged(qreal); void pointOpacityChanged(qreal); void pointBrushChanged(QBrush); void pointPenChanged(const QPen&); void pointErrorBarSizeChanged(qreal); void pointErrorBarBrushChanged(QBrush); void pointErrorBarPenChanged(const QPen&); void pointVisibilityChanged(bool); }; #endif // DATAPICKERCURVE_H diff --git a/src/backend/datapicker/DatapickerImage.cpp b/src/backend/datapicker/DatapickerImage.cpp index edcb04b96..aea83d771 100644 --- a/src/backend/datapicker/DatapickerImage.cpp +++ b/src/backend/datapicker/DatapickerImage.cpp @@ -1,685 +1,685 @@ /*************************************************************************** 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 "backend/lib/trace.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]), + foregroundBins( new int[ImageEditor::colorAttributeMax(ColorAttributes::Foreground) + 1]), + hueBins( new int[ImageEditor::colorAttributeMax(ColorAttributes::Hue) + 1]), + saturationBins( new int[ImageEditor::colorAttributeMax(ColorAttributes::Saturation) + 1]), + valueBins( new int[ImageEditor::colorAttributeMax(ColorAttributes::Value) + 1]), + intensityBins( new int[ImageEditor::colorAttributeMax(ColorAttributes::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.type = (DatapickerImage::GraphType) group.readEntry("GraphType", static_cast(DatapickerImage::GraphType::Cartesian)); d->axisPoints.ternaryScale = group.readEntry("TernaryScale", 1); //edit image settings - d->plotImageType = DatapickerImage::OriginalImage; + d->plotImageType = DatapickerImage::PlotImageType::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); // 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->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Unit::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->pointPen.setWidthF( group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Unit::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) + if (d->plotImageType == DatapickerImage::PlotImageType::ProcessedImage) d->discretize(); emit requestUpdate(); } DatapickerImage::PlotImageType DatapickerImage::plotImageType() { return d->plotImageType; } /* =============================== 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 { auto points = parentAspect()->children(ChildIndexFlag::Recursive | ChildIndexFlag::IncludeHidden); for (auto* point : points) point->setPrinting(on); } void DatapickerImage::setPlotPointsType(const PointsType pointsType) { if (d->plotPointsType == pointsType) return; d->plotPointsType = pointsType; - if (pointsType == DatapickerImage::AxisPoints) { + if (pointsType == DatapickerImage::PointsType::AxisPoints) { //clear image auto points = children(ChildIndexFlag::IncludeHidden); if (!points.isEmpty()) { beginMacro(i18n("%1: remove all axis points", name())); for (auto* point : points) point->remove(); endMacro(); } m_segments->setSegmentsVisible(false); - } else if (pointsType == DatapickerImage::CurvePoints) + } else if (pointsType == DatapickerImage::PointsType::CurvePoints) m_segments->setSegmentsVisible(false); - else if (pointsType == DatapickerImage::SegmentPoints) { + else if (pointsType == DatapickerImage::PointsType::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, 1000, 1000), m_scene(new QGraphicsScene(pageRect)) { } QString DatapickerImagePrivate::name() const { return q->name(); } void DatapickerImagePrivate::retransform() { auto points = q->children(AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* point : points) 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); + ImageEditor::uploadHistogram(q->intensityBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Intensity); + ImageEditor::uploadHistogram(q->foregroundBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Foreground); + ImageEditor::uploadHistogram(q->hueBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Hue); + ImageEditor::uploadHistogram(q->saturationBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Saturation); + ImageEditor::uploadHistogram(q->valueBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::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(); + double w = Worksheet::convertToSceneUnits(q->originalPlotImage.width(), Worksheet::Unit::Inch)/QApplication::desktop()->physicalDpiX(); + double h = Worksheet::convertToSceneUnits(q->originalPlotImage.height(), Worksheet::Unit::Inch)/QApplication::desktop()->physicalDpiX(); m_scene->setSceneRect(0, 0, w, h); q->isLoaded = true; } return rc; } void DatapickerImagePrivate::discretize() { PERFTRACE("DatapickerImagePrivate::discretize()"); - if (plotImageType != DatapickerImage::ProcessedImage) + if (plotImageType != DatapickerImage::PlotImageType::ProcessedImage) return; ImageEditor::discretize(&q->processedPlotImage, &q->originalPlotImage, settings, q->background); - if (plotPointsType != DatapickerImage::SegmentPoints) + if (plotPointsType != DatapickerImage::PointsType::SegmentPoints) emit q->requestUpdate(); else makeSegments(); } void DatapickerImagePrivate::makeSegments() { - if (plotPointsType != DatapickerImage::SegmentPoints) + if (plotPointsType != DatapickerImage::PointsType::SegmentPoints) return; PERFTRACE("DatapickerImagePrivate::makeSegments()"); 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)) fileName = address; } else { //hide segments if they are visible q->m_segments->setSegmentsVisible(false); } auto points = q->parentAspect()->children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); if (!points.isEmpty()) { for (auto* point : points) 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); //general properties writer->writeStartElement( "general" ); writer->writeAttribute( "fileName", d->fileName ); - writer->writeAttribute( "plotPointsType", QString::number(d->plotPointsType) ); + writer->writeAttribute( "plotPointsType", QString::number(static_cast(d->plotPointsType)) ); writer->writeEndElement(); writer->writeStartElement( "axisPoint" ); - writer->writeAttribute( "graphType", QString::number(d->axisPoints.type) ); + writer->writeAttribute( "graphType", QString::number(static_cast(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( "plotImageType", QString::number(static_cast(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(ChildIndexFlag::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 (!preview && reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); d->fileName = str; READ_INT_VALUE("plotPointsType", plotPointsType, DatapickerImage::PointsType); } 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/backend/datapicker/DatapickerImage.h b/src/backend/datapicker/DatapickerImage.h index bd1c24213..89bf9a545 100644 --- a/src/backend/datapicker/DatapickerImage.h +++ b/src/backend/datapicker/DatapickerImage.h @@ -1,159 +1,159 @@ /*************************************************************************** File : DatapickerImage.h Project : LabPlot Description : Worksheet 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 * * * ***************************************************************************/ #ifndef DATAPICKERIMAGE_H #define DATAPICKERIMAGE_H #include "backend/core/AbstractPart.h" #include "backend/lib/macros.h" #include "backend/worksheet/plots/cartesian/Symbol.h" #include #include class QImage; class QBrush; class DatapickerImagePrivate; class DatapickerImageView; class ImageEditor; class Segments; class QGraphicsScene; class QGraphicsPixmapItem; class DatapickerImage : public AbstractPart { Q_OBJECT public: explicit DatapickerImage(const QString& name, bool loading = false); ~DatapickerImage() override; - enum GraphType { Cartesian, PolarInDegree, PolarInRadians, LogarithmicX, LogarithmicY, Ternary}; - enum ColorAttributes { None, Intensity, Foreground, Hue, Saturation, Value }; - enum PlotImageType { NoImage, OriginalImage, ProcessedImage }; - enum PointsType { AxisPoints, CurvePoints, SegmentPoints }; + enum class GraphType {Cartesian, PolarInDegree, PolarInRadians, LogarithmicX, LogarithmicY, Ternary}; + enum class ColorAttributes {None, Intensity, Foreground, Hue, Saturation, Value}; + enum class PlotImageType {NoImage, OriginalImage, ProcessedImage}; + enum class PointsType {AxisPoints, CurvePoints, SegmentPoints}; struct ReferencePoints { - GraphType type{Cartesian}; + GraphType type{GraphType::Cartesian}; QPointF scenePos[3]; QVector3D logicalPos[3]; double ternaryScale{1.0}; }; struct EditorSettings { int intensityThresholdLow{20}; int intensityThresholdHigh{100}; int foregroundThresholdLow{30}; int foregroundThresholdHigh{90}; int hueThresholdLow{0}; int hueThresholdHigh{360}; int saturationThresholdLow{30}; int saturationThresholdHigh{100}; int valueThresholdLow{30}; int valueThresholdHigh{90}; }; QIcon icon() const override; QMenu* createContextMenu() override; void createContextMenu(QMenu*); QWidget* view() const override; bool exportView() const override; bool printView() override; bool printPreview() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; QRectF pageRect() const; void setPageRect(const QRectF&); QGraphicsScene *scene() const; void setPrinting(bool) const; void setSelectedInView(const bool); void setSegmentsHoverEvent(const bool); void setPlotImageType(const DatapickerImage::PlotImageType); DatapickerImage::PlotImageType plotImageType(); bool isLoaded{false}; QImage originalPlotImage; QImage processedPlotImage; QColor background; int *foregroundBins; int *hueBins; int *saturationBins; int *valueBins; int *intensityBins; QGraphicsPixmapItem* m_magnificationWindow{nullptr}; CLASS_D_ACCESSOR_DECL(QString, fileName, FileName) CLASS_D_ACCESSOR_DECL(DatapickerImage::ReferencePoints, axisPoints, AxisPoints) CLASS_D_ACCESSOR_DECL(DatapickerImage::EditorSettings, settings, Settings) BASIC_D_ACCESSOR_DECL(float, rotationAngle, RotationAngle) BASIC_D_ACCESSOR_DECL(PointsType, plotPointsType, PlotPointsType) BASIC_D_ACCESSOR_DECL(int, pointSeparation, PointSeparation) BASIC_D_ACCESSOR_DECL(int, minSegmentLength, minSegmentLength) BASIC_D_ACCESSOR_DECL(Symbol::Style, pointStyle, PointStyle) BASIC_D_ACCESSOR_DECL(qreal, pointOpacity, PointOpacity) BASIC_D_ACCESSOR_DECL(qreal, pointRotationAngle, PointRotationAngle) BASIC_D_ACCESSOR_DECL(qreal, pointSize, PointSize) CLASS_D_ACCESSOR_DECL(QBrush, pointBrush, PointBrush) CLASS_D_ACCESSOR_DECL(QPen, pointPen, PointPen) BASIC_D_ACCESSOR_DECL(bool, pointVisibility, PointVisibility) typedef DatapickerImagePrivate Private; private: void init(); DatapickerImagePrivate* const d; mutable DatapickerImageView* m_view{nullptr}; friend class DatapickerImagePrivate; Segments* m_segments; signals: void requestProjectContextMenu(QMenu*); void requestUpdate(); void requestUpdateActions(); void fileNameChanged(const QString&); void rotationAngleChanged(float); void axisPointsChanged(const DatapickerImage::ReferencePoints&); void settingsChanged(const DatapickerImage::EditorSettings&); void minSegmentLengthChanged(const int); void pointStyleChanged(Symbol::Style); void pointSizeChanged(qreal); void pointRotationAngleChanged(qreal); void pointOpacityChanged(qreal); void pointBrushChanged(QBrush); void pointPenChanged(const QPen&); void pointVisibilityChanged(bool); }; #endif diff --git a/src/backend/datapicker/DatapickerImagePrivate.h b/src/backend/datapicker/DatapickerImagePrivate.h index 327f41dd9..e8cd27dd5 100644 --- a/src/backend/datapicker/DatapickerImagePrivate.h +++ b/src/backend/datapicker/DatapickerImagePrivate.h @@ -1,68 +1,68 @@ /*************************************************************************** File : DatapickerImagePrivate.h Project : LabPlot Description : Worksheet for Datapicker, private class -------------------------------------------------------------------- Copyright : (C) 2015-2016 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 * * * ***************************************************************************/ #ifndef DATAPICKERIMAGEPRIVATE_H #define DATAPICKERIMAGEPRIVATE_H class QBrush; class QGraphicsScene; class DatapickerImagePrivate { public: explicit DatapickerImagePrivate(DatapickerImage*); virtual ~DatapickerImagePrivate(); DatapickerImage::ReferencePoints axisPoints; DatapickerImage::EditorSettings settings; - DatapickerImage::PointsType plotPointsType{DatapickerImage::AxisPoints}; - DatapickerImage::PlotImageType plotImageType{DatapickerImage::NoImage}; + DatapickerImage::PointsType plotPointsType{DatapickerImage::PointsType::AxisPoints}; + DatapickerImage::PlotImageType plotImageType{DatapickerImage::PlotImageType::NoImage}; DatapickerImage* const q; QRectF pageRect; QGraphicsScene* m_scene; float rotationAngle{0.0}; QString fileName; int pointSeparation{30}; int minSegmentLength{30}; qreal pointRotationAngle{0.0}; Symbol::Style pointStyle; QBrush pointBrush; QPen pointPen; qreal pointOpacity; qreal pointSize; bool pointVisibility{true}; QString name() const; void retransform(); void updateFileName(); void discretize(); void makeSegments(); bool uploadImage(const QString&); }; #endif diff --git a/src/backend/datapicker/DatapickerPoint.cpp b/src/backend/datapicker/DatapickerPoint.cpp index 271ee5080..b6fc2a3ac 100644 --- a/src/backend/datapicker/DatapickerPoint.cpp +++ b/src/backend/datapicker/DatapickerPoint.cpp @@ -1,581 +1,581 @@ /*************************************************************************** File : DatapickerPoint.cpp Project : LabPlot Description : Graphic Item for coordinate points of Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2019 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 "DatapickerPoint.h" #include "backend/worksheet/Worksheet.h" #include "DatapickerPointPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datapicker/DatapickerCurve.h" #include #include #include #include #include #include #include /** * \class ErrorBarItem * \brief A customizable error-bar for DatapickerPoint. */ ErrorBarItem::ErrorBarItem(DatapickerPoint* parent, ErrorBarType type) : QGraphicsRectItem(parent->graphicsItem()), barLineItem(new QGraphicsLineItem(parent->graphicsItem())), m_type(type), m_parentItem(parent) { setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); initRect(); setAcceptHoverEvents(true); } void ErrorBarItem::initRect() { QRectF xBarRect(-0.15, -0.5, 0.3, 1); QRectF yBarRect(-0.5, -0.15, 1, 0.3); - if (m_type == PlusDeltaX || m_type == MinusDeltaX) + if (m_type == ErrorBarType::PlusDeltaX || m_type == ErrorBarType::MinusDeltaX) m_rect = xBarRect; else m_rect = yBarRect; } void ErrorBarItem::setPosition(QPointF position) { setPos(position); barLineItem->setLine(0, 0, position.x(), position.y()); } void ErrorBarItem::setRectSize(qreal size) { QMatrix matrix; matrix.scale(size, size); setRect(matrix.mapRect(m_rect)); } void ErrorBarItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { - if (m_type == PlusDeltaX) + if (m_type == ErrorBarType::PlusDeltaX) m_parentItem->setPlusDeltaXPos(pos()); - else if (m_type == MinusDeltaX) + else if (m_type == ErrorBarType::MinusDeltaX) m_parentItem->setMinusDeltaXPos(pos()); - else if (m_type == PlusDeltaY) + else if (m_type == ErrorBarType::PlusDeltaY) m_parentItem->setPlusDeltaYPos(pos()); - else if (m_type == MinusDeltaY) + else if (m_type == ErrorBarType::MinusDeltaY) m_parentItem->setMinusDeltaYPos(pos()); QGraphicsItem::mouseReleaseEvent(event); } void ErrorBarItem::hoverEnterEvent(QGraphicsSceneHoverEvent*) { - if (m_type == PlusDeltaX ||m_type == MinusDeltaX) + if (m_type == ErrorBarType::PlusDeltaX ||m_type == ErrorBarType::MinusDeltaX) setCursor(Qt::SizeHorCursor); else setCursor(Qt::SizeVerCursor); } QVariant ErrorBarItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { QPointF newPos = value.toPointF(); - if (m_type == PlusDeltaX || m_type == MinusDeltaX) { + if (m_type == ErrorBarType::PlusDeltaX || m_type == ErrorBarType::MinusDeltaX) { newPos.setY(0); barLineItem->setLine(0, 0, newPos.x(), 0); } else { newPos.setX(0); barLineItem->setLine(0, 0, 0, newPos.y()); } return QGraphicsRectItem::itemChange(change, newPos); } return QGraphicsRectItem::itemChange(change, value); } /** * \class Datapicker-Point * \brief A customizable symbol supports error-bars. * * The datapicker-Point is aligned relative to the specified position. * The position can be either specified by mouse events or by providing the * x- and y- coordinates in parent's coordinate system, or by specifying one * of the predefined position flags (\c HorizontalPosition, \c VerticalPosition). */ DatapickerPoint::DatapickerPoint(const QString& name) : AbstractAspect(name, AspectType::DatapickerPoint), d_ptr(new DatapickerPointPrivate(this)) { init(); } DatapickerPoint::DatapickerPoint(const QString& name, DatapickerPointPrivate *dd) : AbstractAspect(name, AspectType::DatapickerPoint), d_ptr(dd) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene DatapickerPoint::~DatapickerPoint() = default; void DatapickerPoint::init() { Q_D(DatapickerPoint); KConfig config; KConfigGroup group; group = config.group("DatapickerPoint"); - d->position.setX( group.readEntry("PositionXValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); - d->position.setY( group.readEntry("PositionYValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); + d->position.setX( group.readEntry("PositionXValue", Worksheet::convertToSceneUnits(1, Worksheet::Unit::Centimeter)) ); + d->position.setY( group.readEntry("PositionYValue", Worksheet::convertToSceneUnits(1, Worksheet::Unit::Centimeter)) ); d->plusDeltaXPos = group.readEntry("PlusDeltaXPos", QPointF(30, 0)); d->minusDeltaXPos = group.readEntry("MinusDeltaXPos", QPointF(-30, 0)); d->plusDeltaYPos = group.readEntry("PlusDeltaYPos", QPointF(0, -30)); d->minusDeltaYPos = group.readEntry("MinusDeltaYPos", QPointF(0, 30)); } void DatapickerPoint::initErrorBar(DatapickerCurve::Errors errors) { m_errorBarItemList.clear(); - if (errors.x != DatapickerCurve::NoError) { - ErrorBarItem* plusDeltaXItem = new ErrorBarItem(this, ErrorBarItem::PlusDeltaX); + if (errors.x != DatapickerCurve::ErrorType::NoError) { + auto* plusDeltaXItem = new ErrorBarItem(this, ErrorBarItem::ErrorBarType::PlusDeltaX); plusDeltaXItem->setPosition(plusDeltaXPos()); connect(this, &DatapickerPoint::plusDeltaXPosChanged, plusDeltaXItem, &ErrorBarItem::setPosition); - ErrorBarItem* minusDeltaXItem = new ErrorBarItem(this, ErrorBarItem::MinusDeltaX); + auto* minusDeltaXItem = new ErrorBarItem(this, ErrorBarItem::ErrorBarType::MinusDeltaX); minusDeltaXItem->setPosition(minusDeltaXPos()); connect(this, &DatapickerPoint::minusDeltaXPosChanged, minusDeltaXItem, &ErrorBarItem::setPosition); m_errorBarItemList<setPosition(plusDeltaYPos()); connect(this, &DatapickerPoint::plusDeltaYPosChanged, plusDeltaYItem, &ErrorBarItem::setPosition); - ErrorBarItem* minusDeltaYItem = new ErrorBarItem(this, ErrorBarItem::MinusDeltaY); + auto* minusDeltaYItem = new ErrorBarItem(this, ErrorBarItem::ErrorBarType::MinusDeltaY); minusDeltaYItem->setPosition(minusDeltaYPos()); connect(this, &DatapickerPoint::minusDeltaYPosChanged, minusDeltaYItem, &ErrorBarItem::setPosition); m_errorBarItemList<setParentItem(item); } void DatapickerPoint::retransform() { Q_D(DatapickerPoint); d->retransform(); } /* ============================ getter methods ================= */ //point CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, position, position) //error-bar CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, plusDeltaXPos, plusDeltaXPos) CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, minusDeltaXPos, minusDeltaXPos) CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, plusDeltaYPos, plusDeltaYPos) CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, minusDeltaYPos, minusDeltaYPos) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetPosition, QPointF, position, retransform) void DatapickerPoint::setPosition(QPointF pos) { Q_D(DatapickerPoint); if (pos != d->position) exec(new DatapickerPointSetPositionCmd(d, pos, ki18n("%1: set position"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetPlusDeltaXPos, QPointF, plusDeltaXPos, updatePoint) void DatapickerPoint::setPlusDeltaXPos(QPointF pos) { Q_D(DatapickerPoint); if (pos != d->plusDeltaXPos) { auto* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set +delta_X position", name())); - if (curve->curveErrorTypes().x == DatapickerCurve::SymmetricError) { + if (curve->curveErrorTypes().x == DatapickerCurve::ErrorType::SymmetricError) { exec(new DatapickerPointSetPlusDeltaXPosCmd(d, pos, ki18n("%1: set +delta X position"))); setMinusDeltaXPos(QPointF(-qAbs(pos.x()), pos.y())); } else exec(new DatapickerPointSetPlusDeltaXPosCmd(d, pos, ki18n("%1: set +delta X position"))); endMacro(); } } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetMinusDeltaXPos, QPointF, minusDeltaXPos, updatePoint) void DatapickerPoint::setMinusDeltaXPos(QPointF pos) { Q_D(DatapickerPoint); if (pos != d->minusDeltaXPos) { auto* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set -delta_X position", name())); - if (curve->curveErrorTypes().x == DatapickerCurve::SymmetricError) { + if (curve->curveErrorTypes().x == DatapickerCurve::ErrorType::SymmetricError) { exec(new DatapickerPointSetMinusDeltaXPosCmd(d, pos, ki18n("%1: set -delta_X position"))); setPlusDeltaXPos(QPointF(qAbs(pos.x()), pos.y())); } else exec(new DatapickerPointSetMinusDeltaXPosCmd(d, pos, ki18n("%1: set -delta_X position"))); endMacro(); } } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetPlusDeltaYPos, QPointF, plusDeltaYPos, updatePoint) void DatapickerPoint::setPlusDeltaYPos(QPointF pos) { Q_D(DatapickerPoint); if (pos != d->plusDeltaYPos) { auto* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set +delta_Y position", name())); - if (curve->curveErrorTypes().y == DatapickerCurve::SymmetricError) { + if (curve->curveErrorTypes().y == DatapickerCurve::ErrorType::SymmetricError) { exec(new DatapickerPointSetPlusDeltaYPosCmd(d, pos, ki18n("%1: set +delta_Y position"))); setMinusDeltaYPos(QPointF(pos.x(), qAbs(pos.y()))); } else exec(new DatapickerPointSetPlusDeltaYPosCmd(d, pos, ki18n("%1: set +delta_Y position"))); endMacro(); } } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetMinusDeltaYPos, QPointF, minusDeltaYPos, updatePoint) void DatapickerPoint::setMinusDeltaYPos(QPointF pos) { Q_D(DatapickerPoint); if (pos != d->minusDeltaYPos) { auto* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set -delta_Y position", name())); - if (curve->curveErrorTypes().y == DatapickerCurve::SymmetricError) { + if (curve->curveErrorTypes().y == DatapickerCurve::ErrorType::SymmetricError) { exec(new DatapickerPointSetMinusDeltaYPosCmd(d, pos, ki18n("%1: set -delta_Y position"))); setPlusDeltaYPos(QPointF(pos.x(), -qAbs(pos.y()))); } else exec(new DatapickerPointSetMinusDeltaYPosCmd(d, pos, ki18n("%1: set -delta_Y position"))); endMacro(); } } void DatapickerPoint::setPrinting(bool on) { Q_D(DatapickerPoint); d->m_printing = on; } //############################################################################## //####################### Private implementation ############################### //############################################################################## DatapickerPointPrivate::DatapickerPointPrivate(DatapickerPoint* owner) : q(owner) { setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); } QString DatapickerPointPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the item/point. Called on geometry or properties changes. */ void DatapickerPointPrivate::retransform() { updatePropeties(); setPos(position); QPainterPath path = Symbol::pathFromStyle(pointStyle); boundingRectangle = path.boundingRect(); recalcShapeAndBoundingRect(); retransformErrorBar(); updatePoint(); } /*! update color and size of all error-bar. */ void DatapickerPointPrivate::retransformErrorBar() { for (auto* item : q->m_errorBarItemList) { if (item) { item->setBrush(errorBarBrush); item->setPen(errorBarPen); item->setRectSize(errorBarSize); } } } /*! update datasheet on any change in position of Datapicker-Point or it's error-bar. */ void DatapickerPointPrivate::updatePoint() { auto* curve = dynamic_cast(q->parentAspect()); if (curve) curve->updatePoint(q); } void DatapickerPointPrivate::updatePropeties() { auto* curve = dynamic_cast(q->parentAspect()); auto* image = dynamic_cast(q->parentAspect()); if (image) { rotationAngle = image->pointRotationAngle(); pointStyle = image->pointStyle(); brush = image->pointBrush(); pen = image->pointPen(); opacity = image->pointOpacity(); size = image->pointSize(); setVisible(image->pointVisibility()); } else if (curve) { rotationAngle = curve->pointRotationAngle(); pointStyle = curve->pointStyle(); brush = curve->pointBrush(); pen = curve->pointPen(); opacity = curve->pointOpacity(); size = curve->pointSize(); errorBarBrush = curve->pointErrorBarBrush(); errorBarPen = curve->pointErrorBarPen(); errorBarSize = curve->pointErrorBarSize(); setVisible(curve->pointVisibility()); } } /*! Returns the outer bounds of the item as a rectangle. */ QRectF DatapickerPointPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath DatapickerPointPrivate::shape() const { return itemShape; } /*! recalculates the outer bounds and the shape of the item. */ void DatapickerPointPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); QMatrix matrix; matrix.scale(size, size); matrix.rotate(-rotationAngle); transformedBoundingRectangle = matrix.mapRect(boundingRectangle); itemShape = QPainterPath(); itemShape.addRect(transformedBoundingRectangle); itemShape = WorksheetElement::shapeFromPath(itemShape, pen); } void DatapickerPointPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { q->setPosition(pos()); QGraphicsItem::mouseReleaseEvent(event); } void DatapickerPointPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { setCursor(Qt::ArrowCursor); } void DatapickerPointPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { setCursor(Qt::CrossCursor); } void DatapickerPointPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) QPainterPath path = Symbol::pathFromStyle(pointStyle); QTransform trafo; trafo.scale(size, size); path = trafo.map(path); trafo.reset(); if (rotationAngle != 0) { trafo.rotate(-rotationAngle); path = trafo.map(path); } painter->save(); painter->setPen(pen); painter->setBrush(brush); painter->setOpacity(opacity); painter->drawPath(path); painter->restore(); if (isSelected() && !m_printing) { //TODO: move the initialization of QPen to a parent class later so we don't //need to create it in every paint() call. painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 1, Qt::SolidLine)); painter->drawPath(itemShape); } } void DatapickerPointPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void DatapickerPoint::save(QXmlStreamWriter* writer) const { Q_D(const DatapickerPoint); writer->writeStartElement( "datapickerPoint" ); writeBasicAttributes(writer); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.x()) ); writer->writeAttribute( "y", QString::number(d->position.y()) ); writer->writeEndElement(); auto* curve = dynamic_cast(parentAspect()); - if (curve && (curve->curveErrorTypes().x != DatapickerCurve::NoError - || curve->curveErrorTypes().y != DatapickerCurve::NoError)) { + if (curve && (curve->curveErrorTypes().x != DatapickerCurve::ErrorType::NoError + || curve->curveErrorTypes().y != DatapickerCurve::ErrorType::NoError)) { writer->writeStartElement( "errorBar" ); writer->writeAttribute( "plusDeltaXPos_x", QString::number(d->plusDeltaXPos.x()) ); writer->writeAttribute( "plusDeltaXPos_y", QString::number(d->plusDeltaXPos.y()) ); writer->writeAttribute( "minusDeltaXPos_x", QString::number(d->minusDeltaXPos.x()) ); writer->writeAttribute( "minusDeltaXPos_y", QString::number(d->minusDeltaXPos.y()) ); writer->writeAttribute( "plusDeltaYPos_x", QString::number(d->plusDeltaYPos.x()) ); writer->writeAttribute( "plusDeltaYPos_y", QString::number(d->plusDeltaYPos.y()) ); writer->writeAttribute( "minusDeltaYPos_x", QString::number(d->minusDeltaYPos.x()) ); writer->writeAttribute( "minusDeltaYPos_y", QString::number(d->minusDeltaYPos.y()) ); writer->writeEndElement(); } writer->writeEndElement(); // close "DatapickerPoint" section } //! Load from XML bool DatapickerPoint::load(XmlStreamReader* reader, bool preview) { Q_D(DatapickerPoint); 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() == "datapickerPoint") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->position.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->position.setY(str.toDouble()); } else if (!preview && reader->name() == "errorBar") { attribs = reader->attributes(); str = attribs.value("plusDeltaXPos_x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("plusDeltaXPos_x").toString()); else d->plusDeltaXPos.setX(str.toDouble()); str = attribs.value("plusDeltaXPos_y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("plusDeltaXPos_y").toString()); else d->plusDeltaXPos.setY(str.toDouble()); str = attribs.value("minusDeltaXPos_x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("minusDeltaXPos_x").toString()); else d->minusDeltaXPos.setX(str.toDouble()); str = attribs.value("minusDeltaXPos_y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("minusDeltaXPos_y").toString()); else d->minusDeltaXPos.setY(str.toDouble()); str = attribs.value("plusDeltaYPos_x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("plusDeltaYPos_x").toString()); else d->plusDeltaYPos.setX(str.toDouble()); str = attribs.value("plusDeltaYPos_y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("plusDeltaYPos_y").toString()); else d->plusDeltaYPos.setY(str.toDouble()); str = attribs.value("minusDeltaYPos_x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("minusDeltaYPos_x").toString()); else d->minusDeltaYPos.setX(str.toDouble()); str = attribs.value("minusDeltaYPos_y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("minusDeltaYPos_y").toString()); else d->minusDeltaYPos.setY(str.toDouble()); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } retransform(); return true; } diff --git a/src/backend/datapicker/DatapickerPoint.h b/src/backend/datapicker/DatapickerPoint.h index 6496927e2..80f4cc2b4 100644 --- a/src/backend/datapicker/DatapickerPoint.h +++ b/src/backend/datapicker/DatapickerPoint.h @@ -1,110 +1,110 @@ /*************************************************************************** File : DatapickerPoint.h Project : LabPlot Description : Graphic Item for coordinate points of Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2019 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 * * * ***************************************************************************/ #ifndef DATAPICKERPOINT_H #define DATAPICKERPOINT_H #include "backend/datapicker/DatapickerCurve.h" #include "backend/lib/macros.h" #include //TODO: own file class ErrorBarItem : public QObject, public QGraphicsRectItem { Q_OBJECT public: - enum ErrorBarType {PlusDeltaX, MinusDeltaX, PlusDeltaY, MinusDeltaY}; + enum class ErrorBarType {PlusDeltaX, MinusDeltaX, PlusDeltaY, MinusDeltaY}; - explicit ErrorBarItem(DatapickerPoint* parent = nullptr, ErrorBarType type = PlusDeltaX); + explicit ErrorBarItem(DatapickerPoint* parent = nullptr, ErrorBarType type = ErrorBarType::PlusDeltaX); void setRectSize(const qreal); public slots: void setPosition(QPointF); private: void initRect(); void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; QVariant itemChange(GraphicsItemChange, const QVariant &value) override; QGraphicsLineItem* barLineItem; QRectF m_rect; ErrorBarType m_type; DatapickerPoint* m_parentItem; }; class DatapickerPointPrivate; class DatapickerPoint : public AbstractAspect { Q_OBJECT public: explicit DatapickerPoint(const QString& name); ~DatapickerPoint() override; QIcon icon() const override; QMenu* createContextMenu() override; QGraphicsItem* graphicsItem() const; void setParentGraphicsItem(QGraphicsItem*); void setPrinting(bool); void initErrorBar(DatapickerCurve::Errors); void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; BASIC_D_ACCESSOR_DECL(QPointF, position, Position) BASIC_D_ACCESSOR_DECL(QPointF, plusDeltaXPos, PlusDeltaXPos) BASIC_D_ACCESSOR_DECL(QPointF, minusDeltaXPos, MinusDeltaXPos) BASIC_D_ACCESSOR_DECL(QPointF, plusDeltaYPos, PlusDeltaYPos) BASIC_D_ACCESSOR_DECL(QPointF, minusDeltaYPos, MinusDeltaYPos) typedef DatapickerPointPrivate Private; public slots: void retransform(); protected: DatapickerPointPrivate* const d_ptr; DatapickerPoint(const QString &name, DatapickerPointPrivate *dd); static QPen selectedPen; static float selectedOpacity; private: Q_DECLARE_PRIVATE(DatapickerPoint) void init(); QList m_errorBarItemList; signals: void positionChanged(QPointF); void plusDeltaXPosChanged(QPointF); void minusDeltaXPosChanged(QPointF); void plusDeltaYPosChanged(QPointF); void minusDeltaYPosChanged(QPointF); }; #endif diff --git a/src/backend/datapicker/ImageEditor.cpp b/src/backend/datapicker/ImageEditor.cpp index 2680975b0..c69ecd825 100644 --- a/src/backend/datapicker/ImageEditor.cpp +++ b/src/backend/datapicker/ImageEditor.cpp @@ -1,352 +1,352 @@ /*************************************************************************** File : ImageEditor.cpp Project : LabPlot Description : Edit Image on the basis of input color attributes -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2016 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 "ImageEditor.h" #include #include #include #include static const QRgb white = QColor(Qt::white).rgb(); static const QRgb black = QColor(Qt::black).rgb(); //Intensity, Foreground, Saturation and Value are from 0 to 100, Hue is from 0 to 360 static const int maxIntensity = 100; static const int maxForeground = 100; static const int maxHue = 360; static const int maxSaturation = 100; static const int maxValue = 100; QMutex mutex; class DiscretizeTask : public QRunnable { public: DiscretizeTask(int start, int end, QImage* plotImage, QImage* originalImage, const DatapickerImage::EditorSettings& settings, QColor background) : m_start(start), m_end(end), m_plotImage(plotImage), m_originalImage(originalImage), m_settings(settings), m_background(std::move(background)) {}; void run() override { for (int y = m_start; y < m_end; ++y) { mutex.lock(); QRgb* line = reinterpret_cast(m_plotImage->scanLine(y)); mutex.unlock(); for (int x = 0; x < m_plotImage->width(); ++x) { int value = ImageEditor::discretizeHue(x, y, m_originalImage); - if (!ImageEditor::pixelIsOn(value, DatapickerImage::Hue, m_settings)) + if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Hue, m_settings)) continue; value = ImageEditor::discretizeSaturation(x, y, m_originalImage); - if (!ImageEditor::pixelIsOn(value, DatapickerImage::Saturation, m_settings)) + if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Saturation, m_settings)) continue; value = ImageEditor::discretizeValue(x, y, m_originalImage); - if (!ImageEditor::pixelIsOn(value, DatapickerImage::Value, m_settings)) + if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Value, m_settings)) continue; value = ImageEditor::discretizeIntensity(x, y, m_originalImage); - if (!ImageEditor::pixelIsOn(value, DatapickerImage::Saturation, m_settings)) + if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Saturation, m_settings)) continue; value = ImageEditor::discretizeForeground(x, y, m_background, m_originalImage); - if (!ImageEditor::pixelIsOn(value, DatapickerImage::Foreground, m_settings)) + if (!ImageEditor::pixelIsOn(value, DatapickerImage::ColorAttributes::Foreground, m_settings)) continue; line[x] = black; } } } private: int m_start; int m_end; QImage* m_plotImage; QImage* m_originalImage; DatapickerImage::EditorSettings m_settings; QColor m_background; }; /*! * */ void ImageEditor::discretize(QImage* plotImage, QImage* originalImage, const DatapickerImage::EditorSettings& settings, QColor background) { plotImage->fill(white); QThreadPool* pool = QThreadPool::globalInstance(); int range = ceil(double(plotImage->height())/pool->maxThreadCount()); for (int i = 0; i < pool->maxThreadCount(); ++i) { const int start = i*range; int end = (i+1)*range; if (end > plotImage->height()) end = plotImage->height(); auto* task = new DiscretizeTask(start, end, plotImage, originalImage, settings, background); pool->start(task); } pool->waitForDone(); } bool ImageEditor::processedPixelIsOn(const QImage& plotImage, int x, int y) { if ((x < 0) || (plotImage.width() <= x) || (y < 0) || (plotImage.height() <= y)) return false; // pixel is on if it is closer to black than white in gray scale. this test must be performed // on little endian and big endian systems, with or without alpha bits (which are typically high bits) const int BLACK_WHITE_THRESHOLD = 255 / 2; // put threshold in middle of range int gray = qGray(plotImage.pixel(x, y)); return (gray < BLACK_WHITE_THRESHOLD); } //############################################################################## //##################### private helper functions ############################# //############################################################################## QRgb ImageEditor::findBackgroundColor(const QImage* plotImage) { ColorList::iterator itrC; ColorList colors; int x, y = 0; for (x = 0; x < plotImage->width(); ++x) { ColorEntry c; c.color = plotImage->pixel(x,y); c.count = 0; bool found = false; for (itrC = colors.begin(); itrC != colors.end(); ++itrC) { if (colorCompare(c.color.rgb(), (*itrC).color.rgb())) { found = true; ++(*itrC).count; break; } } if (!found) colors.append(c); if (++y >= plotImage->height()) y = 0; } ColorEntry cMax; cMax.count = 0; for (itrC = colors.begin(); itrC != colors.end(); ++itrC) { if ((*itrC).count > cMax.count) cMax = (*itrC); } return cMax.color.rgb(); } void ImageEditor::uploadHistogram(int* bins, QImage* originalImage, QColor background, DatapickerImage::ColorAttributes type) { //reset bin for (int i = 0; i <= colorAttributeMax(type); ++i) bins [i] = 0; for (int x = 0; x < originalImage->width(); ++x) { for (int y = 0; y < originalImage->height(); ++y) { int value = discretizeValueForeground(x, y, type, background, originalImage); bins[value] += 1; } } } int ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes type) { //Intensity, Foreground, Saturation and Value are from 0 to 100 //Hue is from 0 to 360 switch (type) { - case DatapickerImage::None: + case DatapickerImage::ColorAttributes::None: return 0; - case DatapickerImage::Intensity: + case DatapickerImage::ColorAttributes::Intensity: return 100; - case DatapickerImage::Foreground: + case DatapickerImage::ColorAttributes::Foreground: return 100; - case DatapickerImage::Hue: + case DatapickerImage::ColorAttributes::Hue: return 360; - case DatapickerImage::Saturation: + case DatapickerImage::ColorAttributes::Saturation: return 100; - case DatapickerImage::Value: + case DatapickerImage::ColorAttributes::Value: default: return 100; } } bool ImageEditor::colorCompare(QRgb color1, QRgb color2) { const long MASK = 0xf0f0f0f0; return (color1 & MASK) == (color2 & MASK); } int ImageEditor::discretizeHue(int x, int y, const QImage* originalImage) { const QColor color(originalImage->pixel(x,y)); const int h = color.hue(); int value = h * maxHue / 359; if (value < 0) //QColor::hue() can return -1 value = 0; if (maxHue < value) value = maxHue; return value; } int ImageEditor::discretizeSaturation(int x, int y, const QImage* originalImage) { const QColor color(originalImage->pixel(x,y)); const int s = color.saturation(); int value = s * maxSaturation / 255; if (maxSaturation < value) value = maxSaturation; return value; } int ImageEditor::discretizeValue(int x, int y, const QImage* originalImage) { const QColor color(originalImage->pixel(x,y)); const int v = color.value(); int value = v * maxValue / 255; if (maxValue < value) value = maxValue; return value; } int ImageEditor::discretizeIntensity(int x, int y, const QImage* originalImage) { const QRgb color = originalImage->pixel(x,y); const int r = qRed(color); const int g = qGreen(color); const int b = qBlue(color); const double intensity = sqrt ((double) (r * r + g * g + b * b)); int value = (int) (intensity * maxIntensity / sqrt((double) (255 * 255 + 255 * 255 + 255 * 255)) + 0.5); if (maxIntensity < value) value = maxIntensity; return value; } int ImageEditor::discretizeForeground(int x, int y, const QColor background, const QImage* originalImage) { const QRgb color = originalImage->pixel(x,y); const int r = qRed(color); const int g = qGreen(color); const int b = qBlue(color); const int rBg = background.red(); const int gBg = background.green(); const int bBg = background.blue(); const double distance = sqrt ((double) ((r - rBg) * (r - rBg) + (g - gBg) * (g - gBg) + (b - bBg) * (b - bBg))); int value = (int) (distance * maxForeground / sqrt((double) (255 * 255 + 255 * 255 + 255 * 255)) + 0.5); if (maxForeground < value) value = maxForeground; return value; } int ImageEditor::discretizeValueForeground(int x, int y, DatapickerImage::ColorAttributes type, const QColor background, const QImage* originalImage) { const QColor color(originalImage->pixel(x,y)); // convert hue from 0 to 359, saturation from 0 to 255, value from 0 to 255 int value = 0; switch (type) { - case DatapickerImage::None: + case DatapickerImage::ColorAttributes::None: break; - case DatapickerImage::Intensity: { + case DatapickerImage::ColorAttributes::Intensity: { const int r = color.red(); const int g = color.green(); const int b = color.blue(); const double intensity = sqrt ((double) (r * r + g * g + b * b)); value = (int) (intensity * maxIntensity / sqrt((double) (255 * 255 + 255 * 255 + 255 * 255)) + 0.5); if (maxIntensity < value) value = maxIntensity; break; } - case DatapickerImage::Foreground: { + case DatapickerImage::ColorAttributes::Foreground: { const int r = color.red(); const int g = color.green(); const int b = color.blue(); const int rBg = background.red(); const int gBg = background.green(); const int bBg = background.blue(); const double distance = sqrt ((double) ((r - rBg) * (r - rBg) + (g - gBg) * (g - gBg) + (b - bBg) * (b - bBg))); value = (int) (distance * maxForeground / sqrt((double) (255 * 255 + 255 * 255 + 255 * 255)) + 0.5); if (maxForeground < value) value = maxForeground; break; } - case DatapickerImage::Hue: { + case DatapickerImage::ColorAttributes::Hue: { const int h = color.hue(); value = h * maxHue / 359; if (value < 0) value = 0; if (maxHue < value) value = maxHue; break; } - case DatapickerImage::Saturation: { + case DatapickerImage::ColorAttributes::Saturation: { const int s = color.saturation(); value = s * maxSaturation / 255; if (maxSaturation < value) value = maxSaturation; break; } - case DatapickerImage::Value: { + case DatapickerImage::ColorAttributes::Value: { const int v = color.value(); value = v * maxValue / 255; if (maxValue < value) value = maxValue; break; } } return value; } bool ImageEditor::pixelIsOn(int value, int low, int high) { if (low < high) return ((low <= value) && (value <= high)); else return ((low <= value) || (value <= high)); } bool ImageEditor::pixelIsOn( int value, DatapickerImage::ColorAttributes type, const DatapickerImage::EditorSettings& settings ) { switch (type) { - case DatapickerImage::None: + case DatapickerImage::ColorAttributes::None: break; - case DatapickerImage::Intensity: + case DatapickerImage::ColorAttributes::Intensity: return pixelIsOn(value, settings.intensityThresholdLow, settings.intensityThresholdHigh); - case DatapickerImage::Foreground: + case DatapickerImage::ColorAttributes::Foreground: return pixelIsOn(value, settings.foregroundThresholdLow, settings.foregroundThresholdHigh); - case DatapickerImage::Hue: + case DatapickerImage::ColorAttributes::Hue: return pixelIsOn(value, settings.hueThresholdLow, settings.hueThresholdHigh); - case DatapickerImage::Saturation: + case DatapickerImage::ColorAttributes::Saturation: return pixelIsOn(value, settings.saturationThresholdLow, settings.saturationThresholdHigh); - case DatapickerImage::Value: + case DatapickerImage::ColorAttributes::Value: return pixelIsOn(value, settings.valueThresholdLow, settings.valueThresholdHigh); } return false; } diff --git a/src/backend/datapicker/Segment.cpp b/src/backend/datapicker/Segment.cpp index c268ecc69..a845d5212 100644 --- a/src/backend/datapicker/Segment.cpp +++ b/src/backend/datapicker/Segment.cpp @@ -1,199 +1,199 @@ /*************************************************************************** File : Segment.cpp Project : LabPlot Description : Graphics-item for curve of Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) ***************************************************************************/ /*************************************************************************** * * * 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 "Segment.h" #include "SegmentPrivate.h" #include "backend/datapicker/DatapickerImage.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/worksheet/Worksheet.h" #include "backend/datapicker/Datapicker.h" #include #include #include #include /** * \class Segment * \brief graphics-item class for curve-segment */ Segment::Segment(DatapickerImage* image) : m_image(image), d_ptr(new SegmentPrivate(this)) { m_image->scene()->addItem(this->graphicsItem()); } QGraphicsItem* Segment::graphicsItem() const { return d_ptr; } void Segment::setParentGraphicsItem(QGraphicsItem* item) { Q_D(Segment); d->setParentItem(item); } void Segment::retransform() { Q_D(Segment); d->retransform(); } bool Segment::isVisible() const { Q_D(const Segment); return d->isVisible(); } void Segment::setVisible(bool on) { Q_D(Segment); d->setVisible(on); } //############################################################################## //####################### Private implementation ############################### //############################################################################## SegmentPrivate::SegmentPrivate(Segment *owner) : - scaleFactor(Worksheet::convertToSceneUnits(1, Worksheet::Inch)/QApplication::desktop()->physicalDpiX()), + scaleFactor(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)/QApplication::desktop()->physicalDpiX()), q(owner) { setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setAcceptHoverEvents(true); setVisible(false); pen = QPen(Qt::green, 3, Qt::SolidLine); } /*! calculates the position and the bounding box of the item. Called on geometry or properties changes. */ void SegmentPrivate::retransform() { QMatrix matrix; matrix.scale(scaleFactor, scaleFactor); for (auto* line : q->path) { const QLine& scaledLine = matrix.map(*line); linePath.moveTo(scaledLine.p1()); linePath.lineTo(scaledLine.p2()); } recalcShapeAndBoundingRect(); } /*! Returns the outer bounds of the item as a rectangle. */ QRectF SegmentPrivate::boundingRect() const { return boundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath SegmentPrivate::shape() const { return itemShape; } /*! recalculates the outer bounds and the shape of the item. */ void SegmentPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); boundingRectangle = linePath.boundingRect(); itemShape = QPainterPath(); itemShape.addRect(boundingRectangle); } void SegmentPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) painter->setPen(pen); painter->drawPath(linePath); if (m_hovered && !isSelected()) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(linePath); } // if (isSelected()) { // painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); // painter->setOpacity(selectedOpacity); // painter->drawPath(itemShape); // } } void SegmentPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; update(); } } void SegmentPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; update(); } } QVariant SegmentPrivate::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemSelectedChange && value == true) { auto* datapicker = static_cast(q->m_image->parentAspect()); if (datapicker->activeCurve()) { int count = 0; QList posList; posList.clear(); for (QLine* line : q->path) { const int l = (line->y1() > line->y2())?line->y2():line->y1(); const int h = (line->y1() > line->y2())?line->y1():line->y2(); for (int i = l; i <= h; ++i) { if (count%q->m_image->pointSeparation() == 0) { bool positionUsed = false; const auto points = datapicker->activeCurve()->children(AbstractAspect::ChildIndexFlag::IncludeHidden); for (const auto* point : points) { if (point->position() == QPoint(line->x1(), i)*scaleFactor) positionUsed = true; } if (!positionUsed) posList<x1(), i)*scaleFactor; } count++; } } if (!posList.isEmpty()) { datapicker->activeCurve()->beginMacro(i18n("%1: draw points over segment", datapicker->activeCurve()->name())); for (const QPointF& pos : posList) datapicker->addNewPoint(pos, datapicker->activeCurve()); datapicker->activeCurve()->endMacro(); } } //no need to keep segment selected return false; } return QGraphicsItem::itemChange(change, value); } diff --git a/src/backend/datapicker/Transform.cpp b/src/backend/datapicker/Transform.cpp index 5e9421699..4017e76e2 100644 --- a/src/backend/datapicker/Transform.cpp +++ b/src/backend/datapicker/Transform.cpp @@ -1,152 +1,152 @@ /*************************************************************************** File : Transform.cpp Project : LabPlot Description : transformation for mapping between scene and logical coordinates of Datapicker-image -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) ***************************************************************************/ /*************************************************************************** * * * 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 "Transform.h" #include Transform::Transform() = default; bool Transform::mapTypeToCartesian(const DatapickerImage::ReferencePoints& axisPoints) { - if (axisPoints.type == DatapickerImage::LogarithmicX) { + if (axisPoints.type == DatapickerImage::GraphType::LogarithmicX) { for (int i = 0; i < 3; ++i) { if (axisPoints.logicalPos[i].x() <= 0) return false; x[i] = log(axisPoints.logicalPos[i].x()); y[i] = axisPoints.logicalPos[i].y(); X[i] = axisPoints.scenePos[i].x(); Y[i] = axisPoints.scenePos[i].y(); } - } else if (axisPoints.type == DatapickerImage::LogarithmicY) { + } else if (axisPoints.type == DatapickerImage::GraphType::LogarithmicY) { for (int i = 0; i < 3; ++i) { if (axisPoints.logicalPos[i].y() <= 0) return false; x[i] = axisPoints.logicalPos[i].x(); y[i] = log(axisPoints.logicalPos[i].y()); X[i] = axisPoints.scenePos[i].x(); Y[i] = axisPoints.scenePos[i].y(); } - } else if (axisPoints.type == DatapickerImage::PolarInDegree) { + } else if (axisPoints.type == DatapickerImage::GraphType::PolarInDegree) { for (int i = 0; i < 3; ++i) { if (axisPoints.logicalPos[i].x() < 0) return false; x[i] = axisPoints.logicalPos[i].x()*cos(axisPoints.logicalPos[i].y()*M_PI / 180.0); y[i] = axisPoints.logicalPos[i].x()*sin(axisPoints.logicalPos[i].y()*M_PI / 180.0); X[i] = axisPoints.scenePos[i].x(); Y[i] = axisPoints.scenePos[i].y(); } - } else if (axisPoints.type == DatapickerImage::PolarInRadians) { + } else if (axisPoints.type == DatapickerImage::GraphType::PolarInRadians) { for (int i = 0; i < 3; ++i) { if (axisPoints.logicalPos[i].x() < 0) return false; x[i] = axisPoints.logicalPos[i].x()*cos(axisPoints.logicalPos[i].y()); y[i] = axisPoints.logicalPos[i].x()*sin(axisPoints.logicalPos[i].y()); X[i] = axisPoints.scenePos[i].x(); Y[i] = axisPoints.scenePos[i].y(); } - } else if (axisPoints.type == DatapickerImage::Ternary) { + } else if (axisPoints.type == DatapickerImage::GraphType::Ternary) { for (int i = 0; i < 3; ++i) { x[i] = (2*axisPoints.logicalPos[i].y() + axisPoints.logicalPos[i].z())/(2*axisPoints.ternaryScale); y[i] = (sqrt(3)*axisPoints.logicalPos[i].z())/(2*axisPoints.ternaryScale); X[i] = axisPoints.scenePos[i].x(); Y[i] = axisPoints.scenePos[i].y(); } } else { for (int i = 0; i < 3; ++i) { x[i] = axisPoints.logicalPos[i].x(); y[i] = axisPoints.logicalPos[i].y(); X[i] = axisPoints.scenePos[i].x(); Y[i] = axisPoints.scenePos[i].y(); } } return true; } QVector3D Transform::mapSceneToLogical(QPointF scenePoint, const DatapickerImage::ReferencePoints& axisPoints) { X[3] = scenePoint.x(); Y[3] = scenePoint.y(); if (mapTypeToCartesian(axisPoints)) { double sin; double cos; double scaleOfX; double scaleOfY; if (((Y[1] - Y[0])*(x[2] - x[0]) - (Y[2] - Y[0])*(x[1] - x[0])) != 0) { double tan = ((X[1] - X[0])*(x[2] - x[0]) - (X[2] - X[0])*(x[1] - x[0]))/((Y[1] - Y[0])*(x[2] - x[0]) - (Y[2] - Y[0])*(x[1] - x[0])); sin = tan/sqrt(1 + tan*tan); cos = sqrt(1 - sin*sin); } else { sin = 1; cos = 0; } if ((x[1] - x[0]) != 0) { scaleOfX = (x[1] - x[0])/((X[1] - X[0])*cos - (Y[1] - Y[0])*sin); } else { scaleOfX = (x[2] - x[0])/((X[2] - X[0])*cos - (Y[2] - Y[0])*sin); } if ((y[1]-y[0]) != 0) { scaleOfY = (y[1] - y[0])/((X[1] - X[0])*sin + (Y[1] - Y[0])*cos); } else { scaleOfY = (y[2] - y[0])/((X[2] - X[0])*sin + (Y[2] - Y[0])*cos); } x[3] = x[0] + (((X[3] - X[0])*cos - (Y[3] - Y[0])*sin)*scaleOfX); y[3] = y[0] + (((X[3] - X[0])*sin + (Y[3] - Y[0])*cos)*scaleOfY); return mapCartesianToType(QPointF(x[3], y[3]), axisPoints); } return QVector3D{}; } QVector3D Transform::mapSceneLengthToLogical(QPointF errorSpan, const DatapickerImage::ReferencePoints& axisPoints) { return mapSceneToLogical(errorSpan, axisPoints) - mapSceneToLogical(QPointF(0,0), axisPoints); } QVector3D Transform::mapCartesianToType(QPointF point, const DatapickerImage::ReferencePoints &axisPoints) const { - if (axisPoints.type == DatapickerImage::LogarithmicX) { + if (axisPoints.type == DatapickerImage::GraphType::LogarithmicX) { return QVector3D(exp(point.x()), point.y(), 0); - } else if (axisPoints.type == DatapickerImage::LogarithmicY) { + } else if (axisPoints.type == DatapickerImage::GraphType::LogarithmicY) { return QVector3D(point.x(), exp(point.y()), 0); - } else if (axisPoints.type == DatapickerImage::PolarInDegree) { + } else if (axisPoints.type == DatapickerImage::GraphType::PolarInDegree) { double r = sqrt(point.x()*point.x() + point.y()*point.y()); double angle = atan(point.y()*180/(point.x()*M_PI)); return QVector3D(r, angle, 0); - } else if (axisPoints.type == DatapickerImage::PolarInRadians) { + } else if (axisPoints.type == DatapickerImage::GraphType::PolarInRadians) { double r = sqrt(point.x()*point.x() + point.y()*point.y()); double angle = atan(point.y()/point.x()); return QVector3D(r, angle, 0); - } else if (axisPoints.type == DatapickerImage::Ternary) { + } else if (axisPoints.type == DatapickerImage::GraphType::Ternary) { double c = (point.y()*2*axisPoints.ternaryScale)/sqrt(3); double b = (point.x()*2*axisPoints.ternaryScale - c)/2; double a = axisPoints.ternaryScale - b - c; return QVector3D(a, b, c); } else { return QVector3D(point.x(), point.y(), 0); } } diff --git a/src/backend/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp index ce5239cdd..2ee60f397 100644 --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -1,943 +1,939 @@ /*************************************************************************** File : LiveDataSource.cpp Project : LabPlot Description : Represents live data source -------------------------------------------------------------------- Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "backend/core/Project.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class LiveDataSource \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters. \ingroup datasources */ LiveDataSource::LiveDataSource(const QString& name, bool loading) : Spreadsheet(name, loading, AspectType::LiveDataSource), m_updateTimer(new QTimer(this)), m_watchTimer(new QTimer(this)) { initActions(); connect(m_updateTimer, &QTimer::timeout, this, &LiveDataSource::read); connect(m_watchTimer, &QTimer::timeout, this, &LiveDataSource::readOnUpdate); } LiveDataSource::~LiveDataSource() { //stop reading before deleting the objects pauseReading(); delete m_filter; delete m_fileSystemWatcher; delete m_localSocket; delete m_tcpSocket; delete m_serialPort; } void LiveDataSource::initActions() { m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this); connect(m_plotDataAction, &QAction::triggered, this, &LiveDataSource::plotData); m_watchTimer->setSingleShot(true); m_watchTimer->setInterval(100); } QWidget* LiveDataSource::view() const { if (!m_partView) { m_view = new SpreadsheetView(const_cast(this), true); m_partView = m_view; } return m_partView; } /*! * \brief Returns a list with the names of the available ports */ QStringList LiveDataSource::availablePorts() { QStringList ports; // qDebug() << "available ports count:" << QSerialPortInfo::availablePorts().size(); for (const QSerialPortInfo& sp : QSerialPortInfo::availablePorts()) { ports.append(sp.portName()); DEBUG(" port " << STDSTRING(sp.portName()) << ": " << STDSTRING(sp.systemLocation()) << STDSTRING(sp.description()) << ' ' << STDSTRING(sp.manufacturer()) << ' ' << STDSTRING(sp.serialNumber())); } // For Testing: // ports.append("/dev/pts/26"); return ports; } /*! * \brief Returns a list with the supported baud rates */ QStringList LiveDataSource::supportedBaudRates() { QStringList baudRates; for (const auto& baud : QSerialPortInfo::standardBaudRates()) baudRates.append(QString::number(baud)); return baudRates; } /*! * \brief Updates this data source at this moment */ void LiveDataSource::updateNow() { DEBUG("LiveDataSource::updateNow() update interval = " << m_updateInterval); - if (m_updateType == TimeInterval) + if (m_updateType == UpdateType::TimeInterval) m_updateTimer->stop(); else m_pending = false; read(); //restart the timer after update - if (m_updateType == TimeInterval && !m_paused) + if (m_updateType == UpdateType::TimeInterval && !m_paused) m_updateTimer->start(m_updateInterval); } /*! * \brief Continue reading from the live data source after it was paused */ void LiveDataSource::continueReading() { m_paused = false; if (m_pending) { m_pending = false; updateNow(); } } /*! * \brief Pause the reading of the live data source */ void LiveDataSource::pauseReading() { m_paused = true; - if (m_updateType == TimeInterval) { + if (m_updateType == UpdateType::TimeInterval) { m_pending = true; m_updateTimer->stop(); } } void LiveDataSource::setFileName(const QString& name) { m_fileName = name; } QString LiveDataSource::fileName() const { return m_fileName; } /*! * \brief Sets the local socket's server name to name * \param name */ void LiveDataSource::setLocalSocketName(const QString& name) { m_localSocketName = name; } QString LiveDataSource::localSocketName() const { return m_localSocketName; } void LiveDataSource::setFileType(AbstractFileFilter::FileType type) { m_fileType = type; } AbstractFileFilter::FileType LiveDataSource::fileType() const { return m_fileType; } void LiveDataSource::setFilter(AbstractFileFilter* f) { delete m_filter; m_filter = f; } AbstractFileFilter* LiveDataSource::filter() const { return m_filter; } /*! * \brief Sets the serial port's baud rate * \param baudrate */ void LiveDataSource::setBaudRate(int baudrate) { m_baudRate = baudrate; } int LiveDataSource::baudRate() const { return m_baudRate; } /*! * \brief Sets the source's update interval to \c interval * \param interval */ void LiveDataSource::setUpdateInterval(int interval) { m_updateInterval = interval; if (!m_paused) m_updateTimer->start(m_updateInterval); } int LiveDataSource::updateInterval() const { return m_updateInterval; } /*! * \brief Sets how many values we should keep when keepLastValues is true * \param keepnvalues */ void LiveDataSource::setKeepNValues(int keepnvalues) { m_keepNValues = keepnvalues; } int LiveDataSource::keepNValues() const { return m_keepNValues; } /*! * \brief Sets the network socket's port to port * \param port */ void LiveDataSource::setPort(quint16 port) { m_port = port; } void LiveDataSource::setBytesRead(qint64 bytes) { m_bytesRead = bytes; } int LiveDataSource::bytesRead() const { return m_bytesRead; } int LiveDataSource::port() const { return m_port; } /*! * \brief Sets the serial port's name to name * \param name */ void LiveDataSource::setSerialPort(const QString& name) { m_serialPortName = name; } QString LiveDataSource::serialPortName() const { return m_serialPortName; } bool LiveDataSource::isPaused() const { return m_paused; } /*! * \brief Sets the sample size to size * \param size */ void LiveDataSource::setSampleSize(int size) { m_sampleSize = size; } int LiveDataSource::sampleSize() const { return m_sampleSize; } /*! * \brief Sets the source's type to sourcetype * \param sourcetype */ void LiveDataSource::setSourceType(SourceType sourcetype) { m_sourceType = sourcetype; } LiveDataSource::SourceType LiveDataSource::sourceType() const { return m_sourceType; } /*! * \brief Sets the source's reading type to readingType * \param readingType */ void LiveDataSource::setReadingType(ReadingType readingType) { m_readingType = readingType; } LiveDataSource::ReadingType LiveDataSource::readingType() const { return m_readingType; } /*! * \brief Sets the source's update type to updatetype and handles this change * \param updatetype */ void LiveDataSource::setUpdateType(UpdateType updatetype) { switch (updatetype) { - case NewData: { + case UpdateType::NewData: { m_updateTimer->stop(); if (!m_fileSystemWatcher) m_fileSystemWatcher = new QFileSystemWatcher(this); m_fileSystemWatcher->addPath(m_fileName); QFileInfo file(m_fileName); // If the watched file currently does not exist (because it is recreated for instance), watch its containing // directory instead. Once the file exists again, switch to watching the file in readOnUpdate(). // Reading will only start 100ms after the last update, to prevent continuous re-reading while the file is updated. // If the watched file intentionally is updated more often than that, the user should switch to periodic reading. if (m_fileSystemWatcher->files().contains(m_fileName)) m_fileSystemWatcher->removePath(file.absolutePath()); else m_fileSystemWatcher->addPath(file.absolutePath()); connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [&](){ m_watchTimer->start(); }); connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [&](){ m_watchTimer->start(); }); break; } - case TimeInterval: + case UpdateType::TimeInterval: delete m_fileSystemWatcher; m_fileSystemWatcher = nullptr; break; } m_updateType = updatetype; } LiveDataSource::UpdateType LiveDataSource::updateType() const { return m_updateType; } /*! * \brief Sets the network socket's host * \param host */ void LiveDataSource::setHost(const QString& host) { m_host = host.simplified(); } QString LiveDataSource::host() const { return m_host; } /*! sets whether only a link to the file is saved in the project file (\c b=true) or the whole content of the file (\c b=false). */ void LiveDataSource::setFileLinked(bool b) { m_fileLinked = b; } /*! returns \c true if only a link to the file is saved in the project file. \c false otherwise. */ bool LiveDataSource::isFileLinked() const { return m_fileLinked; } void LiveDataSource::setUseRelativePath(bool b) { m_relativePath = b; } bool LiveDataSource::useRelativePath() const { return m_relativePath; } QIcon LiveDataSource::icon() const { QIcon icon; switch (m_fileType) { case AbstractFileFilter::Ascii: icon = QIcon::fromTheme("text-plain"); break; case AbstractFileFilter::Binary: icon = QIcon::fromTheme("application-octet-stream"); break; case AbstractFileFilter::Image: icon = QIcon::fromTheme("image-x-generic"); break; // TODO: missing icons case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: break; case AbstractFileFilter::FITS: icon = QIcon::fromTheme("kstars_fitsviewer"); break; case AbstractFileFilter::JSON: icon = QIcon::fromTheme("application-json"); break; case AbstractFileFilter::ROOT: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } return icon; } QMenu* LiveDataSource::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); 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->insertAction(firstAction, m_plotDataAction); menu->insertSeparator(firstAction); return menu; } //############################################################################## //################################# SLOTS #################################### //############################################################################## /* * Called when the watch timer times out, i.e. when modifying the file or directory * presumably has finished. Also see LiveDataSource::setUpdateType(). */ void LiveDataSource::readOnUpdate() { if (!m_fileSystemWatcher->files().contains(m_fileName)) { m_fileSystemWatcher->addPath(m_fileName); QFileInfo file(m_fileName); if (m_fileSystemWatcher->files().contains(m_fileName)) m_fileSystemWatcher->removePath(file.absolutePath()); else { m_fileSystemWatcher->addPath(file.absolutePath()); return; } } if (m_paused) // flag file for reading, once the user decides to continue reading m_pending = true; else read(); } /* * called periodically or on new data changes (file changed, new data in the socket, etc.) */ void LiveDataSource::read() { DEBUG("\nLiveDataSource::read()"); if (!m_filter) return; if (m_reading) return; m_reading = true; //initialize the device (file, socket, serial port) when calling this function for the first time if (!m_prepared) { DEBUG(" Preparing device: update type = " << ENUM_TO_STRING(LiveDataSource, UpdateType, m_updateType)); switch (m_sourceType) { - case FileOrPipe: + case SourceType::FileOrPipe: delete m_device; m_device = new QFile(m_fileName); break; - case NetworkTcpSocket: + case SourceType::NetworkTcpSocket: m_tcpSocket = new QTcpSocket(this); m_device = m_tcpSocket; m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); connect(m_tcpSocket, &QTcpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_tcpSocket, static_cast(&QTcpSocket::error), this, &LiveDataSource::tcpSocketError); break; - case NetworkUdpSocket: + case SourceType::NetworkUdpSocket: m_udpSocket = new QUdpSocket(this); m_device = m_udpSocket; m_udpSocket->bind(QHostAddress(m_host), m_port); m_udpSocket->connectToHost(m_host, 0, QUdpSocket::ReadOnly); // only connect to readyRead when update is on new data - if (m_updateType == NewData) + if (m_updateType == UpdateType::NewData) connect(m_udpSocket, &QUdpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_udpSocket, static_cast(&QUdpSocket::error), this, &LiveDataSource::tcpSocketError); break; - case LocalSocket: + case SourceType::LocalSocket: m_localSocket = new QLocalSocket(this); m_device = m_localSocket; m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); connect(m_localSocket, &QLocalSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_localSocket, static_cast(&QLocalSocket::error), this, &LiveDataSource::localSocketError); break; - case SerialPort: + case SourceType::SerialPort: m_serialPort = new QSerialPort(this); m_device = m_serialPort; DEBUG(" Serial: " << STDSTRING(m_serialPortName) << ", " << m_baudRate); m_serialPort->setBaudRate(m_baudRate); m_serialPort->setPortName(m_serialPortName); m_serialPort->open(QIODevice::ReadOnly); // only connect to readyRead when update is on new data - if (m_updateType == NewData) + if (m_updateType == UpdateType::NewData) connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead); connect(m_serialPort, static_cast(&QSerialPort::error), this, &LiveDataSource::serialPortError); break; - case MQTT: + case SourceType::MQTT: break; } m_prepared = true; } qint64 bytes = 0; switch (m_sourceType) { - case FileOrPipe: + case SourceType::FileOrPipe: DEBUG("Reading FileOrPipe. type = " << ENUM_TO_STRING(AbstractFileFilter, FileType, m_fileType)); switch (m_fileType) { case AbstractFileFilter::Ascii: if (m_readingType == LiveDataSource::ReadingType::WholeFile) { static_cast(m_filter)->readFromLiveDevice(*m_device, this, 0); } else { bytes = static_cast(m_filter)->readFromLiveDevice(*m_device, this, m_bytesRead); m_bytesRead += bytes; DEBUG("Read " << bytes << " bytes, in total: " << m_bytesRead); } break; case AbstractFileFilter::Binary: //TODO: not implemented yet // bytes = qSharedPointerCast(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); // m_bytesRead += bytes; case AbstractFileFilter::ROOT: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: //only re-reading of the whole file is supported m_filter->readDataFromFile(m_fileName, this); break; //TODO: other types not implemented yet case AbstractFileFilter::Image: case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: case AbstractFileFilter::JSON: break; } break; - case NetworkTcpSocket: + case SourceType::NetworkTcpSocket: DEBUG("reading from TCP socket. state before abort = " << m_tcpSocket->state()); m_tcpSocket->abort(); m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); DEBUG("reading from TCP socket. state after reconnect = " << m_tcpSocket->state()); break; - case NetworkUdpSocket: + case SourceType::NetworkUdpSocket: DEBUG(" Reading from UDP socket. state = " << m_udpSocket->state()); // reading data here if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; - case LocalSocket: + case SourceType::LocalSocket: DEBUG(" Reading from local socket. state before abort = " << m_localSocket->state()); if (m_localSocket->state() == QLocalSocket::ConnectingState) m_localSocket->abort(); m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); if (m_localSocket->waitForConnected()) m_localSocket->waitForReadyRead(); DEBUG(" Reading from local socket. state after reconnect = " << m_localSocket->state()); break; - case SerialPort: + case SourceType::SerialPort: DEBUG(" Reading from serial port"); // reading data here if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; - case MQTT: + case SourceType::MQTT: break; } m_reading = false; } /*! * Slot for the signal that is emitted once every time new data is available for reading from the device (not UDP or Serial). * It will only be emitted again once new data is available, such as when a new payload of network data has arrived on the network socket, * or when a new block of data has been appended to your device. */ void LiveDataSource::readyRead() { DEBUG("LiveDataSource::readyRead() update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType)); DEBUG(" REMAINING TIME = " << m_updateTimer->remainingTime()); if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //TODO: not implemented yet // else if (m_fileType == AbstractFileFilter::Binary) // dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //since we won't have the timer to call read() where we create new connections //for sequential devices in read() we just request data/connect to servers - if (m_updateType == NewData) + if (m_updateType == UpdateType::NewData) read(); } void LiveDataSource::localSocketError(QLocalSocket::LocalSocketError socketError) { Q_UNUSED(socketError); /*disconnect(m_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(localSocketError(QLocalSocket::LocalSocketError))); disconnect(m_localSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));*/ /*switch (socketError) { case QLocalSocket::ServerNotFoundError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket was not found. Please check the socket name.")); break; case QLocalSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The connection was refused by the peer")); break; case QLocalSocket::PeerClosedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket has closed the connection.")); break; default: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The following error occurred: %1.", m_localSocket->errorString())); }*/ } void LiveDataSource::tcpSocketError(QAbstractSocket::SocketError socketError) { Q_UNUSED(socketError); /*switch (socketError) { case QAbstractSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The connection was refused by the peer. Make sure the server is running and check the host name and port settings.")); break; case QAbstractSocket::RemoteHostClosedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The remote host closed the connection.")); break; case QAbstractSocket::HostNotFoundError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The host was not found. Please check the host name and port settings.")); break; default: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The following error occurred: %1.", m_tcpSocket->errorString())); }*/ } void LiveDataSource::serialPortError(QSerialPort::SerialPortError serialPortError) { switch (serialPortError) { case QSerialPort::DeviceNotFoundError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device.")); break; case QSerialPort::PermissionError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device. Please check your permissions on this device.")); break; case QSerialPort::OpenError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Device already opened.")); break; case QSerialPort::NotOpenError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device is not opened.")); break; case QSerialPort::ReadError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data.")); break; case QSerialPort::ResourceError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data. The device is removed.")); break; case QSerialPort::TimeoutError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device timed out.")); break; #ifndef _MSC_VER //MSVC complains about the usage of deprecated enums, g++ and clang complain about missing enums case QSerialPort::ParityError: case QSerialPort::FramingError: case QSerialPort::BreakConditionError: #endif case QSerialPort::WriteError: case QSerialPort::UnsupportedOperationError: case QSerialPort::UnknownError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The following error occurred: %1.", m_serialPort->errorString())); break; case QSerialPort::NoError: break; } } void LiveDataSource::plotData() { auto* dlg = new PlotDataDialog(this); dlg->exec(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void LiveDataSource::save(QXmlStreamWriter* writer) const { writer->writeStartElement("liveDataSource"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); switch (m_sourceType) { - case FileOrPipe: + case SourceType::FileOrPipe: writer->writeAttribute("fileType", QString::number(m_fileType)); writer->writeAttribute("fileLinked", QString::number(m_fileLinked)); writer->writeAttribute("relativePath", QString::number(m_relativePath)); if (m_relativePath) { //convert from the absolute to the relative path and save it const Project* p = const_cast(this)->project(); QFileInfo fi(p->fileName()); writer->writeAttribute("fileName", fi.dir().relativeFilePath(m_fileName)); }else writer->writeAttribute("fileName", m_fileName); break; - case SerialPort: + case SourceType::SerialPort: writer->writeAttribute("baudRate", QString::number(m_baudRate)); writer->writeAttribute("serialPortName", m_serialPortName); break; - case NetworkTcpSocket: - case NetworkUdpSocket: + case SourceType::NetworkTcpSocket: + case SourceType::NetworkUdpSocket: writer->writeAttribute("host", m_host); writer->writeAttribute("port", QString::number(m_port)); break; - case LocalSocket: + case SourceType::LocalSocket: break; - case MQTT: - break; - default: + case SourceType::MQTT: break; } - writer->writeAttribute("updateType", QString::number(m_updateType)); - writer->writeAttribute("readingType", QString::number(m_readingType)); - writer->writeAttribute("sourceType", QString::number(m_sourceType)); + writer->writeAttribute("updateType", QString::number(static_cast(m_updateType))); + writer->writeAttribute("readingType", QString::number(static_cast(m_readingType))); + writer->writeAttribute("sourceType", QString::number(static_cast(m_sourceType))); writer->writeAttribute("keepNValues", QString::number(m_keepNValues)); - if (m_updateType == TimeInterval) + if (m_updateType == UpdateType::TimeInterval) writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); - if (m_readingType != TillEnd) + if (m_readingType != ReadingType::TillEnd) writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); writer->writeEndElement(); //general //filter m_filter->save(writer); //columns if (!m_fileLinked) { for (auto* col : children(ChildIndexFlag::IncludeHidden)) col->save(writer); } writer->writeEndElement(); // "liveDataSource" } /*! Loads from XML. */ bool LiveDataSource::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() == "liveDataSource" || reader->name() == "LiveDataSource")) //TODO: remove "LiveDataSources" in couple of releases break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileName").toString()); else m_fileName = str; str = attribs.value("fileType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileType").toString()); else m_fileType = (AbstractFileFilter::FileType)str.toInt(); str = attribs.value("fileLinked").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileLinked").toString()); else m_fileLinked = str.toInt(); str = attribs.value("relativePath").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("relativePath").toString()); else m_relativePath = str.toInt(); str = attribs.value("updateType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateType").toString()); else m_updateType = static_cast(str.toInt()); str = attribs.value("sourceType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sourceType").toString()); else m_sourceType = static_cast(str.toInt()); str = attribs.value("readingType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("readingType").toString()); else m_readingType = static_cast(str.toInt()); - if (m_updateType == TimeInterval) { + if (m_updateType == UpdateType::TimeInterval) { str = attribs.value("updateInterval").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateInterval").toString()); else m_updateInterval = str.toInt(); } - if (m_readingType != TillEnd) { + if (m_readingType != ReadingType::TillEnd) { str = attribs.value("sampleSize").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sampleSize").toString()); else m_sampleSize = str.toInt(); } switch (m_sourceType) { - case SerialPort: + case SourceType::SerialPort: str = attribs.value("baudRate").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("baudRate").toString()); else m_baudRate = str.toInt(); str = attribs.value("serialPortName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("serialPortName").toString()); else m_serialPortName = str; break; - case NetworkTcpSocket: - case NetworkUdpSocket: + case SourceType::NetworkTcpSocket: + case SourceType::NetworkUdpSocket: str = attribs.value("host").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("host").toString()); else m_host = str; str = attribs.value("port").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("port").toString()); else m_host = str; break; - case MQTT: - break; - case FileOrPipe: + case SourceType::MQTT: break; - case LocalSocket: + case SourceType::FileOrPipe: break; - default: + case SourceType::LocalSocket: break; } } else if (reader->name() == "asciiFilter") { setFilter(new AsciiFilter); if (!m_filter->load(reader)) return false; } else if (reader->name() == "rootFilter") { setFilter(new ROOTFilter); if (!m_filter->load(reader)) return false; } else if (reader->name() == "column") { Column* column = new Column(QString(), AbstractColumn::ColumnMode::Text); if (!column->load(reader, preview)) { delete column; setColumnCount(0); return false; } addChild(column); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return !reader->hasError(); } void LiveDataSource::finalizeLoad() { //convert from the relative path saved in the project file to the absolute file to work with if (m_relativePath) { QFileInfo fi(project()->fileName()); m_fileName = fi.dir().absoluteFilePath(m_fileName); } //read the content of the file if it was only linked if (m_fileLinked && QFile::exists(m_fileName)) this->read(); //call setUpdateType() to start watching the file for changes, is required setUpdateType(m_updateType); } diff --git a/src/backend/datasources/LiveDataSource.h b/src/backend/datasources/LiveDataSource.h index df04f516f..934d1ce59 100644 --- a/src/backend/datasources/LiveDataSource.h +++ b/src/backend/datasources/LiveDataSource.h @@ -1,207 +1,207 @@ /*************************************************************************** File : LiveDataSource.h Project : LabPlot Description : File data source -------------------------------------------------------------------- Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2017-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ #ifndef LIVEDATASOURCE_H #define LIVEDATASOURCE_H #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include #include #include #include #include class QString; class AbstractFileFilter; class QFileSystemWatcher; class QAction; class QTcpSocket; class QUdpSocket; class LiveDataSource : public Spreadsheet { Q_OBJECT Q_ENUMS(SourceType) Q_ENUMS(UpdateType) Q_ENUMS(ReadingType) public: - enum SourceType { + enum class SourceType { FileOrPipe = 0, // regular file or pipe NetworkTcpSocket, // TCP socket NetworkUdpSocket, // UDP socket LocalSocket, // local socket SerialPort, // serial port MQTT }; - enum UpdateType { + enum class UpdateType { TimeInterval = 0, // update periodically using given interval NewData // update when new data is available }; - enum ReadingType { + enum class ReadingType { ContinuousFixed = 0, // read continuously sampleSize number of samples (lines) FromEnd, // read sampleSize number of samples (lines) from end TillEnd, // read until the end WholeFile // reread whole file }; explicit LiveDataSource(const QString& name, bool loading = false); ~LiveDataSource() override; static QStringList supportedBaudRates(); static QStringList availablePorts(); void setFileType(const AbstractFileFilter::FileType); AbstractFileFilter::FileType fileType() const; UpdateType updateType() const; void setUpdateType(UpdateType); SourceType sourceType() const; void setSourceType(SourceType); ReadingType readingType() const; void setReadingType(ReadingType); int sampleSize() const; void setSampleSize(int); void setBytesRead(qint64 bytes); int bytesRead() const; int port() const; void setPort(quint16); bool isPaused() const; void setSerialPort(const QString& name); QString serialPortName() const; QString host() const; void setHost(const QString&); int baudRate() const; void setBaudRate(int); void setUpdateInterval(int); int updateInterval() const; void setKeepNValues(int); int keepNValues() const; void setKeepLastValues(bool); bool keepLastValues() const; void setFileLinked(bool); bool isFileLinked() const; void setUseRelativePath(bool); bool useRelativePath() const; void setFileName(const QString&); QString fileName() const; void setLocalSocketName(const QString&); QString localSocketName() const; void updateNow(); void pauseReading(); void continueReading(); void setFilter(AbstractFileFilter*); AbstractFileFilter* filter() const; QIcon icon() const override; QMenu* createContextMenu() override; QWidget* view() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; void finalizeLoad(); private: void initActions(); QString m_fileName; QString m_dirName; QString m_serialPortName; QString m_localSocketName; QString m_host; AbstractFileFilter::FileType m_fileType{AbstractFileFilter::Ascii}; UpdateType m_updateType; SourceType m_sourceType; ReadingType m_readingType; bool m_fileWatched{false}; bool m_fileLinked{false}; bool m_relativePath{false}; bool m_paused{false}; bool m_prepared{false}; bool m_reading{false}; bool m_pending{false}; int m_sampleSize{1}; int m_keepNValues{0}; // number of values to keep (0 - all) int m_updateInterval{1000}; quint16 m_port{1027}; int m_baudRate{9600}; qint64 m_bytesRead{0}; AbstractFileFilter* m_filter{nullptr}; QTimer* m_updateTimer; QTimer* m_watchTimer; QFileSystemWatcher* m_fileSystemWatcher{nullptr}; QLocalSocket* m_localSocket{nullptr}; QTcpSocket* m_tcpSocket{nullptr}; QUdpSocket* m_udpSocket{nullptr}; QSerialPort* m_serialPort{nullptr}; QIODevice* m_device{nullptr}; QAction* m_plotDataAction{nullptr}; public slots: void read(); void readOnUpdate(); private slots: void plotData(); void readyRead(); void localSocketError(QLocalSocket::LocalSocketError); void tcpSocketError(QAbstractSocket::SocketError); void serialPortError(QSerialPort::SerialPortError); }; #endif diff --git a/src/backend/datasources/MQTTClient.h b/src/backend/datasources/MQTTClient.h index b5e4a6c99..b42800e38 100644 --- a/src/backend/datasources/MQTTClient.h +++ b/src/backend/datasources/MQTTClient.h @@ -1,255 +1,255 @@ /*************************************************************************** File : MQTTClient.h Project : LabPlot Description : Represents a MQTT Client -------------------------------------------------------------------- Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ #ifndef MQTTCLIENT_H #define MQTTCLIENT_H #include "backend/core/Folder.h" #include #include #include #include #include #include #include class AsciiFilter; class MQTTSubscription; class QAction; class QTimer; class QString; class MQTTClient : public Folder { Q_OBJECT public: - enum UpdateType { + enum class UpdateType { TimeInterval = 0, NewData }; - enum ReadingType { + enum class ReadingType { ContinuousFixed = 0, FromEnd, TillEnd }; - enum WillMessageType { + enum class WillMessageType { OwnMessage = 0, Statistics, LastMessage }; - enum WillUpdateType { + enum class WillUpdateType { TimePeriod = 0, OnClick }; - enum WillStatisticsType { + enum class WillStatisticsType { NoStatistics = -1, Minimum, Maximum, ArithmeticMean, GeometricMean, HarmonicMean, ContraharmonicMean, Median, Variance, StandardDeviation, MeanDeviation, MeanDeviationAroundMedian, MedianDeviation, Skewness, Kurtosis, Entropy }; struct MQTTWill { bool enabled{false}; QString willMessage; QString willTopic; bool willRetain{false}; quint8 willQoS{0}; WillMessageType willMessageType{MQTTClient::WillMessageType::OwnMessage}; QString willOwnMessage; QString willLastMessage; int willTimeInterval{1000}; WillUpdateType willUpdateType{MQTTClient::WillUpdateType::TimePeriod}; QVector willStatistics{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; }; explicit MQTTClient(const QString& name); virtual ~MQTTClient() override; void ready(); UpdateType updateType() const; void setUpdateType(UpdateType); ReadingType readingType() const; void setReadingType(ReadingType); int sampleSize() const; void setSampleSize(int); bool isPaused() const; void setUpdateInterval(int); int updateInterval() const; void setKeepNValues(int); int keepNValues() const; void setKeepLastValues(bool); bool keepLastValues() const; void setMQTTClientHostPort(const QString&, quint16); void setMQTTClientAuthentication(const QString&, const QString&); void setMQTTClientId(const QString&); void addInitialMQTTSubscriptions(const QMqttTopicFilter&, quint8); QVector MQTTSubscriptions() const; bool checkTopicContains(const QString& superior, const QString& inferior); QString checkCommonLevel(const QString& first, const QString& second); QString clientHostName() const; quint16 clientPort() const; QString clientPassword() const; QString clientUserName() const; QString clientID () const; void updateNow(); void pauseReading(); void continueReading(); void setFilter(AsciiFilter*); AsciiFilter* filter() const; QIcon icon() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; QVector topicNames() const; bool checkAllArrived(); void setWillSettings(const MQTTWill&); MQTTWill willSettings() const; void setMQTTWillUse(bool); bool MQTTWillUse() const; void setWillTopic(const QString&); QString willTopic() const; void setWillRetain(bool); bool willRetain() const; void setWillQoS(quint8); quint8 willQoS() const; void setWillMessageType(WillMessageType); WillMessageType willMessageType() const; void setWillOwnMessage(const QString&); QString willOwnMessage() const; WillUpdateType willUpdateType() const; void setWillUpdateType(WillUpdateType); int willTimeInterval() const; void setWillTimeInterval(int); void startWillTimer() const; void stopWillTimer() const; void updateWillMessage() ; void setMQTTRetain(bool); bool MQTTRetain() const; void setMQTTUseID(bool); bool MQTTUseID() const; void setMQTTUseAuthentication(bool); bool MQTTUseAuthentication() const; void clearLastMessage(); void addWillStatistics(WillStatisticsType); void removeWillStatistics(WillStatisticsType); QVector willStatistics() const; void addMQTTSubscription(const QString&, quint8); void removeMQTTSubscription(const QString&); void addBeforeRemoveSubscription(const QString&, quint8); void reparentTopic(const QString& topic, const QString& parent); private: UpdateType m_updateType{TimeInterval}; ReadingType m_readingType{ContinuousFixed}; bool m_paused{false}; bool m_prepared{false}; int m_sampleSize{1}; int m_keepNValues{0}; int m_updateInterval{1000}; AsciiFilter* m_filter{nullptr}; QTimer* m_updateTimer; QMqttClient* m_client; QMap m_subscribedTopicNameQoS; QVector m_subscriptions; QVector m_topicNames; bool m_MQTTTest{false}; QTimer* m_willTimer; bool m_MQTTFirstConnectEstablished{false}; bool m_MQTTRetain{false}; bool m_MQTTUseID{false}; bool m_MQTTUseAuthentication{false}; QVector m_MQTTSubscriptions; bool m_disconnectForWill{false}; bool m_loaded{false}; int m_subscriptionsLoaded{0}; int m_subscriptionCountToLoad{0}; MQTTWill m_MQTTWill; public slots: void read(); private slots: void onMQTTConnect(); void MQTTSubscriptionMessageReceived(const QMqttMessage&); void MQTTErrorChanged(QMqttClient::ClientError); void subscriptionLoaded(const QString&); signals: void MQTTSubscribed(); void MQTTTopicsChanged(); void readFromTopics(); void clientAboutToBeDeleted(const QString&, quint16); }; #endif // MQTTCLIENT_H diff --git a/src/backend/datasources/projects/OriginProjectParser.cpp b/src/backend/datasources/projects/OriginProjectParser.cpp index 03d1f4666..c83c1384b 100644 --- a/src/backend/datasources/projects/OriginProjectParser.cpp +++ b/src/backend/datasources/projects/OriginProjectParser.cpp @@ -1,2143 +1,2143 @@ /*************************************************************************** File : OriginProjectParser.h Project : LabPlot Description : parser for Origin projects -------------------------------------------------------------------- Copyright : (C) 2017-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2019 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 "backend/datasources/projects/OriginProjectParser.h" #include "backend/core/column/Column.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/Project.h" #include "backend/core/Workbook.h" #include "backend/matrix/Matrix.h" #include "backend/note/Note.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYEquationCurve.h" #include "backend/worksheet/TextLabel.h" #include #include #include #include #include #include /*! \class OriginProjectParser \brief parser for Origin projects. \ingroup datasources */ OriginProjectParser::OriginProjectParser() : ProjectParser() { m_topLevelClasses = {AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::Matrix, AspectType::Worksheet, AspectType::Note}; } bool OriginProjectParser::isOriginProject(const QString& fileName) { //TODO add opju later when liborigin supports it return fileName.endsWith(QLatin1String(".opj"), Qt::CaseInsensitive); } void OriginProjectParser::setImportUnusedObjects(bool importUnusedObjects) { m_importUnusedObjects = importUnusedObjects; } bool OriginProjectParser::hasUnusedObjects() { m_originFile = new OriginFile((const char*)m_projectFileName.toLocal8Bit()); if (!m_originFile->parse()) { delete m_originFile; m_originFile = nullptr; return false; } for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { const Origin::SpreadSheet& spread = m_originFile->spread(i); if (spread.objectID < 0) return true; } for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { const Origin::Excel& excel = m_originFile->excel(i); if (excel.objectID < 0) return true; } for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { const Origin::Matrix& originMatrix = m_originFile->matrix(i); if (originMatrix.objectID < 0) return true; } delete m_originFile; m_originFile = nullptr; return false; } QString OriginProjectParser::supportedExtensions() { //TODO add opju later when liborigin supports it static const QString extensions = "*.opj *.OPJ"; return extensions; } unsigned int OriginProjectParser::findSpreadByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { const Origin::SpreadSheet& spread = m_originFile->spread(i); if (spread.name == name.toStdString()) { m_spreadNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findMatrixByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { const Origin::Matrix& originMatrix = m_originFile->matrix(i); if (originMatrix.name == name.toStdString()) { m_matrixNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findExcelByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { const Origin::Excel& excel = m_originFile->excel(i); if (excel.name == name.toStdString()) { m_excelNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findGraphByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { const Origin::Graph& graph = m_originFile->graph(i); if (graph.name == name.toStdString()) { m_graphNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findNoteByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { const Origin::Note& originNote = m_originFile->note(i); if (originNote.name == name.toStdString()) { m_noteNameList << name; return i; } } return 0; } //############################################################################## //############## Deserialization from Origin's project tree #################### //############################################################################## bool OriginProjectParser::load(Project* project, bool preview) { DEBUG("OriginProjectParser::load()"); //read and parse the m_originFile-file m_originFile = new OriginFile((const char*)m_projectFileName.toLocal8Bit()); if (!m_originFile->parse()) { delete m_originFile; m_originFile = nullptr; return false; } //Origin project tree and the iterator pointing to the root node const tree* projectTree = m_originFile->project(); tree::iterator projectIt = projectTree->begin(projectTree->begin()); m_spreadNameList.clear(); m_excelNameList.clear(); m_matrixNameList.clear(); m_graphNameList.clear(); m_noteNameList.clear(); //convert the project tree from liborigin's representation to LabPlot's project object project->setIsLoading(true); if (projectIt.node) { // only opj files from version >= 6.0 do have project tree DEBUG(" have a project tree"); QString name(QString::fromLatin1(projectIt->name.c_str())); project->setName(name); project->setCreationTime(creationTime(projectIt)); loadFolder(project, projectIt, preview); } else { // for lower versions put all windows on rootfolder DEBUG(" have no project tree"); int pos = m_projectFileName.lastIndexOf(QDir::separator()) + 1; project->setName((const char*)m_projectFileName.mid(pos).toLocal8Bit()); } // imports all loose windows (like prior version 6 which has no project tree) handleLooseWindows(project, preview); //restore column pointers: //1. extend the pathes to contain the parent structures first //2. restore the pointers from the pathes const QVector columns = project->children(AbstractAspect::ChildIndexFlag::Recursive); const QVector spreadsheets = project->children(AbstractAspect::ChildIndexFlag::Recursive); for (auto* curve : project->children(AbstractAspect::ChildIndexFlag::Recursive)) { curve->suppressRetransform(true); //x-column QString spreadsheetName = curve->xColumnPath().left(curve->xColumnPath().indexOf(QLatin1Char('/'))); for (const auto* spreadsheet : spreadsheets) { if (spreadsheet->name() == spreadsheetName) { const QString& newPath = spreadsheet->parentAspect()->path() + '/' + curve->xColumnPath(); curve->setXColumnPath(newPath); for (auto* column : columns) { if (!column) continue; if (column->path() == newPath) { curve->setXColumn(column); break; } } break; } } //x-column spreadsheetName = curve->yColumnPath().left(curve->yColumnPath().indexOf(QLatin1Char('/'))); for (const auto* spreadsheet : spreadsheets) { if (spreadsheet->name() == spreadsheetName) { const QString& newPath = spreadsheet->parentAspect()->path() + '/' + curve->yColumnPath(); curve->setYColumnPath(newPath); for (auto* column : columns) { if (!column) continue; if (column->path() == newPath) { curve->setYColumn(column); break; } } break; } } //TODO: error columns curve->suppressRetransform(false); } if (!preview) { for (auto* plot : project->children(AbstractAspect::ChildIndexFlag::Recursive)) { plot->setIsLoading(false); plot->retransform(); } } emit project->loaded(); project->setIsLoading(false); delete m_originFile; m_originFile = nullptr; return true; } bool OriginProjectParser::loadFolder(Folder* folder, tree::iterator baseIt, bool preview) { DEBUG("OriginProjectParser::loadFolder()") const tree* projectTree = m_originFile->project(); // do not skip anything if pathesToLoad() contains only root folder bool containsRootFolder = (folder->pathesToLoad().size() == 1 && folder->pathesToLoad().contains(folder->path())); if (containsRootFolder) { DEBUG(" pathesToLoad contains only folder path \"" << STDSTRING(folder->path()) << "\". Clearing pathes to load.") folder->setPathesToLoad(QStringList()); } //load folder's children: logic for reading the selected objects only is similar to Folder::readChildAspectElement for (tree::sibling_iterator it = projectTree->begin(baseIt); it != projectTree->end(baseIt); ++it) { QString name(QString::fromLatin1(it->name.c_str())); //name of the current child DEBUG(" * folder item name = " << STDSTRING(name)) //check whether we need to skip the loading of the current child if (!folder->pathesToLoad().isEmpty()) { //child's path is not available yet (child not added yet) -> construct the path manually const QString childPath = folder->path() + '/' + name; DEBUG(" path = " << STDSTRING(childPath)) //skip the current child aspect it is not in the list of aspects to be loaded if (folder->pathesToLoad().indexOf(childPath) == -1) { DEBUG(" skip it!") continue; } } //load top-level children AbstractAspect* aspect = nullptr; switch (it->type) { case Origin::ProjectNode::Folder: { DEBUG(" top level folder"); Folder* f = new Folder(name); if (!folder->pathesToLoad().isEmpty()) { //a child folder to be read -> provide the list of aspects to be loaded to the child folder, too. //since the child folder and all its children are not added yet (path() returns empty string), //we need to remove the path of the current child folder from the full pathes provided in pathesToLoad. //E.g. we want to import the path "Project/Folder/Spreadsheet" in the following project // Project // \Spreadsheet // \Folder // \Spreadsheet // //Here, we remove the part "Project/Folder/" and proceed for this child folder with "Spreadsheet" only. //With this the logic above where it is determined whether to import the child aspect or not works out. //manually construct the path of the child folder to be read const QString& curFolderPath = folder->path() + '/' + name; //remove the path of the current child folder QStringList pathesToLoadNew; for (const auto& path : folder->pathesToLoad()) { if (path.startsWith(curFolderPath)) pathesToLoadNew << path.right(path.length() - curFolderPath.length()); } f->setPathesToLoad(pathesToLoadNew); } loadFolder(f, it, preview); aspect = f; break; } case Origin::ProjectNode::SpreadSheet: { DEBUG(" top level spreadsheet"); Spreadsheet* spreadsheet = new Spreadsheet(name); loadSpreadsheet(spreadsheet, preview, name); aspect = spreadsheet; break; } case Origin::ProjectNode::Graph: { DEBUG(" top level graph"); Worksheet* worksheet = new Worksheet(name); worksheet->setIsLoading(true); worksheet->setTheme(QString()); loadWorksheet(worksheet, preview); aspect = worksheet; break; } case Origin::ProjectNode::Matrix: { DEBUG(" top level matrix"); const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(name)); DEBUG(" matrix name = " << originMatrix.name); DEBUG(" number of sheets = " << originMatrix.sheets.size()); if (originMatrix.sheets.size() == 1) { // single sheet -> load into a matrix Matrix* matrix = new Matrix(name); loadMatrix(matrix, preview); aspect = matrix; } else { // multiple sheets -> load into a workbook Workbook* workbook = new Workbook(name); loadMatrixWorkbook(workbook, preview); aspect = workbook; } break; } case Origin::ProjectNode::Excel: { DEBUG(" top level excel"); Workbook* workbook = new Workbook(name); loadWorkbook(workbook, preview); aspect = workbook; break; } case Origin::ProjectNode::Note: { DEBUG("top level note"); Note* note = new Note(name); loadNote(note, preview); aspect = note; break; } case Origin::ProjectNode::Graph3D: default: //TODO: add UnsupportedAspect break; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(creationTime(it)); aspect->setIsLoading(false); } } // ResultsLog QString resultsLog = QString::fromLatin1(m_originFile->resultsLogString().c_str()); if (resultsLog.length() > 0) { DEBUG("Results log:\t\tyes"); Note* note = new Note("ResultsLog"); if (preview) folder->addChildFast(note); else { //only import the log if it is in the list of aspects to be loaded const QString childPath = folder->path() + '/' + note->name(); if (folder->pathesToLoad().indexOf(childPath) != -1) { note->setText(resultsLog); folder->addChildFast(note); } } } else DEBUG("Results log:\t\tno"); return folder; } void OriginProjectParser::handleLooseWindows(Folder* folder, bool preview) { DEBUG("OriginProjectParser::handleLooseWindows()"); QDEBUG("pathes to load:" << folder->pathesToLoad()); m_spreadNameList.removeDuplicates(); m_excelNameList.removeDuplicates(); m_matrixNameList.removeDuplicates(); m_graphNameList.removeDuplicates(); m_noteNameList.removeDuplicates(); QDEBUG(" spreads =" << m_spreadNameList); QDEBUG(" excels =" << m_excelNameList); QDEBUG(" matrices =" << m_matrixNameList); QDEBUG(" graphs =" << m_graphNameList); QDEBUG(" notes =" << m_noteNameList); DEBUG("Number of spreads loaded:\t" << m_spreadNameList.size() << ", in file: " << m_originFile->spreadCount()); DEBUG("Number of excels loaded:\t" << m_excelNameList.size() << ", in file: " << m_originFile->excelCount()); DEBUG("Number of matrices loaded:\t" << m_matrixNameList.size() << ", in file: " << m_originFile->matrixCount()); DEBUG("Number of graphs loaded:\t" << m_graphNameList.size() << ", in file: " << m_originFile->graphCount()); DEBUG("Number of notes loaded:\t\t" << m_noteNameList.size() << ", in file: " << m_originFile->noteCount()); // loop over all spreads to find loose ones for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::SpreadSheet& spread = m_originFile->spread(i); QString name = QString::fromStdString(spread.name); DEBUG(" spread.objectId = " << spread.objectID); // skip unused spreads if selected if (spread.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose spread: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; // we could also use spread.loose if (!m_spreadNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { DEBUG(" Adding loose spread: " << STDSTRING(name)); Spreadsheet* spreadsheet = new Spreadsheet(name); loadSpreadsheet(spreadsheet, preview, name); aspect = spreadsheet; } if (aspect) { folder->addChildFast(aspect); DEBUG(" creation time as reported by liborigin: " << spread.creationDate); aspect->setCreationTime(QDateTime::fromTime_t(spread.creationDate)); } } // loop over all excels to find loose ones for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Excel& excel = m_originFile->excel(i); QString name = QString::fromStdString(excel.name); DEBUG(" excel.objectId = " << excel.objectID); // skip unused data sets if selected if (excel.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose excel: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; // we could also use excel.loose if (!m_excelNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { DEBUG(" Adding loose excel: " << STDSTRING(name)); DEBUG(" containing number of sheets = " << excel.sheets.size()); Workbook* workbook = new Workbook(name); loadWorkbook(workbook, preview); aspect = workbook; } if (aspect) { folder->addChildFast(aspect); DEBUG(" creation time as reported by liborigin: " << excel.creationDate); aspect->setCreationTime(QDateTime::fromTime_t(excel.creationDate)); } } // loop over all matrices to find loose ones for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Matrix& originMatrix = m_originFile->matrix(i); QString name = QString::fromStdString(originMatrix.name); DEBUG(" originMatrix.objectId = " << originMatrix.objectID); // skip unused data sets if selected if (originMatrix.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose matrix: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; if (!m_matrixNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { DEBUG(" Adding loose matrix: " << STDSTRING(name)); DEBUG(" containing number of sheets = " << originMatrix.sheets.size()); if (originMatrix.sheets.size() == 1) { // single sheet -> load into a matrix Matrix* matrix = new Matrix(name); loadMatrix(matrix, preview); aspect = matrix; } else { // multiple sheets -> load into a workbook Workbook* workbook = new Workbook(name); loadMatrixWorkbook(workbook, preview); aspect = workbook; } } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(originMatrix.creationDate)); } } // handle loose graphs (is this even possible?) for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Graph& graph = m_originFile->graph(i); QString name = QString::fromStdString(graph.name); DEBUG(" graph.objectId = " << graph.objectID); // skip unused graph if selected if (graph.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose graph: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; if (!m_graphNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { DEBUG(" Adding loose graph: " << STDSTRING(name)); Worksheet* worksheet = new Worksheet(name); loadWorksheet(worksheet, preview); aspect = worksheet; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(graph.creationDate)); } } // handle loose notes (is this even possible?) for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Note& originNote = m_originFile->note(i); QString name = QString::fromStdString(originNote.name); DEBUG(" originNote.objectId = " << originNote.objectID); // skip unused notes if selected if (originNote.objectID < 0 && !m_importUnusedObjects) { DEBUG(" Dropping unused loose note: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; if (!m_noteNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { DEBUG(" Adding loose note: " << STDSTRING(name)); Note* note = new Note(name); loadNote(note, preview); aspect = note; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(originNote.creationDate)); } } } bool OriginProjectParser::loadWorkbook(Workbook* workbook, bool preview) { DEBUG("loadWorkbook()"); //load workbook sheets const Origin::Excel& excel = m_originFile->excel(findExcelByName(workbook->name())); DEBUG(" excel name = " << excel.name); DEBUG(" number of sheets = " << excel.sheets.size()); for (unsigned int s = 0; s < excel.sheets.size(); ++s) { Spreadsheet* spreadsheet = new Spreadsheet(QString::fromLatin1(excel.sheets[s].name.c_str())); loadSpreadsheet(spreadsheet, preview, workbook->name(), s); workbook->addChildFast(spreadsheet); } return true; } // load spreadsheet from spread (sheetIndex == -1) or from excel (only sheet sheetIndex) bool OriginProjectParser::loadSpreadsheet(Spreadsheet* spreadsheet, bool preview, const QString& name, int sheetIndex) { DEBUG("loadSpreadsheet() sheetIndex = " << sheetIndex); //load spreadsheet data Origin::SpreadSheet spread; Origin::Excel excel; if (sheetIndex == -1) // spread spread = m_originFile->spread(findSpreadByName(name)); else { excel = m_originFile->excel(findExcelByName(name)); spread = excel.sheets[sheetIndex]; } const size_t cols = spread.columns.size(); int rows = 0; for (size_t j = 0; j < cols; ++j) rows = std::max((int)spread.columns[j].data.size(), rows); // alternative: int rows = excel.maxRows; DEBUG("loadSpreadsheet() cols/maxRows = " << cols << "/" << rows); //TODO QLocale locale = mw->locale(); spreadsheet->setRowCount(rows); spreadsheet->setColumnCount((int)cols); if (sheetIndex == -1) spreadsheet->setComment(QString::fromLatin1(spread.label.c_str())); else spreadsheet->setComment(QString::fromLatin1(excel.label.c_str())); //in Origin column width is measured in characters, we need to convert to pixels //TODO: determine the font used in Origin in order to get the same column width as in Origin QFont font; QFontMetrics fm(font); const int scaling_factor = fm.maxWidth(); for (size_t j = 0; j < cols; ++j) { Origin::SpreadColumn column = spread.columns[j]; Column* col = spreadsheet->column((int)j); QString name(column.name.c_str()); col->setName(name.replace(QRegExp(".*_"), QString())); if (preview) continue; //TODO: we don't support any formulas for cells yet. // if (column.command.size() > 0) // col->setFormula(Interval(0, rows), QString(column.command.c_str())); col->setComment(QString::fromLatin1(column.comment.c_str())); col->setWidth((int)column.width * scaling_factor); //plot designation switch (column.type) { case Origin::SpreadColumn::X: col->setPlotDesignation(AbstractColumn::PlotDesignation::X); break; case Origin::SpreadColumn::Y: col->setPlotDesignation(AbstractColumn::PlotDesignation::Y); break; case Origin::SpreadColumn::Z: col->setPlotDesignation(AbstractColumn::PlotDesignation::Z); break; case Origin::SpreadColumn::XErr: col->setPlotDesignation(AbstractColumn::PlotDesignation::XError); break; case Origin::SpreadColumn::YErr: col->setPlotDesignation(AbstractColumn::PlotDesignation::YError); break; case Origin::SpreadColumn::Label: case Origin::SpreadColumn::NONE: default: col->setPlotDesignation(AbstractColumn::PlotDesignation::NoDesignation); } QString format; switch (column.valueType) { case Origin::Numeric: { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const double value = column.data.at(i).as_double(); if (value != _ONAN) col->setValueAt(i, value); } loadColumnNumericFormat(column, col); break; } case Origin::TextNumeric: { //A TextNumeric column can contain numeric and string values, there is no equivalent column mode in LabPlot. // -> Set the column mode as 'Numeric' or 'Text' depending on the type of first non-empty element in column. for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const Origin::variant value(column.data.at(i)); if (value.type() == Origin::Variant::V_DOUBLE) { if (value.as_double() != _ONAN) break; } else { if (value.as_string() != nullptr) { col->setColumnMode(AbstractColumn::ColumnMode::Text); break; } } } if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const double value = column.data.at(i).as_double(); if (column.data.at(i).type() == Origin::Variant::V_DOUBLE && value != _ONAN) col->setValueAt(i, value); } loadColumnNumericFormat(column, col); } else { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const Origin::variant value(column.data.at(i)); if (value.type() == Origin::Variant::V_STRING) { if (value.as_string() != nullptr) col->setTextAt(i, value.as_string()); } else { if (value.as_double() != _ONAN) col->setTextAt(i, QString::number(value.as_double())); } } } break; } case Origin::Text: col->setColumnMode(AbstractColumn::ColumnMode::Text); for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setTextAt(i, column.data[i].as_string()); break; case Origin::Time: { switch (column.valueTypeSpecification + 128) { case Origin::TIME_HH_MM: format = "hh:mm"; break; case Origin::TIME_HH: format = "hh"; break; case Origin::TIME_HH_MM_SS: format = "hh:mm:ss"; break; case Origin::TIME_HH_MM_SS_ZZ: format = "hh:mm:ss.zzz"; break; case Origin::TIME_HH_AP: format = "hh ap"; break; case Origin::TIME_HH_MM_AP: format = "hh:mm ap"; break; case Origin::TIME_MM_SS: format = "mm:ss"; break; case Origin::TIME_MM_SS_ZZ: format = "mm:ss.zzz"; break; case Origin::TIME_HHMM: format = "hhmm"; break; case Origin::TIME_HHMMSS: format = "hhmmss"; break; case Origin::TIME_HH_MM_SS_ZZZ: format = "hh:mm:ss.zzz"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::ColumnMode::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Date: { switch (column.valueTypeSpecification) { case Origin::DATE_DD_MM_YYYY: format = "dd/MM/yyyy"; break; case Origin::DATE_DD_MM_YYYY_HH_MM: format = "dd/MM/yyyy HH:mm"; break; case Origin::DATE_DD_MM_YYYY_HH_MM_SS: format = "dd/MM/yyyy HH:mm:ss"; break; case Origin::DATE_DDMMYYYY: case Origin::DATE_DDMMYYYY_HH_MM: case Origin::DATE_DDMMYYYY_HH_MM_SS: format = "dd.MM.yyyy"; break; case Origin::DATE_MMM_D: format = "MMM d"; break; case Origin::DATE_M_D: format = "M/d"; break; case Origin::DATE_D: format = 'd'; break; case Origin::DATE_DDD: case Origin::DATE_DAY_LETTER: format = "ddd"; break; case Origin::DATE_YYYY: format = "yyyy"; break; case Origin::DATE_YY: format = "yy"; break; case Origin::DATE_YYMMDD: case Origin::DATE_YYMMDD_HH_MM: case Origin::DATE_YYMMDD_HH_MM_SS: case Origin::DATE_YYMMDD_HHMM: case Origin::DATE_YYMMDD_HHMMSS: format = "yyMMdd"; break; case Origin::DATE_MMM: case Origin::DATE_MONTH_LETTER: format = "MMM"; break; case Origin::DATE_M_D_YYYY: format = "M-d-yyyy"; break; default: format = "dd.MM.yyyy"; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::ColumnMode::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Month: { switch (column.valueTypeSpecification) { case Origin::MONTH_MMM: format = "MMM"; break; case Origin::MONTH_MMMM: format = "MMMM"; break; case Origin::MONTH_LETTER: format = 'M'; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::ColumnMode::Month); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Day: { switch (column.valueTypeSpecification) { case Origin::DAY_DDD: format = "ddd"; break; case Origin::DAY_DDDD: format = "dddd"; break; case Origin::DAY_LETTER: format = 'd'; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::ColumnMode::Day); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::ColumnHeading: case Origin::TickIndexedDataset: case Origin::Categorical: break; } } //TODO: "hidden" not supported yet // if (spread.hidden || spread.loose) // mw->hideWindow(spreadsheet); return true; } void OriginProjectParser::loadColumnNumericFormat(const Origin::SpreadColumn& originColumn, Column* column) const { if (originColumn.numericDisplayType != 0) { int fi = 0; switch (originColumn.valueTypeSpecification) { case Origin::Decimal: fi = 1; break; case Origin::Scientific: fi = 2; break; case Origin::Engineering: case Origin::DecimalWithMarks: break; } Double2StringFilter* filter = static_cast(column->outputFilter()); filter->setNumericFormat(fi); filter->setNumDigits(originColumn.decimalPlaces); } } bool OriginProjectParser::loadMatrixWorkbook(Workbook* workbook, bool preview) { DEBUG("loadMatrixWorkbook()"); //load matrix workbook sheets const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(workbook->name())); for (size_t s = 0; s < originMatrix.sheets.size(); ++s) { Matrix* matrix = new Matrix(QString::fromLatin1(originMatrix.sheets[s].name.c_str())); loadMatrix(matrix, preview, s, workbook->name()); workbook->addChildFast(matrix); } return true; } bool OriginProjectParser::loadMatrix(Matrix* matrix, bool preview, size_t sheetIndex, const QString& mwbName) { DEBUG("loadMatrix()"); //import matrix data const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(mwbName)); if (preview) return true; //in Origin column width is measured in characters, we need to convert to pixels //TODO: determine the font used in Origin in order to get the same column width as in Origin QFont font; QFontMetrics fm(font); const int scaling_factor = fm.maxWidth(); const Origin::MatrixSheet& layer = originMatrix.sheets[sheetIndex]; const int colCount = layer.columnCount; const int rowCount = layer.rowCount; matrix->setRowCount(rowCount); matrix->setColumnCount(colCount); matrix->setFormula(layer.command.c_str()); //TODO: how to handle different widths for different columns? for (int j = 0; j < colCount; j++) matrix->setColumnWidth(j, layer.width * scaling_factor); //TODO: check column major vs. row major to improve the performance here for (int i = 0; i < rowCount; i++) { for (int j = 0; j < colCount; j++) matrix->setCell(i, j, layer.data[j + i*colCount]); } char format = 'g'; //TODO: prec not support by Matrix //int prec = 6; switch (layer.valueTypeSpecification) { case 0: //Decimal 1000 format = 'f'; // prec = layer.decimalPlaces; break; case 1: //Scientific format = 'e'; // prec = layer.decimalPlaces; break; case 2: //Engineering case 3: //Decimal 1,000 format = 'g'; // prec = layer.significantDigits; break; } matrix->setNumericFormat(format); return true; } bool OriginProjectParser::loadWorksheet(Worksheet* worksheet, bool preview) { DEBUG("OriginProjectParser::loadWorksheet()"); //load worksheet data const Origin::Graph& graph = m_originFile->graph(findGraphByName(worksheet->name())); DEBUG(" graph name = " << graph.name); worksheet->setComment(graph.label.c_str()); //TODO: width, height, view mode (print view, page view, window view, draft view) //Origin allows to freely resize the window and ajusts the size of the plot (layer) automatically //by keeping a certain width-to-height ratio. It's not clear what the actual size of the plot/layer is and how to handle this. //For now we simply create a new wokrsheet here with it's default size and make it using the whole view size. //Later we can decide to use one of the following properties: // 1) Window.frameRect gives Rect-corner coordinates (in pixels) of the Window object // 2) GraphLayer.clientRect gives Rect-corner coordinates (pixels) of the Layer inside the (printer?) page. // 3) Graph.width, Graph.height give the (printer?) page size in pixels. // const QRectF size(0, 0, // Worksheet::convertToSceneUnits(graph.width/600., Worksheet::Inch), // Worksheet::convertToSceneUnits(graph.height/600., Worksheet::Inch)); // worksheet->setPageRect(size); worksheet->setUseViewSize(true); QHash textLabelPositions; // worksheet background color const Origin::ColorGradientDirection bckgColorGradient = graph.windowBackgroundColorGradient; const Origin::Color bckgBaseColor = graph.windowBackgroundColorBase; const Origin::Color bckgEndColor = graph.windowBackgroundColorEnd; worksheet->setBackgroundColorStyle(backgroundColorStyle(bckgColorGradient)); switch (bckgColorGradient) { case Origin::ColorGradientDirection::NoGradient: case Origin::ColorGradientDirection::TopLeft: case Origin::ColorGradientDirection::Left: case Origin::ColorGradientDirection::BottomLeft: case Origin::ColorGradientDirection::Top: worksheet->setBackgroundFirstColor(color(bckgEndColor)); worksheet->setBackgroundSecondColor(color(bckgBaseColor)); break; case Origin::ColorGradientDirection::Center: break; case Origin::ColorGradientDirection::Bottom: case Origin::ColorGradientDirection::TopRight: case Origin::ColorGradientDirection::Right: case Origin::ColorGradientDirection::BottomRight: worksheet->setBackgroundFirstColor(color(bckgBaseColor)); worksheet->setBackgroundSecondColor(color(bckgEndColor)); } //TODO: do we need changes on the worksheet layout? //add plots int index = 1; for (const auto& layer : graph.layers) { if (!layer.is3D()) { CartesianPlot* plot = new CartesianPlot(i18n("Plot%1", QString::number(index))); worksheet->addChildFast(plot); plot->setIsLoading(true); //TODO: width, height //background color const Origin::Color& regColor = layer.backgroundColor; if (regColor.type == Origin::Color::None) plot->plotArea()->setBackgroundOpacity(0); else plot->plotArea()->setBackgroundFirstColor(color(regColor)); //border if (layer.borderType == Origin::BorderType::None) plot->plotArea()->setBorderPen(QPen(Qt::NoPen)); else plot->plotArea()->setBorderPen(QPen(Qt::SolidLine)); //ranges plot->setAutoScaleX(false); plot->setAutoScaleY(false); const Origin::GraphAxis& originXAxis = layer.xAxis; const Origin::GraphAxis& originYAxis = layer.yAxis; plot->setXMin(originXAxis.min); plot->setXMax(originXAxis.max); plot->setYMin(originYAxis.min); plot->setYMax(originYAxis.max); //scales switch (originXAxis.scale) { case Origin::GraphAxis::Linear: plot->setXScale(CartesianPlot::ScaleLinear); break; case Origin::GraphAxis::Log10: plot->setXScale(CartesianPlot::ScaleLog10); break; case Origin::GraphAxis::Ln: plot->setXScale(CartesianPlot::ScaleLn); break; case Origin::GraphAxis::Log2: plot->setXScale(CartesianPlot::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: plot->setXScale(CartesianPlot::ScaleLinear); break; } switch (originYAxis.scale) { case Origin::GraphAxis::Linear: plot->setYScale(CartesianPlot::ScaleLinear); break; case Origin::GraphAxis::Log10: plot->setYScale(CartesianPlot::ScaleLog10); break; case Origin::GraphAxis::Ln: plot->setYScale(CartesianPlot::ScaleLn); break; case Origin::GraphAxis::Log2: plot->setYScale(CartesianPlot::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: plot->setYScale(CartesianPlot::ScaleLinear); break; } //axes //x bottom if (layer.curves.size()) { Origin::GraphCurve originCurve = layer.curves[0]; QString xColumnName = QString::fromLatin1(originCurve.xColumnName.c_str()); //TODO: "Partikelgrö" DEBUG(" xColumnName = " << STDSTRING(xColumnName)); QDEBUG(" UTF8 xColumnName = " << xColumnName.toUtf8()); QString yColumnName = QString::fromLatin1(originCurve.yColumnName.c_str()); if (!originXAxis.formatAxis[0].hidden) { Axis* axis = new Axis("x", Axis::AxisHorizontal); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisBottom); plot->addChildFast(axis); loadAxis(originXAxis, axis, 0, xColumnName); axis->setSuppressRetransform(false); } //x top if (!originXAxis.formatAxis[1].hidden) { Axis* axis = new Axis("x top", Axis::AxisHorizontal); axis->setPosition(Axis::AxisTop); axis->setSuppressRetransform(true); plot->addChildFast(axis); loadAxis(originXAxis, axis, 1, xColumnName); axis->setSuppressRetransform(false); } //y left if (!originYAxis.formatAxis[0].hidden) { Axis* axis = new Axis("y", Axis::AxisVertical); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisLeft); plot->addChildFast(axis); loadAxis(originYAxis, axis, 0, yColumnName); axis->setSuppressRetransform(false); } //y right if (!originYAxis.formatAxis[1].hidden) { Axis* axis = new Axis("y right", Axis::AxisVertical); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisRight); plot->addChildFast(axis); loadAxis(originYAxis, axis, 1, yColumnName); axis->setSuppressRetransform(false); } } else { //TODO: ? } //range breaks //TODO //add legend if available const Origin::TextBox& originLegend = layer.legend; const QString& legendText = QString::fromLatin1(originLegend.text.c_str()); DEBUG(" legend text = " << STDSTRING(legendText)); if (!originLegend.text.empty()) { CartesianPlotLegend* legend = new CartesianPlotLegend(plot, i18n("legend")); //Origin's legend uses "\l(...)" or "\L(...)" string to format the legend symbol // and "%(...) to format the legend text for each curve //s. a. https://www.originlab.com/doc/Origin-Help/Legend-ManualControl //the text before these formatting tags, if available, is interpreted as the legend title QString legendTitle; //search for the first occurrence of the legend symbol substring int index = legendText.indexOf(QLatin1String("\\l("), 0, Qt::CaseInsensitive); if (index != -1) legendTitle = legendText.left(index); else { //check legend text index = legendText.indexOf(QLatin1String("%(")); if (index != -1) legendTitle = legendText.left(index); } legendTitle = legendTitle.trimmed(); if (!legendTitle.isEmpty()) legendTitle = parseOriginText(legendTitle); DEBUG(" legend title = " << STDSTRING(legendTitle)); legend->title()->setText(legendTitle); //TODO: text color //const Origin::Color& originColor = originLegend.color; //position //TODO: for the first release with OPJ support we put the legend to the bottom left corner, //in the next release we'll evaluate originLegend.clientRect giving the position inside of the whole page in Origin. //In Origin the legend can be placed outside of the plot which is not possible in LabPlot. //To achieve this we'll need to increase padding area in the plot and to place the legend outside of the plot area. CartesianPlotLegend::PositionWrapper position; - position.horizontalPosition = CartesianPlotLegend::hPositionRight; - position.verticalPosition = CartesianPlotLegend::vPositionBottom; + position.horizontalPosition = WorksheetElement::HorizontalPosition::Right; + position.verticalPosition = WorksheetElement::VerticalPosition::Bottom; legend->setPosition(position); //rotation legend->setRotationAngle(originLegend.rotation); //border line if (originLegend.borderType == Origin::BorderType::None) legend->setBorderPen(QPen(Qt::NoPen)); else legend->setBorderPen(QPen(Qt::SolidLine)); //background color, determine it with the help of the border type if (originLegend.borderType == Origin::BorderType::DarkMarble) legend->setBackgroundFirstColor(Qt::darkGray); else if (originLegend.borderType == Origin::BorderType::BlackOut) legend->setBackgroundFirstColor(Qt::black); else legend->setBackgroundFirstColor(Qt::white); plot->addLegend(legend); } //texts for (const auto& s : layer.texts) { DEBUG("EXTRA TEXT = " << s.text.c_str()); TextLabel* label = new TextLabel("text label"); label->setText(parseOriginText(QString::fromLatin1(s.text.c_str()))); plot->addChild(label); label->setParentGraphicsItem(plot->graphicsItem()); //position //determine the relative position inside of the layer rect const float horRatio = (float)(s.clientRect.left-layer.clientRect.left)/(layer.clientRect.right-layer.clientRect.left); const float vertRatio = (float)(s.clientRect.top-layer.clientRect.top)/(layer.clientRect.bottom-layer.clientRect.top); textLabelPositions[label] = QSizeF(horRatio, vertRatio); DEBUG("horizontal/vertical ratio = " << horRatio << ", " << vertRatio); //rotation label->setRotationAngle(s.rotation); //TODO: // Color color; // unsigned short fontSize; // int tab; // BorderType borderType; // Attach attach; } //curves int curveIndex = 1; for (const auto& originCurve : layer.curves) { QString data(originCurve.dataName.c_str()); switch (data[0].toLatin1()) { case 'T': case 'E': { if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::Scatter || originCurve.type == Origin::GraphCurve::LineSymbol || originCurve.type == Origin::GraphCurve::ErrorBar || originCurve.type == Origin::GraphCurve::XErrorBar) { // parse and use legend text // find substring between %c{curveIndex} and %c{curveIndex+1} int pos1 = legendText.indexOf(QString("\\c{%1}").arg(curveIndex)) + 5; int pos2 = legendText.indexOf(QString("\\c{%1}").arg(curveIndex+1)); QString curveText = legendText.mid(pos1, pos2 - pos1); // replace %(1), %(2), etc. with curve name curveText.replace(QString("%(%1)").arg(curveIndex), QString::fromLatin1(originCurve.yColumnName.c_str())); curveText = curveText.trimmed(); DEBUG(" curve " << curveIndex << " text = " << STDSTRING(curveText)); //XYCurve* xyCurve = new XYCurve(i18n("Curve%1", QString::number(curveIndex))); //TODO: curve (legend) does not support HTML text yet. //XYCurve* xyCurve = new XYCurve(curveText); XYCurve* curve = new XYCurve(QString::fromLatin1(originCurve.yColumnName.c_str())); const QString& tableName = data.right(data.length() - 2); curve->setXColumnPath(tableName + '/' + originCurve.xColumnName.c_str()); curve->setYColumnPath(tableName + '/' + originCurve.yColumnName.c_str()); curve->suppressRetransform(true); if (!preview) loadCurve(originCurve, curve); plot->addChildFast(curve); curve->suppressRetransform(false); } else if (originCurve.type == Origin::GraphCurve::Column) { //vertical bars } else if (originCurve.type == Origin::GraphCurve::Bar) { //horizontal bars } else if (originCurve.type == Origin::GraphCurve::Histogram) { } } break; case 'F': { Origin::Function function; const vector::difference_type funcIndex = m_originFile->functionIndex(data.right(data.length()-2).toStdString().c_str()); if (funcIndex < 0) { ++curveIndex; continue; } function = m_originFile->function(funcIndex); XYEquationCurve* xyEqCurve = new XYEquationCurve(function.name.c_str()); XYEquationCurve::EquationData eqData; eqData.count = function.totalPoints; eqData.expression1 = QString(function.formula.c_str()); if (function.type == Origin::Function::Polar) { eqData.type = XYEquationCurve::Polar; //replace 'x' by 'phi' eqData.expression1 = eqData.expression1.replace('x', "phi"); //convert from degrees to radians eqData.min = QString::number(function.begin/180) + QLatin1String("*pi"); eqData.max = QString::number(function.end/180) + QLatin1String("*pi"); } else { eqData.expression1 = QString(function.formula.c_str()); eqData.min = QString::number(function.begin); eqData.max = QString::number(function.end); } xyEqCurve->suppressRetransform(true); xyEqCurve->setEquationData(eqData); if (!preview) loadCurve(originCurve, xyEqCurve); plot->addChildFast(xyEqCurve); xyEqCurve->suppressRetransform(false); } } ++curveIndex; } } else { //no support for 3D plots yet //TODO: add an "UnsupportedAspect" here } ++index; } if (!preview) { worksheet->updateLayout(); //worksheet and plots got their sizes, //-> position all text labels inside the plots correctly by converting //the relative positions determined above to the absolute values QHash::const_iterator it = textLabelPositions.constBegin(); while (it != textLabelPositions.constEnd()) { TextLabel* label = it.key(); const QSizeF& ratios = it.value(); const CartesianPlot* plot = static_cast(label->parentAspect()); TextLabel::PositionWrapper position = label->position(); position.point.setX(plot->dataRect().width()*(ratios.width()-0.5)); position.point.setY(plot->dataRect().height()*(ratios.height()-0.5)); label->setPosition(position); ++it; } } return true; } /* * sets the axis properties (format and ticks) as defined in \c originAxis in \c axis, * \c index being 0 or 1 for "top" and "bottom" or "left" and "right" for horizontal or vertical axes, respectively. */ void OriginProjectParser::loadAxis(const Origin::GraphAxis& originAxis, Axis* axis, int index, const QString& axisTitle) const { // int axisPosition; // possible values: // 0: Axis is at default position // 1: Axis is at (axisPositionValue)% from standard position // 2: Axis is at (axisPositionValue) position of orthogonal axis // double axisPositionValue; // bool zeroLine; // bool oppositeLine; //ranges axis->setStart(originAxis.min); axis->setEnd(originAxis.max); //ticks axis->setMajorTicksType(Axis::TicksSpacing); axis->setMajorTicksSpacing(originAxis.step); axis->setMinorTicksType(Axis::TicksTotalNumber); axis->setMinorTicksNumber(originAxis.minorTicks); //scale switch (originAxis.scale) { case Origin::GraphAxis::Linear: axis->setScale(Axis::ScaleLinear); break; case Origin::GraphAxis::Log10: axis->setScale(Axis::ScaleLog10); break; case Origin::GraphAxis::Ln: axis->setScale(Axis::ScaleLn); break; case Origin::GraphAxis::Log2: axis->setScale(Axis::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: axis->setScale(Axis::ScaleLinear); break; } //major grid const Origin::GraphGrid& majorGrid = originAxis.majorGrid; QPen gridPen = axis->majorGridPen(); Qt::PenStyle penStyle(Qt::NoPen); if (!majorGrid.hidden) { switch (majorGrid.style) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } } gridPen.setStyle(penStyle); Origin::Color gridColor; gridColor.type = Origin::Color::ColorType::Regular; gridColor.regular = majorGrid.color; gridPen.setColor(OriginProjectParser::color(gridColor)); - gridPen.setWidthF(Worksheet::convertToSceneUnits(majorGrid.width, Worksheet::Point)); + gridPen.setWidthF(Worksheet::convertToSceneUnits(majorGrid.width, Worksheet::Unit::Point)); axis->setMajorGridPen(gridPen); //minor grid const Origin::GraphGrid& minorGrid = originAxis.minorGrid; gridPen = axis->minorGridPen(); penStyle = Qt::NoPen; if (!minorGrid.hidden) { switch (minorGrid.style) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } } gridPen.setStyle(penStyle); gridColor.regular = minorGrid.color; gridPen.setColor(OriginProjectParser::color(gridColor)); - gridPen.setWidthF(Worksheet::convertToSceneUnits(minorGrid.width, Worksheet::Point)); + gridPen.setWidthF(Worksheet::convertToSceneUnits(minorGrid.width, Worksheet::Unit::Point)); axis->setMinorGridPen(gridPen); //process Origin::GraphAxisFormat const Origin::GraphAxisFormat& axisFormat = originAxis.formatAxis[index]; QPen pen; Origin::Color color; color.type = Origin::Color::ColorType::Regular; color.regular = axisFormat.color; pen.setColor(OriginProjectParser::color(color)); - pen.setWidthF(Worksheet::convertToSceneUnits(axisFormat.thickness, Worksheet::Point)); + pen.setWidthF(Worksheet::convertToSceneUnits(axisFormat.thickness, Worksheet::Unit::Point)); axis->setLinePen(pen); - axis->setMajorTicksLength( Worksheet::convertToSceneUnits(axisFormat.majorTickLength, Worksheet::Point) ); + axis->setMajorTicksLength( Worksheet::convertToSceneUnits(axisFormat.majorTickLength, Worksheet::Unit::Point) ); axis->setMajorTicksDirection( (Axis::TicksFlags) axisFormat.majorTicksType); axis->setMajorTicksPen(pen); axis->setMinorTicksLength( axis->majorTicksLength()/2); // minorTicksLength is half of majorTicksLength axis->setMinorTicksDirection( (Axis::TicksFlags) axisFormat.minorTicksType); axis->setMinorTicksPen(pen); QString titleText = parseOriginText(QString::fromLatin1(axisFormat.label.text.c_str())); DEBUG(" axis title text = " << STDSTRING(titleText)); //TODO: parseOriginText() returns html formatted string. What is axisFormat.color used for? //TODO: use axisFormat.fontSize to override the global font size for the hmtl string? //TODO: convert special character here too DEBUG(" curve name = " << STDSTRING(axisTitle)); titleText.replace("%(?X)", axisTitle); titleText.replace("%(?Y)", axisTitle); DEBUG(" axis title = " << STDSTRING(titleText)); axis->title()->setText(titleText); axis->title()->setRotationAngle(axisFormat.label.rotation); axis->setLabelsPrefix(axisFormat.prefix.c_str()); axis->setLabelsSuffix(axisFormat.suffix.c_str()); //TODO: handle string factor member in GraphAxisFormat //process Origin::GraphAxisTick const Origin::GraphAxisTick& tickAxis = originAxis.tickAxis[index]; if (tickAxis.showMajorLabels) { color.type = Origin::Color::ColorType::Regular; color.regular = tickAxis.color; axis->setLabelsColor(OriginProjectParser::color(color)); //TODO: how to set labels position (top vs. bottom)? } else { axis->setLabelsPosition(Axis::LabelsPosition::NoLabels); } //TODO: handle ValueType valueType member in GraphAxisTick //TODO: handle int valueTypeSpecification in GraphAxisTick //precision if (tickAxis.decimalPlaces == -1) axis->setLabelsAutoPrecision(true); else { axis->setLabelsPrecision(tickAxis.decimalPlaces); axis->setLabelsAutoPrecision(false); } QFont font; //TODO: font family? - font.setPixelSize( Worksheet::convertToSceneUnits(tickAxis.fontSize, Worksheet::Point) ); + font.setPixelSize( Worksheet::convertToSceneUnits(tickAxis.fontSize, Worksheet::Unit::Point) ); font.setBold(tickAxis.fontBold); axis->setLabelsFont(font); //TODO: handle string dataName member in GraphAxisTick //TODO: handle string columnName member in GraphAxisTick axis->setLabelsRotationAngle(tickAxis.rotation); } void OriginProjectParser::loadCurve(const Origin::GraphCurve& originCurve, XYCurve* curve) const { //line properties QPen pen = curve->linePen(); Qt::PenStyle penStyle(Qt::NoPen); if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::LineSymbol) { switch (originCurve.lineConnect) { case Origin::GraphCurve::NoLine: curve->setLineType(XYCurve::NoLine); break; case Origin::GraphCurve::Straight: curve->setLineType(XYCurve::Line); break; case Origin::GraphCurve::TwoPointSegment: curve->setLineType(XYCurve::Segments2); break; case Origin::GraphCurve::ThreePointSegment: curve->setLineType(XYCurve::Segments3); break; case Origin::GraphCurve::BSpline: case Origin::GraphCurve::Bezier: case Origin::GraphCurve::Spline: curve->setLineType(XYCurve::SplineCubicNatural); break; case Origin::GraphCurve::StepHorizontal: curve->setLineType(XYCurve::StartHorizontal); break; case Origin::GraphCurve::StepVertical: curve->setLineType(XYCurve::StartVertical); break; case Origin::GraphCurve::StepHCenter: curve->setLineType(XYCurve::MidpointHorizontal); break; case Origin::GraphCurve::StepVCenter: curve->setLineType(XYCurve::MidpointVertical); break; } switch (originCurve.lineStyle) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } pen.setStyle(penStyle); - pen.setWidthF( Worksheet::convertToSceneUnits(originCurve.lineWidth, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(originCurve.lineWidth, Worksheet::Unit::Point) ); pen.setColor(color(originCurve.lineColor)); curve->setLineOpacity(1 - originCurve.lineTransparency/255); //TODO: handle unsigned char boxWidth of Origin::GraphCurve } pen.setStyle(penStyle); curve->setLinePen(pen); //symbol properties if (originCurve.type == Origin::GraphCurve::Scatter || originCurve.type == Origin::GraphCurve::LineSymbol) { //try to map the different symbols, mapping is not exact curve->setSymbolsRotationAngle(0); switch (originCurve.symbolShape) { case 0: //NoSymbol curve->setSymbolsStyle(Symbol::NoSymbols); break; case 1: //Rect curve->setSymbolsStyle(Symbol::Square); break; case 2: //Ellipse case 20://Sphere curve->setSymbolsStyle(Symbol::Circle); break; case 3: //UTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 4: //DTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 5: //Diamond curve->setSymbolsStyle(Symbol::Diamond); break; case 6: //Cross + curve->setSymbolsStyle(Symbol::Cross); break; case 7: //Cross x curve->setSymbolsStyle(Symbol::Cross); break; case 8: //Snow curve->setSymbolsStyle(Symbol::Star4); break; case 9: //Horizontal - curve->setSymbolsStyle(Symbol::Line); curve->setSymbolsRotationAngle(90); break; case 10: //Vertical | curve->setSymbolsStyle(Symbol::Line); break; case 15: //LTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 16: //RTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 17: //Hexagon case 19: //Pentagon curve->setSymbolsStyle(Symbol::Square); break; case 18: //Star curve->setSymbolsStyle(Symbol::Star5); break; default: curve->setSymbolsStyle(Symbol::NoSymbols); } //symbol size - curve->setSymbolsSize(Worksheet::convertToSceneUnits(originCurve.symbolSize, Worksheet::Point)); + curve->setSymbolsSize(Worksheet::convertToSceneUnits(originCurve.symbolSize, Worksheet::Unit::Point)); //symbol fill color QBrush brush = curve->symbolsBrush(); if (originCurve.symbolFillColor.type == Origin::Color::ColorType::Automatic) { //"automatic" color -> the color of the line, if available, has to be used, black otherwise if (curve->lineType() != XYCurve::NoLine) brush.setColor(curve->linePen().color()); else brush.setColor(Qt::black); } else brush.setColor(color(originCurve.symbolFillColor)); curve->setSymbolsBrush(brush); //symbol border/edge color and width QPen pen = curve->symbolsPen(); if (originCurve.symbolColor.type == Origin::Color::ColorType::Automatic) { //"automatic" color -> the color of the line, if available, has to be used, black otherwise if (curve->lineType() != XYCurve::NoLine) pen.setColor(curve->linePen().color()); else pen.setColor(Qt::black); } else pen.setColor(color(originCurve.symbolColor)); //border width (edge thickness in Origin) is given by percentage of the symbol radius pen.setWidthF(originCurve.symbolThickness/100.*curve->symbolsSize()/2.); curve->setSymbolsPen(pen); //handle unsigned char pointOffset member //handle bool connectSymbols member } else { curve->setSymbolsStyle(Symbol::NoSymbols); } //filling properties if (originCurve.fillArea) { //TODO: handle unsigned char fillAreaType; //with 'fillAreaType'=0x10 the area between the curve and the x-axis is filled //with 'fillAreaType'=0x14 the area included inside the curve is filled. First and last curve points are joined by a line to close the otherwise open area. //with 'fillAreaType'=0x12 the area excluded outside the curve is filled. The inverse of fillAreaType=0x14 is filled. //At the moment we only support the first type, so set it to XYCurve::FillingBelow curve->setFillingPosition(XYCurve::FillingBelow); if (originCurve.fillAreaPattern == 0) { curve->setFillingType(PlotArea::Color); } else { curve->setFillingType(PlotArea::Pattern); //map different patterns in originCurve.fillAreaPattern (has the values of Origin::FillPattern) to Qt::BrushStyle; switch (originCurve.fillAreaPattern) { case 0: curve->setFillingBrushStyle(Qt::NoBrush); break; case 1: case 2: case 3: curve->setFillingBrushStyle(Qt::BDiagPattern); break; case 4: case 5: case 6: curve->setFillingBrushStyle(Qt::FDiagPattern); break; case 7: case 8: case 9: curve->setFillingBrushStyle(Qt::DiagCrossPattern); break; case 10: case 11: case 12: curve->setFillingBrushStyle(Qt::HorPattern); break; case 13: case 14: case 15: curve->setFillingBrushStyle(Qt::VerPattern); break; case 16: case 17: case 18: curve->setFillingBrushStyle(Qt::CrossPattern); break; } } curve->setFillingFirstColor(color(originCurve.fillAreaColor)); curve->setFillingOpacity(1 - originCurve.fillAreaTransparency/255); //Color fillAreaPatternColor - color for the pattern lines, not supported //double fillAreaPatternWidth - width of the pattern lines, not supported //bool fillAreaWithLineTransparency - transparency of the pattern lines independent of the area transparency, not supported //TODO: //unsigned char fillAreaPatternBorderStyle; //Color fillAreaPatternBorderColor; //double fillAreaPatternBorderWidth; //The Border properties are used only in "Column/Bar" (histogram) plots. Those properties are: //fillAreaPatternBorderStyle for the line style (use enum Origin::LineStyle here) //fillAreaPatternBorderColor for the line color //fillAreaPatternBorderWidth for the line width } else curve->setFillingPosition(XYCurve::NoFilling); } bool OriginProjectParser::loadNote(Note* note, bool preview) { DEBUG("OriginProjectParser::loadNote()"); //load note data const Origin::Note& originNote = m_originFile->note(findNoteByName(note->name())); if (preview) return true; note->setComment(originNote.label.c_str()); note->setNote(QString::fromLatin1(originNote.text.c_str())); return true; } //############################################################################## //########################### Helper functions ################################ //############################################################################## QDateTime OriginProjectParser::creationTime(tree::iterator it) const { //this logic seems to be correct only for the first node (project node). For other nodes the current time is returned. char time_str[21]; strftime(time_str, sizeof(time_str), "%F %T", gmtime(&(*it).creationDate)); return QDateTime::fromString(QString(time_str), Qt::ISODate); } QString OriginProjectParser::parseOriginText(const QString &str) const { DEBUG("parseOriginText()"); QStringList lines = str.split('\n'); QString text; for (int i = 0; i < lines.size(); ++i) { if (i > 0) text.append("
"); text.append(parseOriginTags(lines[i])); } DEBUG(" PARSED TEXT = " << STDSTRING(text)); return text; } QColor OriginProjectParser::color(Origin::Color color) const { switch (color.type) { case Origin::Color::ColorType::Regular: switch (color.regular) { case Origin::Color::Black: return QColor{Qt::black}; case Origin::Color::Red: return QColor{Qt::red}; case Origin::Color::Green: return QColor{Qt::green}; case Origin::Color::Blue: return QColor{Qt::blue}; case Origin::Color::Cyan: return QColor{Qt::cyan}; case Origin::Color::Magenta: return QColor{Qt::magenta}; case Origin::Color::Yellow: return QColor{Qt::yellow}; case Origin::Color::DarkYellow: return QColor{Qt::darkYellow}; case Origin::Color::Navy: return QColor{0, 0, 128}; case Origin::Color::Purple: return QColor{128, 0, 128}; case Origin::Color::Wine: return QColor{128, 0, 0}; case Origin::Color::Olive: return QColor{0, 128, 0}; case Origin::Color::DarkCyan: return QColor{Qt::darkCyan}; case Origin::Color::Royal: return QColor{0, 0, 160}; case Origin::Color::Orange: return QColor{255, 128, 0}; case Origin::Color::Violet: return QColor{128, 0, 255}; case Origin::Color::Pink: return QColor{255, 0, 128}; case Origin::Color::White: return QColor{Qt::white}; case Origin::Color::LightGray: return QColor{Qt::lightGray}; case Origin::Color::Gray: return QColor{Qt::gray}; case Origin::Color::LTYellow: return QColor{255, 0, 128}; case Origin::Color::LTCyan: return QColor{128, 255, 255}; case Origin::Color::LTMagenta: return QColor{255, 128, 255}; case Origin::Color::DarkGray: return QColor{Qt::darkGray}; case Origin::Color::SpecialV7Axis: return QColor{Qt::black}; } break; case Origin::Color::ColorType::Custom: return QColor{color.custom[0], color.custom[1], color.custom[2]}; case Origin::Color::ColorType::None: case Origin::Color::ColorType::Automatic: case Origin::Color::ColorType::Increment: case Origin::Color::ColorType::Indexing: case Origin::Color::ColorType::RGB: case Origin::Color::ColorType::Mapping: break; } return QColor(Qt::white); } PlotArea::BackgroundColorStyle OriginProjectParser::backgroundColorStyle(Origin::ColorGradientDirection colorGradient) const { switch (colorGradient) { case Origin::ColorGradientDirection::NoGradient: return PlotArea::BackgroundColorStyle::SingleColor; case Origin::ColorGradientDirection::TopLeft: return PlotArea::BackgroundColorStyle::TopLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Left: return PlotArea::BackgroundColorStyle::HorizontalLinearGradient; case Origin::ColorGradientDirection::BottomLeft: return PlotArea::BackgroundColorStyle::BottomLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Top: return PlotArea::BackgroundColorStyle::VerticalLinearGradient; case Origin::ColorGradientDirection::Center: return PlotArea::BackgroundColorStyle::RadialGradient; case Origin::ColorGradientDirection::Bottom: return PlotArea::BackgroundColorStyle::VerticalLinearGradient; case Origin::ColorGradientDirection::TopRight: return PlotArea::BackgroundColorStyle::BottomLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Right: return PlotArea::BackgroundColorStyle::HorizontalLinearGradient; case Origin::ColorGradientDirection::BottomRight: return PlotArea::BackgroundColorStyle::TopLeftDiagonalLinearGradient; } return PlotArea::BackgroundColorStyle::SingleColor; } QString strreverse(const QString &str) { //QString reversing QByteArray ba = str.toLocal8Bit(); std::reverse(ba.begin(), ba.end()); return QString(ba); } QList> OriginProjectParser::charReplacementList() const { QList> replacements; // TODO: probably missed some. Is there any generic method? replacements << qMakePair(QString("ä"), QString("ä")); replacements << qMakePair(QString("ö"), QString("ö")); replacements << qMakePair(QString("ü"), QString("ü")); replacements << qMakePair(QString("Ä"), QString("Ä")); replacements << qMakePair(QString("Ö"), QString("Ö")); replacements << qMakePair(QString("Ü"), QString("Ü")); replacements << qMakePair(QString("ß"), QString("ß")); replacements << qMakePair(QString("€"), QString("€")); replacements << qMakePair(QString("£"), QString("£")); replacements << qMakePair(QString("¥"), QString("¥")); replacements << qMakePair(QString("¤"), QString("¤")); // krazy:exclude=spelling replacements << qMakePair(QString("¦"), QString("¦")); replacements << qMakePair(QString("§"), QString("§")); replacements << qMakePair(QString("µ"), QString("µ")); replacements << qMakePair(QString("¹"), QString("¹")); replacements << qMakePair(QString("²"), QString("²")); replacements << qMakePair(QString("³"), QString("³")); replacements << qMakePair(QString("¶"), QString("¶")); replacements << qMakePair(QString("ø"), QString("ø")); replacements << qMakePair(QString("æ"), QString("æ")); replacements << qMakePair(QString("ð"), QString("ð")); replacements << qMakePair(QString("ħ"), QString("ℏ")); replacements << qMakePair(QString("ĸ"), QString("κ")); replacements << qMakePair(QString("¢"), QString("¢")); replacements << qMakePair(QString("¼"), QString("¼")); replacements << qMakePair(QString("½"), QString("½")); replacements << qMakePair(QString("¾"), QString("¾")); replacements << qMakePair(QString("¬"), QString("¬")); replacements << qMakePair(QString("©"), QString("©")); replacements << qMakePair(QString("®"), QString("®")); replacements << qMakePair(QString("ª"), QString("ª")); replacements << qMakePair(QString("º"), QString("º")); replacements << qMakePair(QString("±"), QString("±")); replacements << qMakePair(QString("¿"), QString("¿")); replacements << qMakePair(QString("×"), QString("×")); replacements << qMakePair(QString("°"), QString("°")); replacements << qMakePair(QString("«"), QString("«")); replacements << qMakePair(QString("»"), QString("»")); replacements << qMakePair(QString("¯"), QString("¯")); replacements << qMakePair(QString("¸"), QString("¸")); replacements << qMakePair(QString("À"), QString("À")); replacements << qMakePair(QString("Á"), QString("Á")); replacements << qMakePair(QString("Â"), QString("Â")); replacements << qMakePair(QString("Ã"), QString("Ã")); replacements << qMakePair(QString("Å"), QString("Å")); replacements << qMakePair(QString("Æ"), QString("Æ")); replacements << qMakePair(QString("Ç"), QString("Ç")); replacements << qMakePair(QString("È"), QString("È")); replacements << qMakePair(QString("É"), QString("É")); replacements << qMakePair(QString("Ê"), QString("Ê")); replacements << qMakePair(QString("Ë"), QString("Ë")); replacements << qMakePair(QString("Ì"), QString("Ì")); replacements << qMakePair(QString("Í"), QString("Í")); replacements << qMakePair(QString("Î"), QString("Î")); replacements << qMakePair(QString("Ï"), QString("Ï")); replacements << qMakePair(QString("Ð"), QString("Ð")); replacements << qMakePair(QString("Ñ"), QString("Ñ")); replacements << qMakePair(QString("Ò"), QString("Ò")); replacements << qMakePair(QString("Ó"), QString("Ó")); replacements << qMakePair(QString("Ô"), QString("Ô")); replacements << qMakePair(QString("Õ"), QString("Õ")); replacements << qMakePair(QString("Ù"), QString("Ù")); replacements << qMakePair(QString("Ú"), QString("Ú")); replacements << qMakePair(QString("Û"), QString("Û")); replacements << qMakePair(QString("Ý"), QString("Ý")); replacements << qMakePair(QString("Þ"), QString("Þ")); replacements << qMakePair(QString("à"), QString("à")); replacements << qMakePair(QString("á"), QString("á")); replacements << qMakePair(QString("â"), QString("â")); replacements << qMakePair(QString("ã"), QString("ã")); replacements << qMakePair(QString("å"), QString("å")); replacements << qMakePair(QString("ç"), QString("ç")); replacements << qMakePair(QString("è"), QString("è")); replacements << qMakePair(QString("é"), QString("é")); replacements << qMakePair(QString("ê"), QString("ê")); replacements << qMakePair(QString("ë"), QString("ë")); replacements << qMakePair(QString("ì"), QString("ì")); replacements << qMakePair(QString("í"), QString("í")); replacements << qMakePair(QString("î"), QString("î")); replacements << qMakePair(QString("ï"), QString("ï")); replacements << qMakePair(QString("ñ"), QString("ñ")); replacements << qMakePair(QString("ò"), QString("ò")); replacements << qMakePair(QString("ó"), QString("ó")); replacements << qMakePair(QString("ô"), QString("ô")); replacements << qMakePair(QString("õ"), QString("õ")); replacements << qMakePair(QString("÷"), QString("÷")); replacements << qMakePair(QString("ù"), QString("ù")); replacements << qMakePair(QString("ú"), QString("ú")); replacements << qMakePair(QString("û"), QString("û")); replacements << qMakePair(QString("ý"), QString("ý")); replacements << qMakePair(QString("þ"), QString("þ")); replacements << qMakePair(QString("ÿ"), QString("ÿ")); replacements << qMakePair(QString("Œ"), QString("Œ")); replacements << qMakePair(QString("œ"), QString("œ")); replacements << qMakePair(QString("Š"), QString("Š")); replacements << qMakePair(QString("š"), QString("š")); replacements << qMakePair(QString("Ÿ"), QString("Ÿ")); replacements << qMakePair(QString("†"), QString("†")); replacements << qMakePair(QString("‡"), QString("‡")); replacements << qMakePair(QString("…"), QString("…")); replacements << qMakePair(QString("‰"), QString("‰")); replacements << qMakePair(QString("™"), QString("™")); return replacements; } QString OriginProjectParser::replaceSpecialChars(const QString& text) const { QString t = text; for (const auto& r : charReplacementList()) t.replace(r.first, r.second); return t; } /*! * converts the string with Origin's syntax for text formatting/highlighting * to a string in the richtext/html format supported by Qt. * For the supported syntax, see: * https://www.originlab.com/doc/LabTalk/ref/Label-cmd * https://www.originlab.com/doc/Origin-Help/TextOb-Prop-Text-tab * https://doc.qt.io/qt-5/richtext-html-subset.html */ QString OriginProjectParser::parseOriginTags(const QString& str) const { DEBUG("parseOriginTags()"); DEBUG(" string: " << STDSTRING(str)); QDEBUG(" UTF8 string: " << str.toUtf8()); QString line = str; //replace %(...) tags // QRegExp rxcol("\\%\\(\\d+\\)"); // replace \l(x) (plot legend tags) with \\c{x}, where x is a digit line.replace(QRegularExpression(QStringLiteral("\\\\\\s*l\\s*\\(\\s*(\\d+)\\s*\\)")), QStringLiteral("\\c{\\1}")); // replace umlauts etc. line = replaceSpecialChars(line); // replace tabs (not really supported) line.replace('\t', "        "); // In PCRE2 (which is what QRegularExpression uses) variable-length lookbehind is supposed to be // exprimental in Perl 5.30; which means it doesn't work at the moment, i.e. using a variable-length // negative lookbehind isn't valid syntax from QRegularExpression POV. // Ultimately we have to reverse the string and use a negative _lookahead_ instead. // The goal is to temporatily replace '(' and ')' that don't denote tags; this is so that we // can handle parenthesis that are inside the tag, e.g. '\b(bold (cf))', we want the '(cf)' part // to remain as is. const QRegularExpression nonTagsRe("\\)([^)(]*)\\((?!\\s*([buigs\\+\\-]|\\d{1,3}\\s*[pc]|[\\w ]+\\s*:\\s*f)\\s*\\\\)"); QString linerev = strreverse(line); const QString lBracket = strreverse("&lbracket;"); const QString rBracket = strreverse("&rbracket;"); linerev.replace(nonTagsRe, rBracket + QStringLiteral("\\1") + lBracket); // change the line back to normal line = strreverse(linerev); //replace \-(...), \+(...), \b(...), \i(...), \u(...), \s(....), \g(...), \f:font(...), // \c'number'(...), \p'size'(...) tags with equivalent supported HTML syntax const QRegularExpression tagsRe(QStringLiteral("\\\\\\s*([-+bgisu]|f:(\\w[\\w ]+)|[pc]\\s*(\\d+))\\s*\\(([^()]+?)\\)")); QRegularExpressionMatch rmatch; while (line.contains(tagsRe, &rmatch)) { QString rep; const QString tagText = rmatch.captured(4); const QString marker = rmatch.captured(1); if (marker.startsWith(QLatin1Char('-'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('+'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('b'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('g'))) { // greek symbols e.g. α φ rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('i'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('s'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('u'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('f'))) { rep = QStringLiteral("%2").arg(rmatch.captured(2).trimmed(), tagText); } else if (marker.startsWith(QLatin1Char('p'))) { // e.g. \p200(...), means use font-size 200% rep = QStringLiteral("%2").arg(rmatch.captured(3), tagText); } else if (marker.startsWith(QLatin1Char('c'))) { // e.g. \c12(...), set the text color to the corresponding color from // the color drop-down list in OriginLab const int colorIndex = rmatch.captured(3).toInt(); Origin::Color c; c.type = Origin::Color::ColorType::Regular; c.regular = colorIndex <= 23 ? static_cast(colorIndex) : Origin::Color::RegularColor::Black; QColor color = OriginProjectParser::color(c); rep = QStringLiteral("%2").arg(color.name(), tagText); } line.replace(rmatch.capturedStart(0), rmatch.capturedLength(0), rep); } // put non-tag '(' and ')' back in their places line.replace("&lbracket;", "("); line.replace("&rbracket;", ")"); // special characters line.replace(QRegularExpression(QStringLiteral("\\\\\\((\\d+)\\)")), "&#\\1;"); DEBUG(" result: " << STDSTRING(line)); return line; } diff --git a/src/backend/worksheet/Image.cpp b/src/backend/worksheet/Image.cpp index cbefb6497..940ff8dc8 100644 --- a/src/backend/worksheet/Image.cpp +++ b/src/backend/worksheet/Image.cpp @@ -1,720 +1,720 @@ /*************************************************************************** File : Image.cpp Project : LabPlot Description : Worksheet element to draw images -------------------------------------------------------------------- Copyright : (C) 2019 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 "Image.h" #include "Worksheet.h" #include "ImagePrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include #include #include /** * \class Image * \brief A label supporting rendering of html- and tex-formatted texts. * * The label is aligned relative to the specified position. * The position can be either specified by providing the x- and y- coordinates * in parent's coordinate system, or by specifying one of the predefined position * flags (\c HorizontalPosition, \c VerticalPosition). */ Image::Image(const QString& name) : WorksheetElement(name, AspectType::Image), d_ptr(new ImagePrivate(this)) { init(); } Image::Image(const QString &name, ImagePrivate *dd) : WorksheetElement(name, AspectType::Image), d_ptr(dd) { init(); } void Image::init() { Q_D(Image); KConfig config; KConfigGroup group = config.group("Image"); d->opacity = group.readEntry("opacity", d->opacity); //geometry d->position.point.setX( group.readEntry("PositionXValue", d->position.point.x()) ); d->position.point.setY( group.readEntry("PositionYValue", d->position.point.y()) ); d->position.horizontalPosition = (WorksheetElement::HorizontalPosition) group.readEntry("PositionX", (int)d->position.horizontalPosition); d->position.verticalPosition = (WorksheetElement::VerticalPosition) group.readEntry("PositionY", (int)d->position.verticalPosition); d->horizontalAlignment = (WorksheetElement::HorizontalAlignment) group.readEntry("HorizontalAlignment", (int)d->horizontalAlignment); d->verticalAlignment = (WorksheetElement::VerticalAlignment) group.readEntry("VerticalAlignment", (int)d->verticalAlignment); d->rotationAngle = group.readEntry("Rotation", d->rotationAngle); //border d->borderPen = QPen(group.readEntry("BorderColor", d->borderPen.color()), group.readEntry("BorderWidth", d->borderPen.widthF()), (Qt::PenStyle) group.readEntry("BorderStyle", (int)(d->borderPen.style()))); d->borderOpacity = group.readEntry("BorderOpacity", d->borderOpacity); //initial placeholder image d->image = QIcon::fromTheme("viewimage").pixmap(d->width, d->height).toImage(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene Image::~Image() = default; QGraphicsItem* Image::graphicsItem() const { return d_ptr; } void Image::setParentGraphicsItem(QGraphicsItem* item) { Q_D(Image); d->setParentItem(item); d->updatePosition(); } void Image::retransform() { Q_D(Image); d->retransform(); } void Image::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("Image::handleResize()"); Q_UNUSED(pageResize); Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); // Q_D(Image); // double ratio = 0; // if (horizontalRatio > 1.0 || verticalRatio > 1.0) // ratio = qMax(horizontalRatio, verticalRatio); // else // ratio = qMin(horizontalRatio, verticalRatio); } /*! Returns an icon to be used in the project explorer. */ QIcon Image::icon() const{ return QIcon::fromTheme("viewimage"); } QMenu* Image::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" if (!visibilityAction) { visibilityAction = new QAction(i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Image::visibilityChanged); } visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertSeparator(firstAction); return menu; } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(Image, QString, fileName, fileName) BASIC_SHARED_D_READER_IMPL(Image, qreal, opacity, opacity) BASIC_SHARED_D_READER_IMPL(Image, int, width, width) BASIC_SHARED_D_READER_IMPL(Image, int, height, height) BASIC_SHARED_D_READER_IMPL(Image, bool, keepRatio, keepRatio) CLASS_SHARED_D_READER_IMPL(Image, WorksheetElement::PositionWrapper, position, position) BASIC_SHARED_D_READER_IMPL(Image, WorksheetElement::HorizontalAlignment, horizontalAlignment, horizontalAlignment) BASIC_SHARED_D_READER_IMPL(Image, WorksheetElement::VerticalAlignment, verticalAlignment, verticalAlignment) BASIC_SHARED_D_READER_IMPL(Image, qreal, rotationAngle, rotationAngle) CLASS_SHARED_D_READER_IMPL(Image, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(Image, qreal, borderOpacity, borderOpacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(Image, SetFileName, QString, fileName, updateImage) void Image::setFileName(const QString& fileName) { Q_D(Image); if (fileName != d->fileName) exec(new ImageSetFileNameCmd(d, fileName, ki18n("%1: set image"))); } STD_SETTER_CMD_IMPL_F_S(Image, SetOpacity, qreal, opacity, update) void Image::setOpacity(qreal opacity) { Q_D(Image); if (opacity != d->opacity) exec(new ImageSetOpacityCmd(d, opacity, ki18n("%1: set border opacity"))); } STD_SETTER_CMD_IMPL_F_S(Image, SetWidth, int, width, scaleImage) void Image::setWidth(int width) { Q_D(Image); if (width != d->width) exec(new ImageSetWidthCmd(d, width, ki18n("%1: set width"))); } STD_SETTER_CMD_IMPL_F_S(Image, SetHeight, int, height, scaleImage) void Image::setHeight(int height) { Q_D(Image); if (height != d->height) exec(new ImageSetHeightCmd(d, height, ki18n("%1: set height"))); } STD_SETTER_CMD_IMPL_S(Image, SetKeepRatio, bool, keepRatio) void Image::setKeepRatio(bool keepRatio) { Q_D(Image); if (keepRatio != d->keepRatio) exec(new ImageSetKeepRatioCmd(d, keepRatio, ki18n("%1: change keep ratio"))); } STD_SETTER_CMD_IMPL_F_S(Image, SetPosition, WorksheetElement::PositionWrapper, position, retransform); void Image::setPosition(const WorksheetElement::PositionWrapper& pos) { Q_D(Image); if (pos.point != d->position.point || pos.horizontalPosition != d->position.horizontalPosition || pos.verticalPosition != d->position.verticalPosition) exec(new ImageSetPositionCmd(d, pos, ki18n("%1: set position"))); } /*! sets the position without undo/redo-stuff */ void Image::setPosition(QPointF point) { Q_D(Image); if (point != d->position.point) { d->position.point = point; retransform(); } } STD_SETTER_CMD_IMPL_F_S(Image, SetRotationAngle, qreal, rotationAngle, recalcShapeAndBoundingRect); void Image::setRotationAngle(qreal angle) { Q_D(Image); if (angle != d->rotationAngle) exec(new ImageSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(Image, SetHorizontalAlignment, WorksheetElement::HorizontalAlignment, horizontalAlignment, retransform); void Image::setHorizontalAlignment(const WorksheetElement::HorizontalAlignment hAlign) { Q_D(Image); if (hAlign != d->horizontalAlignment) exec(new ImageSetHorizontalAlignmentCmd(d, hAlign, ki18n("%1: set horizontal alignment"))); } STD_SETTER_CMD_IMPL_F_S(Image, SetVerticalAlignment, WorksheetElement::VerticalAlignment, verticalAlignment, retransform); void Image::setVerticalAlignment(const WorksheetElement::VerticalAlignment vAlign) { Q_D(Image); if (vAlign != d->verticalAlignment) exec(new ImageSetVerticalAlignmentCmd(d, vAlign, ki18n("%1: set vertical alignment"))); } //Border STD_SETTER_CMD_IMPL_F_S(Image, SetBorderPen, QPen, borderPen, update) void Image::setBorderPen(const QPen &pen) { Q_D(Image); if (pen != d->borderPen) exec(new ImageSetBorderPenCmd(d, pen, ki18n("%1: set border"))); } STD_SETTER_CMD_IMPL_F_S(Image, SetBorderOpacity, qreal, borderOpacity, update) void Image::setBorderOpacity(qreal opacity) { Q_D(Image); if (opacity != d->borderOpacity) exec(new ImageSetBorderOpacityCmd(d, opacity, ki18n("%1: set border opacity"))); } //misc STD_SWAP_METHOD_SETTER_CMD_IMPL_F(Image, SetVisible, bool, swapVisible, retransform); void Image::setVisible(bool on) { Q_D(Image); exec(new ImageSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool Image::isVisible() const { Q_D(const Image); return d->isVisible(); } void Image::setPrinting(bool on) { Q_D(Image); d->m_printing = on; } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Image::visibilityChanged() { Q_D(const Image); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## ImagePrivate::ImagePrivate(Image* owner) : q(owner) { setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setAcceptHoverEvents(true); } QString ImagePrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the label. Called on geometry or text changes. */ void ImagePrivate::retransform() { if (suppressRetransform) return; - if (position.horizontalPosition != WorksheetElement::hPositionCustom - || position.verticalPosition != WorksheetElement::vPositionCustom) + if (position.horizontalPosition != WorksheetElement::HorizontalPosition::Custom + || position.verticalPosition != WorksheetElement::VerticalPosition::Custom) updatePosition(); float x = position.point.x(); float y = position.point.y(); float w = image.width(); float h = image.height(); //depending on the alignment, calculate the new GraphicsItem's position in parent's coordinate system QPointF itemPos; switch (horizontalAlignment) { - case WorksheetElement::hAlignLeft: + case WorksheetElement::HorizontalAlignment::Left: itemPos.setX(x - w/2); break; - case WorksheetElement::hAlignCenter: + case WorksheetElement::HorizontalAlignment::Center: itemPos.setX(x); break; - case WorksheetElement::hAlignRight: + case WorksheetElement::HorizontalAlignment::Right: itemPos.setX(x + w/2); break; } switch (verticalAlignment) { - case WorksheetElement::vAlignTop: + case WorksheetElement::VerticalAlignment::Top: itemPos.setY(y - h/2); break; - case WorksheetElement::vAlignCenter: + case WorksheetElement::VerticalAlignment::Center: itemPos.setY(y); break; - case WorksheetElement::vAlignBottom: + case WorksheetElement::VerticalAlignment::Bottom: itemPos.setY(y + h/2); break; } suppressItemChangeEvent = true; setPos(itemPos); suppressItemChangeEvent = false; boundingRectangle.setX(-w/2); boundingRectangle.setY(-h/2); boundingRectangle.setWidth(w); boundingRectangle.setHeight(h); updateBorder(); } void ImagePrivate::updateImage() { if (!fileName.isEmpty()) { image = QImage(fileName); width = image.width(); height = image.height(); q->widthChanged(width); q->heightChanged(height); } else { - width = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); - height = Worksheet::convertToSceneUnits(3, Worksheet::Centimeter); + width = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Centimeter); + height = Worksheet::convertToSceneUnits(3, Worksheet::Unit::Centimeter); image = QIcon::fromTheme("viewimage").pixmap(width, height).toImage(); q->widthChanged(width); q->heightChanged(height); } retransform(); } void ImagePrivate::scaleImage() { if (keepRatio) { if (width != image.width()) { //width was changed -> rescale the height to keep the ratio height = image.height()*width/image.width(); q->heightChanged(height); } else { //height was changed -> rescale the width to keep the ratio width = image.width()*height/image.height(); q->widthChanged(width); } } image = image.scaled(width, height); retransform(); } /*! calculates the position of the label, when the position relative to the parent was specified (left, right, etc.) */ void ImagePrivate::updatePosition() { //determine the parent item QRectF parentRect; QGraphicsItem* parent = parentItem(); if (parent) { parentRect = parent->boundingRect(); } else { if (!scene()) return; parentRect = scene()->sceneRect(); } - if (position.horizontalPosition != WorksheetElement::hPositionCustom) { - if (position.horizontalPosition == WorksheetElement::hPositionLeft) + if (position.horizontalPosition != WorksheetElement::HorizontalPosition::Custom) { + if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Left) position.point.setX( parentRect.x() ); - else if (position.horizontalPosition == WorksheetElement::hPositionCenter) + else if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Center) position.point.setX( parentRect.x() + parentRect.width()/2 ); - else if (position.horizontalPosition == WorksheetElement::hPositionRight) + else if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Right) position.point.setX( parentRect.x() + parentRect.width() ); } - if (position.verticalPosition != WorksheetElement::vPositionCustom) { - if (position.verticalPosition == WorksheetElement::vPositionTop) + if (position.verticalPosition != WorksheetElement::VerticalPosition::Custom) { + if (position.verticalPosition == WorksheetElement::VerticalPosition::Top) position.point.setY( parentRect.y() ); - else if (position.verticalPosition == WorksheetElement::vPositionCenter) + else if (position.verticalPosition == WorksheetElement::VerticalPosition::Center) position.point.setY( parentRect.y() + parentRect.height()/2 ); - else if (position.verticalPosition == WorksheetElement::vPositionBottom) + else if (position.verticalPosition == WorksheetElement::VerticalPosition::Bottom) position.point.setY( parentRect.y() + parentRect.height() ); } emit q->positionChanged(position); } void ImagePrivate::updateBorder() { borderShapePath = QPainterPath(); borderShapePath.addRect(boundingRectangle); recalcShapeAndBoundingRect(); } bool ImagePrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF ImagePrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath ImagePrivate::shape() const { return imageShape; } /*! recalculates the outer bounds and the shape of the label. */ void ImagePrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); QMatrix matrix; matrix.rotate(-rotationAngle); imageShape = QPainterPath(); if (borderPen.style() != Qt::NoPen) { imageShape.addPath(WorksheetElement::shapeFromPath(borderShapePath, borderPen)); transformedBoundingRectangle = matrix.mapRect(imageShape.boundingRect()); } else { imageShape.addRect(boundingRectangle); transformedBoundingRectangle = matrix.mapRect(boundingRectangle); } imageShape = matrix.map(imageShape); emit q->changed(); } void ImagePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) painter->save(); //draw the image painter->rotate(-rotationAngle); painter->setOpacity(opacity); painter->drawImage(boundingRectangle.topLeft(), image, image.rect()); painter->restore(); //draw the border if (borderPen.style() != Qt::NoPen) { painter->save(); painter->rotate(-rotationAngle); painter->setPen(borderPen); painter->setOpacity(borderOpacity); painter->drawPath(borderShapePath); painter->restore(); } if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(imageShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(imageShape); } } QVariant ImagePrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates WorksheetElement::PositionWrapper tempPosition; tempPosition.point = positionFromItemPosition(value.toPointF()); - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void ImagePrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = positionFromItemPosition(pos()); if (qAbs(point.x()-position.point.x())>20 && qAbs(point.y()-position.point.y())>20 ) { //position was changed -> set the position related member variables suppressRetransform = true; WorksheetElement::PositionWrapper tempPosition; tempPosition.point = point; - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } /*! * converts label's position to GraphicsItem's position. */ QPointF ImagePrivate::positionFromItemPosition(QPointF itemPos) { float x = itemPos.x(); float y = itemPos.y(); float w = image.width(); float h = image.height(); QPointF tmpPosition; //depending on the alignment, calculate the new position switch (horizontalAlignment) { - case WorksheetElement::hAlignLeft: + case WorksheetElement::HorizontalAlignment::Left: tmpPosition.setX(x + w/2); break; - case WorksheetElement::hAlignCenter: + case WorksheetElement::HorizontalAlignment::Center: tmpPosition.setX(x); break; - case WorksheetElement::hAlignRight: + case WorksheetElement::HorizontalAlignment::Right: tmpPosition.setX(x - w/2); break; } switch (verticalAlignment) { - case WorksheetElement::vAlignTop: + case WorksheetElement::VerticalAlignment::Top: tmpPosition.setY(y + h/2); break; - case WorksheetElement::vAlignCenter: + case WorksheetElement::VerticalAlignment::Center: tmpPosition.setY(y); break; - case WorksheetElement::vAlignBottom: + case WorksheetElement::VerticalAlignment::Bottom: tmpPosition.setY(y - h/2); break; } return tmpPosition; } void ImagePrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void ImagePrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void ImagePrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Image::save(QXmlStreamWriter* writer) const { Q_D(const Image); writer->writeStartElement("image"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); writer->writeAttribute("fileName", d->fileName); writer->writeAttribute("opacity", QString::number(d->opacity)); writer->writeEndElement(); //geometry writer->writeStartElement("geometry"); writer->writeAttribute("width", QString::number(d->width)); writer->writeAttribute("height", QString::number(d->height)); writer->writeAttribute("keepRatio", QString::number(d->keepRatio)); writer->writeAttribute("x", QString::number(d->position.point.x())); writer->writeAttribute("y", QString::number(d->position.point.y())); - writer->writeAttribute("horizontalPosition", QString::number(d->position.horizontalPosition)); - writer->writeAttribute("verticalPosition", QString::number(d->position.verticalPosition)); - writer->writeAttribute("horizontalAlignment", QString::number(d->horizontalAlignment)); - writer->writeAttribute("verticalAlignment", QString::number(d->verticalAlignment)); + writer->writeAttribute("horizontalPosition", QString::number(static_cast(d->position.horizontalPosition))); + writer->writeAttribute("verticalPosition", QString::number(static_cast(d->position.verticalPosition))); + writer->writeAttribute("horizontalAlignment", QString::number(static_cast(d->horizontalAlignment))); + writer->writeAttribute("verticalAlignment", QString::number(static_cast(d->verticalAlignment))); writer->writeAttribute("rotationAngle", QString::number(d->rotationAngle)); writer->writeAttribute("visible", QString::number(d->isVisible())); writer->writeEndElement(); //border writer->writeStartElement("border"); WRITE_QPEN(d->borderPen); writer->writeAttribute("borderOpacity", QString::number(d->borderOpacity)); writer->writeEndElement(); writer->writeEndElement(); // close "image" section } //! Load from XML bool Image::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; Q_D(Image); 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() == "image") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); d->fileName = attribs.value("fileName").toString(); READ_DOUBLE_VALUE("opacity", opacity); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); READ_INT_VALUE("width", width, int); READ_INT_VALUE("height", height, int); READ_INT_VALUE("keepRatio", keepRatio, bool); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->position.point.setY(str.toDouble()); READ_INT_VALUE("horizontalPosition", position.horizontalPosition, WorksheetElement::HorizontalPosition); READ_INT_VALUE("verticalPosition", position.verticalPosition, WorksheetElement::VerticalPosition); READ_INT_VALUE("horizontalAlignment", horizontalAlignment, WorksheetElement::HorizontalAlignment); READ_INT_VALUE("verticalAlignment", verticalAlignment, WorksheetElement::VerticalAlignment); READ_DOUBLE_VALUE("rotationAngle", rotationAngle); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_QPEN(d->borderPen); READ_DOUBLE_VALUE("borderOpacity", borderOpacity); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (!preview) { d->image = QImage(d->fileName); d->scaleImage(); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Image::loadThemeConfig(const KConfig& config) { Q_UNUSED(config) } void Image::saveThemeConfig(const KConfig& config) { Q_UNUSED(config) } diff --git a/src/backend/worksheet/ImagePrivate.h b/src/backend/worksheet/ImagePrivate.h index 73aad6dcc..cf2860610 100644 --- a/src/backend/worksheet/ImagePrivate.h +++ b/src/backend/worksheet/ImagePrivate.h @@ -1,100 +1,100 @@ /*************************************************************************** File : ImagePrivate.h Project : LabPlot Description : Worksheet element to draw images -------------------------------------------------------------------- Copyright : (C) 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 * * * ***************************************************************************/ #ifndef IMAGEPRIVATE_H #define IMAGEPRIVATE_H #include #include "backend/worksheet/TextLabel.h" class QGraphicsSceneHoverEvent; class ImagePrivate: public QGraphicsItem { public: explicit ImagePrivate(Image*); QImage image; QString fileName; qreal opacity{1.0}; - int width = (int)Worksheet::convertToSceneUnits(2.0, Worksheet::Centimeter); - int height = (int)Worksheet::convertToSceneUnits(3.0, Worksheet::Centimeter); + int width = (int)Worksheet::convertToSceneUnits(2.0, Worksheet::Unit::Centimeter); + int height = (int)Worksheet::convertToSceneUnits(3.0, Worksheet::Unit::Centimeter); bool keepRatio{true}; //keep aspect ratio when scaling the image qreal rotationAngle{0.0}; // position in parent's coordinate system, the image will be aligned around this point TextLabel::PositionWrapper position{ - QPoint(Worksheet::convertToSceneUnits(1, Worksheet::Centimeter), - Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)), - TextLabel::hPositionCenter, - TextLabel::vPositionCenter}; + QPoint(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Centimeter), + Worksheet::convertToSceneUnits(1, Worksheet::Unit::Centimeter)), + TextLabel::HorizontalPosition::Center, + TextLabel::VerticalPosition::Center}; //alignment - TextLabel::HorizontalAlignment horizontalAlignment{TextLabel::hAlignCenter}; - TextLabel::VerticalAlignment verticalAlignment{TextLabel::vAlignCenter}; + TextLabel::HorizontalAlignment horizontalAlignment{TextLabel::HorizontalAlignment::Center}; + TextLabel::VerticalAlignment verticalAlignment{TextLabel::VerticalAlignment::Center}; //border - QPen borderPen{Qt::black, Worksheet::convertToSceneUnits(1.0, Worksheet::Point), Qt::SolidLine}; + QPen borderPen{Qt::black, Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point), Qt::SolidLine}; qreal borderOpacity{1.0}; QString name() const; void retransform(); bool swapVisible(bool on); virtual void recalcShapeAndBoundingRect(); void updateImage(); void scaleImage(); void updatePosition(); QPointF positionFromItemPosition(QPointF); void updateBorder(); bool suppressItemChangeEvent{false}; bool suppressRetransform{false}; bool m_printing{false}; bool m_hovered{false}; QRectF boundingRectangle; //bounding rectangle of the text QRectF transformedBoundingRectangle; //bounding rectangle of transformed (rotated etc.) text QPainterPath borderShapePath; QPainterPath imageShape; //reimplemented from QGraphicsItem QRectF boundingRect() const override; QPainterPath shape() const override; void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr) override; QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; Image* const q; private: void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; }; #endif diff --git a/src/backend/worksheet/TextLabel.cpp b/src/backend/worksheet/TextLabel.cpp index 197d37d6b..1ef9f8a0e 100644 --- a/src/backend/worksheet/TextLabel.cpp +++ b/src/backend/worksheet/TextLabel.cpp @@ -1,1115 +1,1115 @@ /*************************************************************************** File : TextLabel.cpp Project : LabPlot Description : Text label supporting reach text and latex formatting -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2019 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 "TextLabel.h" #include "Worksheet.h" #include "TextLabelPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * \class TextLabel * \brief A label supporting rendering of html- and tex-formatted texts. * * The label is aligned relative to the specified position. * The position can be either specified by providing the x- and y- coordinates * in parent's coordinate system, or by specifying one of the predefined position * flags (\c HorizontalPosition, \c VerticalPosition). */ TextLabel::TextLabel(const QString& name, Type type) : WorksheetElement(name, AspectType::TextLabel), d_ptr(new TextLabelPrivate(this)), m_type(type) { init(); } TextLabel::TextLabel(const QString &name, TextLabelPrivate *dd, Type type) : WorksheetElement(name, AspectType::TextLabel), d_ptr(dd), m_type(type) { init(); } TextLabel::Type TextLabel::type() const { return m_type; } void TextLabel::init() { Q_D(TextLabel); QString groupName; switch (m_type) { case Type::General: groupName = "TextLabel"; break; case Type::PlotTitle: groupName = "PlotTitle"; break; case Type::AxisTitle: groupName = "AxisTitle"; break; case Type::PlotLegendTitle: groupName = "PlotLegendTitle"; break; } const KConfig config; DEBUG(" config has group \"" << STDSTRING(groupName) << "\": " << config.hasGroup(groupName)); // group is always valid if you call config.group(..; KConfigGroup group; if (config.hasGroup(groupName)) group = config.group(groupName); // non-default settings d->staticText.setTextFormat(Qt::RichText); // explicitly set no wrap mode for text label to avoid unnecessary line breaks QTextOption textOption; textOption.setWrapMode(QTextOption::NoWrap); d->staticText.setTextOption(textOption); if (m_type == Type::PlotTitle || m_type == Type::PlotLegendTitle) { - d->position.verticalPosition = WorksheetElement::vPositionTop; - d->verticalAlignment = WorksheetElement::vAlignBottom; + d->position.verticalPosition = WorksheetElement::VerticalPosition::Top; + d->verticalAlignment = WorksheetElement::VerticalAlignment::Bottom; } else if (m_type == Type::AxisTitle) { - d->position.horizontalPosition = WorksheetElement::hPositionCustom; - d->position.verticalPosition = WorksheetElement::vPositionCustom; + d->position.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; + d->position.verticalPosition = WorksheetElement::VerticalPosition::Custom; } // read settings from config if group exists if (group.isValid()) { //properties common to all types d->textWrapper.teXUsed = group.readEntry("TeXUsed", d->textWrapper.teXUsed); d->teXFont.setFamily(group.readEntry("TeXFontFamily", d->teXFont.family())); d->teXFont.setPointSize(group.readEntry("TeXFontSize", d->teXFont.pointSize())); d->fontColor = group.readEntry("TeXFontColor", d->fontColor); d->backgroundColor = group.readEntry("TeXBackgroundColor", d->backgroundColor); d->rotationAngle = group.readEntry("Rotation", d->rotationAngle); //border d->borderShape = (TextLabel::BorderShape)group.readEntry("BorderShape", (int)d->borderShape); d->borderPen = QPen(group.readEntry("BorderColor", d->borderPen.color()), group.readEntry("BorderWidth", d->borderPen.width()), (Qt::PenStyle) group.readEntry("BorderStyle", (int)(d->borderPen.style()))); d->borderOpacity = group.readEntry("BorderOpacity", d->borderOpacity); //position and alignment relevant properties d->position.point.setX( group.readEntry("PositionXValue", d->position.point.x()) ); d->position.point.setY( group.readEntry("PositionYValue", d->position.point.y()) ); d->position.horizontalPosition = (HorizontalPosition) group.readEntry("PositionX", (int)d->position.horizontalPosition); d->position.verticalPosition = (VerticalPosition) group.readEntry("PositionY", (int)d->position.verticalPosition); - d->horizontalAlignment = (WorksheetElement::HorizontalAlignment) group.readEntry("HorizontalAlignment", (int)d->horizontalAlignment); - d->verticalAlignment = (WorksheetElement::VerticalAlignment) group.readEntry("VerticalAlignment", (int)d->verticalAlignment); + d->horizontalAlignment = (WorksheetElement::HorizontalAlignment) group.readEntry("HorizontalAlignment", static_cast(d->horizontalAlignment)); + d->verticalAlignment = (WorksheetElement::VerticalAlignment) group.readEntry("VerticalAlignment", static_cast(d->verticalAlignment)); } DEBUG("CHECK: default/run time image resolution: " << d->teXImageResolution << '/' << QApplication::desktop()->physicalDpiX()); connect(&d->teXImageFutureWatcher, &QFutureWatcher::finished, this, &TextLabel::updateTeXImage); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene TextLabel::~TextLabel() = default; QGraphicsItem* TextLabel::graphicsItem() const { return d_ptr; } void TextLabel::setParentGraphicsItem(QGraphicsItem* item) { Q_D(TextLabel); d->setParentItem(item); d->updatePosition(); } void TextLabel::retransform() { Q_D(TextLabel); d->retransform(); } void TextLabel::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("TextLabel::handleResize()"); Q_UNUSED(pageResize); Q_D(TextLabel); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); d->teXFont.setPointSizeF(d->teXFont.pointSizeF() * ratio); d->updateText(); //TODO: doesn't seem to work QTextDocument doc; doc.setHtml(d->textWrapper.text); QTextCursor cursor(&doc); cursor.select(QTextCursor::Document); QTextCharFormat fmt = cursor.charFormat(); QFont font = fmt.font(); font.setPointSizeF(font.pointSizeF() * ratio); fmt.setFont(font); cursor.setCharFormat(fmt); } /*! Returns an icon to be used in the project explorer. */ QIcon TextLabel::icon() const{ return QIcon::fromTheme("draw-text"); } QMenu* TextLabel::createContextMenu() { QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" if (!visibilityAction) { visibilityAction = new QAction(i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &TextLabel::visibilityChanged); } visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertSeparator(firstAction); return menu; } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::TextWrapper, text, textWrapper) CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, fontColor, fontColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, backgroundColor, backgroundColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QFont, teXFont, teXFont); CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::PositionWrapper, position, position); BASIC_SHARED_D_READER_IMPL(TextLabel, WorksheetElement::HorizontalAlignment, horizontalAlignment, horizontalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, WorksheetElement::VerticalAlignment, verticalAlignment, verticalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, qreal, rotationAngle, rotationAngle); BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::BorderShape, borderShape, borderShape) CLASS_SHARED_D_READER_IMPL(TextLabel, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(TextLabel, qreal, borderOpacity, borderOpacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(TextLabel, SetText, TextLabel::TextWrapper, textWrapper, updateText); void TextLabel::setText(const TextWrapper &textWrapper) { Q_D(TextLabel); if ( (textWrapper.text != d->textWrapper.text) || (textWrapper.teXUsed != d->textWrapper.teXUsed) ) exec(new TextLabelSetTextCmd(d, textWrapper, ki18n("%1: set label text"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFont, QFont, teXFont, updateText); void TextLabel::setTeXFont(const QFont& font) { Q_D(TextLabel); if (font != d->teXFont) exec(new TextLabelSetTeXFontCmd(d, font, ki18n("%1: set TeX main font"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFontColor, QColor, fontColor, updateText); void TextLabel::setFontColor(const QColor color) { Q_D(TextLabel); if (color != d->fontColor) exec(new TextLabelSetTeXFontColorCmd(d, color, ki18n("%1: set font color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXBackgroundColor, QColor, backgroundColor, updateText); void TextLabel::setBackgroundColor(const QColor color) { Q_D(TextLabel); if (color != d->backgroundColor) exec(new TextLabelSetTeXBackgroundColorCmd(d, color, ki18n("%1: set background color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetPosition, TextLabel::PositionWrapper, position, retransform); void TextLabel::setPosition(const PositionWrapper& pos) { Q_D(TextLabel); if (pos.point != d->position.point || pos.horizontalPosition != d->position.horizontalPosition || pos.verticalPosition != d->position.verticalPosition) exec(new TextLabelSetPositionCmd(d, pos, ki18n("%1: set position"))); } /*! sets the position without undo/redo-stuff */ void TextLabel::setPosition(QPointF point) { Q_D(TextLabel); if (point != d->position.point) { d->position.point = point; retransform(); } } /*! * position is set to invalid if the parent item is not drawn on the scene * (e.g. axis is not drawn because it's outside plot ranges -> don't draw axis' title label) */ void TextLabel::setPositionInvalid(bool invalid) { Q_D(TextLabel); if (invalid != d->positionInvalid) d->positionInvalid = invalid; } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetRotationAngle, qreal, rotationAngle, recalcShapeAndBoundingRect); void TextLabel::setRotationAngle(qreal angle) { Q_D(TextLabel); if (angle != d->rotationAngle) exec(new TextLabelSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetHorizontalAlignment, TextLabel::HorizontalAlignment, horizontalAlignment, retransform); void TextLabel::setHorizontalAlignment(const WorksheetElement::HorizontalAlignment hAlign) { Q_D(TextLabel); if (hAlign != d->horizontalAlignment) exec(new TextLabelSetHorizontalAlignmentCmd(d, hAlign, ki18n("%1: set horizontal alignment"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetVerticalAlignment, WorksheetElement::VerticalAlignment, verticalAlignment, retransform); void TextLabel::setVerticalAlignment(const TextLabel::VerticalAlignment vAlign) { Q_D(TextLabel); if (vAlign != d->verticalAlignment) exec(new TextLabelSetVerticalAlignmentCmd(d, vAlign, ki18n("%1: set vertical alignment"))); } //Border STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderShape, TextLabel::BorderShape, borderShape, updateBorder) void TextLabel::setBorderShape(TextLabel::BorderShape shape) { Q_D(TextLabel); if (shape != d->borderShape) exec(new TextLabelSetBorderShapeCmd(d, shape, ki18n("%1: set border shape"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderPen, QPen, borderPen, update) void TextLabel::setBorderPen(const QPen &pen) { Q_D(TextLabel); if (pen != d->borderPen) exec(new TextLabelSetBorderPenCmd(d, pen, ki18n("%1: set border"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderOpacity, qreal, borderOpacity, update) void TextLabel::setBorderOpacity(qreal opacity) { Q_D(TextLabel); if (opacity != d->borderOpacity) exec(new TextLabelSetBorderOpacityCmd(d, opacity, ki18n("%1: set border opacity"))); } //misc STD_SWAP_METHOD_SETTER_CMD_IMPL_F(TextLabel, SetVisible, bool, swapVisible, retransform); void TextLabel::setVisible(bool on) { Q_D(TextLabel); exec(new TextLabelSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool TextLabel::isVisible() const { Q_D(const TextLabel); return d->isVisible(); } void TextLabel::setPrinting(bool on) { Q_D(TextLabel); d->m_printing = on; } void TextLabel::updateTeXImage() { Q_D(TextLabel); d->updateTeXImage(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void TextLabel::visibilityChanged() { Q_D(const TextLabel); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## TextLabelPrivate::TextLabelPrivate(TextLabel* owner) : q(owner) { setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsFocusable); setAcceptHoverEvents(true); } QString TextLabelPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the label. Called on geometry or text changes. */ void TextLabelPrivate::retransform() { if (suppressRetransform) return; - if (position.horizontalPosition != WorksheetElement::hPositionCustom - || position.verticalPosition != WorksheetElement::vPositionCustom) + if (position.horizontalPosition != WorksheetElement::HorizontalPosition::Custom + || position.verticalPosition != WorksheetElement::VerticalPosition::Custom) updatePosition(); double x = position.point.x(); double y = position.point.y(); //determine the size of the label in scene units. double w, h; if (textWrapper.teXUsed) { //image size is in pixel, convert to scene units w = teXImage.width()*teXImageScaleFactor; h = teXImage.height()*teXImageScaleFactor; } else { //size is in points, convert to scene units w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new GraphicsItem's position in parent's coordinate system QPointF itemPos; switch (horizontalAlignment) { - case WorksheetElement::hAlignLeft: + case WorksheetElement::HorizontalAlignment::Left: itemPos.setX(x - w/2); break; - case WorksheetElement::hAlignCenter: + case WorksheetElement::HorizontalAlignment::Center: itemPos.setX(x); break; - case WorksheetElement::hAlignRight: + case WorksheetElement::HorizontalAlignment::Right: itemPos.setX(x + w/2); break; } switch (verticalAlignment) { - case WorksheetElement::vAlignTop: + case WorksheetElement::VerticalAlignment::Top: itemPos.setY(y - h/2); break; - case WorksheetElement::vAlignCenter: + case WorksheetElement::VerticalAlignment::Center: itemPos.setY(y); break; - case WorksheetElement::vAlignBottom: + case WorksheetElement::VerticalAlignment::Bottom: itemPos.setY(y + h/2); break; } suppressItemChangeEvent = true; setPos(itemPos); suppressItemChangeEvent = false; boundingRectangle.setX(-w/2); boundingRectangle.setY(-h/2); boundingRectangle.setWidth(w); boundingRectangle.setHeight(h); updateBorder(); emit q->changed(); } /*! calculates the position of the label, when the position relative to the parent was specified (left, right, etc.) */ void TextLabelPrivate::updatePosition() { //determine the parent item QRectF parentRect; QGraphicsItem* parent = parentItem(); if (parent) { parentRect = parent->boundingRect(); } else { if (!scene()) return; parentRect = scene()->sceneRect(); } - if (position.horizontalPosition != WorksheetElement::hPositionCustom) { - if (position.horizontalPosition == WorksheetElement::hPositionLeft) + if (position.horizontalPosition != WorksheetElement::HorizontalPosition::Custom) { + if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Left) position.point.setX( parentRect.x() ); - else if (position.horizontalPosition == WorksheetElement::hPositionCenter) + else if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Center) position.point.setX( parentRect.x() + parentRect.width()/2 ); - else if (position.horizontalPosition == WorksheetElement::hPositionRight) + else if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Right) position.point.setX( parentRect.x() + parentRect.width() ); } - if (position.verticalPosition != WorksheetElement::vPositionCustom) { - if (position.verticalPosition == WorksheetElement::vPositionTop) + if (position.verticalPosition != WorksheetElement::VerticalPosition::Custom) { + if (position.verticalPosition == WorksheetElement::VerticalPosition::Top) position.point.setY( parentRect.y() ); - else if (position.verticalPosition == WorksheetElement::vPositionCenter) + else if (position.verticalPosition == WorksheetElement::VerticalPosition::Center) position.point.setY( parentRect.y() + parentRect.height()/2 ); - else if (position.verticalPosition == WorksheetElement::vPositionBottom) + else if (position.verticalPosition == WorksheetElement::VerticalPosition::Bottom) position.point.setY( parentRect.y() + parentRect.height() ); } emit q->positionChanged(position); } /*! updates the static text. */ void TextLabelPrivate::updateText() { if (suppressRetransform) return; if (textWrapper.teXUsed) { TeXRenderer::Formatting format; format.fontColor = fontColor; format.backgroundColor = backgroundColor; format.fontSize = teXFont.pointSize(); format.fontFamily = teXFont.family(); format.dpi = teXImageResolution; QFuture future = QtConcurrent::run(TeXRenderer::renderImageLaTeX, textWrapper.text, &teXRenderSuccessful, format); teXImageFutureWatcher.setFuture(future); //don't need to call retransorm() here since it is done in updateTeXImage //when the asynchronous rendering of the image is finished. } else { staticText.setText(textWrapper.text); //the size of the label was most probably changed. //call retransform() to recalculate the position and the bounding box of the label retransform(); } } void TextLabelPrivate::updateTeXImage() { teXImage = teXImageFutureWatcher.result(); retransform(); DEBUG("teXRenderSuccessful =" << teXRenderSuccessful); emit q->teXImageUpdated(teXRenderSuccessful); } void TextLabelPrivate::updateBorder() { borderShapePath = QPainterPath(); switch (borderShape) { case TextLabel::BorderShape::NoBorder: break; case TextLabel::BorderShape::Rect: borderShapePath.addRect(boundingRectangle); break; case TextLabel::BorderShape::Ellipse: { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.addEllipse(xs - 0.1 * w, ys - 0.1 * h, 1.2 * w, 1.2 * h); break; } case TextLabel::BorderShape::RoundSideRect: { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs, ys); borderShapePath.lineTo(xs + w, ys); borderShapePath.quadTo(xs + w + h/2, ys + h/2, xs + w, ys + h); borderShapePath.lineTo(xs, ys + h); borderShapePath.quadTo(xs - h/2, ys + h/2, xs, ys); break; } case TextLabel::BorderShape::RoundCornerRect: { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h * 0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case TextLabel::BorderShape::InwardsRoundCornerRect: { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs, ys - 0.3 * h); borderShapePath.lineTo(xs + w, ys - 0.3 * h); borderShapePath.quadTo(xs + w, ys, xs + w + 0.3 * h, ys); borderShapePath.lineTo(xs + w + 0.3 * h, ys + h); borderShapePath.quadTo(xs + w, ys + h, xs + w, ys + h + 0.3 * h); borderShapePath.lineTo(xs, ys + h + 0.3 * h); borderShapePath.quadTo(xs, ys + h, xs - 0.3 * h, ys + h); borderShapePath.lineTo(xs - 0.3 * h, ys); borderShapePath.quadTo(xs, ys, xs, ys - 0.3 * h); break; } case TextLabel::BorderShape::DentedBorderRect: { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs - 0.2 * h, ys - 0.2 * h); borderShapePath.quadTo(xs + w / 2, ys, xs + w + 0.2 * h, ys - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h / 2, xs + w + 0.2 * h, ys + h + 0.2 * h); borderShapePath.quadTo(xs + w / 2, ys + h, xs - 0.2 * h, ys + h + 0.2 * h); borderShapePath.quadTo(xs, ys + h / 2, xs - 0.2 * h, ys - 0.2 * h); break; } case TextLabel::BorderShape::Cuboid: { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs, ys); borderShapePath.lineTo(xs + w, ys); borderShapePath.lineTo(xs + w, ys + h); borderShapePath.lineTo(xs, ys + h); borderShapePath.lineTo(xs, ys); borderShapePath.lineTo(xs + 0.3 * h, ys - 0.2 * h); borderShapePath.lineTo(xs + w + 0.3 * h, ys - 0.2 * h); borderShapePath.lineTo(xs + w, ys); borderShapePath.moveTo(xs + w, ys + h); borderShapePath.lineTo(xs + w + 0.3 * h, ys + h - 0.2 * h); borderShapePath.lineTo(xs + w + 0.3 * h, ys - 0.2 * h); break; } case TextLabel::BorderShape::UpPointingRectangle: { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h * 0.2, ys); borderShapePath.lineTo(xs + w / 2 - 0.2 * h, ys); borderShapePath.lineTo(xs + w / 2, ys - 0.2 * h); borderShapePath.lineTo(xs + w / 2 + 0.2 * h, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case (TextLabel::BorderShape::DownPointingRectangle): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs +h * 0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + w / 2 + 0.2 * h, ys + h); borderShapePath.lineTo(xs + w / 2, ys + h + 0.2 * h); borderShapePath.lineTo(xs + w / 2 - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case (TextLabel::BorderShape::LeftPointingRectangle): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h*0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + h / 2 + 0.2 * h); borderShapePath.lineTo(xs - 0.2 * h, ys + h / 2); borderShapePath.lineTo(xs, ys + h / 2 - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case (TextLabel::BorderShape::RightPointingRectangle): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h * 0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h / 2 - 0.2 * h); borderShapePath.lineTo(xs + w + 0.2 * h, ys + h / 2); borderShapePath.lineTo(xs + w, ys + h / 2 + 0.2 * h); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } } recalcShapeAndBoundingRect(); } bool TextLabelPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF TextLabelPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath TextLabelPrivate::shape() const { return labelShape; } /*! recalculates the outer bounds and the shape of the label. */ void TextLabelPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); QMatrix matrix; matrix.rotate(-rotationAngle); labelShape = QPainterPath(); if (borderShape != TextLabel::BorderShape::NoBorder) { labelShape.addPath(WorksheetElement::shapeFromPath(borderShapePath, borderPen)); transformedBoundingRectangle = matrix.mapRect(labelShape.boundingRect()); } else { labelShape.addRect(boundingRectangle); transformedBoundingRectangle = matrix.mapRect(boundingRectangle); } labelShape = matrix.map(labelShape); } void TextLabelPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (positionInvalid) return; if (textWrapper.text.isEmpty()) return; painter->save(); //draw the text painter->rotate(-rotationAngle); if (textWrapper.teXUsed) { if (boundingRect().width() != 0.0 && boundingRect().height() != 0.0) painter->drawImage(boundingRect(), teXImage); } else { // don't set fontColor to pen, because the color // is already in the html code //painter->setPen(fontColor); painter->scale(scaleFactor, scaleFactor); float w = staticText.size().width(); float h = staticText.size().height(); painter->drawStaticText(QPoint(-w/2,-h/2), staticText); } painter->restore(); //draw the border if (borderShape != TextLabel::BorderShape::NoBorder) { painter->save(); painter->rotate(-rotationAngle); painter->setPen(borderPen); painter->setOpacity(borderOpacity); painter->drawPath(borderShapePath); painter->restore(); } if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(labelShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(labelShape); } } QVariant TextLabelPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates TextLabel::PositionWrapper tempPosition; tempPosition.point = positionFromItemPosition(value.toPointF()); - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void TextLabelPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = positionFromItemPosition(pos()); if (point != position.point) { //position was changed -> set the position related member variables suppressRetransform = true; TextLabel::PositionWrapper tempPosition; tempPosition.point = point; - tempPosition.horizontalPosition = TextLabel::hPositionCustom; - tempPosition.verticalPosition = TextLabel::vPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } void TextLabelPrivate::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ||event->key() == Qt::Key_Down) { const int delta = 5; QPointF point = positionFromItemPosition(pos()); WorksheetElement::PositionWrapper tempPosition; if (event->key() == Qt::Key_Left) { point.setX(point.x() - delta); - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; tempPosition.verticalPosition = position.verticalPosition; } else if (event->key() == Qt::Key_Right) { point.setX(point.x() + delta); - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; tempPosition.verticalPosition = position.verticalPosition; } else if (event->key() == Qt::Key_Up) { point.setY(point.y() - delta); tempPosition.horizontalPosition = position.horizontalPosition; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; } else if (event->key() == Qt::Key_Down) { point.setY(point.y() + delta); tempPosition.horizontalPosition = position.horizontalPosition; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; } tempPosition.point = point; q->setPosition(tempPosition); } QGraphicsItem::keyPressEvent(event); } /*! * converts label's position to GraphicsItem's position. */ QPointF TextLabelPrivate::positionFromItemPosition(QPointF itemPos) { double x = itemPos.x(); double y = itemPos.y(); double w, h; QPointF tmpPosition; if (textWrapper.teXUsed) { w = teXImage.width()*scaleFactor; h = teXImage.height()*scaleFactor; } else { w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new position switch (horizontalAlignment) { - case WorksheetElement::hAlignLeft: + case WorksheetElement::HorizontalAlignment::Left: tmpPosition.setX(x + w/2); break; - case WorksheetElement::hAlignCenter: + case WorksheetElement::HorizontalAlignment::Center: tmpPosition.setX(x); break; - case WorksheetElement::hAlignRight: + case WorksheetElement::HorizontalAlignment::Right: tmpPosition.setX(x - w/2); break; } switch (verticalAlignment) { - case WorksheetElement::vAlignTop: + case WorksheetElement::VerticalAlignment::Top: tmpPosition.setY(y + h/2); break; - case WorksheetElement::vAlignCenter: + case WorksheetElement::VerticalAlignment::Center: tmpPosition.setY(y); break; - case WorksheetElement::vAlignBottom: + case WorksheetElement::VerticalAlignment::Bottom: tmpPosition.setY(y - h/2); break; } return tmpPosition; } void TextLabelPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void TextLabelPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void TextLabelPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void TextLabel::save(QXmlStreamWriter* writer) const { Q_D(const TextLabel); writer->writeStartElement( "textLabel" ); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); - writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); - writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); - writer->writeAttribute( "horizontalAlignment", QString::number(d->horizontalAlignment) ); - writer->writeAttribute( "verticalAlignment", QString::number(d->verticalAlignment) ); + writer->writeAttribute( "horizontalPosition", QString::number(static_cast(d->position.horizontalPosition)) ); + writer->writeAttribute( "verticalPosition", QString::number(static_cast(d->position.verticalPosition)) ); + writer->writeAttribute( "horizontalAlignment", QString::number(static_cast(d->horizontalAlignment)) ); + writer->writeAttribute( "verticalAlignment", QString::number(static_cast(d->verticalAlignment)) ); writer->writeAttribute( "rotationAngle", QString::number(d->rotationAngle) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); writer->writeStartElement( "text" ); writer->writeCharacters( d->textWrapper.text ); writer->writeEndElement(); writer->writeStartElement( "format" ); writer->writeAttribute( "teXUsed", QString::number(d->textWrapper.teXUsed) ); WRITE_QFONT(d->teXFont); writer->writeAttribute( "fontColor_r", QString::number(d->fontColor.red()) ); writer->writeAttribute( "fontColor_g", QString::number(d->fontColor.green()) ); writer->writeAttribute( "fontColor_b", QString::number(d->fontColor.blue()) ); writer->writeEndElement(); //border writer->writeStartElement("border"); writer->writeAttribute("borderShape", QString::number(static_cast(d->borderShape))); WRITE_QPEN(d->borderPen); writer->writeAttribute("borderOpacity", QString::number(d->borderOpacity)); writer->writeEndElement(); if (d->textWrapper.teXUsed) { writer->writeStartElement("teXImage"); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); d->teXImage.save(&buffer, "PNG"); writer->writeCharacters(ba.toBase64()); writer->writeEndElement(); } writer->writeEndElement(); // close "textLabel" section } //! Load from XML bool TextLabel::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; Q_D(TextLabel); KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool teXImageFound = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "textLabel") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->position.point.setY(str.toDouble()); READ_INT_VALUE("horizontalPosition", position.horizontalPosition, WorksheetElement::HorizontalPosition); READ_INT_VALUE("verticalPosition", position.verticalPosition, WorksheetElement::VerticalPosition); READ_INT_VALUE("horizontalAlignment", horizontalAlignment, WorksheetElement::HorizontalAlignment); READ_INT_VALUE("verticalAlignment", verticalAlignment, WorksheetElement::VerticalAlignment); READ_DOUBLE_VALUE("rotationAngle", rotationAngle); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "text") { d->textWrapper.text = reader->readElementText(); } else if (!preview && reader->name() == "format") { attribs = reader->attributes(); READ_INT_VALUE("teXUsed", textWrapper.teXUsed, bool); READ_QFONT(d->teXFont); str = attribs.value("fontColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fontColor_r").toString()); else d->fontColor.setRed( str.toInt() ); str = attribs.value("fontColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fontColor_g").toString()); else d->fontColor.setGreen( str.toInt() ); str = attribs.value("fontColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fontColor_b").toString()); else d->fontColor.setBlue( str.toInt() ); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_INT_VALUE("borderShape", borderShape, BorderShape); READ_QPEN(d->borderPen); READ_DOUBLE_VALUE("borderOpacity", borderOpacity); } else if (!preview && reader->name() == "teXImage") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray ba = QByteArray::fromBase64(content.toLatin1()); teXImageFound = d->teXImage.loadFromData(ba); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; //in case we use latex and the image was stored (older versions of LabPlot didn't save the image)and loaded, //we just need to retransform. //otherwise, we set the static text and retransform in updateText() if ( !(d->textWrapper.teXUsed && teXImageFound) ) d->updateText(); else retransform(); return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void TextLabel::loadThemeConfig(const KConfig& config) { Q_D(TextLabel); KConfigGroup group = config.group("Label"); d->fontColor = group.readEntry("FontColor", QColor(Qt::white)); // used when it's latex text d->backgroundColor = group.readEntry("BackgroundColor", QColor(Qt::black)); // used when it's latex text if (!d->textWrapper.teXUsed && !d->textWrapper.text.isEmpty()) { // TODO: Replace QTextEdit by QTextDocument, because this does not contain the graphical stuff // to set the color in a html text, a qTextEdit must be used QTextEdit te; te.setHtml(d->textWrapper.text); te.selectAll(); te.setTextColor(d->fontColor); //te.setTextBackgroundColor(backgroundColor); // for plain text no background color supported, due to bug https://bugreports.qt.io/browse/QTBUG-25420 // update the text. also in the Widget to which is connected TextWrapper wrapper(te.toHtml(), false, true); setText(wrapper); } // otherwise when changing theme while the textlabel dock is visible, the // color comboboxes do not change the color backgroundColorChanged(d->backgroundColor); fontColorChanged(d->fontColor); group = config.group("CartesianPlot"); QPen pen = this->borderPen(); pen.setColor(group.readEntry("BorderColor", pen.color())); pen.setStyle((Qt::PenStyle)(group.readEntry("BorderStyle", (int) pen.style()))); pen.setWidthF(group.readEntry("BorderWidth", pen.widthF())); this->setBorderPen(pen); this->setBorderOpacity(group.readEntry("BorderOpacity", this->borderOpacity())); } void TextLabel::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Label"); //TODO // group.writeEntry("TeXFontColor", (QColor) this->fontColor()); } diff --git a/src/backend/worksheet/TextLabelPrivate.h b/src/backend/worksheet/TextLabelPrivate.h index 1fd9deda5..d7527015d 100644 --- a/src/backend/worksheet/TextLabelPrivate.h +++ b/src/backend/worksheet/TextLabelPrivate.h @@ -1,112 +1,112 @@ /*************************************************************************** File : TextLabelPrivate.h Project : LabPlot Description : Private members of TextLabel -------------------------------------------------------------------- Copyright : (C) 2012-2014 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2019 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ #ifndef TEXTLABELPRIVATE_H #define TEXTLABELPRIVATE_H #include #include #include #include class QGraphicsSceneHoverEvent; class TextLabelPrivate: public QGraphicsItem { public: explicit TextLabelPrivate(TextLabel*); qreal rotationAngle{0.0}; //scaling: //we need to scale from the font size specified in points to scene units. //furhermore, we create the tex-image in a higher resolution then usual desktop resolution // -> take this into account - float scaleFactor{Worksheet::convertToSceneUnits(1, Worksheet::Point)}; + float scaleFactor{Worksheet::convertToSceneUnits(1, Worksheet::Unit::Point)}; int teXImageResolution{QApplication::desktop()->physicalDpiX()}; - float teXImageScaleFactor{Worksheet::convertToSceneUnits(2.54/QApplication::desktop()->physicalDpiX(), Worksheet::Centimeter)}; + float teXImageScaleFactor{Worksheet::convertToSceneUnits(2.54/QApplication::desktop()->physicalDpiX(), Worksheet::Unit::Centimeter)}; TextLabel::TextWrapper textWrapper; QFont teXFont{"Computer Modern", 42}; QColor fontColor{Qt::black}; // used only by the theme for unformatted text. The text font is in the HTML and so this variable is never set QColor backgroundColor{Qt::white}; // used only by the theme for unformatted text. The text font is in the HTML and so this variable is never set QImage teXImage; QFutureWatcher teXImageFutureWatcher; bool teXRenderSuccessful{false}; // see TextLabel::init() for type specific default settings // position in parent's coordinate system, the label gets aligned around this point WorksheetElement::PositionWrapper position{ - QPoint(Worksheet::convertToSceneUnits(1, Worksheet::Centimeter), Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)), - TextLabel::hPositionCenter, TextLabel::vPositionCenter}; + QPoint(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Centimeter), Worksheet::convertToSceneUnits(1, Worksheet::Unit::Centimeter)), + TextLabel::HorizontalPosition::Center, TextLabel::VerticalPosition::Center}; bool positionInvalid{false}; - WorksheetElement::HorizontalAlignment horizontalAlignment{WorksheetElement::hAlignCenter}; - WorksheetElement::VerticalAlignment verticalAlignment{WorksheetElement::vAlignCenter}; + WorksheetElement::HorizontalAlignment horizontalAlignment{WorksheetElement::HorizontalAlignment::Center}; + WorksheetElement::VerticalAlignment verticalAlignment{WorksheetElement::VerticalAlignment::Center}; TextLabel::BorderShape borderShape{TextLabel::BorderShape::NoBorder}; - QPen borderPen{Qt::black, Worksheet::convertToSceneUnits(1.0, Worksheet::Point), Qt::SolidLine}; + QPen borderPen{Qt::black, Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point), Qt::SolidLine}; qreal borderOpacity{1.0}; QString name() const; void retransform(); bool swapVisible(bool on); virtual void recalcShapeAndBoundingRect(); void updatePosition(); QPointF positionFromItemPosition(QPointF); void updateText(); void updateTeXImage(); void updateBorder(); QStaticText staticText; bool suppressItemChangeEvent{false}; bool suppressRetransform{false}; bool m_printing{false}; bool m_hovered{false}; QRectF boundingRectangle; //bounding rectangle of the text QRectF transformedBoundingRectangle; //bounding rectangle of transformed (rotated etc.) text QPainterPath borderShapePath; QPainterPath labelShape; //reimplemented from QGraphicsItem QRectF boundingRect() const override; QPainterPath shape() const override; void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr) override; QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; TextLabel* const q; private: void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; void keyPressEvent(QKeyEvent*) override; void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; }; #endif diff --git a/src/backend/worksheet/Worksheet.cpp b/src/backend/worksheet/Worksheet.cpp index 7cd79af7e..ce8e43fa2 100644 --- a/src/backend/worksheet/Worksheet.cpp +++ b/src/backend/worksheet/Worksheet.cpp @@ -1,1589 +1,1589 @@ /*************************************************************************** File : Worksheet.cpp Project : LabPlot Description : Worksheet -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2011-2020 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 "Worksheet.h" #include "WorksheetPrivate.h" #include "WorksheetElement.h" #include "commonfrontend/worksheet/WorksheetView.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/Image.h" #include "backend/worksheet/TreeModel.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "kdefrontend/worksheet/ExportWorksheetDialog.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include #include #include /** * \class Worksheet * \brief Top-level container for worksheet elements like plot, labels, etc. * * The worksheet is, besides the data containers \c Spreadsheet and \c Matrix, another central part of the application * and provides an area for showing and grouping together different kinds of worksheet objects - plots, labels &etc; * * * \ingroup worksheet */ Worksheet::Worksheet(const QString& name, bool loading) : AbstractPart(name, AspectType::Worksheet), d(new WorksheetPrivate(this)) { connect(this, &Worksheet::aspectAdded, this, &Worksheet::handleAspectAdded); connect(this, &Worksheet::aspectAboutToBeRemoved, this, &Worksheet::handleAspectAboutToBeRemoved); connect(this, &Worksheet::aspectRemoved, this, &Worksheet::handleAspectRemoved); if (!loading) init(); } Worksheet::~Worksheet() { delete d; } void Worksheet::init() { KConfig config; KConfigGroup group = config.group("Worksheet"); //size d->scaleContent = group.readEntry("ScaleContent", false); d->useViewSize = group.readEntry("UseViewSize", false); d->pageRect.setX(0); d->pageRect.setY(0); d->pageRect.setWidth(group.readEntry("Width", 1000)); d->pageRect.setHeight(group.readEntry("Height", 1000)); d->m_scene->setSceneRect(d->pageRect); //background d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); //layout - d->layout = (Worksheet::Layout) group.readEntry("Layout", (int) Worksheet::VerticalLayout); - d->layoutTopMargin = group.readEntry("LayoutTopMargin", convertToSceneUnits(0.5, Centimeter)); - d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", convertToSceneUnits(0.5, Centimeter)); - d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", convertToSceneUnits(0.5, Centimeter)); - d->layoutRightMargin = group.readEntry("LayoutRightMargin", convertToSceneUnits(0.5, Centimeter)); - d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", convertToSceneUnits(0.5, Centimeter)); - d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", convertToSceneUnits(0.5, Centimeter)); + d->layout = (Layout) group.readEntry("Layout", static_cast(Layout::VerticalLayout)); + d->layoutTopMargin = group.readEntry("LayoutTopMargin", convertToSceneUnits(0.5, Unit::Centimeter)); + d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", convertToSceneUnits(0.5, Unit::Centimeter)); + d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", convertToSceneUnits(0.5, Unit::Centimeter)); + d->layoutRightMargin = group.readEntry("LayoutRightMargin", convertToSceneUnits(0.5, Unit::Centimeter)); + d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", convertToSceneUnits(0.5, Unit::Centimeter)); + d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", convertToSceneUnits(0.5, Unit::Centimeter)); d->layoutRowCount = group.readEntry("LayoutRowCount", 2); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 2); //default theme KConfigGroup settings = KSharedConfig::openConfig()->group(QLatin1String("Settings_Worksheet")); d->theme = settings.readEntry(QStringLiteral("Theme"), QString()); loadTheme(d->theme); } /*! converts from \c unit to the scene units. At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertToSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { - case Worksheet::Millimeter: - return value*10.0; - case Worksheet::Centimeter: - return value*100.0; - case Worksheet::Inch: - return value*25.4*10.; - case Worksheet::Point: - return value*25.4/72.*10.; + case Unit::Millimeter: + return value * 10.0; + case Unit::Centimeter: + return value * 100.0; + case Unit::Inch: + return value * 25.4 * 10.; + case Unit::Point: + return value * 25.4/72. * 10.; } return 0; } /*! converts from the scene units to \c unit . At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertFromSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { - case Worksheet::Millimeter: + case Unit::Millimeter: return value/10.0; - case Worksheet::Centimeter: + case Unit::Centimeter: return value/100.0; - case Worksheet::Inch: + case Unit::Inch: return value/25.4/10.; - case Worksheet::Point: + case Unit::Point: return value/25.4/10.*72.; } return 0; } QIcon Worksheet::icon() const { return QIcon::fromTheme("labplot-worksheet"); } /** * Return a new context menu. The caller takes ownership of the menu. */ QMenu* Worksheet::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); Q_ASSERT(menu); emit requestProjectContextMenu(menu); return 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* Worksheet::view() const { if (!m_partView) { m_view = new WorksheetView(const_cast(this)); m_partView = m_view; connect(m_view, &WorksheetView::statusInfo, this, &Worksheet::statusInfo); connect(this, &Worksheet::cartesianPlotMouseModeChanged, m_view, &WorksheetView::cartesianPlotMouseModeChangedSlot); } return m_partView; } /*! * returns the list of all parent aspects (folders and sub-folders) * together with all the data containers required to plot the data in the worksheet */ QVector Worksheet::dependsOn() const { //add all parent aspects (folders and sub-folders) QVector aspects = AbstractAspect::dependsOn(); //traverse all plots and add all data containers they depend on for (const auto* plot : children()) aspects << plot->dependsOn(); return aspects; } bool Worksheet::exportView() const { auto* dlg = new ExportWorksheetDialog(m_view); dlg->setFileName(name()); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { QString path = dlg->path(); const WorksheetView::ExportFormat format = dlg->exportFormat(); const WorksheetView::ExportArea area = dlg->exportArea(); const bool background = dlg->exportBackground(); const int resolution = dlg->exportResolution(); WAIT_CURSOR; m_view->exportToFile(path, format, area, background, resolution); RESET_CURSOR; } delete dlg; return ret; } bool Worksheet::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, m_view); dlg->setWindowTitle(i18nc("@title:window", "Print Worksheet")); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool Worksheet::printPreview() const { auto* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &WorksheetView::print); return dlg->exec(); } void Worksheet::handleAspectAdded(const AbstractAspect* aspect) { const auto* addedElement = qobject_cast(aspect); if (!addedElement) return; if (aspect->parentAspect() != this) return; //add the GraphicsItem of the added child to the scene QGraphicsItem* item = addedElement->graphicsItem(); d->m_scene->addItem(item); const CartesianPlot* plot = dynamic_cast(aspect); if (plot) { connect(plot, &CartesianPlot::mouseMoveCursorModeSignal, this, &Worksheet::cartesianPlotMouseMoveCursorMode); connect(plot, &CartesianPlot::mouseMoveZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseMoveZoomSelectionMode); connect(plot, &CartesianPlot::mousePressCursorModeSignal, this, &Worksheet::cartesianPlotMousePressCursorMode); connect(plot, &CartesianPlot::mousePressZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMousePressZoomSelectionMode); connect(plot, &CartesianPlot::mouseReleaseZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseReleaseZoomSelectionMode); connect(plot, &CartesianPlot::mouseHoverZoomSelectionModeSignal, this, &Worksheet::cartesianPlotMouseHoverZoomSelectionMode); connect(plot, &CartesianPlot::mouseHoverOutsideDataRectSignal, this, &Worksheet::cartesianPlotMouseHoverOutsideDataRect); connect(plot, &CartesianPlot::aspectDescriptionChanged, this, &Worksheet::updateCompleteCursorTreeModel); connect(plot, &CartesianPlot::curveNameChanged, this, &Worksheet::updateCompleteCursorTreeModel); connect(plot, &CartesianPlot::curveRemoved, this, &Worksheet::curveRemoved); connect(plot, &CartesianPlot::curveAdded, this, &Worksheet::curveAdded); connect(plot, &CartesianPlot::visibleChanged, this, &Worksheet::updateCompleteCursorTreeModel); connect(plot, &CartesianPlot::curveVisibilityChangedSignal, this, &Worksheet::updateCompleteCursorTreeModel); connect(plot, &CartesianPlot::curveDataChanged, this, &Worksheet::curveDataChanged); connect(plot, static_cast(&CartesianPlot::curveLinePenChanged), this, &Worksheet::updateCurveBackground); connect(plot, &CartesianPlot::mouseModeChanged, this, &Worksheet::cartesianPlotMouseModeChangedSlot); auto* p = const_cast(plot); p->setLocked(d->plotsLocked); cursorModelPlotAdded(p->name()); } qreal zVal = 0; for (auto* child : children(ChildIndexFlag::IncludeHidden)) child->graphicsItem()->setZValue(zVal++); //if a theme was selected in the worksheet, apply this theme for newly added children if (!d->theme.isEmpty() && !isLoading()) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(addedElement)->loadThemeConfig(config); } //recalculated the layout if (!isLoading()) { - if (d->layout != Worksheet::NoLayout) + if (d->layout != Worksheet::Layout::NoLayout) d->updateLayout(false); } } void Worksheet::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const auto* removedElement = qobject_cast(aspect); if (removedElement) { QGraphicsItem* item = removedElement->graphicsItem(); d->m_scene->removeItem(item); } } void Worksheet::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); - if (d->layout != Worksheet::NoLayout) + if (d->layout != Worksheet::Layout::NoLayout) d->updateLayout(false); auto* plot = dynamic_cast(child); if (plot) cursorModelPlotRemoved(plot->name()); } QGraphicsScene* Worksheet::scene() const { return d->m_scene; } QRectF Worksheet::pageRect() const { return d->m_scene->sceneRect(); } /*! this slot is called when a worksheet element is selected in the project explorer. emits \c itemSelected() which forwards this event to the \c WorksheetView in order to select the corresponding \c QGraphicsItem. */ void Worksheet::childSelected(const AbstractAspect* aspect) { auto* element = qobject_cast(const_cast(aspect)); if (element) emit itemSelected(element->graphicsItem()); } /*! this slot is called when a worksheet element is deselected in the project explorer. emits \c itemDeselected() which forwards this event to \c WorksheetView in order to deselect the corresponding \c QGraphicsItem. */ void Worksheet::childDeselected(const AbstractAspect* aspect) { auto* element = qobject_cast(const_cast(aspect)); if (element) emit itemDeselected(element->graphicsItem()); } /*! * Emits the signal to select or to deselect the aspect corresponding to \c QGraphicsItem \c item in the project explorer, * if \c selected=true or \c selected=false, respectively. * The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer. * This function is called in \c WorksheetView upon selection changes. */ void Worksheet::setItemSelectedInView(const QGraphicsItem* item, const bool b) { //determine the corresponding aspect const AbstractAspect* aspect(nullptr); for (const auto* child : children(ChildIndexFlag::IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; //forward selection/deselection to AbstractTreeModel if (b) emit childAspectSelectedInView(aspect); else emit childAspectDeselectedInView(aspect); } /*! * helper function: checks whether \c aspect or one of its children has the \c GraphicsItem \c item * Returns a pointer to \c WorksheetElement having this item. */ WorksheetElement* Worksheet::aspectFromGraphicsItem(const WorksheetElement* aspect, const QGraphicsItem* item) const { if ( aspect->graphicsItem() == item ) return const_cast(aspect); else { for (const auto* child : aspect->children(AbstractAspect::ChildIndexFlag::IncludeHidden) ) { WorksheetElement* a = this->aspectFromGraphicsItem(child, item); if (a) return a; } return nullptr; } } /*! Selects or deselects the worksheet in the project explorer. This function is called in \c WorksheetView. The worksheet gets deselected if there are selected items in the view, and selected if there are no selected items in the view. */ void Worksheet::setSelectedInView(const bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } void Worksheet::deleteAspectFromGraphicsItem(const QGraphicsItem* item) { Q_ASSERT(item); //determine the corresponding aspect AbstractAspect* aspect(nullptr); for (const auto* child : children(ChildIndexFlag::IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; if (aspect->parentAspect()) aspect->parentAspect()->removeChild(aspect); else this->removeChild(aspect); } void Worksheet::setIsClosing() { if (m_view) m_view->setIsClosing(); } /*! * \brief Worksheet::getPlotCount * \return number of CartesianPlot's in the Worksheet */ int Worksheet::getPlotCount() { return children().length(); } /*! * \brief Worksheet::getPlot * \param index Number of plot which should be returned * \return Pointer to the CartesianPlot which was searched with index */ CartesianPlot* Worksheet::getPlot(int index) { auto plots = children(); if (plots.length() - 1 >= index) return plots.at(index); return nullptr; } TreeModel* Worksheet::cursorModel() { return d->cursorData; } void Worksheet::update() { emit requestUpdate(); } void Worksheet::setSuppressLayoutUpdate(bool value) { d->suppressLayoutUpdate = value; } void Worksheet::updateLayout() { d->updateLayout(); } Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotActionMode() { return d->cartesianPlotActionMode; } Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotCursorMode() { return d->cartesianPlotCursorMode; } bool Worksheet::plotsLocked() { return d->plotsLocked; } void Worksheet::setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode mode) { if (d->cartesianPlotActionMode == mode) return; d->cartesianPlotActionMode = mode; project()->setChanged(true); } void Worksheet::setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode mode) { if (d->cartesianPlotCursorMode == mode) return; d->cartesianPlotCursorMode = mode; - if (mode == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { + if (mode == CartesianPlotActionMode::ApplyActionToAll) { d->suppressCursorPosChanged = true; QVector plots = children(); QPointF logicPos; if (!plots.isEmpty()) { for (int i = 0; i < 2; i++) { logicPos = QPointF(plots[0]->cursorPos(i), 0); // y value does not matter cartesianPlotMousePressCursorMode(i, logicPos); } } d->suppressCursorPosChanged = false; } updateCompleteCursorTreeModel(); project()->setChanged(true); } void Worksheet::setPlotsLocked(bool lock) { if (d->plotsLocked == lock) return; d->plotsLocked = lock; for (auto* plot: children()) plot->setLocked(lock); project()->setChanged(true); } void Worksheet::registerShortcuts() { m_view->registerShortcuts(); } void Worksheet::unregisterShortcuts() { m_view->unregisterShortcuts(); } /* =============================== getter methods for general options ==================================== */ BASIC_D_READER_IMPL(Worksheet, bool, scaleContent, scaleContent) BASIC_D_READER_IMPL(Worksheet, bool, useViewSize, useViewSize) /* =============================== getter methods for background options ================================= */ BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_D_READER_IMPL(Worksheet, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_D_READER_IMPL(Worksheet, QString, backgroundFileName, backgroundFileName) BASIC_D_READER_IMPL(Worksheet, float, backgroundOpacity, backgroundOpacity) /* =============================== getter methods for layout options ====================================== */ BASIC_D_READER_IMPL(Worksheet, Worksheet::Layout, layout, layout) BASIC_D_READER_IMPL(Worksheet, float, layoutTopMargin, layoutTopMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutBottomMargin, layoutBottomMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutLeftMargin, layoutLeftMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutRightMargin, layoutRightMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_D_READER_IMPL(Worksheet, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_D_READER_IMPL(Worksheet, int, layoutRowCount, layoutRowCount) BASIC_D_READER_IMPL(Worksheet, int, layoutColumnCount, layoutColumnCount) CLASS_D_READER_IMPL(Worksheet, QString, theme, theme) /* ============================ setter methods and undo commands for general options ===================== */ void Worksheet::setUseViewSize(bool useViewSize) { if (useViewSize != d->useViewSize) { d->useViewSize = useViewSize; emit useViewSizeRequested(); } } STD_SETTER_CMD_IMPL_S(Worksheet, SetScaleContent, bool, scaleContent) void Worksheet::setScaleContent(bool scaleContent) { if (scaleContent != d->scaleContent) exec(new WorksheetSetScaleContentCmd(d, scaleContent, ki18n("%1: change \"rescale the content\" property"))); } /* ============================ setter methods and undo commands for background options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void Worksheet::setBackgroundType(PlotArea::BackgroundType type) { if (type != d->backgroundType) exec(new WorksheetSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void Worksheet::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { if (style != d->backgroundColorStyle) exec(new WorksheetSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void Worksheet::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { if (style != d->backgroundImageStyle) exec(new WorksheetSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void Worksheet::setBackgroundBrushStyle(Qt::BrushStyle style) { if (style != d->backgroundBrushStyle) exec(new WorksheetSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void Worksheet::setBackgroundFirstColor(const QColor &color) { if (color!= d->backgroundFirstColor) exec(new WorksheetSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void Worksheet::setBackgroundSecondColor(const QColor &color) { if (color!= d->backgroundSecondColor) exec(new WorksheetSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFileName, QString, backgroundFileName, update) void Worksheet::setBackgroundFileName(const QString& fileName) { if (fileName!= d->backgroundFileName) exec(new WorksheetSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundOpacity, float, backgroundOpacity, update) void Worksheet::setBackgroundOpacity(float opacity) { if (opacity != d->backgroundOpacity) exec(new WorksheetSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set opacity"))); } /* ============================ setter methods and undo commands for layout options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetLayout, Worksheet::Layout, layout, updateLayout) void Worksheet::setLayout(Worksheet::Layout layout) { if (layout != d->layout) { beginMacro(i18n("%1: set layout", name())); exec(new WorksheetSetLayoutCmd(d, layout, ki18n("%1: set layout"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutTopMargin, float, layoutTopMargin, updateLayout) void Worksheet::setLayoutTopMargin(float margin) { if (margin != d->layoutTopMargin) { beginMacro(i18n("%1: set layout top margin", name())); exec(new WorksheetSetLayoutTopMarginCmd(d, margin, ki18n("%1: set layout top margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutBottomMargin, float, layoutBottomMargin, updateLayout) void Worksheet::setLayoutBottomMargin(float margin) { if (margin != d->layoutBottomMargin) { beginMacro(i18n("%1: set layout bottom margin", name())); exec(new WorksheetSetLayoutBottomMarginCmd(d, margin, ki18n("%1: set layout bottom margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutLeftMargin, float, layoutLeftMargin, updateLayout) void Worksheet::setLayoutLeftMargin(float margin) { if (margin != d->layoutLeftMargin) { beginMacro(i18n("%1: set layout left margin", name())); exec(new WorksheetSetLayoutLeftMarginCmd(d, margin, ki18n("%1: set layout left margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRightMargin, float, layoutRightMargin, updateLayout) void Worksheet::setLayoutRightMargin(float margin) { if (margin != d->layoutRightMargin) { beginMacro(i18n("%1: set layout right margin", name())); exec(new WorksheetSetLayoutRightMarginCmd(d, margin, ki18n("%1: set layout right margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, updateLayout) void Worksheet::setLayoutVerticalSpacing(float spacing) { if (spacing != d->layoutVerticalSpacing) { beginMacro(i18n("%1: set layout vertical spacing", name())); exec(new WorksheetSetLayoutVerticalSpacingCmd(d, spacing, ki18n("%1: set layout vertical spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, updateLayout) void Worksheet::setLayoutHorizontalSpacing(float spacing) { if (spacing != d->layoutHorizontalSpacing) { beginMacro(i18n("%1: set layout horizontal spacing", name())); exec(new WorksheetSetLayoutHorizontalSpacingCmd(d, spacing, ki18n("%1: set layout horizontal spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRowCount, int, layoutRowCount, updateLayout) void Worksheet::setLayoutRowCount(int count) { if (count != d->layoutRowCount) { beginMacro(i18n("%1: set layout row count", name())); exec(new WorksheetSetLayoutRowCountCmd(d, count, ki18n("%1: set layout row count"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutColumnCount, int, layoutColumnCount, updateLayout) void Worksheet::setLayoutColumnCount(int count) { if (count != d->layoutColumnCount) { beginMacro(i18n("%1: set layout column count", name())); exec(new WorksheetSetLayoutColumnCountCmd(d, count, ki18n("%1: set layout column count"))); endMacro(); } } class WorksheetSetPageRectCmd : public StandardMacroSetterCmd { public: WorksheetSetPageRectCmd(Worksheet::Private* target, QRectF newValue, const KLocalizedString& description) : StandardMacroSetterCmd(target, &Worksheet::Private::pageRect, newValue, description) {} void finalize() override { m_target->updatePageRect(); emit m_target->q->pageRectChanged(m_target->*m_field); } void finalizeUndo() override { m_target->m_scene->setSceneRect(m_target->*m_field); emit m_target->q->pageRectChanged(m_target->*m_field); } }; void Worksheet::setPageRect(const QRectF& rect) { //don't allow any rectangulars of width/height equal to zero if (qFuzzyCompare(rect.width(), 0.) || qFuzzyCompare(rect.height(), 0.)) { emit pageRectChanged(d->pageRect); return; } if (rect != d->pageRect) { if (!d->useViewSize) { beginMacro(i18n("%1: set page size", name())); exec(new WorksheetSetPageRectCmd(d, rect, ki18n("%1: set page size"))); endMacro(); } else { d->pageRect = rect; d->updatePageRect(); emit pageRectChanged(d->pageRect); } } } void Worksheet::setPrinting(bool on) const { QVector childElements = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* child : childElements) child->setPrinting(on); } STD_SETTER_CMD_IMPL_S(Worksheet, SetTheme, QString, theme) void Worksheet::setTheme(const QString& theme) { if (theme != d->theme) { QString info; if (!theme.isEmpty()) info = i18n("%1: load theme %2", name(), theme); else info = i18n("%1: load default theme", name()); beginMacro(info); exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } } void Worksheet::cartesianPlotMousePressZoomSelectionMode(QPointF logicPos) { - if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotActionMode() == CartesianPlotActionMode::ApplyActionToAll) { auto plots = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* plot : plots) plot->mousePressZoomSelectionMode(logicPos); } else { CartesianPlot* plot = static_cast(QObject::sender()); plot->mousePressZoomSelectionMode(logicPos); } } void Worksheet::cartesianPlotMouseReleaseZoomSelectionMode() { - if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotActionMode() == CartesianPlotActionMode::ApplyActionToAll) { auto plots = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* plot : plots) plot->mouseReleaseZoomSelectionMode(); } else { CartesianPlot* plot = static_cast(QObject::sender()); plot->mouseReleaseZoomSelectionMode(); } } void Worksheet::cartesianPlotMousePressCursorMode(int cursorNumber, QPointF logicPos) { - if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) { auto plots = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* plot : plots) plot->mousePressCursorMode(cursorNumber, logicPos); } else { CartesianPlot* plot = static_cast(QObject::sender()); plot->mousePressCursorMode(cursorNumber, logicPos); } cursorPosChanged(cursorNumber, logicPos.x()); } void Worksheet::cartesianPlotMouseMoveZoomSelectionMode(QPointF logicPos) { - if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotActionMode() == CartesianPlotActionMode::ApplyActionToAll) { QVector plots = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* plot : plots) plot->mouseMoveZoomSelectionMode(logicPos); } else { CartesianPlot* plot = static_cast(QObject::sender()); plot->mouseMoveZoomSelectionMode(logicPos); } } void Worksheet::cartesianPlotMouseHoverZoomSelectionMode(QPointF logicPos) { - if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotActionMode() == CartesianPlotActionMode::ApplyActionToAll) { auto plots = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* plot : plots) plot->mouseHoverZoomSelectionMode(logicPos); } else { CartesianPlot* plot = static_cast(QObject::sender()); plot->mouseHoverZoomSelectionMode(logicPos); } } void Worksheet::cartesianPlotMouseHoverOutsideDataRect() { - if (cartesianPlotActionMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotActionMode() == CartesianPlotActionMode::ApplyActionToAll) { auto plots = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* plot : plots) plot->mouseHoverOutsideDataRect(); } else { CartesianPlot* plot = static_cast(QObject::sender()); plot->mouseHoverOutsideDataRect(); } } void Worksheet::cartesianPlotMouseMoveCursorMode(int cursorNumber, QPointF logicPos) { - if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) { auto plots = children(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* plot : plots) plot->mouseMoveCursorMode(cursorNumber, logicPos); } else { CartesianPlot* plot = static_cast(QObject::sender()); plot->mouseMoveCursorMode(cursorNumber, logicPos); } cursorPosChanged(cursorNumber, logicPos.x()); } /*! * \brief Worksheet::cursorPosChanged * Updates the cursor treemodel with the new data * \param xPos: new position of the cursor * It is assumed, that the plots/curves are in the same order than receiving from * the children() function. It's not checked if the names are the same */ void Worksheet::cursorPosChanged(int cursorNumber, double xPos) { if (d->suppressCursorPosChanged) return; auto* sender = dynamic_cast(QObject::sender()); if (!sender) return; TreeModel* treeModel = cursorModel(); // if ApplyActionToSelection, each plot has it's own x value - if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) { + if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) { // x values int rowPlot = 1; QModelIndex xName = treeModel->index(0, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME)); treeModel->setData(xName, QVariant("X")); double valueCursor[2]; for (int i = 0; i < 2; i++) { // need both cursors to calculate diff valueCursor[i] = sender->cursorPos(i); treeModel->setTreeData(QVariant(valueCursor[i]), 0, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0) + i); } treeModel->setTreeData(QVariant(valueCursor[1] - valueCursor[0]), 0, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF)); // y values for (int i = 0; i < getPlotCount(); i++) { // i=0 is the x Axis auto* plot = getPlot(i); if (!plot || !plot->isVisible()) continue; QModelIndex plotIndex = treeModel->index(rowPlot, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); // curves int rowCurve = 0; for (int j = 0; j < plot->curveCount(); j++) { // assumption: index of signals in model is the same than the index of the signal in the plot bool valueFound; const XYCurve* curve = plot->getCurve(j); if (!curve->isVisible()) continue; double value = curve->y(xPos, valueFound); if (cursorNumber == 0) { treeModel->setTreeData(QVariant(value), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex); double valueCursor1 = treeModel->treeData(rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex).toDouble(); treeModel->setTreeData(QVariant(valueCursor1 - value), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex); } else { treeModel->setTreeData(QVariant(value), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex); double valueCursor0 = treeModel->treeData(rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex).toDouble(); treeModel->setTreeData(QVariant(value - valueCursor0), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex); } rowCurve++; } rowPlot++; } } else { // apply to selection // assumption: plot is visible int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); if (plotIndex.data().toString().compare(sender->name()) != 0) continue; // x values (first row always exist) treeModel->setTreeData(QVariant("X"), 0, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex); double valueCursor[2]; for (int i = 0; i < 2; i++) { // need both cursors to calculate diff valueCursor[i] = sender->cursorPos(i); treeModel->setTreeData(QVariant(valueCursor[i]), 0, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0) + i, plotIndex); } treeModel->setTreeData(QVariant(valueCursor[1]-valueCursor[0]), 0, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex); // y values int rowCurve = 1; // first is x value for (int j = 0; j< sender->curveCount(); j++) { // j=0 are the x values const XYCurve* curve = sender->getCurve(j); // -1 because we start with 1 for the x axis if (!curve->isVisible()) continue; // assumption: index of signals in model is the same than the index of the signal in the plot bool valueFound; double value = curve->y(xPos, valueFound); if (cursorNumber == 0) { treeModel->setTreeData(QVariant(value), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex); double valueCursor1 = treeModel->treeData(rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex).toDouble(); treeModel->setTreeData(QVariant(valueCursor1 - value), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex); } else { treeModel->setTreeData(QVariant(value), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex); double valueCursor0 = treeModel->treeData(rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex).toDouble(); treeModel->setTreeData(QVariant(value - valueCursor0), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex); } rowCurve++; } } } } void Worksheet::cursorModelPlotAdded(const QString& name) { Q_UNUSED(name); // TreeModel* treeModel = cursorModel(); // int rowCount = treeModel->rowCount(); // // add plot at the end // treeModel->insertRows(rowCount, 1); // add empty rows. Then they become filled // treeModel->setTreeData(QVariant(name), rowCount, WorksheetPrivate::TreeModelColumn::PLOTNAME); // rowCount instead of rowCount -1 because first row is the x value updateCompleteCursorTreeModel(); } void Worksheet::cursorModelPlotRemoved(const QString& name) { TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); // first is x Axis for (int i = 1; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); if (plotIndex.data().toString().compare(name) != 0) continue; treeModel->removeRows(plotIndex.row(), 1); return; } } void Worksheet::cartesianPlotMouseModeChangedSlot(CartesianPlot::MouseMode mode) { if (d->updateCompleteCursorModel) { updateCompleteCursorTreeModel(); d->updateCompleteCursorModel = false; } emit cartesianPlotMouseModeChanged(mode); } void Worksheet::curveDataChanged(const XYCurve* curve) { auto* plot = dynamic_cast(QObject::sender()); if (!plot) return; TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; for (int j = 0; j < plot->curveCount(); j++) { if (plot->getCurve(j)->name().compare(curve->name()) != 0) continue; treeModel->setTreeData(QVariant(curve->name()), j, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex); bool valueFound; double valueCursor0 = curve->y(plot->cursorPos(0), valueFound); treeModel->setTreeData(QVariant(valueCursor0), j, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex); double valueCursor1 = curve->y(plot->cursorPos(1), valueFound); treeModel->setTreeData(QVariant(valueCursor1), j, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex); treeModel->setTreeData(QVariant(valueCursor1-valueCursor0), j, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex); break; } break; } } void Worksheet::curveAdded(const XYCurve* curve) { auto* plot = dynamic_cast(QObject::sender()); if (!plot) return; TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); int i = 0; // first row is the x axis when applied to all plots. Starting at the second row - if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) + if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) i = 1; for (; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; int row = 0; for (int j = 0; j < plot->curveCount(); j++) { if (plot->getCurve(j)->name().compare(curve->name()) != 0) { if (plot->getCurve(j)->isVisible()) row ++; continue; } treeModel->insertRow(row, plotIndex); treeModel->setTreeData(QVariant(curve->name()), row, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex); QColor curveColor = curve->linePen().color(); curveColor.setAlpha(50); treeModel->setTreeData(QVariant(curveColor),row, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex, Qt::BackgroundRole); bool valueFound; double valueCursor0 = curve->y(plot->cursorPos(0), valueFound); treeModel->setTreeData(QVariant(valueCursor0), row, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0), plotIndex); double valueCursor1 = curve->y(plot->cursorPos(1), valueFound); treeModel->setTreeData(QVariant(valueCursor1), row, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR1), plotIndex); treeModel->setTreeData(QVariant(valueCursor1-valueCursor0), row, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotIndex); break; } break; } } void Worksheet::curveRemoved(const XYCurve* curve) { auto* plot = dynamic_cast(QObject::sender()); if (!plot) return; TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; int curveCount = treeModel->rowCount(plotIndex); for (int j = 0; j < curveCount; j++) { QModelIndex curveIndex = treeModel->index(j, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex); if (curveIndex.data().toString().compare(curve->name()) != 0) continue; treeModel->removeRow(j, plotIndex); break; } break; } } /*! * Updates the background of the cuves entry in the treeview * @param pen Pen of the curve * @param curveName Curve name to find in treemodel */ void Worksheet::updateCurveBackground(const QPen& pen, const QString& curveName) { const CartesianPlot* plot = static_cast(QObject::sender()); TreeModel* treeModel = cursorModel(); int rowCount = treeModel->rowCount(); for (int i = 0; i < rowCount; i++) { QModelIndex plotIndex = treeModel->index(i, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); if (plotIndex.data().toString().compare(plot->name()) != 0) continue; int curveCount = treeModel->rowCount(plotIndex); for (int j = 0; j < curveCount; j++) { QModelIndex curveIndex = treeModel->index(j, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex); if (curveIndex.data().toString().compare(curveName) != 0) continue; QColor curveColor = pen.color(); curveColor.setAlpha(50); treeModel->setTreeData(QVariant(curveColor), j, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotIndex, Qt::BackgroundRole); return; } return; } } /** * @brief Worksheet::updateCompleteCursorTreeModel * If the plot or the curve are not available, the plot/curve is not in the treemodel! */ void Worksheet::updateCompleteCursorTreeModel() { if (isLoading()) return; TreeModel* treeModel = cursorModel(); if (treeModel->rowCount() > 0) treeModel->removeRows(0, treeModel->rowCount()); // remove all data int plotCount = getPlotCount(); if (plotCount < 1) return; - if (cartesianPlotCursorMode() == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { + if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) { // 1 because of the X data treeModel->insertRows(0, 1); //, treeModel->index(0,0)); // add empty rows. Then they become filled // set X data QModelIndex xName = treeModel->index(0, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME)); treeModel->setData(xName, QVariant("X")); auto* plot0 = getPlot(0); if (plot0) { double valueCursor[2]; for (int i = 0; i < 2; i++) { valueCursor[i] = plot0->cursorPos(i); QModelIndex cursor = treeModel->index(0, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0) + i); treeModel->setData(cursor, QVariant(valueCursor[i])); } QModelIndex diff = treeModel->index(0, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF)); treeModel->setData(diff, QVariant(valueCursor[1]-valueCursor[0])); } } else { //treeModel->insertRows(0, plotCount, treeModel->index(0,0)); // add empty rows. Then they become filled } // set plot name, y value, background for (int i = 0; i < plotCount; i++) { auto* plot = getPlot(i); QModelIndex plotName; int addOne = 0; if (!plot || !plot->isVisible()) continue; // add new entry for the plot treeModel->insertRows(treeModel->rowCount(), 1); //, treeModel->index(0, 0)); // add plot name and X row if needed - if (cartesianPlotCursorMode() == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { + if (cartesianPlotCursorMode() == CartesianPlotActionMode::ApplyActionToAll) { plotName = treeModel->index(i + 1, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); // plus one because first row are the x values treeModel->setData(plotName, QVariant(plot->name())); } else { addOne = 1; plotName = treeModel->index(i, static_cast(WorksheetPrivate::TreeModelColumn::PLOTNAME)); treeModel->setData(plotName, QVariant(plot->name())); treeModel->insertRows(0, 1, plotName); // one, because the first row are the x values QModelIndex xName = treeModel->index(0, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotName); treeModel->setData(xName, QVariant("X")); double valueCursor[2]; for (int i = 0; i < 2; i++) { valueCursor[i] = plot->cursorPos(i); QModelIndex cursor = treeModel->index(0, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0) + i, plotName); treeModel->setData(cursor, QVariant(valueCursor[i])); } QModelIndex diff = treeModel->index(0, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotName); treeModel->setData(diff, QVariant(valueCursor[1]-valueCursor[0])); } int rowCurve = addOne; for (int j = 0; j < plot->curveCount(); j++) { double cursorValue[2] = {NAN, NAN}; const XYCurve* curve = plot->getCurve(j); if (!curve->isVisible()) continue; for (int k = 0; k < 2; k++) { double xPos = plot->cursorPos(k); bool valueFound; cursorValue[k] = curve->y(xPos,valueFound); } treeModel->insertRows(rowCurve, 1, plotName); QColor curveColor = curve->linePen().color(); curveColor.setAlpha(50); treeModel->setTreeData(QVariant(curveColor), rowCurve, 0, plotName, Qt::BackgroundRole); treeModel->setTreeData(QVariant(curve->name()), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::SIGNALNAME), plotName); treeModel->setTreeData(QVariant(cursorValue[0]), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR0), plotName); treeModel->setTreeData(QVariant(cursorValue[1]), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSOR1), plotName); treeModel->setTreeData(QVariant(cursorValue[1] - cursorValue[0]), rowCurve, static_cast(WorksheetPrivate::TreeModelColumn::CURSORDIFF), plotName); rowCurve++; } } } //############################################################################## //###################### Private implementation ############################### //############################################################################## WorksheetPrivate::WorksheetPrivate(Worksheet* owner) : q(owner), m_scene(new QGraphicsScene()) { QStringList headers = {i18n("Curves"), "V1", "V2", "V2-V1"}; cursorData = new TreeModel(headers, nullptr); } QString WorksheetPrivate::name() const { return q->name(); } /*! * called if the worksheet page (the actual size of worksheet's rectangular) was changed. * if a layout is active, it is is updated - this adjusts the sizes of the elements in the layout to the new page size. * if no layout is active and the option "scale content" is active, \c handleResize() is called to adjust zhe properties. */ void WorksheetPrivate::updatePageRect() { if (q->isLoading()) return; QRectF oldRect = m_scene->sceneRect(); m_scene->setSceneRect(pageRect); - if (layout != Worksheet::NoLayout) + if (layout != Worksheet::Layout::NoLayout) updateLayout(); else { if (scaleContent) { qreal horizontalRatio = pageRect.width() / oldRect.width(); qreal verticalRatio = pageRect.height() / oldRect.height(); QVector childElements = q->children(AbstractAspect::ChildIndexFlag::IncludeHidden); if (useViewSize) { //don't make the change of the geometry undoable/redoable if the view size is used. for (auto* elem : childElements) { elem->setUndoAware(false); elem->handleResize(horizontalRatio, verticalRatio, true); elem->setUndoAware(true); } } else { // for (auto* child : childElements) // child->handleResize(horizontalRatio, verticalRatio, true); } } } } void WorksheetPrivate::update() { q->update(); } WorksheetPrivate::~WorksheetPrivate() { delete m_scene; } void WorksheetPrivate::updateLayout(bool undoable) { if (suppressLayoutUpdate) return; QVector list = q->children(); - if (layout == Worksheet::NoLayout) { + if (layout == Worksheet::Layout::NoLayout) { for (auto* elem : list) elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); return; } float x = layoutLeftMargin; float y = layoutTopMargin; float w, h; int count = list.count(); - if (layout == Worksheet::VerticalLayout) { + if (layout == Worksheet::Layout::VerticalLayout) { w = m_scene->sceneRect().width() - layoutLeftMargin - layoutRightMargin; h = (m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (count-1)*layoutVerticalSpacing)/count; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); y += h + layoutVerticalSpacing; } - } else if (layout == Worksheet::HorizontalLayout) { + } else if (layout == Worksheet::Layout::HorizontalLayout) { w = (m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (count-1)*layoutHorizontalSpacing)/count; h = m_scene->sceneRect().height() - layoutTopMargin-layoutBottomMargin; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x += w + layoutHorizontalSpacing; } } else { //GridLayout //add new rows, if not sufficient if (count > layoutRowCount*layoutColumnCount) { layoutRowCount = floor( (float)count/layoutColumnCount + 0.5); emit q->layoutRowCountChanged(layoutRowCount); } w = (m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (layoutColumnCount-1)*layoutHorizontalSpacing)/layoutColumnCount; h = (m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (layoutRowCount-1)*layoutVerticalSpacing)/layoutRowCount; int columnIndex = 0; //counts the columns in a row for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x += w + layoutHorizontalSpacing; columnIndex++; if (columnIndex == layoutColumnCount) { columnIndex = 0; x = layoutLeftMargin; y += h + layoutVerticalSpacing; } } } } void WorksheetPrivate::setContainerRect(WorksheetElementContainer* elem, float x, float y, float h, float w, bool undoable) { if (useViewSize) { //when using the view size, no need to put rect changes onto the undo-stack elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else { //don't put rect changed onto the undo-stack if undoable-flag is set to true, //e.g. when new child is added or removed (the layout and the childrend rects will be updated anyway) if (!undoable) { elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else elem->setRect(QRectF(x,y,w,h)); } elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Worksheet::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "worksheet" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //geometry writer->writeStartElement( "geometry" ); QRectF rect = d->m_scene->sceneRect(); writer->writeAttribute( "x", QString::number(rect.x()) ); writer->writeAttribute( "y", QString::number(rect.y()) ); writer->writeAttribute( "width", QString::number(rect.width()) ); writer->writeAttribute( "height", QString::number(rect.height()) ); writer->writeAttribute( "useViewSize", QString::number(d->useViewSize) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); - writer->writeAttribute( "layout", QString::number(d->layout) ); + writer->writeAttribute( "layout", QString::number(static_cast(d->layout)) ); writer->writeAttribute( "topMargin", QString::number(d->layoutTopMargin) ); writer->writeAttribute( "bottomMargin", QString::number(d->layoutBottomMargin) ); writer->writeAttribute( "leftMargin", QString::number(d->layoutLeftMargin) ); writer->writeAttribute( "rightMargin", QString::number(d->layoutRightMargin) ); writer->writeAttribute( "verticalSpacing", QString::number(d->layoutVerticalSpacing) ); writer->writeAttribute( "horizontalSpacing", QString::number(d->layoutHorizontalSpacing) ); writer->writeAttribute( "columnCount", QString::number(d->layoutColumnCount) ); writer->writeAttribute( "rowCount", QString::number(d->layoutRowCount) ); writer->writeEndElement(); //background properties writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); // cartesian properties writer->writeStartElement( "plotProperties" ); writer->writeAttribute( "plotsLocked", QString::number(d->plotsLocked) ); - writer->writeAttribute( "cartesianPlotActionMode", QString::number(d->cartesianPlotActionMode)); - writer->writeAttribute( "cartesianPlotCursorMode", QString::number(d->cartesianPlotCursorMode)); + writer->writeAttribute( "cartesianPlotActionMode", QString::number(static_cast(d->cartesianPlotActionMode))); + writer->writeAttribute( "cartesianPlotCursorMode", QString::number(static_cast(d->cartesianPlotCursorMode))); writer->writeEndElement(); //serialize all children for (auto* child : children(ChildIndexFlag::IncludeHidden)) child->save(writer); writer->writeEndElement(); // close "worksheet" section } //! Load from XML bool Worksheet::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; //clear the theme that was potentially set in init() in order to correctly load here the worksheets without any theme used d->theme.clear(); KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; QRectF rect; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "worksheet") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else rect.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else rect.setY(str.toDouble()); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else rect.setWidth(str.toDouble()); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else rect.setHeight(str.toDouble()); READ_INT_VALUE("useViewSize", useViewSize, int); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); READ_INT_VALUE("layout", layout, Worksheet::Layout); READ_DOUBLE_VALUE("topMargin", layoutTopMargin); READ_DOUBLE_VALUE("bottomMargin", layoutBottomMargin); READ_DOUBLE_VALUE("leftMargin", layoutLeftMargin); READ_DOUBLE_VALUE("rightMargin", layoutRightMargin); READ_DOUBLE_VALUE("verticalSpacing", layoutVerticalSpacing); READ_DOUBLE_VALUE("horizontalSpacing", layoutHorizontalSpacing); READ_INT_VALUE("columnCount", layoutColumnCount, int); READ_INT_VALUE("rowCount", layoutRowCount, int); } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); READ_INT_VALUE("type", backgroundType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", backgroundColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", backgroundImageStyle, PlotArea::BackgroundImageStyle); READ_INT_VALUE("brushStyle", backgroundBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; READ_DOUBLE_VALUE("opacity", backgroundOpacity); } else if(!preview && reader->name() == "plotProperties") { attribs = reader->attributes(); READ_INT_VALUE("plotsLocked", plotsLocked, bool); READ_INT_VALUE("cartesianPlotActionMode", cartesianPlotActionMode, Worksheet::CartesianPlotActionMode); READ_INT_VALUE("cartesianPlotCursorMode", cartesianPlotCursorMode, Worksheet::CartesianPlotActionMode); } else if (reader->name() == "cartesianPlot") { CartesianPlot* plot = new CartesianPlot(QString()); plot->setIsLoading(true); if (!plot->load(reader, preview)) { delete plot; return false; } else addChildFast(plot); } else if (reader->name() == "textLabel") { TextLabel* label = new TextLabel(QString()); if (!label->load(reader, preview)) { delete label; return false; } else addChildFast(label); } else if (reader->name() == "image") { Image* image = new Image(QString()); if (!image->load(reader, preview)) { delete image; return false; } else addChildFast(image); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (!preview) { d->m_scene->setSceneRect(rect); d->updateLayout(); updateCompleteCursorTreeModel(); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Worksheet::loadTheme(const QString& theme) { KConfigGroup group; KConfig* config = nullptr; if (!theme.isEmpty()) { //load values from the theme config config = new KConfig(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); //apply the same background color for Worksheet as for the CartesianPlot group = config->group("CartesianPlot"); //load the theme for all the children const auto& children = this->children(ChildIndexFlag::IncludeHidden); for (auto* child : children) child->loadThemeConfig(*config); } else { //load default values config = new KConfig(); group = config->group("Worksheet"); } this->setBackgroundType((PlotArea::BackgroundType) group.readEntry("BackgroundType", (int) PlotArea::Color)); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor)); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled)); this->setBackgroundBrushStyle((Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern)); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor", QColor(Qt::white))); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor", QColor(Qt::black))); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", 1.0)); //load the theme for all the children const auto& children = this->children(ChildIndexFlag::IncludeHidden); for (auto* child : children) child->loadThemeConfig(*config); delete config; } diff --git a/src/backend/worksheet/Worksheet.h b/src/backend/worksheet/Worksheet.h index 4f93953ea..00d33a151 100644 --- a/src/backend/worksheet/Worksheet.h +++ b/src/backend/worksheet/Worksheet.h @@ -1,195 +1,195 @@ /*************************************************************************** File : Worksheet.h Project : LabPlot Description : Worksheet (2D visualization) part -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2011-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 * * * ***************************************************************************/ #ifndef WORKSHEET_H #define WORKSHEET_H #include "backend/core/AbstractPart.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" class QGraphicsItem; class QGraphicsScene; class QRectF; class WorksheetPrivate; class WorksheetView; class TreeModel; class XYCurve; class CartesianPlot; class Worksheet : public AbstractPart { Q_OBJECT public: explicit Worksheet(const QString& name, bool loading = false); ~Worksheet() override; - enum Unit {Millimeter, Centimeter, Inch, Point}; - enum Layout {NoLayout, VerticalLayout, HorizontalLayout, GridLayout}; - enum CartesianPlotActionMode {ApplyActionToSelection, ApplyActionToAll}; + enum class Unit {Millimeter, Centimeter, Inch, Point}; + enum class Layout {NoLayout, VerticalLayout, HorizontalLayout, GridLayout}; + enum class CartesianPlotActionMode {ApplyActionToSelection, ApplyActionToAll}; static float convertToSceneUnits(const float value, const Worksheet::Unit unit); static float convertFromSceneUnits(const float value, const Worksheet::Unit unit); QIcon icon() const override; QMenu* createContextMenu() override; QWidget* view() const override; QVector dependsOn() const override; bool exportView() const override; bool printView() override; bool printPreview() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; QRectF pageRect() const; void setPageRect(const QRectF&); QGraphicsScene* scene() const; void update(); void setPrinting(bool) const; void setThemeName(const QString&); void setItemSelectedInView(const QGraphicsItem*, const bool); void setSelectedInView(const bool); void deleteAspectFromGraphicsItem(const QGraphicsItem*); void setIsClosing(); CartesianPlotActionMode cartesianPlotActionMode(); void setCartesianPlotActionMode(CartesianPlotActionMode mode); CartesianPlotActionMode cartesianPlotCursorMode(); void setCartesianPlotCursorMode(CartesianPlotActionMode mode); void setPlotsLocked(bool); bool plotsLocked(); int getPlotCount(); CartesianPlot* getPlot(int index); TreeModel* cursorModel(); void cursorModelPlotAdded(const QString& name); void cursorModelPlotRemoved(const QString& name); BASIC_D_ACCESSOR_DECL(float, backgroundOpacity, BackgroundOpacity) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundType, backgroundType, BackgroundType) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundColorStyle, backgroundColorStyle, BackgroundColorStyle) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundImageStyle, backgroundImageStyle, BackgroundImageStyle) BASIC_D_ACCESSOR_DECL(Qt::BrushStyle, backgroundBrushStyle, BackgroundBrushStyle) CLASS_D_ACCESSOR_DECL(QColor, backgroundFirstColor, BackgroundFirstColor) CLASS_D_ACCESSOR_DECL(QColor, backgroundSecondColor, BackgroundSecondColor) CLASS_D_ACCESSOR_DECL(QString, backgroundFileName, BackgroundFileName) BASIC_D_ACCESSOR_DECL(bool, scaleContent, ScaleContent) BASIC_D_ACCESSOR_DECL(bool, useViewSize, UseViewSize) BASIC_D_ACCESSOR_DECL(Worksheet::Layout, layout, Layout) BASIC_D_ACCESSOR_DECL(float, layoutTopMargin, LayoutTopMargin) BASIC_D_ACCESSOR_DECL(float, layoutBottomMargin, LayoutBottomMargin) BASIC_D_ACCESSOR_DECL(float, layoutLeftMargin, LayoutLeftMargin) BASIC_D_ACCESSOR_DECL(float, layoutRightMargin, LayoutRightMargin) BASIC_D_ACCESSOR_DECL(float, layoutHorizontalSpacing, LayoutHorizontalSpacing) BASIC_D_ACCESSOR_DECL(float, layoutVerticalSpacing, LayoutVerticalSpacing) BASIC_D_ACCESSOR_DECL(int, layoutRowCount, LayoutRowCount) BASIC_D_ACCESSOR_DECL(int, layoutColumnCount, LayoutColumnCount) QString theme() const; void setSuppressLayoutUpdate(bool); void updateLayout(); void registerShortcuts() override; void unregisterShortcuts() override; typedef WorksheetPrivate Private; public slots: void setTheme(const QString&); void cartesianPlotMousePressZoomSelectionMode(QPointF logicPos); void cartesianPlotMousePressCursorMode(int cursorNumber, QPointF logicPos); void cartesianPlotMouseMoveZoomSelectionMode(QPointF logicPos); void cartesianPlotMouseMoveCursorMode(int cursorNumber, QPointF logicPos); void cartesianPlotMouseReleaseZoomSelectionMode(); void cartesianPlotMouseHoverZoomSelectionMode(QPointF logicPos); void cartesianPlotMouseHoverOutsideDataRect(); void cartesianPlotMouseModeChangedSlot(CartesianPlot::MouseMode); // slots needed by the cursor void updateCurveBackground(const QPen&, const QString& curveName); void updateCompleteCursorTreeModel(); void cursorPosChanged(int cursorNumber, double xPos); void curveAdded(const XYCurve* curve); void curveRemoved(const XYCurve* curve); void curveDataChanged(const XYCurve* curve); private: void init(); WorksheetElement* aspectFromGraphicsItem(const WorksheetElement*, const QGraphicsItem*) const; void loadTheme(const QString&); WorksheetPrivate* const d; mutable WorksheetView* m_view{nullptr}; friend class WorksheetPrivate; private slots: void handleAspectAdded(const AbstractAspect*); void handleAspectAboutToBeRemoved(const AbstractAspect*); void handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child); void childSelected(const AbstractAspect*) override; void childDeselected(const AbstractAspect*) override; signals: void requestProjectContextMenu(QMenu*); void itemSelected(QGraphicsItem*); void itemDeselected(QGraphicsItem*); void requestUpdate(); void useViewSizeRequested(); void cartesianPlotMouseModeChanged(CartesianPlot::MouseMode); void backgroundTypeChanged(PlotArea::BackgroundType); void backgroundColorStyleChanged(PlotArea::BackgroundColorStyle); void backgroundImageStyleChanged(PlotArea::BackgroundImageStyle); void backgroundBrushStyleChanged(Qt::BrushStyle); void backgroundFirstColorChanged(const QColor&); void backgroundSecondColorChanged(const QColor&); void backgroundFileNameChanged(const QString&); void backgroundOpacityChanged(float); void scaleContentChanged(bool); void pageRectChanged(const QRectF&); void layoutChanged(Worksheet::Layout); void layoutTopMarginChanged(float); void layoutBottomMarginChanged(float); void layoutLeftMarginChanged(float); void layoutRightMarginChanged(float); void layoutVerticalSpacingChanged(float); void layoutHorizontalSpacingChanged(float); void layoutRowCountChanged(int); void layoutColumnCountChanged(int); void themeChanged(const QString&); void showCursorDock(TreeModel*, QVector); }; #endif diff --git a/src/backend/worksheet/WorksheetElement.cpp b/src/backend/worksheet/WorksheetElement.cpp index 75aa5d547..93d2905a2 100644 --- a/src/backend/worksheet/WorksheetElement.cpp +++ b/src/backend/worksheet/WorksheetElement.cpp @@ -1,253 +1,253 @@ /*************************************************************************** File : WorksheetElement.cpp Project : LabPlot Description : Base class for all Worksheet children. -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2017 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 "backend/worksheet/Worksheet.h" #include "backend/worksheet/WorksheetElement.h" #include "backend/worksheet/plots/AbstractPlot.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include #include #include #include /** * \class WorksheetElement * \brief Base class for all Worksheet children. * */ WorksheetElement::WorksheetElement(const QString &name, AspectType type) : AbstractAspect(name, type) { m_drawingOrderMenu = new QMenu(i18n("Drawing &order")); m_drawingOrderMenu->setIcon(QIcon::fromTheme("layer-bottom")); m_moveBehindMenu = new QMenu(i18n("Move &behind")); m_moveBehindMenu->setIcon(QIcon::fromTheme("draw-arrow-down")); m_moveInFrontOfMenu = new QMenu(i18n("Move in &front of")); m_moveInFrontOfMenu->setIcon(QIcon::fromTheme("draw-arrow-up")); m_drawingOrderMenu->addMenu(m_moveBehindMenu); m_drawingOrderMenu->addMenu(m_moveInFrontOfMenu); connect(m_moveBehindMenu, &QMenu::aboutToShow, this, &WorksheetElement::prepareMoveBehindMenu); connect(m_moveInFrontOfMenu, &QMenu::aboutToShow, this, &WorksheetElement::prepareMoveInFrontOfMenu); connect(m_moveBehindMenu, &QMenu::triggered, this, &WorksheetElement::execMoveBehind); connect(m_moveInFrontOfMenu, &QMenu::triggered, this, &WorksheetElement::execMoveInFrontOf); } WorksheetElement::~WorksheetElement() { delete m_moveBehindMenu; delete m_moveInFrontOfMenu; delete m_drawingOrderMenu; } /** * \fn QGraphicsItem *WorksheetElement::graphicsItem() const * \brief Return the graphics item representing this element. * */ /** * \fn void WorksheetElement::setVisible(bool on) * \brief Show/hide the element. * */ /** * \fn bool WorksheetElement::isVisible() const * \brief Return whether the element is (at least) partially visible. * */ /** * \brief Return whether the element is fully visible (i.e., including all child elements). * * The standard implementation returns isVisible(). */ bool WorksheetElement::isFullyVisible() const { return isVisible(); } /** * \fn void WorksheetElement::setPrinting(bool on) * \brief Switches the printing mode on/off * */ /** * \fn void WorksheetElement::retransform() * \brief Tell the element to newly transform its graphics item into its coordinate system. * * This method must not change the undo-aware data of the element, only * the graphics item which represents the item is to be updated. */ void WorksheetElement::setZValue(qreal value) { graphicsItem()->setZValue(value); } /** This does exactly what Qt internally does to creates a shape from a painter path. */ QPainterPath WorksheetElement::shapeFromPath(const QPainterPath &path, const QPen &pen) { if (path == QPainterPath()) return path; // PERFTRACE("WorksheetElement::shapeFromPath()"); // TODO: We unfortunately need this hack as QPainterPathStroker will set a width of 1.0 // if we pass a value of 0.0 to QPainterPathStroker::setWidth() const qreal penWidthZero = qreal(0.00000001); QPainterPathStroker ps; ps.setCapStyle(pen.capStyle()); if (pen.widthF() <= 0.0) ps.setWidth(penWidthZero); else ps.setWidth(pen.widthF()); ps.setJoinStyle(pen.joinStyle()); ps.setMiterLimit(pen.miterLimit()); QPainterPath p = ps.createStroke(path); p.addPath(path); return p; } QMenu* WorksheetElement::createContextMenu() { QMenu* menu = AbstractAspect::createContextMenu(); //add the sub-menu for the drawing order //don't add the drawing order menu for axes, they're always drawn on top of each other elements if (dynamic_cast(this)) return menu; //for plots in a worksheet with an active layout the Z-factor is not relevant but we still //want to use the "Drawing order" menu to be able to change the position/order of the plot in the layout. //Since the order of the child in the list of children is opposite to the Z-factor, we change //the names of the menus to adapt to the reversed logic. if (dynamic_cast(this) ) { const Worksheet* w = dynamic_cast(this->parentAspect()); if (!w) return menu; - if (w->layout() != Worksheet::NoLayout) { + if (w->layout() != Worksheet::Layout::NoLayout) { m_moveBehindMenu->setTitle(i18n("Move in &front of")); m_moveBehindMenu->setIcon(QIcon::fromTheme("draw-arrow-up")); m_moveInFrontOfMenu->setTitle(i18n("Move &behind")); m_moveInFrontOfMenu->setIcon(QIcon::fromTheme("draw-arrow-down")); } else { m_moveBehindMenu->setTitle(i18n("Move &behind")); m_moveBehindMenu->setIcon(QIcon::fromTheme("draw-arrow-down")); m_moveInFrontOfMenu->setTitle(i18n("Move in &front of")); m_moveInFrontOfMenu->setIcon(QIcon::fromTheme("draw-arrow-up")); } } //don't add the drawing order menu if the parent element has no other children int children = 0; for (auto* child : parentAspect()->children()) { if ( !dynamic_cast(child) ) children++; } if (children > 1) { menu->addSeparator(); menu->addMenu(m_drawingOrderMenu); } return menu; } void WorksheetElement::prepareMoveBehindMenu() { m_moveBehindMenu->clear(); AbstractAspect* parent = parentAspect(); int index = parent->indexOfChild(this); const QVector& children = parent->children(); for (int i = 0; i < index; ++i) { const WorksheetElement* elem = children.at(i); //axes are always drawn on top of other elements, don't add them to the menu if (!dynamic_cast(elem)) { QAction* action = m_moveBehindMenu->addAction(elem->icon(), elem->name()); action->setData(i); } } //TODO: doesn't always work properly //hide the "move behind" menu if it doesn't have any entries, show if not shown yet otherwise //m_moveBehindMenu->menuAction()->setVisible(!m_moveBehindMenu->isEmpty()); } void WorksheetElement::prepareMoveInFrontOfMenu() { m_moveInFrontOfMenu->clear(); AbstractAspect* parent = parentAspect(); int index = parent->indexOfChild(this); const QVector& children = parent->children(); for (int i = index + 1; i < children.size(); ++i) { const WorksheetElement* elem = children.at(i); //axes are always drawn on top of other elements, don't add them to the menu if (!dynamic_cast(elem)) { QAction* action = m_moveInFrontOfMenu->addAction(elem->icon(), elem->name()); action->setData(i); } } //TODO: doesn't alway work properly //hide the "move in front" menu if it doesn't have any entries, show if not shown yet otherwise //m_moveInFrontOfMenu->menuAction()->setVisible(!m_moveInFrontOfMenu->isEmpty()); } void WorksheetElement::execMoveInFrontOf(QAction* action) { AbstractAspect* parent = parentAspect(); int index = action->data().toInt(); AbstractAspect* sibling1 = parent->child(index); AbstractAspect* sibling2 = parent->child(index + 1); beginMacro(i18n("%1: move behind %2.", name(), sibling1->name())); remove(); parent->insertChildBefore(this, sibling2); endMacro(); } void WorksheetElement::execMoveBehind(QAction* action) { AbstractAspect* parent = parentAspect(); int index = action->data().toInt(); AbstractAspect* sibling = parent->child(index); beginMacro(i18n("%1: move in front of %2.", name(), sibling->name())); remove(); parent->insertChildBefore(this, sibling); endMacro(); } void WorksheetElement::loadThemeConfig(const KConfig &) { } void WorksheetElement::saveThemeConfig(const KConfig &) { } diff --git a/src/backend/worksheet/WorksheetElement.h b/src/backend/worksheet/WorksheetElement.h index 83fb7dbe6..1d6b23d8a 100644 --- a/src/backend/worksheet/WorksheetElement.h +++ b/src/backend/worksheet/WorksheetElement.h @@ -1,107 +1,107 @@ /*************************************************************************** File : WorksheetElement.h Project : LabPlot Description : Base class for all Worksheet children. -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2015 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 * * * ***************************************************************************/ #ifndef WORKSHEETELEMENT_H #define WORKSHEETELEMENT_H #include "backend/core/AbstractAspect.h" #include class QAction; class QGraphicsItem; class QPen; class KConfig; class WorksheetElement : public AbstractAspect { Q_OBJECT public: WorksheetElement(const QString&, AspectType); ~WorksheetElement() override; - enum WorksheetElementName {NameCartesianPlot = 1}; + enum class WorksheetElementName {NameCartesianPlot = 1}; - enum HorizontalPosition {hPositionLeft, hPositionCenter, hPositionRight, hPositionCustom}; - enum VerticalPosition {vPositionTop, vPositionCenter, vPositionBottom, vPositionCustom}; + enum class HorizontalPosition {Left, Center, Right, Custom}; + enum class VerticalPosition {Top, Center, Bottom, Custom}; - enum HorizontalAlignment {hAlignLeft, hAlignCenter, hAlignRight}; - enum VerticalAlignment {vAlignTop, vAlignCenter, vAlignBottom}; + enum class HorizontalAlignment {Left, Center, Right}; + enum class VerticalAlignment {Top, Center, Bottom}; struct PositionWrapper { QPointF point; WorksheetElement::HorizontalPosition horizontalPosition; WorksheetElement::VerticalPosition verticalPosition; }; virtual QGraphicsItem* graphicsItem() const = 0; virtual void setZValue(qreal); virtual void setVisible(bool on) = 0; virtual bool isVisible() const = 0; virtual bool isFullyVisible() const; virtual void setPrinting(bool) = 0; QMenu* createContextMenu() override; virtual void loadThemeConfig(const KConfig&); virtual void saveThemeConfig(const KConfig&); static QPainterPath shapeFromPath(const QPainterPath&, const QPen&); virtual void handleResize(double horizontalRatio, double verticalRatio, bool pageResize = false) = 0; public slots: virtual void retransform() = 0; private: QMenu* m_drawingOrderMenu; QMenu* m_moveBehindMenu; QMenu* m_moveInFrontOfMenu; private slots: void prepareMoveBehindMenu(); void prepareMoveInFrontOfMenu(); void execMoveBehind(QAction*); void execMoveInFrontOf(QAction*); signals: friend class AbstractPlotSetHorizontalPaddingCmd; friend class AbstractPlotSetVerticalPaddingCmd; friend class AbstractPlotSetRightPaddingCmd; friend class AbstractPlotSetBottomPaddingCmd; friend class AbstractPlotSetSymmetricPaddingCmd; void horizontalPaddingChanged(float); void verticalPaddingChanged(float); void rightPaddingChanged(double); void bottomPaddingChanged(double); void symmetricPaddingChanged(double); void hovered(); void unhovered(); }; #endif diff --git a/src/backend/worksheet/WorksheetPrivate.h b/src/backend/worksheet/WorksheetPrivate.h index 2e47a63bf..30e509210 100644 --- a/src/backend/worksheet/WorksheetPrivate.h +++ b/src/backend/worksheet/WorksheetPrivate.h @@ -1,96 +1,96 @@ /*************************************************************************** File : WorksheetPrivate.h Project : LabPlot Description : Private members of Worksheet. -------------------------------------------------------------------- Copyright : (C) 2012 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 * * * ***************************************************************************/ #ifndef WORKSHEETPRIVATE_H #define WORKSHEETPRIVATE_H #include #include class QBrush; class Worksheet; class WorksheetElementContainer; class QGraphicsScene; class TreeModel; class WorksheetPrivate { public: explicit WorksheetPrivate(Worksheet*); virtual ~WorksheetPrivate(); Worksheet* const q; QRectF pageRect; QGraphicsScene* m_scene; bool useViewSize{false}; bool scaleContent{false}; QString name() const; void update(); void updateLayout(bool undoable = true); void setContainerRect(WorksheetElementContainer*, float x, float y, float h, float w, bool undoable); void updatePageRect(); PlotArea::BackgroundType backgroundType; PlotArea::BackgroundColorStyle backgroundColorStyle; PlotArea::BackgroundImageStyle backgroundImageStyle; Qt::BrushStyle backgroundBrushStyle; QColor backgroundFirstColor; QColor backgroundSecondColor; QString backgroundFileName; float backgroundOpacity{1.0}; - Worksheet::Layout layout{Worksheet::VerticalLayout}; + Worksheet::Layout layout{Worksheet::Layout::VerticalLayout}; bool suppressLayoutUpdate{false}; bool suppressCursorPosChanged{false}; float layoutTopMargin{0.0}; float layoutBottomMargin{0.0}; float layoutLeftMargin{0.0}; float layoutRightMargin{0.0}; float layoutVerticalSpacing{0.0}; float layoutHorizontalSpacing{0.0}; int layoutColumnCount{2}; int layoutRowCount{2}; QString theme; bool plotsLocked{false}; bool updateCompleteCursorModel{true}; Worksheet::CartesianPlotActionMode cartesianPlotActionMode{Worksheet::CartesianPlotActionMode::ApplyActionToSelection}; Worksheet::CartesianPlotActionMode cartesianPlotCursorMode{Worksheet::CartesianPlotActionMode::ApplyActionToAll}; enum class TreeModelColumn { PLOTNAME = 0, SIGNALNAME = 0, CURSOR0, CURSOR1, CURSORDIFF }; TreeModel* cursorData{nullptr}; }; #endif diff --git a/src/backend/worksheet/plots/PlotArea.cpp b/src/backend/worksheet/plots/PlotArea.cpp index 9af905a5d..9e51780f0 100644 --- a/src/backend/worksheet/plots/PlotArea.cpp +++ b/src/backend/worksheet/plots/PlotArea.cpp @@ -1,623 +1,623 @@ /*************************************************************************** File : PlotArea.cpp Project : LabPlot Description : Plot area (for background filling and clipping). -------------------------------------------------------------------- Copyright : (C) 2011-2020 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 "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/PlotAreaPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include #include #include #include #include /** * \class PlotArea * \brief Plot area (for background filling and clipping). * * \ingroup worksheet */ PlotArea::PlotArea(const QString &name, CartesianPlot* parent) : WorksheetElement(name, AspectType::PlotArea), d_ptr(new PlotAreaPrivate(this)), m_parent(parent) { init(); } PlotArea::PlotArea(const QString &name, CartesianPlot* parent, PlotAreaPrivate *dd) : WorksheetElement(name, AspectType::PlotArea), d_ptr(dd), m_parent(parent) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene PlotArea::~PlotArea() = default; void PlotArea::init() { Q_D(PlotArea); setHidden(true);//we don't show PlotArea aspect in the model view. d->rect = QRectF(0, 0, 1, 1); d->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); KConfig config; KConfigGroup group = config.group("PlotArea"); //Background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int)PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //Border d->borderPen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), - group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)), + group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)), (Qt::PenStyle) group.readEntry("BorderStyle", (int)Qt::SolidLine)); d->borderCornerRadius = group.readEntry("BorderCornerRadius", 0.0); d->borderOpacity = group.readEntry("BorderOpacity", 1.0); } QGraphicsItem *PlotArea::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(PlotArea, SetVisible, bool, swapVisible) void PlotArea::setVisible(bool on) { Q_D(PlotArea); exec(new PlotAreaSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool PlotArea::isVisible() const { Q_D(const PlotArea); return d->isVisible(); } bool PlotArea::isHovered() const { return m_parent->isHovered(); } bool PlotArea::isPrinted() const { return m_parent->isPrinted(); } bool PlotArea::isSelected() const { return m_parent->isSelected(); } void PlotArea::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("PlotArea::handleResize()"); Q_D(PlotArea); Q_UNUSED(pageResize); d->rect.setWidth(d->rect.width()*horizontalRatio); d->rect.setHeight(d->rect.height()*verticalRatio); // TODO: scale line width } void PlotArea::retransform() { } /* ============================ getter methods ================= */ BASIC_SHARED_D_READER_IMPL(PlotArea, bool, clippingEnabled, clippingEnabled()) CLASS_SHARED_D_READER_IMPL(PlotArea, QRectF, rect, rect) BASIC_SHARED_D_READER_IMPL(PlotArea, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_SHARED_D_READER_IMPL(PlotArea, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_SHARED_D_READER_IMPL(PlotArea, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) CLASS_SHARED_D_READER_IMPL(PlotArea, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_SHARED_D_READER_IMPL(PlotArea, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_SHARED_D_READER_IMPL(PlotArea, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_SHARED_D_READER_IMPL(PlotArea, QString, backgroundFileName, backgroundFileName) BASIC_SHARED_D_READER_IMPL(PlotArea, qreal, backgroundOpacity, backgroundOpacity) CLASS_SHARED_D_READER_IMPL(PlotArea, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(PlotArea, qreal, borderCornerRadius, borderCornerRadius) BASIC_SHARED_D_READER_IMPL(PlotArea, qreal, borderOpacity, borderOpacity) /* ============================ setter methods and undo commands ================= */ STD_SWAP_METHOD_SETTER_CMD_IMPL(PlotArea, SetClippingEnabled, bool, toggleClipping); void PlotArea::setClippingEnabled(bool on) { Q_D(PlotArea); if (d->clippingEnabled() != on) exec(new PlotAreaSetClippingEnabledCmd(d, on, ki18n("%1: toggle clipping"))); } /*! * sets plot area rect in scene coordinates. */ void PlotArea::setRect(const QRectF &newRect) { Q_D(PlotArea); d->setRect(newRect); } //Background STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void PlotArea::setBackgroundType(BackgroundType type) { Q_D(PlotArea); if (type != d->backgroundType) exec(new PlotAreaSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void PlotArea::setBackgroundColorStyle(BackgroundColorStyle style) { Q_D(PlotArea); if (style != d->backgroundColorStyle) exec(new PlotAreaSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void PlotArea::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(PlotArea); if (style != d->backgroundImageStyle) exec(new PlotAreaSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void PlotArea::setBackgroundBrushStyle(Qt::BrushStyle style) { Q_D(PlotArea); if (style != d->backgroundBrushStyle) exec(new PlotAreaSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void PlotArea::setBackgroundFirstColor(const QColor &color) { Q_D(PlotArea); if (color != d->backgroundFirstColor) exec(new PlotAreaSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void PlotArea::setBackgroundSecondColor(const QColor &color) { Q_D(PlotArea); if (color != d->backgroundSecondColor) exec(new PlotAreaSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundFileName, QString, backgroundFileName, update) void PlotArea::setBackgroundFileName(const QString& fileName) { Q_D(PlotArea); if (fileName != d->backgroundFileName) exec(new PlotAreaSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundOpacity, qreal, backgroundOpacity, update) void PlotArea::setBackgroundOpacity(qreal opacity) { Q_D(PlotArea); if (opacity != d->backgroundOpacity) exec(new PlotAreaSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set plot area opacity"))); } //Border STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderPen, QPen, borderPen, update) void PlotArea::setBorderPen(const QPen &pen) { Q_D(PlotArea); if (pen != d->borderPen) exec(new PlotAreaSetBorderPenCmd(d, pen, ki18n("%1: set plot area border"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderCornerRadius, qreal, borderCornerRadius, update) void PlotArea::setBorderCornerRadius(qreal radius) { Q_D(PlotArea); if (radius != d->borderCornerRadius) exec(new PlotAreaSetBorderCornerRadiusCmd(d, radius, ki18n("%1: set plot area corner radius"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderOpacity, qreal, borderOpacity, update) void PlotArea::setBorderOpacity(qreal opacity) { Q_D(PlotArea); if (opacity != d->borderOpacity) exec(new PlotAreaSetBorderOpacityCmd(d, opacity, ki18n("%1: set plot area border opacity"))); } //##################################################################### //################### Private implementation ########################## //##################################################################### PlotAreaPrivate::PlotAreaPrivate(PlotArea *owner) : q(owner) { } QString PlotAreaPrivate::name() const { return q->name(); } bool PlotAreaPrivate::clippingEnabled() const { return (flags() & QGraphicsItem::ItemClipsChildrenToShape); } bool PlotAreaPrivate::toggleClipping(bool on) { bool oldValue = clippingEnabled(); setFlag(QGraphicsItem::ItemClipsChildrenToShape, on); return oldValue; } bool PlotAreaPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); return oldValue; } void PlotAreaPrivate::setRect(const QRectF& r) { prepareGeometryChange(); rect = mapRectFromScene(r); } QRectF PlotAreaPrivate::boundingRect () const { float width = rect.width(); float height = rect.height(); float penWidth = borderPen.width(); return QRectF{-width/2 - penWidth/2, -height/2 - penWidth/2, width + penWidth, height + penWidth}; } QPainterPath PlotAreaPrivate::shape() const { QPainterPath path; if (qFuzzyIsNull(borderCornerRadius)) path.addRect(rect); else path.addRoundedRect(rect, borderCornerRadius, borderCornerRadius); return path; } void PlotAreaPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget *widget) { // DEBUG("PlotAreaPrivate::paint()"); Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; //draw the area painter->setOpacity(backgroundOpacity); painter->setPen(Qt::NoPen); if (backgroundType == PlotArea::Color) { switch (backgroundColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(backgroundFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, backgroundFirstColor); radialGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (backgroundType == PlotArea::Image) { if ( !backgroundFileName.trimmed().isEmpty() ) { QPixmap pix(backgroundFileName); switch (backgroundImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::Centered: painter->drawPixmap(QPointF(rect.center().x()-pix.size().width()/2,rect.center().y()-pix.size().height()/2),pix); break; case PlotArea::Tiled: painter->setBrush(QBrush(pix)); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } } } else if (backgroundType == PlotArea::Pattern) { painter->setBrush(QBrush(backgroundFirstColor,backgroundBrushStyle)); } if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); //draw the border if (borderPen.style() != Qt::NoPen && !q->isHovered() && !isSelected()) { painter->setPen(borderPen); painter->setBrush(Qt::NoBrush); painter->setOpacity(borderOpacity); if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } double penWidth = 6.; QRectF rect = boundingRect(); rect = QRectF(-rect.width()/2 + penWidth / 2, -rect.height()/2 + penWidth / 2, rect.width() - penWidth, rect.height() - penWidth); if (q->isHovered() && !q->isSelected() && !q->isPrinted()) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), penWidth, Qt::SolidLine)); painter->drawRect(rect); } if (q->isSelected() && !q->isPrinted()) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), penWidth, Qt::SolidLine)); painter->drawRect(rect); } // DEBUG("PlotAreaPrivate::paint() DONE"); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void PlotArea::save(QXmlStreamWriter* writer) const { Q_D(const PlotArea); writer->writeStartElement( "plotArea" ); writeBasicAttributes(writer); writeCommentElement(writer); writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); //border writer->writeStartElement( "border" ); WRITE_QPEN(d->borderPen); writer->writeAttribute( "borderOpacity", QString::number(d->borderOpacity) ); writer->writeAttribute( "borderCornerRadius", QString::number(d->borderCornerRadius) ); writer->writeEndElement(); writer->writeEndElement(); } //! Load from XML bool PlotArea::load(XmlStreamReader* reader, bool preview) { Q_D(PlotArea); 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() == "plotArea") break; if ( !reader->isStartElement() ) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("type").toString()); else d->backgroundType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("colorStyle").toString()); else d->backgroundColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("imageStyle").toString()); else d->backgroundImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("brushStyle").toString()); else d->backgroundBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("opacity").toString()); else d->backgroundOpacity = str.toDouble(); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_QPEN(d->borderPen); str = attribs.value("borderOpacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("borderOpacity").toString()); else d->borderOpacity = str.toDouble(); str = attribs.value("borderCornerRadius").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("borderCornerRadius").toString()); else d->borderCornerRadius = str.toDouble(); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } void PlotArea::loadThemeConfig(const KConfig& config) { KConfigGroup group; if (config.hasGroup(QLatin1String("Theme"))) group = config.group("CartesianPlot"); else group = config.group("PlotArea"); //background this->setBackgroundType((PlotArea::BackgroundType) group.readEntry("BackgroundType", (int)PlotArea::Color)); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor)); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled)); this->setBackgroundBrushStyle((Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern)); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor", QColor(Qt::white))); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor", QColor(Qt::black))); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", 1.0)); //border QPen pen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), - group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)), + group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)), (Qt::PenStyle)group.readEntry("BorderStyle", (int)Qt::SolidLine)); this->setBorderPen(pen); this->setBorderCornerRadius(group.readEntry("BorderCornerRadius", 0.0)); this->setBorderOpacity(group.readEntry("BorderOpacity", 1.0)); } void PlotArea::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); group.writeEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle()); group.writeEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()); group.writeEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor()); group.writeEntry("BackgroundImageStyle",(int) this->backgroundImageStyle()); group.writeEntry("BackgroundOpacity", this->backgroundOpacity()); group.writeEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor()); group.writeEntry("BackgroundType",(int) this->backgroundType()); group.writeEntry("BorderColor",(QColor) this->borderPen().color()); group.writeEntry("BorderCornerRadius", this->borderCornerRadius()); group.writeEntry("BorderOpacity", this->borderOpacity()); group.writeEntry("BorderStyle", (int) this->borderPen().style()); group.writeEntry("BorderWidth", this->borderPen().widthF()); } diff --git a/src/backend/worksheet/plots/cartesian/Axis.cpp b/src/backend/worksheet/plots/cartesian/Axis.cpp index f2b0c0437..47b38e85a 100644 --- a/src/backend/worksheet/plots/cartesian/Axis.cpp +++ b/src/backend/worksheet/plots/cartesian/Axis.cpp @@ -1,2360 +1,2360 @@ /*************************************************************************** File : Axis.cpp Project : LabPlot Description : Axis for cartesian coordinate systems. -------------------------------------------------------------------- Copyright : (C) 2011-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2020 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/AxisPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/TextLabel.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/core/AbstractColumn.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" // #include "backend/lib/trace.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include #include extern "C" { #include #include "backend/nsl/nsl_math.h" } /** * \class AxisGrid * \brief Helper class to get the axis grid drawn with the z-Value=0. * * The painting of the grid lines is separated from the painting of the axis itself. * This allows to use a different z-values for the grid lines (z=0, drawn below all other objects ) * and for the axis (z=FLT_MAX, drawn on top of all other objects) * * \ingroup worksheet */ class AxisGrid : public QGraphicsItem { public: explicit AxisGrid(AxisPrivate* a) { axis = a; setFlag(QGraphicsItem::ItemIsSelectable, false); setFlag(QGraphicsItem::ItemIsFocusable, false); setAcceptHoverEvents(false); } QRectF boundingRect() const override { QPainterPath gridShape; gridShape.addPath(WorksheetElement::shapeFromPath(axis->majorGridPath, axis->majorGridPen)); gridShape.addPath(WorksheetElement::shapeFromPath(axis->minorGridPath, axis->minorGridPen)); QRectF boundingRectangle = gridShape.boundingRect(); return boundingRectangle; } void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { Q_UNUSED(option) Q_UNUSED(widget) if (!axis->isVisible()) return; if (axis->linePath.isEmpty()) return; //draw major grid if (axis->majorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->majorGridOpacity); painter->setPen(axis->majorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->majorGridPath); } //draw minor grid if (axis->minorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->minorGridOpacity); painter->setPen(axis->minorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->minorGridPath); } } private: AxisPrivate* axis; }; /** * \class Axis * \brief Axis for cartesian coordinate systems. * * \ingroup worksheet */ Axis::Axis(const QString& name, AxisOrientation orientation) : WorksheetElement(name, AspectType::Axis), d_ptr(new AxisPrivate(this)) { d_ptr->orientation = orientation; init(); } Axis::Axis(const QString& name, AxisOrientation orientation, AxisPrivate* dd) : WorksheetElement(name, AspectType::Axis), d_ptr(dd) { d_ptr->orientation = orientation; init(); } void Axis::finalizeAdd() { Q_D(Axis); d->plot = dynamic_cast(parentAspect()); Q_ASSERT(d->plot); d->cSystem = dynamic_cast(d->plot->coordinateSystem()); } void Axis::init() { Q_D(Axis); KConfig config; KConfigGroup group = config.group("Axis"); d->autoScale = true; d->position = Axis::AxisCustom; d->offset = group.readEntry("PositionOffset", 0); d->scale = (Axis::AxisScale) group.readEntry("Scale", (int) Axis::ScaleLinear); d->autoScale = group.readEntry("AutoScale", true); d->start = group.readEntry("Start", 0); d->end = group.readEntry("End", 10); d->zeroOffset = group.readEntry("ZeroOffset", 0); d->scalingFactor = group.readEntry("ScalingFactor", 1.0); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int) Qt::SolidLine) ); - d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); + d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Unit::Point ) ) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->arrowType = (Axis::ArrowType) group.readEntry("ArrowType", (int)Axis::NoArrow); d->arrowPosition = (Axis::ArrowPosition) group.readEntry("ArrowPosition", (int)Axis::ArrowRight); - d->arrowSize = group.readEntry("ArrowSize", Worksheet::convertToSceneUnits(10, Worksheet::Point)); + d->arrowSize = group.readEntry("ArrowSize", Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point)); // axis title d->title = new TextLabel(this->name(), TextLabel::Type::AxisTitle); connect( d->title, &TextLabel::changed, this, &Axis::labelChanged); addChild(d->title); d->title->setHidden(true); d->title->graphicsItem()->setParentItem(d); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, false); d->title->graphicsItem()->setAcceptHoverEvents(false); d->title->setText(this->name()); if (d->orientation == AxisVertical) d->title->setRotationAngle(90); - d->titleOffsetX = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels - d->titleOffsetY = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels + d->titleOffsetX = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Point); //distance to the axis tick labels + d->titleOffsetY = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Point); //distance to the axis tick labels d->majorTicksDirection = (Axis::TicksDirection) group.readEntry("MajorTicksDirection", (int) Axis::ticksOut); d->majorTicksType = (Axis::TicksType) group.readEntry("MajorTicksType", (int) Axis::TicksTotalNumber); d->majorTicksNumber = group.readEntry("MajorTicksNumber", 11); d->majorTicksSpacing = group.readEntry("MajorTicksIncrement", 0.0); // set to 0, so axisdock determines the value to not have to many labels the first time switched to Spacing d->majorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine) ); d->majorTicksPen.setColor( group.readEntry("MajorTicksColor", QColor(Qt::black) ) ); - d->majorTicksPen.setWidthF( group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); - d->majorTicksLength = group.readEntry("MajorTicksLength", Worksheet::convertToSceneUnits(6.0, Worksheet::Point)); + d->majorTicksPen.setWidthF( group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point) ) ); + d->majorTicksLength = group.readEntry("MajorTicksLength", Worksheet::convertToSceneUnits(6.0, Worksheet::Unit::Point)); d->majorTicksOpacity = group.readEntry("MajorTicksOpacity", 1.0); d->minorTicksDirection = (Axis::TicksDirection) group.readEntry("MinorTicksDirection", (int) Axis::ticksOut); d->minorTicksType = (Axis::TicksType) group.readEntry("MinorTicksType", (int) Axis::TicksTotalNumber); d->minorTicksNumber = group.readEntry("MinorTicksNumber", 1); d->minorTicksIncrement = group.readEntry("MinorTicksIncrement", 0.0); // see MajorTicksIncrement d->minorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine) ); d->minorTicksPen.setColor( group.readEntry("MinorTicksColor", QColor(Qt::black) ) ); - d->minorTicksPen.setWidthF( group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); - d->minorTicksLength = group.readEntry("MinorTicksLength", Worksheet::convertToSceneUnits(3.0, Worksheet::Point)); + d->minorTicksPen.setWidthF( group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point) ) ); + d->minorTicksLength = group.readEntry("MinorTicksLength", Worksheet::convertToSceneUnits(3.0, Worksheet::Unit::Point)); d->minorTicksOpacity = group.readEntry("MinorTicksOpacity", 1.0); //Labels d->labelsFormat = (Axis::LabelsFormat) group.readEntry("LabelsFormat", (int)Axis::FormatDecimal); d->labelsAutoPrecision = group.readEntry("LabelsAutoPrecision", true); d->labelsPrecision = group.readEntry("LabelsPrecision", 1); d->labelsDateTimeFormat = group.readEntry("LabelsDateTimeFormat", "yyyy-MM-dd hh:mm:ss"); d->labelsPosition = (Axis::LabelsPosition) group.readEntry("LabelsPosition", (int) Axis::LabelsOut); - d->labelsOffset = group.readEntry("LabelsOffset", Worksheet::convertToSceneUnits( 5.0, Worksheet::Point )); + d->labelsOffset = group.readEntry("LabelsOffset", Worksheet::convertToSceneUnits( 5.0, Worksheet::Unit::Point )); d->labelsRotationAngle = group.readEntry("LabelsRotation", 0); d->labelsFont = group.readEntry("LabelsFont", QFont()); - d->labelsFont.setPixelSize( Worksheet::convertToSceneUnits( 10.0, Worksheet::Point ) ); + d->labelsFont.setPixelSize( Worksheet::convertToSceneUnits( 10.0, Worksheet::Unit::Point ) ); d->labelsColor = group.readEntry("LabelsFontColor", QColor(Qt::black)); d->labelsPrefix = group.readEntry("LabelsPrefix", "" ); d->labelsSuffix = group.readEntry("LabelsSuffix", "" ); d->labelsOpacity = group.readEntry("LabelsOpacity", 1.0); //major grid d->majorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MajorGridStyle", (int) Qt::NoPen) ); d->majorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) ); - d->majorGridPen.setWidthF( group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); + d->majorGridPen.setWidthF( group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Unit::Point ) ) ); d->majorGridOpacity = group.readEntry("MajorGridOpacity", 1.0); //minor grid d->minorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MinorGridStyle", (int) Qt::NoPen) ); d->minorGridPen.setColor(group.readEntry("MinorGridColor", QColor(Qt::gray)) ); - d->minorGridPen.setWidthF( group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); + d->minorGridPen.setWidthF( group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Unit::Point ) ) ); d->minorGridOpacity = group.readEntry("MinorGridOpacity", 1.0); } /*! * For the most frequently edited properties, create Actions and ActionGroups for the context menu. * For some ActionGroups the actual actions are created in \c GuiTool, */ void Axis::initActions() { visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Axis::visibilityChangedSlot); //Orientation orientationActionGroup = new QActionGroup(this); orientationActionGroup->setExclusive(true); connect(orientationActionGroup, &QActionGroup::triggered, this, &Axis::orientationChangedSlot); orientationHorizontalAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal"), orientationActionGroup); orientationHorizontalAction->setCheckable(true); orientationVerticalAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical"), orientationActionGroup); orientationVerticalAction->setCheckable(true); //Line lineStyleActionGroup = new QActionGroup(this); lineStyleActionGroup->setExclusive(true); connect(lineStyleActionGroup, &QActionGroup::triggered, this, &Axis::lineStyleChanged); lineColorActionGroup = new QActionGroup(this); lineColorActionGroup->setExclusive(true); connect(lineColorActionGroup, &QActionGroup::triggered, this, &Axis::lineColorChanged); //Ticks //TODO } void Axis::initMenus() { this->initActions(); //Orientation orientationMenu = new QMenu(i18n("Orientation")); orientationMenu->setIcon(QIcon::fromTheme("labplot-axis-horizontal")); orientationMenu->addAction(orientationHorizontalAction); orientationMenu->addAction(orientationVerticalAction); //Line lineMenu = new QMenu(i18n("Line")); lineMenu->setIcon(QIcon::fromTheme("draw-line")); lineStyleMenu = new QMenu(i18n("Style"), lineMenu); lineStyleMenu->setIcon(QIcon::fromTheme("object-stroke-style")); lineMenu->setIcon(QIcon::fromTheme("draw-line")); lineMenu->addMenu( lineStyleMenu ); lineColorMenu = new QMenu(i18n("Color"), lineMenu); lineColorMenu->setIcon(QIcon::fromTheme("fill-color")); GuiTools::fillColorMenu( lineColorMenu, lineColorActionGroup ); lineMenu->addMenu( lineColorMenu ); } QMenu* Axis::createContextMenu() { if (!orientationMenu) initMenus(); Q_D(const Axis); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //Orientation if ( d->orientation == AxisHorizontal ) orientationHorizontalAction->setChecked(true); else orientationVerticalAction->setChecked(true); menu->insertMenu(firstAction, orientationMenu); //Line styles GuiTools::updatePenStyles( lineStyleMenu, lineStyleActionGroup, d->linePen.color() ); GuiTools::selectPenStyleAction(lineStyleActionGroup, d->linePen.style() ); GuiTools::selectColorAction(lineColorActionGroup, d->linePen.color() ); menu->insertMenu(firstAction, lineMenu); menu->insertSeparator(firstAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon Axis::icon() const{ Q_D(const Axis); QIcon ico; if (d->orientation == Axis::AxisHorizontal) ico = QIcon::fromTheme("labplot-axis-horizontal"); else ico = QIcon::fromTheme("labplot-axis-vertical"); return ico; } Axis::~Axis() { if (orientationMenu) { delete orientationMenu; delete lineMenu; } //no need to delete d->title, since it was added with addChild in init(); //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } QGraphicsItem *Axis::graphicsItem() const { return d_ptr; } /*! * overrides the implementation in WorksheetElement and sets the z-value to the maximal possible, * axes are drawn on top of all other object in the plot. */ void Axis::setZValue(qreal) { Q_D(Axis); d->setZValue(std::numeric_limits::max()); d->gridItem->setParentItem(d->parentItem()); d->gridItem->setZValue(0); } void Axis::retransform() { Q_D(Axis); d->retransform(); } void Axis::retransformTickLabelStrings() { Q_D(Axis); d->retransformTickLabelStrings(); } void Axis::setSuppressRetransform(bool value) { Q_D(Axis); d->suppressRetransform = value; } void Axis::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_D(Axis); Q_UNUSED(pageResize); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); QPen pen = d->linePen; pen.setWidthF(pen.widthF() * ratio); d->linePen = pen; d->majorTicksLength *= ratio; // ticks are perpendicular to axis line -> verticalRatio relevant d->minorTicksLength *= ratio; d->labelsFont.setPixelSize( d->labelsFont.pixelSize() * ratio ); //TODO: take into account rotated labels d->labelsOffset *= ratio; d->title->handleResize(horizontalRatio, verticalRatio, pageResize); } /* ============================ getter methods ================= */ BASIC_SHARED_D_READER_IMPL(Axis, bool, autoScale, autoScale) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisOrientation, orientation, orientation) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisPosition, position, position) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisScale, scale, scale) BASIC_SHARED_D_READER_IMPL(Axis, double, offset, offset) BASIC_SHARED_D_READER_IMPL(Axis, double, start, start) BASIC_SHARED_D_READER_IMPL(Axis, double, end, end) BASIC_SHARED_D_READER_IMPL(Axis, qreal, scalingFactor, scalingFactor) BASIC_SHARED_D_READER_IMPL(Axis, qreal, zeroOffset, zeroOffset) BASIC_SHARED_D_READER_IMPL(Axis, TextLabel*, title, title) BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetX, titleOffsetX) BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetY, titleOffsetY) CLASS_SHARED_D_READER_IMPL(Axis, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, lineOpacity, lineOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowType, arrowType, arrowType) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowPosition, arrowPosition, arrowPosition) BASIC_SHARED_D_READER_IMPL(Axis, qreal, arrowSize, arrowSize) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, majorTicksDirection, majorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, majorTicksType, majorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, majorTicksNumber, majorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksSpacing, majorTicksSpacing) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, majorTicksColumn, majorTicksColumn) QString& Axis::majorTicksColumnPath() const { return d_ptr->majorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksLength, majorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorTicksPen, majorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksOpacity, majorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, minorTicksDirection, minorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, minorTicksType, minorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, minorTicksNumber, minorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksSpacing, minorTicksIncrement) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, minorTicksColumn, minorTicksColumn) QString& Axis::minorTicksColumnPath() const { return d_ptr->minorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksLength, minorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorTicksPen, minorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksOpacity, minorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsFormat, labelsFormat, labelsFormat); BASIC_SHARED_D_READER_IMPL(Axis, bool, labelsAutoPrecision, labelsAutoPrecision); BASIC_SHARED_D_READER_IMPL(Axis, int, labelsPrecision, labelsPrecision); BASIC_SHARED_D_READER_IMPL(Axis, QString, labelsDateTimeFormat, labelsDateTimeFormat); BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsPosition, labelsPosition, labelsPosition); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOffset, labelsOffset); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsRotationAngle, labelsRotationAngle); CLASS_SHARED_D_READER_IMPL(Axis, QColor, labelsColor, labelsColor); CLASS_SHARED_D_READER_IMPL(Axis, QFont, labelsFont, labelsFont); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsPrefix, labelsPrefix); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsSuffix, labelsSuffix); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOpacity, labelsOpacity); CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorGridPen, majorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorGridOpacity, majorGridOpacity) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorGridPen, minorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorGridOpacity, minorGridOpacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(Axis, SetAutoScale, bool, autoScale, retransform); void Axis::setAutoScale(bool autoScale) { Q_D(Axis); if (autoScale != d->autoScale) { exec(new AxisSetAutoScaleCmd(d, autoScale, ki18n("%1: set axis auto scaling"))); if (autoScale) { auto* plot = qobject_cast(parentAspect()); if (!plot) return; if (d->orientation == Axis::AxisHorizontal) { d->end = plot->xMax(); d->start = plot->xMin(); } else { d->end = plot->yMax(); d->start = plot->yMin(); } retransform(); emit endChanged(d->end); emit startChanged(d->start); } } } STD_SWAP_METHOD_SETTER_CMD_IMPL(Axis, SetVisible, bool, swapVisible); void Axis::setVisible(bool on) { Q_D(Axis); exec(new AxisSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool Axis::isVisible() const { Q_D(const Axis); return d->isVisible(); } void Axis::setPrinting(bool on) { Q_D(Axis); d->setPrinting(on); } STD_SETTER_CMD_IMPL_F_S(Axis, SetOrientation, Axis::AxisOrientation, orientation, retransform); void Axis::setOrientation( AxisOrientation orientation) { Q_D(Axis); if (orientation != d->orientation) exec(new AxisSetOrientationCmd(d, orientation, ki18n("%1: set axis orientation"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetPosition, Axis::AxisPosition, position, retransform); void Axis::setPosition(AxisPosition position) { Q_D(Axis); if (position != d->position) exec(new AxisSetPositionCmd(d, position, ki18n("%1: set axis position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScaling, Axis::AxisScale, scale, retransformTicks); void Axis::setScale(AxisScale scale) { Q_D(Axis); if (scale != d->scale) exec(new AxisSetScalingCmd(d, scale, ki18n("%1: set axis scale"))); } STD_SETTER_CMD_IMPL_F(Axis, SetOffset, double, offset, retransform); void Axis::setOffset(double offset, bool undo) { Q_D(Axis); if (offset != d->offset) { if (undo) { exec(new AxisSetOffsetCmd(d, offset, ki18n("%1: set axis offset"))); } else { d->offset = offset; //don't need to call retransform() afterward //since the only usage of this call is in CartesianPlot, where retransform is called for all children anyway. } emit positionChanged(offset); } } STD_SETTER_CMD_IMPL_F_S(Axis, SetStart, double, start, retransform); void Axis::setStart(double start) { Q_D(Axis); if (start != d->start) exec(new AxisSetStartCmd(d, start, ki18n("%1: set axis start"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetEnd, double, end, retransform); void Axis::setEnd(double end) { Q_D(Axis); if (end != d->end) exec(new AxisSetEndCmd(d, end, ki18n("%1: set axis end"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetZeroOffset, qreal, zeroOffset, retransform); void Axis::setZeroOffset(qreal zeroOffset) { Q_D(Axis); if (zeroOffset != d->zeroOffset) exec(new AxisSetZeroOffsetCmd(d, zeroOffset, ki18n("%1: set axis zero offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScalingFactor, qreal, scalingFactor, retransform); void Axis::setScalingFactor(qreal scalingFactor) { Q_D(Axis); if (scalingFactor != d->scalingFactor) exec(new AxisSetScalingFactorCmd(d, scalingFactor, ki18n("%1: set axis scaling factor"))); } //Title STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetX, qreal, titleOffsetX, retransform); void Axis::setTitleOffsetX(qreal offset) { Q_D(Axis); if (offset != d->titleOffsetX) exec(new AxisSetTitleOffsetXCmd(d, offset, ki18n("%1: set title offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetY, qreal, titleOffsetY, retransform); void Axis::setTitleOffsetY(qreal offset) { Q_D(Axis); if (offset != d->titleOffsetY) exec(new AxisSetTitleOffsetYCmd(d, offset, ki18n("%1: set title offset"))); } //Line STD_SETTER_CMD_IMPL_F_S(Axis, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect); void Axis::setLinePen(const QPen &pen) { Q_D(Axis); if (pen != d->linePen) exec(new AxisSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLineOpacity, qreal, lineOpacity, update); void Axis::setLineOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->lineOpacity) exec(new AxisSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowType, Axis::ArrowType, arrowType, retransformArrow); void Axis::setArrowType(ArrowType type) { Q_D(Axis); if (type != d->arrowType) exec(new AxisSetArrowTypeCmd(d, type, ki18n("%1: set arrow type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowPosition, Axis::ArrowPosition, arrowPosition, retransformArrow); void Axis::setArrowPosition(ArrowPosition position) { Q_D(Axis); if (position != d->arrowPosition) exec(new AxisSetArrowPositionCmd(d, position, ki18n("%1: set arrow position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowSize, qreal, arrowSize, retransformArrow); void Axis::setArrowSize(qreal arrowSize) { Q_D(Axis); if (arrowSize != d->arrowSize) exec(new AxisSetArrowSizeCmd(d, arrowSize, ki18n("%1: set arrow size"))); } //Major ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksDirection, Axis::TicksDirection, majorTicksDirection, retransformTicks); void Axis::setMajorTicksDirection(TicksDirection majorTicksDirection) { Q_D(Axis); if (majorTicksDirection != d->majorTicksDirection) exec(new AxisSetMajorTicksDirectionCmd(d, majorTicksDirection, ki18n("%1: set major ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksType, Axis::TicksType, majorTicksType, retransformTicks); void Axis::setMajorTicksType(TicksType majorTicksType) { Q_D(Axis); if (majorTicksType!= d->majorTicksType) exec(new AxisSetMajorTicksTypeCmd(d, majorTicksType, ki18n("%1: set major ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksNumber, int, majorTicksNumber, retransformTicks); void Axis::setMajorTicksNumber(int majorTicksNumber) { Q_D(Axis); if (majorTicksNumber != d->majorTicksNumber) exec(new AxisSetMajorTicksNumberCmd(d, majorTicksNumber, ki18n("%1: set the total number of the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksSpacing, qreal, majorTicksSpacing, retransformTicks); void Axis::setMajorTicksSpacing(qreal majorTicksSpacing) { Q_D(Axis); if (majorTicksSpacing != d->majorTicksSpacing) exec(new AxisSetMajorTicksSpacingCmd(d, majorTicksSpacing, ki18n("%1: set the spacing of the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksColumn, const AbstractColumn*, majorTicksColumn, retransformTicks) void Axis::setMajorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->majorTicksColumn) { exec(new AxisSetMajorTicksColumnCmd(d, column, ki18n("%1: assign major ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::majorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksPen, QPen, majorTicksPen, recalcShapeAndBoundingRect); void Axis::setMajorTicksPen(const QPen& pen) { Q_D(Axis); if (pen != d->majorTicksPen) exec(new AxisSetMajorTicksPenCmd(d, pen, ki18n("%1: set major ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksLength, qreal, majorTicksLength, retransformTicks); void Axis::setMajorTicksLength(qreal majorTicksLength) { Q_D(Axis); if (majorTicksLength != d->majorTicksLength) exec(new AxisSetMajorTicksLengthCmd(d, majorTicksLength, ki18n("%1: set major ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksOpacity, qreal, majorTicksOpacity, update); void Axis::setMajorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorTicksOpacity) exec(new AxisSetMajorTicksOpacityCmd(d, opacity, ki18n("%1: set major ticks opacity"))); } //Minor ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksDirection, Axis::TicksDirection, minorTicksDirection, retransformTicks); void Axis::setMinorTicksDirection(TicksDirection minorTicksDirection) { Q_D(Axis); if (minorTicksDirection != d->minorTicksDirection) exec(new AxisSetMinorTicksDirectionCmd(d, minorTicksDirection, ki18n("%1: set minor ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksType, Axis::TicksType, minorTicksType, retransformTicks); void Axis::setMinorTicksType(TicksType minorTicksType) { Q_D(Axis); if (minorTicksType!= d->minorTicksType) exec(new AxisSetMinorTicksTypeCmd(d, minorTicksType, ki18n("%1: set minor ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksNumber, int, minorTicksNumber, retransformTicks); void Axis::setMinorTicksNumber(int minorTicksNumber) { Q_D(Axis); if (minorTicksNumber != d->minorTicksNumber) exec(new AxisSetMinorTicksNumberCmd(d, minorTicksNumber, ki18n("%1: set the total number of the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksSpacing, qreal, minorTicksIncrement, retransformTicks); void Axis::setMinorTicksSpacing(qreal minorTicksSpacing) { Q_D(Axis); if (minorTicksSpacing != d->minorTicksIncrement) exec(new AxisSetMinorTicksSpacingCmd(d, minorTicksSpacing, ki18n("%1: set the spacing of the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksColumn, const AbstractColumn*, minorTicksColumn, retransformTicks) void Axis::setMinorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->minorTicksColumn) { exec(new AxisSetMinorTicksColumnCmd(d, column, ki18n("%1: assign minor ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::minorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksPen, QPen, minorTicksPen, recalcShapeAndBoundingRect); void Axis::setMinorTicksPen(const QPen& pen) { Q_D(Axis); if (pen != d->minorTicksPen) exec(new AxisSetMinorTicksPenCmd(d, pen, ki18n("%1: set minor ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksLength, qreal, minorTicksLength, retransformTicks); void Axis::setMinorTicksLength(qreal minorTicksLength) { Q_D(Axis); if (minorTicksLength != d->minorTicksLength) exec(new AxisSetMinorTicksLengthCmd(d, minorTicksLength, ki18n("%1: set minor ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksOpacity, qreal, minorTicksOpacity, update); void Axis::setMinorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorTicksOpacity) exec(new AxisSetMinorTicksOpacityCmd(d, opacity, ki18n("%1: set minor ticks opacity"))); } //Labels STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFormat, Axis::LabelsFormat, labelsFormat, retransformTicks); void Axis::setLabelsFormat(LabelsFormat labelsFormat) { Q_D(Axis); if (labelsFormat != d->labelsFormat) { //TODO: this part is not undo/redo-aware if (labelsFormat == Axis::FormatDecimal) d->labelsFormatDecimalOverruled = true; else d->labelsFormatDecimalOverruled = false; exec(new AxisSetLabelsFormatCmd(d, labelsFormat, ki18n("%1: set labels format"))); } } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsAutoPrecision, bool, labelsAutoPrecision, retransformTickLabelStrings); void Axis::setLabelsAutoPrecision(bool labelsAutoPrecision) { Q_D(Axis); if (labelsAutoPrecision != d->labelsAutoPrecision) exec(new AxisSetLabelsAutoPrecisionCmd(d, labelsAutoPrecision, ki18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrecision, int, labelsPrecision, retransformTickLabelStrings); void Axis::setLabelsPrecision(int labelsPrecision) { Q_D(Axis); if (labelsPrecision != d->labelsPrecision) exec(new AxisSetLabelsPrecisionCmd(d, labelsPrecision, ki18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsDateTimeFormat, QString, labelsDateTimeFormat, retransformTickLabelStrings); void Axis::setLabelsDateTimeFormat(const QString& format) { Q_D(Axis); if (format != d->labelsDateTimeFormat) exec(new AxisSetLabelsDateTimeFormatCmd(d, format, ki18n("%1: set labels datetime format"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPosition, Axis::LabelsPosition, labelsPosition, retransformTickLabelPositions); void Axis::setLabelsPosition(LabelsPosition labelsPosition) { Q_D(Axis); if (labelsPosition != d->labelsPosition) exec(new AxisSetLabelsPositionCmd(d, labelsPosition, ki18n("%1: set labels position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOffset, double, labelsOffset, retransformTickLabelPositions); void Axis::setLabelsOffset(double offset) { Q_D(Axis); if (offset != d->labelsOffset) exec(new AxisSetLabelsOffsetCmd(d, offset, ki18n("%1: set label offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, retransformTickLabelPositions); void Axis::setLabelsRotationAngle(qreal angle) { Q_D(Axis); if (angle != d->labelsRotationAngle) exec(new AxisSetLabelsRotationAngleCmd(d, angle, ki18n("%1: set label rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsColor, QColor, labelsColor, update); void Axis::setLabelsColor(const QColor& color) { Q_D(Axis); if (color != d->labelsColor) exec(new AxisSetLabelsColorCmd(d, color, ki18n("%1: set label color"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFont, QFont, labelsFont, retransformTickLabelStrings); void Axis::setLabelsFont(const QFont& font) { Q_D(Axis); if (font != d->labelsFont) exec(new AxisSetLabelsFontCmd(d, font, ki18n("%1: set label font"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrefix, QString, labelsPrefix, retransformTickLabelStrings); void Axis::setLabelsPrefix(const QString& prefix) { Q_D(Axis); if (prefix != d->labelsPrefix) exec(new AxisSetLabelsPrefixCmd(d, prefix, ki18n("%1: set label prefix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsSuffix, QString, labelsSuffix, retransformTickLabelStrings); void Axis::setLabelsSuffix(const QString& suffix) { Q_D(Axis); if (suffix != d->labelsSuffix) exec(new AxisSetLabelsSuffixCmd(d, suffix, ki18n("%1: set label suffix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOpacity, qreal, labelsOpacity, update); void Axis::setLabelsOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->labelsOpacity) exec(new AxisSetLabelsOpacityCmd(d, opacity, ki18n("%1: set labels opacity"))); } //Major grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridPen, QPen, majorGridPen, retransformMajorGrid); void Axis::setMajorGridPen(const QPen& pen) { Q_D(Axis); if (pen != d->majorGridPen) exec(new AxisSetMajorGridPenCmd(d, pen, ki18n("%1: set major grid style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridOpacity, qreal, majorGridOpacity, updateGrid); void Axis::setMajorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorGridOpacity) exec(new AxisSetMajorGridOpacityCmd(d, opacity, ki18n("%1: set major grid opacity"))); } //Minor grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridPen, QPen, minorGridPen, retransformMinorGrid); void Axis::setMinorGridPen(const QPen& pen) { Q_D(Axis); if (pen != d->minorGridPen) exec(new AxisSetMinorGridPenCmd(d, pen, ki18n("%1: set minor grid style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridOpacity, qreal, minorGridOpacity, updateGrid); void Axis::setMinorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorGridOpacity) exec(new AxisSetMinorGridOpacityCmd(d, opacity, ki18n("%1: set minor grid opacity"))); } //############################################################################## //#################################### SLOTs ################################ //############################################################################## void Axis::labelChanged() { Q_D(Axis); d->recalcShapeAndBoundingRect(); } void Axis::retransformTicks() { Q_D(Axis); d->retransformTicks(); } void Axis::majorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect == d->majorTicksColumn) { d->majorTicksColumn = nullptr; d->retransformTicks(); } } void Axis::minorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect == d->minorTicksColumn) { d->minorTicksColumn = nullptr; d->retransformTicks(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Axis::orientationChangedSlot(QAction* action) { if (action == orientationHorizontalAction) this->setOrientation(AxisHorizontal); else this->setOrientation(AxisVertical); } void Axis::lineStyleChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action)); this->setLinePen(pen); } void Axis::lineColorChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setColor(GuiTools::colorFromAction(lineColorActionGroup, action)); this->setLinePen(pen); } void Axis::visibilityChangedSlot() { Q_D(const Axis); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### AxisPrivate::AxisPrivate(Axis* owner) : gridItem(new AxisGrid(this)), q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemIsFocusable, true); setAcceptHoverEvents(true); } QString AxisPrivate::name() const{ return q->name(); } bool AxisPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } QRectF AxisPrivate::boundingRect() const{ return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath AxisPrivate::shape() const{ return axisShape; } /*! recalculates the position of the axis on the worksheet */ void AxisPrivate::retransform() { if (suppressRetransform || !plot) return; // PERFTRACE(name().toLatin1() + ", AxisPrivate::retransform()"); m_suppressRecalc = true; retransformLine(); m_suppressRecalc = false; recalcShapeAndBoundingRect(); } void AxisPrivate::retransformLine() { if (suppressRetransform) return; linePath = QPainterPath(); lines.clear(); QPointF startPoint; QPointF endPoint; if (orientation == Axis::AxisHorizontal) { if (position == Axis::AxisTop) offset = plot->yMax(); else if (position == Axis::AxisBottom) offset = plot->yMin(); else if (position == Axis::AxisCentered) offset = plot->yMin() + (plot->yMax()-plot->yMin())/2; startPoint.setX(start); startPoint.setY(offset); endPoint.setX(end); endPoint.setY(offset); } else { // vertical if (position == Axis::AxisLeft) offset = plot->xMin(); else if (position == Axis::AxisRight) offset = plot->xMax(); else if (position == Axis::AxisCentered) offset = plot->xMin() + (plot->xMax()-plot->xMin())/2; startPoint.setX(offset); startPoint.setY(start); endPoint.setY(end); endPoint.setX(offset); } lines.append(QLineF(startPoint, endPoint)); lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MarkGaps); for (const auto& line : lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } if (linePath.isEmpty()) { recalcShapeAndBoundingRect(); return; } else { retransformArrow(); retransformTicks(); } } void AxisPrivate::retransformArrow() { if (suppressRetransform) return; arrowPath = QPainterPath(); if (arrowType == Axis::NoArrow || lines.isEmpty()) { recalcShapeAndBoundingRect(); return; } if (arrowPosition == Axis::ArrowRight || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(lines.size()-1).p2(); this->addArrow(endPoint, 1); } if (arrowPosition == Axis::ArrowLeft || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(0).p1(); this->addArrow(endPoint, -1); } recalcShapeAndBoundingRect(); } void AxisPrivate::addArrow(QPointF startPoint, int direction) { static const double cos_phi = cos(M_PI/6.); if (orientation == Axis::AxisHorizontal) { QPointF endPoint = QPointF(startPoint.x() + direction*arrowSize, startPoint.y()); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/8, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; } } else { //vertical orientation QPointF endPoint = QPointF(startPoint.x(), startPoint.y()-direction*arrowSize); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/8)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; } } } //! helper function for retransformTicks() bool AxisPrivate::transformAnchor(QPointF* anchorPoint) { QVector points; points.append(*anchorPoint); points = cSystem->mapLogicalToScene(points); if (points.count() != 1) { // point is not mappable or in a coordinate gap return false; } else { *anchorPoint = points.at(0); return true; } } /*! recalculates the position of the axis ticks. */ void AxisPrivate::retransformTicks() { if (suppressRetransform) return; //TODO: check that start and end are > 0 for log and >=0 for sqrt, etc. majorTicksPath = QPainterPath(); minorTicksPath = QPainterPath(); majorTickPoints.clear(); minorTickPoints.clear(); tickLabelValues.clear(); if ( majorTicksNumber < 1 || (majorTicksDirection == Axis::noTicks && minorTicksDirection == Axis::noTicks) ) { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } //determine the increment for the major ticks double majorTicksIncrement = 0; int tmpMajorTicksNumber = 0; if (majorTicksType == Axis::TicksTotalNumber) { //the total number of major ticks is given - > determine the increment tmpMajorTicksNumber = majorTicksNumber; switch (scale) { case Axis::ScaleLinear: majorTicksIncrement = (end-start)/(majorTicksNumber-1); break; case Axis::ScaleLog10: majorTicksIncrement = (log10(end)-log10(start))/(majorTicksNumber-1); break; case Axis::ScaleLog2: majorTicksIncrement = (log(end)-log(start))/log(2)/(majorTicksNumber-1); break; case Axis::ScaleLn: majorTicksIncrement = (log(end)-log(start))/(majorTicksNumber-1); break; case Axis::ScaleSqrt: majorTicksIncrement = (sqrt(end)-sqrt(start))/(majorTicksNumber-1); break; case Axis::ScaleX2: majorTicksIncrement = (end*end - start*start)/(majorTicksNumber-1); } } else if (majorTicksType == Axis::TicksSpacing) { //the increment of the major ticks is given -> determine the number majorTicksIncrement = majorTicksSpacing * GSL_SIGN(end-start); switch (scale) { case Axis::ScaleLinear: tmpMajorTicksNumber = qRound((end-start)/majorTicksIncrement + 1); break; case Axis::ScaleLog10: tmpMajorTicksNumber = qRound((log10(end)-log10(start))/majorTicksIncrement + 1); break; case Axis::ScaleLog2: tmpMajorTicksNumber = qRound((log(end)-log(start))/log(2)/majorTicksIncrement + 1); break; case Axis::ScaleLn: tmpMajorTicksNumber = qRound((log(end)-log(start))/majorTicksIncrement + 1); break; case Axis::ScaleSqrt: tmpMajorTicksNumber = qRound((sqrt(end)-sqrt(start))/majorTicksIncrement + 1); break; case Axis::ScaleX2: tmpMajorTicksNumber = qRound((end*end - start*start)/majorTicksIncrement + 1); } } else { //custom column was provided if (majorTicksColumn) { tmpMajorTicksNumber = majorTicksColumn->rowCount(); } else { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } } int tmpMinorTicksNumber; if (minorTicksType == Axis::TicksTotalNumber) tmpMinorTicksNumber = minorTicksNumber; else if (minorTicksType == Axis::TicksSpacing) tmpMinorTicksNumber = fabs(end - start)/ (majorTicksNumber - 1)/minorTicksIncrement - 1; else (minorTicksColumn) ? tmpMinorTicksNumber = minorTicksColumn->rowCount() : tmpMinorTicksNumber = 0; QPointF anchorPoint; QPointF startPoint; QPointF endPoint; qreal majorTickPos = 0.0; qreal minorTickPos; qreal nextMajorTickPos = 0.0; const int xDirection = cSystem->xDirection(); const int yDirection = cSystem->yDirection(); const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2; const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2; bool valid; //DEBUG("tmpMajorTicksNumber = " << tmpMajorTicksNumber) for (int iMajor = 0; iMajor < tmpMajorTicksNumber; iMajor++) { //DEBUG("major tick " << iMajor) //calculate major tick's position if (majorTicksType != Axis::TicksCustomColumn) { switch (scale) { case Axis::ScaleLinear: majorTickPos = start + majorTicksIncrement * iMajor; nextMajorTickPos = majorTickPos + majorTicksIncrement; break; case Axis::ScaleLog10: majorTickPos = start * pow(10, majorTicksIncrement*iMajor); nextMajorTickPos = majorTickPos * pow(10, majorTicksIncrement); break; case Axis::ScaleLog2: majorTickPos = start * pow(2, majorTicksIncrement*iMajor); nextMajorTickPos = majorTickPos * pow(2, majorTicksIncrement); break; case Axis::ScaleLn: majorTickPos = start * exp(majorTicksIncrement*iMajor); nextMajorTickPos = majorTickPos * exp(majorTicksIncrement); break; case Axis::ScaleSqrt: majorTickPos = pow(sqrt(start) + majorTicksIncrement*iMajor, 2); nextMajorTickPos = pow(sqrt(start) + majorTicksIncrement*(iMajor+1), 2); break; case Axis::ScaleX2: majorTickPos = sqrt(start*start + majorTicksIncrement*iMajor); nextMajorTickPos = sqrt(start*start + majorTicksIncrement*(iMajor+1)); break; } } else { // custom column if (!majorTicksColumn->isValid(iMajor) || majorTicksColumn->isMasked(iMajor)) continue; majorTickPos = majorTicksColumn->valueAt(iMajor); // set next major tick pos for minor ticks if (iMajor < tmpMajorTicksNumber - 1) { if (majorTicksColumn->isValid(iMajor+1) && !majorTicksColumn->isMasked(iMajor+1)) nextMajorTickPos = majorTicksColumn->valueAt(iMajor+1); } else // last major tick tmpMinorTicksNumber = 0; } //calculate start and end points for major tick's line if (majorTicksDirection != Axis::noTicks) { if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(majorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } } } else { // vertical anchorPoint.setY(majorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } } } double value = scalingFactor * majorTickPos + zeroOffset; //if custom column is used, we can have duplicated values in it and we need only unique values if (majorTicksType == Axis::TicksCustomColumn && tickLabelValues.indexOf(value) != -1) valid = false; //add major tick's line to the painter path if (valid) { majorTicksPath.moveTo(startPoint); majorTicksPath.lineTo(endPoint); majorTickPoints << anchorPoint; tickLabelValues << value; } } //minor ticks //DEBUG(" tmpMinorTicksNumber = " << tmpMinorTicksNumber) if (Axis::noTicks != minorTicksDirection && tmpMajorTicksNumber > 1 && tmpMinorTicksNumber > 0 && iMajor < tmpMajorTicksNumber - 1 && nextMajorTickPos != majorTickPos) { //minor ticks are placed at equidistant positions independent of the selected scaling for the major ticks positions double minorTicksIncrement = (nextMajorTickPos - majorTickPos)/(tmpMinorTicksNumber + 1); //DEBUG(" nextMajorTickPos = " << nextMajorTickPos) //DEBUG(" majorTickPos = " << majorTickPos) //DEBUG(" minorTicksIncrement = " << minorTicksIncrement) for (int iMinor = 0; iMinor < tmpMinorTicksNumber; iMinor++) { //calculate minor tick's position if (minorTicksType != Axis::TicksCustomColumn) { minorTickPos = majorTickPos + (iMinor + 1) * minorTicksIncrement; } else { if (!minorTicksColumn->isValid(iMinor) || minorTicksColumn->isMasked(iMinor)) continue; minorTickPos = minorTicksColumn->valueAt(iMinor); //in the case a custom column is used for the minor ticks, we draw them _once_ for the whole range of the axis. //execute the minor ticks loop only once. if (iMajor > 0) break; } //DEBUG(" minorTickPos = " << minorTickPos) //calculate start and end points for minor tick's line if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(minorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? -yDirection * minorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? -yDirection * minorTicksLength : 0); } } } else { // vertical anchorPoint.setY(minorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? -xDirection * minorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? -xDirection * minorTicksLength : 0, 0); } } } //add minor tick's line to the painter path if (valid) { minorTicksPath.moveTo(startPoint); minorTicksPath.lineTo(endPoint); minorTickPoints << anchorPoint; } } } } //tick positions where changed -> update the position of the tick labels and grid lines retransformTickLabelStrings(); retransformMajorGrid(); retransformMinorGrid(); } /*! creates the tick label strings starting with the most optimal (=the smallest possible number of float digits) precision for the floats */ void AxisPrivate::retransformTickLabelStrings() { if (suppressRetransform) return; //DEBUG("AxisPrivate::retransformTickLabelStrings()"); if (labelsAutoPrecision) { //check, whether we need to increase the current precision int newPrecision = upperLabelsPrecision(labelsPrecision); if (newPrecision != labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } else { //check, whether we can reduce the current precision newPrecision = lowerLabelsPrecision(labelsPrecision); if (newPrecision != labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } } } //DEBUG("labelsPrecision =" << labelsPrecision); //automatically switch from 'decimal' to 'scientific' format for big numbers (>10^4) //and back to decimal when the numbers get smaller after the auto-switch again if (labelsFormat == Axis::FormatDecimal && !labelsFormatDecimalOverruled) { for (auto value : tickLabelValues) { if (std::abs(value) > 1e4) { labelsFormat = Axis::FormatScientificE; emit q->labelsFormatChanged(labelsFormat); labelsFormatAutoChanged = true; break; } } } else if (labelsFormatAutoChanged ) { //check whether we still have big numbers bool changeBack = true; for (auto value : tickLabelValues) { if (std::abs(value) > 1e4) { changeBack = false; break; } } if (changeBack) { labelsFormatAutoChanged = false; labelsFormat = Axis::FormatDecimal; emit q->labelsFormatChanged(labelsFormat); } } tickLabelStrings.clear(); QString str; if ( (orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ) { if (labelsFormat == Axis::FormatDecimal) { QString nullStr = QString::number(0, 'f', labelsPrecision); for (const auto value : tickLabelValues) { str = QString::number(value, 'f', labelsPrecision); if (str == "-" + nullStr) str = nullStr; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatScientificE) { QString nullStr = QString::number(0, 'e', labelsPrecision); for (const auto value : tickLabelValues) { str = QString::number(value, 'e', labelsPrecision); if (str == "-" + nullStr) str = nullStr; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers10) { for (const auto value : tickLabelValues) { str = "10" + QString::number(log10(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers2) { for (const auto value : tickLabelValues) { str = "2" + QString::number(log2(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowersE) { for (const auto value : tickLabelValues) { str = "e" + QString::number(log(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatMultipliesPi) { for (const auto value : tickLabelValues) { str = "" + QString::number(value / M_PI, 'f', labelsPrecision) + "" + QChar(0x03C0); str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } } else { for (const auto value : tickLabelValues) { QDateTime dateTime; dateTime.setMSecsSinceEpoch(value); str = dateTime.toString(labelsDateTimeFormat); str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } //recalculate the position of the tick labels retransformTickLabelPositions(); } /*! returns the smallest upper limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::upperLabelsPrecision(int precision) { //DEBUG("AxisPrivate::upperLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, increase the precision. QVector tempValues; for (const auto value : tickLabelValues) tempValues.append( nsl_math_round_places(value, precision) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate for the current precision found, increase the precision and check again return upperLabelsPrecision(precision + 1); } } } //no duplicates for the current precision found: return the current value //DEBUG(" upper precision = " << precision); return precision; } /*! returns highest lower limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::lowerLabelsPrecision(int precision) { // DEBUG("AxisPrivate::lowerLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, decrease the precision. QVector tempValues; for (const auto value : tickLabelValues) tempValues.append( nsl_math_round_places(value, precision-1) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate found for the reduced precision //-> current precision cannot be reduced, return the current value // DEBUG(" lower precision = " << precision); return precision; } } } //no duplicates found, reduce further, and check again if (precision == 0) return 0; else return lowerLabelsPrecision(precision - 1); } /*! recalculates the position of the tick labels. Called when the geometry related properties (position, offset, font size, suffix, prefix) of the labels are changed. */ void AxisPrivate::retransformTickLabelPositions() { tickLabelPoints.clear(); if (majorTicksDirection == Axis::noTicks || labelsPosition == Axis::NoLabels) { recalcShapeAndBoundingRect(); return; } QFontMetrics fm(labelsFont); double width = 0; double height = fm.ascent(); QPointF pos; const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2; const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2; const int xDirection = cSystem->xDirection(); const int yDirection = cSystem->yDirection(); QPointF startPoint, endPoint, anchorPoint; QTextDocument td; td.setDefaultFont(labelsFont); double cosine = cos(labelsRotationAngle * M_PI / 180.); // calculate only one time double sine = sin(labelsRotationAngle * M_PI / 180.); // calculate only one time for ( int i = 0; i < majorTickPoints.size(); i++ ) { if ((orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric)) { if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { width = fm.boundingRect(tickLabelStrings.at(i)).width(); } else { td.setHtml(tickLabelStrings.at(i)); width = td.size().width(); height = td.size().height(); } } else { // Datetime width = fm.boundingRect(tickLabelStrings.at(i)).width(); } double diffx = cosine * width; double diffy = sine * width; anchorPoint = majorTickPoints.at(i); //center align all labels with respect to the end point of the tick line if (orientation == Axis::AxisHorizontal) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } // for rotated labels (angle is not zero), align label's corner at the position of the tick if (fabs(fabs(labelsRotationAngle) - 180.) < 1.e-2) { // +-180° if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() + width/2); pos.setY(endPoint.y() + labelsOffset ); } else { pos.setX(startPoint.x() + width/2); pos.setY(startPoint.y() - height - labelsOffset); } } else if (labelsRotationAngle <= -0.01) { // [-0.01°, -180°) if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() + sine * height/2); pos.setY(endPoint.y() + labelsOffset + cosine * height/2); } else { pos.setX(startPoint.x() + sine * height/2 - diffx); pos.setY(startPoint.y() - labelsOffset + cosine * height/2 + diffy); } } else if (labelsRotationAngle >= 0.01) { // [0.01°, 180°) if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() - diffx + sine * height/2); pos.setY(endPoint.y() + labelsOffset + diffy + cosine * height/2); } else { pos.setX(startPoint.x() + sine * height/2); pos.setY(startPoint.y() - labelsOffset + cosine * height/2); } } else { // 0° if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() - width/2); pos.setY(endPoint.y() + height + labelsOffset); } else { pos.setX(startPoint.x() - width/2); pos.setY(startPoint.y() - labelsOffset); } } // ---------------------- vertical ------------------------- } else { if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } if (fabs(labelsRotationAngle - 90.) < 1.e-2) { // +90° if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() - labelsOffset); pos.setY(endPoint.y() + width/2 ); } else { pos.setX(startPoint.x() + labelsOffset); pos.setY(startPoint.y() + width/2); } } else if (fabs(labelsRotationAngle + 90.) < 1.e-2) { // -90° if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() - labelsOffset - height); pos.setY(endPoint.y() - width/2); } else { pos.setX(startPoint.x() + labelsOffset); pos.setY(startPoint.y() - width/2); } } else if (fabs(fabs(labelsRotationAngle) - 180.) < 1.e-2) { // +-180° if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() - labelsOffset); pos.setY(endPoint.y() - height/2); } else { pos.setX(startPoint.x() + labelsOffset + width); pos.setY(startPoint.y() - height/2); } } else if (fabs(labelsRotationAngle) >= 0.01 && fabs(labelsRotationAngle) <= 89.99) { // [0.01°, 90°) if (labelsPosition == Axis::LabelsOut) { // left pos.setX(endPoint.x() - labelsOffset - diffx + sine * height/2); pos.setY(endPoint.y() + cosine * height/2 + diffy); } else { pos.setX(startPoint.x() + labelsOffset + sine * height/2); pos.setY(startPoint.y() + cosine * height/2); } } else if (fabs(labelsRotationAngle) >= 90.01 && fabs(labelsRotationAngle) <= 179.99) { // [90.01, 180) if (labelsPosition == Axis::LabelsOut) { // left pos.setX(endPoint.x() - labelsOffset + sine * height/2); pos.setY(endPoint.y() + cosine * height/2); } else { pos.setX(startPoint.x() + labelsOffset - diffx + sine * height/2); pos.setY(startPoint.y() + diffy + cosine * height/2); } } else { // 0° if (labelsPosition == Axis::LabelsOut) { pos.setX(endPoint.x() - width - labelsOffset); pos.setY(endPoint.y() + height/2); } else { pos.setX(startPoint.x() + labelsOffset); pos.setY(startPoint.y() + height/2); } } } tickLabelPoints << pos; } recalcShapeAndBoundingRect(); } void AxisPrivate::retransformMajorGrid() { if (suppressRetransform) return; majorGridPath = QPainterPath(); if (majorGridPen.style() == Qt::NoPen || majorTickPoints.size() == 0) { recalcShapeAndBoundingRect(); return; } //major tick points are already in scene coordinates, convert them back to logical... //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function. //Currently, grid lines disappear somtimes without this flag QVector logicalMajorTickPoints = cSystem->mapSceneToLogical(majorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); if (logicalMajorTickPoints.isEmpty()) return; //TODO: //when iterating over all grid lines, skip the first and the last points for auto scaled axes, //since we don't want to paint any grid lines at the plot boundaries bool skipLowestTick, skipUpperTick; if (orientation == Axis::AxisHorizontal) { //horizontal axis skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).x(), plot->xMin()); skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).x(), plot->xMax()); } else { skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).y(), plot->yMin()); skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).y(), plot->yMax()); } int start, end; if (skipLowestTick) { if (logicalMajorTickPoints.size() > 1) start = 1; else start = 0; } else { start = 0; } if (skipUpperTick) { if (logicalMajorTickPoints.size() > 1) end = logicalMajorTickPoints.size() - 1; else end = 0; } else { end = logicalMajorTickPoints.size(); } QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis double yMin = plot->yMin(); double yMax = plot->yMax(); for (int i = start; i < end; ++i) { const QPointF& point = logicalMajorTickPoints.at(i); lines.append( QLineF(point.x(), yMin, point.x(), yMax) ); } } else { //vertical axis double xMin = plot->xMin(); double xMax = plot->xMax(); //skip the first and the last points, since we don't want to paint any grid lines at the plot boundaries for (int i = start; i < end; ++i) { const QPointF& point = logicalMajorTickPoints.at(i); lines.append( QLineF(xMin, point.y(), xMax, point.y()) ); } } lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); for (const auto& line : lines) { majorGridPath.moveTo(line.p1()); majorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void AxisPrivate::retransformMinorGrid() { if (suppressRetransform) return; minorGridPath = QPainterPath(); if (minorGridPen.style() == Qt::NoPen) { recalcShapeAndBoundingRect(); return; } //minor tick points are already in scene coordinates, convert them back to logical... //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function. //Currently, grid lines disappear somtimes without this flag QVector logicalMinorTickPoints = cSystem->mapSceneToLogical(minorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis double yMin = plot->yMin(); double yMax = plot->yMax(); for (const auto point : logicalMinorTickPoints) lines.append( QLineF(point.x(), yMin, point.x(), yMax) ); } else { //vertical axis double xMin = plot->xMin(); double xMax = plot->xMax(); for (const auto point: logicalMinorTickPoints) lines.append( QLineF(xMin, point.y(), xMax, point.y()) ); } lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); for (const auto& line : lines) { minorGridPath.moveTo(line.p1()); minorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } /*! * called when the opacity of the grid was changes, update the grid graphics item */ //TODO: this function is only needed for loaded projects where update() doesn't seem to be enough //and we have to call gridItem->update() explicitly. //This is not required for newly created plots/axes. Why is this difference? void AxisPrivate::updateGrid() { gridItem->update(); } void AxisPrivate::recalcShapeAndBoundingRect() { if (m_suppressRecalc) return; prepareGeometryChange(); if (linePath.isEmpty()) { axisShape = QPainterPath(); boundingRectangle = QRectF(); title->setPositionInvalid(true); if (plot) plot->prepareGeometryChange(); return; } else { title->setPositionInvalid(false); } axisShape = WorksheetElement::shapeFromPath(linePath, linePen); axisShape.addPath(WorksheetElement::shapeFromPath(arrowPath, linePen)); axisShape.addPath(WorksheetElement::shapeFromPath(majorTicksPath, majorTicksPen)); axisShape.addPath(WorksheetElement::shapeFromPath(minorTicksPath, minorTicksPen)); QPainterPath tickLabelsPath = QPainterPath(); if (labelsPosition != Axis::NoLabels) { QTransform trafo; QPainterPath tempPath; QFontMetrics fm(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); for (int i = 0; i < tickLabelPoints.size(); i++) { tempPath = QPainterPath(); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { tempPath.addRect(fm.boundingRect(tickLabelStrings.at(i))); } else { td.setHtml(tickLabelStrings.at(i)); tempPath.addRect(QRectF(0, -td.size().height(), td.size().width(), td.size().height())); } trafo.reset(); trafo.translate( tickLabelPoints.at(i).x(), tickLabelPoints.at(i).y() ); trafo.rotate(-labelsRotationAngle); tempPath = trafo.map(tempPath); tickLabelsPath.addPath(WorksheetElement::shapeFromPath(tempPath, linePen)); } axisShape.addPath(WorksheetElement::shapeFromPath(tickLabelsPath, QPen())); } //add title label, if available if ( title->isVisible() && !title->text().text.isEmpty() ) { const QRectF& titleRect = title->graphicsItem()->boundingRect(); if (titleRect.width() != 0.0 && titleRect.height() != 0.0) { //determine the new position of the title label: //we calculate the new position here and not in retransform(), //since it depends on the size and position of the tick labels, tickLabelsPath, available here. QRectF rect = linePath.boundingRect(); qreal offsetX = titleOffsetX; //the distance to the axis line qreal offsetY = titleOffsetY; //the distance to the axis line if (orientation == Axis::AxisHorizontal) { offsetY -= titleRect.height()/2; if (labelsPosition == Axis::LabelsOut) offsetY -= labelsOffset + tickLabelsPath.boundingRect().height(); title->setPosition( QPointF( (rect.topLeft().x() + rect.topRight().x())/2 + titleOffsetX, rect.bottomLeft().y() - offsetY ) ); } else { offsetX -= titleRect.width()/2; if (labelsPosition == Axis::LabelsOut) offsetX -= labelsOffset+ tickLabelsPath.boundingRect().width(); title->setPosition( QPointF( rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y())/2 - titleOffsetY) ); } axisShape.addPath(WorksheetElement::shapeFromPath(title->graphicsItem()->mapToParent(title->graphicsItem()->shape()), linePen)); } } boundingRectangle = axisShape.boundingRect(); //if the axis goes beyond the current bounding box of the plot (too high offset is used, too long labels etc.) //request a prepareGeometryChange() for the plot in order to properly keep track of geometry changes if (plot) plot->prepareGeometryChange(); } /*! paints the content of the axis. Reimplemented from \c QGraphicsItem. \sa QGraphicsItem::paint() */ void AxisPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (linePath.isEmpty()) return; //draw the line if (linePen.style() != Qt::NoPen) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::SolidPattern); painter->drawPath(linePath); //draw the arrow if (arrowType != Axis::NoArrow) painter->drawPath(arrowPath); } //draw the major ticks if (majorTicksDirection != Axis::noTicks) { painter->setOpacity(majorTicksOpacity); painter->setPen(majorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(majorTicksPath); } //draw the minor ticks if (minorTicksDirection != Axis::noTicks) { painter->setOpacity(minorTicksOpacity); painter->setPen(minorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(minorTicksPath); } // draw tick labels if (labelsPosition != Axis::NoLabels) { painter->setOpacity(labelsOpacity); painter->setPen(QPen(labelsColor)); painter->setFont(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); if ((orientation == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (orientation == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric)) { for (int i = 0; i < tickLabelPoints.size(); i++) { painter->translate(tickLabelPoints.at(i)); painter->save(); painter->rotate(-labelsRotationAngle); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); } else { td.setHtml(tickLabelStrings.at(i)); painter->translate(0, -td.size().height()); td.drawContents(painter); } painter->restore(); painter->translate(-tickLabelPoints.at(i)); } } else { // datetime for (int i = 0; i < tickLabelPoints.size(); i++) { painter->translate(tickLabelPoints.at(i)); painter->save(); painter->rotate(-labelsRotationAngle); painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); painter->restore(); painter->translate(-tickLabelPoints.at(i)); } } } if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(axisShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(axisShape); } } void AxisPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void AxisPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(axisShape.boundingRect()); } } void AxisPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(axisShape.boundingRect()); } } void AxisPrivate::setPrinting(bool on) { m_printing = on; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Axis::save(QXmlStreamWriter* writer) const{ Q_D(const Axis); writer->writeStartElement( "axis" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); writer->writeAttribute( "autoScale", QString::number(d->autoScale) ); writer->writeAttribute( "orientation", QString::number(d->orientation) ); writer->writeAttribute( "position", QString::number(d->position) ); writer->writeAttribute( "scale", QString::number(d->scale) ); writer->writeAttribute( "offset", QString::number(d->offset) ); writer->writeAttribute( "start", QString::number(d->start) ); writer->writeAttribute( "end", QString::number(d->end) ); writer->writeAttribute( "scalingFactor", QString::number(d->scalingFactor) ); writer->writeAttribute( "zeroOffset", QString::number(d->zeroOffset) ); writer->writeAttribute( "titleOffsetX", QString::number(d->titleOffsetX) ); writer->writeAttribute( "titleOffsetY", QString::number(d->titleOffsetY) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //label d->title->save( writer ); //line writer->writeStartElement( "line" ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeAttribute( "arrowType", QString::number(d->arrowType) ); writer->writeAttribute( "arrowPosition", QString::number(d->arrowPosition) ); writer->writeAttribute( "arrowSize", QString::number(d->arrowSize) ); writer->writeEndElement(); //major ticks writer->writeStartElement( "majorTicks" ); writer->writeAttribute( "direction", QString::number(d->majorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->majorTicksType) ); writer->writeAttribute( "number", QString::number(d->majorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->majorTicksSpacing) ); WRITE_COLUMN(d->majorTicksColumn, majorTicksColumn); writer->writeAttribute( "length", QString::number(d->majorTicksLength) ); WRITE_QPEN(d->majorTicksPen); writer->writeAttribute( "opacity", QString::number(d->majorTicksOpacity) ); writer->writeEndElement(); //minor ticks writer->writeStartElement( "minorTicks" ); writer->writeAttribute( "direction", QString::number(d->minorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->minorTicksType) ); writer->writeAttribute( "number", QString::number(d->minorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->minorTicksIncrement) ); WRITE_COLUMN(d->minorTicksColumn, minorTicksColumn); writer->writeAttribute( "length", QString::number(d->minorTicksLength) ); WRITE_QPEN(d->minorTicksPen); writer->writeAttribute( "opacity", QString::number(d->minorTicksOpacity) ); writer->writeEndElement(); //extra ticks //labels writer->writeStartElement( "labels" ); writer->writeAttribute( "position", QString::number(d->labelsPosition) ); writer->writeAttribute( "offset", QString::number(d->labelsOffset) ); writer->writeAttribute( "rotation", QString::number(d->labelsRotationAngle) ); writer->writeAttribute( "format", QString::number(d->labelsFormat) ); writer->writeAttribute( "precision", QString::number(d->labelsPrecision) ); writer->writeAttribute( "autoPrecision", QString::number(d->labelsAutoPrecision) ); writer->writeAttribute( "dateTimeFormat", d->labelsDateTimeFormat ); WRITE_QCOLOR(d->labelsColor); WRITE_QFONT(d->labelsFont); writer->writeAttribute( "prefix", d->labelsPrefix ); writer->writeAttribute( "suffix", d->labelsSuffix ); writer->writeAttribute( "opacity", QString::number(d->labelsOpacity) ); writer->writeEndElement(); //grid writer->writeStartElement( "majorGrid" ); WRITE_QPEN(d->majorGridPen); writer->writeAttribute( "opacity", QString::number(d->majorGridOpacity) ); writer->writeEndElement(); writer->writeStartElement( "minorGrid" ); WRITE_QPEN(d->minorGridPen); writer->writeAttribute( "opacity", QString::number(d->minorGridOpacity) ); writer->writeEndElement(); writer->writeEndElement(); // close "axis" section } //! Load from XML bool Axis::load(XmlStreamReader* reader, bool preview) { Q_D(Axis); 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() == "axis") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_INT_VALUE("autoScale", autoScale, bool); READ_INT_VALUE("orientation", orientation, Axis::AxisOrientation); READ_INT_VALUE("position", position, Axis::AxisPosition); READ_INT_VALUE("scale", scale, Axis::AxisScale); READ_DOUBLE_VALUE("offset", offset); READ_DOUBLE_VALUE("start", start); READ_DOUBLE_VALUE("end", end); READ_DOUBLE_VALUE("scalingFactor", scalingFactor); READ_DOUBLE_VALUE("zeroOffset", zeroOffset); READ_DOUBLE_VALUE("titleOffsetX", titleOffsetX); READ_DOUBLE_VALUE("titleOffsetY", titleOffsetY); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (reader->name() == "textLabel") { d->title->load(reader, preview); } else if (!preview && reader->name() == "line") { attribs = reader->attributes(); READ_QPEN(d->linePen); READ_DOUBLE_VALUE("opacity", lineOpacity); READ_INT_VALUE("arrowType", arrowType, Axis::ArrowType); READ_INT_VALUE("arrowPosition", arrowPosition, Axis::ArrowPosition); READ_DOUBLE_VALUE("arrowSize", arrowSize); } else if (!preview && reader->name() == "majorTicks") { attribs = reader->attributes(); READ_INT_VALUE("direction", majorTicksDirection, Axis::TicksDirection); READ_INT_VALUE("type", majorTicksType, Axis::TicksType); READ_INT_VALUE("number", majorTicksNumber, int); READ_DOUBLE_VALUE("increment", majorTicksSpacing); READ_COLUMN(majorTicksColumn); READ_DOUBLE_VALUE("length", majorTicksLength); READ_QPEN(d->majorTicksPen); READ_DOUBLE_VALUE("opacity", majorTicksOpacity); } else if (!preview && reader->name() == "minorTicks") { attribs = reader->attributes(); READ_INT_VALUE("direction", minorTicksDirection, Axis::TicksDirection); READ_INT_VALUE("type", minorTicksType, Axis::TicksType); READ_INT_VALUE("number", minorTicksNumber, int); READ_DOUBLE_VALUE("increment", minorTicksIncrement); READ_COLUMN(minorTicksColumn); READ_DOUBLE_VALUE("length", minorTicksLength); READ_QPEN(d->minorTicksPen); READ_DOUBLE_VALUE("opacity", minorTicksOpacity); } else if (!preview && reader->name() == "labels") { attribs = reader->attributes(); READ_INT_VALUE("position", labelsPosition, Axis::LabelsPosition); READ_DOUBLE_VALUE("offset", labelsOffset); READ_DOUBLE_VALUE("rotation", labelsRotationAngle); READ_INT_VALUE("format", labelsFormat, Axis::LabelsFormat); READ_INT_VALUE("precision", labelsPrecision, int); READ_INT_VALUE("autoPrecision", labelsAutoPrecision, bool); d->labelsDateTimeFormat = attribs.value("dateTimeFormat").toString(); READ_QCOLOR(d->labelsColor); READ_QFONT(d->labelsFont); //don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml) d->labelsPrefix = attribs.value("prefix").toString(); d->labelsSuffix = attribs.value("suffix").toString(); READ_DOUBLE_VALUE("opacity", labelsOpacity); } else if (!preview && reader->name() == "majorGrid") { attribs = reader->attributes(); READ_QPEN(d->majorGridPen); READ_DOUBLE_VALUE("opacity", majorGridOpacity); } else if (!preview && reader->name() == "minorGrid") { attribs = reader->attributes(); READ_QPEN(d->minorGridPen); READ_DOUBLE_VALUE("opacity", minorGridOpacity); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Axis::loadThemeConfig(const KConfig& config) { const KConfigGroup group = config.group("Axis"); //we don't want to show the major and minor grid lines for non-first horizontal/vertical axes //determine the index of the axis among other axes having the same orientation bool firstAxis = true; for (const auto* axis : parentAspect()->children()) { if (orientation() == axis->orientation()) { if (axis == this) { break; } else { firstAxis = false; break; } } } QPen p; // Tick label this->setLabelsColor(group.readEntry("LabelsFontColor", QColor(Qt::black))); this->setLabelsOpacity(group.readEntry("LabelsOpacity", 1.0)); //Line this->setLineOpacity(group.readEntry("LineOpacity", 1.0)); p.setColor(group.readEntry("LineColor", (QColor) this->linePen().color())); p.setStyle((Qt::PenStyle)group.readEntry("LineStyle",(int) this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); this->setLinePen(p); //Major grid if (firstAxis) p.setStyle((Qt::PenStyle)group.readEntry("MajorGridStyle", (int)Qt::NoPen)); else p.setStyle(Qt::NoPen); p.setColor(group.readEntry("MajorGridColor",QColor(Qt::gray))); - p.setWidthF(group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point))); + p.setWidthF(group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point))); this->setMajorGridPen(p); this->setMajorGridOpacity(group.readEntry("MajorGridOpacity", 1.0)); //Major ticks p.setStyle((Qt::PenStyle)group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine)); p.setColor(group.readEntry("MajorTicksColor", QColor(Qt::black))); - p.setWidthF(group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point))); + p.setWidthF(group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point))); this->setMajorTicksPen(p); this->setMajorTicksOpacity(group.readEntry("MajorTicksOpacity", 1.0)); //Minor grid if (firstAxis) p.setStyle((Qt::PenStyle)group.readEntry("MinorGridStyle", (int)Qt::NoPen)); else p.setStyle(Qt::NoPen); p.setColor(group.readEntry("MinorGridColor", QColor(Qt::gray))); - p.setWidthF(group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point))); + p.setWidthF(group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point))); this->setMinorGridOpacity(group.readEntry("MinorGridOpacity", 1.0)); this->setMinorGridPen(p); //Minor ticks p.setStyle((Qt::PenStyle)group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine)); p.setColor(group.readEntry("MinorTicksColor", QColor(Qt::black))); - p.setWidthF(group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point))); + p.setWidthF(group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point))); this->setMinorTicksPen(p); this->setMinorTicksOpacity(group.readEntry("MinorTicksOpacity", 1.0)); const QVector& childElements = children(AbstractAspect::ChildIndexFlag::IncludeHidden); for (auto* child : childElements) child->loadThemeConfig(config); } void Axis::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Axis"); // Tick label group.writeEntry("LabelsFontColor", (QColor) this->labelsColor()); group.writeEntry("LabelsOpacity", this->labelsOpacity()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineColor", (QColor) this->linePen().color()); group.writeEntry("LineStyle", (int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Major ticks group.writeEntry("MajorGridOpacity", this->majorGridOpacity()); group.writeEntry("MajorGridColor", (QColor) this->majorGridPen().color()); group.writeEntry("MajorGridStyle", (int) this->majorGridPen().style()); group.writeEntry("MajorGridWidth", this->majorGridPen().widthF()); group.writeEntry("MajorTicksColor", (QColor)this->majorTicksPen().color()); group.writeEntry("MajorTicksLineStyle", (int) this->majorTicksPen().style()); group.writeEntry("MajorTicksWidth", this->majorTicksPen().widthF()); group.writeEntry("MajorTicksOpacity", this->majorTicksOpacity()); group.writeEntry("MajorTicksType", (int)this->majorTicksType()); //Minor ticks group.writeEntry("MinorGridOpacity", this->minorGridOpacity()); group.writeEntry("MinorGridColor",(QColor) this->minorGridPen().color()); group.writeEntry("MinorGridStyle", (int) this->minorGridPen().style()); group.writeEntry("MinorGridWidth", this->minorGridPen().widthF()); group.writeEntry("MinorTicksColor", (QColor) this->minorTicksPen().color()); group.writeEntry("MinorTicksLineStyle",( int) this->minorTicksPen().style()); group.writeEntry("MinorTicksWidth", this->minorTicksPen().widthF()); group.writeEntry("MinorTicksOpacity", this->minorTicksOpacity()); group.writeEntry("MinorTicksType", (int)this->minorTicksType()); const QVector& childElements = children(AbstractAspect::ChildIndexFlag::IncludeHidden); childElements.at(0)->saveThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index 5df2020a6..542c565b8 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,4189 +1,4189 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2020 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017-2018 by Garvit Khatri (garvitdelhi@gmail.com) ***************************************************************************/ /*************************************************************************** * * * 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 "CartesianPlot.h" #include "CartesianPlotPrivate.h" #include "Axis.h" #include "XYCurve.h" #include "Histogram.h" #include "XYEquationCurve.h" #include "XYDataReductionCurve.h" #include "XYDifferentiationCurve.h" #include "XYIntegrationCurve.h" #include "XYInterpolationCurve.h" #include "XYSmoothCurve.h" #include "XYFitCurve.h" #include "XYFourierFilterCurve.h" #include "XYFourierTransformCurve.h" #include "XYConvolutionCurve.h" #include "XYCorrelationCurve.h" #include "backend/core/Project.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/cartesian/ReferenceLine.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/Image.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum. #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/widgets/ThemesWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name) : AbstractPlot(name, new CartesianPlotPrivate(this), AspectType::CartesianPlot) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd) : AbstractPlot(name, dd, AspectType::CartesianPlot) { init(); } CartesianPlot::~CartesianPlot() { if (m_menusInitialized) { delete addNewMenu; delete zoomMenu; delete themeMenu; } delete m_coordinateSystem; //don't need to delete objects added with addChild() //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } /*! initializes all member variables of \c CartesianPlot */ void CartesianPlot::init() { Q_D(CartesianPlot); d->cSystem = new CartesianCoordinateSystem(this); m_coordinateSystem = d->cSystem; d->rangeType = CartesianPlot::RangeFree; d->xRangeFormat = CartesianPlot::Numeric; d->yRangeFormat = CartesianPlot::Numeric; d->xRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->yRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->rangeFirstValues = 1000; d->rangeLastValues = 1000; d->autoScaleX = true; d->autoScaleY = true; d->xScale = ScaleLinear; d->yScale = ScaleLinear; d->xRangeBreakingEnabled = false; d->yRangeBreakingEnabled = false; //the following factor determines the size of the offset between the min/max points of the curves //and the coordinate system ranges, when doing auto scaling //Factor 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. //TODO: make this factor optional. //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option d->autoScaleOffsetFactor = 0.0f; m_plotArea = new PlotArea(name() + " plot area", this); addChildFast(m_plotArea); //Plot title m_title = new TextLabel(this->name() + QLatin1String("- ") + i18n("Title"), TextLabel::Type::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //offset between the plot area and the area defining the coordinate system, in scene units. - d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); - d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); - d->rightPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); - d->bottomPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); + d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); + d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); + d->rightPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); + d->bottomPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Unit::Centimeter); d->symmetricPadding = true; connect(this, &AbstractAspect::aspectAdded, this, &CartesianPlot::childAdded); connect(this, &AbstractAspect::aspectRemoved, this, &CartesianPlot::childRemoved); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); //theme is not set at this point, initialize the color palette with default colors this->setColorPalette(KConfig()); } /*! initializes all children of \c CartesianPlot and setups a default plot of type \c type with a plot title. */ void CartesianPlot::initDefault(Type type) { Q_D(CartesianPlot); switch (type) { case FourAxes: { d->xMin = 0.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; //Axes Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); QPen pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("x axis 2", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisTop); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMinorGridPen(pen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("y axis 2", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisRight); axis->setStart(0); axis->setEnd(1); axis->setOffset(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMinorGridPen(pen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxes: { d->xMin = 0.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); break; } case TwoAxesCentered: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; - d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); - d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); + d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); + d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxesCenteredZero: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; - d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); - d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); + d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); + d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } } d->xMinPrev = d->xMin; d->xMaxPrev = d->xMax; d->yMinPrev = d->yMin; d->yMaxPrev = d->yMax; //Geometry, specify the plot rect in scene coordinates. //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system - float x = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); - float y = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); - float w = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); - float h = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); + float x = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Centimeter); + float y = Worksheet::convertToSceneUnits(2, Worksheet::Unit::Centimeter); + float w = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Centimeter); + float h = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Centimeter); //all plot children are initialized -> set the geometry of the plot in scene coordinates. d->rect = QRectF(x,y,w,h); d->retransform(); } void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this); addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), this); // no icons yet addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this); addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiation"), this); addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integration"), this); addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolation"), this); addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this); addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Fit"), this); addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this); addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this); addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("(De-)Convolution"), this); addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("Auto-/Cross-Correlation"), this); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), this); if (children().size()>0) addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal Axis"), this); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical Axis"), this); addTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), this); addImageAction = new QAction(QIcon::fromTheme("viewimage"), i18n("Image"), this); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), this); addReferenceLineAction = new QAction(QIcon::fromTheme("draw-line"), i18n("Reference Line"), this); connect(addCurveAction, &QAction::triggered, this, &CartesianPlot::addCurve); connect(addHistogramAction,&QAction::triggered, this, &CartesianPlot::addHistogram); connect(addEquationCurveAction, &QAction::triggered, this, &CartesianPlot::addEquationCurve); connect(addDataReductionCurveAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationCurveAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationCurveAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationCurveAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothCurveAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addFitCurveAction, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); connect(addFourierTransformCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve); connect(addConvolutionCurveAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationCurveAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); connect(addLegendAction, &QAction::triggered, this, static_cast(&CartesianPlot::addLegend)); connect(addHorizontalAxisAction, &QAction::triggered, this, &CartesianPlot::addHorizontalAxis); connect(addVerticalAxisAction, &QAction::triggered, this, &CartesianPlot::addVerticalAxis); connect(addTextLabelAction, &QAction::triggered, this, &CartesianPlot::addTextLabel); connect(addImageAction, &QAction::triggered, this, &CartesianPlot::addImage); connect(addCustomPointAction, &QAction::triggered, this, &CartesianPlot::addCustomPoint); connect(addReferenceLineAction, &QAction::triggered, this, &CartesianPlot::addReferenceLine); //Analysis menu actions // addDataOperationAction = new QAction(i18n("Data Operation"), this); addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this); addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiate"), this); addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integrate"), this); addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this); addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this); addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Convolute/Deconvolute"), this); addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Auto-/Cross-Correlation"), this); QAction* fitAction = new QAction(i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); addFitAction.append(fitAction); fitAction = new QAction(i18n("Power"), this); fitAction->setData(PlotDataDialog::FitPower); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 1)"), this); fitAction->setData(PlotDataDialog::FitExp1); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 2)"), this); fitAction->setData(PlotDataDialog::FitExp2); addFitAction.append(fitAction); fitAction = new QAction(i18n("Inverse exponential"), this); fitAction->setData(PlotDataDialog::FitInvExp); addFitAction.append(fitAction); fitAction = new QAction(i18n("Gauss"), this); fitAction->setData(PlotDataDialog::FitGauss); addFitAction.append(fitAction); fitAction = new QAction(i18n("Cauchy-Lorentz"), this); fitAction->setData(PlotDataDialog::FitCauchyLorentz); addFitAction.append(fitAction); fitAction = new QAction(i18n("Arc Tangent"), this); fitAction->setData(PlotDataDialog::FitTan); addFitAction.append(fitAction); fitAction = new QAction(i18n("Hyperbolic Tangent"), this); fitAction->setData(PlotDataDialog::FitTanh); addFitAction.append(fitAction); fitAction = new QAction(i18n("Error Function"), this); fitAction->setData(PlotDataDialog::FitErrFunc); addFitAction.append(fitAction); fitAction = new QAction(i18n("Custom"), this); fitAction->setData(PlotDataDialog::FitCustom); addFitAction.append(fitAction); addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this); addFourierTransformAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this); connect(addDataReductionAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addConvolutionAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); for (const auto& action : addFitAction) connect(action, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); connect(addFourierTransformAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve); //zoom/navigate actions scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("Auto Scale"), this); scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("Auto Scale X"), this); scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("Auto Scale Y"), this); zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this); zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this); zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("Zoom In X"), this); zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("Zoom Out X"), this); zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("Zoom In Y"), this); zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("Zoom Out Y"), this); shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left X"), this); shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right X"), this); shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Up Y"), this); shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Down Y"), this); connect(scaleAutoAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered); connect(scaleAutoXAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered); connect(scaleAutoYAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered); connect(zoomInAction, &QAction::triggered, this, &CartesianPlot::zoomIn); connect(zoomOutAction, &QAction::triggered, this, &CartesianPlot::zoomOut); connect(zoomInXAction, &QAction::triggered, this, &CartesianPlot::zoomInX); connect(zoomOutXAction, &QAction::triggered, this, &CartesianPlot::zoomOutX); connect(zoomInYAction, &QAction::triggered, this, &CartesianPlot::zoomInY); connect(zoomOutYAction, &QAction::triggered, this, &CartesianPlot::zoomOutY); connect(shiftLeftXAction, &QAction::triggered, this, &CartesianPlot::shiftLeftX); connect(shiftRightXAction, &QAction::triggered, this, &CartesianPlot::shiftRightX); connect(shiftUpYAction, &QAction::triggered, this, &CartesianPlot::shiftUpY); connect(shiftDownYAction, &QAction::triggered, this, &CartesianPlot::shiftDownY); //visibility action visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlot::visibilityChanged); } void CartesianPlot::initMenus() { initActions(); addNewMenu = new QMenu(i18n("Add New")); addNewMenu->setIcon(QIcon::fromTheme("list-add")); addNewMenu->addAction(addCurveAction); addNewMenu->addAction(addHistogramAction); addNewMenu->addAction(addEquationCurveAction); addNewMenu->addSeparator(); addNewAnalysisMenu = new QMenu(i18n("Analysis Curve")); addNewAnalysisMenu->addAction(addFitCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addDifferentiationCurveAction); addNewAnalysisMenu->addAction(addIntegrationCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addInterpolationCurveAction); addNewAnalysisMenu->addAction(addSmoothCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addFourierFilterCurveAction); addNewAnalysisMenu->addAction(addFourierTransformCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addConvolutionCurveAction); addNewAnalysisMenu->addAction(addCorrelationCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addDataReductionCurveAction); addNewMenu->addMenu(addNewAnalysisMenu); addNewMenu->addSeparator(); addNewMenu->addAction(addLegendAction); addNewMenu->addSeparator(); addNewMenu->addAction(addHorizontalAxisAction); addNewMenu->addAction(addVerticalAxisAction); addNewMenu->addSeparator(); addNewMenu->addAction(addTextLabelAction); addNewMenu->addAction(addImageAction); addNewMenu->addSeparator(); addNewMenu->addAction(addCustomPointAction); addNewMenu->addAction(addReferenceLineAction); zoomMenu = new QMenu(i18n("Zoom/Navigate")); zoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); zoomMenu->addAction(scaleAutoAction); zoomMenu->addAction(scaleAutoXAction); zoomMenu->addAction(scaleAutoYAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInAction); zoomMenu->addAction(zoomOutAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInXAction); zoomMenu->addAction(zoomOutXAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInYAction); zoomMenu->addAction(zoomOutYAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftLeftXAction); zoomMenu->addAction(shiftRightXAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftUpYAction); zoomMenu->addAction(shiftDownYAction); // Data manipulation menu // QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); // dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); // dataManipulationMenu->addAction(addDataOperationAction); // dataManipulationMenu->addAction(addDataReductionAction); // Data fit menu QMenu* dataFitMenu = new QMenu(i18n("Fit")); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); dataFitMenu->addAction(addFitAction.at(2)); dataFitMenu->addAction(addFitAction.at(3)); dataFitMenu->addAction(addFitAction.at(4)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(5)); dataFitMenu->addAction(addFitAction.at(6)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(7)); dataFitMenu->addAction(addFitAction.at(8)); dataFitMenu->addAction(addFitAction.at(9)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(10)); //analysis menu dataAnalysisMenu = new QMenu(i18n("Analysis")); dataAnalysisMenu->addMenu(dataFitMenu); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addDifferentiationAction); dataAnalysisMenu->addAction(addIntegrationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addInterpolationAction); dataAnalysisMenu->addAction(addSmoothAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addFourierFilterAction); dataAnalysisMenu->addAction(addFourierTransformAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addConvolutionAction); dataAnalysisMenu->addAction(addCorrelationAction); dataAnalysisMenu->addSeparator(); // dataAnalysisMenu->insertMenu(nullptr, dataManipulationMenu); dataAnalysisMenu->addAction(addDataReductionAction); //themes menu themeMenu = new QMenu(i18n("Apply Theme")); themeMenu->setIcon(QIcon::fromTheme("color-management")); auto* themeWidget = new ThemesWidget(nullptr); connect(themeWidget, &ThemesWidget::themeSelected, this, &CartesianPlot::loadTheme); connect(themeWidget, &ThemesWidget::themeSelected, themeMenu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(themeWidget); themeMenu->addAction(widgetAction); m_menusInitialized = true; } QMenu* CartesianPlot::createContextMenu() { if (!m_menusInitialized) initMenus(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); menu->insertMenu(firstAction, addNewMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, zoomMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, themeMenu); menu->insertSeparator(firstAction); visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } QMenu* CartesianPlot::analysisMenu() { if (!m_menusInitialized) initMenus(); return dataAnalysisMenu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlot::icon() const { return QIcon::fromTheme("office-chart-line"); } QVector CartesianPlot::dependsOn() const { //aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices) QVector aspects; for (const auto* curve : children()) { if (curve->xColumn() && dynamic_cast(curve->xColumn()->parentAspect()) ) aspects << curve->xColumn()->parentAspect(); if (curve->yColumn() && dynamic_cast(curve->yColumn()->parentAspect()) ) aspects << curve->yColumn()->parentAspect(); } return aspects; } void CartesianPlot::navigate(CartesianPlot::NavigationOperation op) { Q_D(CartesianPlot); if (op == ScaleAuto) { if (d->curvesXMinMaxIsDirty || d->curvesYMinMaxIsDirty || !autoScaleX() || !autoScaleY()) { d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; } scaleAuto(); } else if (op == ScaleAutoX) setAutoScaleX(true); else if (op == ScaleAutoY) setAutoScaleY(true); else if (op == ZoomIn) zoomIn(); else if (op == ZoomOut) zoomOut(); else if (op == ZoomInX) zoomInX(); else if (op == ZoomOutX) zoomOutX(); else if (op == ZoomInY) zoomInY(); else if (op == ZoomOutY) zoomOutY(); else if (op == ShiftLeftX) shiftLeftX(); else if (op == ShiftRightX) shiftRightX(); else if (op == ShiftUpY) shiftUpY(); else if (op == ShiftDownY) shiftDownY(); } void CartesianPlot::setSuppressDataChangedSignal(bool value) { Q_D(CartesianPlot); d->suppressRetransform = value; } void CartesianPlot::processDropEvent(QDropEvent* event) { PERFTRACE("CartesianPlot::processDropEvent"); const QMimeData* mimeData = event->mimeData(); if (!mimeData) return; //deserialize the mime data to the vector of aspect pointers QByteArray data = mimeData->data(QLatin1String("labplot-dnd")); QVector vec; QDataStream stream(&data, QIODevice::ReadOnly); stream >> vec; QVector columns; for (auto a : vec) { auto* aspect = (AbstractAspect*)a; auto* column = dynamic_cast(aspect); if (column) columns << column; } //return if there are no columns being dropped. //TODO: extend this later when we allow to drag&drop plots, etc. if (columns.isEmpty()) return; //determine the first column with "x plot designation" as the x-data column for all curves to be created const AbstractColumn* xColumn = nullptr; for (const auto* column : columns) { if (column->plotDesignation() == AbstractColumn::PlotDesignation::X) { xColumn = column; break; } } //if no column with "x plot designation" is available, use the x-data column of the first curve in the plot, if (xColumn == nullptr) { QVector curves = children(); if (!curves.isEmpty()) xColumn = curves.at(0)->xColumn(); } //use the first dropped column if no column with "x plot designation" nor curves are available if (xColumn == nullptr) xColumn = columns.at(0); //create curves bool curvesAdded = false; for (const auto* column : columns) { if (column == xColumn) continue; XYCurve* curve = new XYCurve(column->name()); curve->suppressRetransform(true); //suppress retransform, all curved will be recalculated at the end curve->setXColumn(xColumn); curve->setYColumn(column); addChild(curve); curve->suppressRetransform(false); curvesAdded = true; } if (curvesAdded) dataChanged(); } bool CartesianPlot::isPanningActive() const { Q_D(const CartesianPlot); return d->panningStarted; } bool CartesianPlot::isHovered() const { Q_D(const CartesianPlot); return d->m_hovered; } bool CartesianPlot::isPrinted() const { Q_D(const CartesianPlot); return d->m_printing; } bool CartesianPlot::isSelected() const { Q_D(const CartesianPlot); return d->isSelected(); } //############################################################################## //################################ getter methods ############################ //############################################################################## BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormat) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormat) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMax, xMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, xScale, xScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMax, yMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, yScale, yScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks) CLASS_SHARED_D_READER_IMPL(CartesianPlot, QPen, cursorPen, cursorPen); CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor0Enable, cursor0Enable); CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor1Enable, cursor1Enable); CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme) /*! returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding) in plot's coordinates */ QRectF CartesianPlot::dataRect() const { Q_D(const CartesianPlot); return d->dataRect; } CartesianPlot::MouseMode CartesianPlot::mouseMode() const { Q_D(const CartesianPlot); return d->mouseMode; } const QString& CartesianPlot::xRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->xRangeDateTimeFormat; } const QString& CartesianPlot::yRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->yRangeDateTimeFormat; } //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## /*! set the rectangular, defined in scene coordinates */ class CartesianPlotSetRectCmd : public QUndoCommand { public: CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) { setText(i18n("%1: change geometry rect", m_private->name())); }; void redo() override { // const double horizontalRatio = m_rect.width() / m_private->rect.width(); // const double verticalRatio = m_rect.height() / m_private->rect.height(); qSwap(m_private->rect, m_rect); // m_private->q->handleResize(horizontalRatio, verticalRatio, false); m_private->retransform(); emit m_private->q->rectChanged(m_private->rect); }; void undo() override { redo(); } private: CartesianPlotPrivate* m_private; QRectF m_rect; }; void CartesianPlot::setRect(const QRectF& rect) { Q_D(CartesianPlot); if (rect != d->rect) exec(new CartesianPlotSetRectCmd(d, rect)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged); void CartesianPlot::setRangeType(RangeType type) { Q_D(CartesianPlot); if (type != d->rangeType) exec(new CartesianPlotSetRangeTypeCmd(d, type, ki18n("%1: set range type"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeFormat, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormatChanged); void CartesianPlot::setXRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->xRangeFormat) exec(new CartesianPlotSetXRangeFormatCmd(d, format, ki18n("%1: set x-range format"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeFormat, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormatChanged); void CartesianPlot::setYRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->yRangeFormat) exec(new CartesianPlotSetYRangeFormatCmd(d, format, ki18n("%1: set y-range format"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged); void CartesianPlot::setRangeLastValues(int values) { Q_D(CartesianPlot); if (values != d->rangeLastValues) exec(new CartesianPlotSetRangeLastValuesCmd(d, values, ki18n("%1: set range"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged); void CartesianPlot::setRangeFirstValues(int values) { Q_D(CartesianPlot); if (values != d->rangeFirstValues) exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, ki18n("%1: set range"))); } class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleX; if (m_autoScale) { m_minOld = m_private->xMin; m_maxOld = m_private->xMax; m_private->q->scaleAutoX(); } m_private->autoScaleX = m_autoScale; emit m_private->q->xAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->xMin = m_minOld; m_private->xMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleX = m_autoScaleOld; emit m_private->q->xAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleX(bool autoScaleX) { Q_D(CartesianPlot); if (autoScaleX != d->autoScaleX) exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales) void CartesianPlot::setXMin(double xMin) { Q_D(CartesianPlot); if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) { d->curvesYMinMaxIsDirty = true; exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x"))); if (d->autoScaleY) scaleAutoY(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales) void CartesianPlot::setXMax(double xMax) { Q_D(CartesianPlot); if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) { d->curvesYMinMaxIsDirty = true; exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x"))); if (d->autoScaleY) scaleAutoY(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales) void CartesianPlot::setXScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->xScale) exec(new CartesianPlotSetXScaleCmd(d, scale, ki18n("%1: set x scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales) void CartesianPlot::setXRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->xRangeBreakingEnabled) exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, ki18n("%1: x-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales) void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) { Q_D(CartesianPlot); exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, ki18n("%1: x-range breaks changed"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleY; if (m_autoScale) { m_minOld = m_private->yMin; m_maxOld = m_private->yMax; m_private->q->scaleAutoY(); } m_private->autoScaleY = m_autoScale; emit m_private->q->yAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->yMin = m_minOld; m_private->yMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleY = m_autoScaleOld; emit m_private->q->yAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleY(bool autoScaleY) { Q_D(CartesianPlot); if (autoScaleY != d->autoScaleY) exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, double, yMin, retransformScales) void CartesianPlot::setYMin(double yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) { d->curvesXMinMaxIsDirty = true; exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y"))); if (d->autoScaleX) scaleAutoX(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales) void CartesianPlot::setYMax(double yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) { d->curvesXMinMaxIsDirty = true; exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%1: set max y"))); if (d->autoScaleX) scaleAutoX(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales) void CartesianPlot::setYScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->yScale) exec(new CartesianPlotSetYScaleCmd(d, scale, ki18n("%1: set y scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales) void CartesianPlot::setYRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->yRangeBreakingEnabled) exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, ki18n("%1: y-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales) void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) { Q_D(CartesianPlot); exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, ki18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursorPen, QPen, cursorPen, update) void CartesianPlot::setCursorPen(const QPen &pen) { Q_D(CartesianPlot); if (pen != d->cursorPen) exec(new CartesianPlotSetCursorPenCmd(d, pen, ki18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor0Enable, bool, cursor0Enable, updateCursor) void CartesianPlot::setCursor0Enable(const bool &enable) { Q_D(CartesianPlot); if (enable != d->cursor0Enable) { if (std::isnan(d->cursor0Pos.x())) { // if never set, set initial position d->cursor0Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x()); mousePressCursorModeSignal(0, d->cursor0Pos); // simulate mousePress to update values in the cursor dock } exec(new CartesianPlotSetCursor0EnableCmd(d, enable, ki18n("%1: Cursor0 enable"))); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor1Enable, bool, cursor1Enable, updateCursor) void CartesianPlot::setCursor1Enable(const bool &enable) { Q_D(CartesianPlot); if (enable != d->cursor1Enable) { if (std::isnan(d->cursor1Pos.x())) { // if never set, set initial position d->cursor1Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x()); mousePressCursorModeSignal(1, d->cursor1Pos); // simulate mousePress to update values in the cursor dock } exec(new CartesianPlotSetCursor1EnableCmd(d, enable, ki18n("%1: Cursor1 enable"))); } } STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme) void CartesianPlot::setTheme(const QString& theme) { Q_D(CartesianPlot); if (theme != d->theme) { QString info; if (!theme.isEmpty()) info = i18n("%1: load theme %2", name(), theme); else info = i18n("%1: load default theme", name()); beginMacro(info); exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", Axis::AxisHorizontal); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(xMin()); axis->setEnd(xMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addVerticalAxis() { Axis* axis = new Axis("y-axis", Axis::AxisVertical); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(yMin()); axis->setEnd(yMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addCurve() { addChild(new XYCurve("xy-curve")); } void CartesianPlot::addEquationCurve() { addChild(new XYEquationCurve("f(x)")); } void CartesianPlot::addHistogram() { addChild(new Histogram("Histogram")); } /*! * returns the first selected XYCurve in the plot */ const XYCurve* CartesianPlot::currentCurve() const { for (const auto* curve : this->children()) { if (curve->graphicsItem()->isSelected()) return curve; } return nullptr; } void CartesianPlot::addDataReductionCurve() { XYDataReductionCurve* curve = new XYDataReductionCurve("Data reduction"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: reduce '%2'", name(), curCurve->name()) ); curve->setName( i18n("Reduction of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->dataReductionDataChanged(curve->dataReductionData()); } else { beginMacro(i18n("%1: add data reduction curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addDifferentiationCurve() { XYDifferentiationCurve* curve = new XYDifferentiationCurve("Differentiation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Derivative of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->differentiationDataChanged(curve->differentiationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addIntegrationCurve() { XYIntegrationCurve* curve = new XYIntegrationCurve("Integration"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Integral of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->integrationDataChanged(curve->integrationData()); } else { beginMacro(i18n("%1: add integration curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addInterpolationCurve() { XYInterpolationCurve* curve = new XYInterpolationCurve("Interpolation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Interpolation of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); curve->recalculate(); this->addChild(curve); emit curve->interpolationDataChanged(curve->interpolationData()); } else { beginMacro(i18n("%1: add interpolation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addSmoothCurve() { XYSmoothCurve* curve = new XYSmoothCurve("Smooth"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) ); curve->setName( i18n("Smoothing of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->smoothDataChanged(curve->smoothData()); } else { beginMacro(i18n("%1: add smoothing curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFitCurve() { DEBUG("CartesianPlot::addFitCurve()"); XYFitCurve* curve = new XYFitCurve("fit"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fit to '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); //set the fit model category and type const auto* action = qobject_cast(QObject::sender()); PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); curve->initFitData(type); curve->initStartValues(curCurve); //fit with weights for y if the curve has error bars for y if (curCurve->yErrorType() == XYCurve::SymmetricError && curCurve->yErrorPlusColumn()) { XYFitCurve::FitData fitData = curve->fitData(); fitData.yWeightsType = nsl_fit_weight_instrumental; curve->setFitData(fitData); curve->setYErrorColumn(curCurve->yErrorPlusColumn()); } curve->recalculate(); //add the child after the fit was calculated so the dock widgets gets the fit results //and call retransform() after this to calculate and to paint the data points of the fit-curve this->addChild(curve); curve->retransform(); } else { beginMacro(i18n("%1: add fit curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierFilterCurve() { XYFourierFilterCurve* curve = new XYFourierFilterCurve("Fourier filter"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); } else { beginMacro(i18n("%1: add Fourier filter curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierTransformCurve() { XYFourierTransformCurve* curve = new XYFourierTransformCurve("Fourier transform"); this->addChild(curve); } void CartesianPlot::addConvolutionCurve() { XYConvolutionCurve* curve = new XYConvolutionCurve("Convolution"); this->addChild(curve); } void CartesianPlot::addCorrelationCurve() { XYCorrelationCurve* curve = new XYCorrelationCurve("Auto-/Cross-Correlation"); this->addChild(curve); } /*! * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser. */ void CartesianPlot::addLegend(CartesianPlotLegend* legend) { m_legend = legend; this->addChild(legend); } void CartesianPlot::addLegend() { //don't do anything if there's already a legend if (m_legend) return; m_legend = new CartesianPlotLegend(this, "legend"); this->addChild(m_legend); m_legend->retransform(); //only one legend is allowed -> disable the action if (m_menusInitialized) addLegendAction->setEnabled(false); } void CartesianPlot::addTextLabel() { TextLabel* label = new TextLabel("text label"); this->addChild(label); label->setParentGraphicsItem(graphicsItem()); } void CartesianPlot::addImage() { Image* image = new Image("image"); this->addChild(image); } void CartesianPlot::addCustomPoint() { CustomPoint* point = new CustomPoint(this, "custom point"); this->addChild(point); point->retransform(); } void CartesianPlot::addReferenceLine() { ReferenceLine* line = new ReferenceLine(this, "reference line"); this->addChild(line); line->retransform(); } int CartesianPlot::curveCount(){ return children().length(); } const XYCurve* CartesianPlot::getCurve(int index){ return children().at(index); } double CartesianPlot::cursorPos(int cursorNumber) { Q_D(CartesianPlot); if (cursorNumber == 0) return d->cursor0Pos.x(); else return d->cursor1Pos.x(); } void CartesianPlot::childAdded(const AbstractAspect* child) { Q_D(CartesianPlot); const auto* curve = qobject_cast(child); if (curve) { connect(curve, &XYCurve::dataChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xDataChanged, this, &CartesianPlot::xDataChanged); connect(curve, &XYCurve::xErrorTypeChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xErrorPlusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xErrorMinusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::yDataChanged, this, &CartesianPlot::yDataChanged); connect(curve, &XYCurve::yErrorTypeChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::yErrorPlusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::yErrorMinusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, static_cast(&XYCurve::visibilityChanged), this, &CartesianPlot::curveVisibilityChanged); //update the legend on changes of the name, line and symbol styles connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::curveNameChanged); connect(curve, &XYCurve::lineTypeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::linePenChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::linePenChanged, this, static_cast(&CartesianPlot::curveLinePenChanged)); connect(curve, &XYCurve::lineOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsStyleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsSizeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsRotationAngleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsBrushChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsPenChanged, this, &CartesianPlot::updateLegend); updateLegend(); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; //in case the first curve is added, check whether we start plotting datetime data if (children().size() == 1) { const auto* col = dynamic_cast(curve->xColumn()); if (col) { if (col->columnMode() == AbstractColumn::ColumnMode::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all horizontal axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisHorizontal) { auto* filter = static_cast(col->outputFilter()); d->xRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->xRangeDateTimeFormat); axis->setUndoAware(true); } } } } col = dynamic_cast(curve->yColumn()); if (col) { if (col->columnMode() == AbstractColumn::ColumnMode::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all vertical axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisVertical) { auto* filter = static_cast(col->outputFilter()); d->yRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->yRangeDateTimeFormat); axis->setUndoAware(true); } } } } } emit curveAdded(curve); } else { const auto* hist = qobject_cast(child); if (hist) { connect(hist, &Histogram::dataChanged, this, &CartesianPlot::dataChanged); connect(hist, &Histogram::visibilityChanged, this, &CartesianPlot::curveVisibilityChanged); updateLegend(); } // if an element is hovered, the curves which are handled manually in this class // must be unhovered const auto* element = static_cast(child); connect(element, &WorksheetElement::hovered, this, &CartesianPlot::childHovered); } if (!isLoading()) { //if a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty()) { const auto* elem = dynamic_cast(child); if (elem) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(elem)->loadThemeConfig(config); } } else { //no theme is available, apply the default colors for curves only, s.a. XYCurve::loadThemeConfig() const auto* curve = dynamic_cast(child); if (curve) { int index = indexOfChild(curve); QColor themeColor; if (index < m_themeColorPalette.size()) themeColor = m_themeColorPalette.at(index); else { if (m_themeColorPalette.size()) themeColor = m_themeColorPalette.last(); } auto* c = const_cast(curve); //Line QPen p = curve->linePen(); p.setColor(themeColor); c->setLinePen(p); //Drop line p = curve->dropLinePen(); p.setColor(themeColor); c->setDropLinePen(p); //Symbol QBrush brush = c->symbolsBrush(); brush.setColor(themeColor); c->setSymbolsBrush(brush); p = c->symbolsPen(); p.setColor(themeColor); c->setSymbolsPen(p); //Filling c->setFillingFirstColor(themeColor); //Error bars p.setColor(themeColor); c->setErrorBarsPen(p); } } } } void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); if (m_legend == child) { if (m_menusInitialized) addLegendAction->setEnabled(true); m_legend = nullptr; } else { const auto* curve = qobject_cast(child); if (curve) { updateLegend(); emit curveRemoved(curve); } } } /*! * \brief CartesianPlot::childHovered * Unhover all curves, when another child is hovered. The hover handling for the curves is done in their parent (CartesianPlot), * because the hover should set when the curve is hovered and not just the bounding rect (for more see hoverMoveEvent) */ void CartesianPlot::childHovered() { Q_D(CartesianPlot); bool curveSender = dynamic_cast(QObject::sender()) != nullptr; if (!d->isSelected()) { if (d->m_hovered) d->m_hovered = false; d->update(); } if (!curveSender) { for (auto curve: children()) curve->setHover(false); } } void CartesianPlot::updateLegend() { if (m_legend) m_legend->retransform(); } /*! called when in one of the curves the data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::dataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; bool updated = false; if (d->autoScaleX && d->autoScaleY) updated = scaleAuto(); else if (d->autoScaleX) updated = scaleAutoX(); else if (d->autoScaleY) updated = scaleAutoY(); if (!updated || !QObject::sender()) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); else { //no sender available, the function was called directly in the file filter (live data source got new data) //or in Project::load() -> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive for (auto* c : children()) { c->recalcLogicalPoints(); c->retransform(); } } } } } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::xDataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; bool updated = false; if (d->autoScaleX) updated = this->scaleAutoX(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); if (curve) { const AbstractColumn* col = curve->xColumn(); if (col->columnMode() == AbstractColumn::ColumnMode::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } } emit curveDataChanged(dynamic_cast(QObject::sender())); } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::yDataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; bool updated = false; if (d->autoScaleY) updated = this->scaleAutoY(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); if (curve) { const AbstractColumn* col = curve->yColumn(); if (col->columnMode() == AbstractColumn::ColumnMode::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } } emit curveDataChanged(dynamic_cast(QObject::sender())); } void CartesianPlot::curveVisibilityChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; updateLegend(); if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); emit curveVisibilityChangedSignal(); } void CartesianPlot::curveLinePenChanged(QPen pen) { const auto* curve = qobject_cast(QObject::sender()); emit curveLinePenChanged(pen, curve->name()); } void CartesianPlot::setMouseMode(const MouseMode mouseMode) { Q_D(CartesianPlot); d->mouseMode = mouseMode; d->setHandlesChildEvents(mouseMode != CartesianPlot::SelectionMode); QList items = d->childItems(); if (d->mouseMode == CartesianPlot::SelectionMode) { d->setZoomSelectionBandShow(false); d->setCursor(Qt::ArrowCursor); for (auto* item : items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, false); } else { for (auto* item : items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, true); } //when doing zoom selection, prevent the graphics item from being movable //if it's currently movable (no worksheet layout available) const auto* worksheet = dynamic_cast(parentAspect()); if (worksheet) { if (mouseMode == CartesianPlot::SelectionMode) { - if (worksheet->layout() != Worksheet::NoLayout) + if (worksheet->layout() != Worksheet::Layout::NoLayout) graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); else graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); } else //zoom m_selection graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } emit mouseModeChanged(mouseMode); } void CartesianPlot::setLocked(bool locked) { Q_D(CartesianPlot); d->locked = locked; } bool CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { calculateCurvesXMinMax(false); //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; if (!curve->dataColumn()) continue; const double min = curve->getXMinimum(); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->getXMaximum(); if (max > d->curvesXMax) d->curvesXMax = max; } // do it at the end, because it must be from the real min/max values double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->yErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { // must be done, because retransformScales uses xMin/xMax if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) d->xMin = d->curvesXMin; if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) d->xMax = d->curvesXMax; // When the previous scale is completely different. The mapTo functions scale with wrong values. To prevent // this a rescale must be done. // The errorBarsCapSize is in Scene coordinates. So this value must be transformed into a logical value. Due // to nonlinear scalings it cannot only be multiplied with a scaling factor and depends on the position of the // column value // dirty hack: call setIsLoading(true) to suppress the call of retransform() in retransformScales() since a // retransform is already done at the end of this function setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); // Problem is, when the scaling is not linear (for example log(x)) and the minimum is 0. In this // case mapLogicalToScene returns (0,0) which is smaller than the curves minimum if (point.x() < d->curvesXMin) d->curvesXMin = point.x(); point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.x() > d->curvesXMax) d->curvesXMax = point.x(); } d->curvesYMinMaxIsDirty = true; d->curvesXMinMaxIsDirty = false; } bool update = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; update = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; update = true; } if (update) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } d->retransformScales(); } return update; } bool CartesianPlot::scaleAutoY() { Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { calculateCurvesYMinMax(false); // loop over all curves //loop over all histograms and determine the maximum y-value for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; const double min = curve->getYMinimum(); if (d->curvesYMin > min) d->curvesYMin = min; const double max = curve->getYMaximum(); if (max > d->curvesYMax) d->curvesYMax = max; } // do it at the end, because it must be from the real min/max values double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->xErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) d->yMin = d->curvesYMin; if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) d->yMax = d->curvesYMax; setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() < d->curvesYMin) d->curvesYMin = point.y(); point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() > d->curvesYMax) d->curvesYMax = point.y(); } d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = false; } bool update = false; if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; update = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; update = true; } if (update) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } d->retransformScales(); } return update; } void CartesianPlot::scaleAutoTriggered() { QAction* action = dynamic_cast(QObject::sender()); if (!action) return; if (action == scaleAutoAction) scaleAuto(); else if (action == scaleAutoXAction) setAutoScaleX(true); else if (action == scaleAutoYAction) setAutoScaleY(true); } bool CartesianPlot::scaleAuto() { DEBUG("CartesianPlot::scaleAuto()"); Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { calculateCurvesXMinMax(); double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->yErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) d->xMin = d->curvesXMin; if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) d->xMax = d->curvesXMax; setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.x() < d->curvesXMin) d->curvesXMin = point.x(); point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.x() > d->curvesXMax) d->curvesXMax = point.x(); } d->curvesXMinMaxIsDirty = false; } if (d->curvesYMinMaxIsDirty) { calculateCurvesYMinMax(); double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->xErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) d->yMin = d->curvesYMin; if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) d->yMax = d->curvesYMax; setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() < d->curvesYMin) d->curvesYMin = point.y(); point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() > d->curvesYMax) d->curvesYMax = point.y(); } d->curvesYMinMaxIsDirty = false; } bool updateX = false; bool updateY = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; updateX = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; updateX = true; } if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; updateY = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; updateY = true; } DEBUG(" xmin/xmax = " << d->xMin << '/' << d->xMax << ", ymin/ymax = " << d->yMin << '/' << d->yMax); if (updateX || updateY) { if (updateX) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } setAutoScaleX(true); } if (updateY) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } setAutoScaleY(true); } d->retransformScales(); } return (updateX || updateY); } /*! * Calculates and sets curves y min and max. This function does not respect the range * of the y axis */ void CartesianPlot::calculateCurvesXMinMax(bool completeRange) { Q_D(CartesianPlot); d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum x-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; auto* xColumn = curve->xColumn(); if (!xColumn) continue; double min = d->curvesXMin; double max = d->curvesXMax; int start =0; int end = 0; if (d->rangeType == CartesianPlot::RangeFree && curve->yColumn() && !completeRange) { curve->yColumn()->indicesMinMax(yMin(), yMax(), start, end); if (end < curve->yColumn()->rowCount()) end ++; } else { switch (d->rangeType) { case CartesianPlot::RangeFree: start = 0; end = xColumn->rowCount(); break; case CartesianPlot::RangeLast: start = xColumn->rowCount() - d->rangeLastValues; end = xColumn->rowCount(); break; case CartesianPlot::RangeFirst: start = 0; end = d->rangeFirstValues; break; } } curve->minMaxX(start, end, min, max, true); if (min < d->curvesXMin) d->curvesXMin = min; if (max > d->curvesXMax) d->curvesXMax = max; } //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; if (!curve->dataColumn()) continue; const double min = curve->getXMinimum(); if (d->curvesXMin > min) d->curvesXMin = min; const double max = curve->getXMaximum(); if (max > d->curvesXMax) d->curvesXMax = max; } } /*! * Calculates and sets curves y min and max. This function does not respect the range * of the x axis */ void CartesianPlot::calculateCurvesYMinMax(bool completeRange) { Q_D(CartesianPlot); d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; double min = d->curvesYMin; double max = d->curvesYMax; //loop over all xy-curves and determine the maximum and minimum y-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; auto* yColumn = curve->yColumn(); if (!yColumn) continue; int start =0; int end = 0; if (d->rangeType == CartesianPlot::RangeFree && curve->xColumn() && !completeRange) { curve->xColumn()->indicesMinMax(xMin(), xMax(), start, end); if (end < curve->xColumn()->rowCount()) end ++; // because minMaxY excludes indexMax } else { switch (d->rangeType) { case CartesianPlot::RangeFree: start = 0; end = yColumn->rowCount(); break; case CartesianPlot::RangeLast: start = yColumn->rowCount() - d->rangeLastValues; end = yColumn->rowCount(); break; case CartesianPlot::RangeFirst: start = 0; end = d->rangeFirstValues; break; } } curve->minMaxY(start, end, min, max, true); if (min < d->curvesYMin) d->curvesYMin = min; if (max > d->curvesYMax) d->curvesYMax = max; } //loop over all histograms and determine the maximum y-value for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; const double min = curve->getYMinimum(); if (d->curvesYMin > min) d->curvesYMin = min; const double max = curve->getYMaximum(); if (max > d->curvesYMax) d->curvesYMax = max; } } void CartesianPlot::zoomIn() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; zoom(true, true); //zoom in x zoom(false, true); //zoom in y d->retransformScales(); } void CartesianPlot::zoomOut() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; zoom(true, false); //zoom out x zoom(false, false); //zoom out y d->retransformScales(); } void CartesianPlot::zoomInX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(true, true); //zoom in x if (d->autoScaleY && autoScaleY()) return; d->retransformScales(); } void CartesianPlot::zoomOutX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(true, false); //zoom out x if (d->autoScaleY && autoScaleY()) return; d->retransformScales(); } void CartesianPlot::zoomInY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(false, true); //zoom in y if (d->autoScaleX && autoScaleX()) return; d->retransformScales(); } void CartesianPlot::zoomOutY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(false, false); //zoom out y if (d->autoScaleX && autoScaleX()) return; d->retransformScales(); } /*! * helper function called in other zoom*() functions * and doing the actual change of the data ranges. * @param x if set to \true the x-range is modified, the y-range for \c false * @param in the "zoom in" is performed if set to \c \true, "zoom out" for \c false */ void CartesianPlot::zoom(bool x, bool in) { Q_D(CartesianPlot); double min; double max; CartesianPlot::Scale scale; if (x) { min = d->xMin; max = d->xMax; scale = d->xScale; } else { min = d->yMin; max = d->yMax; scale = d->yScale; } double factor = m_zoomFactor; if (in) factor = 1/factor; switch (scale) { case ScaleLinear: { double oldRange = max - min; double newRange = (max - min) * factor; max = max + (newRange - oldRange) / 2; min = min - (newRange - oldRange) / 2; break; } case ScaleLog10: case ScaleLog10Abs: { double oldRange = log10(max) - log10(min); double newRange = (log10(max) - log10(min)) * factor; max = max * pow(10, (newRange - oldRange) / 2.); min = min / pow(10, (newRange - oldRange) / 2.); break; } case ScaleLog2: case ScaleLog2Abs: { double oldRange = log2(max) - log2(min); double newRange = (log2(max) - log2(min)) * factor; max = max * pow(2, (newRange - oldRange) / 2.); min = min / pow(2, (newRange - oldRange) / 2.); break; } case ScaleLn: case ScaleLnAbs: { double oldRange = log(max) - log(min); double newRange = (log(max) - log(min)) * factor; max = max * exp((newRange - oldRange) / 2.); min = min / exp((newRange - oldRange) / 2.); break; } case ScaleSqrt: case ScaleX2: break; } if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) { if (x) { d->xMin = min; d->xMax = max; } else { d->yMin = min; d->yMax = max; } } } /*! * helper function called in other shift*() functions * and doing the actual change of the data ranges. * @param x if set to \true the x-range is modified, the y-range for \c false * @param leftOrDown the "shift left" for x or "shift dows" for y is performed if set to \c \true, * "shift right" or "shift up" for \c false */ void CartesianPlot::shift(bool x, bool leftOrDown) { Q_D(CartesianPlot); double min; double max; CartesianPlot::Scale scale; double offset = 0.0; double factor = 0.1; if (x) { min = d->xMin; max = d->xMax; scale = d->xScale; } else { min = d->yMin; max = d->yMax; scale = d->yScale; } if (leftOrDown) factor *= -1.; switch (scale) { case ScaleLinear: { offset = (max - min) * factor; min += offset; max += offset; break; } case ScaleLog10: case ScaleLog10Abs: { offset = (log10(max) - log10(min)) * factor; min *= pow(10, offset); max *= pow(10, offset); break; } case ScaleLog2: case ScaleLog2Abs: { offset = (log2(max) - log2(min)) * factor; min *= pow(2, offset); max *= pow(2, offset); break; } case ScaleLn: case ScaleLnAbs: { offset = (log10(max) - log10(min)) * factor; min *= exp(offset); max *= exp(offset); break; } case ScaleSqrt: case ScaleX2: break; } if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) { if (x) { d->xMin = min; d->xMax = max; } else { d->yMin = min; d->yMax = max; } } } void CartesianPlot::shiftLeftX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; shift(true, true); if (d->autoScaleY && scaleAutoY()) return; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; shift(true, false); if (d->autoScaleY && scaleAutoY()) return; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; shift(false, false); if (d->autoScaleX && scaleAutoX()) return; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; shift(false, true); if (d->autoScaleX && scaleAutoX()) return; d->retransformScales(); } void CartesianPlot::cursor() { Q_D(CartesianPlot); d->retransformScales(); } void CartesianPlot::mousePressZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mousePressZoomSelectionMode(logicPos); } void CartesianPlot::mousePressCursorMode(int cursorNumber, QPointF logicPos) { Q_D(CartesianPlot); d->mousePressCursorMode(cursorNumber, logicPos); } void CartesianPlot::mouseMoveZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mouseMoveZoomSelectionMode(logicPos); } void CartesianPlot::mouseMoveCursorMode(int cursorNumber, QPointF logicPos) { Q_D(CartesianPlot); d->mouseMoveCursorMode(cursorNumber, logicPos); } void CartesianPlot::mouseReleaseZoomSelectionMode() { Q_D(CartesianPlot); d->mouseReleaseZoomSelectionMode(); } void CartesianPlot::mouseHoverZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mouseHoverZoomSelectionMode(logicPos); } void CartesianPlot::mouseHoverOutsideDataRect() { Q_D(CartesianPlot); d->mouseHoverOutsideDataRect(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlot::visibilityChanged() { Q_D(CartesianPlot); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) : AbstractPlotPrivate(plot), q(plot) { - setData(0, WorksheetElement::NameCartesianPlot); + setData(0, static_cast(WorksheetElement::WorksheetElementName::NameCartesianPlot)); m_cursor0Text.prepare(); m_cursor1Text.prepare(); } /*! updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales. The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc. and which can pose the parent item for several sub-items (like TextLabel). Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area. Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area. */ void CartesianPlotPrivate::retransform() { if (suppressRetransform) return; PERFTRACE("CartesianPlotPrivate::retransform()"); prepareGeometryChange(); setPos( rect.x()+rect.width()/2, rect.y()+rect.height()/2); updateDataRect(); retransformScales(); //plotArea position is always (0, 0) in parent's coordinates, don't need to update here q->plotArea()->setRect(rect); //call retransform() for the title and the legend (if available) //when a predefined position relative to the (Left, Centered etc.) is used, //the actual position needs to be updated on plot's geometry changes. if (q->title()) q->title()->retransform(); if (q->m_legend) q->m_legend->retransform(); WorksheetElementContainerPrivate::recalcShapeAndBoundingRect(); } void CartesianPlotPrivate::retransformScales() { DEBUG("CartesianPlotPrivate::retransformScales()"); DEBUG(" xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax); PERFTRACE("CartesianPlotPrivate::retransformScales()"); QVector scales; //check ranges for log-scales if (xScale != CartesianPlot::ScaleLinear) checkXRange(); //check whether we have x-range breaks - the first break, if available, should be valid bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid()); static const int breakGap = 20; double sceneStart, sceneEnd, logicalStart, logicalEnd; //create x-scales int plotSceneStart = dataRect.x(); int plotSceneEnd = dataRect.x() + dataRect.width(); if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = xMin; logicalEnd = xMax; //TODO: how should we handle the case sceneStart == sceneEnd? //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots) if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = xMin; for (const auto& rb : xRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range) sceneStart = sceneEndLast+breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = xMax; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setXScales(scales); //check ranges for log-scales if (yScale != CartesianPlot::ScaleLinear) checkYRange(); //check whether we have y-range breaks - the first break, if available, should be valid hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid()); //create y-scales scales.clear(); plotSceneStart = dataRect.y() + dataRect.height(); plotSceneEnd = dataRect.y(); if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = yMin; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = yMin; for (const auto& rb : yRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range) sceneStart = sceneEndLast-breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setYScales(scales); //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax double deltaXMin = 0; double deltaXMax = 0; double deltaYMin = 0; double deltaYMax = 0; if (xMin != xMinPrev) { deltaXMin = xMin - xMinPrev; emit q->xMinChanged(xMin); } if (xMax != xMaxPrev) { deltaXMax = xMax - xMaxPrev; emit q->xMaxChanged(xMax); } if (yMin != yMinPrev) { deltaYMin = yMin - yMinPrev; emit q->yMinChanged(yMin); } if (yMax != yMaxPrev) { deltaYMax = yMax - yMaxPrev; emit q->yMaxChanged(yMax); } xMinPrev = xMin; xMaxPrev = xMax; yMinPrev = yMin; yMaxPrev = yMax; //adjust auto-scale axes for (auto* axis : q->children()) { if (!axis->autoScale()) continue; if (axis->orientation() == Axis::AxisHorizontal) { if (deltaXMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(xMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaXMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(xMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaYMin != 0) { // axis->setOffset(axis->offset() + deltaYMin, false); // } } else { if (deltaYMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(yMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaYMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(yMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaXMin != 0) { // axis->setOffset(axis->offset() + deltaXMin, false); // } } } // call retransform() on the parent to trigger the update of all axes and curves. //no need to do this on load since all plots are retransformed again after the project is loaded. if (!q->isLoading()) q->retransform(); } /* * calculates the rectangular of the are showing the actual data (plot's rect minus padding), * in plot's coordinates. */ void CartesianPlotPrivate::updateDataRect() { dataRect = mapRectFromScene(rect); double paddingLeft = horizontalPadding; double paddingRight = rightPadding; double paddingTop = verticalPadding; double paddingBottom = bottomPadding; if (symmetricPadding) { paddingRight = horizontalPadding; paddingBottom = verticalPadding; } dataRect.setX(dataRect.x() + paddingLeft); dataRect.setY(dataRect.y() + paddingTop); double newHeight = dataRect.height() - paddingBottom; if (newHeight < 0) newHeight = 0; dataRect.setHeight(newHeight); double newWidth = dataRect.width() - paddingRight; if (newWidth < 0) newWidth = 0; dataRect.setWidth(newWidth); } void CartesianPlotPrivate::rangeChanged() { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; if (autoScaleX && autoScaleY) q->scaleAuto(); else if (autoScaleX) q->scaleAutoX(); else if (autoScaleY) q->scaleAutoY(); } void CartesianPlotPrivate::xRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisHorizontal) axis->retransformTickLabelStrings(); } } void CartesianPlotPrivate::yRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisVertical) axis->retransformTickLabelStrings(); } } /*! * don't allow any negative values for the x range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkXRange() { double min = 0.01; if (xMin <= 0.0) { (min < xMax*min) ? xMin = min : xMin = xMax*min; emit q->xMinChanged(xMin); } else if (xMax <= 0.0) { (-min > xMin*min) ? xMax = -min : xMax = xMin*min; emit q->xMaxChanged(xMax); } } /*! * don't allow any negative values for the y range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkYRange() { double min = 0.01; if (yMin <= 0.0) { (min < yMax*min) ? yMin = min : yMin = yMax*min; emit q->yMinChanged(yMin); } else if (yMax <= 0.0) { (-min > yMin*min) ? yMax = -min : yMax = yMin*min; emit q->yMaxChanged(yMax); } } CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) { DEBUG("CartesianPlotPrivate::createScale() scene start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd); // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (std::numeric_limits::lowest(), std::numeric_limits::max()); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); else return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, type); } /*! * Reimplemented from QGraphicsItem. */ QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); emit q->rectChanged(newRect); } return QGraphicsItem::itemChange(change, value); } //############################################################################## //################################## Events ################################## //############################################################################## /*! * \brief CartesianPlotPrivate::mousePressEvent * In this function only basic stuff is done. The mousePressEvent is forwarded to the Worksheet, which * has access to all cartesian plots and can apply the changes to all plots if the option "applyToAll" * is set. The worksheet calls then the corresponding mousepressZoomMode/CursorMode function in this class * This is done for mousePress, mouseMove and mouseRelease event * This function sends a signal with the logical position, because this is the only value which is the same * in all plots. Using the scene coordinates is not possible * \param event */ void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) emit q->mousePressZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit)); else if (mouseMode == CartesianPlot::Cursor) { setCursor(Qt::SizeHorCursor); QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); double cursorPenWidth2 = cursorPen.width()/2.; if (cursorPenWidth2 < 10.) cursorPenWidth2 = 10.; if (cursor0Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) { selectedCursor = 0; } else if (cursor1Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2) { selectedCursor = 1; } else if (QApplication::keyboardModifiers() & Qt::ControlModifier){ cursor1Enable = true; selectedCursor = 1; emit q->cursor1EnableChanged(cursor1Enable); } else { cursor0Enable = true; selectedCursor = 0; emit q->cursor0EnableChanged(cursor0Enable); } emit q->mousePressCursorModeSignal(selectedCursor, logicalPos); } else { if (!locked && dataRect.contains(event->pos())) { panningStarted = true; m_panningStart = event->pos(); setCursor(Qt::ClosedHandCursor); } } QGraphicsItem::mousePressEvent(event); } void CartesianPlotPrivate::mousePressZoomSelectionMode(QPointF logicalPos) { if (mouseMode == CartesianPlot::ZoomSelectionMode) { if (logicalPos.x() < xMin) logicalPos.setX(xMin); if (logicalPos.x() > xMax) logicalPos.setX(xMax); if (logicalPos.y() < yMin) logicalPos.setY(yMin); if (logicalPos.y() > yMax) logicalPos.setY(yMax); m_selectionStart = cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionStart.setX(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping).x()); m_selectionStart.setY(dataRect.y()); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionStart.setX(dataRect.x()); m_selectionStart.setY(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping).y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } void CartesianPlotPrivate::mousePressCursorMode(int cursorNumber, QPointF logicalPos) { cursorNumber == 0 ? cursor0Enable = true : cursor1Enable = true; QPointF p1(logicalPos.x(), yMin); QPointF p2(logicalPos.x(), yMax); if (cursorNumber == 0) { cursor0Pos.setX(logicalPos.x()); cursor0Pos.setY(0); } else { cursor1Pos.setX(logicalPos.x()); cursor1Pos.setY(0); } update(); } void CartesianPlotPrivate::updateCursor() { update(); } void CartesianPlotPrivate::setZoomSelectionBandShow(bool show) { m_selectionBandIsShown = show; } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { if (panningStarted && dataRect.contains(event->pos()) ) { //don't retransform on small mouse movement deltas const int deltaXScene = (m_panningStart.x() - event->pos().x()); const int deltaYScene = (m_panningStart.y() - event->pos().y()); if (abs(deltaXScene) < 5 && abs(deltaYScene) < 5) return; const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos()); const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart); //handle the change in x switch (xScale) { case CartesianPlot::ScaleLinear: { const float deltaX = (logicalStart.x() - logicalEnd.x()); xMax += deltaX; xMin += deltaX; break; } case CartesianPlot::ScaleLog10: case CartesianPlot::ScaleLog10Abs: { const float deltaX = log10(logicalStart.x()) - log10(logicalEnd.x()); xMin *= pow(10, deltaX); xMax *= pow(10, deltaX); break; } case CartesianPlot::ScaleLog2: case CartesianPlot::ScaleLog2Abs: { const float deltaX = log2(logicalStart.x()) - log2(logicalEnd.x()); xMin *= pow(2, deltaX); xMax *= pow(2, deltaX); break; } case CartesianPlot::ScaleLn: case CartesianPlot::ScaleLnAbs: { const float deltaX = log(logicalStart.x()) - log(logicalEnd.x()); xMin *= exp(deltaX); xMax *= exp(deltaX); break; } case CartesianPlot::ScaleSqrt: case CartesianPlot::ScaleX2: break; } //handle the change in y switch (yScale) { case CartesianPlot::ScaleLinear: { const float deltaY = (logicalStart.y() - logicalEnd.y()); yMax += deltaY; yMin += deltaY; break; } case CartesianPlot::ScaleLog10: case CartesianPlot::ScaleLog10Abs: { const float deltaY = log10(logicalStart.y()) - log10(logicalEnd.y()); yMin *= pow(10, deltaY); yMax *= pow(10, deltaY); break; } case CartesianPlot::ScaleLog2: case CartesianPlot::ScaleLog2Abs: { const float deltaY = log2(logicalStart.y()) - log2(logicalEnd.y()); yMin *= pow(2, deltaY); yMax *= pow(2, deltaY); break; } case CartesianPlot::ScaleLn: case CartesianPlot::ScaleLnAbs: { const float deltaY = log(logicalStart.y()) - log(logicalEnd.y()); yMin *= exp(deltaY); yMax *= exp(deltaY); break; } case CartesianPlot::ScaleSqrt: case CartesianPlot::ScaleX2: break; } q->setUndoAware(false); q->setAutoScaleX(false); q->setAutoScaleY(false); q->setUndoAware(true); retransformScales(); m_panningStart = event->pos(); } else QGraphicsItem::mouseMoveEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { q->info(QString()); return; } emit q->mouseMoveZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), CartesianCoordinateSystem::MappingFlag::Limit)); } else if (mouseMode == CartesianPlot::Cursor) { QGraphicsItem::mouseMoveEvent(event); if (!boundingRect().contains(event->pos())) { q->info(i18n("Not inside of the bounding rect")); return; } QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); // updating treeview data and cursor position // updatign cursor position is done in Worksheet, because // multiple plots must be updated emit q->mouseMoveCursorModeSignal(selectedCursor, logicalPos); } } void CartesianPlotPrivate::mouseMoveZoomSelectionMode(QPointF logicalPos) { QString info; QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionEnd = cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); QPointF logicalEnd = logicalPos; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); info += QLatin1String(", "); if (yRangeFormat == CartesianPlot::Numeric) info += QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info += i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionEnd.setX(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).x());//event->pos().x()); m_selectionEnd.setY(dataRect.bottom()); QPointF logicalEnd = logicalPos; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionEnd.setX(dataRect.right()); logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionEnd.setY(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).y());//event->pos().y()); QPointF logicalEnd = logicalPos; if (yRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info = i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } q->info(info); update(); } void CartesianPlotPrivate::mouseMoveCursorMode(int cursorNumber, QPointF logicalPos) { QPointF p1(logicalPos.x(), 0); cursorNumber == 0 ? cursor0Pos = p1 : cursor1Pos = p1; QString info; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("x=") + QString::number(logicalPos.x()); else info = i18n("x=%1", QDateTime::fromMSecsSinceEpoch(logicalPos.x()).toString(xRangeDateTimeFormat)); q->info(info); update(); } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { setCursor(Qt::ArrowCursor); if (mouseMode == CartesianPlot::SelectionMode) { panningStarted = false; //TODO: why do we do this all the time?!?! const QPointF& itemPos = pos();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and set it QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); suppressRetransform = true; q->setRect(newRect); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { emit q->mouseReleaseZoomSelectionModeSignal(); } } void CartesianPlotPrivate::mouseReleaseZoomSelectionMode() { //don't zoom if very small region was selected, avoid occasional/unwanted zooming if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) { m_selectionBandIsShown = false; return; } bool retransformPlot = true; //determine the new plot ranges QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::SuppressPageClipping); QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::SuppressPageClipping); if (m_selectionEnd.x() > m_selectionStart.x()) { xMin = logicalZoomStart.x(); xMax = logicalZoomEnd.x(); } else { xMin = logicalZoomEnd.x(); xMax = logicalZoomStart.x(); } if (m_selectionEnd.y() > m_selectionStart.y()) { yMin = logicalZoomEnd.y(); yMax = logicalZoomStart.y(); } else { yMin = logicalZoomStart.y(); yMax = logicalZoomEnd.y(); } if (mouseMode == CartesianPlot::ZoomSelectionMode) { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; q->setAutoScaleX(false); q->setAutoScaleY(false); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { curvesYMinMaxIsDirty = true; q->setAutoScaleX(false); if (q->autoScaleY() && q->scaleAutoY()) retransformPlot = false; } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { curvesXMinMaxIsDirty = true; q->setAutoScaleY(false); if (q->autoScaleX() && q->scaleAutoX()) retransformPlot = false; } if (retransformPlot) retransformScales(); m_selectionBandIsShown = false; } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { if (locked) return; //determine first, which axes are selected and zoom only in the corresponding direction. //zoom the entire plot if no axes selected. bool zoomX = false; bool zoomY = false; for (auto* axis : q->children()) { if (!axis->graphicsItem()->isSelected()) continue; if (axis->orientation() == Axis::AxisHorizontal) zoomX = true; else zoomY = true; } if (event->delta() > 0) { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomIn(); } else { if (zoomX) q->zoomInX(); if (zoomY) q->zoomInY(); } } else { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomOut(); } else { if (zoomX) q->zoomOutX(); if (zoomY) q->zoomOutY(); } } } void CartesianPlotPrivate::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Escape) { setCursor(Qt::ArrowCursor); q->setMouseMode(CartesianPlot::MouseMode::SelectionMode); m_selectionBandIsShown = false; } else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ||event->key() == Qt::Key_Down) { const auto* worksheet = static_cast(q->parentAspect()); - if (worksheet->layout() == Worksheet::NoLayout) { + if (worksheet->layout() == Worksheet::Layout::NoLayout) { const int delta = 5; QRectF rect = q->rect(); if (event->key() == Qt::Key_Left) { rect.setX(rect.x() - delta); rect.setWidth(rect.width() - delta); } else if (event->key() == Qt::Key_Right) { rect.setX(rect.x() + delta); rect.setWidth(rect.width() + delta); } else if (event->key() == Qt::Key_Up) { rect.setY(rect.y() - delta); rect.setHeight(rect.height() - delta); } else if (event->key() == Qt::Key_Down) { rect.setY(rect.y() + delta); rect.setHeight(rect.height() + delta); } q->setRect(rect); } } QGraphicsItem::keyPressEvent(event); } void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { QPointF point = event->pos(); QString info; if (dataRect.contains(point)) { QPointF logicalPoint = cSystem->mapSceneToLogical(point); if ((mouseMode == CartesianPlot::ZoomSelectionMode) || mouseMode == CartesianPlot::SelectionMode) { info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); info += ", y="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); } if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { info = "y="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::MouseMode::SelectionMode) { // hover the nearest curve to the mousepointer // hovering curves is implemented in the parent, because no ignoreEvent() exists // for it. Checking all curves and hover the first bool curve_hovered = false; QVector curves = q->children(); for (int i=curves.count() - 1; i >= 0; i--){ // because the last curve is above the other curves if (curve_hovered){ // if a curve is already hovered, disable hover for the rest curves[i]->setHover(false); continue; } if (curves[i]->activateCurve(event->pos())){ curves[i]->setHover(true); curve_hovered = true; continue; } curves[i]->setHover(false); } } else if (mouseMode == CartesianPlot::Cursor){ info = "x="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); double cursorPenWidth2 = cursorPen.width()/2.; if (cursorPenWidth2 < 10.) cursorPenWidth2 = 10.; if ((cursor0Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) || (cursor1Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2)) setCursor(Qt::SizeHorCursor); else setCursor(Qt::ArrowCursor); update(); } } else emit q->mouseHoverOutsideDataRectSignal(); q->info(info); QGraphicsItem::hoverMoveEvent(event); } void CartesianPlotPrivate::mouseHoverOutsideDataRect() { m_insideDataRect = false; update(); } void CartesianPlotPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { QVector curves = q->children(); for (auto* curve : curves) curve->setHover(false); m_hovered = false; QGraphicsItem::hoverLeaveEvent(event); } void CartesianPlotPrivate::mouseHoverZoomSelectionMode(QPointF logicPos) { m_insideDataRect = true; if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { QPointF p1(logicPos.x(), yMin); QPointF p2(logicPos.x(), yMax); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit)); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { QPointF p1(xMin, logicPos.y()); QPointF p2(xMax, logicPos.y()); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit)); } update(); // because if previous another selection mode was selected, the lines must be deleted } void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (!m_printing) { painter->save(); painter->setPen(cursorPen); QFont font = painter->font(); font.setPointSize(font.pointSize() * 4); painter->setFont(font); QPointF p1 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)); if (cursor0Enable && p1 != QPointF(0,0)){ QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMax)); painter->drawLine(p1,p2); QPointF textPos = p2; textPos.setX(p2.x() - m_cursor0Text.size().width()/2); textPos.setY(p2.y() - m_cursor0Text.size().height()); if (textPos.y() < boundingRect().y()) textPos.setY(boundingRect().y()); painter->drawStaticText(textPos, m_cursor0Text); } p1 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)); if (cursor1Enable && p1 != QPointF(0,0)){ QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMax)); painter->drawLine(p1,p2); QPointF textPos = p2; // TODO: Moving this stuff into other function to not calculate it every time textPos.setX(p2.x() - m_cursor1Text.size().width()/2); textPos.setY(p2.y() - m_cursor1Text.size().height()); if (textPos.y() < boundingRect().y()) textPos.setY(boundingRect().y()); painter->drawStaticText(textPos, m_cursor1Text); } painter->restore(); } painter->setPen(QPen(Qt::black, 3)); if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) && (!m_selectionBandIsShown) && m_insideDataRect) painter->drawLine(m_selectionStartLine); if (m_selectionBandIsShown) { QPointF selectionStart = m_selectionStart; if (m_selectionStart.x() > dataRect.right()) selectionStart.setX(dataRect.right()); if (m_selectionStart.x() < dataRect.left()) selectionStart.setX(dataRect.left()); if (m_selectionStart.y() > dataRect.bottom()) selectionStart.setY(dataRect.bottom()); if (m_selectionStart.y() < dataRect.top()) selectionStart.setY(dataRect.top()); QPointF selectionEnd = m_selectionEnd; if (m_selectionEnd.x() > dataRect.right()) selectionEnd.setX(dataRect.right()); if (m_selectionEnd.x() < dataRect.left()) selectionEnd.setX(dataRect.left()); if (m_selectionEnd.y() > dataRect.bottom()) selectionEnd.setY(dataRect.bottom()); if (m_selectionEnd.y() < dataRect.top()) selectionEnd.setY(dataRect.top()); painter->save(); painter->setPen(QPen(Qt::black, 5)); painter->drawRect(QRectF(selectionStart, selectionEnd)); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(QRectF(selectionStart, selectionEnd)); painter->restore(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlot::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlot); writer->writeStartElement( "cartesianPlot" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //cursor writer->writeStartElement( "cursor" ); WRITE_QPEN(d->cursorPen); writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->rect.x()) ); writer->writeAttribute( "y", QString::number(d->rect.y()) ); writer->writeAttribute( "width", QString::number(d->rect.width()) ); writer->writeAttribute( "height", QString::number(d->rect.height()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //coordinate system and padding writer->writeStartElement( "coordinateSystem" ); writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) ); writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) ); writer->writeAttribute( "xMin", QString::number(d->xMin, 'g', 16)); writer->writeAttribute( "xMax", QString::number(d->xMax, 'g', 16) ); writer->writeAttribute( "yMin", QString::number(d->yMin, 'g', 16) ); writer->writeAttribute( "yMax", QString::number(d->yMax, 'g', 16) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "xRangeFormat", QString::number(d->xRangeFormat) ); writer->writeAttribute( "yRangeFormat", QString::number(d->yRangeFormat) ); writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) ); writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) ); writer->writeAttribute( "rightPadding", QString::number(d->rightPadding) ); writer->writeAttribute( "bottomPadding", QString::number(d->bottomPadding) ); writer->writeAttribute( "symmetricPadding", QString::number(d->symmetricPadding)); writer->writeEndElement(); //x-scale breaks if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) { writer->writeStartElement("xRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) ); for (const auto& rb : d->xRangeBreaks.list) { writer->writeStartElement("xRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //y-scale breaks if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) { writer->writeStartElement("yRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) ); for (const auto& rb : d->yRangeBreaks.list) { writer->writeStartElement("yRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //serialize all children (plot area, title text label, axes and curves) for (auto* elem : children(ChildIndexFlag::IncludeHidden)) elem->save(writer); writer->writeEndElement(); // close "cartesianPlot" section } //! Load from XML bool CartesianPlot::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlot); if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool titleLabelRead = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlot") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "cursor") { attribs = reader->attributes(); QPen pen; pen.setWidth(attribs.value("width").toInt()); pen.setStyle(static_cast(attribs.value("style").toInt())); QColor color; color.setRed(attribs.value("color_r").toInt()); color.setGreen(attribs.value("color_g").toInt()); color.setBlue(attribs.value("color_b").toInt()); pen.setColor(color); d->cursorPen = pen; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->rect.setX( str.toDouble() ); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->rect.setY( str.toDouble() ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else d->rect.setWidth( str.toDouble() ); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else d->rect.setHeight( str.toDouble() ); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "coordinateSystem") { attribs = reader->attributes(); READ_INT_VALUE("autoScaleX", autoScaleX, bool); READ_INT_VALUE("autoScaleY", autoScaleY, bool); str = attribs.value("xMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMin").toString()); else { d->xMin = str.toDouble(); d->xMinPrev = d->xMin; } str = attribs.value("xMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMax").toString()); else { d->xMax = str.toDouble(); d->xMaxPrev = d->xMax; } str = attribs.value("yMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMin").toString()); else { d->yMin = str.toDouble(); d->yMinPrev = d->yMin; } str = attribs.value("yMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMax").toString()); else { d->yMax = str.toDouble(); d->yMaxPrev = d->yMax; } READ_INT_VALUE("xScale", xScale, CartesianPlot::Scale); READ_INT_VALUE("yScale", yScale, CartesianPlot::Scale); READ_INT_VALUE("xRangeFormat", xRangeFormat, CartesianPlot::RangeFormat); READ_INT_VALUE("yRangeFormat", yRangeFormat, CartesianPlot::RangeFormat); READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding); READ_DOUBLE_VALUE("verticalPadding", verticalPadding); READ_DOUBLE_VALUE("rightPadding", rightPadding); READ_DOUBLE_VALUE("bottomPadding", bottomPadding); READ_INT_VALUE("symmetricPadding", symmetricPadding, bool); } else if (!preview && reader->name() == "xRangeBreaks") { //delete default rang break d->xRangeBreaks.list.clear(); attribs = reader->attributes(); READ_INT_VALUE("enabled", xRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "xRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->xRangeBreaks.list << b; } else if (!preview && reader->name() == "yRangeBreaks") { //delete default rang break d->yRangeBreaks.list.clear(); attribs = reader->attributes(); READ_INT_VALUE("enabled", yRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "yRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->yRangeBreaks.list << b; } else if (reader->name() == "textLabel") { if (!titleLabelRead) { //the first text label is always the title label m_title->load(reader, preview); titleLabelRead = true; //TODO: the name is read in m_title->load() but we overwrite it here //since the old projects don't have this " - Title" appendix yet that we add in init(). //can be removed in couple of releases m_title->setName(name() + QLatin1String(" - ") + i18n("Title")); } else { TextLabel* label = new TextLabel("text label"); if (label->load(reader, preview)) { addChildFast(label); label->setParentGraphicsItem(graphicsItem()); } else { delete label; return false; } } } else if (reader->name() == "image") { Image* image = new Image(QString()); if (!image->load(reader, preview)) { delete image; return false; } else addChildFast(image); } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { Axis* axis = new Axis(QString()); if (axis->load(reader, preview)) addChildFast(axis); else { delete axis; return false; } } else if (reader->name() == "xyCurve") { XYCurve* curve = new XYCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = new XYEquationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = new XYDataReductionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = new XYDifferentiationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = new XYIntegrationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = new XYInterpolationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = new XYSmoothCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = new XYFitCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = new XYFourierFilterCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = new XYFourierTransformCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyConvolutionCurve") { XYConvolutionCurve* curve = new XYConvolutionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyCorrelationCurve") { XYCorrelationCurve* curve = new XYCorrelationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, QString()); if (m_legend->load(reader, preview)) addChildFast(m_legend); else { delete m_legend; return false; } } else if (reader->name() == "customPoint") { CustomPoint* point = new CustomPoint(this, QString()); if (point->load(reader, preview)) addChildFast(point); else { delete point; return false; } } else if (reader->name() == "referenceLine") { ReferenceLine* line = new ReferenceLine(this, QString()); if (line->load(reader, preview)) addChildFast(line); else { delete line; return false; } } else if (reader->name() == "Histogram") { Histogram* curve = new Histogram("Histogram"); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else { // unknown element reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; d->retransform(); //if a theme was used, initialize the color palette if (!d->theme.isEmpty()) { //TODO: check whether the theme config really exists KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig ); this->setColorPalette(config); } else { //initialize the color palette with default colors this->setColorPalette(KConfig()); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void CartesianPlot::loadTheme(const QString& theme) { if (!theme.isEmpty()) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); loadThemeConfig(config); } else { KConfig config; loadThemeConfig(config); } } void CartesianPlot::loadThemeConfig(const KConfig& config) { Q_D(CartesianPlot); QString theme = QString(); if (config.hasGroup(QLatin1String("Theme"))) { theme = config.name(); // theme path is saved with UNIX dir separator theme = theme.right(theme.length() - theme.lastIndexOf(QLatin1Char('/')) - 1); DEBUG(" set theme to " << STDSTRING(theme)); } //loadThemeConfig() can be called from //1. CartesianPlot::setTheme() when the user changes the theme for the plot //2. Worksheet::setTheme() -> Worksheet::loadTheme() when the user changes the theme for the worksheet //In the second case (i.e. when d->theme is not equal to theme yet), ///we need to put the new theme name on the undo-stack. if (theme != d->theme) exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme"))); //load the color palettes for the curves this->setColorPalette(config); //load the theme for all the children for (auto* child : children(ChildIndexFlag::IncludeHidden)) child->loadThemeConfig(config); d->update(this->rect()); } void CartesianPlot::saveTheme(KConfig &config) { const QVector& axisElements = children(ChildIndexFlag::IncludeHidden); const QVector& plotAreaElements = children(ChildIndexFlag::IncludeHidden); const QVector& textLabelElements = children(ChildIndexFlag::IncludeHidden); axisElements.at(0)->saveThemeConfig(config); plotAreaElements.at(0)->saveThemeConfig(config); textLabelElements.at(0)->saveThemeConfig(config); for (auto *child : children(ChildIndexFlag::IncludeHidden)) child->saveThemeConfig(config); } //Generating colors from 5-color theme palette void CartesianPlot::setColorPalette(const KConfig& config) { if (config.hasGroup(QLatin1String("Theme"))) { KConfigGroup group = config.group(QLatin1String("Theme")); //read the five colors defining the palette m_themeColorPalette.clear(); m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor())); } else { //no theme is available, provide 5 "default colors" m_themeColorPalette.clear(); m_themeColorPalette.append(QColor(25, 25, 25)); m_themeColorPalette.append(QColor(0, 0, 127)); m_themeColorPalette.append(QColor(127 ,0, 0)); m_themeColorPalette.append(QColor(0, 127, 0)); m_themeColorPalette.append(QColor(85, 0, 127)); } //generate 30 additional shades if the color palette contains more than one color if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) { QColor c; //3 factors to create shades from theme's palette std::array fac = {0.25f, 0.45f, 0.65f}; //Generate 15 lighter shades for (int i = 0; i < 5; i++) { for (int j = 1; j < 4; j++) { c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) ); c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) ); c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) ); m_themeColorPalette.append(c); } } //Generate 15 darker shades for (int i = 0; i < 5; i++) { for (int j = 4; j < 7; j++) { c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) ); c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) ); c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) ); m_themeColorPalette.append(c); } } } } const QList& CartesianPlot::themeColorPalette() const { return m_themeColorPalette; } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp index 700168b4c..718eba16a 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp @@ -1,1267 +1,1266 @@ /*************************************************************************** File : CartesianPlotLegend.cpp Project : LabPlot Description : Legend for the cartesian plot -------------------------------------------------------------------- Copyright : (C) 2013-2018 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 * * * ***************************************************************************/ /*! \class CartesianPlotLegend \brief Legend for the cartesian plot. \ingroup kdefrontend */ #include "CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegendPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include #include #include #include #include #include #include #include CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name) : WorksheetElement(name, AspectType::CartesianPlotLegend), d_ptr(new CartesianPlotLegendPrivate(this)), m_plot(plot) { init(); } CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name, CartesianPlotLegendPrivate *dd) : WorksheetElement(name, AspectType::CartesianPlotLegend), d_ptr(dd), m_plot(plot) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene CartesianPlotLegend::~CartesianPlotLegend() = default; void CartesianPlotLegend::init() { Q_D(CartesianPlotLegend); KConfig config; KConfigGroup group = config.group( "CartesianPlotLegend" ); d->labelFont = group.readEntry("LabelsFont", QFont()); - d->labelFont.setPixelSize( Worksheet::convertToSceneUnits( 10, Worksheet::Point ) ); + d->labelFont.setPixelSize( Worksheet::convertToSceneUnits( 10, Worksheet::Unit::Point ) ); d->labelColor = Qt::black; d->labelColumnMajor = true; - d->lineSymbolWidth = group.readEntry("LineSymbolWidth", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)); + d->lineSymbolWidth = group.readEntry("LineSymbolWidth", Worksheet::convertToSceneUnits(1, Worksheet::Unit::Centimeter)); d->rowCount = 0; d->columnCount = 0; - d->position.horizontalPosition = CartesianPlotLegend::hPositionRight; - d->position.verticalPosition = CartesianPlotLegend::vPositionBottom; + d->position.horizontalPosition = WorksheetElement::HorizontalPosition::Right; + d->position.verticalPosition = WorksheetElement::VerticalPosition::Bottom; d->rotationAngle = group.readEntry("Rotation", 0.0); //Title d->title = new TextLabel(this->name(), TextLabel::Type::PlotLegendTitle); d->title->setBorderShape(TextLabel::BorderShape::NoBorder); addChild(d->title); d->title->setHidden(true); d->title->setParentGraphicsItem(d); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, false); connect(d->title, &TextLabel::changed, this, &CartesianPlotLegend::retransform); //Background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int) PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //Border - d->borderPen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), - group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)), - (Qt::PenStyle) group.readEntry("BorderStyle", (int)Qt::SolidLine)); + d->borderPen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)), + (Qt::PenStyle) group.readEntry("BorderStyle", (int)Qt::SolidLine)); d->borderCornerRadius = group.readEntry("BorderCornerRadius", 0.0); d->borderOpacity = group.readEntry("BorderOpacity", 1.0); //Layout - d->layoutTopMargin = group.readEntry("LayoutTopMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); - d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); - d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); - d->layoutRightMargin = group.readEntry("LayoutRightMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); - d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Centimeter)); - d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Centimeter)); + d->layoutTopMargin = group.readEntry("LayoutTopMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Unit::Centimeter)); + d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Unit::Centimeter)); + d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Unit::Centimeter)); + d->layoutRightMargin = group.readEntry("LayoutRightMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Unit::Centimeter)); + d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Unit::Centimeter)); + d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Unit::Centimeter)); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 1); this->initActions(); } void CartesianPlotLegend::initActions() { visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlotLegend::visibilityChangedSlot); } QMenu* CartesianPlotLegend::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlotLegend::icon() const{ return QIcon::fromTheme("text-field"); } STD_SWAP_METHOD_SETTER_CMD_IMPL(CartesianPlotLegend, SetVisible, bool, swapVisible) void CartesianPlotLegend::setVisible(bool on) { Q_D(CartesianPlotLegend); exec(new CartesianPlotLegendSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool CartesianPlotLegend::isVisible() const{ Q_D(const CartesianPlotLegend); return d->isVisible(); } void CartesianPlotLegend::setPrinting(bool on) { Q_D(CartesianPlotLegend); d->m_printing = on; } QGraphicsItem *CartesianPlotLegend::graphicsItem() const{ return d_ptr; } void CartesianPlotLegend::retransform() { d_ptr->retransform(); } void CartesianPlotLegend::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); //TODO // Q_D(const CartesianPlotLegend); } //############################################################################## //################################ getter methods ############################ //############################################################################## CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QFont, labelFont, labelFont) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, labelColor, labelColor) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, bool, labelColumnMajor, labelColumnMajor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, WorksheetElement::PositionWrapper, position, position) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, qreal, rotationAngle, rotationAngle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, lineSymbolWidth, lineSymbolWidth) //Title TextLabel* CartesianPlotLegend::title() { return d_ptr->title; } //Background BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QString, backgroundFileName, backgroundFileName) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, backgroundOpacity, backgroundOpacity) //Border CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderCornerRadius, borderCornerRadius) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderOpacity, borderOpacity) //Layout BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutTopMargin, layoutTopMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutBottomMargin, layoutBottomMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutLeftMargin, layoutLeftMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutRightMargin, layoutRightMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, int, layoutColumnCount, layoutColumnCount) //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelFont, QFont, labelFont, retransform) void CartesianPlotLegend::setLabelFont(const QFont& font) { Q_D(CartesianPlotLegend); if (font!= d->labelFont) exec(new CartesianPlotLegendSetLabelFontCmd(d, font, ki18n("%1: set font"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColor, QColor, labelColor, update) void CartesianPlotLegend::setLabelColor(const QColor& color) { Q_D(CartesianPlotLegend); if (color!= d->labelColor) exec(new CartesianPlotLegendSetLabelColorCmd(d, color, ki18n("%1: set font color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColumnMajor, bool, labelColumnMajor, retransform) void CartesianPlotLegend::setLabelColumnMajor(bool columnMajor) { Q_D(CartesianPlotLegend); if (columnMajor != d->labelColumnMajor) exec(new CartesianPlotLegendSetLabelColumnMajorCmd(d, columnMajor, ki18n("%1: change column order"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLineSymbolWidth, float, lineSymbolWidth, retransform) void CartesianPlotLegend::setLineSymbolWidth(float width) { Q_D(CartesianPlotLegend); if (width != d->lineSymbolWidth) exec(new CartesianPlotLegendSetLineSymbolWidthCmd(d, width, ki18n("%1: change line+symbol width"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetPosition, WorksheetElement::PositionWrapper, position, updatePosition); void CartesianPlotLegend::setPosition(const PositionWrapper& pos) { Q_D(CartesianPlotLegend); if (pos.point != d->position.point || pos.horizontalPosition != d->position.horizontalPosition || pos.verticalPosition != d->position.verticalPosition) exec(new CartesianPlotLegendSetPositionCmd(d, pos, ki18n("%1: set position"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetRotationAngle, qreal, rotationAngle, retransform) void CartesianPlotLegend::setRotationAngle(qreal angle) { Q_D(CartesianPlotLegend); if (angle != d->rotationAngle) { exec(new CartesianPlotLegendSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); d->title->setRotationAngle(angle); } } //Background STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void CartesianPlotLegend::setBackgroundType(PlotArea::BackgroundType type) { Q_D(CartesianPlotLegend); if (type != d->backgroundType) exec(new CartesianPlotLegendSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void CartesianPlotLegend::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundColorStyle) exec(new CartesianPlotLegendSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void CartesianPlotLegend::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundImageStyle) exec(new CartesianPlotLegendSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void CartesianPlotLegend::setBackgroundBrushStyle(Qt::BrushStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundBrushStyle) exec(new CartesianPlotLegendSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void CartesianPlotLegend::setBackgroundFirstColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundFirstColor) exec(new CartesianPlotLegendSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void CartesianPlotLegend::setBackgroundSecondColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundSecondColor) exec(new CartesianPlotLegendSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFileName, QString, backgroundFileName, update) void CartesianPlotLegend::setBackgroundFileName(const QString& fileName) { Q_D(CartesianPlotLegend); if (fileName!= d->backgroundFileName) exec(new CartesianPlotLegendSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundOpacity, float, backgroundOpacity, update) void CartesianPlotLegend::setBackgroundOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->backgroundOpacity) exec(new CartesianPlotLegendSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set opacity"))); } //Border STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderPen, QPen, borderPen, update) void CartesianPlotLegend::setBorderPen(const QPen &pen) { Q_D(CartesianPlotLegend); if (pen != d->borderPen) exec(new CartesianPlotLegendSetBorderPenCmd(d, pen, ki18n("%1: set border style"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderCornerRadius, qreal, borderCornerRadius, update) void CartesianPlotLegend::setBorderCornerRadius(float radius) { Q_D(CartesianPlotLegend); if (radius != d->borderCornerRadius) exec(new CartesianPlotLegendSetBorderCornerRadiusCmd(d, radius, ki18n("%1: set border corner radius"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderOpacity, qreal, borderOpacity, update) void CartesianPlotLegend::setBorderOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->borderOpacity) exec(new CartesianPlotLegendSetBorderOpacityCmd(d, opacity, ki18n("%1: set border opacity"))); } //Layout STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutTopMargin, float, layoutTopMargin, retransform) void CartesianPlotLegend::setLayoutTopMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutTopMargin) exec(new CartesianPlotLegendSetLayoutTopMarginCmd(d, margin, ki18n("%1: set layout top margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutBottomMargin, float, layoutBottomMargin, retransform) void CartesianPlotLegend::setLayoutBottomMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutBottomMargin) exec(new CartesianPlotLegendSetLayoutBottomMarginCmd(d, margin, ki18n("%1: set layout bottom margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutLeftMargin, float, layoutLeftMargin, retransform) void CartesianPlotLegend::setLayoutLeftMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutLeftMargin) exec(new CartesianPlotLegendSetLayoutLeftMarginCmd(d, margin, ki18n("%1: set layout left margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutRightMargin, float, layoutRightMargin, retransform) void CartesianPlotLegend::setLayoutRightMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutRightMargin) exec(new CartesianPlotLegendSetLayoutRightMarginCmd(d, margin, ki18n("%1: set layout right margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, retransform) void CartesianPlotLegend::setLayoutVerticalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutVerticalSpacing) exec(new CartesianPlotLegendSetLayoutVerticalSpacingCmd(d, spacing, ki18n("%1: set layout vertical spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, retransform) void CartesianPlotLegend::setLayoutHorizontalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutHorizontalSpacing) exec(new CartesianPlotLegendSetLayoutHorizontalSpacingCmd(d, spacing, ki18n("%1: set layout horizontal spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutColumnCount, int, layoutColumnCount, retransform) void CartesianPlotLegend::setLayoutColumnCount(int count) { Q_D(CartesianPlotLegend); if (count != d->layoutColumnCount) exec(new CartesianPlotLegendSetLayoutColumnCountCmd(d, count, ki18n("%1: set layout column count"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlotLegend::visibilityChangedSlot() { Q_D(const CartesianPlotLegend); this->setVisible(!d->isVisible()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## CartesianPlotLegendPrivate::CartesianPlotLegendPrivate(CartesianPlotLegend *owner) : q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsFocusable); setAcceptHoverEvents(true); } QString CartesianPlotLegendPrivate::name() const { return q->name(); } QRectF CartesianPlotLegendPrivate::boundingRect() const { if (rotationAngle != 0) { QMatrix matrix; matrix.rotate(-rotationAngle); return matrix.mapRect(rect); } else return rect; } void CartesianPlotLegendPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } /*! Returns the shape of the CartesianPlotLegend as a QPainterPath in local coordinates */ QPainterPath CartesianPlotLegendPrivate::shape() const { QPainterPath path; if ( qFuzzyIsNull(borderCornerRadius) ) path.addRect(rect); else path.addRoundedRect(rect, borderCornerRadius, borderCornerRadius); if (rotationAngle != 0) { QTransform trafo; trafo.rotate(-rotationAngle); path = trafo.map(path); } return path; } bool CartesianPlotLegendPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the rectangular of the legend. */ void CartesianPlotLegendPrivate::retransform() { if (suppressRetransform) return; prepareGeometryChange(); curvesList.clear(); //add xy-curves for (auto* curve : q->m_plot->children()) { if (curve && curve->isVisible()) curvesList.push_back(curve); } //add histograms for (auto* hist : q->m_plot->children()) { if (hist && hist->isVisible()) curvesList.push_back(hist); } int curveCount = curvesList.size(); columnCount = (curveCount= curveCount ) break; const WorksheetElement* curve = dynamic_cast(curvesList.at(index)); if (curve) { if (!curve->isVisible()) continue; w = fm.boundingRect(curve->name()).width(); if (w>maxTextWidth) maxTextWidth = w; } } maxColumnTextWidths.append(maxTextWidth); legendWidth += maxTextWidth; } legendWidth += layoutLeftMargin + layoutRightMargin; //margins legendWidth += columnCount*lineSymbolWidth + layoutHorizontalSpacing; //width of the columns without the text legendWidth += (columnCount-1)*2*layoutHorizontalSpacing; //spacings between the columns if (title->isVisible() && !title->text().text.isEmpty()) { float titleWidth; if (rotationAngle == 0.0) titleWidth = title->graphicsItem()->boundingRect().width(); else { QRectF rect = title->graphicsItem()->boundingRect(); QMatrix matrix; matrix.rotate(-rotationAngle); rect = matrix.mapRect(rect); titleWidth = rect.width(); } if (titleWidth > legendWidth) legendWidth = titleWidth; } //determine the height of the legend float legendHeight = layoutTopMargin + layoutBottomMargin; //margins legendHeight += rowCount*h; //height of the rows legendHeight += (rowCount-1)*layoutVerticalSpacing; //spacing between the rows if (title->isVisible() && !title->text().text.isEmpty()) { if (rotationAngle == 0.0) legendHeight += title->graphicsItem()->boundingRect().height(); //legend title else { QRectF rect = title->graphicsItem()->boundingRect(); QMatrix matrix; matrix.rotate(-rotationAngle); rect = matrix.mapRect(rect); legendHeight += rect.height(); //legend title } } rect.setX(-legendWidth/2); rect.setY(-legendHeight/2); rect.setWidth(legendWidth); rect.setHeight(legendHeight); updatePosition(); } /*! calculates the position of the legend, when the position relative to the parent was specified (left, right, etc.) */ void CartesianPlotLegendPrivate::updatePosition() { //position the legend relative to the actual plot size minus small offset //TODO: make the offset dependent on the size of axis ticks. const QRectF parentRect = q->m_plot->dataRect(); - float hOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); - float vOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); + float hOffset = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point); + float vOffset = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point); - if (position.horizontalPosition != CartesianPlotLegend::hPositionCustom) { - if (position.horizontalPosition == CartesianPlotLegend::hPositionLeft) + if (position.horizontalPosition != WorksheetElement::HorizontalPosition::Custom) { + if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Left) position.point.setX(parentRect.x() + rect.width()/2 + hOffset); - else if (position.horizontalPosition == CartesianPlotLegend::hPositionCenter) + else if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Center) position.point.setX(parentRect.x() + parentRect.width()/2); - else if (position.horizontalPosition == CartesianPlotLegend::hPositionRight) + else if (position.horizontalPosition == WorksheetElement::HorizontalPosition::Right) position.point.setX(parentRect.x() + parentRect.width() - rect.width()/2 - hOffset); } - if (position.verticalPosition != CartesianPlotLegend::vPositionCustom) { - if (position.verticalPosition == CartesianPlotLegend::vPositionTop) + if (position.verticalPosition != WorksheetElement::VerticalPosition::Custom) { + if (position.verticalPosition == WorksheetElement::VerticalPosition::Top) position.point.setY(parentRect.y() + rect.height()/2 + vOffset); - else if (position.verticalPosition == CartesianPlotLegend::vPositionCenter) + else if (position.verticalPosition == WorksheetElement::VerticalPosition::Center) position.point.setY(parentRect.y() + parentRect.height()/2); - else if (position.verticalPosition == CartesianPlotLegend::vPositionBottom) + else if (position.verticalPosition == WorksheetElement::VerticalPosition::Bottom) position.point.setY(parentRect.y() + parentRect.height() - rect.height()/2 -vOffset); } suppressItemChangeEvent = true; setPos(position.point); suppressItemChangeEvent = false; emit q->positionChanged(position); suppressRetransform = true; title->retransform(); suppressRetransform = false; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the legend. \sa QGraphicsItem::paint(). */ void CartesianPlotLegendPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->save(); painter->rotate(-rotationAngle); //draw the area painter->setOpacity(backgroundOpacity); painter->setPen(Qt::NoPen); if (backgroundType == PlotArea::Color) { switch (backgroundColorStyle) { case PlotArea::SingleColor:{ painter->setBrush(QBrush(backgroundFirstColor)); break; } case PlotArea::HorizontalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient:{ QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, backgroundFirstColor); radialGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (backgroundType == PlotArea::Image) { if ( !backgroundFileName.trimmed().isEmpty() ) { QPixmap pix(backgroundFileName); switch (backgroundImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Centered: painter->drawPixmap(QPointF(rect.center().x()-pix.size().width()/2,rect.center().y()-pix.size().height()/2),pix); break; case PlotArea::Tiled: painter->drawTiledPixmap(rect,pix); break; case PlotArea::CenterTiled: painter->drawTiledPixmap(rect,pix,QPoint(rect.size().width()/2,rect.size().height()/2)); } } } else if (backgroundType == PlotArea::Pattern) { painter->setBrush(QBrush(backgroundFirstColor,backgroundBrushStyle)); } if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); //draw the border if (borderPen.style() != Qt::NoPen) { painter->setPen(borderPen); painter->setBrush(Qt::NoBrush); painter->setOpacity(borderOpacity); if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } //draw curve's line+symbol and the names int curveCount = curvesList.size(); QFontMetrics fm(labelFont); float h = fm.ascent(); painter->setFont(labelFont); //translate to left upper corner of the bounding rect plus the layout offset and the height of the title painter->translate(-rect.width()/2+layoutLeftMargin, -rect.height()/2+layoutTopMargin); if (title->isVisible() && !title->text().text.isEmpty()) painter->translate(0, title->graphicsItem()->boundingRect().height()); painter->save(); int index; for (int c = 0; c < columnCount; ++c) { for (int r = 0; r < rowCount; ++r) { if (labelColumnMajor) index = c*rowCount + r; else index = r*columnCount + c; if ( index >= curveCount ) break; //draw the legend item for histogram (simple rectangular with the sizes of the ascent) const Histogram* hist = dynamic_cast(curvesList.at(index)); if (hist) { //use line's pen (histogram bars, envelope or drop lines) if available, if (hist->lineType() != Histogram::NoLine && hist->linePen() != Qt::NoPen) { painter->setOpacity(hist->lineOpacity()); painter->setPen(hist->linePen()); } //for the brush, use the histogram filling or symbols filling or no brush if (hist->fillingEnabled()) painter->setBrush(QBrush(hist->fillingFirstColor(), hist->fillingBrushStyle())); else if (hist->symbolsStyle() != Symbol::NoSymbols) painter->setBrush(hist->symbolsBrush()); else painter->setBrush(Qt::NoBrush); painter->translate(QPointF(lineSymbolWidth/2, h/2)); painter->drawRect(QRectF(-h/2, -h/2, h, h)); painter->translate(-QPointF(lineSymbolWidth/2, h/2)); //curve's name painter->setPen(QPen(labelColor)); painter->setOpacity(1.0); //TODO: support HTML text? painter->drawText(QPoint(lineSymbolWidth + layoutHorizontalSpacing, h), hist->name()); painter->translate(0, layoutVerticalSpacing + h); continue; } //draw the legend item for histogram const XYCurve* curve = static_cast(curvesList.at(index)); //curve's line (painted at the half of the ascent size) if (curve->lineType() != XYCurve::NoLine) { painter->setPen(curve->linePen()); painter->setOpacity(curve->lineOpacity()); painter->drawLine(0, h/2, lineSymbolWidth, h/2); } //error bars if ( (curve->xErrorType() != XYCurve::NoError && curve->xErrorPlusColumn()) || (curve->yErrorType() != XYCurve::NoError && curve->yErrorPlusColumn()) ) { painter->setOpacity(curve->errorBarsOpacity()); painter->setPen(curve->errorBarsPen()); //curve's error bars for x - float errorBarsSize = Worksheet::convertToSceneUnits(10, Worksheet::Point); + float errorBarsSize = Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point); if (curve->symbolsStyle()!=Symbol::NoSymbols && errorBarsSizesymbolsSize()*1.4) errorBarsSize = curve->symbolsSize()*1.4; switch (curve->errorBarsType()) { case XYCurve::ErrorBarsSimple: //horiz. line if (curve->xErrorType() != XYCurve::NoError) painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //vert. line if (curve->yErrorType() != XYCurve::NoError) painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); break; case XYCurve::ErrorBarsWithEnds: //horiz. line if (curve->xErrorType() != XYCurve::NoError) { painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //caps for the horiz. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2-errorBarsSize/2, h/2+errorBarsSize/4); painter->drawLine(lineSymbolWidth/2+errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2+errorBarsSize/2, h/2+errorBarsSize/4); } //vert. line if (curve->yErrorType() != XYCurve::NoError) { painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); //caps for the vert. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2-errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2-errorBarsSize/2); painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2+errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2+errorBarsSize/2); } break; } } //curve's symbol if (curve->symbolsStyle()!=Symbol::NoSymbols) { painter->setOpacity(curve->symbolsOpacity()); painter->setBrush(curve->symbolsBrush()); painter->setPen(curve->symbolsPen()); QPainterPath path = Symbol::pathFromStyle(curve->symbolsStyle()); QTransform trafo; trafo.scale(curve->symbolsSize(), curve->symbolsSize()); path = trafo.map(path); if (curve->symbolsRotationAngle() != 0) { trafo.reset(); trafo.rotate(curve->symbolsRotationAngle()); path = trafo.map(path); } painter->translate(QPointF(lineSymbolWidth/2, h/2)); painter->drawPath(path); painter->translate(-QPointF(lineSymbolWidth/2, h/2)); } //curve's name painter->setPen(QPen(labelColor)); painter->setOpacity(1.0); //TODO: support HTML text? painter->drawText(QPoint(lineSymbolWidth+layoutHorizontalSpacing, h), curve->name()); painter->translate(0,layoutVerticalSpacing+h); } //translate to the beginning of the next column painter->restore(); int deltaX = lineSymbolWidth+layoutHorizontalSpacing+maxColumnTextWidths.at(c); //the width of the current columns deltaX += 2*layoutHorizontalSpacing; //spacing between two columns painter->translate(deltaX,0); painter->save(); } painter->restore(); painter->restore(); if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(shape()); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(shape()); } } QVariant CartesianPlotLegendPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates WorksheetElement::PositionWrapper tempPosition; tempPosition.point = value.toPointF(); - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void CartesianPlotLegendPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = pos(); if (point != position.point) { //position was changed -> set the position related member variables suppressRetransform = true; WorksheetElement::PositionWrapper tempPosition; tempPosition.point = point; - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } void CartesianPlotLegendPrivate::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ||event->key() == Qt::Key_Down) { const int delta = 5; QPointF point = pos(); WorksheetElement::PositionWrapper tempPosition; if (event->key() == Qt::Key_Left) { point.setX(point.x() - delta); - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; tempPosition.verticalPosition = position.verticalPosition; } else if (event->key() == Qt::Key_Right) { point.setX(point.x() + delta); - tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; + tempPosition.horizontalPosition = WorksheetElement::HorizontalPosition::Custom; tempPosition.verticalPosition = position.verticalPosition; } else if (event->key() == Qt::Key_Up) { point.setY(point.y() - delta); tempPosition.horizontalPosition = position.horizontalPosition; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; } else if (event->key() == Qt::Key_Down) { point.setY(point.y() + delta); tempPosition.horizontalPosition = position.horizontalPosition; - tempPosition.verticalPosition = WorksheetElement::vPositionCustom; + tempPosition.verticalPosition = WorksheetElement::VerticalPosition::Custom; } tempPosition.point = point; q->setPosition(tempPosition); } QGraphicsItem::keyPressEvent(event); } void CartesianPlotLegendPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void CartesianPlotLegendPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlotLegend::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlotLegend); writer->writeStartElement( "cartesianPlotLegend" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_QCOLOR(d->labelColor); WRITE_QFONT(d->labelFont); writer->writeAttribute( "columnMajor", QString::number(d->labelColumnMajor) ); writer->writeAttribute( "lineSymbolWidth", QString::number(d->lineSymbolWidth) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); - writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); - writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); + writer->writeAttribute( "horizontalPosition", QString::number(static_cast(d->position.horizontalPosition)) ); + writer->writeAttribute( "verticalPosition", QString::number(static_cast(d->position.verticalPosition)) ); writer->writeAttribute( "rotation", QString::number(d->rotationAngle) ); writer->writeEndElement(); //title d->title->save(writer); //background writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); //border writer->writeStartElement( "border" ); WRITE_QPEN(d->borderPen); writer->writeAttribute( "borderOpacity", QString::number(d->borderOpacity) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); writer->writeAttribute( "topMargin", QString::number(d->layoutTopMargin) ); writer->writeAttribute( "bottomMargin", QString::number(d->layoutBottomMargin) ); writer->writeAttribute( "leftMargin", QString::number(d->layoutLeftMargin) ); writer->writeAttribute( "rightMargin", QString::number(d->layoutRightMargin) ); writer->writeAttribute( "verticalSpacing", QString::number(d->layoutVerticalSpacing) ); writer->writeAttribute( "horizontalSpacing", QString::number(d->layoutHorizontalSpacing) ); writer->writeAttribute( "columnCount", QString::number(d->layoutColumnCount) ); writer->writeEndElement(); writer->writeEndElement(); // close "cartesianPlotLegend" section } //! Load from XML bool CartesianPlotLegend::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlotLegend); 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() == "cartesianPlotLegend") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_QCOLOR(d->labelColor); READ_QFONT(d->labelFont); str = attribs.value("columnMajor").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("columnMajor").toString()); else d->labelColumnMajor = str.toInt(); str = attribs.value("lineSymbolWidth").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("lineSymbolWidth").toString()); else d->lineSymbolWidth = str.toDouble(); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->position.point.setY(str.toDouble()); str = attribs.value("horizontalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("horizontalPosition").toString()); else d->position.horizontalPosition = (WorksheetElement::HorizontalPosition)str.toInt(); str = attribs.value("verticalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("verticalPosition").toString()); else d->position.verticalPosition = (WorksheetElement::VerticalPosition)str.toInt(); READ_DOUBLE_VALUE("rotation", rotationAngle); } else if (reader->name() == "textLabel") { if (!d->title->load(reader, preview)) { delete d->title; d->title = nullptr; return false; } } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("type").toString()); else d->backgroundType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("colorStyle").toString()); else d->backgroundColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("imageStyle").toString()); else d->backgroundImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("brushStyle").toString()); else d->backgroundBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("opacity").toString()); else d->backgroundOpacity = str.toDouble(); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_QPEN(d->borderPen); str = attribs.value("borderOpacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("borderOpacity").toString()); else d->borderOpacity = str.toDouble(); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); str = attribs.value("topMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("topMargin").toString()); else d->layoutTopMargin = str.toDouble(); str = attribs.value("bottomMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("bottomMargin").toString()); else d->layoutBottomMargin = str.toDouble(); str = attribs.value("leftMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("leftMargin").toString()); else d->layoutLeftMargin = str.toDouble(); str = attribs.value("rightMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("rightMargin").toString()); else d->layoutRightMargin = str.toDouble(); str = attribs.value("verticalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("verticalSpacing").toString()); else d->layoutVerticalSpacing = str.toDouble(); str = attribs.value("horizontalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("horizontalSpacing").toString()); else d->layoutHorizontalSpacing = str.toDouble(); str = attribs.value("columnCount").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("columnCount").toString()); else d->layoutColumnCount = str.toInt(); } } return true; } void CartesianPlotLegend::loadThemeConfig(const KConfig& config) { KConfigGroup groupLabel = config.group("Label"); this->setLabelColor(groupLabel.readEntry("FontColor", QColor(Qt::white))); const KConfigGroup group = config.group("CartesianPlot"); this->setBackgroundBrushStyle((Qt::BrushStyle)group.readEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle())); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle)(group.readEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()))); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor())); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle)group.readEntry("BackgroundImageStyle",(int) this->backgroundImageStyle())); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", this->backgroundOpacity())); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor())); this->setBackgroundType((PlotArea::BackgroundType)(group.readEntry("BackgroundType",(int) this->backgroundType()))); QPen pen = this->borderPen(); pen.setColor(group.readEntry("BorderColor", pen.color())); pen.setStyle((Qt::PenStyle)(group.readEntry("BorderStyle", (int) pen.style()))); pen.setWidthF(group.readEntry("BorderWidth", pen.widthF())); this->setBorderPen(pen); this->setBorderCornerRadius(group.readEntry("BorderCornerRadius", this->borderCornerRadius())); this->setBorderOpacity(group.readEntry("BorderOpacity", this->borderOpacity())); title()->loadThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h index 4ef753ef6..03fec3ffa 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h @@ -1,130 +1,130 @@ /*************************************************************************** File : CartesianPlotPrivate.h Project : LabPlot Description : Private members of CartesianPlot. -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) *******************************************************7*******************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ #ifndef CARTESIANPLOTPRIVATE_H #define CARTESIANPLOTPRIVATE_H #include "CartesianPlot.h" #include "CartesianCoordinateSystem.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include #include class CartesianPlotPrivate : public AbstractPlotPrivate { public: explicit CartesianPlotPrivate(CartesianPlot*); void retransform() override; void retransformScales(); void rangeChanged(); void xRangeFormatChanged(); void yRangeFormatChanged(); void mouseMoveZoomSelectionMode(QPointF logicalPos); void mouseMoveCursorMode(int cursorNumber, QPointF logicalPos); void mouseReleaseZoomSelectionMode(); void mouseHoverZoomSelectionMode(QPointF logicPos); void mouseHoverOutsideDataRect(); void mousePressZoomSelectionMode(QPointF logicalPos); void mousePressCursorMode(int cursorNumber, QPointF logicalPos); void updateCursor(); void setZoomSelectionBandShow(bool show); QRectF dataRect; CartesianPlot::RangeType rangeType{CartesianPlot::RangeFree}; CartesianPlot::RangeFormat xRangeFormat{CartesianPlot::Numeric}; CartesianPlot::RangeFormat yRangeFormat{CartesianPlot::Numeric}; QString xRangeDateTimeFormat; QString yRangeDateTimeFormat; int rangeFirstValues{1000}; int rangeLastValues{1000}; double xMin{0.0}, xMax{1.0}, yMin{0.0}, yMax{1.0}; float xMinPrev{0.0}, xMaxPrev{1.0}, yMinPrev{0.0}, yMaxPrev{1.0}; bool autoScaleX{true}, autoScaleY{true}; float autoScaleOffsetFactor{0.0f}; CartesianPlot::Scale xScale{CartesianPlot::ScaleLinear}, yScale{CartesianPlot::ScaleLinear}; bool xRangeBreakingEnabled{false}; bool yRangeBreakingEnabled{false}; CartesianPlot::RangeBreaks xRangeBreaks; CartesianPlot::RangeBreaks yRangeBreaks; QString theme; //cached values of minimum and maximum for all visible curves bool curvesXMinMaxIsDirty{false}, curvesYMinMaxIsDirty{false}; double curvesXMin{INFINITY}, curvesXMax{-INFINITY}, curvesYMin{INFINITY}, curvesYMax{-INFINITY}; CartesianPlot* const q; CartesianPlot::MouseMode mouseMode{CartesianPlot::SelectionMode}; CartesianCoordinateSystem* cSystem{nullptr}; bool suppressRetransform{false}; bool panningStarted{false}; bool locked{false}; // Cursor bool cursor0Enable{false}; int selectedCursor{0}; QPointF cursor0Pos{QPointF(NAN, NAN)}; bool cursor1Enable{false}; QPointF cursor1Pos{QPointF(NAN, NAN)}; - QPen cursorPen{Qt::red, Worksheet::convertToSceneUnits(1.0, Worksheet::Point), Qt::SolidLine}; + QPen cursorPen{Qt::red, Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point), Qt::SolidLine}; signals: void mousePressZoomSelectionModeSignal(QPointF logicalPos); void mousePressCursorModeSignal(QPointF logicalPos); private: QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; void mousePressEvent(QGraphicsSceneMouseEvent*) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; void wheelEvent(QGraphicsSceneWheelEvent*) override; void keyPressEvent(QKeyEvent*) override; void hoverMoveEvent(QGraphicsSceneHoverEvent*) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr) override; void updateDataRect(); void checkXRange(); void checkYRange(); CartesianScale* createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd); bool m_insideDataRect{false}; bool m_selectionBandIsShown{false}; QPointF m_selectionStart; QPointF m_selectionEnd; QLineF m_selectionStartLine; QPointF m_panningStart; QStaticText m_cursor0Text{"1"}; QStaticText m_cursor1Text{"2"}; }; #endif diff --git a/src/backend/worksheet/plots/cartesian/CustomPoint.cpp b/src/backend/worksheet/plots/cartesian/CustomPoint.cpp index 0f7d550e8..3401235ed 100644 --- a/src/backend/worksheet/plots/cartesian/CustomPoint.cpp +++ b/src/backend/worksheet/plots/cartesian/CustomPoint.cpp @@ -1,476 +1,476 @@ /*************************************************************************** File : CustomPoint.cpp Project : LabPlot Description : Custom user-defined point on the plot -------------------------------------------------------------------- Copyright : (C) 2015 Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015 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 "CustomPoint.h" #include "CustomPointPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include /** * \class CustomPoint * \brief A customizable point. * * The position can be either specified by mouse events or by providing the * x- and y- coordinates in parent's coordinate system */ CustomPoint::CustomPoint(const CartesianPlot* plot, const QString& name) : WorksheetElement(name, AspectType::CustomPoint), d_ptr(new CustomPointPrivate(this, plot)) { init(); } CustomPoint::CustomPoint(const QString& name, CustomPointPrivate* dd) : WorksheetElement(name, AspectType::CustomPoint), d_ptr(dd) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene CustomPoint::~CustomPoint() = default; void CustomPoint::init() { Q_D(CustomPoint); KConfig config; KConfigGroup group; group = config.group("CustomPoint"); d->position.setX( group.readEntry("PositionXValue", d->plot->xMin() + (d->plot->xMax()-d->plot->xMin())/2) ); d->position.setY( group.readEntry("PositionYValue", d->plot->yMin() + (d->plot->yMax()-d->plot->yMin())/2) ); d->symbolStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::Circle); - d->symbolSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); + d->symbolSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point)); d->symbolRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::red)) ); d->symbolPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); - d->symbolPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); + d->symbolPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point)) ); this->initActions(); } void CustomPoint::initActions() { visibilityAction = new QAction(i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CustomPoint::visibilityChanged); } /*! Returns an icon to be used in the project explorer. */ QIcon CustomPoint::icon() const { return QIcon::fromTheme("draw-cross"); } QMenu* CustomPoint::createContextMenu() { QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } QGraphicsItem* CustomPoint::graphicsItem() const { return d_ptr; } void CustomPoint::retransform() { Q_D(CustomPoint); d->retransform(); } void CustomPoint::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(CustomPoint, QPointF, position, position) //symbols BASIC_SHARED_D_READER_IMPL(CustomPoint, Symbol::Style, symbolStyle, symbolStyle) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolOpacity, symbolOpacity) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolRotationAngle, symbolRotationAngle) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolSize, symbolSize) CLASS_SHARED_D_READER_IMPL(CustomPoint, QBrush, symbolBrush, symbolBrush) CLASS_SHARED_D_READER_IMPL(CustomPoint, QPen, symbolPen, symbolPen) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetPosition, QPointF, position, retransform) void CustomPoint::setPosition(QPointF position) { Q_D(CustomPoint); if (position != d->position) exec(new CustomPointSetPositionCmd(d, position, ki18n("%1: set position"))); } //Symbol STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolStyle, Symbol::Style, symbolStyle, recalcShapeAndBoundingRect) void CustomPoint::setSymbolStyle(Symbol::Style style) { Q_D(CustomPoint); if (style != d->symbolStyle) exec(new CustomPointSetSymbolStyleCmd(d, style, ki18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolSize, qreal, symbolSize, recalcShapeAndBoundingRect) void CustomPoint::setSymbolSize(qreal size) { Q_D(CustomPoint); if (!qFuzzyCompare(1 + size, 1 + d->symbolSize)) exec(new CustomPointSetSymbolSizeCmd(d, size, ki18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolRotationAngle, qreal, symbolRotationAngle, recalcShapeAndBoundingRect) void CustomPoint::setSymbolRotationAngle(qreal angle) { Q_D(CustomPoint); if (!qFuzzyCompare(1 + angle, 1 + d->symbolRotationAngle)) exec(new CustomPointSetSymbolRotationAngleCmd(d, angle, ki18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolBrush, QBrush, symbolBrush, update) void CustomPoint::setSymbolBrush(const QBrush &brush) { Q_D(CustomPoint); if (brush != d->symbolBrush) exec(new CustomPointSetSymbolBrushCmd(d, brush, ki18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolPen, QPen, symbolPen, recalcShapeAndBoundingRect) void CustomPoint::setSymbolPen(const QPen &pen) { Q_D(CustomPoint); if (pen != d->symbolPen) exec(new CustomPointSetSymbolPenCmd(d, pen, ki18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolOpacity, qreal, symbolOpacity, update) void CustomPoint::setSymbolOpacity(qreal opacity) { Q_D(CustomPoint); if (opacity != d->symbolOpacity) exec(new CustomPointSetSymbolOpacityCmd(d, opacity, ki18n("%1: set symbol opacity"))); } STD_SWAP_METHOD_SETTER_CMD_IMPL_F(CustomPoint, SetVisible, bool, swapVisible, update); void CustomPoint::setVisible(bool on) { Q_D(CustomPoint); exec(new CustomPointSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool CustomPoint::isVisible() const { Q_D(const CustomPoint); return d->isVisible(); } void CustomPoint::setPrinting(bool on) { Q_D(CustomPoint); d->m_printing = on; } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CustomPoint::visibilityChanged() { Q_D(const CustomPoint); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## CustomPointPrivate::CustomPointPrivate(CustomPoint* owner, const CartesianPlot* p) : plot(p), q(owner) { setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); } QString CustomPointPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the item/point. Called on geometry or properties changes. */ void CustomPointPrivate::retransform() { if (suppressRetransform) return; //calculate the point in the scene coordinates const auto* cSystem = dynamic_cast(plot->coordinateSystem()); QVector listScene = cSystem->mapLogicalToScene(QVector{position}); if (!listScene.isEmpty()) { m_visible = true; positionScene = listScene.at(0); suppressItemChangeEvent = true; setPos(positionScene); suppressItemChangeEvent = false; } else m_visible = false; recalcShapeAndBoundingRect(); } bool CustomPointPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF CustomPointPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath CustomPointPrivate::shape() const { return pointShape; } /*! recalculates the outer bounds and the shape of the item. */ void CustomPointPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); pointShape = QPainterPath(); if (m_visible && symbolStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolStyle); QTransform trafo; trafo.scale(symbolSize, symbolSize); path = trafo.map(path); trafo.reset(); if (symbolRotationAngle != 0) { trafo.rotate(symbolRotationAngle); path = trafo.map(path); } pointShape.addPath(WorksheetElement::shapeFromPath(trafo.map(path), symbolPen)); transformedBoundingRectangle = pointShape.boundingRect(); } } void CustomPointPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!m_visible) return; if (symbolStyle != Symbol::NoSymbols) { painter->setOpacity(symbolOpacity); painter->setPen(symbolPen); painter->setBrush(symbolBrush); painter->drawPath(pointShape); } if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(pointShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(pointShape); } } QVariant CustomPointPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. const auto* cSystem = dynamic_cast(plot->coordinateSystem()); emit q->positionChanged(cSystem->mapSceneToLogical(value.toPointF())); } return QGraphicsItem::itemChange(change, value); } void CustomPointPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //position was changed -> set the position member variables suppressRetransform = true; const auto* cSystem = dynamic_cast(plot->coordinateSystem()); q->setPosition(cSystem->mapSceneToLogical(pos())); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } void CustomPointPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void CustomPointPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void CustomPointPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CustomPoint::save(QXmlStreamWriter* writer) const { Q_D(const CustomPoint); writer->writeStartElement("customPoint"); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement("geometry"); writer->writeAttribute( "x", QString::number(d->position.x()) ); writer->writeAttribute( "y", QString::number(d->position.y()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Symbols writer->writeStartElement("symbol"); writer->writeAttribute( "symbolStyle", QString::number(d->symbolStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolSize) ); WRITE_QBRUSH(d->symbolBrush); WRITE_QPEN(d->symbolPen); writer->writeEndElement(); writer->writeEndElement(); // close "CustomPoint" section } //! Load from XML bool CustomPoint::load(XmlStreamReader* reader, bool preview) { Q_D(CustomPoint); 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() == "customPoint") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->position.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->position.setY(str.toDouble()); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "symbol") { attribs = reader->attributes(); str = attribs.value("symbolStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("symbolStyle").toString()); else d->symbolStyle = (Symbol::Style)str.toInt(); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("opacity").toString()); else d->symbolOpacity = str.toDouble(); str = attribs.value("rotation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("rotation").toString()); else d->symbolRotationAngle = str.toDouble(); str = attribs.value("size").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("size").toString()); else d->symbolSize = str.toDouble(); READ_QBRUSH(d->symbolBrush); READ_QPEN(d->symbolPen); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } retransform(); return true; } diff --git a/src/backend/worksheet/plots/cartesian/Histogram.cpp b/src/backend/worksheet/plots/cartesian/Histogram.cpp index 29a8d3948..d72cd0d7c 100644 --- a/src/backend/worksheet/plots/cartesian/Histogram.cpp +++ b/src/backend/worksheet/plots/cartesian/Histogram.cpp @@ -1,1867 +1,1867 @@ /*************************************************************************** File : Histogram.cpp Project : LabPlot Description : Histogram -------------------------------------------------------------------- Copyright : (C) 2016 Anu Mittal (anu22mittal@gmail.com) Copyright : (C) 2016-2018 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 by Garvit Khatri (garvitdelhi@gmail.com) ***************************************************************************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ /*! \class Histogram \brief A 2D-curve, provides an interface for editing many properties of the curve. \ingroup worksheet */ #include "Histogram.h" #include "HistogramPrivate.h" #include "backend/core/column/Column.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/lib/commandtemplates.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "tools/ImageTools.h" #include "backend/lib/trace.h" #include #include #include #include #include #include #include extern "C" { #include #include #include } Histogram::Histogram(const QString &name) : WorksheetElement(name, AspectType::Histogram), d_ptr(new HistogramPrivate(this)) { init(); } Histogram::Histogram(const QString &name, HistogramPrivate *dd) : WorksheetElement(name, AspectType::Histogram), d_ptr(dd) { init(); } void Histogram::init() { Q_D(Histogram); KConfig config; KConfigGroup group = config.group("Histogram"); d->dataColumn = nullptr; d->type = (Histogram::HistogramType) group.readEntry("Type", (int)Histogram::Ordinary); d->orientation = (Histogram::HistogramOrientation) group.readEntry("Orientation", (int)Histogram::Vertical); d->binningMethod = (Histogram::BinningMethod) group.readEntry("BinningMethod", (int)Histogram::SquareRoot); d->binCount = group.readEntry("BinCount", 10); d->binWidth = group.readEntry("BinWidth", 1.0f); d->autoBinRanges = group.readEntry("AutoBinRanges", true); d->binRangesMin = 0.0; d->binRangesMax = 1.0; d->lineType = (Histogram::LineType) group.readEntry("LineType", (int)Histogram::Bars); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int)Qt::SolidLine) ); d->linePen.setColor( group.readEntry("LineColor", QColor(Qt::black)) ); - d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); + d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->symbolsStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::NoSymbols); - d->symbolsSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); + d->symbolsSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point)); d->symbolsRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolsOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolsBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolsBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::black)) ); d->symbolsPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolsPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); - d->symbolsPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); + d->symbolsPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point)) ); d->valuesType = (Histogram::ValuesType) group.readEntry("ValuesType", (int)Histogram::NoValues); d->valuesColumn = nullptr; d->valuesPosition = (Histogram::ValuesPosition) group.readEntry("ValuesPosition", (int)Histogram::ValuesAbove); - d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Point)); + d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point)); d->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); d->valuesNumericFormat = group.readEntry("ValuesNumericFormat", "f").at(0).toLatin1(); d->valuesPrecision = group.readEntry("ValuesNumericFormat", 2); d->valuesDateTimeFormat = group.readEntry("ValuesDateTimeFormat", "yyyy-MM-dd"); d->valuesPrefix = group.readEntry("ValuesPrefix", ""); d->valuesSuffix = group.readEntry("ValuesSuffix", ""); d->valuesFont = group.readEntry("ValuesFont", QFont()); - d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Point ) ); + d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Unit::Point ) ); d->valuesColor = group.readEntry("ValuesColor", QColor(Qt::black)); d->fillingEnabled = group.readEntry("FillingEnabled", true); d->fillingType = (PlotArea::BackgroundType) group.readEntry("FillingType", (int)PlotArea::Color); d->fillingColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("FillingColorStyle", (int) PlotArea::SingleColor); d->fillingImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("FillingImageStyle", (int) PlotArea::Scaled); d->fillingBrushStyle = (Qt::BrushStyle) group.readEntry("FillingBrushStyle", (int) Qt::SolidPattern); d->fillingFileName = group.readEntry("FillingFileName", QString()); d->fillingFirstColor = group.readEntry("FillingFirstColor", QColor(Qt::white)); d->fillingSecondColor = group.readEntry("FillingSecondColor", QColor(Qt::black)); d->fillingOpacity = group.readEntry("FillingOpacity", 1.0); d->errorType = (Histogram::ErrorType) group.readEntry("ErrorType", (int)Histogram::NoError); d->errorBarsType = (XYCurve::ErrorBarsType) group.readEntry("ErrorBarsType", (int)XYCurve::ErrorBarsSimple); - d->errorBarsCapSize = group.readEntry( "ErrorBarsCapSize", Worksheet::convertToSceneUnits(10, Worksheet::Point) ); + d->errorBarsCapSize = group.readEntry( "ErrorBarsCapSize", Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point) ); d->errorBarsPen.setStyle( (Qt::PenStyle)group.readEntry("ErrorBarsStyle", (int)Qt::SolidLine) ); d->errorBarsPen.setColor( group.readEntry("ErrorBarsColor", QColor(Qt::black)) ); - d->errorBarsPen.setWidthF( group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); + d->errorBarsPen.setWidthF( group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)) ); d->errorBarsOpacity = group.readEntry("ErrorBarsOpacity", 1.0); this->initActions(); } void Histogram::initActions() { visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Histogram::visibilityChangedSlot); } QMenu* Histogram::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon Histogram::icon() const { return QIcon::fromTheme("view-object-histogram-linear"); } QGraphicsItem* Histogram::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(Histogram, SetVisible, bool, swapVisible) void Histogram::setVisible(bool on) { Q_D(Histogram); exec(new HistogramSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool Histogram::isVisible() const { Q_D(const Histogram); return d->isVisible(); } void Histogram::setPrinting(bool on) { Q_D(Histogram); d->m_printing = on; } //############################################################################## //########################## getter methods ################################## //############################################################################## //general BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::HistogramType, type, type) BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::HistogramOrientation, orientation, orientation) BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::BinningMethod, binningMethod, binningMethod) BASIC_SHARED_D_READER_IMPL(Histogram, int, binCount, binCount) BASIC_SHARED_D_READER_IMPL(Histogram, float, binWidth, binWidth) BASIC_SHARED_D_READER_IMPL(Histogram, bool, autoBinRanges, autoBinRanges) BASIC_SHARED_D_READER_IMPL(Histogram, double, binRangesMin, binRangesMin) BASIC_SHARED_D_READER_IMPL(Histogram, double, binRangesMax, binRangesMax) BASIC_SHARED_D_READER_IMPL(Histogram, const AbstractColumn*, dataColumn, dataColumn) QString& Histogram::dataColumnPath() const { return d_ptr->dataColumnPath; } //line BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::LineType, lineType, lineType) CLASS_SHARED_D_READER_IMPL(Histogram, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, lineOpacity, lineOpacity) //symbols BASIC_SHARED_D_READER_IMPL(Histogram, Symbol::Style, symbolsStyle, symbolsStyle) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, symbolsOpacity, symbolsOpacity) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, symbolsRotationAngle, symbolsRotationAngle) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, symbolsSize, symbolsSize) CLASS_SHARED_D_READER_IMPL(Histogram, QBrush, symbolsBrush, symbolsBrush) CLASS_SHARED_D_READER_IMPL(Histogram, QPen, symbolsPen, symbolsPen) //values BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::ValuesType, valuesType, valuesType) BASIC_SHARED_D_READER_IMPL(Histogram, const AbstractColumn *, valuesColumn, valuesColumn) QString& Histogram::valuesColumnPath() const { return d_ptr->valuesColumnPath; } BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::ValuesPosition, valuesPosition, valuesPosition) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesDistance, valuesDistance) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesRotationAngle, valuesRotationAngle) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesOpacity, valuesOpacity) CLASS_SHARED_D_READER_IMPL(Histogram, char, valuesNumericFormat, valuesNumericFormat) BASIC_SHARED_D_READER_IMPL(Histogram, int, valuesPrecision, valuesPrecision) CLASS_SHARED_D_READER_IMPL(Histogram, QString, valuesDateTimeFormat, valuesDateTimeFormat) CLASS_SHARED_D_READER_IMPL(Histogram, QString, valuesPrefix, valuesPrefix) CLASS_SHARED_D_READER_IMPL(Histogram, QString, valuesSuffix, valuesSuffix) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, valuesColor, valuesColor) CLASS_SHARED_D_READER_IMPL(Histogram, QFont, valuesFont, valuesFont) //filling BASIC_SHARED_D_READER_IMPL(Histogram, bool, fillingEnabled, fillingEnabled) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundType, fillingType, fillingType) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundColorStyle, fillingColorStyle, fillingColorStyle) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundImageStyle, fillingImageStyle, fillingImageStyle) CLASS_SHARED_D_READER_IMPL(Histogram, Qt::BrushStyle, fillingBrushStyle, fillingBrushStyle) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, fillingFirstColor, fillingFirstColor) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, fillingSecondColor, fillingSecondColor) CLASS_SHARED_D_READER_IMPL(Histogram, QString, fillingFileName, fillingFileName) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, fillingOpacity, fillingOpacity) //error bars BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::ErrorType, errorType, errorType) BASIC_SHARED_D_READER_IMPL(Histogram, XYCurve::ErrorBarsType, errorBarsType, errorBarsType) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, errorBarsCapSize, errorBarsCapSize) CLASS_SHARED_D_READER_IMPL(Histogram, QPen, errorBarsPen, errorBarsPen) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, errorBarsOpacity, errorBarsOpacity) double Histogram::getYMaximum() const { return d_ptr->getYMaximum(); } double Histogram::getYMinimum() const { return d_ptr->getYMinimum(); } double Histogram::getXMaximum() const { return d_ptr->getXMaximum(); } double Histogram::getXMinimum() const { return d_ptr->getXMinimum(); } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## //General STD_SETTER_CMD_IMPL_F_S(Histogram, SetDataColumn, const AbstractColumn*, dataColumn, recalcHistogram) void Histogram::setDataColumn(const AbstractColumn* column) { Q_D(Histogram); if (column != d->dataColumn) { exec(new HistogramSetDataColumnCmd(d, column, ki18n("%1: set data column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Histogram::dataChanged); //update the curve itself on changes connect(column, &AbstractColumn::dataChanged, this, &Histogram::recalcHistogram); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Histogram::dataColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Histogram, SetHistogramType, Histogram::HistogramType, type, updateType) void Histogram::setType(Histogram::HistogramType type) { Q_D(Histogram); if (type != d->type) exec(new HistogramSetHistogramTypeCmd(d, type, ki18n("%1: set histogram type"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetHistogramOrientation, Histogram::HistogramOrientation, orientation, updateOrientation) void Histogram::setOrientation(Histogram::HistogramOrientation orientation) { Q_D(Histogram); if (orientation != d->orientation) exec(new HistogramSetHistogramOrientationCmd(d, orientation, ki18n("%1: set histogram orientation"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinningMethod, Histogram::BinningMethod, binningMethod, recalcHistogram) void Histogram::setBinningMethod(Histogram::BinningMethod method) { Q_D(Histogram); if (method != d->binningMethod) exec(new HistogramSetBinningMethodCmd(d, method, ki18n("%1: set binning method"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinCount, int, binCount, recalcHistogram) void Histogram::setBinCount(int count) { Q_D(Histogram); if (count != d->binCount) exec(new HistogramSetBinCountCmd(d, count, ki18n("%1: set bin count"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinWidth, float, binWidth, recalcHistogram) void Histogram::setBinWidth(float width) { Q_D(Histogram); if (width != d->binWidth) exec(new HistogramSetBinWidthCmd(d, width, ki18n("%1: set bin width"))); } class HistogramSetAutoBinRangesCmd : public QUndoCommand { public: HistogramSetAutoBinRangesCmd(HistogramPrivate* private_obj, bool autoBinRanges) : m_private(private_obj), m_autoBinRanges(autoBinRanges) { setText(i18n("%1: change auto bin ranges", m_private->name())); }; void redo() override { m_autoBinRangesOld = m_private->autoBinRanges; if (m_autoBinRanges) { m_binRangesMinOld = m_private->binRangesMin; m_binRangesMaxOld = m_private->binRangesMax; m_private->q->recalcHistogram(); } m_private->autoBinRanges = m_autoBinRanges; emit m_private->q->autoBinRangesChanged(m_autoBinRanges); }; void undo() override { if (!m_autoBinRangesOld) { m_private->binRangesMin = m_binRangesMinOld; m_private->binRangesMax = m_binRangesMaxOld; m_private->recalcHistogram(); } m_private->autoBinRanges = m_autoBinRangesOld; emit m_private->q->autoBinRangesChanged(m_autoBinRangesOld); } private: HistogramPrivate* m_private; bool m_autoBinRanges; bool m_autoBinRangesOld{false}; double m_binRangesMinOld{0.0}; double m_binRangesMaxOld{0.0}; }; void Histogram::setAutoBinRanges(bool autoBinRanges) { Q_D(Histogram); if (autoBinRanges != d->autoBinRanges) exec(new HistogramSetAutoBinRangesCmd(d, autoBinRanges)); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinRangesMin, double, binRangesMin, recalcHistogram) void Histogram::setBinRangesMin(double binRangesMin) { Q_D(Histogram); if (binRangesMin != d->binRangesMin) exec(new HistogramSetBinRangesMinCmd(d, binRangesMin, ki18n("%1: set bin ranges start"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetBinRangesMax, double, binRangesMax, recalcHistogram) void Histogram::setBinRangesMax(double binRangesMax) { Q_D(Histogram); if (binRangesMax != d->binRangesMax) exec(new HistogramSetBinRangesMaxCmd(d, binRangesMax, ki18n("%1: set bin ranges end"))); } //Line STD_SETTER_CMD_IMPL_F_S(Histogram, SetLineType, Histogram::LineType, lineType, updateLines) void Histogram::setLineType(LineType type) { Q_D(Histogram); if (type != d->lineType) exec(new HistogramSetLineTypeCmd(d, type, ki18n("%1: line type changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect) void Histogram::setLinePen(const QPen &pen) { Q_D(Histogram); if (pen != d->linePen) exec(new HistogramSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetLineOpacity, qreal, lineOpacity, updatePixmap); void Histogram::setLineOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->lineOpacity) exec(new HistogramSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } // Symbols STD_SETTER_CMD_IMPL_F_S(Histogram, SetSymbolsStyle, Symbol::Style, symbolsStyle, updateSymbols) void Histogram::setSymbolsStyle(Symbol::Style style) { Q_D(Histogram); if (style != d->symbolsStyle) exec(new HistogramSetSymbolsStyleCmd(d, style, ki18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetSymbolsSize, qreal, symbolsSize, updateSymbols) void Histogram::setSymbolsSize(qreal size) { Q_D(Histogram); if (!qFuzzyCompare(1 + size, 1 + d->symbolsSize)) exec(new HistogramSetSymbolsSizeCmd(d, size, ki18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetSymbolsRotationAngle, qreal, symbolsRotationAngle, updateSymbols) void Histogram::setSymbolsRotationAngle(qreal angle) { Q_D(Histogram); if (!qFuzzyCompare(1 + angle, 1 + d->symbolsRotationAngle)) exec(new HistogramSetSymbolsRotationAngleCmd(d, angle, ki18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetSymbolsBrush, QBrush, symbolsBrush, updatePixmap) void Histogram::setSymbolsBrush(const QBrush &brush) { Q_D(Histogram); if (brush != d->symbolsBrush) exec(new HistogramSetSymbolsBrushCmd(d, brush, ki18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetSymbolsPen, QPen, symbolsPen, updateSymbols) void Histogram::setSymbolsPen(const QPen &pen) { Q_D(Histogram); if (pen != d->symbolsPen) exec(new HistogramSetSymbolsPenCmd(d, pen, ki18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetSymbolsOpacity, qreal, symbolsOpacity, updatePixmap) void Histogram::setSymbolsOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->symbolsOpacity) exec(new HistogramSetSymbolsOpacityCmd(d, opacity, ki18n("%1: set symbols opacity"))); } //Values STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesType, Histogram::ValuesType, valuesType, updateValues) void Histogram::setValuesType(Histogram::ValuesType type) { Q_D(Histogram); if (type != d->valuesType) exec(new HistogramSetValuesTypeCmd(d, type, ki18n("%1: set values type"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesColumn, const AbstractColumn*, valuesColumn, updateValues) void Histogram::setValuesColumn(const AbstractColumn* column) { Q_D(Histogram); if (column != d->valuesColumn) { exec(new HistogramSetValuesColumnCmd(d, column, ki18n("%1: set values column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Histogram::updateValues); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Histogram::valuesColumnAboutToBeRemoved); } } } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesPosition, Histogram::ValuesPosition, valuesPosition, updateValues) void Histogram::setValuesPosition(ValuesPosition position) { Q_D(Histogram); if (position != d->valuesPosition) exec(new HistogramSetValuesPositionCmd(d, position, ki18n("%1: set values position"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesDistance, qreal, valuesDistance, updateValues) void Histogram::setValuesDistance(qreal distance) { Q_D(Histogram); if (distance != d->valuesDistance) exec(new HistogramSetValuesDistanceCmd(d, distance, ki18n("%1: set values distance"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues) void Histogram::setValuesRotationAngle(qreal angle) { Q_D(Histogram); if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle)) exec(new HistogramSetValuesRotationAngleCmd(d, angle, ki18n("%1: rotate values"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesOpacity, qreal, valuesOpacity, updatePixmap) void Histogram::setValuesOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->valuesOpacity) exec(new HistogramSetValuesOpacityCmd(d, opacity, ki18n("%1: set values opacity"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesNumericFormat, char, valuesNumericFormat, updateValues) void Histogram::setValuesNumericFormat(char format) { Q_D(Histogram); if (format != d->valuesNumericFormat) exec(new HistogramSetValuesNumericFormatCmd(d, format, ki18n("%1: set values numeric format"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesPrecision, int, valuesPrecision, updateValues) void Histogram::setValuesPrecision(int precision) { Q_D(Histogram); if (precision != d->valuesPrecision) exec(new HistogramSetValuesPrecisionCmd(d, precision, ki18n("%1: set values precision"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesDateTimeFormat, QString, valuesDateTimeFormat, updateValues) void Histogram::setValuesDateTimeFormat(const QString& format) { Q_D(Histogram); if (format != d->valuesDateTimeFormat) exec(new HistogramSetValuesDateTimeFormatCmd(d, format, ki18n("%1: set values datetime format"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesPrefix, QString, valuesPrefix, updateValues) void Histogram::setValuesPrefix(const QString& prefix) { Q_D(Histogram); if (prefix!= d->valuesPrefix) exec(new HistogramSetValuesPrefixCmd(d, prefix, ki18n("%1: set values prefix"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesSuffix, QString, valuesSuffix, updateValues) void Histogram::setValuesSuffix(const QString& suffix) { Q_D(Histogram); if (suffix!= d->valuesSuffix) exec(new HistogramSetValuesSuffixCmd(d, suffix, ki18n("%1: set values suffix"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesFont, QFont, valuesFont, updateValues) void Histogram::setValuesFont(const QFont& font) { Q_D(Histogram); if (font!= d->valuesFont) exec(new HistogramSetValuesFontCmd(d, font, ki18n("%1: set values font"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesColor, QColor, valuesColor, updatePixmap) void Histogram::setValuesColor(const QColor& color) { Q_D(Histogram); if (color != d->valuesColor) exec(new HistogramSetValuesColorCmd(d, color, ki18n("%1: set values color"))); } //Filling STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingEnabled, bool, fillingEnabled, updateFilling) void Histogram::setFillingEnabled(bool enabled) { Q_D(Histogram); if (enabled != d->fillingEnabled) exec(new HistogramSetFillingEnabledCmd(d, enabled, ki18n("%1: filling changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingType, PlotArea::BackgroundType, fillingType, updatePixmap) void Histogram::setFillingType(PlotArea::BackgroundType type) { Q_D(Histogram); if (type != d->fillingType) exec(new HistogramSetFillingTypeCmd(d, type, ki18n("%1: filling type changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingColorStyle, PlotArea::BackgroundColorStyle, fillingColorStyle, updatePixmap) void Histogram::setFillingColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(Histogram); if (style != d->fillingColorStyle) exec(new HistogramSetFillingColorStyleCmd(d, style, ki18n("%1: filling color style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingImageStyle, PlotArea::BackgroundImageStyle, fillingImageStyle, updatePixmap) void Histogram::setFillingImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(Histogram); if (style != d->fillingImageStyle) exec(new HistogramSetFillingImageStyleCmd(d, style, ki18n("%1: filling image style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingBrushStyle, Qt::BrushStyle, fillingBrushStyle, updatePixmap) void Histogram::setFillingBrushStyle(Qt::BrushStyle style) { Q_D(Histogram); if (style != d->fillingBrushStyle) exec(new HistogramSetFillingBrushStyleCmd(d, style, ki18n("%1: filling brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingFirstColor, QColor, fillingFirstColor, updatePixmap) void Histogram::setFillingFirstColor(const QColor& color) { Q_D(Histogram); if (color!= d->fillingFirstColor) exec(new HistogramSetFillingFirstColorCmd(d, color, ki18n("%1: set filling first color"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingSecondColor, QColor, fillingSecondColor, updatePixmap) void Histogram::setFillingSecondColor(const QColor& color) { Q_D(Histogram); if (color!= d->fillingSecondColor) exec(new HistogramSetFillingSecondColorCmd(d, color, ki18n("%1: set filling second color"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingFileName, QString, fillingFileName, updatePixmap) void Histogram::setFillingFileName(const QString& fileName) { Q_D(Histogram); if (fileName!= d->fillingFileName) exec(new HistogramSetFillingFileNameCmd(d, fileName, ki18n("%1: set filling image"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingOpacity, qreal, fillingOpacity, updatePixmap) void Histogram::setFillingOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->fillingOpacity) exec(new HistogramSetFillingOpacityCmd(d, opacity, ki18n("%1: set filling opacity"))); } //Error bars STD_SETTER_CMD_IMPL_F_S(Histogram, SetErrorType, Histogram::ErrorType, errorType, updateErrorBars) void Histogram::setErrorType(ErrorType type) { Q_D(Histogram); if (type != d->errorType) exec(new HistogramSetErrorTypeCmd(d, type, ki18n("%1: x-error type changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetErrorBarsCapSize, qreal, errorBarsCapSize, updateErrorBars) void Histogram::setErrorBarsCapSize(qreal size) { Q_D(Histogram); if (size != d->errorBarsCapSize) exec(new HistogramSetErrorBarsCapSizeCmd(d, size, ki18n("%1: set error bar cap size"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetErrorBarsType, XYCurve::ErrorBarsType, errorBarsType, updateErrorBars) void Histogram::setErrorBarsType(XYCurve::ErrorBarsType type) { Q_D(Histogram); if (type != d->errorBarsType) exec(new HistogramSetErrorBarsTypeCmd(d, type, ki18n("%1: error bar type changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetErrorBarsPen, QPen, errorBarsPen, recalcShapeAndBoundingRect) void Histogram::setErrorBarsPen(const QPen& pen) { Q_D(Histogram); if (pen != d->errorBarsPen) exec(new HistogramSetErrorBarsPenCmd(d, pen, ki18n("%1: set error bar style"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetErrorBarsOpacity, qreal, errorBarsOpacity, updatePixmap) void Histogram::setErrorBarsOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->errorBarsOpacity) exec(new HistogramSetErrorBarsOpacityCmd(d, opacity, ki18n("%1: set error bar opacity"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## void Histogram::retransform() { d_ptr->retransform(); } void Histogram::recalcHistogram() { d_ptr->recalcHistogram(); } //TODO void Histogram::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(pageResize); Q_UNUSED(verticalRatio); Q_D(const Histogram); //setValuesDistance(d->distance*); QFont font = d->valuesFont; font.setPointSizeF(font.pointSizeF()*horizontalRatio); setValuesFont(font); retransform(); } void Histogram::updateValues() { d_ptr->updateValues(); } void Histogram::dataColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Histogram); if (aspect == d->dataColumn) { d->dataColumn = nullptr; d->retransform(); } } void Histogram::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Histogram); if (aspect == d->valuesColumn) { d->valuesColumn = nullptr; d->updateValues(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Histogram::visibilityChangedSlot() { Q_D(const Histogram); this->setVisible(!d->isVisible()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## HistogramPrivate::HistogramPrivate(Histogram *owner) : q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(true); } HistogramPrivate::~HistogramPrivate() { if (m_histogram) gsl_histogram_free(m_histogram); } QString HistogramPrivate::name() const { return q->name(); } QRectF HistogramPrivate::boundingRect() const { return boundingRectangle; } double HistogramPrivate::getMaximumOccuranceofHistogram() { if (m_histogram) { double yMaxRange = -INFINITY; switch (type) { case Histogram::Ordinary: { size_t maxYAddes = gsl_histogram_max_bin(m_histogram); yMaxRange = gsl_histogram_get(m_histogram, maxYAddes); break; } case Histogram::Cumulative: { size_t maxYAddes = gsl_histogram_max_bin(m_histogram); yMaxRange = gsl_histogram_get(m_histogram, maxYAddes); double point = 0.0; for (size_t i = 0; i < m_bins; ++i) { point+= gsl_histogram_get(m_histogram,i); if (point > yMaxRange) { yMaxRange = point; } } //yMaxRange = dataColumn->rowCount(); break; } case Histogram::AvgShift: { //TODO } } return yMaxRange; } return -INFINITY; } double HistogramPrivate::getXMinimum() { switch (orientation) { case Histogram::Vertical: return autoBinRanges ? dataColumn->minimum() : binRangesMin; case Histogram::Horizontal: return 0; } return INFINITY; } double HistogramPrivate::getXMaximum() { switch (orientation) { case Histogram::Vertical: return autoBinRanges ? dataColumn->maximum() : binRangesMax; case Histogram::Horizontal: return getMaximumOccuranceofHistogram(); } return -INFINITY; } double HistogramPrivate::getYMinimum() { switch (orientation) { case Histogram::Vertical: return 0; case Histogram::Horizontal: return autoBinRanges ? dataColumn->minimum() : binRangesMin; } return INFINITY; } double HistogramPrivate::getYMaximum() { switch (orientation) { case Histogram::Vertical: return getMaximumOccuranceofHistogram(); case Histogram::Horizontal: return autoBinRanges ? dataColumn->maximum() : binRangesMax; } return INFINITY; } /*! Returns the shape of the Histogram as a QPainterPath in local coordinates */ QPainterPath HistogramPrivate::shape() const { return curveShape; } void HistogramPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } bool HistogramPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed. recalculates the position of the scene points to be drawn. triggers the update of lines, drop lines, symbols etc. */ void HistogramPrivate::retransform() { if (m_suppressRetransform) return; if (!isVisible()) return; PERFTRACE(name().toLatin1() + ", HistogramPrivate::retransform()"); if (!dataColumn) { linePath = QPainterPath(); symbolsPath = QPainterPath(); valuesPath = QPainterPath(); curveShape = QPainterPath(); lines.clear(); pointsLogical.clear(); pointsScene.clear(); visiblePoints.clear(); valuesPoints.clear(); valuesStrings.clear(); fillPolygon.clear(); recalcShapeAndBoundingRect(); return; } m_suppressRecalc = true; updateLines(); updateSymbols(); updateValues(); m_suppressRecalc = false; } /*! * called when the data was changed. recalculates the histogram. */ void HistogramPrivate::recalcHistogram() { PERFTRACE(name().toLatin1() + ", HistogramPrivate::recalcHistogram()"); if (m_histogram) { gsl_histogram_free(m_histogram); m_histogram = nullptr; } if (!dataColumn) return; //calculate the number of valid data points int count = 0; for (int row = 0; row < dataColumn->rowCount(); ++row) { if (dataColumn->isValid(row) && !dataColumn->isMasked(row)) ++count; } //calculate the number of bins if (count > 0) { if (autoBinRanges) { if (binRangesMin != dataColumn->minimum()) { binRangesMin = dataColumn->minimum(); emit q->binRangesMinChanged(binRangesMin); } if (binRangesMax != dataColumn->maximum()) { binRangesMax = dataColumn->maximum(); emit q->binRangesMaxChanged(binRangesMax); } } else { if (binRangesMin >= binRangesMax) { emit q->dataChanged(); return; } } switch (binningMethod) { case Histogram::ByNumber: m_bins = (size_t)binCount; break; case Histogram::ByWidth: m_bins = (size_t) (binRangesMax-binRangesMin)/binWidth; break; case Histogram::SquareRoot: m_bins = (size_t)sqrt(count); break; case Histogram::Rice: m_bins = (size_t)2*cbrt(count); break; case Histogram::Sturges: m_bins = (size_t) 1 + log2(count); break; case Histogram::Doane: { const double skewness = static_cast(dataColumn)->statistics().skewness; m_bins = (size_t)( 1 + log2(count) + log2(1 + abs(skewness)/sqrt((double)6*(count-2)/(count+1)/(count+3))) ); break; } case Histogram::Scott: { const double sigma = static_cast(dataColumn)->statistics().standardDeviation; const double width = 3.5*sigma/cbrt(count); DEBUG("blablub " << sigma << " " << width << " " <<(binRangesMax - binRangesMin)/width); m_bins = (size_t)(binRangesMax - binRangesMin)/width; break; } } DEBUG("min " << binRangesMin) DEBUG("max " << binRangesMax) DEBUG("number of bins " << m_bins) //calculate the histogram if (m_bins > 0) { m_histogram = gsl_histogram_alloc (m_bins); gsl_histogram_set_ranges_uniform (m_histogram, binRangesMin, binRangesMax); for (int row = 0; row < dataColumn->rowCount(); ++row) { if ( dataColumn->isValid(row) && !dataColumn->isMasked(row) ) gsl_histogram_increment(m_histogram, dataColumn->valueAt(row)); } } else DEBUG("Number of bins must be positiv integer") } //histogram changed because of the actual data changes or because of new bin settings, //emit dataChanged() in order to recalculate everything with the new size/shape of the histogram emit q->dataChanged(); } void HistogramPrivate::updateType() { //type (ordinary or cumulative) changed, //emit dataChanged() in order to recalculate everything with the new size/shape of the histogram emit q->dataChanged(); } void HistogramPrivate::updateOrientation() { //orientation (horizontal or vertical) changed //emit dataChanged() in order to recalculate everything with the new size/shape of the histogram emit q->dataChanged(); } /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. */ void HistogramPrivate::updateLines() { PERFTRACE(name().toLatin1() + ", HistogramPrivate::updateLines()"); linePath = QPainterPath(); lines.clear(); pointsLogical.clear(); pointsScene.clear(); if (orientation == Histogram::Vertical) verticalHistogram(); else horizontalHistogram(); //map the lines and the symbol positions to the scene coordinates const auto* plot = static_cast(q->parentAspect()); const auto* cSystem = static_cast(plot->coordinateSystem()); lines = cSystem->mapLogicalToScene(lines); visiblePoints = std::vector(pointsLogical.count(), false); cSystem->mapLogicalToScene(pointsLogical, pointsScene, visiblePoints); //new line path for (const auto& line : lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } updateFilling(); recalcShapeAndBoundingRect(); } void HistogramPrivate::verticalHistogram() { if (!m_histogram) return; const double width = (binRangesMax - binRangesMin)/m_bins; double value = 0.; if (lineType == Histogram::Bars) { for (size_t i = 0; i < m_bins; ++i) { if (type == Histogram::Ordinary) value = gsl_histogram_get(m_histogram, i); else value += gsl_histogram_get(m_histogram, i); const double x = binRangesMin + i*width; lines.append(QLineF(x, 0., x, value)); lines.append(QLineF(x, value, x + width, value)); lines.append(QLineF(x + width, value, x + width, 0.)); pointsLogical.append(QPointF(x+width/2, value)); } } else if (lineType == Histogram::NoLine || lineType == Histogram::Envelope) { double prevValue = 0.; for (size_t i = 0; i < m_bins; ++i) { if (type == Histogram::Ordinary) value = gsl_histogram_get(m_histogram, i); else value += gsl_histogram_get(m_histogram, i); const double x = binRangesMin + i*width; lines.append(QLineF(x, prevValue, x, value)); lines.append(QLineF(x, value, x + width, value)); pointsLogical.append(QPointF(x+width/2, value)); if (i == m_bins - 1) lines.append(QLineF(x + width, value, x + width, 0.)); prevValue = value; } } else { //drop lines for (size_t i = 0; i < m_bins; ++i) { if (type == Histogram::Ordinary) value = gsl_histogram_get(m_histogram, i); else value += gsl_histogram_get(m_histogram, i); const double x = binRangesMin + i*width - width/2; lines.append(QLineF(x, 0., x, value)); pointsLogical.append(QPointF(x, value)); } } if (lineType != Histogram::DropLines) lines.append(QLineF(binRangesMin, 0., binRangesMax, 0.)); } void HistogramPrivate::horizontalHistogram() { if (!m_histogram) return; const double width = (binRangesMax - binRangesMin)/m_bins; double value = 0.; if (lineType == Histogram::Bars) { for (size_t i = 0; i < m_bins; ++i) { if (type == Histogram::Ordinary) value = gsl_histogram_get(m_histogram,i); else value += gsl_histogram_get(m_histogram,i); const double y = binRangesMin + i*width; lines.append(QLineF(0., y, value, y)); lines.append(QLineF(value, y, value, y + width)); lines.append(QLineF(value, y + width, 0., y + width)); pointsLogical.append(QPointF(value, y+width/2)); } } else if (lineType == Histogram::NoLine || lineType == Histogram::Envelope) { double prevValue = 0.; for (size_t i = 0; i < m_bins; ++i) { if (type == Histogram::Ordinary) value = gsl_histogram_get(m_histogram, i); else value += gsl_histogram_get(m_histogram, i); const double y = binRangesMin + i*width; lines.append(QLineF(prevValue, y, value, y)); lines.append(QLineF(value, y, value, y + width)); pointsLogical.append(QPointF(value, y+width/2)); if (i == m_bins - 1) lines.append(QLineF(value, y + width, 0., y + width)); prevValue = value; } } else { //drop lines for (size_t i = 0; i < m_bins; ++i) { if (type == Histogram::Ordinary) value = gsl_histogram_get(m_histogram, i); else value += gsl_histogram_get(m_histogram, i); const double y = binRangesMin + i*width - width/2; lines.append(QLineF(0., y, value, y)); pointsLogical.append(QPointF(value, y)); } } if (lineType != Histogram::DropLines) lines.append(QLineF(0., binRangesMin, 0., binRangesMax)); } void HistogramPrivate::updateSymbols() { symbolsPath = QPainterPath(); if (symbolsStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : pointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); symbolsPath.addPath(trafo.map(path)); } } recalcShapeAndBoundingRect(); } /*! recreates the value strings to be shown and recalculates their draw position. */ void HistogramPrivate::updateValues() { valuesPath = QPainterPath(); valuesPoints.clear(); valuesStrings.clear(); if (valuesType == Histogram::NoValues || !m_histogram) { recalcShapeAndBoundingRect(); return; } //determine the value string for all points that are currently visible in the plot if (valuesType == Histogram::ValuesBinEntries) { switch (type) { case Histogram::Ordinary: for (size_t i = 0; i < m_bins; ++i) { if (!visiblePoints[i]) continue; valuesStrings << valuesPrefix + QString::number(gsl_histogram_get(m_histogram, i)) + valuesSuffix; } break; case Histogram::Cumulative: { value = 0; for (size_t i = 0; i < m_bins; ++i) { if (!visiblePoints[i]) continue; value += gsl_histogram_get(m_histogram, i); valuesStrings << valuesPrefix + QString::number(value) + valuesSuffix; } break; } case Histogram::AvgShift: break; } } else if (valuesType == Histogram::ValuesCustomColumn) { if (!valuesColumn) { recalcShapeAndBoundingRect(); return; } const int endRow = qMin(pointsLogical.size(), valuesColumn->rowCount()); const auto xColMode = valuesColumn->columnMode(); for (int i = 0; i < endRow; ++i) { if (!visiblePoints[i]) continue; if ( !valuesColumn->isValid(i) || valuesColumn->isMasked(i) ) continue; switch (xColMode) { case AbstractColumn::ColumnMode::Numeric: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i), valuesNumericFormat, valuesPrecision) + valuesSuffix; break; case AbstractColumn::ColumnMode::Integer: case AbstractColumn::ColumnMode::BigInt: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; case AbstractColumn::ColumnMode::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: valuesStrings << valuesPrefix + valuesColumn->dateTimeAt(i).toString(valuesDateTimeFormat) + valuesSuffix; break; } } } //Calculate the coordinates where to paint the value strings. //The coordinates depend on the actual size of the string. QPointF tempPoint; QFontMetrics fm(valuesFont); qreal w; const qreal h = fm.ascent(); switch (valuesPosition) { case Histogram::ValuesAbove: for (int i = 0; i < valuesStrings.size(); i++) { w = fm.boundingRect(valuesStrings.at(i)).width(); tempPoint.setX( pointsScene.at(i).x() -w/2); tempPoint.setY( pointsScene.at(i).y() - valuesDistance ); valuesPoints.append(tempPoint); } break; case Histogram::ValuesUnder: for (int i = 0; i < valuesStrings.size(); i++) { w = fm.boundingRect(valuesStrings.at(i)).width(); tempPoint.setX( pointsScene.at(i).x() -w/2); tempPoint.setY( pointsScene.at(i).y() + valuesDistance + h/2); valuesPoints.append(tempPoint); } break; case Histogram::ValuesLeft: for (int i = 0; i < valuesStrings.size(); i++) { w = fm.boundingRect(valuesStrings.at(i)).width(); tempPoint.setX( pointsScene.at(i).x() - valuesDistance - w - 1); tempPoint.setY( pointsScene.at(i).y()); valuesPoints.append(tempPoint); } break; case Histogram::ValuesRight: for (int i = 0; i < valuesStrings.size(); i++) { tempPoint.setX( pointsScene.at(i).x() + valuesDistance - 1); tempPoint.setY( pointsScene.at(i).y() ); valuesPoints.append(tempPoint); } break; } QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle!=0) trafo.rotate( -valuesRotationAngle ); valuesPath.addPath(trafo.map(path)); } recalcShapeAndBoundingRect(); } void HistogramPrivate::updateFilling() { fillPolygon.clear(); if (!fillingEnabled || lineType == Histogram::DropLines) { recalcShapeAndBoundingRect(); return; } QVector fillLines; const auto* plot = static_cast(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); //if there're no interpolation lines available (Histogram::NoLine selected), create line-interpolation, //use already available lines otherwise. if (lines.size()) fillLines = lines; else { for (int i = 0; i < pointsLogical.count()-1; i++) fillLines.append(QLineF(pointsLogical.at(i), pointsLogical.at(i+1))); fillLines = cSystem->mapLogicalToScene(fillLines); } //no lines available (no points), nothing to do if (!fillLines.size()) return; QPointF p1, p2; for (int i = 0; i < fillLines.size(); ++i) { const QLineF& line = fillLines.at(i); p1 = line.p1(); p2 = line.p2(); if (i != 0 && p1 != fillLines.at(i-1).p2()) fillPolygon << fillLines.at(i-1).p2() << p1; fillPolygon << p1 << p2; } //close the last polygon float yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin()>0 ? plot->yMin() : 0)).y(); fillPolygon << QPointF(fillLines.last().p2().x(), yEnd); fillPolygon << QPointF(fillLines.first().p1().x(), yEnd); recalcShapeAndBoundingRect(); } void HistogramPrivate::updateErrorBars() { } /*! recalculates the outer bounds and the shape of the curve. */ void HistogramPrivate::recalcShapeAndBoundingRect() { //if (m_suppressRecalc) // return; prepareGeometryChange(); curveShape = QPainterPath(); if (lineType != Histogram::NoLine) curveShape.addPath(WorksheetElement::shapeFromPath(linePath, linePen)); if (symbolsStyle != Symbol::NoSymbols) curveShape.addPath(symbolsPath); if (valuesType != Histogram::NoValues) curveShape.addPath(valuesPath); boundingRectangle = curveShape.boundingRect(); boundingRectangle = boundingRectangle.united(fillPolygon.boundingRect()); //TODO: when the selection is painted, line intersections are visible. //simplified() removes those artifacts but is horrible slow for curves with large number of points. //search for an alternative. //curveShape = curveShape.simplified(); updatePixmap(); } void HistogramPrivate::draw(QPainter* painter) { PERFTRACE(name().toLatin1() + ", HistogramPrivate::draw()"); //drawing line if (lineType != Histogram::NoLine) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::NoBrush); painter->drawPath(linePath); } //draw filling if (fillingEnabled) { painter->setOpacity(fillingOpacity); painter->setPen(Qt::SolidLine); drawFilling(painter); } //draw symbols if (symbolsStyle != Symbol::NoSymbols) { painter->setOpacity(symbolsOpacity); painter->setPen(symbolsPen); painter->setBrush(symbolsBrush); drawSymbols(painter); } //draw values if (valuesType != Histogram::NoValues) { painter->setOpacity(valuesOpacity); painter->setPen(valuesColor); painter->setBrush(Qt::SolidPattern); drawValues(painter); } } void HistogramPrivate::updatePixmap() { QPixmap pixmap(boundingRectangle.width(), boundingRectangle.height()); if (boundingRectangle.width() == 0 || boundingRectangle.height() == 0) { m_pixmap = pixmap; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; return; } pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(-boundingRectangle.topLeft()); draw(&painter); painter.end(); m_pixmap = pixmap; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve. \sa QGraphicsItem::paint(). */ void HistogramPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if ( KSharedConfig::openConfig()->group("Settings_Worksheet").readEntry("DoubleBuffering", true) ) painter->drawPixmap(boundingRectangle.topLeft(), m_pixmap); //draw the cached pixmap (fast) else draw(painter); //draw directly again (slow) if (m_hovered && !isSelected() && !m_printing) { if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; QPainter p(&pix); p.setCompositionMode(QPainter::CompositionMode_SourceIn); // source (shadow) pixels merged with the alpha channel of the destination (m_pixmap) p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Shadow)); p.end(); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); return; } if (isSelected() && !m_printing) { if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; QPainter p(&pix); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Highlight)); p.end(); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); return; } } void HistogramPrivate::drawSymbols(QPainter* painter) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(-symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : pointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); painter->drawPath(trafo.map(path)); } } void HistogramPrivate::drawValues(QPainter* painter) { QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle!=0) trafo.rotate(-valuesRotationAngle ); painter->drawPath(trafo.map(path)); } } void HistogramPrivate::drawFilling(QPainter* painter) { const QRectF& rect = fillPolygon.boundingRect(); if (fillingType == PlotArea::Color) { switch (fillingColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(fillingFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, fillingFirstColor); radialGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (fillingType == PlotArea::Image) { if ( !fillingFileName.trimmed().isEmpty() ) { QPixmap pix(fillingFileName); switch (fillingImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::Centered: { QPixmap backpix(rect.size().toSize()); backpix.fill(); QPainter p(&backpix); p.drawPixmap(QPointF(0,0),pix); p.end(); painter->setBrush(QBrush(backpix)); painter->setBrushOrigin(-pix.size().width()/2,-pix.size().height()/2); break; } case PlotArea::Tiled: painter->setBrush(QBrush(pix)); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); } } } else if (fillingType == PlotArea::Pattern) painter->setBrush(QBrush(fillingFirstColor,fillingBrushStyle)); painter->drawPolygon(fillPolygon); } void HistogramPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { const auto* plot = static_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && !isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void HistogramPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { const auto* plot = static_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Histogram::save(QXmlStreamWriter* writer) const { Q_D(const Histogram); writer->writeStartElement("Histogram"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); WRITE_COLUMN(d->dataColumn, dataColumn); writer->writeAttribute( "type", QString::number(d->type) ); writer->writeAttribute( "orientation", QString::number(d->orientation) ); writer->writeAttribute( "binningMethod", QString::number(d->binningMethod) ); writer->writeAttribute( "binCount", QString::number(d->binCount)); writer->writeAttribute( "binWidth", QString::number(d->binWidth)); writer->writeAttribute( "autoBinRanges", QString::number(d->autoBinRanges) ); writer->writeAttribute( "binRangesMin", QString::number(d->binRangesMin) ); writer->writeAttribute( "binRangesMax", QString::number(d->binRangesMax) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Line writer->writeStartElement("line"); writer->writeAttribute( "type", QString::number(d->lineType) ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeEndElement(); //Symbols writer->writeStartElement( "symbols" ); writer->writeAttribute( "symbolsStyle", QString::number(d->symbolsStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolsOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolsRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolsSize) ); WRITE_QBRUSH(d->symbolsBrush); WRITE_QPEN(d->symbolsPen); writer->writeEndElement(); //Values writer->writeStartElement("values"); writer->writeAttribute( "type", QString::number(d->valuesType) ); WRITE_COLUMN(d->valuesColumn, valuesColumn); writer->writeAttribute( "position", QString::number(d->valuesPosition) ); writer->writeAttribute( "distance", QString::number(d->valuesDistance) ); writer->writeAttribute( "rotation", QString::number(d->valuesRotationAngle) ); writer->writeAttribute( "opacity", QString::number(d->valuesOpacity) ); //TODO values format and precision writer->writeAttribute( "prefix", d->valuesPrefix ); writer->writeAttribute( "suffix", d->valuesSuffix ); WRITE_QCOLOR(d->valuesColor); WRITE_QFONT(d->valuesFont); writer->writeEndElement(); //Filling writer->writeStartElement("filling"); writer->writeAttribute( "enalbed", QString::number(d->fillingEnabled) ); writer->writeAttribute( "type", QString::number(d->fillingType) ); writer->writeAttribute( "colorStyle", QString::number(d->fillingColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->fillingImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->fillingBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->fillingFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->fillingFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->fillingFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->fillingSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->fillingSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->fillingSecondColor.blue()) ); writer->writeAttribute( "fileName", d->fillingFileName ); writer->writeAttribute( "opacity", QString::number(d->fillingOpacity) ); writer->writeEndElement(); writer->writeEndElement(); //close "Histogram" section } //! Load from XML bool Histogram::load(XmlStreamReader* reader, bool preview) { Q_D(Histogram); 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() == "Histogram") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_COLUMN(dataColumn); READ_INT_VALUE("type", type, Histogram::HistogramType); READ_INT_VALUE("orientation", orientation, Histogram::HistogramOrientation); READ_INT_VALUE("binningMethod", binningMethod, Histogram::BinningMethod); READ_INT_VALUE("binCount", binCount, int); READ_DOUBLE_VALUE("binWidth", binWidth); READ_INT_VALUE("autoBinRanges", autoBinRanges, bool); READ_DOUBLE_VALUE("binRangesMin", binRangesMin); READ_DOUBLE_VALUE("binRangesMax", binRangesMax); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "line") { attribs = reader->attributes(); READ_INT_VALUE("type", lineType, Histogram::LineType); READ_QPEN(d->linePen); READ_DOUBLE_VALUE("opacity", lineOpacity); } else if (!preview && reader->name() == "symbols") { attribs = reader->attributes(); READ_INT_VALUE("symbolsStyle", symbolsStyle, Symbol::Style); READ_DOUBLE_VALUE("opacity", symbolsOpacity); READ_DOUBLE_VALUE("rotation", symbolsRotationAngle); READ_DOUBLE_VALUE("size", symbolsSize); READ_QBRUSH(d->symbolsBrush); READ_QPEN(d->symbolsPen); } else if (!preview && reader->name() == "values") { attribs = reader->attributes(); READ_INT_VALUE("type", valuesType, Histogram::ValuesType); READ_COLUMN(valuesColumn); READ_INT_VALUE("position", valuesPosition, Histogram::ValuesPosition); READ_DOUBLE_VALUE("distance", valuesRotationAngle); READ_DOUBLE_VALUE("rotation", valuesRotationAngle); READ_DOUBLE_VALUE("opacity", valuesOpacity); //don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml) d->valuesPrefix = attribs.value("prefix").toString(); d->valuesSuffix = attribs.value("suffix").toString(); READ_QCOLOR(d->valuesColor); READ_QFONT(d->valuesFont); } else if (!preview && reader->name() == "filling") { attribs = reader->attributes(); READ_INT_VALUE("enabled", fillingEnabled, bool); READ_INT_VALUE("type", fillingType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", fillingColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", fillingImageStyle, PlotArea::BackgroundImageStyle); READ_INT_VALUE("brushStyle", fillingBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->fillingFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->fillingFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->fillingFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->fillingSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->fillingSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->fillingSecondColor.setBlue(str.toInt()); d->fillingFileName = attribs.value("fileName").toString(); READ_DOUBLE_VALUE("opacity", fillingOpacity); } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Histogram::loadThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); int index = parentAspect()->indexOfChild(this); const auto* plot = static_cast(parentAspect()); QColor themeColor; if (indexthemeColorPalette().size()) themeColor = plot->themeColorPalette().at(index); else { if (plot->themeColorPalette().size()) themeColor = plot->themeColorPalette().last(); } QPen p; Q_D(Histogram); d->m_suppressRecalc = true; //Line p.setStyle((Qt::PenStyle)group.readEntry("LineStyle", (int)this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); p.setColor(themeColor); this->setLinePen(p); this->setLineOpacity(group.readEntry("LineOpacity", this->lineOpacity())); //Symbol this->setSymbolsOpacity(group.readEntry("SymbolOpacity", this->symbolsOpacity())); QBrush brush = symbolsBrush(); brush.setColor(themeColor); this->setSymbolsBrush(brush); p = symbolsPen(); p.setColor(themeColor); this->setSymbolsPen(p); //Values this->setValuesOpacity(group.readEntry("ValuesOpacity", this->valuesOpacity())); this->setValuesColor(group.readEntry("ValuesColor", this->valuesColor())); //Filling this->setFillingBrushStyle((Qt::BrushStyle)group.readEntry("FillingBrushStyle",(int) this->fillingBrushStyle())); this->setFillingColorStyle((PlotArea::BackgroundColorStyle)group.readEntry("FillingColorStyle",(int) this->fillingColorStyle())); this->setFillingOpacity(group.readEntry("FillingOpacity", this->fillingOpacity())); this->setFillingSecondColor(group.readEntry("FillingSecondColor",(QColor) this->fillingSecondColor())); this->setFillingFirstColor(themeColor); this->setFillingType((PlotArea::BackgroundType)group.readEntry("FillingType",(int) this->fillingType())); //Error Bars //TODO: // p.setStyle((Qt::PenStyle)group.readEntry("ErrorBarsStyle",(int) this->errorBarsPen().style())); // p.setWidthF(group.readEntry("ErrorBarsWidth", this->errorBarsPen().widthF())); // p.setColor(themeColor); // this->setErrorBarsPen(p); // this->setErrorBarsOpacity(group.readEntry("ErrorBarsOpacity",this->errorBarsOpacity())); d->m_suppressRecalc = false; d->recalcShapeAndBoundingRect(); } void Histogram::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Histogram"); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineStyle",(int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Error Bars // group.writeEntry("ErrorBarsCapSize",this->errorBarsCapSize()); // group.writeEntry("ErrorBarsOpacity",this->errorBarsOpacity()); // group.writeEntry("ErrorBarsColor",(QColor) this->errorBarsPen().color()); // group.writeEntry("ErrorBarsStyle",(int) this->errorBarsPen().style()); // group.writeEntry("ErrorBarsWidth", this->errorBarsPen().widthF()); //Filling group.writeEntry("FillingBrushStyle",(int) this->fillingBrushStyle()); group.writeEntry("FillingColorStyle",(int) this->fillingColorStyle()); group.writeEntry("FillingOpacity", this->fillingOpacity()); group.writeEntry("FillingSecondColor",(QColor) this->fillingSecondColor()); group.writeEntry("FillingType",(int) this->fillingType()); //Symbol group.writeEntry("SymbolOpacity", this->symbolsOpacity()); //Values group.writeEntry("ValuesOpacity", this->valuesOpacity()); group.writeEntry("ValuesColor", (QColor) this->valuesColor()); group.writeEntry("ValuesFont", this->valuesFont()); int index = parentAspect()->indexOfChild(this); if (index < 5) { KConfigGroup themeGroup = config.group("Theme"); for (int i = index; i < 5; i++) { QString s = "ThemePaletteColor" + QString::number(i+1); themeGroup.writeEntry(s,(QColor) this->linePen().color()); } } } diff --git a/src/backend/worksheet/plots/cartesian/ReferenceLine.cpp b/src/backend/worksheet/plots/cartesian/ReferenceLine.cpp index 6e1d5f4ba..5dfa1cd2a 100644 --- a/src/backend/worksheet/plots/cartesian/ReferenceLine.cpp +++ b/src/backend/worksheet/plots/cartesian/ReferenceLine.cpp @@ -1,548 +1,548 @@ /*************************************************************************** File : ReferenceLine.cpp Project : LabPlot Description : Custom user-defined point on the plot -------------------------------------------------------------------- Copyright : (C) 2020 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 "ReferenceLine.h" #include "ReferenceLinePrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include #include /** * \class ReferenceLine * \brief A customizable point. * * The position can be either specified by mouse events or by providing the * x- and y- coordinates in parent's coordinate system */ ReferenceLine::ReferenceLine(const CartesianPlot* plot, const QString& name) : WorksheetElement(name, AspectType::ReferenceLine), d_ptr(new ReferenceLinePrivate(this, plot)) { init(); } ReferenceLine::ReferenceLine(const QString& name, ReferenceLinePrivate* dd) : WorksheetElement(name, AspectType::ReferenceLine), d_ptr(dd) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene ReferenceLine::~ReferenceLine() = default; void ReferenceLine::init() { Q_D(ReferenceLine); KConfig config; KConfigGroup group; group = config.group("ReferenceLine"); d->orientation = (Orientation)group.readEntry("Orientation", (int)Vertical); d->position = group.readEntry("Position", d->plot->xMin() + (d->plot->xMax() - d->plot->xMin())/2); d->pen.setStyle( (Qt::PenStyle) group.readEntry("Style", (int)Qt::SolidLine) ); d->pen.setColor( group.readEntry("Color", QColor(Qt::black)) ); - d->pen.setWidthF( group.readEntry("Width", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); + d->pen.setWidthF( group.readEntry("Width", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)) ); d->opacity = group.readEntry("Opacity", 1.0); } /*! Returns an icon to be used in the project explorer. */ QIcon ReferenceLine::icon() const { return QIcon::fromTheme(QLatin1String("draw-line")); } void ReferenceLine::initActions() { visibilityAction = new QAction(i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &ReferenceLine::visibilityChangedSlot); //Orientation orientationActionGroup = new QActionGroup(this); orientationActionGroup->setExclusive(true); connect(orientationActionGroup, &QActionGroup::triggered, this, &ReferenceLine::orientationChangedSlot); orientationHorizontalAction = new QAction(QIcon::fromTheme(QLatin1String("labplot-axis-horizontal")), i18n("Horizontal"), orientationActionGroup); orientationHorizontalAction->setCheckable(true); orientationVerticalAction = new QAction(QIcon::fromTheme(QLatin1String("labplot-axis-vertical")), i18n("Vertical"), orientationActionGroup); orientationVerticalAction->setCheckable(true); //Line lineStyleActionGroup = new QActionGroup(this); lineStyleActionGroup->setExclusive(true); connect(lineStyleActionGroup, &QActionGroup::triggered, this, &ReferenceLine::lineStyleChanged); lineColorActionGroup = new QActionGroup(this); lineColorActionGroup->setExclusive(true); connect(lineColorActionGroup, &QActionGroup::triggered, this, &ReferenceLine::lineColorChanged); } void ReferenceLine::initMenus() { this->initActions(); //Orientation orientationMenu = new QMenu(i18n("Orientation")); orientationMenu->setIcon(QIcon::fromTheme(QLatin1String("labplot-axis-horizontal"))); orientationMenu->addAction(orientationHorizontalAction); orientationMenu->addAction(orientationVerticalAction); //Line lineMenu = new QMenu(i18n("Line")); lineMenu->setIcon(QIcon::fromTheme(QLatin1String("draw-line"))); lineStyleMenu = new QMenu(i18n("Style"), lineMenu); lineStyleMenu->setIcon(QIcon::fromTheme(QLatin1String("object-stroke-style"))); lineMenu->setIcon(QIcon::fromTheme(QLatin1String("draw-line"))); lineMenu->addMenu(lineStyleMenu); lineColorMenu = new QMenu(i18n("Color"), lineMenu); lineColorMenu->setIcon(QIcon::fromTheme(QLatin1String("fill-color"))); GuiTools::fillColorMenu(lineColorMenu, lineColorActionGroup); lineMenu->addMenu(lineColorMenu); } QMenu* ReferenceLine::createContextMenu() { if (!orientationMenu) initMenus(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); Q_D(const ReferenceLine); //Orientation if (d->orientation == Horizontal) orientationHorizontalAction->setChecked(true); else orientationVerticalAction->setChecked(true); menu->insertMenu(firstAction, orientationMenu); //Line styles GuiTools::updatePenStyles(lineStyleMenu, lineStyleActionGroup, d->pen.color()); GuiTools::selectPenStyleAction(lineStyleActionGroup, d->pen.style()); GuiTools::selectColorAction(lineColorActionGroup, d->pen.color() ); menu->insertMenu(firstAction, lineMenu); menu->insertSeparator(firstAction); return menu; } QGraphicsItem* ReferenceLine::graphicsItem() const { return d_ptr; } void ReferenceLine::retransform() { Q_D(ReferenceLine); d->retransform(); } void ReferenceLine::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(ReferenceLine, ReferenceLine::Orientation, orientation, orientation) BASIC_SHARED_D_READER_IMPL(ReferenceLine, double, position, position) CLASS_SHARED_D_READER_IMPL(ReferenceLine, QPen, pen, pen) BASIC_SHARED_D_READER_IMPL(ReferenceLine, qreal, opacity, opacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(ReferenceLine, SetOrientation, ReferenceLine::Orientation, orientation, retransform) void ReferenceLine::setOrientation(Orientation orientation) { Q_D(ReferenceLine); if (orientation != d->orientation) exec(new ReferenceLineSetOrientationCmd(d, orientation, ki18n("%1: set orientation"))); } STD_SETTER_CMD_IMPL_F_S(ReferenceLine, SetPosition, double, position, retransform) void ReferenceLine::setPosition(double position) { Q_D(ReferenceLine); if (position != d->position) exec(new ReferenceLineSetPositionCmd(d, position, ki18n("%1: set position"))); } STD_SETTER_CMD_IMPL_F_S(ReferenceLine, SetPen, QPen, pen, recalcShapeAndBoundingRect) void ReferenceLine::setPen(const QPen& pen) { Q_D(ReferenceLine); if (pen != d->pen) exec(new ReferenceLineSetPenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(ReferenceLine, SetOpacity, qreal, opacity, update); void ReferenceLine::setOpacity(qreal opacity) { Q_D(ReferenceLine); if (opacity != d->opacity) exec(new ReferenceLineSetOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } STD_SWAP_METHOD_SETTER_CMD_IMPL_F(ReferenceLine, SetVisible, bool, swapVisible, update); void ReferenceLine::setVisible(bool on) { Q_D(ReferenceLine); exec(new ReferenceLineSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool ReferenceLine::isVisible() const { Q_D(const ReferenceLine); return d->isVisible(); } void ReferenceLine::setPrinting(bool on) { Q_D(ReferenceLine); d->m_printing = on; } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void ReferenceLine::orientationChangedSlot(QAction* action) { if (action == orientationHorizontalAction) this->setOrientation(Horizontal); else this->setOrientation(Vertical); } void ReferenceLine::lineStyleChanged(QAction* action) { Q_D(const ReferenceLine); QPen pen = d->pen; pen.setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action)); this->setPen(pen); } void ReferenceLine::lineColorChanged(QAction* action) { Q_D(const ReferenceLine); QPen pen = d->pen; pen.setColor(GuiTools::colorFromAction(lineColorActionGroup, action)); this->setPen(pen); } void ReferenceLine::visibilityChangedSlot() { Q_D(const ReferenceLine); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## ReferenceLinePrivate::ReferenceLinePrivate(ReferenceLine* owner, const CartesianPlot* p) : plot(p), q(owner) { setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); } QString ReferenceLinePrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the item/point. Called on geometry or properties changes. */ void ReferenceLinePrivate::retransform() { if (suppressRetransform) return; //calculate the position in the scene coordinates QVector listLogical; if (orientation == ReferenceLine::Vertical) listLogical << QPointF(position, plot->yMin() + (plot->yMax() - plot->yMin())/2); else listLogical << QPointF(plot->xMin() + (plot->xMax() - plot->xMin())/2, position); const auto* cSystem = static_cast(plot->coordinateSystem()); QVector listScene = cSystem->mapLogicalToScene(listLogical); if (!listScene.isEmpty()) { positionScene = listScene.at(0); m_visible = true; suppressItemChangeEvent = true; setPos(positionScene); suppressItemChangeEvent = false; //determine the length of the line to be drawn QVector pointsLogical; if (orientation == ReferenceLine::Vertical) pointsLogical << QPointF(position, plot->yMin()) << QPointF(position, plot->yMax()); else pointsLogical << QPointF(plot->xMin(), position) << QPointF(plot->xMax(), position); QVector pointsScene = cSystem->mapLogicalToScene(pointsLogical); if (pointsScene.size() > 1) { if (orientation == ReferenceLine::Vertical) length = pointsScene.at(0).y() - pointsScene.at(1).y(); else length = pointsScene.at(0).x() - pointsScene.at(1).x(); } } else m_visible = false; recalcShapeAndBoundingRect(); } bool ReferenceLinePrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); // emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF ReferenceLinePrivate::boundingRect() const { return boundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath ReferenceLinePrivate::shape() const { return lineShape; } /*! recalculates the outer bounds and the shape of the item. */ void ReferenceLinePrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); lineShape = QPainterPath(); if (m_visible) { QPainterPath path; if (orientation == ReferenceLine::Horizontal) { path.moveTo(-length/2, 0); path.lineTo(length/2, 0); } else { path.moveTo(0, length/2); path.lineTo(0, -length/2); } lineShape.addPath(WorksheetElement::shapeFromPath(path, pen)); boundingRectangle = lineShape.boundingRect(); } } void ReferenceLinePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!m_visible) return; painter->setOpacity(opacity); painter->setPen(pen); if (orientation == ReferenceLine::Horizontal) painter->drawLine(-length/2, 0, length/2, 0); else painter->drawLine(0, length/2, 0, -length/2); if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(lineShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(lineShape); } } QVariant ReferenceLinePrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change != QGraphicsItem::ItemPositionChange) return QGraphicsItem::itemChange(change, value); QPointF positionSceneNew = value.toPointF(); //don't allow to move the line outside of the plot rect //in the direction orthogonal to the orientation of the line if (orientation == ReferenceLine::Horizontal) { if (positionSceneNew.x() != positionScene.x()) positionSceneNew.setX(positionScene.x()); } else { if (positionSceneNew.y() != positionScene.y()) positionSceneNew.setY(positionScene.y()); } if (plot->dataRect().contains(positionSceneNew)) { //emit the signals in order to notify the UI (dock widget and status bar) about the new logical position. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. const auto* cSystem = static_cast(plot->coordinateSystem()); QPointF positionLogical = cSystem->mapSceneToLogical(positionSceneNew); if (orientation == ReferenceLine::Horizontal) { emit q->positionChanged(positionLogical.y()); emit q->statusInfo(QLatin1String("y=") + QString::number(positionLogical.y())); } else { emit q->positionChanged(positionLogical.x()); emit q->statusInfo(QLatin1String("x=") + QString::number(positionLogical.x())); } } else { //line is moved outside of the plot, keep it at the plot boundary if (orientation == ReferenceLine::Horizontal) { if (positionSceneNew.y() < plot->dataRect().y()) positionSceneNew.setY(plot->dataRect().y()); else positionSceneNew.setY(plot->dataRect().y() + plot->dataRect().height()); } else { if (positionSceneNew.x() < plot->dataRect().x()) positionSceneNew.setX(plot->dataRect().x()); else positionSceneNew.setX(plot->dataRect().x() + plot->dataRect().width()); } } return QGraphicsItem::itemChange(change, QVariant(positionSceneNew)); } void ReferenceLinePrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //position was changed -> set the position member variables suppressRetransform = true; const auto* cSystem = static_cast(plot->coordinateSystem()); QPointF positionLogical = cSystem->mapSceneToLogical(pos()); if (orientation == ReferenceLine::Horizontal) q->setPosition(positionLogical.y()); else q->setPosition(positionLogical.x()); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } void ReferenceLinePrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void ReferenceLinePrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void ReferenceLinePrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void ReferenceLine::save(QXmlStreamWriter* writer) const { Q_D(const ReferenceLine); writer->writeStartElement("referenceLine"); writeBasicAttributes(writer); writeCommentElement(writer); writer->writeStartElement("general"); writer->writeAttribute("orientation", QString::number(d->orientation)); writer->writeAttribute("position", QString::number(d->position)); writer->writeAttribute("visible", QString::number(d->isVisible())); writer->writeEndElement(); writer->writeStartElement("line"); WRITE_QPEN(d->pen); writer->writeAttribute("opacity", QString::number(d->opacity)); writer->writeEndElement(); writer->writeEndElement(); // close "ReferenceLine" section } //! Load from XML bool ReferenceLine::load(XmlStreamReader* reader, bool preview) { Q_D(ReferenceLine); 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() == "referenceLine") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_DOUBLE_VALUE("position", position); READ_INT_VALUE("orientation", orientation, ReferenceLine::Orientation); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "line") { attribs = reader->attributes(); READ_QPEN(d->pen); READ_DOUBLE_VALUE("opacity", opacity); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (!preview) retransform(); return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp index 34362326f..2345770fd 100644 --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -1,3331 +1,3331 @@ /*************************************************************************** File : XYCurve.cpp Project : LabPlot Description : A xy-curve -------------------------------------------------------------------- Copyright : (C) 2010-2020 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2020 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ /*! \class XYCurve \brief A 2D-curve, provides an interface for editing many properties of the curve. \ingroup worksheet */ #include "XYCurve.h" #include "XYCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/lib/commandtemplates.h" #include "backend/core/Project.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "backend/gsl/errors.h" #include "tools/ImageTools.h" #include #include #include #include #include #include #include #include extern "C" { #include #include #include } XYCurve::XYCurve(const QString &name, AspectType type) : WorksheetElement(name, type), d_ptr(new XYCurvePrivate(this)) { init(); } XYCurve::XYCurve(const QString& name, XYCurvePrivate* dd, AspectType type) : WorksheetElement(name, type), d_ptr(dd) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYCurve::~XYCurve() = default; void XYCurve::finalizeAdd() { Q_D(XYCurve); d->plot = static_cast(parentAspect()); d->cSystem = static_cast(d->plot->coordinateSystem()); } void XYCurve::init() { Q_D(XYCurve); KConfig config; KConfigGroup group = config.group("XYCurve"); d->lineType = (XYCurve::LineType) group.readEntry("LineType", (int)XYCurve::Line); d->lineIncreasingXOnly = group.readEntry("LineIncreasingXOnly", false); d->lineSkipGaps = group.readEntry("SkipLineGaps", false); d->lineInterpolationPointsCount = group.readEntry("LineInterpolationPointsCount", 1); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int)Qt::SolidLine) ); d->linePen.setColor( group.readEntry("LineColor", QColor(Qt::black)) ); - d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); + d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->dropLineType = (XYCurve::DropLineType) group.readEntry("DropLineType", (int)XYCurve::NoLine); d->dropLinePen.setStyle( (Qt::PenStyle) group.readEntry("DropLineStyle", (int)Qt::SolidLine) ); d->dropLinePen.setColor( group.readEntry("DropLineColor", QColor(Qt::black))); - d->dropLinePen.setWidthF( group.readEntry("DropLineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); + d->dropLinePen.setWidthF( group.readEntry("DropLineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)) ); d->dropLineOpacity = group.readEntry("DropLineOpacity", 1.0); d->symbolsStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::NoSymbols); - d->symbolsSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); + d->symbolsSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point)); d->symbolsRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolsOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolsBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolsBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::black)) ); d->symbolsPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolsPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); - d->symbolsPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); + d->symbolsPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point)) ); d->valuesType = (XYCurve::ValuesType) group.readEntry("ValuesType", (int)XYCurve::NoValues); d->valuesPosition = (XYCurve::ValuesPosition) group.readEntry("ValuesPosition", (int)XYCurve::ValuesAbove); - d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Point)); + d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Unit::Point)); d->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); d->valuesNumericFormat = group.readEntry("ValuesNumericFormat", "f").at(0).toLatin1(); d->valuesPrecision = group.readEntry("ValuesNumericFormat", 2); d->valuesDateTimeFormat = group.readEntry("ValuesDateTimeFormat", "yyyy-MM-dd"); d->valuesPrefix = group.readEntry("ValuesPrefix", ""); d->valuesSuffix = group.readEntry("ValuesSuffix", ""); d->valuesFont = group.readEntry("ValuesFont", QFont()); - d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Point ) ); + d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Unit::Point ) ); d->valuesColor = group.readEntry("ValuesColor", QColor(Qt::black)); d->fillingPosition = (XYCurve::FillingPosition) group.readEntry("FillingPosition", (int)XYCurve::NoFilling); d->fillingType = (PlotArea::BackgroundType) group.readEntry("FillingType", (int)PlotArea::Color); d->fillingColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("FillingColorStyle", (int) PlotArea::SingleColor); d->fillingImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("FillingImageStyle", (int) PlotArea::Scaled); d->fillingBrushStyle = (Qt::BrushStyle) group.readEntry("FillingBrushStyle", (int) Qt::SolidPattern); d->fillingFileName = group.readEntry("FillingFileName", QString()); d->fillingFirstColor = group.readEntry("FillingFirstColor", QColor(Qt::white)); d->fillingSecondColor = group.readEntry("FillingSecondColor", QColor(Qt::black)); d->fillingOpacity = group.readEntry("FillingOpacity", 1.0); d->xErrorType = (XYCurve::ErrorType) group.readEntry("XErrorType", (int)XYCurve::NoError); d->yErrorType = (XYCurve::ErrorType) group.readEntry("YErrorType", (int)XYCurve::NoError); d->errorBarsType = (XYCurve::ErrorBarsType) group.readEntry("ErrorBarsType", (int)XYCurve::ErrorBarsSimple); - d->errorBarsCapSize = group.readEntry( "ErrorBarsCapSize", Worksheet::convertToSceneUnits(10, Worksheet::Point) ); + d->errorBarsCapSize = group.readEntry( "ErrorBarsCapSize", Worksheet::convertToSceneUnits(10, Worksheet::Unit::Point) ); d->errorBarsPen.setStyle( (Qt::PenStyle)group.readEntry("ErrorBarsStyle", (int)Qt::SolidLine) ); d->errorBarsPen.setColor( group.readEntry("ErrorBarsColor", QColor(Qt::black)) ); - d->errorBarsPen.setWidthF( group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); + d->errorBarsPen.setWidthF( group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point)) ); d->errorBarsOpacity = group.readEntry("ErrorBarsOpacity", 1.0); } void XYCurve::initActions() { visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered(bool)), this, SLOT(visibilityChanged())); navigateToAction = new QAction(QIcon::fromTheme("go-next-view"), QString(), this); connect(navigateToAction, SIGNAL(triggered(bool)), this, SLOT(navigateTo())); m_menusInitialized = true; } QMenu* XYCurve::createContextMenu() { if (!m_menusInitialized) initActions(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //"data analysis" menu auto* plot = static_cast(parentAspect()); menu->insertMenu(visibilityAction, plot->analysisMenu()); menu->insertSeparator(visibilityAction); //"Navigate to spreadsheet"-action, show only if x- or y-columns have data from a spreadsheet AbstractAspect* parentSpreadsheet = nullptr; if (xColumn() && dynamic_cast(xColumn()->parentAspect()) ) parentSpreadsheet = xColumn()->parentAspect(); else if (yColumn() && dynamic_cast(yColumn()->parentAspect()) ) parentSpreadsheet = yColumn()->parentAspect(); if (parentSpreadsheet) { navigateToAction->setText(i18n("Navigate to \"%1\"", parentSpreadsheet->name())); navigateToAction->setData(parentSpreadsheet->path()); menu->insertAction(visibilityAction, navigateToAction); menu->insertSeparator(visibilityAction); } //if the context menu is called on an item that is not selected yet, select it if (!graphicsItem()->isSelected()) graphicsItem()->setSelected(true); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon XYCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } QGraphicsItem* XYCurve::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(XYCurve, SetVisible, bool, swapVisible) void XYCurve::setVisible(bool on) { Q_D(XYCurve); exec(new XYCurveSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool XYCurve::isVisible() const { Q_D(const XYCurve); return d->isVisible(); } void XYCurve::setPrinting(bool on) { Q_D(XYCurve); d->setPrinting(on); } //############################################################################## //########################## getter methods ################################## //############################################################################## //data source BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xColumn, xColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yColumn, yColumn) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, xColumnPath, xColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, yColumnPath, yColumnPath) //line BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::LineType, lineType, lineType) BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineSkipGaps, lineSkipGaps) BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineIncreasingXOnly, lineIncreasingXOnly) BASIC_SHARED_D_READER_IMPL(XYCurve, int, lineInterpolationPointsCount, lineInterpolationPointsCount) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, lineOpacity, lineOpacity) //droplines BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::DropLineType, dropLineType, dropLineType) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, dropLinePen, dropLinePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, dropLineOpacity, dropLineOpacity) //symbols BASIC_SHARED_D_READER_IMPL(XYCurve, Symbol::Style, symbolsStyle, symbolsStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsOpacity, symbolsOpacity) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsRotationAngle, symbolsRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsSize, symbolsSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QBrush, symbolsBrush, symbolsBrush) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, symbolsPen, symbolsPen) //values BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesType, valuesType, valuesType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn *, valuesColumn, valuesColumn) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesColumnPath, valuesColumnPath) BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesPosition, valuesPosition, valuesPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesDistance, valuesDistance) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesRotationAngle, valuesRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesOpacity, valuesOpacity) CLASS_SHARED_D_READER_IMPL(XYCurve, char, valuesNumericFormat, valuesNumericFormat) BASIC_SHARED_D_READER_IMPL(XYCurve, int, valuesPrecision, valuesPrecision) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesDateTimeFormat, valuesDateTimeFormat) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesPrefix, valuesPrefix) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesSuffix, valuesSuffix) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, valuesColor, valuesColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QFont, valuesFont, valuesFont) //filling BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::FillingPosition, fillingPosition, fillingPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundType, fillingType, fillingType) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundColorStyle, fillingColorStyle, fillingColorStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundImageStyle, fillingImageStyle, fillingImageStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, Qt::BrushStyle, fillingBrushStyle, fillingBrushStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingFirstColor, fillingFirstColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingSecondColor, fillingSecondColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, fillingFileName, fillingFileName) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, fillingOpacity, fillingOpacity) //error bars BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, xErrorType, xErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorPlusColumn, xErrorPlusColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorMinusColumn, xErrorMinusColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, yErrorType, yErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorPlusColumn, yErrorPlusColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorMinusColumn, yErrorMinusColumn) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, xErrorPlusColumnPath, xErrorPlusColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, xErrorMinusColumnPath, xErrorMinusColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, yErrorPlusColumnPath, yErrorPlusColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, yErrorMinusColumnPath, yErrorMinusColumnPath) BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorBarsType, errorBarsType, errorBarsType) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsCapSize, errorBarsCapSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, errorBarsPen, errorBarsPen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsOpacity, errorBarsOpacity) /*! * return \c true if the data in the source columns (x, y) used in the analysis curves, \c false otherwise */ bool XYCurve::isSourceDataChangedSinceLastRecalc() const { Q_D(const XYCurve); return d->sourceDataChangedSinceLastRecalc; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## // 1) add XYCurveSetXColumnCmd as friend class to XYCurve // 2) add XYCURVE_COLUMN_CONNECT(x) as private method to XYCurve // 3) define all missing slots XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(X, x, recalcLogicalPoints) void XYCurve::setXColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xColumn) exec(new XYCurveSetXColumnCmd(d, column, ki18n("%1: x-data source changed"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(Y, y, recalcLogicalPoints) void XYCurve::setYColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yColumn) exec(new XYCurveSetYColumnCmd(d, column, ki18n("%1: y-data source changed"))); } void XYCurve::setXColumnPath(const QString& path) { Q_D(XYCurve); d->xColumnPath = path; } void XYCurve::setYColumnPath(const QString& path) { Q_D(XYCurve); d->yColumnPath = path; } //Line STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineType, XYCurve::LineType, lineType, updateLines) void XYCurve::setLineType(LineType type) { Q_D(XYCurve); if (type != d->lineType) exec(new XYCurveSetLineTypeCmd(d, type, ki18n("%1: line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineSkipGaps, bool, lineSkipGaps, updateLines) void XYCurve::setLineSkipGaps(bool skip) { Q_D(XYCurve); if (skip != d->lineSkipGaps) exec(new XYCurveSetLineSkipGapsCmd(d, skip, ki18n("%1: set skip line gaps"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineIncreasingXOnly, bool, lineIncreasingXOnly, updateLines) void XYCurve::setLineIncreasingXOnly(bool incr) { Q_D(XYCurve); if (incr != d->lineIncreasingXOnly) exec(new XYCurveSetLineIncreasingXOnlyCmd(d, incr, ki18n("%1: set increasing X"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineInterpolationPointsCount, int, lineInterpolationPointsCount, updateLines) void XYCurve::setLineInterpolationPointsCount(int count) { Q_D(XYCurve); if (count != d->lineInterpolationPointsCount) exec(new XYCurveSetLineInterpolationPointsCountCmd(d, count, ki18n("%1: set the number of interpolation points"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect) void XYCurve::setLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->linePen) exec(new XYCurveSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineOpacity, qreal, lineOpacity, updatePixmap); void XYCurve::setLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->lineOpacity) exec(new XYCurveSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } //Drop lines STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineType, XYCurve::DropLineType, dropLineType, updateDropLines) void XYCurve::setDropLineType(DropLineType type) { Q_D(XYCurve); if (type != d->dropLineType) exec(new XYCurveSetDropLineTypeCmd(d, type, ki18n("%1: drop line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLinePen, QPen, dropLinePen, recalcShapeAndBoundingRect) void XYCurve::setDropLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->dropLinePen) exec(new XYCurveSetDropLinePenCmd(d, pen, ki18n("%1: set drop line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineOpacity, qreal, dropLineOpacity, updatePixmap) void XYCurve::setDropLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->dropLineOpacity) exec(new XYCurveSetDropLineOpacityCmd(d, opacity, ki18n("%1: set drop line opacity"))); } // Symbols-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsStyle, Symbol::Style, symbolsStyle, retransform) void XYCurve::setSymbolsStyle(Symbol::Style style) { Q_D(XYCurve); if (style != d->symbolsStyle) exec(new XYCurveSetSymbolsStyleCmd(d, style, ki18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsSize, qreal, symbolsSize, updateSymbols) void XYCurve::setSymbolsSize(qreal size) { Q_D(XYCurve); if (!qFuzzyCompare(1 + size, 1 + d->symbolsSize)) exec(new XYCurveSetSymbolsSizeCmd(d, size, ki18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsRotationAngle, qreal, symbolsRotationAngle, updateSymbols) void XYCurve::setSymbolsRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->symbolsRotationAngle)) exec(new XYCurveSetSymbolsRotationAngleCmd(d, angle, ki18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsBrush, QBrush, symbolsBrush, updatePixmap) void XYCurve::setSymbolsBrush(const QBrush &brush) { Q_D(XYCurve); if (brush != d->symbolsBrush) exec(new XYCurveSetSymbolsBrushCmd(d, brush, ki18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsPen, QPen, symbolsPen, updateSymbols) void XYCurve::setSymbolsPen(const QPen &pen) { Q_D(XYCurve); if (pen != d->symbolsPen) exec(new XYCurveSetSymbolsPenCmd(d, pen, ki18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsOpacity, qreal, symbolsOpacity, updatePixmap) void XYCurve::setSymbolsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->symbolsOpacity) exec(new XYCurveSetSymbolsOpacityCmd(d, opacity, ki18n("%1: set symbols opacity"))); } //Values-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesType, XYCurve::ValuesType, valuesType, updateValues) void XYCurve::setValuesType(XYCurve::ValuesType type) { Q_D(XYCurve); if (type != d->valuesType) exec(new XYCurveSetValuesTypeCmd(d, type, ki18n("%1: set values type"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(Values, values, updateValues) void XYCurve::setValuesColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->valuesColumn) { exec(new XYCurveSetValuesColumnCmd(d, column, ki18n("%1: set values column"))); if (column) connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateValues())); } } void XYCurve::setValuesColumnPath(const QString& path) { Q_D(XYCurve); d->valuesColumnPath = path; } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPosition, XYCurve::ValuesPosition, valuesPosition, updateValues) void XYCurve::setValuesPosition(ValuesPosition position) { Q_D(XYCurve); if (position != d->valuesPosition) exec(new XYCurveSetValuesPositionCmd(d, position, ki18n("%1: set values position"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesDistance, qreal, valuesDistance, updateValues) void XYCurve::setValuesDistance(qreal distance) { Q_D(XYCurve); if (distance != d->valuesDistance) exec(new XYCurveSetValuesDistanceCmd(d, distance, ki18n("%1: set values distance"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues) void XYCurve::setValuesRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle)) exec(new XYCurveSetValuesRotationAngleCmd(d, angle, ki18n("%1: rotate values"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesOpacity, qreal, valuesOpacity, updatePixmap) void XYCurve::setValuesOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->valuesOpacity) exec(new XYCurveSetValuesOpacityCmd(d, opacity, ki18n("%1: set values opacity"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesNumericFormat, char, valuesNumericFormat, updateValues) void XYCurve::setValuesNumericFormat(char format) { Q_D(XYCurve); if (format != d->valuesNumericFormat) exec(new XYCurveSetValuesNumericFormatCmd(d, format, ki18n("%1: set values numeric format"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPrecision, int, valuesPrecision, updateValues) void XYCurve::setValuesPrecision(int precision) { Q_D(XYCurve); if (precision != d->valuesPrecision) exec(new XYCurveSetValuesPrecisionCmd(d, precision, ki18n("%1: set values precision"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesDateTimeFormat, QString, valuesDateTimeFormat, updateValues) void XYCurve::setValuesDateTimeFormat(const QString& format) { Q_D(XYCurve); if (format != d->valuesDateTimeFormat) exec(new XYCurveSetValuesDateTimeFormatCmd(d, format, ki18n("%1: set values datetime format"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPrefix, QString, valuesPrefix, updateValues) void XYCurve::setValuesPrefix(const QString& prefix) { Q_D(XYCurve); if (prefix != d->valuesPrefix) exec(new XYCurveSetValuesPrefixCmd(d, prefix, ki18n("%1: set values prefix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesSuffix, QString, valuesSuffix, updateValues) void XYCurve::setValuesSuffix(const QString& suffix) { Q_D(XYCurve); if (suffix != d->valuesSuffix) exec(new XYCurveSetValuesSuffixCmd(d, suffix, ki18n("%1: set values suffix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesFont, QFont, valuesFont, updateValues) void XYCurve::setValuesFont(const QFont& font) { Q_D(XYCurve); if (font != d->valuesFont) exec(new XYCurveSetValuesFontCmd(d, font, ki18n("%1: set values font"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesColor, QColor, valuesColor, updatePixmap) void XYCurve::setValuesColor(const QColor& color) { Q_D(XYCurve); if (color != d->valuesColor) exec(new XYCurveSetValuesColorCmd(d, color, ki18n("%1: set values color"))); } //Filling STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingPosition, XYCurve::FillingPosition, fillingPosition, updateFilling) void XYCurve::setFillingPosition(FillingPosition position) { Q_D(XYCurve); if (position != d->fillingPosition) exec(new XYCurveSetFillingPositionCmd(d, position, ki18n("%1: filling position changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingType, PlotArea::BackgroundType, fillingType, updatePixmap) void XYCurve::setFillingType(PlotArea::BackgroundType type) { Q_D(XYCurve); if (type != d->fillingType) exec(new XYCurveSetFillingTypeCmd(d, type, ki18n("%1: filling type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingColorStyle, PlotArea::BackgroundColorStyle, fillingColorStyle, updatePixmap) void XYCurve::setFillingColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(XYCurve); if (style != d->fillingColorStyle) exec(new XYCurveSetFillingColorStyleCmd(d, style, ki18n("%1: filling color style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingImageStyle, PlotArea::BackgroundImageStyle, fillingImageStyle, updatePixmap) void XYCurve::setFillingImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(XYCurve); if (style != d->fillingImageStyle) exec(new XYCurveSetFillingImageStyleCmd(d, style, ki18n("%1: filling image style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingBrushStyle, Qt::BrushStyle, fillingBrushStyle, updatePixmap) void XYCurve::setFillingBrushStyle(Qt::BrushStyle style) { Q_D(XYCurve); if (style != d->fillingBrushStyle) exec(new XYCurveSetFillingBrushStyleCmd(d, style, ki18n("%1: filling brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFirstColor, QColor, fillingFirstColor, updatePixmap) void XYCurve::setFillingFirstColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingFirstColor) exec(new XYCurveSetFillingFirstColorCmd(d, color, ki18n("%1: set filling first color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingSecondColor, QColor, fillingSecondColor, updatePixmap) void XYCurve::setFillingSecondColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingSecondColor) exec(new XYCurveSetFillingSecondColorCmd(d, color, ki18n("%1: set filling second color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFileName, QString, fillingFileName, updatePixmap) void XYCurve::setFillingFileName(const QString& fileName) { Q_D(XYCurve); if (fileName != d->fillingFileName) exec(new XYCurveSetFillingFileNameCmd(d, fileName, ki18n("%1: set filling image"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingOpacity, qreal, fillingOpacity, updatePixmap) void XYCurve::setFillingOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->fillingOpacity) exec(new XYCurveSetFillingOpacityCmd(d, opacity, ki18n("%1: set filling opacity"))); } //Error bars STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorType, XYCurve::ErrorType, xErrorType, updateErrorBars) void XYCurve::setXErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->xErrorType) exec(new XYCurveSetXErrorTypeCmd(d, type, ki18n("%1: x-error type changed"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(XErrorPlus, xErrorPlus, updateErrorBars) void XYCurve::setXErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorPlusColumn) { exec(new XYCurveSetXErrorPlusColumnCmd(d, column, ki18n("%1: set x-error column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &XYCurve::updateErrorBars); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setXErrorPlusColumnPath(const QString& path) { Q_D(XYCurve); d->xErrorPlusColumnPath = path; } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(XErrorMinus, xErrorMinus, updateErrorBars) void XYCurve::setXErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorMinusColumn) { exec(new XYCurveSetXErrorMinusColumnCmd(d, column, ki18n("%1: set x-error column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &XYCurve::updateErrorBars); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setXErrorMinusColumnPath(const QString& path) { Q_D(XYCurve); d->xErrorMinusColumnPath = path; } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorType, XYCurve::ErrorType, yErrorType, updateErrorBars) void XYCurve::setYErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->yErrorType) exec(new XYCurveSetYErrorTypeCmd(d, type, ki18n("%1: y-error type changed"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(YErrorPlus, yErrorPlus, updateErrorBars) void XYCurve::setYErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorPlusColumn) { exec(new XYCurveSetYErrorPlusColumnCmd(d, column, ki18n("%1: set y-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setYErrorPlusColumnPath(const QString& path) { Q_D(XYCurve); d->yErrorPlusColumnPath = path; } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(YErrorMinus, yErrorMinus, updateErrorBars) void XYCurve::setYErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorMinusColumn) { exec(new XYCurveSetYErrorMinusColumnCmd(d, column, ki18n("%1: set y-error column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &XYCurve::updateErrorBars); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setYErrorMinusColumnPath(const QString& path) { Q_D(XYCurve); d->yErrorMinusColumnPath = path; } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsCapSize, qreal, errorBarsCapSize, updateErrorBars) void XYCurve::setErrorBarsCapSize(qreal size) { Q_D(XYCurve); if (size != d->errorBarsCapSize) exec(new XYCurveSetErrorBarsCapSizeCmd(d, size, ki18n("%1: set error bar cap size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsType, XYCurve::ErrorBarsType, errorBarsType, updateErrorBars) void XYCurve::setErrorBarsType(ErrorBarsType type) { Q_D(XYCurve); if (type != d->errorBarsType) exec(new XYCurveSetErrorBarsTypeCmd(d, type, ki18n("%1: error bar type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsPen, QPen, errorBarsPen, recalcShapeAndBoundingRect) void XYCurve::setErrorBarsPen(const QPen& pen) { Q_D(XYCurve); if (pen != d->errorBarsPen) exec(new XYCurveSetErrorBarsPenCmd(d, pen, ki18n("%1: set error bar style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsOpacity, qreal, errorBarsOpacity, updatePixmap) void XYCurve::setErrorBarsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->errorBarsOpacity) exec(new XYCurveSetErrorBarsOpacityCmd(d, opacity, ki18n("%1: set error bar opacity"))); } void XYCurve::suppressRetransform(bool b) { Q_D(XYCurve); d->suppressRetransform(b); } //############################################################################## //################################# SLOTS #################################### //############################################################################## void XYCurve::retransform() { Q_D(XYCurve); d->retransform(); } void XYCurve::recalcLogicalPoints() { Q_D(XYCurve); d->recalcLogicalPoints(); } void XYCurve::updateValues() { Q_D(XYCurve); d->updateValues(); } void XYCurve::updateErrorBars() { Q_D(XYCurve); d->updateErrorBars(); } //TODO void XYCurve::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(pageResize); Q_D(const XYCurve); setSymbolsSize(d->symbolsSize * horizontalRatio); QPen pen = d->symbolsPen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setSymbolsPen(pen); pen = d->linePen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setLinePen(pen); //setValuesDistance(d->distance*); QFont font = d->valuesFont; font.setPointSizeF(font.pointSizeF()*horizontalRatio); setValuesFont(font); } /*! * returns \c true if the aspect being removed \c removedAspect is equal to \c column * or to one of its parents. returns \c false otherwise. */ bool XYCurve::columnRemoved(const AbstractColumn* column, const AbstractAspect* removedAspect) const { // TODO: BAD HACK. // In macrosXYCurve.h every parent of the column is connected to the function aspectAboutToBeRemoved(). // When a column is removed, the function aspectAboutToBeRemoved is called and the column pointer is set to nullptr. // However, when a child of the parent is removed, the parent calls the aspectAboutToBeRemoved() again, but // the column was already disconnected. // Better solution would be to emit aspectAboutToBeRemoved() for every column when their parents are removed. // At the moment this signal is only emitted when the column is deleted directly and not when its parent is deleted. // Once this is done, the connection of all parents to the aspectAboutToBeRemoved() signal can be removed. if (!column) return false; bool removed = (removedAspect == column); if (!removed) { auto* parent = column->parentAspect(); while (parent) { if (parent == removedAspect) { removed = true; break; } parent = parent->parentAspect(); } } return removed; } void XYCurve::xColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->xColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->xColumn = nullptr; d->retransform(); } } void XYCurve::yColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->yColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->yColumn = nullptr; d->retransform(); } } void XYCurve::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->valuesColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->valuesColumn = nullptr; d->updateValues(); } } void XYCurve::xErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->xErrorPlusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->xErrorPlusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::xErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->xErrorMinusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->xErrorMinusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::yErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->yErrorPlusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->yErrorPlusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::yErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->yErrorMinusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->yErrorMinusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::xColumnNameChanged() { Q_D(XYCurve); setXColumnPath(d->xColumn->path()); } void XYCurve::yColumnNameChanged() { Q_D(XYCurve); setYColumnPath(d->yColumn->path()); } void XYCurve::xErrorPlusColumnNameChanged() { Q_D(XYCurve); setXErrorPlusColumnPath(d->xErrorPlusColumn->path()); } void XYCurve::xErrorMinusColumnNameChanged() { Q_D(XYCurve); setXErrorMinusColumnPath(d->xErrorMinusColumn->path()); } void XYCurve::yErrorPlusColumnNameChanged() { Q_D(XYCurve); setYErrorPlusColumnPath(d->yErrorPlusColumn->path()); } void XYCurve::yErrorMinusColumnNameChanged() { Q_D(XYCurve); setYErrorMinusColumnPath(d->yErrorMinusColumn->path()); } void XYCurve::valuesColumnNameChanged() { Q_D(XYCurve); setValuesColumnPath(d->valuesColumn->path()); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void XYCurve::visibilityChanged() { Q_D(const XYCurve); this->setVisible(!d->isVisible()); } void XYCurve::navigateTo() { project()->navigateTo(navigateToAction->data().toString()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYCurvePrivate::XYCurvePrivate(XYCurve *owner) : q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(false); } QString XYCurvePrivate::name() const { return q->name(); } QRectF XYCurvePrivate::boundingRect() const { return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath XYCurvePrivate::shape() const { return curveShape; } void XYCurvePrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { if (q->activateCurve(event->pos())) { q->createContextMenu()->exec(event->screenPos()); return; } QGraphicsItem::contextMenuEvent(event); } bool XYCurvePrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); retransform(); return oldValue; } /*! called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed. recalculates the position of the scene points to be drawn. triggers the update of lines, drop lines, symbols etc. */ void XYCurvePrivate::retransform() { if (!isVisible()) return; DEBUG("\nXYCurvePrivate::retransform() name = " << STDSTRING(name()) << ", m_suppressRetransform = " << m_suppressRetransform); DEBUG(" plot = " << plot); if (m_suppressRetransform || !plot) return; { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform()"); #endif symbolPointsScene.clear(); if ( (nullptr == xColumn) || (nullptr == yColumn) ) { DEBUG(" xColumn or yColumn == NULL"); linePath = QPainterPath(); dropLinePath = QPainterPath(); symbolsPath = QPainterPath(); valuesPath = QPainterPath(); errorBarsPath = QPainterPath(); curveShape = QPainterPath(); lines.clear(); valuesPoints.clear(); valuesStrings.clear(); fillPolygons.clear(); recalcShapeAndBoundingRect(); return; } if (!plot->isPanningActive()) WAIT_CURSOR; //calculate the scene coordinates // This condition cannot be used, because symbolPointsLogical is also used in updateErrorBars(), updateDropLines() and in updateFilling() // TODO: check updateErrorBars() and updateDropLines() and if they aren't available don't calculate this part //if (symbolsStyle != Symbol::NoSymbols || valuesType != XYCurve::NoValues ) { { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform(), map logical points to scene coordinates"); #endif if (!symbolPointsLogical.isEmpty()) { - float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Inch); - float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Inch); + float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Unit::Inch); + float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Unit::Inch); int countPixelX = ceil(widthDatarectInch*QApplication::desktop()->physicalDpiX()); int countPixelY = ceil(heightDatarectInch*QApplication::desktop()->physicalDpiY()); if (countPixelX <= 0 || countPixelY <= 0) { RESET_CURSOR; return; } double minLogicalDiffX = 1./(plot->dataRect().width()/countPixelX); double minLogicalDiffY = 1./(plot->dataRect().height()/countPixelY); QVector> scenePointsUsed; // size of the datarect in pixels scenePointsUsed.resize(countPixelX + 1); for (int i = 0; i < countPixelX + 1; i++) scenePointsUsed[i].resize(countPixelY + 1); auto columnProperties = xColumn->properties(); int startIndex; int endIndex; if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || columnProperties == AbstractColumn::Properties::MonotonicIncreasing) { double xMin = cSystem->mapSceneToLogical(plot->dataRect().topLeft()).x(); double xMax = cSystem->mapSceneToLogical(plot->dataRect().bottomRight()).x(); startIndex = Column::indexForValue(xMin, symbolPointsLogical, static_cast(columnProperties)); endIndex = Column::indexForValue(xMax, symbolPointsLogical, static_cast(columnProperties)); if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) std::swap(startIndex, endIndex); if (startIndex < 0) startIndex = 0; if (endIndex < 0) endIndex = symbolPointsLogical.size()-1; } else { startIndex = 0; endIndex = symbolPointsLogical.size()-1; } visiblePoints = std::vector(symbolPointsLogical.count(), false); cSystem->mapLogicalToScene(startIndex, endIndex, symbolPointsLogical, symbolPointsScene, visiblePoints, scenePointsUsed, minLogicalDiffX, minLogicalDiffY); } } //} // (symbolsStyle != Symbol::NoSymbols || valuesType != XYCurve::NoValues ) m_suppressRecalc = true; updateLines(); updateDropLines(); updateSymbols(); updateValues(); m_suppressRecalc = false; updateErrorBars(); RESET_CURSOR; } } /*! * called if the x- or y-data was changed. * copies the valid data points from the x- and y-columns into the internal container */ void XYCurvePrivate::recalcLogicalPoints() { DEBUG("XYCurvePrivate::recalcLogicalPoints()"); PERFTRACE(name().toLatin1() + ", XYCurvePrivate::recalcLogicalPoints()"); symbolPointsLogical.clear(); connectedPointsLogical.clear(); validPointsIndicesLogical.clear(); visiblePoints.clear(); if (!xColumn || !yColumn) return; auto xColMode = xColumn->columnMode(); auto yColMode = yColumn->columnMode(); QPointF tempPoint; //take over only valid and non masked points. for (int row = 0; row < xColumn->rowCount(); row++) { if ( xColumn->isValid(row) && yColumn->isValid(row) && (!xColumn->isMasked(row)) && (!yColumn->isMasked(row)) ) { switch (xColMode) { case AbstractColumn::ColumnMode::Numeric: case AbstractColumn::ColumnMode::Integer: case AbstractColumn::ColumnMode::BigInt: tempPoint.setX(xColumn->valueAt(row)); break; case AbstractColumn::ColumnMode::Text: break; case AbstractColumn::ColumnMode::DateTime: tempPoint.setX(xColumn->dateTimeAt(row).toMSecsSinceEpoch()); break; case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: break; } switch (yColMode) { case AbstractColumn::ColumnMode::Numeric: case AbstractColumn::ColumnMode::Integer: case AbstractColumn::ColumnMode::BigInt: tempPoint.setY(yColumn->valueAt(row)); break; case AbstractColumn::ColumnMode::Text: break; case AbstractColumn::ColumnMode::DateTime: tempPoint.setY(yColumn->dateTimeAt(row).toMSecsSinceEpoch()); break; case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: break; } symbolPointsLogical.append(tempPoint); connectedPointsLogical.push_back(true); validPointsIndicesLogical.push_back(row); } else { if (!connectedPointsLogical.empty()) connectedPointsLogical[connectedPointsLogical.size()-1] = false; } } visiblePoints = std::vector(symbolPointsLogical.count(), false); } /*! * Adds a line, which connects two points, but only if the don't lie on the same xAxis pixel. * If they lie on the same x pixel, draw a vertical line between the minimum and maximum y value. So all points are included * This function is only valid for linear x Axis scale! * @param p0 first point * @param p1 second point * @param minY * @param maxY * @param overlap if at the previous call was an overlap between the previous two points * @param minLogicalDiffX logical difference between two pixels * @param pixelDiff x pixel distance between two points */ void XYCurvePrivate::addLine(QPointF p0, QPointF p1, double& minY, double& maxY, bool& overlap, double minLogicalDiffX, int& pixelDiff) { pixelDiff = (int)(p1.x() * minLogicalDiffX) - (int)(p0.x() * minLogicalDiffX); addLine(p0, p1, minY, maxY, overlap, pixelDiff); } /*! * Adds a line, which connects two points, but only if they don't lie on the same xAxis pixel. * If they lie on the same x pixel, draw a vertical line between the minimum and maximum y value. So all points are included * This function can be used for all axis scalings (linear, log, sqrt, ...). For the linear case use the function above, because it's optimized for the linear case * @param p0 first point * @param p1 second point * @param minY * @param maxY * @param overlap if at the previous call was an overlap between the previous two points * @param pixelDiff x pixel distance between two points * @param pixelCount pixel count */ void XYCurvePrivate::addLine(QPointF p0, QPointF p1, double& minY, double& maxY, bool& overlap, int& pixelDiff, int pixelCount) { if (plot->xScale() == CartesianPlot::Scale::ScaleLinear) { // implemented for completeness only double minLogicalDiffX = 1./((plot->xMax() - plot->xMin())/pixelCount); addLine(p0, p1, minY, maxY, overlap, minLogicalDiffX, pixelDiff); } else { // for nonlinear scaling the pixel distance must be calculated for every point pair QPointF p0Scene = cSystem->mapLogicalToScene(p0, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); QPointF p1Scene = cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); // if the point is not valid, don't create a line //if (std::isnan(p0Scene.x()) || std::isnan(p0Scene.y())) if ((p0Scene.x() == 0 && p0Scene.y() == 0) || (p1Scene.x() == 0 && p1Scene.y() == 0)) { // not possible to create line DEBUG("Not possible to create a line between : " << p0Scene.x() << ' ' << p0Scene.y() << ", "<< p1Scene.x() << ' ' << p1Scene.y()) return; } // using only the difference between the points is not sufficient, because p0 is updated always // independent if new line added or not int p0Pixel = (int)((p0Scene.x() - plot->dataRect().x()) / plot->dataRect().width() * pixelCount); int p1Pixel = (int)((p1Scene.x() - plot->dataRect().x()) / plot->dataRect().width() * pixelCount); pixelDiff = p1Pixel - p0Pixel; addLine(p0, p1, minY, maxY, overlap, pixelDiff); } } /*! * \brief XYCurvePrivate::addLine * This function is called from the other two addLine() functions to avoid duplication * @param p0 first point * @param p1 second point * @param minY * @param maxY * @param overlap if at the previous call was an overlap between the previous two points * @param pixelDiff x pixel distance between two points */ void XYCurvePrivate::addLine(QPointF p0, QPointF p1, double& minY, double& maxY, bool& overlap, int& pixelDiff) { //QDEBUG("XYCurvePrivate::addLine():" << p0 << ' ' << p1 << ' ' << minY << ' ' << maxY << ' ' << overlap << ' ' << pixelDiff) if (pixelDiff == 0) { if (overlap) { // second and so the x axis pixels are the same if (p1.y() > maxY) maxY = p1.y(); if (p1.y() < minY) minY = p1.y(); } else { // first time pixel are same if (p0.y() < p1.y()) { minY = p0.y(); maxY = p1.y(); } else { maxY = p0.y(); minY = p1.y(); } overlap = true; } } else { if (overlap) { // when previously overlap was true, draw the previous line overlap = false; // last point from previous pixel must be evaluated if (p0.y() > maxY) maxY = p0.y(); if (p0.y() < minY) minY = p0.y(); if (true) { //p1.x() >= plot->xMin() && p1.x() <= plot->xMax()) { // x inside scene if (minY == maxY) { lines.append(QLineF(p0, p1)); // line from previous point to actual point } else if (p0.y() == minY) { // draw vertical line lines.append(QLineF(p0.x(), maxY, p0.x(), minY)); if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) return; lines.append(QLineF(p0, p1)); } else if (p0.y() == maxY) { // draw vertical line lines.append(QLineF(p0.x(), maxY, p0.x(), minY)); if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) return; // draw line, only if there is a pixelDiff = 1 otherwise no line needed, because when drawing a new vertical line, this line is already included lines.append(QLineF(p0, p1)); } else { // last point nor min nor max lines.append(QLineF(p0.x(), maxY, p0.x(), minY)); if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) return; lines.append(QLineF(p0, p1)); } } else // x in scene DEBUG("addLine: not in scene"); } else // no overlap lines.append(QLineF(p0, p1)); } } /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. At the moment also the points which are outside of the scene are added. This algorithm can be improved by letting away all lines where both points are outside of the scene */ void XYCurvePrivate::updateLines() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines()"); #endif linePath = QPainterPath(); lines.clear(); if (lineType == XYCurve::NoLine) { DEBUG(" nothing to do, since line type is XYCurve::NoLine"); updateFilling(); recalcShapeAndBoundingRect(); return; } unsigned int count = (unsigned int)symbolPointsLogical.count(); if (count <= 1) { DEBUG(" nothing to do, since no data points available"); recalcShapeAndBoundingRect(); return; } - float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Inch); - //float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Inch); // unsed + float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Unit::Inch); + //float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Unit::Inch); // unsed int countPixelX = ceil(widthDatarectInch*QApplication::desktop()->physicalDpiX()); //int countPixelY = ceil(heightDatarectInch*QApplication::desktop()->physicalDpiY()); // unused // only valid for linear scale //double minLogicalDiffX = 1/((plot->xMax()-plot->xMin())/countPixelX); // unused //double minLogicalDiffY = 1/((plot->yMax()-plot->yMin())/countPixelY); // unused //calculate the lines connecting the data points { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate the lines connecting the data points"); #endif QPointF tempPoint1, tempPoint2; // used as temporaryPoints to interpolate datapoints if the corresponding setting is set int startIndex, endIndex; // find index for xMin and xMax to not loop throug all values auto columnProperties = q->xColumn()->properties(); if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || columnProperties == AbstractColumn::Properties::MonotonicIncreasing) { double xMin = cSystem->mapSceneToLogical(plot->dataRect().topLeft()).x(); double xMax = cSystem->mapSceneToLogical(plot->dataRect().bottomRight()).x(); startIndex= Column::indexForValue(xMin, symbolPointsLogical, columnProperties); endIndex = Column::indexForValue(xMax, symbolPointsLogical, columnProperties); if (startIndex > endIndex) std::swap(startIndex, endIndex); startIndex--; // use one value before endIndex ++; if (startIndex < 0) startIndex = 0; if(endIndex < 0 || endIndex >= static_cast(count)) endIndex = static_cast(count)-1; count = static_cast(endIndex - startIndex +1); }else { startIndex = 0; endIndex = static_cast(count)-1; } if (columnProperties == AbstractColumn::Properties::Constant) { tempPoint1 = QPointF(plot->xMin(), plot->yMin()); tempPoint2 = QPointF(plot->xMin(), plot->yMax()); lines.append(QLineF(tempPoint1, tempPoint2)); } else { bool overlap = false; double maxY, minY; // are initialized in add line() int pixelDiff; QPointF p0; QPointF p1; switch (lineType) { case XYCurve::NoLine: break; case XYCurve::Line: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) // when option set skip points continue; addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::StartHorizontal: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p1.x(), p0.y()); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::StartVertical: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p0.x(), p1.y()); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::MidpointHorizontal: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p0.x() + (p1.x()-p0.x())/2, p0.y()); tempPoint2 = QPointF(p0.x() + (p1.x()-p0.x())/2, p1.y()); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, tempPoint2, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint2, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::MidpointVertical: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p0.x(), p0.y() + (p1.y()-p0.y())/2); tempPoint2 = QPointF(p1.x(), p0.y() + (p1.y()-p0.y())/2); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, tempPoint2, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint2, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::Segments2: { int skip = 0; for (int i = startIndex; i < endIndex; i++) { p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (skip != 1) { if ( (!lineSkipGaps && !connectedPointsLogical[i]) || (lineIncreasingXOnly && (p1.x() < p0.x())) ) { skip = 0; continue; } addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); skip++; } else { skip = 0; if (overlap) { overlap = false; lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY))); } } } // add last line if (overlap) lines.append(QLineF(symbolPointsLogical[endIndex-1], symbolPointsLogical[endIndex])); break; } case XYCurve::Segments3: { int skip = 0; for (int i = startIndex; i < endIndex; i++) { if (skip != 2) { p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if ( (!lineSkipGaps && !connectedPointsLogical[i]) || (lineIncreasingXOnly && (p1.x() < p0.x())) ) { skip = 0; continue; } addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); skip++; } else { skip = 0; if (overlap) { overlap = false; lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY))); } } } // add last line if (overlap) lines.append(QLineF(symbolPointsLogical[endIndex-1], symbolPointsLogical[endIndex])); break; } case XYCurve::SplineCubicNatural: case XYCurve::SplineCubicPeriodic: case XYCurve::SplineAkimaNatural: case XYCurve::SplineAkimaPeriodic: { gsl_interp_accel *acc = gsl_interp_accel_alloc(); gsl_spline *spline = nullptr; double* x = new double[count]; double* y = new double[count]; for (unsigned int i = 0; i < count; i++) { // TODO: interpolating only between the visible points? x[i] = symbolPointsLogical[i+startIndex].x(); y[i] = symbolPointsLogical[i+startIndex].y(); } gsl_set_error_handler_off(); if (lineType == XYCurve::SplineCubicNatural) spline = gsl_spline_alloc(gsl_interp_cspline, count); else if (lineType == XYCurve::SplineCubicPeriodic) spline = gsl_spline_alloc(gsl_interp_cspline_periodic, count); else if (lineType == XYCurve::SplineAkimaNatural) spline = gsl_spline_alloc(gsl_interp_akima, count); else if (lineType == XYCurve::SplineAkimaPeriodic) spline = gsl_spline_alloc(gsl_interp_akima_periodic, count); if (!spline) { QString msg; if ( (lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) && count < 5) msg = i18n("Error: Akima spline interpolation requires a minimum of 5 points."); else msg = i18n("Error: Could not initialize the spline function."); emit q->info(msg); recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_interp_accel_free (acc); return; } int status = gsl_spline_init (spline, x, y, count); if (status) { //TODO: check in gsl/interp.c when GSL_EINVAL is thrown QString gslError; if (status == GSL_EINVAL) gslError = i18n("x values must be monotonically increasing."); else gslError = gslErrorToString(status); emit q->info( i18n("Error: %1", gslError) ); recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); return; } //create interpolating points std::vector xinterp, yinterp; for (unsigned int i = 0; i < count - 1; i++) { const double x1 = x[i]; const double x2 = x[i+1]; const double step = std::abs(x2 - x1)/(lineInterpolationPointsCount + 1); for (int i = 0; i < (lineInterpolationPointsCount + 1); i++) { double xi = x1+i*step; double yi = gsl_spline_eval(spline, xi, acc); xinterp.push_back(xi); yinterp.push_back(yi); } } if (!xinterp.empty()) { for (unsigned int i = 0; i < xinterp.size() - 1; i++) { p0 = QPointF(xinterp[i], yinterp[i]); p1 = QPointF(xinterp[i+1], yinterp[i+1]); addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); } addLine(QPointF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1]), QPointF(x[count-1], y[count-1]), minY, maxY, overlap, pixelDiff, countPixelX); // add last line if (overlap) lines.append(QLineF(QPointF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1]), QPointF(x[count-1], y[count-1]))); } delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); break; } } } } //map the lines to scene coordinates { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), map lines to scene coordinates"); #endif lines = cSystem->mapLogicalToScene(lines); } { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate new line path"); #endif //new line path for (const auto& line : lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } } updateFilling(); recalcShapeAndBoundingRect(); } /*! recalculates the painter path for the drop lines. Called each time when the type of the drop lines is changed. */ void XYCurvePrivate::updateDropLines() { dropLinePath = QPainterPath(); if (dropLineType == XYCurve::NoDropLine) { recalcShapeAndBoundingRect(); return; } //calculate drop lines QVector lines; float xMin = 0; float yMin = 0; xMin = plot->xMin(); yMin = plot->yMin(); switch (dropLineType) { case XYCurve::NoDropLine: break; case XYCurve::DropLineX: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(point.x(), yMin))); } break; case XYCurve::DropLineY: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(xMin, point.y()))); } break; case XYCurve::DropLineXY: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(point.x(), yMin))); lines.append(QLineF(point, QPointF(xMin, point.y()))); } break; case XYCurve::DropLineXZeroBaseline: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(point.x(), 0))); } break; case XYCurve::DropLineXMinBaseline: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append( QLineF(point, QPointF(point.x(), yColumn->minimum())) ); } break; case XYCurve::DropLineXMaxBaseline: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append( QLineF(point, QPointF(point.x(), yColumn->maximum())) ); } break; } //map the drop lines to scene coordinates lines = cSystem->mapLogicalToScene(lines); //new painter path for the drop lines for (const auto& line : lines) { dropLinePath.moveTo(line.p1()); dropLinePath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateSymbols() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateSymbols()"); #endif symbolsPath = QPainterPath(); if (symbolsStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); symbolsPath.addPath(trafo.map(path)); } } recalcShapeAndBoundingRect(); } /*! recreates the value strings to be shown and recalculates their draw position. */ void XYCurvePrivate::updateValues() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateValues()"); #endif valuesPath = QPainterPath(); valuesPoints.clear(); valuesStrings.clear(); if (valuesType == XYCurve::NoValues || symbolPointsLogical.isEmpty()) { recalcShapeAndBoundingRect(); return; } //determine the value string for all points that are currently visible in the plot switch (valuesType) { case XYCurve::NoValues: case XYCurve::ValuesX: { CartesianPlot::RangeFormat rangeFormat = plot->xRangeFormat(); int precision = valuesPrecision; if (xColumn->columnMode() == AbstractColumn::ColumnMode::Integer || xColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) precision = 0; for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; QString value; if (rangeFormat == CartesianPlot::Numeric) value = QString::number(symbolPointsLogical[i].x(), valuesNumericFormat, precision); else value = QDateTime::fromMSecsSinceEpoch(symbolPointsLogical[i].x()).toString(valuesDateTimeFormat); valuesStrings << valuesPrefix + value + valuesSuffix; } break; } case XYCurve::ValuesY: { CartesianPlot::RangeFormat rangeFormat = plot->yRangeFormat(); int precision = valuesPrecision; if (yColumn->columnMode() == AbstractColumn::ColumnMode::Integer || yColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) precision = 0; for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; QString value; if (rangeFormat == CartesianPlot::Numeric) value = QString::number(symbolPointsLogical[i].y(), valuesNumericFormat, precision); else value = QDateTime::fromMSecsSinceEpoch(symbolPointsLogical[i].y()).toString(valuesDateTimeFormat); valuesStrings << valuesPrefix + value + valuesSuffix; } break; } case XYCurve::ValuesXY: case XYCurve::ValuesXYBracketed: { CartesianPlot::RangeFormat xRangeFormat = plot->xRangeFormat(); CartesianPlot::RangeFormat yRangeFormat = plot->yRangeFormat(); int xPrecision = valuesPrecision; if (xColumn->columnMode() == AbstractColumn::ColumnMode::Integer || xColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) xPrecision = 0; int yPrecision = valuesPrecision; if (yColumn->columnMode() == AbstractColumn::ColumnMode::Integer || yColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) yPrecision = 0; for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; QString value; if (valuesType == XYCurve::ValuesXYBracketed) value = '('; if (xRangeFormat == CartesianPlot::Numeric) value += QString::number(symbolPointsLogical[i].x(), valuesNumericFormat, xPrecision); else value += QDateTime::fromMSecsSinceEpoch(symbolPointsLogical[i].x()).toString(valuesDateTimeFormat); if (yRangeFormat == CartesianPlot::Numeric) value += ',' + QString::number(symbolPointsLogical[i].y(), valuesNumericFormat, yPrecision); else value += ',' + QDateTime::fromMSecsSinceEpoch(symbolPointsLogical[i].y()).toString(valuesDateTimeFormat); if (valuesType == XYCurve::ValuesXYBracketed) value += ')'; valuesStrings << valuesPrefix + value + valuesSuffix; } break; } case XYCurve::ValuesCustomColumn: { if (!valuesColumn) { recalcShapeAndBoundingRect(); return; } const int endRow = qMin(symbolPointsLogical.size(), valuesColumn->rowCount()); auto xColMode = valuesColumn->columnMode(); for (int i = 0; i < endRow; ++i) { if (!visiblePoints[i]) continue; if ( !valuesColumn->isValid(i) || valuesColumn->isMasked(i) ) continue; switch (xColMode) { case AbstractColumn::ColumnMode::Numeric: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i), valuesNumericFormat, valuesPrecision) + valuesSuffix; break; case AbstractColumn::ColumnMode::Integer: case AbstractColumn::ColumnMode::BigInt: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; case AbstractColumn::ColumnMode::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: valuesStrings << valuesPrefix + valuesColumn->dateTimeAt(i).toString(valuesDateTimeFormat) + valuesSuffix; break; } } } } //Calculate the coordinates where to paint the value strings. //The coordinates depend on the actual size of the string. QPointF tempPoint; QFontMetrics fm(valuesFont); qreal w; qreal h = fm.ascent(); for (int i = 0; i < valuesStrings.size(); i++) { w = fm.boundingRect(valuesStrings.at(i)).width(); switch (valuesPosition) { case XYCurve::ValuesAbove: tempPoint.setX( symbolPointsScene.at(i).x() - w/2); tempPoint.setY( symbolPointsScene.at(i).y() - valuesDistance ); break; case XYCurve::ValuesUnder: tempPoint.setX( symbolPointsScene.at(i).x() -w/2 ); tempPoint.setY( symbolPointsScene.at(i).y() + valuesDistance + h/2); break; case XYCurve::ValuesLeft: tempPoint.setX( symbolPointsScene.at(i).x() - valuesDistance - w - 1 ); tempPoint.setY( symbolPointsScene.at(i).y()); break; case XYCurve::ValuesRight: tempPoint.setX( symbolPointsScene.at(i).x() + valuesDistance - 1 ); tempPoint.setY( symbolPointsScene.at(i).y() ); break; } valuesPoints.append(tempPoint); } QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle != 0) trafo.rotate( -valuesRotationAngle ); valuesPath.addPath(trafo.map(path)); } recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateFilling() { if (m_suppressRetransform) return; fillPolygons.clear(); //don't try to calculate the filling polygons if // - no filling was enabled // - the nubmer of visible points on the scene is too high // - no scene points available, everything outside of the plot region or no scene points calculated yet if (fillingPosition == XYCurve::NoFilling || symbolPointsScene.size() > 1000 || symbolPointsScene.isEmpty()) { recalcShapeAndBoundingRect(); return; } QVector fillLines; //if there're no interpolation lines available (XYCurve::NoLine selected), create line-interpolation, //use already available lines otherwise. if (!lines.isEmpty()) fillLines = lines; else { for (int i = 0; i < symbolPointsLogical.count() - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; fillLines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); } //no lines available (no points), nothing to do if (fillLines.isEmpty()) return; fillLines = cSystem->mapLogicalToScene(fillLines); //no lines available (no points) after mapping, nothing to do if (fillLines.isEmpty()) return; } //create polygon(s): //1. Depending on the current zoom-level, only a subset of the curve may be visible in the plot //and more of the filling area should be shown than the area defined by the start and end points of the currently visible points. //We check first whether the curve crosses the boundaries of the plot and determine new start and end points and put them to the boundaries. //2. Furthermore, depending on the current filling type we determine the end point (x- or y-coordinate) where all polygons are closed at the end. QPolygonF pol; QPointF start = fillLines.at(0).p1(); //starting point of the current polygon, initialize with the first visible point QPointF end = fillLines.at(fillLines.size()-1).p2(); //end point of the current polygon, initialize with the last visible point const QPointF& first = symbolPointsLogical.at(0); //first point of the curve, may not be visible currently const QPointF& last = symbolPointsLogical.at(symbolPointsLogical.size()-1);//last point of the curve, may not be visible currently QPointF edge; float xEnd = 0, yEnd = 0; if (fillingPosition == XYCurve::FillingAbove) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())).y(); } else if (fillingPosition == XYCurve::FillingBelow) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).y(); } else if (fillingPosition == XYCurve::FillingZeroBaseline) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (plot->yMax() > 0) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } else { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (plot->yMax() > 0) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } else { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } } yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin()>0 ? plot->yMin() : 0)).y(); } else if (fillingPosition == XYCurve::FillingLeft) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).x(); } else { //FillingRight edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())).x(); } if (start != fillLines.at(0).p1()) pol << start; QPointF p1, p2; for (int i = 0; i < fillLines.size(); ++i) { const QLineF& line = fillLines.at(i); p1 = line.p1(); p2 = line.p2(); if (i != 0 && p1 != fillLines.at(i-1).p2()) { //the first point of the current line is not equal to the last point of the previous line //->check whether we have a break in between. const bool gap = false; //TODO if (!gap) { //-> we have no break in the curve -> connect the points by a horizontal/vertical line pol << fillLines.at(i-1).p2() << p1; } else { //-> we have a break in the curve -> close the polygon, add it to the polygon list and start a new polygon if (fillingPosition == XYCurve::FillingAbove || fillingPosition == XYCurve::FillingBelow || fillingPosition == XYCurve::FillingZeroBaseline) { pol << QPointF(fillLines.at(i-1).p2().x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, fillLines.at(i-1).p2().y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; pol.clear(); start = p1; } } pol << p1 << p2; } if (p2 != end) pol << end; //close the last polygon if (fillingPosition == XYCurve::FillingAbove || fillingPosition == XYCurve::FillingBelow || fillingPosition == XYCurve::FillingZeroBaseline) { pol << QPointF(end.x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, end.y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; recalcShapeAndBoundingRect(); } /*! * Find y value which corresponds to a @p x . @p valueFound indicates, if value was found. * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. * @param x * @param valueFound * @return */ double XYCurve::y(double x, bool &valueFound) const { if (!yColumn() || !xColumn()) { valueFound = false; return NAN; } auto yColumnMode = yColumn()->columnMode(); int index = xColumn()->indexForValue(x); if (index < 0) { valueFound = false; return NAN; } valueFound = true; if (yColumnMode == AbstractColumn::ColumnMode::Numeric || yColumnMode == AbstractColumn::ColumnMode::Integer || yColumnMode == AbstractColumn::ColumnMode::BigInt) { return yColumn()->valueAt(index); } else { valueFound = false; return NAN; } } /*! * Find y DateTime which corresponds to a @p x . @p valueFound indicates, if value was found. * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. * @param x * @param valueFound * @return Return found value */ QDateTime XYCurve::yDateTime(double x, bool &valueFound) const { if (!yColumn() || !xColumn()) { valueFound = false; return QDateTime(); } auto yColumnMode = yColumn()->columnMode(); int index = xColumn()->indexForValue(x); if (index < 0) { valueFound = false; return QDateTime(); } valueFound = true; if (yColumnMode == AbstractColumn::ColumnMode::Day || yColumnMode == AbstractColumn::ColumnMode::Month || yColumnMode == AbstractColumn::ColumnMode::DateTime) return yColumn()->dateTimeAt(index); valueFound = false; return QDateTime(); } bool XYCurve::minMaxY(int indexMin, int indexMax, double& yMin, double& yMax, bool includeErrorBars) const { return minMax(yColumn(), xColumn(), yErrorType(), yErrorPlusColumn(), yErrorMinusColumn(), indexMin, indexMax, yMin, yMax, includeErrorBars); } bool XYCurve::minMaxX(int indexMin, int indexMax, double& xMin, double& xMax, bool includeErrorBars) const { return minMax(xColumn(), yColumn(), xErrorType(), xErrorPlusColumn(), xErrorMinusColumn(), indexMin, indexMax, xMin, xMax, includeErrorBars); } /*! * Calculates the minimum \p min and maximum \p max of a curve with optionally respecting the error bars * This function does not check if the values are out of range * \p indexMax is not included * \p column * \p errorType * \p errorPlusColumn * \p errorMinusColumn * \p indexMin * \p indexMax * \p min * \p max * \ includeErrorBars If true respect the error bars in the min/max calculation */ bool XYCurve::minMax(const AbstractColumn* column1, const AbstractColumn* column2, const ErrorType errorType, const AbstractColumn* errorPlusColumn, const AbstractColumn* errorMinusColumn, int indexMin, int indexMax, double& min, double& max, bool includeErrorBars) const { // when property is increasing or decreasing there is a benefit in finding minimum and maximum // for property == AbstractColumn::Properties::No it must be iterated over all values so it does not matter if this function or the below one is used // if the property of the second column is not AbstractColumn::Properties::No means, that all values are valid and not masked if ((!includeErrorBars || errorType == XYCurve::NoError) && column1->properties() != AbstractColumn::Properties::No && column2 && column2->properties() != AbstractColumn::Properties::No) { min = column1->minimum(indexMin, indexMax); max = column1->maximum(indexMin, indexMax); return true; } if (column1->rowCount() == 0) return false; min = INFINITY; max = -INFINITY; for (int i = indexMin; i < indexMax; ++i) { if (!column1->isValid(i) || column1->isMasked(i) || (column2 && (!column2->isValid(i) || column2->isMasked(i)))) continue; if ( (errorPlusColumn && i >= errorPlusColumn->rowCount()) || (errorMinusColumn && i >= errorMinusColumn->rowCount()) ) continue; //determine the values for the errors double errorPlus, errorMinus; if (errorPlusColumn && errorPlusColumn->isValid(i) && !errorPlusColumn->isMasked(i)) if (errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Numeric || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Integer || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) errorPlus = errorPlusColumn->valueAt(i); else if (errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Month || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Day) errorPlus = errorPlusColumn->dateTimeAt(i).toMSecsSinceEpoch(); else return false; else errorPlus = 0; if (errorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (errorMinusColumn && errorMinusColumn->isValid(i) && !errorMinusColumn->isMasked(i)) if (errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Numeric || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Integer || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) errorMinus = errorMinusColumn->valueAt(i); else if (errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Month || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Day) errorMinus = errorMinusColumn->dateTimeAt(i).toMSecsSinceEpoch(); else return false; else errorMinus = 0; } double value; if (column1->columnMode() == AbstractColumn::ColumnMode::Numeric || column1->columnMode() == AbstractColumn::ColumnMode::Integer || column1->columnMode() == AbstractColumn::ColumnMode::BigInt) value = column1->valueAt(i); else if (column1->columnMode() == AbstractColumn::ColumnMode::DateTime || column1->columnMode() == AbstractColumn::ColumnMode::Month || column1->columnMode() == AbstractColumn::ColumnMode::Day) { value = column1->dateTimeAt(i).toMSecsSinceEpoch(); } else return false; if (value - errorMinus < min) min = value - errorMinus; if (value + errorPlus > max) max = value + errorPlus; } return true; } /*! * \brief XYCurve::activateCurve * Checks if the mousepos distance to the curve is less than @p maxDist * \p mouseScenePos * \p maxDist Maximum distance the point lies away from the curve * \return Returns true if the distance is smaller than maxDist. */ bool XYCurve::activateCurve(QPointF mouseScenePos, double maxDist) { Q_D(XYCurve); return d->activateCurve(mouseScenePos, maxDist); } bool XYCurvePrivate::activateCurve(QPointF mouseScenePos, double maxDist) { if (!isVisible()) return false; int rowCount = 0; if (lineType != XYCurve::LineType::NoLine) rowCount = lines.count(); else if (symbolsStyle != Symbol::Style::NoSymbols) rowCount = symbolPointsScene.count(); else return false; if (rowCount == 0) return false; if (maxDist < 0) maxDist = (linePen.width() < 10) ? 10 : linePen.width(); double maxDistSquare = gsl_pow_2(maxDist); auto properties = q->xColumn()->properties(); if (properties == AbstractColumn::Properties::No) { // assumption: points exist if no line. otherwise previously returned false if (lineType == XYCurve::NoLine) { QPointF curvePosPrevScene = symbolPointsScene[0]; QPointF curvePosScene = curvePosPrevScene; for (int row = 0; row < rowCount; row ++) { if (gsl_pow_2(mouseScenePos.x() - curvePosScene.x()) + gsl_pow_2(mouseScenePos.y() - curvePosScene.y()) <= maxDistSquare) return true; curvePosPrevScene = curvePosScene; curvePosScene = symbolPointsScene[row]; } } else { for (int row = 0; row < rowCount; row++) { QLineF line = lines[row]; if (pointLiesNearLine(line.p1(), line.p2(), mouseScenePos, maxDist)) return true; } } } else if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { bool increase = true; if (properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; double x = mouseScenePos.x()-maxDist; int index = 0; QPointF curvePosScene; QPointF curvePosPrevScene; if (lineType == XYCurve::NoLine) { curvePosScene = symbolPointsScene[index]; curvePosPrevScene = curvePosScene; index = Column::indexForValue(x, symbolPointsScene, static_cast(properties)); } else index = Column::indexForValue(x, lines, static_cast(properties)); if (index >= 1) index --; // use one before so it is secured that I'm before point.x() else if (index == -1) return false; double xMaxSquare = mouseScenePos.x() + maxDist; bool stop = false; while (true) { // assumption: points exist if no line. otherwise previously returned false if (lineType == XYCurve::NoLine) {// check points only if no line otherwise check only the lines if (curvePosScene.x() > xMaxSquare) stop = true; // one more time if bigger if (gsl_pow_2(mouseScenePos.x()- curvePosScene.x()) + gsl_pow_2(mouseScenePos.y()-curvePosScene.y()) <= maxDistSquare) return true; } else { if (lines[index].p1().x() > xMaxSquare) stop = true; // one more time if bigger QLineF line = lines[index]; if (pointLiesNearLine(line.p1(), line.p2(), mouseScenePos, maxDist)) return true; } if (stop || (index >= rowCount-1 && increase) || (index <=0 && !increase)) break; if (increase) index++; else index--; if (lineType == XYCurve::NoLine) { curvePosPrevScene = curvePosScene; curvePosScene = symbolPointsScene[index]; } } } return false; } /*! * \brief XYCurve::pointLiesNearLine * Calculates if a point \p pos lies near than maxDist to the line created by the points \p p1 and \p p2 * https://stackoverflow.com/questions/11604680/point-laying-near-line * \p p1 first point of the line * \p p2 second point of the line * \p pos Position to check * \p maxDist Maximal distance away from the curve, which is valid * \return Return true if point lies next to the line */ bool XYCurvePrivate::pointLiesNearLine(const QPointF p1, const QPointF p2, const QPointF pos, const double maxDist) const{ double dx12 = p2.x() - p1.x(); double dy12 = p2.y() - p1.y(); double vecLength = gsl_hypot(dx12, dy12); if (vecLength == 0) { if (gsl_pow_2(p1.x() - pos.x()) + gsl_pow_2(p1.y()-pos.y()) <= gsl_pow_2(maxDist)) return true; return false; } QPointF unitvec(dx12/vecLength, dy12/vecLength); double dx1m = pos.x() - p1.x(); double dy1m = pos.y() - p1.y(); double dist_segm = std::abs(dx1m*unitvec.y() - dy1m*unitvec.x()); double scalarProduct = dx1m*unitvec.x() + dy1m*unitvec.y(); if (scalarProduct > 0) { if (scalarProduct < vecLength && dist_segm < maxDist) return true; } return false; } // TODO: curvePosScene.x() >= mouseScenePos.x() && // curvePosPrevScene.x() < mouseScenePos.x() // dürfte eigentlich nicht drin sein bool XYCurvePrivate::pointLiesNearCurve(const QPointF mouseScenePos, const QPointF curvePosPrevScene, const QPointF curvePosScene, const int index, const double maxDist) const { if (q->lineType() != XYCurve::LineType::NoLine && curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { if (q->lineType() == XYCurve::LineType::Line) { // point is not in the near of the point, but it can be in the near of the connection line of two points if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::StartHorizontal) { QPointF tempPoint = curvePosPrevScene; tempPoint.setX(curvePosScene.x()); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::StartVertical) { QPointF tempPoint = curvePosPrevScene; tempPoint.setY(curvePosScene.y()); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::MidpointHorizontal) { QPointF tempPoint = curvePosPrevScene; tempPoint.setX(curvePosPrevScene.x()+(curvePosScene.x()-curvePosPrevScene.x())/2); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; QPointF tempPoint2(tempPoint.x(), curvePosScene.y()); if (pointLiesNearLine(tempPoint,tempPoint2, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint2,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::MidpointVertical) { QPointF tempPoint = curvePosPrevScene; tempPoint.setY(curvePosPrevScene.y()+(curvePosScene.y()-curvePosPrevScene.y())/2); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; QPointF tempPoint2(tempPoint.y(), curvePosScene.x()); if (pointLiesNearLine(tempPoint,tempPoint2, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint2,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::SplineAkimaNatural || q->lineType() == XYCurve::LineType::SplineCubicNatural || q->lineType() == XYCurve::LineType::SplineAkimaPeriodic || q->lineType() == XYCurve::LineType::SplineCubicPeriodic) { for (int i=0; i < q->lineInterpolationPointsCount()+1; i++) { QLineF line = lines[index*(q->lineInterpolationPointsCount()+1)+i]; QPointF p1 = line.p1(); //cSystem->mapLogicalToScene(line.p1()); QPointF p2 = line.p2(); //cSystem->mapLogicalToScene(line.p2()); if (pointLiesNearLine(p1, p2, mouseScenePos, maxDist)) return true; } } else { // point is not in the near of the point, but it can be in the near of the connection line of two points if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) return true; } } return false; } /*! * \brief XYCurve::setHover * Will be called in CartesianPlot::hoverMoveEvent() * See d->setHover(on) for more documentation * \p on */ void XYCurve::setHover(bool on) { Q_D(XYCurve); d->setHover(on); } void XYCurvePrivate::updateErrorBars() { errorBarsPath = QPainterPath(); if (xErrorType == XYCurve::NoError && yErrorType == XYCurve::NoError) { recalcShapeAndBoundingRect(); return; } QVector lines; QVector pointsErrorBarAnchorX; QVector pointsErrorBarAnchorY; float errorPlus, errorMinus; for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); int index = validPointsIndicesLogical.at(i); //error bars for x if (xErrorType != XYCurve::NoError) { //determine the values for the errors if (xErrorPlusColumn && xErrorPlusColumn->isValid(index) && !xErrorPlusColumn->isMasked(index)) errorPlus = xErrorPlusColumn->valueAt(index); else errorPlus = 0; if (xErrorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (xErrorMinusColumn && xErrorMinusColumn->isValid(index) && !xErrorMinusColumn->isMasked(index)) errorMinus = xErrorMinusColumn->valueAt(index); else errorMinus = 0; } //draw the error bars if (errorMinus != 0 || errorPlus !=0) lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()), QPointF(point.x()+errorPlus, point.y()))); //determine the end points of the errors bars in logical coordinates to draw later the cap if (errorBarsType == XYCurve::ErrorBarsWithEnds) { pointsErrorBarAnchorX << QPointF(point.x() - errorMinus, point.y()); pointsErrorBarAnchorX << QPointF(point.x() + errorPlus, point.y()); } } //error bars for y if (yErrorType != XYCurve::NoError) { //determine the values for the errors if (yErrorPlusColumn && yErrorPlusColumn->isValid(index) && !yErrorPlusColumn->isMasked(index)) errorPlus = yErrorPlusColumn->valueAt(index); else errorPlus = 0; if (yErrorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (yErrorMinusColumn && yErrorMinusColumn->isValid(index) && !yErrorMinusColumn->isMasked(index) ) errorMinus = yErrorMinusColumn->valueAt(index); else errorMinus = 0; } //draw the error bars if (errorMinus != 0 || errorPlus !=0) lines.append(QLineF(QPointF(point.x(), point.y() + errorMinus), QPointF(point.x(), point.y() - errorPlus))); //determine the end points of the errors bars in logical coordinates to draw later the cap if (errorBarsType == XYCurve::ErrorBarsWithEnds) { pointsErrorBarAnchorY << QPointF(point.x(), point.y() + errorMinus); pointsErrorBarAnchorY << QPointF(point.x(), point.y() - errorPlus); } } } //map the error bars to scene coordinates lines = cSystem->mapLogicalToScene(lines); //new painter path for the error bars for (const auto& line : lines) { errorBarsPath.moveTo(line.p1()); errorBarsPath.lineTo(line.p2()); } //add caps for x error bars if (!pointsErrorBarAnchorX.isEmpty()) { pointsErrorBarAnchorX = cSystem->mapLogicalToScene(pointsErrorBarAnchorX); for (const auto& point : pointsErrorBarAnchorX) { errorBarsPath.moveTo(QPointF(point.x(), point.y() - errorBarsCapSize/2)); errorBarsPath.lineTo(QPointF(point.x(), point.y() + errorBarsCapSize/2)); } } //add caps for y error bars if (!pointsErrorBarAnchorY.isEmpty()) { pointsErrorBarAnchorY = cSystem->mapLogicalToScene(pointsErrorBarAnchorY); for (const auto& point : pointsErrorBarAnchorY) { errorBarsPath.moveTo(QPointF(point.x() - errorBarsCapSize/2, point.y())); errorBarsPath.lineTo(QPointF(point.x() + errorBarsCapSize/2, point.y())); } } recalcShapeAndBoundingRect(); } /*! recalculates the outer bounds and the shape of the curve. */ void XYCurvePrivate::recalcShapeAndBoundingRect() { DEBUG("XYCurvePrivate::recalcShapeAndBoundingRect() m_suppressRecalc = " << m_suppressRecalc); if (m_suppressRecalc) return; #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::recalcShapeAndBoundingRect()"); #endif prepareGeometryChange(); curveShape = QPainterPath(); if (lineType != XYCurve::NoLine) curveShape.addPath(WorksheetElement::shapeFromPath(linePath, linePen)); if (dropLineType != XYCurve::NoDropLine) curveShape.addPath(WorksheetElement::shapeFromPath(dropLinePath, dropLinePen)); if (symbolsStyle != Symbol::NoSymbols) curveShape.addPath(symbolsPath); if (valuesType != XYCurve::NoValues) curveShape.addPath(valuesPath); if (xErrorType != XYCurve::NoError || yErrorType != XYCurve::NoError) curveShape.addPath(WorksheetElement::shapeFromPath(errorBarsPath, errorBarsPen)); boundingRectangle = curveShape.boundingRect(); for (const auto& pol : fillPolygons) boundingRectangle = boundingRectangle.united(pol.boundingRect()); //TODO: when the selection is painted, line intersections are visible. //simplified() removes those artifacts but is horrible slow for curves with large number of points. //search for an alternative. //curveShape = curveShape.simplified(); updatePixmap(); } void XYCurvePrivate::draw(QPainter* painter) { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::draw()"); #endif //draw filling if (fillingPosition != XYCurve::NoFilling) { painter->setOpacity(fillingOpacity); painter->setPen(Qt::SolidLine); drawFilling(painter); } //draw lines if (lineType != XYCurve::NoLine) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::NoBrush); painter->drawPath(linePath); } //draw drop lines if (dropLineType != XYCurve::NoDropLine) { painter->setOpacity(dropLineOpacity); painter->setPen(dropLinePen); painter->setBrush(Qt::NoBrush); painter->drawPath(dropLinePath); } //draw error bars if ( (xErrorType != XYCurve::NoError) || (yErrorType != XYCurve::NoError) ) { painter->setOpacity(errorBarsOpacity); painter->setPen(errorBarsPen); painter->setBrush(Qt::NoBrush); painter->drawPath(errorBarsPath); } //draw symbols if (symbolsStyle != Symbol::NoSymbols) { painter->setOpacity(symbolsOpacity); painter->setPen(symbolsPen); painter->setBrush(symbolsBrush); drawSymbols(painter); } //draw values if (valuesType != XYCurve::NoValues) { painter->setOpacity(valuesOpacity); //don't use any painter pen, since this will force QPainter to render the text outline which is expensive painter->setPen(Qt::NoPen); painter->setBrush(valuesColor); drawValues(painter); } } void XYCurvePrivate::updatePixmap() { DEBUG("XYCurvePrivate::updatePixmap() m_suppressRecalc = " << m_suppressRecalc); if (m_suppressRecalc) return; WAIT_CURSOR; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; if (boundingRectangle.width() == 0 || boundingRectangle.height() == 0) { DEBUG(" boundingRectangle.width() or boundingRectangle.height() == 0"); m_pixmap = QPixmap(); RESET_CURSOR; return; } QPixmap pixmap(ceil(boundingRectangle.width()), ceil(boundingRectangle.height())); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(-boundingRectangle.topLeft()); draw(&painter); painter.end(); m_pixmap = pixmap; update(); RESET_CURSOR; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve. \sa QGraphicsItem::paint(). */ void XYCurvePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if ( KSharedConfig::openConfig()->group("Settings_Worksheet").readEntry("DoubleBuffering", true) ) painter->drawPixmap(boundingRectangle.topLeft(), m_pixmap); //draw the cached pixmap (fast) else draw(painter); //draw directly again (slow) if (m_hovered && !isSelected() && !m_printing) { if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; QPainter p(&pix); p.setCompositionMode(QPainter::CompositionMode_SourceIn); // source (shadow) pixels merged with the alpha channel of the destination (m_pixmap) p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Shadow)); p.end(); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); return; } if (isSelected() && !m_printing) { if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; QPainter p(&pix); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Highlight)); p.end(); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); } } /*! Drawing of symbolsPath is very slow, so we draw every symbol in the loop which is much faster (factor 10) */ void XYCurvePrivate::drawSymbols(QPainter* painter) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(-symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); painter->drawPath(trafo.map(path)); } } void XYCurvePrivate::drawValues(QPainter* painter) { QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle != 0) trafo.rotate( -valuesRotationAngle ); painter->drawPath(trafo.map(path)); } } void XYCurvePrivate::drawFilling(QPainter* painter) { for (const auto& pol : fillPolygons) { QRectF rect = pol.boundingRect(); if (fillingType == PlotArea::Color) { switch (fillingColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(fillingFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, fillingFirstColor); radialGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (fillingType == PlotArea::Image) { if ( !fillingFileName.trimmed().isEmpty() ) { QPixmap pix(fillingFileName); switch (fillingImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Centered: { QPixmap backpix(rect.size().toSize()); backpix.fill(); QPainter p(&backpix); p.drawPixmap(QPointF(0, 0), pix); p.end(); painter->setBrush(QBrush(backpix)); painter->setBrushOrigin(-pix.size().width()/2, -pix.size().height()/2); break; } case PlotArea::Tiled: painter->setBrush(QBrush(pix)); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); } } } else if (fillingType == PlotArea::Pattern) painter->setBrush(QBrush(fillingFirstColor, fillingBrushStyle)); painter->drawPolygon(pol); } } void XYCurvePrivate::setPrinting(bool on) { m_printing = on; } void XYCurvePrivate::suppressRetransform(bool on) { m_suppressRetransform = on; m_suppressRecalc = on; } /*! * \brief XYCurvePrivate::mousePressEvent * checks with activateCurve, if the mousePress was in the near * of the curve. If it was, the curve will be selected * \p event */ void XYCurvePrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (plot->mouseMode() != CartesianPlot::MouseMode::SelectionMode) { event->ignore(); return QGraphicsItem::mousePressEvent(event); } if(q->activateCurve(event->pos())){ setSelected(true); return; } event->ignore(); setSelected(false); QGraphicsItem::mousePressEvent(event); } /*! * \brief XYCurvePrivate::setHover * Will be called from CartesianPlot::hoverMoveEvent which * determines, which curve is hovered * \p on */ void XYCurvePrivate::setHover(bool on) { if(on == m_hovered) return; // don't update if state not changed m_hovered = on; on ? emit q->hovered() : emit q->unhovered(); update(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYCurve); writer->writeStartElement( "xyCurve" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_COLUMN(d->xColumn, xColumn); WRITE_COLUMN(d->yColumn, yColumn); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Line writer->writeStartElement( "lines" ); writer->writeAttribute( "type", QString::number(d->lineType) ); writer->writeAttribute( "skipGaps", QString::number(d->lineSkipGaps) ); writer->writeAttribute( "increasingXOnly", QString::number(d->lineIncreasingXOnly) ); writer->writeAttribute( "interpolationPointsCount", QString::number(d->lineInterpolationPointsCount) ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeEndElement(); //Drop lines writer->writeStartElement( "dropLines" ); writer->writeAttribute( "type", QString::number(d->dropLineType) ); WRITE_QPEN(d->dropLinePen); writer->writeAttribute( "opacity", QString::number(d->dropLineOpacity) ); writer->writeEndElement(); //Symbols writer->writeStartElement( "symbols" ); writer->writeAttribute( "symbolsStyle", QString::number(d->symbolsStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolsOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolsRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolsSize) ); WRITE_QBRUSH(d->symbolsBrush); WRITE_QPEN(d->symbolsPen); writer->writeEndElement(); //Values writer->writeStartElement( "values" ); writer->writeAttribute( "type", QString::number(d->valuesType) ); WRITE_COLUMN(d->valuesColumn, valuesColumn); writer->writeAttribute( "position", QString::number(d->valuesPosition) ); writer->writeAttribute( "distance", QString::number(d->valuesDistance) ); writer->writeAttribute( "rotation", QString::number(d->valuesRotationAngle) ); writer->writeAttribute( "opacity", QString::number(d->valuesOpacity) ); //TODO values format and precision writer->writeAttribute( "prefix", d->valuesPrefix ); writer->writeAttribute( "suffix", d->valuesSuffix ); WRITE_QCOLOR(d->valuesColor); WRITE_QFONT(d->valuesFont); writer->writeEndElement(); //Filling writer->writeStartElement( "filling" ); writer->writeAttribute( "position", QString::number(d->fillingPosition) ); writer->writeAttribute( "type", QString::number(d->fillingType) ); writer->writeAttribute( "colorStyle", QString::number(d->fillingColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->fillingImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->fillingBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->fillingFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->fillingFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->fillingFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->fillingSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->fillingSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->fillingSecondColor.blue()) ); writer->writeAttribute( "fileName", d->fillingFileName ); writer->writeAttribute( "opacity", QString::number(d->fillingOpacity) ); writer->writeEndElement(); //Error bars writer->writeStartElement( "errorBars" ); writer->writeAttribute( "xErrorType", QString::number(d->xErrorType) ); WRITE_COLUMN(d->xErrorPlusColumn, xErrorPlusColumn); WRITE_COLUMN(d->xErrorMinusColumn, xErrorMinusColumn); writer->writeAttribute( "yErrorType", QString::number(d->yErrorType) ); WRITE_COLUMN(d->yErrorPlusColumn, yErrorPlusColumn); WRITE_COLUMN(d->yErrorMinusColumn, yErrorMinusColumn); writer->writeAttribute( "type", QString::number(d->errorBarsType) ); writer->writeAttribute( "capSize", QString::number(d->errorBarsCapSize) ); WRITE_QPEN(d->errorBarsPen); writer->writeAttribute( "opacity", QString::number(d->errorBarsOpacity) ); writer->writeEndElement(); writer->writeEndElement(); //close "xyCurve" section } //! Load from XML bool XYCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYCurve); 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() == "xyCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); READ_COLUMN(xColumn); READ_COLUMN(yColumn); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "lines") { attribs = reader->attributes(); READ_INT_VALUE("type", lineType, XYCurve::LineType); READ_INT_VALUE("skipGaps", lineSkipGaps, bool); READ_INT_VALUE("increasingXOnly", lineIncreasingXOnly, bool); READ_INT_VALUE("interpolationPointsCount", lineInterpolationPointsCount, int); READ_QPEN(d->linePen); READ_DOUBLE_VALUE("opacity", lineOpacity); } else if (!preview && reader->name() == "dropLines") { attribs = reader->attributes(); READ_INT_VALUE("type", dropLineType, XYCurve::DropLineType); READ_QPEN(d->dropLinePen); READ_DOUBLE_VALUE("opacity", dropLineOpacity); } else if (!preview && reader->name() == "symbols") { attribs = reader->attributes(); READ_INT_VALUE("symbolsStyle", symbolsStyle, Symbol::Style); READ_DOUBLE_VALUE("opacity", symbolsOpacity); READ_DOUBLE_VALUE("rotation", symbolsRotationAngle); READ_DOUBLE_VALUE("size", symbolsSize); READ_QBRUSH(d->symbolsBrush); READ_QPEN(d->symbolsPen); } else if (!preview && reader->name() == "values") { attribs = reader->attributes(); READ_INT_VALUE("type", valuesType, XYCurve::ValuesType); READ_COLUMN(valuesColumn); READ_INT_VALUE("position", valuesPosition, XYCurve::ValuesPosition); READ_DOUBLE_VALUE("distance", valuesDistance); READ_DOUBLE_VALUE("rotation", valuesRotationAngle); READ_DOUBLE_VALUE("opacity", valuesOpacity); //don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml) d->valuesPrefix = attribs.value("prefix").toString(); d->valuesSuffix = attribs.value("suffix").toString(); READ_QCOLOR(d->valuesColor); READ_QFONT(d->valuesFont); } else if (!preview && reader->name() == "filling") { attribs = reader->attributes(); READ_INT_VALUE("position", fillingPosition, XYCurve::FillingPosition); READ_INT_VALUE("type", fillingType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", fillingColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", fillingImageStyle, PlotArea::BackgroundImageStyle ); READ_INT_VALUE("brushStyle", fillingBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->fillingFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->fillingFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->fillingFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->fillingSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->fillingSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->fillingSecondColor.setBlue(str.toInt()); READ_STRING_VALUE("fileName", fillingFileName); READ_DOUBLE_VALUE("opacity", fillingOpacity); } else if (!preview && reader->name() == "errorBars") { attribs = reader->attributes(); READ_INT_VALUE("xErrorType", xErrorType, XYCurve::ErrorType); READ_COLUMN(xErrorPlusColumn); READ_COLUMN(xErrorMinusColumn); READ_INT_VALUE("yErrorType", yErrorType, XYCurve::ErrorType); READ_COLUMN(yErrorPlusColumn); READ_COLUMN(yErrorMinusColumn); READ_INT_VALUE("type", errorBarsType, XYCurve::ErrorBarsType); READ_DOUBLE_VALUE("capSize", errorBarsCapSize); READ_QPEN(d->errorBarsPen); READ_DOUBLE_VALUE("opacity", errorBarsOpacity); } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void XYCurve::loadThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); int index = parentAspect()->indexOfChild(this); const auto* plot = dynamic_cast(parentAspect()); QColor themeColor; if (indexthemeColorPalette().size()) themeColor = plot->themeColorPalette().at(index); else { if (plot->themeColorPalette().size()) themeColor = plot->themeColorPalette().last(); } QPen p; Q_D(XYCurve); d->m_suppressRecalc = true; //Line p.setStyle((Qt::PenStyle)group.readEntry("LineStyle", (int)Qt::SolidLine)); - p.setWidthF(group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point))); + p.setWidthF(group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point))); p.setColor(themeColor); this->setLinePen(p); this->setLineOpacity(group.readEntry("LineOpacity", 1.0)); //Drop line p.setStyle((Qt::PenStyle)group.readEntry("DropLineStyle", (int)Qt::SolidLine)); - p.setWidthF(group.readEntry("DropLineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point))); + p.setWidthF(group.readEntry("DropLineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point))); p.setColor(themeColor); this->setDropLinePen(p); this->setDropLineOpacity(group.readEntry("DropLineOpacity", 1.0)); //Symbol this->setSymbolsOpacity(group.readEntry("SymbolOpacity", 1.0)); QBrush brush; brush.setStyle((Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern)); brush.setColor(themeColor); this->setSymbolsBrush(brush); p.setStyle((Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine)); p.setColor(themeColor); - p.setWidthF(group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point))); + p.setWidthF(group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Unit::Point))); this->setSymbolsPen(p); //Values this->setValuesOpacity(group.readEntry("ValuesOpacity", 1.0)); this->setValuesColor(group.readEntry("ValuesColor", QColor(Qt::black))); //Filling this->setFillingBrushStyle((Qt::BrushStyle)group.readEntry("FillingBrushStyle", (int)Qt::SolidPattern)); this->setFillingColorStyle((PlotArea::BackgroundColorStyle)group.readEntry("FillingColorStyle", (int)PlotArea::SingleColor)); this->setFillingOpacity(group.readEntry("FillingOpacity", 1.0)); this->setFillingPosition((XYCurve::FillingPosition)group.readEntry("FillingPosition", (int)XYCurve::NoFilling)); this->setFillingFirstColor(themeColor); this->setFillingSecondColor(group.readEntry("FillingSecondColor", QColor(Qt::black))); this->setFillingType((PlotArea::BackgroundType)group.readEntry("FillingType", (int)PlotArea::Color)); //Error Bars p.setStyle((Qt::PenStyle)group.readEntry("ErrorBarsStyle", (int)Qt::SolidLine)); - p.setWidthF(group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point))); + p.setWidthF(group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Unit::Point))); p.setColor(themeColor); this->setErrorBarsPen(p); this->setErrorBarsOpacity(group.readEntry("ErrorBarsOpacity", 1.0)); d->m_suppressRecalc = false; d->recalcShapeAndBoundingRect(); } void XYCurve::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); //Drop line group.writeEntry("DropLineColor",(QColor) this->dropLinePen().color()); group.writeEntry("DropLineStyle",(int) this->dropLinePen().style()); group.writeEntry("DropLineWidth", this->dropLinePen().widthF()); group.writeEntry("DropLineOpacity",this->dropLineOpacity()); //Error Bars group.writeEntry("ErrorBarsCapSize",this->errorBarsCapSize()); group.writeEntry("ErrorBarsOpacity",this->errorBarsOpacity()); group.writeEntry("ErrorBarsColor",(QColor) this->errorBarsPen().color()); group.writeEntry("ErrorBarsStyle",(int) this->errorBarsPen().style()); group.writeEntry("ErrorBarsWidth", this->errorBarsPen().widthF()); //Filling group.writeEntry("FillingBrushStyle",(int) this->fillingBrushStyle()); group.writeEntry("FillingColorStyle",(int) this->fillingColorStyle()); group.writeEntry("FillingOpacity", this->fillingOpacity()); group.writeEntry("FillingPosition",(int) this->fillingPosition()); group.writeEntry("FillingSecondColor",(QColor) this->fillingSecondColor()); group.writeEntry("FillingType",(int) this->fillingType()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineStyle",(int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Symbol group.writeEntry("SymbolOpacity", this->symbolsOpacity()); //Values group.writeEntry("ValuesOpacity", this->valuesOpacity()); group.writeEntry("ValuesColor", (QColor) this->valuesColor()); group.writeEntry("ValuesFont", this->valuesFont()); int index = parentAspect()->indexOfChild(this); if (index < 5) { KConfigGroup themeGroup = config.group("Theme"); for (int i = index; i<5; i++) { QString s = "ThemePaletteColor" + QString::number(i+1); themeGroup.writeEntry(s,(QColor) this->linePen().color()); } } } diff --git a/src/commonfrontend/datapicker/DatapickerImageView.cpp b/src/commonfrontend/datapicker/DatapickerImageView.cpp index cecafe410..3b61e93a4 100644 --- a/src/commonfrontend/datapicker/DatapickerImageView.cpp +++ b/src/commonfrontend/datapicker/DatapickerImageView.cpp @@ -1,859 +1,859 @@ /*************************************************************************** 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 #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 ); + viewport()->setAttribute(Qt::WA_OpaquePaintEvent); + viewport()->setAttribute(Qt::WA_NoSystemBackground); setCacheMode(QGraphicsView::CacheBackground); initActions(); initMenus(); m_image->setSegmentsHoverEvent(true); setInteractive(true); changeZoom(zoomOriginAction); currentZoomAction = zoomInViewAction; - if (m_image->plotPointsType() == DatapickerImage::AxisPoints) + if (m_image->plotPointsType() == DatapickerImage::PointsType::AxisPoints) setAxisPointsAction->setChecked(true); - else if (m_image->plotPointsType() == DatapickerImage::CurvePoints) + else if (m_image->plotPointsType() == DatapickerImage::PointsType::CurvePoints) setCurvePointsAction->setChecked(true); else selectSegmentAction->setChecked(true); handleImageActions(); changeRotationAngle(); //signal/slot connections //for general actions connect(m_image, &DatapickerImage::requestProjectContextMenu, this, &DatapickerImageView::createContextMenu); connect(m_image, &DatapickerImage::requestUpdate, this, &DatapickerImageView::updateBackground); connect(m_image, &DatapickerImage::requestUpdateActions, this, &DatapickerImageView::handleImageActions); connect(m_datapicker, &Datapicker::requestUpdateActions, this, &DatapickerImageView::handleImageActions); connect(m_image, &DatapickerImage::rotationAngleChanged, this, &DatapickerImageView::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); + float w = Worksheet::convertFromSceneUnits(sceneRect().width(), Worksheet::Unit::Inch); + float h = Worksheet::convertFromSceneUnits(sceneRect().height(), Worksheet::Unit::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)); + static const float hscale = QApplication::desktop()->physicalDpiX()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); + static const float vscale = QApplication::desktop()->physicalDpiY()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); setTransform(QTransform::fromScale(hscale, vscale)); } DatapickerImageView::~DatapickerImageView() { delete m_transform; } void DatapickerImageView::initActions() { auto* zoomActionGroup = new QActionGroup(this); auto* mouseModeActionGroup = 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 navigationModeAction = new QAction(QIcon::fromTheme("input-mouse"), i18n("Navigate"), mouseModeActionGroup); navigationModeAction->setCheckable(true); navigationModeAction->setData(NavigationMode); zoomSelectionModeAction = new QAction(QIcon::fromTheme("page-zoom"), i18n("Select and Zoom"), mouseModeActionGroup); zoomSelectionModeAction->setCheckable(true); zoomSelectionModeAction->setData(ZoomSelectionMode); setAxisPointsAction = new QAction(QIcon::fromTheme("labplot-plot-axis-points"), i18n("Set Axis Points"), mouseModeActionGroup); setAxisPointsAction->setCheckable(true); setAxisPointsAction->setData(ReferencePointsEntryMode); setCurvePointsAction = new QAction(QIcon::fromTheme("labplot-xy-curve-points"), i18n("Set Curve Points"), mouseModeActionGroup); setCurvePointsAction->setCheckable(true); setCurvePointsAction->setData(CurvePointsEntryMode); selectSegmentAction = new QAction(QIcon::fromTheme("labplot-xy-curve-segments"), i18n("Select Curve Segments"), mouseModeActionGroup); selectSegmentAction->setCheckable(true); selectSegmentAction->setData(CurveSegmentsEntryMode); 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); //set some default values currentZoomAction = zoomInViewAction; currentMagnificationAction = noMagnificationAction; switch(m_image->plotPointsType()) { - case DatapickerImage::AxisPoints: + case DatapickerImage::PointsType::AxisPoints: currentPlotPointsTypeAction = setAxisPointsAction; setAxisPointsAction->setChecked(true); mouseModeChanged(setAxisPointsAction); break; - case DatapickerImage::CurvePoints: + case DatapickerImage::PointsType::CurvePoints: currentPlotPointsTypeAction = setCurvePointsAction; setCurvePointsAction->setChecked(true); mouseModeChanged(setCurvePointsAction); break; - case DatapickerImage::SegmentPoints: + case DatapickerImage::PointsType::SegmentPoints: currentPlotPointsTypeAction = selectSegmentAction; selectSegmentAction->setChecked(true); mouseModeChanged(selectSegmentAction); } //signal-slot connections connect(mouseModeActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::mouseModeChanged); connect(zoomActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::changeZoom); connect(addCurveAction, &QAction::triggered, this, &DatapickerImageView::addCurve); connect(navigationActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::changeSelectedItemsPosition); connect(magnificationActionGroup, &QActionGroup::triggered, this, &DatapickerImageView::magnificationChanged); } void DatapickerImageView::initMenus() { m_viewMouseModeMenu = new QMenu(i18n("Mouse Mode"), this); m_viewMouseModeMenu->setIcon(QIcon::fromTheme("input-mouse")); m_viewMouseModeMenu->addAction(setAxisPointsAction); m_viewMouseModeMenu->addAction(setCurvePointsAction); m_viewMouseModeMenu->addAction(selectSegmentAction); m_viewMouseModeMenu->addSeparator(); m_viewMouseModeMenu->addAction(navigationModeAction); m_viewMouseModeMenu->addAction(zoomSelectionModeAction); 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("zoom-in")); 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->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->addAction(navigationModeAction); toolBar->addAction(zoomSelectionModeAction); toolBar->addSeparator(); toolBar->addAction(addCurveAction); toolBar->addSeparator(); toolBar->addAction(shiftRightAction); toolBar->addAction(shiftLeftAction); toolBar->addAction(shiftUpAction); toolBar->addAction(shiftDownAction); tbZoom = new QToolButton(toolBar); tbZoom->setPopupMode(QToolButton::MenuButtonPopup); tbZoom->setMenu(m_zoomMenu); tbZoom->setDefaultAction(currentZoomAction); toolBar->addSeparator(); toolBar->addWidget(tbZoom); tbMagnification = new QToolButton(toolBar); tbMagnification->setPopupMode(QToolButton::MenuButtonPopup); tbMagnification->setMenu(m_magnificationMenu); tbMagnification->setDefaultAction(currentMagnificationAction); toolBar->addWidget(tbMagnification); } 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) { + if (m_image->plotImageType() == DatapickerImage::PlotImageType::OriginalImage) { const 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) { + } else if (m_image->plotImageType() == DatapickerImage::PlotImageType::ProcessedImage) { const 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()); bool entryMode = (m_mouseMode == ReferencePointsEntryMode || m_mouseMode == CurvePointsEntryMode || m_mouseMode == CurveSegmentsEntryMode); //check whether there is a point item under the cursor bool pointsUnderCursor = false; auto items = this->items(event->pos()); for (auto* item : items) { if (item != m_image->m_magnificationWindow) { pointsUnderCursor = true; break; } } if (entryMode && !pointsUnderCursor && m_image->isLoaded && sceneRect().contains(eventPos)) { - if ( m_image->plotPointsType() == DatapickerImage::AxisPoints ) { + if ( m_image->plotPointsType() == DatapickerImage::PointsType::AxisPoints ) { int childCount = m_image->childCount(AbstractAspect::ChildIndexFlag::IncludeHidden); if (childCount < 3) m_datapicker->addNewPoint(eventPos, m_image); - } else if ( m_image->plotPointsType() == DatapickerImage::CurvePoints && m_datapicker->activeCurve() ) { + } else if ( m_image->plotPointsType() == DatapickerImage::PointsType::CurvePoints && m_datapicker->activeCurve() ) { m_datapicker->addNewPoint(eventPos, m_datapicker->activeCurve()); } if (m_image->m_magnificationWindow && m_image->m_magnificationWindow->isVisible()) updateMagnificationWindow(); } // 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) { + if (m_datapicker->activeCurve() && m_image->plotPointsType() != DatapickerImage::PointsType::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) { //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) { + if (m_image->plotPointsType() == DatapickerImage::PointsType::CurvePoints) { QVector3D logicalPos = m_transform->mapSceneToLogical(pos, m_image->axisPoints()); - if (m_image->axisPoints().type == DatapickerImage::Ternary) { + if (m_image->axisPoints().type == DatapickerImage::GraphType::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) { + if (m_image->axisPoints().type == DatapickerImage::GraphType::PolarInDegree) { xLabel = 'r'; yLabel = "y(deg)"; - } else if (m_image->axisPoints().type == DatapickerImage::PolarInRadians) { + } else if (m_image->axisPoints().type == DatapickerImage::GraphType::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_image->isLoaded && sceneRect().contains(pos) ) { if (!m_image->m_magnificationWindow) { m_image->m_magnificationWindow = new QGraphicsPixmapItem; scene()->addItem(m_image->m_magnificationWindow); m_image->m_magnificationWindow->setZValue(std::numeric_limits::max()); } updateMagnificationWindow(); } else if (m_image->m_magnificationWindow) { m_image->m_magnificationWindow->setVisible(false); } QGraphicsView::mouseMoveEvent(event); } void DatapickerImageView::updateMagnificationWindow() { m_image->m_magnificationWindow->setVisible(false); QPointF pos = mapToScene(mapFromGlobal(QCursor::pos())); //copy the part of the view to be shown magnified - const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Centimeter)/transform().m11(); + const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Unit::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); } 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::mouseModeChanged(QAction* action) { m_mouseMode = (DatapickerImageView::MouseMode)action->data().toInt(); if (action == navigationModeAction) { setInteractive(false); setDragMode(QGraphicsView::ScrollHandDrag); m_image->setSegmentsHoverEvent(false); } else if (action == zoomSelectionModeAction){ setInteractive(false); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(false); setCursor(Qt::ArrowCursor); } else { setInteractive(true); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(true); setCursor(Qt::CrossCursor); if (currentPlotPointsTypeAction != action) { if (action == setAxisPointsAction) { int count = m_image->childCount(AbstractAspect::ChildIndexFlag::IncludeHidden); if (count) { auto button = QMessageBox::question(this, i18n("Remove existing reference points?"), - i18n("All available reference points will be removed. Do you want to continue?")); + i18n("All available reference points will be removed. Do you want to continue?")); if (button != QMessageBox::Yes) { currentPlotPointsTypeAction->setChecked(true); return; } } - m_image->setPlotPointsType(DatapickerImage::AxisPoints); + m_image->setPlotPointsType(DatapickerImage::PointsType::AxisPoints); } else if (action == setCurvePointsAction) - m_image->setPlotPointsType(DatapickerImage::CurvePoints); + m_image->setPlotPointsType(DatapickerImage::PointsType::CurvePoints); else if (action == selectSegmentAction) - m_image->setPlotPointsType(DatapickerImage::SegmentPoints); + m_image->setPlotPointsType(DatapickerImage::PointsType::SegmentPoints); currentPlotPointsTypeAction = action; } } } 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)); + static const float hscale = QApplication::desktop()->physicalDpiX()/(25.4*Worksheet::convertToSceneUnits(1, Worksheet::Unit::Millimeter)); + static const float vscale = QApplication::desktop()->physicalDpiY()/(25.4*Worksheet::convertToSceneUnits(1, Worksheet::Unit::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::ChildIndexFlag::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::ChildIndexFlag::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::ChildIndexFlag::IncludeHidden)) { if (!point->graphicsItem()->isSelected()) continue; QPointF newPos = point->position(); newPos = newPos + shift; point->setPosition(newPos); } } m_image->endMacro(); if (m_image->m_magnificationWindow && m_image->m_magnificationWindow->isVisible()) updateMagnificationWindow(); } 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(); setCurvePointsAction->setChecked(true); mouseModeChanged(setCurvePointsAction); } 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::ChildIndexFlag::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); } } 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); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::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); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::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); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::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); + int w = Worksheet::convertFromSceneUnits(scene_rect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(scene_rect.height(), Worksheet::Unit::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) { + if (m_image->plotImageType() == DatapickerImage::PlotImageType::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) { + } else if (m_image->plotImageType() == DatapickerImage::PlotImageType::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); } diff --git a/src/commonfrontend/worksheet/WorksheetView.cpp b/src/commonfrontend/worksheet/WorksheetView.cpp index ec1d364a5..edcfc4d2d 100644 --- a/src/commonfrontend/worksheet/WorksheetView.cpp +++ b/src/commonfrontend/worksheet/WorksheetView.cpp @@ -1,2065 +1,2065 @@ /*************************************************************************** File : WorksheetView.cpp Project : LabPlot Description : Worksheet view -------------------------------------------------------------------- Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 Stefan-Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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/worksheet/WorksheetView.h" #include "backend/core/AbstractColumn.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYCurvePrivate.h" #include "backend/worksheet/Image.h" #include "backend/worksheet/TextLabel.h" #include "commonfrontend/core/PartMdiView.h" #include "kdefrontend/widgets/ThemesWidget.h" #include "kdefrontend/worksheet/GridDialog.h" #include "kdefrontend/worksheet/PresenterWidget.h" #include "kdefrontend/worksheet/DynamicPresenterWidget.h" #include "backend/lib/trace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h" #endif #include /** * \class WorksheetView * \brief Worksheet view */ /*! Constructur of the class. Creates a view for the Worksheet \c worksheet and initializes the internal model. */ WorksheetView::WorksheetView(Worksheet* worksheet) : QGraphicsView(), m_worksheet(worksheet) { setScene(m_worksheet->scene()); setRenderHint(QPainter::Antialiasing); setRubberBandSelectionMode(Qt::ContainsItemBoundingRect); setTransformationAnchor(QGraphicsView::AnchorViewCenter); setResizeAnchor(QGraphicsView::AnchorViewCenter); setMinimumSize(16, 16); setFocusPolicy(Qt::StrongFocus); if (m_worksheet->useViewSize()) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); viewport()->setAttribute( Qt::WA_NoSystemBackground ); setAcceptDrops(true); setCacheMode(QGraphicsView::CacheBackground); m_gridSettings.style = WorksheetView::NoGrid; //signal/slot connections connect(m_worksheet, &Worksheet::requestProjectContextMenu, this, &WorksheetView::createContextMenu); connect(m_worksheet, &Worksheet::itemSelected, this, &WorksheetView::selectItem); connect(m_worksheet, &Worksheet::itemDeselected, this, &WorksheetView::deselectItem); connect(m_worksheet, &Worksheet::requestUpdate, this, &WorksheetView::updateBackground); connect(m_worksheet, &Worksheet::aspectAboutToBeRemoved, this, &WorksheetView::aspectAboutToBeRemoved); connect(m_worksheet, &Worksheet::useViewSizeRequested, this, &WorksheetView::useViewSizeRequested); connect(m_worksheet, &Worksheet::layoutChanged, this, &WorksheetView::layoutChanged); connect(scene(), &QGraphicsScene::selectionChanged, this, &WorksheetView::selectionChanged); //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_worksheet->isLoading()) { - float w = Worksheet::convertFromSceneUnits(sceneRect().width(), Worksheet::Inch); - float h = Worksheet::convertFromSceneUnits(sceneRect().height(), Worksheet::Inch); + float w = Worksheet::convertFromSceneUnits(sceneRect().width(), Worksheet::Unit::Inch); + float h = Worksheet::convertFromSceneUnits(sceneRect().height(), Worksheet::Unit::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)); + static const float hscale = QApplication::desktop()->physicalDpiX()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); + static const float vscale = QApplication::desktop()->physicalDpiY()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); setTransform(QTransform::fromScale(hscale, vscale)); initBasicActions(); } /*! * initializes couple of actions that have shortcuts assigned in the constructor as opposed * to other actions in initAction() that are create on demand only if the context menu is requested */ void WorksheetView::initBasicActions() { selectAllAction = new QAction(QIcon::fromTheme("edit-select-all"), i18n("Select All"), this); this->addAction(selectAllAction); connect(selectAllAction, &QAction::triggered, this, &WorksheetView::selectAllElements); deleteAction = new QAction(QIcon::fromTheme("edit-delete"), i18n("Delete"), this); this->addAction(deleteAction); connect(deleteAction, &QAction::triggered, this, &WorksheetView::deleteElement); backspaceAction = new QAction(this); this->addAction(backspaceAction); connect(backspaceAction, &QAction::triggered, this, &WorksheetView::deleteElement); //Zoom actions zoomInViewAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this); zoomOutViewAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this); zoomOriginAction = new QAction(QIcon::fromTheme("zoom-original"), i18n("Original Size"), this); } void WorksheetView::initActions() { auto* addNewActionGroup = new QActionGroup(this); auto* zoomActionGroup = new QActionGroup(this); auto* mouseModeActionGroup = new QActionGroup(this); auto* layoutActionGroup = new QActionGroup(this); auto* gridActionGroup = new QActionGroup(this); gridActionGroup->setExclusive(true); auto* magnificationActionGroup = new QActionGroup(this); zoomActionGroup->addAction(zoomInViewAction); zoomActionGroup->addAction(zoomOutViewAction); zoomActionGroup->addAction(zoomOriginAction); 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); zoomFitSelectionAction = new QAction(i18n("Fit to Selection"), zoomActionGroup); // Mouse mode actions selectionModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Edit"), mouseModeActionGroup); selectionModeAction->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); //Magnification actions 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); //TODO implement later "group selection action" where multiple objects can be selected by drawing a rectangular // selectionModeAction = new QAction(QIcon::fromTheme("select-rectangular"), i18n("Selection"), mouseModeActionGroup); // selectionModeAction->setCheckable(true); //"Add new" related actions addCartesianPlot1Action = new QAction(QIcon::fromTheme("labplot-xy-plot-four-axes"), i18n("Box Plot, Four Axes"), addNewActionGroup); addCartesianPlot2Action = new QAction(QIcon::fromTheme("labplot-xy-plot-two-axes"), i18n("Box Plot, Two Axes"), addNewActionGroup); addCartesianPlot3Action = new QAction(QIcon::fromTheme("labplot-xy-plot-two-axes-centered"), i18n("Two Axes, Centered"), addNewActionGroup); addCartesianPlot4Action = new QAction(QIcon::fromTheme("labplot-xy-plot-two-axes-centered-origin"), i18n("Two Axes, Crossing at Origin"), addNewActionGroup); addTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), addNewActionGroup); addImageAction = new QAction(QIcon::fromTheme("viewimage"), i18n("Image"), addNewActionGroup); //Layout actions //TODO: the icons labplot-editvlayout and labplot-edithlayout are confusing for the user. //the orientation is visualized as a horizontal or vertical line on the icon, but the user //percieves the two objects (resembles plots on the worksheet) separated by this line much stronger than the line itself. //with this, the two objects separated by a vertical line are percieved to be layed out in a _horizontal_ order and the //same for the vertical line. Because of this we change the icons here. We can rename the icons later in the breeze icon set. verticalLayoutAction = new QAction(QIcon::fromTheme("labplot-edithlayout"), i18n("Vertical Layout"), layoutActionGroup); verticalLayoutAction->setCheckable(true); horizontalLayoutAction = new QAction(QIcon::fromTheme("labplot-editvlayout"), i18n("Horizontal Layout"), layoutActionGroup); horizontalLayoutAction->setCheckable(true); gridLayoutAction = new QAction(QIcon::fromTheme("labplot-editgrid"), i18n("Grid Layout"), layoutActionGroup); gridLayoutAction->setCheckable(true); breakLayoutAction = new QAction(QIcon::fromTheme("labplot-editbreaklayout"), i18n("Break Layout"), layoutActionGroup); breakLayoutAction->setEnabled(false); //Grid actions noGridAction = new QAction(i18n("No Grid"), gridActionGroup); noGridAction->setCheckable(true); noGridAction->setChecked(true); noGridAction->setData(WorksheetView::NoGrid); denseLineGridAction = new QAction(i18n("Dense Line Grid"), gridActionGroup); denseLineGridAction->setCheckable(true); sparseLineGridAction = new QAction(i18n("Sparse Line Grid"), gridActionGroup); sparseLineGridAction->setCheckable(true); denseDotGridAction = new QAction(i18n("Dense Dot Grid"), gridActionGroup); denseDotGridAction->setCheckable(true); sparseDotGridAction = new QAction(i18n("Sparse Dot Grid"), gridActionGroup); sparseDotGridAction->setCheckable(true); customGridAction = new QAction(i18n("Custom Grid"), gridActionGroup); customGridAction->setCheckable(true); snapToGridAction = new QAction(i18n("Snap to Grid"), this); snapToGridAction->setCheckable(true); showPresenterMode = new QAction(QIcon::fromTheme("view-fullscreen"), i18n("Show in Presenter Mode"), this); //check the action corresponding to the currently active layout in worksheet this->layoutChanged(m_worksheet->layout()); connect(addNewActionGroup, &QActionGroup::triggered, this, &WorksheetView::addNew); connect(mouseModeActionGroup, &QActionGroup::triggered, this, &WorksheetView::mouseModeChanged); connect(zoomActionGroup, &QActionGroup::triggered, this, &WorksheetView::changeZoom); connect(magnificationActionGroup, &QActionGroup::triggered, this, &WorksheetView::magnificationChanged); connect(layoutActionGroup, &QActionGroup::triggered, this, &WorksheetView::changeLayout); connect(gridActionGroup, &QActionGroup::triggered, this, &WorksheetView::changeGrid); connect(snapToGridAction, &QAction::triggered, this, &WorksheetView::changeSnapToGrid); connect(showPresenterMode, &QAction::triggered, this, &WorksheetView::presenterMode); //worksheet control actions plotsLockedAction = new QAction(i18n("Non-interactive Plots"), this); plotsLockedAction->setToolTip(i18n("If activated, plots on the worksheet don't react on drag and mouse wheel events.")); plotsLockedAction->setCheckable(true); plotsLockedAction->setChecked(m_worksheet->plotsLocked()); connect(plotsLockedAction, &QAction::triggered, this, &WorksheetView::plotsLockedActionChanged); //action for cartesian plots auto* cartesianPlotActionModeActionGroup = new QActionGroup(this); cartesianPlotActionModeActionGroup->setExclusive(true); cartesianPlotApplyToSelectionAction = new QAction(i18n("Selected Plots"), cartesianPlotActionModeActionGroup); cartesianPlotApplyToSelectionAction->setCheckable(true); cartesianPlotApplyToAllAction = new QAction(i18n("All Plots"), cartesianPlotActionModeActionGroup); cartesianPlotApplyToAllAction->setCheckable(true); setCartesianPlotActionMode(m_worksheet->cartesianPlotActionMode()); connect(cartesianPlotActionModeActionGroup, &QActionGroup::triggered, this, &WorksheetView::cartesianPlotActionModeChanged); // cursor apply to all/selected auto* cartesianPlotActionCursorGroup = new QActionGroup(this); cartesianPlotActionCursorGroup->setExclusive(true); cartesianPlotApplyToSelectionCursor = new QAction(i18n("Selected Plots"), cartesianPlotActionCursorGroup); cartesianPlotApplyToSelectionCursor->setCheckable(true); cartesianPlotApplyToAllCursor = new QAction(i18n("All Plots"), cartesianPlotActionCursorGroup); cartesianPlotApplyToAllCursor->setCheckable(true); setCartesianPlotCursorMode(m_worksheet->cartesianPlotCursorMode()); connect(cartesianPlotActionCursorGroup, &QActionGroup::triggered, this, &WorksheetView::cartesianPlotCursorModeChanged); auto* cartesianPlotMouseModeActionGroup = new QActionGroup(this); cartesianPlotMouseModeActionGroup->setExclusive(true); cartesianPlotSelectionModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Edit"), cartesianPlotMouseModeActionGroup); cartesianPlotSelectionModeAction->setCheckable(true); cartesianPlotSelectionModeAction->setChecked(true); cartesianPlotZoomSelectionModeAction = new QAction(QIcon::fromTheme("labplot-zoom-select"), i18n("Select Region and Zoom In"), cartesianPlotMouseModeActionGroup); cartesianPlotZoomSelectionModeAction->setCheckable(true); cartesianPlotZoomXSelectionModeAction = new QAction(QIcon::fromTheme("labplot-zoom-select-x"), i18n("Select x-region and Zoom In"), cartesianPlotMouseModeActionGroup); cartesianPlotZoomXSelectionModeAction->setCheckable(true); cartesianPlotZoomYSelectionModeAction = new QAction(QIcon::fromTheme("labplot-zoom-select-y"), i18n("Select y-region and Zoom In"), cartesianPlotMouseModeActionGroup); cartesianPlotZoomYSelectionModeAction->setCheckable(true); // TODO: change ICON cartesianPlotCursorModeAction = new QAction(QIcon::fromTheme("debug-execute-from-cursor"), i18n("Cursor"), cartesianPlotMouseModeActionGroup); cartesianPlotCursorModeAction->setCheckable(true); connect(cartesianPlotMouseModeActionGroup, &QActionGroup::triggered, this, &WorksheetView::cartesianPlotMouseModeChanged); auto* cartesianPlotAddNewActionGroup = new QActionGroup(this); addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), cartesianPlotAddNewActionGroup); addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), cartesianPlotAddNewActionGroup); addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), cartesianPlotAddNewActionGroup); // TODO: no own icons yet addDataOperationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Operation"), cartesianPlotAddNewActionGroup); // addDataOperationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-data-operation-curve"), i18n("Data Operation"), cartesianPlotAddNewActionGroup); addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), cartesianPlotAddNewActionGroup); // addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-data-reduction-curve"), i18n("Data Reduction"), cartesianPlotAddNewActionGroup); addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiation"), cartesianPlotAddNewActionGroup); // addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-differentiation-curve"), i18n("Differentiation"), cartesianPlotAddNewActionGroup); addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integration"), cartesianPlotAddNewActionGroup); // addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-integration-curve"), i18n("Integration"), cartesianPlotAddNewActionGroup); addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("(De-)Convolution"), cartesianPlotAddNewActionGroup); // addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("(De-)Convolution"), cartesianPlotAddNewActionGroup); addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); // addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolation"), cartesianPlotAddNewActionGroup); addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), cartesianPlotAddNewActionGroup); addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Fit"), cartesianPlotAddNewActionGroup); addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), cartesianPlotAddNewActionGroup); addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), cartesianPlotAddNewActionGroup); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), cartesianPlotAddNewActionGroup); addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal Axis"), cartesianPlotAddNewActionGroup); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical Axis"), cartesianPlotAddNewActionGroup); addPlotTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), cartesianPlotAddNewActionGroup); addPlotImageAction = new QAction(QIcon::fromTheme("viewimage"), i18n("Image"), cartesianPlotAddNewActionGroup); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), cartesianPlotAddNewActionGroup); // Analysis menu // TODO: no own icons yet addDataOperationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Operation"), cartesianPlotAddNewActionGroup); // addDataOperationAction = new QAction(QIcon::fromTheme("labplot-xy-data-operation-curve"), i18n("Data Operation"), cartesianPlotAddNewActionGroup); addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), cartesianPlotAddNewActionGroup); // addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-data-reduction-curve"), i18n("Data Reduction"), cartesianPlotAddNewActionGroup); addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiation"), cartesianPlotAddNewActionGroup); // addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-differentiation-curve"), i18n("Differentiation"), cartesianPlotAddNewActionGroup); addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integration"), cartesianPlotAddNewActionGroup); // addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-integration-curve"), i18n("Integration"), cartesianPlotAddNewActionGroup); addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Convolution/Deconvolution"), cartesianPlotAddNewActionGroup); // addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("Convolution/Deconvolution"), cartesianPlotAddNewActionGroup); addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); // addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-correlation-curve"), i18n("Auto-/Cross-Correlation"), cartesianPlotAddNewActionGroup); addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolation"), cartesianPlotAddNewActionGroup); addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), cartesianPlotAddNewActionGroup); addFitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Fit"), cartesianPlotAddNewActionGroup); addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), cartesianPlotAddNewActionGroup); addFourierTransformAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), cartesianPlotAddNewActionGroup); connect(cartesianPlotAddNewActionGroup, &QActionGroup::triggered, this, &WorksheetView::cartesianPlotAddNew); auto* cartesianPlotNavigationGroup = new QActionGroup(this); scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("Auto Scale"), cartesianPlotNavigationGroup); scaleAutoAction->setData(CartesianPlot::ScaleAuto); scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("Auto Scale X"), cartesianPlotNavigationGroup); scaleAutoXAction->setData(CartesianPlot::ScaleAutoX); scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("Auto Scale Y"), cartesianPlotNavigationGroup); scaleAutoYAction->setData(CartesianPlot::ScaleAutoY); zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), cartesianPlotNavigationGroup); zoomInAction->setData(CartesianPlot::ZoomIn); zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), cartesianPlotNavigationGroup); zoomOutAction->setData(CartesianPlot::ZoomOut); zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("Zoom In X"), cartesianPlotNavigationGroup); zoomInXAction->setData(CartesianPlot::ZoomInX); zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("Zoom Out X"), cartesianPlotNavigationGroup); zoomOutXAction->setData(CartesianPlot::ZoomOutX); zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("Zoom In Y"), cartesianPlotNavigationGroup); zoomInYAction->setData(CartesianPlot::ZoomInY); zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("Zoom Out Y"), cartesianPlotNavigationGroup); zoomOutYAction->setData(CartesianPlot::ZoomOutY); shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left X"), cartesianPlotNavigationGroup); shiftLeftXAction->setData(CartesianPlot::ShiftLeftX); shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right X"), cartesianPlotNavigationGroup); shiftRightXAction->setData(CartesianPlot::ShiftRightX); shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Up Y"), cartesianPlotNavigationGroup); shiftUpYAction->setData(CartesianPlot::ShiftUpY); shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Down Y"), cartesianPlotNavigationGroup); shiftDownYAction->setData(CartesianPlot::ShiftDownY); connect(cartesianPlotNavigationGroup, &QActionGroup::triggered, this, &WorksheetView::cartesianPlotNavigationChanged); //set some default values selectionModeAction->setChecked(true); handleCartesianPlotActions(); currentZoomAction = zoomInViewAction; currentMagnificationAction = noMagnificationAction; m_actionsInitialized = true; } void WorksheetView::initMenus() { if (!m_actionsInitialized) initActions(); m_addNewCartesianPlotMenu = new QMenu(i18n("xy-plot"), this); m_addNewCartesianPlotMenu->addAction(addCartesianPlot1Action); m_addNewCartesianPlotMenu->addAction(addCartesianPlot2Action); m_addNewCartesianPlotMenu->addAction(addCartesianPlot3Action); m_addNewCartesianPlotMenu->addAction(addCartesianPlot4Action); m_addNewMenu = new QMenu(i18n("Add New"), this); m_addNewMenu->setIcon(QIcon::fromTheme("list-add")); m_addNewMenu->addMenu(m_addNewCartesianPlotMenu)->setIcon(QIcon::fromTheme("office-chart-line")); m_addNewMenu->addSeparator(); m_addNewMenu->addAction(addTextLabelAction); m_addNewMenu->addAction(addImageAction); m_viewMouseModeMenu = new QMenu(i18n("Mouse Mode"), this); m_viewMouseModeMenu->setIcon(QIcon::fromTheme("input-mouse")); m_viewMouseModeMenu->addAction(selectionModeAction); m_viewMouseModeMenu->addAction(navigationModeAction); m_viewMouseModeMenu->addAction(zoomSelectionModeAction); m_zoomMenu = new QMenu(i18n("Zoom"), 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_zoomMenu->addAction(zoomFitSelectionAction); m_magnificationMenu = new QMenu(i18n("Magnification"), this); m_magnificationMenu->setIcon(QIcon::fromTheme("zoom-in")); m_magnificationMenu->addAction(noMagnificationAction); m_magnificationMenu->addAction(twoTimesMagnificationAction); m_magnificationMenu->addAction(threeTimesMagnificationAction); m_magnificationMenu->addAction(fourTimesMagnificationAction); m_magnificationMenu->addAction(fiveTimesMagnificationAction); m_layoutMenu = new QMenu(i18n("Layout"), this); m_layoutMenu->setIcon(QIcon::fromTheme("labplot-editbreaklayout")); m_layoutMenu->addAction(verticalLayoutAction); m_layoutMenu->addAction(horizontalLayoutAction); m_layoutMenu->addAction(gridLayoutAction); m_layoutMenu->addSeparator(); m_layoutMenu->addAction(breakLayoutAction); m_gridMenu = new QMenu(i18n("Grid"), this); m_gridMenu->setIcon(QIcon::fromTheme("view-grid")); m_gridMenu->addAction(noGridAction); m_gridMenu->addSeparator(); m_gridMenu->addAction(sparseLineGridAction); m_gridMenu->addAction(denseLineGridAction); m_gridMenu->addSeparator(); m_gridMenu->addAction(sparseDotGridAction); m_gridMenu->addAction(denseDotGridAction); m_gridMenu->addSeparator(); m_gridMenu->addAction(customGridAction); //TODO: implement "snap to grid" and activate this action // m_gridMenu->addSeparator(); // m_gridMenu->addAction(snapToGridAction); m_cartesianPlotMenu = new QMenu(i18n("Cartesian Plot"), this); m_cartesianPlotMenu->setIcon(QIcon::fromTheme("office-chart-line")); m_cartesianPlotMouseModeMenu = new QMenu(i18n("Mouse Mode"), this); m_cartesianPlotMouseModeMenu->setIcon(QIcon::fromTheme("input-mouse")); m_cartesianPlotMouseModeMenu->addAction(cartesianPlotSelectionModeAction); m_cartesianPlotMouseModeMenu->addAction(cartesianPlotZoomSelectionModeAction); m_cartesianPlotMouseModeMenu->addAction(cartesianPlotZoomXSelectionModeAction); m_cartesianPlotMouseModeMenu->addAction(cartesianPlotZoomYSelectionModeAction); m_cartesianPlotMouseModeMenu->addSeparator(); m_cartesianPlotMouseModeMenu->addAction(cartesianPlotCursorModeAction); m_cartesianPlotMouseModeMenu->addSeparator(); m_cartesianPlotAddNewMenu = new QMenu(i18n("Add New"), this); m_cartesianPlotAddNewMenu->setIcon(QIcon::fromTheme("list-add")); m_cartesianPlotAddNewMenu->addAction(addCurveAction); m_cartesianPlotAddNewMenu->addAction(addHistogramAction); m_cartesianPlotAddNewMenu->addAction(addEquationCurveAction); m_cartesianPlotAddNewMenu->addSeparator(); m_cartesianPlotAddNewAnalysisMenu = new QMenu(i18n("Analysis Curve")); m_cartesianPlotAddNewAnalysisMenu->addAction(addFitCurveAction); m_cartesianPlotAddNewAnalysisMenu->addSeparator(); m_cartesianPlotAddNewAnalysisMenu->addAction(addDifferentiationCurveAction); m_cartesianPlotAddNewAnalysisMenu->addAction(addIntegrationCurveAction); m_cartesianPlotAddNewAnalysisMenu->addSeparator(); m_cartesianPlotAddNewAnalysisMenu->addAction(addInterpolationCurveAction); m_cartesianPlotAddNewAnalysisMenu->addAction(addSmoothCurveAction); m_cartesianPlotAddNewAnalysisMenu->addSeparator(); m_cartesianPlotAddNewAnalysisMenu->addAction(addFourierFilterCurveAction); m_cartesianPlotAddNewAnalysisMenu->addAction(addFourierTransformCurveAction); m_cartesianPlotAddNewAnalysisMenu->addSeparator(); m_cartesianPlotAddNewAnalysisMenu->addAction(addConvolutionCurveAction); m_cartesianPlotAddNewAnalysisMenu->addAction(addCorrelationCurveAction); m_cartesianPlotAddNewAnalysisMenu->addSeparator(); // m_cartesianPlotAddNewAnalysisMenu->addAction(addDataOperationCurveAction); m_cartesianPlotAddNewAnalysisMenu->addAction(addDataReductionCurveAction); m_cartesianPlotAddNewMenu->addMenu(m_cartesianPlotAddNewAnalysisMenu); m_cartesianPlotAddNewMenu->addSeparator(); m_cartesianPlotAddNewMenu->addAction(addLegendAction); m_cartesianPlotAddNewMenu->addSeparator(); m_cartesianPlotAddNewMenu->addAction(addHorizontalAxisAction); m_cartesianPlotAddNewMenu->addAction(addVerticalAxisAction); m_cartesianPlotAddNewMenu->addSeparator(); m_cartesianPlotAddNewMenu->addAction(addPlotTextLabelAction); m_cartesianPlotAddNewMenu->addAction(addPlotImageAction); m_cartesianPlotAddNewMenu->addSeparator(); m_cartesianPlotAddNewMenu->addAction(addCustomPointAction); m_cartesianPlotZoomMenu = new QMenu(i18n("Zoom/Navigate"), this); m_cartesianPlotZoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); m_cartesianPlotZoomMenu->addAction(scaleAutoAction); m_cartesianPlotZoomMenu->addAction(scaleAutoXAction); m_cartesianPlotZoomMenu->addAction(scaleAutoYAction); m_cartesianPlotZoomMenu->addSeparator(); m_cartesianPlotZoomMenu->addAction(zoomInAction); m_cartesianPlotZoomMenu->addAction(zoomOutAction); m_cartesianPlotZoomMenu->addSeparator(); m_cartesianPlotZoomMenu->addAction(zoomInXAction); m_cartesianPlotZoomMenu->addAction(zoomOutXAction); m_cartesianPlotZoomMenu->addSeparator(); m_cartesianPlotZoomMenu->addAction(zoomInYAction); m_cartesianPlotZoomMenu->addAction(zoomOutYAction); m_cartesianPlotZoomMenu->addSeparator(); m_cartesianPlotZoomMenu->addAction(shiftLeftXAction); m_cartesianPlotZoomMenu->addAction(shiftRightXAction); m_cartesianPlotZoomMenu->addSeparator(); m_cartesianPlotZoomMenu->addAction(shiftUpYAction); m_cartesianPlotZoomMenu->addAction(shiftDownYAction); m_cartesianPlotActionModeMenu = new QMenu(i18n("Apply Actions to"), this); m_cartesianPlotActionModeMenu->setIcon(QIcon::fromTheme("dialog-ok-apply")); m_cartesianPlotActionModeMenu->addAction(cartesianPlotApplyToSelectionAction); m_cartesianPlotActionModeMenu->addAction(cartesianPlotApplyToAllAction); m_cartesianPlotCursorModeMenu = new QMenu(i18n("Apply Cursor to"), this); m_cartesianPlotCursorModeMenu->addAction(cartesianPlotApplyToSelectionCursor); m_cartesianPlotCursorModeMenu->addAction(cartesianPlotApplyToAllCursor); m_cartesianPlotMenu->addMenu(m_cartesianPlotAddNewMenu); m_cartesianPlotMenu->addSeparator(); m_cartesianPlotMenu->addMenu(m_cartesianPlotMouseModeMenu); m_cartesianPlotMenu->addMenu(m_cartesianPlotZoomMenu); m_cartesianPlotMenu->addSeparator(); m_cartesianPlotMenu->addMenu(m_cartesianPlotActionModeMenu); m_cartesianPlotMenu->addMenu(m_cartesianPlotCursorModeMenu); m_cartesianPlotMenu->addSeparator(); m_cartesianPlotMenu->addAction(plotsLockedAction); // Data manipulation menu m_dataManipulationMenu = new QMenu(i18n("Data Manipulation") ,this); m_dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); m_dataManipulationMenu->addAction(addDataOperationAction); m_dataManipulationMenu->addAction(addDataReductionAction); //themes menu m_themeMenu = new QMenu(i18n("Apply Theme"), this); m_themeMenu->setIcon(QIcon::fromTheme("color-management")); auto* themeWidget = new ThemesWidget(nullptr); connect(themeWidget, &ThemesWidget::themeSelected, m_worksheet, &Worksheet::setTheme); connect(themeWidget, &ThemesWidget::themeSelected, m_themeMenu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(themeWidget); m_themeMenu->addAction(widgetAction); m_menusInitialized = true; } /*! * Populates the menu \c menu with the worksheet and worksheet view relevant actions. * The menu is used * - as the context menu in WorksheetView * - as the "worksheet menu" in the main menu-bar (called form MainWin) * - as a part of the worksheet context menu in project explorer */ void WorksheetView::createContextMenu(QMenu* menu) { Q_ASSERT(menu != nullptr); if (!m_menusInitialized) initMenus(); 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_addNewMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_viewMouseModeMenu); menu->insertMenu(firstAction, m_zoomMenu); menu->insertMenu(firstAction, m_magnificationMenu); menu->insertMenu(firstAction, m_layoutMenu); menu->insertMenu(firstAction, m_gridMenu); menu->insertMenu(firstAction, m_themeMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, plotsLockedAction); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_cartesianPlotMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, showPresenterMode); menu->insertSeparator(firstAction); } void WorksheetView::createAnalysisMenu(QMenu* menu) { Q_ASSERT(menu != nullptr); if (!m_menusInitialized) initMenus(); // Data manipulation menu // menu->insertMenu(nullptr, m_dataManipulationMenu); menu->addAction(addFitAction); menu->addSeparator(); menu->addAction(addDifferentiationAction); menu->addAction(addIntegrationAction); menu->addSeparator(); menu->addAction(addInterpolationAction); menu->addAction(addSmoothAction); menu->addSeparator(); menu->addAction(addFourierFilterAction); menu->addAction(addFourierTransformAction); menu->addSeparator(); menu->addAction(addConvolutionAction); menu->addAction(addCorrelationAction); menu->addSeparator(); menu->addAction(addDataReductionAction); } void WorksheetView::fillToolBar(QToolBar* toolBar) { toolBar->addSeparator(); tbNewCartesianPlot = new QToolButton(toolBar); tbNewCartesianPlot->setPopupMode(QToolButton::MenuButtonPopup); tbNewCartesianPlot->setMenu(m_addNewCartesianPlotMenu); tbNewCartesianPlot->setDefaultAction(addCartesianPlot1Action); toolBar->addWidget(tbNewCartesianPlot); toolBar->addAction(addTextLabelAction); toolBar->addAction(addImageAction); toolBar->addSeparator(); toolBar->addAction(verticalLayoutAction); toolBar->addAction(horizontalLayoutAction); toolBar->addAction(gridLayoutAction); toolBar->addAction(breakLayoutAction); toolBar->addSeparator(); toolBar->addAction(selectionModeAction); toolBar->addAction(navigationModeAction); toolBar->addAction(zoomSelectionModeAction); toolBar->addSeparator(); tbZoom = new QToolButton(toolBar); tbZoom->setPopupMode(QToolButton::MenuButtonPopup); tbZoom->setMenu(m_zoomMenu); tbZoom->setDefaultAction(currentZoomAction); toolBar->addWidget(tbZoom); tbMagnification = new QToolButton(toolBar); tbMagnification->setPopupMode(QToolButton::MenuButtonPopup); tbMagnification->setMenu(m_magnificationMenu); tbMagnification->setDefaultAction(currentMagnificationAction); toolBar->addWidget(tbMagnification); } #ifdef Q_OS_MAC void WorksheetView::fillTouchBar(KDMacTouchBar* touchBar){ //touchBar->addAction(addCartesianPlot1Action); touchBar->addAction(zoomInViewAction); touchBar->addAction(zoomOutViewAction); touchBar->addAction(showPresenterMode); } #endif void WorksheetView::fillCartesianPlotToolBar(QToolBar* toolBar) { toolBar->addAction(cartesianPlotSelectionModeAction); toolBar->addAction(cartesianPlotZoomSelectionModeAction); toolBar->addAction(cartesianPlotZoomXSelectionModeAction); toolBar->addAction(cartesianPlotZoomYSelectionModeAction); toolBar->addAction(cartesianPlotCursorModeAction); toolBar->addSeparator(); toolBar->addAction(addCurveAction); toolBar->addAction(addHistogramAction); toolBar->addAction(addEquationCurveAction); // don't over-populate the tool bar // toolBar->addAction(addDifferentiationCurveAction); // toolBar->addAction(addIntegrationCurveAction); // toolBar->addAction(addDataOperationCurveAction); // toolBar->addAction(addDataReductionCurveAction); // toolBar->addAction(addInterpolationCurveAction); // toolBar->addAction(addSmoothCurveAction); // toolBar->addAction(addFitCurveAction); // toolBar->addAction(addFourierFilterCurveAction); // toolBar->addAction(addFourierTransformCurveAction); // toolBar->addAction(addConvolutionCurveAction); // toolBar->addAction(addCorrelationCurveAction); toolBar->addSeparator(); toolBar->addAction(addLegendAction); toolBar->addSeparator(); toolBar->addAction(addHorizontalAxisAction); toolBar->addAction(addVerticalAxisAction); toolBar->addSeparator(); toolBar->addAction(addPlotTextLabelAction); toolBar->addAction(addPlotImageAction); toolBar->addSeparator(); toolBar->addAction(scaleAutoAction); toolBar->addAction(scaleAutoXAction); toolBar->addAction(scaleAutoYAction); toolBar->addAction(zoomInAction); toolBar->addAction(zoomOutAction); toolBar->addAction(zoomInXAction); toolBar->addAction(zoomOutXAction); toolBar->addAction(zoomInYAction); toolBar->addAction(zoomOutYAction); toolBar->addAction(shiftLeftXAction); toolBar->addAction(shiftRightXAction); toolBar->addAction(shiftUpYAction); toolBar->addAction(shiftDownYAction); toolBar->addSeparator(); handleCartesianPlotActions(); } void WorksheetView::setScene(QGraphicsScene* scene) { QGraphicsView::setScene(scene); } void WorksheetView::setIsClosing() { m_isClosing = true; } void WorksheetView::setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode mode) { if (mode == Worksheet::CartesianPlotActionMode::ApplyActionToAll) cartesianPlotApplyToAllAction->setChecked(true); else cartesianPlotApplyToSelectionAction->setChecked(true); } void WorksheetView::setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode mode) { if (mode == Worksheet::CartesianPlotActionMode::ApplyActionToAll) cartesianPlotApplyToAllCursor->setChecked(true); else cartesianPlotApplyToSelectionCursor->setChecked(true); } void WorksheetView::setPlotLock(bool lock) { plotsLockedAction->setChecked(lock); } void WorksheetView::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(); //TODO: don't hardcode for black here, use a a different color depending on the theme of the worksheet/plot under the mouse cursor? painter->setPen(QPen(Qt::black, 5/transform().m11())); painter->drawRect(selRect); painter->setBrush(QApplication::palette().color(QPalette::Highlight)); painter->setOpacity(0.2); painter->drawRect(selRect); painter->restore(); } QGraphicsView::drawForeground(painter, rect); } void WorksheetView::drawBackgroundItems(QPainter* painter, const QRectF& scene_rect) { // canvas painter->setOpacity(m_worksheet->backgroundOpacity()); if (m_worksheet->backgroundType() == PlotArea::Color) { switch (m_worksheet->backgroundColorStyle()) { case PlotArea::SingleColor: { painter->setBrush(QBrush(m_worksheet->backgroundFirstColor())); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(scene_rect.topLeft(), scene_rect.topRight()); linearGrad.setColorAt(0, m_worksheet->backgroundFirstColor()); linearGrad.setColorAt(1, m_worksheet->backgroundSecondColor()); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(scene_rect.topLeft(), scene_rect.bottomLeft()); linearGrad.setColorAt(0, m_worksheet->backgroundFirstColor()); linearGrad.setColorAt(1, m_worksheet->backgroundSecondColor()); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(scene_rect.topLeft(), scene_rect.bottomRight()); linearGrad.setColorAt(0, m_worksheet->backgroundFirstColor()); linearGrad.setColorAt(1, m_worksheet->backgroundSecondColor()); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(scene_rect.bottomLeft(), scene_rect.topRight()); linearGrad.setColorAt(0, m_worksheet->backgroundFirstColor()); linearGrad.setColorAt(1, m_worksheet->backgroundSecondColor()); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(scene_rect.center(), scene_rect.width()/2); radialGrad.setColorAt(0, m_worksheet->backgroundFirstColor()); radialGrad.setColorAt(1, m_worksheet->backgroundSecondColor()); painter->setBrush(QBrush(radialGrad)); break; } //default: // painter->setBrush(QBrush(m_worksheet->backgroundFirstColor())); } painter->drawRect(scene_rect); } else if (m_worksheet->backgroundType() == PlotArea::Image) { // background image const QString& backgroundFileName = m_worksheet->backgroundFileName().trimmed(); if ( !backgroundFileName.isEmpty() ) { QPixmap pix(backgroundFileName); switch (m_worksheet->backgroundImageStyle()) { case PlotArea::ScaledCropped: pix = pix.scaled(scene_rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->drawPixmap(scene_rect.topLeft(),pix); break; case PlotArea::Scaled: pix = pix.scaled(scene_rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(scene_rect.topLeft(),pix); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(scene_rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(scene_rect.topLeft(),pix); break; case PlotArea::Centered: painter->drawPixmap(QPointF(scene_rect.center().x()-pix.size().width()/2,scene_rect.center().y()-pix.size().height()/2),pix); break; case PlotArea::Tiled: painter->drawTiledPixmap(scene_rect,pix); break; case PlotArea::CenterTiled: painter->drawTiledPixmap(scene_rect,pix,QPoint(scene_rect.size().width()/2,scene_rect.size().height()/2)); break; //default: // painter->drawPixmap(scene_rect.topLeft(),pix); } } } else if (m_worksheet->backgroundType() == PlotArea::Pattern) { // background pattern painter->setBrush(QBrush(m_worksheet->backgroundFirstColor(),m_worksheet->backgroundBrushStyle())); painter->drawRect(scene_rect); } //grid if (m_gridSettings.style != WorksheetView::NoGrid) { QColor c = m_gridSettings.color; c.setAlphaF(m_gridSettings.opacity); painter->setPen(c); qreal x, y; qreal left = scene_rect.left(); qreal right = scene_rect.right(); qreal top = scene_rect.top(); qreal bottom = scene_rect.bottom(); if (m_gridSettings.style == WorksheetView::LineGrid) { QLineF line; //horizontal lines y = top + m_gridSettings.verticalSpacing; while (y < bottom) { line.setLine( left, y, right, y ); painter->drawLine(line); y += m_gridSettings.verticalSpacing; } //vertical lines x = left + m_gridSettings.horizontalSpacing; while (x < right) { line.setLine( x, top, x, bottom ); painter->drawLine(line); x += m_gridSettings.horizontalSpacing; } } else { //DotGrid y = top + m_gridSettings.verticalSpacing; while (y < bottom) { x = left;// + m_gridSettings.horizontalSpacing; while (x < right) { x += m_gridSettings.horizontalSpacing; painter->drawPoint(x, y); } y += m_gridSettings.verticalSpacing; } } } } void WorksheetView::drawBackground(QPainter* painter, const QRectF& rect) { painter->save(); //painter->setRenderHint(QPainter::Antialiasing); QRectF scene_rect = sceneRect(); if (!m_worksheet->useViewSize()) { // background KColorScheme scheme(QPalette::Active, KColorScheme::Window); const QColor& color = scheme.background().color(); if (!scene_rect.contains(rect)) painter->fillRect(rect, color); //shadow // int shadowSize = scene_rect.width()*0.02; // QRectF rightShadowRect(scene_rect.right(), scene_rect.top() + shadowSize, shadowSize, scene_rect.height()); // QRectF bottomShadowRect(scene_rect.left() + shadowSize, scene_rect.bottom(), scene_rect.width(), shadowSize); // // const QColor& shadeColor = scheme.shade(color, KColorScheme::MidShade); // painter->fillRect(rightShadowRect.intersected(rect), shadeColor); // painter->fillRect(bottomShadowRect.intersected(rect), shadeColor); } drawBackgroundItems(painter, scene_rect); invalidateScene(rect, QGraphicsScene::BackgroundLayer); painter->restore(); } bool WorksheetView::isPlotAtPos(QPoint pos) const { bool plot = false; QGraphicsItem* item = itemAt(pos); if (item) { - plot = item->data(0).toInt() == WorksheetElement::NameCartesianPlot; + plot = item->data(0).toInt() == static_cast(WorksheetElement::WorksheetElementName::NameCartesianPlot); if (!plot && item->parentItem()) - plot = item->parentItem()->data(0).toInt() == WorksheetElement::NameCartesianPlot; + plot = item->parentItem()->data(0).toInt() == static_cast(WorksheetElement::WorksheetElementName::NameCartesianPlot); } return plot; } CartesianPlot* WorksheetView::plotAt(QPoint pos) const { QGraphicsItem* item = itemAt(pos); if (!item) return nullptr; QGraphicsItem* plotItem = nullptr; - if (item->data(0).toInt() == WorksheetElement::NameCartesianPlot) + if (item->data(0).toInt() == static_cast(WorksheetElement::WorksheetElementName::NameCartesianPlot)) plotItem = item; else { - if (item->parentItem() && item->parentItem()->data(0).toInt() == WorksheetElement::NameCartesianPlot) + if (item->parentItem() && item->parentItem()->data(0).toInt() == static_cast(WorksheetElement::WorksheetElementName::NameCartesianPlot)) plotItem = item->parentItem(); } if (plotItem == nullptr) return nullptr; CartesianPlot* plot = nullptr; for (auto* p : m_worksheet->children()) { if (p->graphicsItem() == plotItem) { plot = p; break; } } return plot; } //############################################################################## //#################################### Events ############################### //############################################################################## void WorksheetView::resizeEvent(QResizeEvent* event) { if (m_isClosing) return; if (m_worksheet->useViewSize()) this->processResize(); QGraphicsView::resizeEvent(event); } void WorksheetView::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 WorksheetView::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, &WorksheetView::scalingTime); connect(anim, &QTimeLine::finished, this, &WorksheetView::animFinished); anim->start(); } void WorksheetView::scalingTime() { qreal factor = 1.0 + qreal(m_numScheduledScalings) / 300.0; scale(factor, factor); } void WorksheetView::animFinished() { if (m_numScheduledScalings > 0) m_numScheduledScalings--; else m_numScheduledScalings++; sender()->~QObject(); } void WorksheetView::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_selectionEnd = m_selectionStart; //select&zoom'g starts -> reset the end point to the start point m_selectionBandIsShown = true; QGraphicsView::mousePressEvent(event); return; } // select the worksheet in the project explorer if the view was clicked // and there is no selection currently. We need this for the case when // there is a single worksheet in the project and we change from the project-node // in the project explorer to the worksheet-node by clicking the view. if ( scene()->selectedItems().isEmpty() ) m_worksheet->setSelectedInView(true); QGraphicsView::mousePressEvent(event); } void WorksheetView::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 WorksheetView::mouseMoveEvent(QMouseEvent* event) { if (m_mouseMode == SelectionMode && m_cartesianPlotMouseMode != CartesianPlot::SelectionMode ) { //check whether there is a cartesian plot under the cursor //and set the cursor appearance according to the current mouse mode for the cartesian plots if ( isPlotAtPos(event->pos()) ) { if (m_cartesianPlotMouseMode == CartesianPlot::ZoomSelectionMode) setCursor(Qt::CrossCursor); else if (m_cartesianPlotMouseMode == CartesianPlot::ZoomXSelectionMode) setCursor(Qt::SizeHorCursor); else if (m_cartesianPlotMouseMode == CartesianPlot::ZoomYSelectionMode) setCursor(Qt::SizeVerCursor); } else setCursor(Qt::ArrowCursor); } else if (m_mouseMode == SelectionMode && m_cartesianPlotMouseMode == CartesianPlot::SelectionMode ) setCursor(Qt::ArrowCursor); else if (m_selectionBandIsShown) { QRect rect = QRect(m_selectionStart, m_selectionEnd).normalized(); m_selectionEnd = event->pos(); rect = rect.united(QRect(m_selectionStart, m_selectionEnd).normalized()); qreal 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); } //show the magnification window if (magnificationFactor /*&& m_mouseMode == SelectAndEditMode*/) { if (!m_magnificationWindow) { m_magnificationWindow = new QGraphicsPixmapItem(nullptr); m_magnificationWindow->setZValue(std::numeric_limits::max()); scene()->addItem(m_magnificationWindow); } m_magnificationWindow->setVisible(false); //copy the part of the view to be shown magnified QPointF pos = mapToScene(event->pos()); - const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Centimeter)/transform().m11(); + const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Unit::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_magnificationWindow->setPixmap(px); m_magnificationWindow->setPos(pos.x()- px.width()/2, pos.y()- px.height()/2); m_magnificationWindow->setVisible(true); } else if (m_magnificationWindow) m_magnificationWindow->setVisible(false); QGraphicsView::mouseMoveEvent(event); } void WorksheetView::contextMenuEvent(QContextMenuEvent* e) { if ( (m_magnificationWindow && m_magnificationWindow->isVisible() && items(e->pos()).size() == 1) || !itemAt(e->pos()) ) { //no item or only the magnification window under the cursor -> show the context menu for the worksheet QMenu *menu = new QMenu(this); this->createContextMenu(menu); menu->exec(QCursor::pos()); } else { //propagate the event to the scene and graphics items QGraphicsView::contextMenuEvent(e); } } void WorksheetView::keyPressEvent(QKeyEvent* event) { if (event->matches(QKeySequence::Copy)) { //add here copying of objects exportToClipboard(); } QGraphicsView::keyPressEvent(event); } void WorksheetView::keyReleaseEvent(QKeyEvent* event) { QGraphicsView::keyReleaseEvent(event); } void WorksheetView::dragEnterEvent(QDragEnterEvent* event) { //ignore events not related to internal drags of columns etc., e.g. dropping of external files onto LabPlot const QMimeData* mimeData = event->mimeData(); if (!mimeData) { event->ignore(); return; } if (mimeData->formats().at(0) != QLatin1String("labplot-dnd")) { event->ignore(); return; } //select the worksheet in the project explorer and bring the view to the foreground m_worksheet->setSelectedInView(true); m_worksheet->mdiSubWindow()->mdiArea()->setActiveSubWindow(m_worksheet->mdiSubWindow()); event->setAccepted(true); } void WorksheetView::dragMoveEvent(QDragMoveEvent* event) { // only accept drop events if we have a plot under the cursor where we can drop columns onto bool plot = isPlotAtPos(event->pos()); event->setAccepted(plot); } void WorksheetView::dropEvent(QDropEvent* event) { CartesianPlot* plot = plotAt(event->pos()); if (plot != nullptr) plot->processDropEvent(event); } //############################################################################## //#################################### SLOTs ################################ //############################################################################## void WorksheetView::useViewSizeRequested() { if (!m_actionsInitialized) initActions(); if (m_worksheet->useViewSize()) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); zoomFitPageHeightAction->setVisible(false); zoomFitPageWidthAction->setVisible(false); currentZoomAction = zoomInViewAction; if (tbZoom) tbZoom->setDefaultAction(zoomInViewAction); //determine and set the current view size this->processResize(); } else { setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); zoomFitPageHeightAction->setVisible(true); zoomFitPageWidthAction->setVisible(true); } } void WorksheetView::processResize() { if (size() != sceneRect().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)); + static const float hscale = QApplication::desktop()->physicalDpiX()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); + static const float vscale = QApplication::desktop()->physicalDpiY()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); m_worksheet->setUndoAware(false); m_worksheet->setPageRect(QRectF(0.0, 0.0, width()/hscale, height()/vscale)); m_worksheet->setUndoAware(true); } } void WorksheetView::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()/(Worksheet::convertToSceneUnits(1,Worksheet::Inch)); - static const float vscale = QApplication::desktop()->physicalDpiY()/(Worksheet::convertToSceneUnits(1,Worksheet::Inch)); + static const float hscale = QApplication::desktop()->physicalDpiX()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); + static const float vscale = QApplication::desktop()->physicalDpiY()/(Worksheet::convertToSceneUnits(1, Worksheet::Unit::Inch)); setTransform(QTransform::fromScale(hscale, vscale)); } else if (action == zoomFitPageWidthAction) { float scaleFactor = viewport()->width()/scene()->sceneRect().width(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); } else if (action == zoomFitPageHeightAction) { float scaleFactor = viewport()->height()/scene()->sceneRect().height(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); } else if (action == zoomFitSelectionAction) fitInView(scene()->selectionArea().boundingRect(),Qt::KeepAspectRatio); currentZoomAction = action; if (tbZoom) tbZoom->setDefaultAction(action); } void WorksheetView::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; currentMagnificationAction = action; if (tbMagnification) tbMagnification->setDefaultAction(action); } void WorksheetView::mouseModeChanged(QAction* action) { if (action == selectionModeAction) { m_mouseMode = SelectionMode; setInteractive(true); setDragMode(QGraphicsView::NoDrag); } else if (action == navigationModeAction) { m_mouseMode = NavigationMode; setInteractive(false); setDragMode(QGraphicsView::ScrollHandDrag); } else { m_mouseMode = ZoomSelectionMode; setInteractive(false); setDragMode(QGraphicsView::NoDrag); } } //"Add new" related slots void WorksheetView::addNew(QAction* action) { WorksheetElement* aspect = nullptr; if (action == addCartesianPlot1Action) { CartesianPlot* plot = new CartesianPlot(i18n("xy-plot")); plot->initDefault(CartesianPlot::FourAxes); plot->setMouseMode(m_cartesianPlotMouseMode); aspect = plot; if (tbNewCartesianPlot) tbNewCartesianPlot->setDefaultAction(addCartesianPlot1Action); } else if (action == addCartesianPlot2Action) { CartesianPlot* plot = new CartesianPlot(i18n("xy-plot")); plot->initDefault(CartesianPlot::TwoAxes); plot->setMouseMode(m_cartesianPlotMouseMode); aspect = plot; if (tbNewCartesianPlot) tbNewCartesianPlot->setDefaultAction(addCartesianPlot2Action); } else if (action == addCartesianPlot3Action) { CartesianPlot* plot = new CartesianPlot(i18n("xy-plot")); plot->initDefault(CartesianPlot::TwoAxesCentered); plot->setMouseMode(m_cartesianPlotMouseMode); aspect = plot; if (tbNewCartesianPlot) tbNewCartesianPlot->setDefaultAction(addCartesianPlot3Action); } else if (action == addCartesianPlot4Action) { CartesianPlot* plot = new CartesianPlot(i18n("xy-plot")); plot->initDefault(CartesianPlot::TwoAxesCenteredZero); plot->setMouseMode(m_cartesianPlotMouseMode); aspect = plot; if (tbNewCartesianPlot) tbNewCartesianPlot->setDefaultAction(addCartesianPlot4Action); } else if (action == addTextLabelAction) { TextLabel* l = new TextLabel(i18n("Text Label")); l->setText(i18n("Text Label")); aspect = l; } else if (action == addImageAction) { Image* l = new Image(i18n("Image")); aspect = l; } if (!aspect) return; m_worksheet->addChild(aspect); //labels and images with their initial positions need to be retransformed //ater they have gotten a parent if (aspect->type() == AspectType::TextLabel || aspect->type() == AspectType::Image) aspect->retransform(); handleCartesianPlotActions(); if (!m_fadeInTimeLine) { m_fadeInTimeLine = new QTimeLine(1000, this); m_fadeInTimeLine->setFrameRange(0, 100); connect(m_fadeInTimeLine, &QTimeLine::valueChanged, this, &WorksheetView::fadeIn); } //if there is already an element fading in, stop the time line and show the element with the full opacity. if (m_fadeInTimeLine->state() == QTimeLine::Running) { m_fadeInTimeLine->stop(); auto* effect = new QGraphicsOpacityEffect(); effect->setOpacity(1); lastAddedWorksheetElement->graphicsItem()->setGraphicsEffect(effect); } //fade-in the newly added element lastAddedWorksheetElement = aspect; auto* effect = new QGraphicsOpacityEffect(); effect->setOpacity(0); lastAddedWorksheetElement->graphicsItem()->setGraphicsEffect(effect); m_fadeInTimeLine->start(); } /*! * select all top-level items */ void WorksheetView::selectAllElements() { //deselect all previously selected items since there can be some non top-level items belong them m_suppressSelectionChangedEvent = true; for (auto* item : m_selectedItems) m_worksheet->setItemSelectedInView(item, false); //select top-level items for (auto* item : scene()->items()) { if (!item->parentItem()) item->setSelected(true); } m_suppressSelectionChangedEvent = false; this->selectionChanged(); } /*! * deletes selected worksheet elements */ void WorksheetView::deleteElement() { if (m_selectedItems.isEmpty()) return; int rc = KMessageBox::warningYesNo( this, i18np("Do you really want to delete the selected object?", "Do you really want to delete the selected %1 objects?", m_selectedItems.size()), i18np("Delete selected object", "Delete selected objects", m_selectedItems.size())); if (rc == KMessageBox::No) return; m_suppressSelectionChangedEvent = true; m_worksheet->beginMacro(i18n("%1: Remove selected worksheet elements.", m_worksheet->name())); for (auto* item : m_selectedItems) m_worksheet->deleteAspectFromGraphicsItem(item); m_worksheet->endMacro(); m_suppressSelectionChangedEvent = false; } void WorksheetView::aspectAboutToBeRemoved(const AbstractAspect* aspect) { lastAddedWorksheetElement = dynamic_cast(const_cast(aspect)); if (!lastAddedWorksheetElement) return; //FIXME: fading-out doesn't work //also, the following code collides with undo/redo of the deletion //of a worksheet element (after redoing the element is not shown with the full opacity /* if (!m_fadeOutTimeLine) { m_fadeOutTimeLine = new QTimeLine(1000, this); m_fadeOutTimeLine->setFrameRange(0, 100); connect(m_fadeOutTimeLine, SIGNAL(valueChanged(qreal)), this, SLOT(fadeOut(qreal))); } //if there is already an element fading out, stop the time line if (m_fadeOutTimeLine->state() == QTimeLine::Running) m_fadeOutTimeLine->stop(); m_fadeOutTimeLine->start(); */ } void WorksheetView::fadeIn(qreal value) { auto* effect = new QGraphicsOpacityEffect(); effect->setOpacity(value); lastAddedWorksheetElement->graphicsItem()->setGraphicsEffect(effect); } void WorksheetView::fadeOut(qreal value) { auto* effect = new QGraphicsOpacityEffect(); effect->setOpacity(1 - value); lastAddedWorksheetElement->graphicsItem()->setGraphicsEffect(effect); } /*! * called when one of the layout-actions in WorkseetView was triggered. * sets the layout in Worksheet and enables/disables the layout actions. */ void WorksheetView::changeLayout(QAction* action) { if (action == breakLayoutAction) { verticalLayoutAction->setEnabled(true); verticalLayoutAction->setChecked(false); horizontalLayoutAction->setEnabled(true); horizontalLayoutAction->setChecked(false); gridLayoutAction->setEnabled(true); gridLayoutAction->setChecked(false); breakLayoutAction->setEnabled(false); - m_worksheet->setLayout(Worksheet::NoLayout); + m_worksheet->setLayout(Worksheet::Layout::NoLayout); } else { verticalLayoutAction->setEnabled(false); horizontalLayoutAction->setEnabled(false); gridLayoutAction->setEnabled(false); breakLayoutAction->setEnabled(true); if (action == verticalLayoutAction) { verticalLayoutAction->setChecked(true); - m_worksheet->setLayout(Worksheet::VerticalLayout); + m_worksheet->setLayout(Worksheet::Layout::VerticalLayout); } else if (action == horizontalLayoutAction) { horizontalLayoutAction->setChecked(true); - m_worksheet->setLayout(Worksheet::HorizontalLayout); + m_worksheet->setLayout(Worksheet::Layout::HorizontalLayout); } else { gridLayoutAction->setChecked(true); - m_worksheet->setLayout(Worksheet::GridLayout); + m_worksheet->setLayout(Worksheet::Layout::GridLayout); } } } void WorksheetView::changeGrid(QAction* action) { if (action == noGridAction) { m_gridSettings.style = WorksheetView::NoGrid; snapToGridAction->setEnabled(false); } else if (action == sparseLineGridAction) { m_gridSettings.style = WorksheetView::LineGrid; m_gridSettings.color = Qt::gray; m_gridSettings.opacity = 0.7; m_gridSettings.horizontalSpacing = 15; m_gridSettings.verticalSpacing = 15; } else if (action == denseLineGridAction) { m_gridSettings.style = WorksheetView::LineGrid; m_gridSettings.color = Qt::gray; m_gridSettings.opacity = 0.7; m_gridSettings.horizontalSpacing = 5; m_gridSettings.verticalSpacing = 5; } else if (action == denseDotGridAction) { m_gridSettings.style = WorksheetView::DotGrid; m_gridSettings.color = Qt::black; m_gridSettings.opacity = 0.7; m_gridSettings.horizontalSpacing = 5; m_gridSettings.verticalSpacing = 5; } else if (action == sparseDotGridAction) { m_gridSettings.style = WorksheetView::DotGrid; m_gridSettings.color = Qt::black; m_gridSettings.opacity = 0.7; m_gridSettings.horizontalSpacing = 15; m_gridSettings.verticalSpacing = 15; } else if (action == customGridAction) { auto* dlg = new GridDialog(this); if (dlg->exec() == QDialog::Accepted) dlg->save(m_gridSettings); else return; } if (m_gridSettings.style == WorksheetView::NoGrid) snapToGridAction->setEnabled(false); else snapToGridAction->setEnabled(true); invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } //TODO void WorksheetView::changeSnapToGrid() { } /*! * Selects the QGraphicsItem \c item in \c WorksheetView. * The selection in \c ProjectExplorer is forwarded to \c Worksheet * and is finally handled here. */ void WorksheetView::selectItem(QGraphicsItem* item) { m_suppressSelectionChangedEvent = true; item->setSelected(true); m_selectedItems<setSelected(false); m_selectedItems.removeOne(item); handleCartesianPlotActions(); m_suppressSelectionChangedEvent = false; } /*! * Called on selection changes in the view. * Determines which items were selected and deselected * and forwards these changes to \c Worksheet */ void WorksheetView::selectionChanged() { //if the project is being closed, the scene items are being removed and the selection can change. //don't react on these changes since this can lead crashes (worksheet object is already in the destructor). if (m_isClosing) return; if (m_suppressSelectionChangedEvent) return; QList items = scene()->selectedItems(); //When making a graphics item invisible, it gets deselected in the scene. //In this case we don't want to deselect the item in the project explorer. bool invisibleDeselected = false; //check, whether the previously selected items were deselected now. //Forward the deselection prior to the selection of new items //in order to avoid the unwanted multiple selection in project explorer for (auto* item : m_selectedItems ) { if ( items.indexOf(item) == -1 ) { if (item->isVisible()) m_worksheet->setItemSelectedInView(item, false); else invisibleDeselected = true; } } //select new items if (items.isEmpty() && invisibleDeselected == false) { //no items selected -> select the worksheet again. m_worksheet->setSelectedInView(true); //if one of the "zoom&select" plot mouse modes was selected before, activate the default "selection mode" again //since no plots are selected now. if (m_mouseMode == SelectionMode && m_cartesianPlotMouseMode!= CartesianPlot::SelectionMode) { cartesianPlotSelectionModeAction->setChecked(true); cartesianPlotMouseModeChanged(cartesianPlotSelectionModeAction); } } else { for (const auto* item : items) m_worksheet->setItemSelectedInView(item, true); //items selected -> deselect the worksheet in the project explorer //prevents unwanted multiple selection with worksheet (if it was selected before) m_worksheet->setSelectedInView(false); } m_selectedItems = items; handleCartesianPlotActions(); } //check whether we have cartesian plots selected and activate/deactivate void WorksheetView::handleCartesianPlotActions() { if (!m_menusInitialized) return; bool plot = false; if (m_worksheet->cartesianPlotActionMode() == Worksheet::CartesianPlotActionMode::ApplyActionToSelection) { //check whether we have cartesian plots selected for (auto* item : m_selectedItems) { //TODO: or if a children of a plot is selected - if (item->data(0).toInt() == WorksheetElement::NameCartesianPlot) { + if (item->data(0).toInt() == static_cast(WorksheetElement::WorksheetElementName::NameCartesianPlot)) { plot = true; break; } } } else { //actions are applied to all available plots -> check whether we have plots plot = (m_worksheet->children().size() != 0); } cartesianPlotSelectionModeAction->setEnabled(plot); cartesianPlotZoomSelectionModeAction->setEnabled(plot); cartesianPlotZoomXSelectionModeAction->setEnabled(plot); cartesianPlotZoomYSelectionModeAction->setEnabled(plot); cartesianPlotCursorModeAction->setEnabled(plot); m_cartesianPlotAddNewMenu->setEnabled(plot); m_cartesianPlotZoomMenu->setEnabled(plot); m_cartesianPlotMouseModeMenu->setEnabled(plot); // analysis menu //TODO: enable also if children of plots are selected // m_dataManipulationMenu->setEnabled(plot); // addDataOperationAction->setEnabled(false); addDataReductionAction->setEnabled(false); addDifferentiationAction->setEnabled(plot); addIntegrationAction->setEnabled(plot); addInterpolationAction->setEnabled(plot); addSmoothAction->setEnabled(plot); addFitAction->setEnabled(plot); addFourierFilterAction->setEnabled(plot); addFourierTransformAction->setEnabled(plot); addConvolutionAction->setEnabled(plot); addCorrelationAction->setEnabled(plot); } void WorksheetView::exportToFile(const QString& path, const ExportFormat format, const ExportArea area, const bool background, const int resolution) { QRectF sourceRect; //determine the rectangular to print if (area == WorksheetView::ExportBoundingBox) sourceRect = scene()->itemsBoundingRect(); else if (area == WorksheetView::ExportSelection) { //TODO doesn't work: rect = scene()->selectionArea().boundingRect(); for (const auto* item : m_selectedItems) sourceRect = sourceRect.united( item->mapToScene(item->boundingRect()).boundingRect() ); } else 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); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::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, background); painter.end(); } else if (format == WorksheetView::Svg) { QSvgGenerator generator; generator.setFileName(path); // if (!generator.isValid()) { // RESET_CURSOR; // QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path)); // } - int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); - int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::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, background); 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); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::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, background); painter.end(); if (!path.isEmpty()) { bool rc = image.save(path, "PNG"); if (!rc) { RESET_CURSOR; QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path)); } } else QApplication::clipboard()->setImage(image, QClipboard::Clipboard); } } void WorksheetView::exportToClipboard() { QRectF sourceRect; if (m_selectedItems.size() == 0) sourceRect = scene()->itemsBoundingRect(); else { //export selection for (const auto* item : m_selectedItems) sourceRect = sourceRect.united( item->mapToScene(item->boundingRect()).boundingRect() ); } - int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); - int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::Millimeter); w = w*QApplication::desktop()->physicalDpiX()/25.4; h = h*QApplication::desktop()->physicalDpiY()/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, true); painter.end(); QApplication::clipboard()->setImage(image, QClipboard::Clipboard); } void WorksheetView::exportPaint(QPainter* painter, const QRectF& targetRect, const QRectF& sourceRect, const bool background) { //draw the background if (background) { painter->save(); painter->scale(targetRect.width()/sourceRect.width(), targetRect.height()/sourceRect.height()); drawBackground(painter, sourceRect); painter->restore(); } //draw the scene items m_worksheet->setPrinting(true); scene()->render(painter, QRectF(), sourceRect); m_worksheet->setPrinting(false); } void WorksheetView::print(QPrinter* printer) { m_worksheet->setPrinting(true); QPainter painter(printer); painter.setRenderHint(QPainter::Antialiasing); // draw background QRectF page_rect = printer->pageRect(); QRectF scene_rect = scene()->sceneRect(); float scale = qMax(scene_rect.width()/page_rect.width(),scene_rect.height()/page_rect.height()); drawBackgroundItems(&painter, QRectF(0,0,scene_rect.width()/scale,scene_rect.height()/scale)); // draw scene scene()->render(&painter); m_worksheet->setPrinting(false); } void WorksheetView::updateBackground() { invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } /*! * called when the layout was changed in Worksheet, * enables the corresponding action */ void WorksheetView::layoutChanged(Worksheet::Layout layout) { - if (layout == Worksheet::NoLayout) { + if (layout == Worksheet::Layout::NoLayout) { verticalLayoutAction->setEnabled(true); verticalLayoutAction->setChecked(false); horizontalLayoutAction->setEnabled(true); horizontalLayoutAction->setChecked(false); gridLayoutAction->setEnabled(true); gridLayoutAction->setChecked(false); breakLayoutAction->setEnabled(false); } else { verticalLayoutAction->setEnabled(false); horizontalLayoutAction->setEnabled(false); gridLayoutAction->setEnabled(false); breakLayoutAction->setEnabled(true); - if (layout == Worksheet::VerticalLayout) + if (layout == Worksheet::Layout::VerticalLayout) verticalLayoutAction->setChecked(true); - else if (layout == Worksheet::HorizontalLayout) + else if (layout == Worksheet::Layout::HorizontalLayout) horizontalLayoutAction->setChecked(true); else gridLayoutAction->setChecked(true); } } void WorksheetView::registerShortcuts() { selectAllAction->setShortcut(Qt::CTRL+Qt::Key_A); deleteAction->setShortcut(Qt::Key_Delete); backspaceAction->setShortcut(Qt::Key_Backspace); zoomInViewAction->setShortcut(Qt::CTRL+Qt::Key_Plus); zoomOutViewAction->setShortcut(Qt::CTRL+Qt::Key_Minus); zoomOriginAction->setShortcut(Qt::CTRL+Qt::Key_1); } void WorksheetView::unregisterShortcuts() { selectAllAction->setShortcut(QKeySequence()); deleteAction->setShortcut(QKeySequence()); backspaceAction->setShortcut(QKeySequence()); zoomInViewAction->setShortcut(QKeySequence()); zoomOutViewAction->setShortcut(QKeySequence()); zoomOriginAction->setShortcut(QKeySequence()); } //############################################################################## //######################## SLOTs for cartesian plots ######################## //############################################################################## void WorksheetView::cartesianPlotActionModeChanged(QAction* action) { if (action == cartesianPlotApplyToSelectionAction) m_worksheet->setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode::ApplyActionToSelection); else m_worksheet->setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode::ApplyActionToAll); handleCartesianPlotActions(); } void WorksheetView::cartesianPlotCursorModeChanged(QAction* action) { if (action == cartesianPlotApplyToSelectionCursor) m_worksheet->setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode::ApplyActionToSelection); else m_worksheet->setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode::ApplyActionToAll); handleCartesianPlotActions(); } void WorksheetView::plotsLockedActionChanged(bool checked) { m_worksheet->setPlotsLocked(checked); } void WorksheetView::cartesianPlotMouseModeChanged(QAction* action) { if (m_suppressMouseModeChange) return; if (action == cartesianPlotSelectionModeAction) m_cartesianPlotMouseMode = CartesianPlot::SelectionMode; else if (action == cartesianPlotZoomSelectionModeAction) m_cartesianPlotMouseMode = CartesianPlot::ZoomSelectionMode; else if (action == cartesianPlotZoomXSelectionModeAction) m_cartesianPlotMouseMode = CartesianPlot::ZoomXSelectionMode; else if (action == cartesianPlotZoomYSelectionModeAction) m_cartesianPlotMouseMode = CartesianPlot::ZoomYSelectionMode; else if (action == cartesianPlotCursorModeAction) m_cartesianPlotMouseMode = CartesianPlot::Cursor; for (auto* plot : m_worksheet->children() ) plot->setMouseMode(m_cartesianPlotMouseMode); } void WorksheetView::cartesianPlotMouseModeChangedSlot(CartesianPlot::MouseMode mouseMode) { if (!m_menusInitialized) return; m_suppressMouseModeChange = true; if (mouseMode == CartesianPlot::MouseMode::SelectionMode) cartesianPlotSelectionModeAction->setChecked(true); else if (mouseMode == CartesianPlot::MouseMode::ZoomSelectionMode) cartesianPlotZoomSelectionModeAction->setChecked(true); else if (mouseMode == CartesianPlot::MouseMode::ZoomXSelectionMode) cartesianPlotZoomXSelectionModeAction->setChecked(true); else if (mouseMode == CartesianPlot::MouseMode::ZoomYSelectionMode) cartesianPlotZoomYSelectionModeAction->setChecked(true); else if (mouseMode == CartesianPlot::MouseMode::Cursor) cartesianPlotCursorModeAction->setChecked(true); m_suppressMouseModeChange = false; } void WorksheetView::cartesianPlotAddNew(QAction* action) { QVector plots = m_worksheet->children(); - if (m_worksheet->cartesianPlotActionMode() == Worksheet::ApplyActionToSelection) { + if (m_worksheet->cartesianPlotActionMode() == Worksheet::CartesianPlotActionMode::ApplyActionToSelection) { int selectedPlots = 0; for (auto* plot : plots) { if (m_selectedItems.indexOf(plot->graphicsItem()) != -1) ++selectedPlots; else { //current plot is not selected, check if one of its children is selected auto children = plot->children(); for (auto* child : children) { if (m_selectedItems.indexOf(child->graphicsItem()) != -1) { ++selectedPlots; break; } } } } if (selectedPlots > 1) m_worksheet->beginMacro(i18n("%1: Add curve to %2 plots", m_worksheet->name(), selectedPlots)); for (auto* plot : plots) { if (m_selectedItems.indexOf(plot->graphicsItem()) != -1) this->cartesianPlotAdd(plot, action); else { //current plot is not selected, check if one of its children is selected auto children = plot->children(); for (auto* child : children) { if (m_selectedItems.indexOf(child->graphicsItem()) != -1) { this->cartesianPlotAdd(plot, action); break; } } } } if (selectedPlots > 1) m_worksheet->endMacro(); } else { if (plots.size() > 1) m_worksheet->beginMacro(i18n("%1: Add curve to %2 plots", m_worksheet->name(), plots.size())); for (auto* plot : plots) this->cartesianPlotAdd(plot, action); if (plots.size() > 1) m_worksheet->endMacro(); } } void WorksheetView::cartesianPlotAdd(CartesianPlot* plot, QAction* action) { DEBUG("WorksheetView::cartesianPlotAdd()"); if (action == addCurveAction) plot->addCurve(); else if (action == addHistogramAction) plot->addHistogram(); else if (action == addEquationCurveAction) plot->addEquationCurve(); else if (action == addDataReductionCurveAction) plot->addDataReductionCurve(); else if (action == addDifferentiationCurveAction) plot->addDifferentiationCurve(); else if (action == addIntegrationCurveAction) plot->addIntegrationCurve(); else if (action == addInterpolationCurveAction) plot->addInterpolationCurve(); else if (action == addSmoothCurveAction) plot->addSmoothCurve(); else if (action == addFitCurveAction) plot->addFitCurve(); else if (action == addFourierFilterCurveAction) plot->addFourierFilterCurve(); else if (action == addFourierTransformCurveAction) plot->addFourierTransformCurve(); else if (action == addConvolutionCurveAction) plot->addConvolutionCurve(); else if (action == addCorrelationCurveAction) plot->addCorrelationCurve(); else if (action == addLegendAction) plot->addLegend(); else if (action == addHorizontalAxisAction) plot->addHorizontalAxis(); else if (action == addVerticalAxisAction) plot->addVerticalAxis(); else if (action == addPlotTextLabelAction) plot->addTextLabel(); else if (action == addPlotImageAction) plot->addImage(); else if (action == addCustomPointAction) plot->addCustomPoint(); // analysis actions else if (action == addDataReductionAction) plot->addDataReductionCurve(); else if (action == addDifferentiationAction) plot->addDifferentiationCurve(); else if (action == addIntegrationAction) plot->addIntegrationCurve(); else if (action == addInterpolationAction) plot->addInterpolationCurve(); else if (action == addSmoothAction) plot->addSmoothCurve(); else if (action == addFitAction) plot->addFitCurve(); else if (action == addFourierFilterAction) plot->addFourierFilterCurve(); else if (action == addFourierTransformAction) plot->addFourierTransformCurve(); else if (action == addConvolutionAction) plot->addConvolutionCurve(); else if (action == addCorrelationAction) plot->addCorrelationCurve(); } void WorksheetView::cartesianPlotNavigationChanged(QAction* action) { CartesianPlot::NavigationOperation op = (CartesianPlot::NavigationOperation)action->data().toInt(); - if (m_worksheet->cartesianPlotActionMode() == Worksheet::ApplyActionToSelection) { + if (m_worksheet->cartesianPlotActionMode() == Worksheet::CartesianPlotActionMode::ApplyActionToSelection) { for (auto* plot : m_worksheet->children() ) { if (m_selectedItems.indexOf(plot->graphicsItem()) != -1) plot->navigate(op); else { // check if one of the plots childrend is selected. Do the operation there too. for (auto* child : plot->children()) { if (m_selectedItems.indexOf(child->graphicsItem()) != -1) { plot->navigate(op); break; } } } } } else { for (auto* plot : m_worksheet->children() ) plot->navigate(op); } } Worksheet::CartesianPlotActionMode WorksheetView::getCartesianPlotActionMode() { return m_worksheet->cartesianPlotActionMode(); } void WorksheetView::presenterMode() { KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet"); //show dynamic presenter widget, if enabled if (group.readEntry("PresenterModeInteractive", false)) { auto* dynamicPresenterWidget = new DynamicPresenterWidget(m_worksheet); dynamicPresenterWidget->showFullScreen(); return; } //show static presenter widget (default) QRectF sourceRect(scene()->sceneRect()); - int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); - int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); + int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Unit::Millimeter); + int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Unit::Millimeter); w *= QApplication::desktop()->physicalDpiX()/25.4; h *= QApplication::desktop()->physicalDpiY()/25.4; QRectF targetRect(0, 0, w, h); const QRectF& screenSize = QGuiApplication::primaryScreen()->availableGeometry();; if (targetRect.width() > screenSize.width() || ((targetRect.height() > screenSize.height()))) { const double ratio = qMin(screenSize.width() / targetRect.width(), screenSize.height() / targetRect.height()); targetRect.setWidth(targetRect.width()* ratio); targetRect.setHeight(targetRect.height() * ratio); } QImage image(QSize(targetRect.width(), targetRect.height()), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); QPainter painter; painter.begin(&image); painter.setRenderHint(QPainter::Antialiasing); exportPaint(&painter, targetRect, sourceRect, true); painter.end(); PresenterWidget* presenterWidget = new PresenterWidget(QPixmap::fromImage(image), m_worksheet->name()); presenterWidget->showFullScreen(); } diff --git a/src/kdefrontend/MainWin.cpp b/src/kdefrontend/MainWin.cpp index 9f077d39b..2fdeacebc 100644 --- a/src/kdefrontend/MainWin.cpp +++ b/src/kdefrontend/MainWin.cpp @@ -1,2458 +1,2458 @@ /*************************************************************************** File : MainWin.cc Project : LabPlot Description : Main window of the application -------------------------------------------------------------------- Copyright : (C) 2008-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2020 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 "MainWin.h" #include "backend/core/Project.h" #include "backend/core/Folder.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Workbook.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/worksheet/Worksheet.h" #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/DatasetHandler.h" #ifdef HAVE_LIBORIGIN #include "backend/datasources/projects/OriginProjectParser.h" #endif #ifdef HAVE_CANTOR_LIBS #include "backend/cantorWorksheet/CantorWorksheet.h" #endif #include "backend/datapicker/Datapicker.h" #include "backend/note/Note.h" #include "backend/lib/macros.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include "commonfrontend/core/PartMdiView.h" #include "commonfrontend/ProjectExplorer.h" #include "commonfrontend/matrix/MatrixView.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "commonfrontend/worksheet/WorksheetView.h" #ifdef HAVE_CANTOR_LIBS #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" #endif #include "commonfrontend/datapicker/DatapickerView.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "commonfrontend/note/NoteView.h" #include "commonfrontend/widgets/MemoryWidget.h" #include "kdefrontend/datasources/ImportFileDialog.h" #include "kdefrontend/datasources/ImportDatasetDialog.h" #include "kdefrontend/datasources/ImportDatasetWidget.h" #include "kdefrontend/datasources/ImportProjectDialog.h" #include "kdefrontend/datasources/ImportSQLDatabaseDialog.h" #include #include "kdefrontend/dockwidgets/ProjectDock.h" #include "kdefrontend/HistoryDialog.h" #include "kdefrontend/SettingsDialog.h" #include "kdefrontend/GuiObserver.h" #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/widgets/FITSHeaderEditDialog.h" #include "DatasetModel.h" // #include "welcomescreen/WelcomeScreenHelper.h" #ifdef HAVE_KUSERFEEDBACK #include #include #include #include #include #include #endif #ifdef Q_OS_MAC #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include // #include // #include // #include // #include // #include // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CANTOR_LIBS #include #include #include #include //required to parse Cantor and Jupyter files #include #include #include #include #endif /*! \class MainWin \brief Main application window. \ingroup kdefrontend */ MainWin::MainWin(QWidget *parent, const QString& filename) : KXmlGuiWindow(parent), m_schemeManager(new KColorSchemeManager(this)) { initGUI(filename); setAcceptDrops(true); #ifdef HAVE_KUSERFEEDBACK m_userFeedbackProvider.setProductIdentifier(QStringLiteral("org.kde.labplot")); m_userFeedbackProvider.setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/"))); m_userFeedbackProvider.setSubmissionInterval(7); m_userFeedbackProvider.setApplicationStartsUntilEncouragement(5); m_userFeedbackProvider.setEncouragementDelay(30); // software version info m_userFeedbackProvider.addDataSource(new KUserFeedback::ApplicationVersionSource); m_userFeedbackProvider.addDataSource(new KUserFeedback::QtVersionSource); // info about the machine m_userFeedbackProvider.addDataSource(new KUserFeedback::PlatformInfoSource); m_userFeedbackProvider.addDataSource(new KUserFeedback::ScreenInfoSource); // usage info m_userFeedbackProvider.addDataSource(new KUserFeedback::StartCountSource); m_userFeedbackProvider.addDataSource(new KUserFeedback::UsageTimeSource); #endif //restore the geometry KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); restoreGeometry(group.readEntry("geometry", QByteArray())); m_lastOpenFileFilter = group.readEntry(QLatin1String("lastOpenFileFilter"), QString()); } MainWin::~MainWin() { //save the recent opened files m_recentProjectsAction->saveEntries( KSharedConfig::openConfig()->group("Recent Files") ); KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); group.writeEntry("geometry", saveGeometry()); KSharedConfig::openConfig()->sync(); group.writeEntry(QLatin1String("lastOpenFileFilter"), m_lastOpenFileFilter); //if welcome screen is shown, save its settings prior to deleting it // if(dynamic_cast(centralWidget())) // QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions"); if (m_project) { // if(dynamic_cast(centralWidget()) == nullptr) // m_mdiArea->closeAllSubWindows(); disconnect(m_project, nullptr, this, nullptr); delete m_project; } if (m_aspectTreeModel) delete m_aspectTreeModel; if (m_guiObserver) delete m_guiObserver; // if(m_welcomeScreenHelper) // delete m_welcomeScreenHelper; } void MainWin::showPresenter() { const Worksheet* w = dynamic_cast(m_currentAspect); if (w) { auto* view = static_cast(w->view()); view->presenterMode(); } else { //currently active object is not a worksheet but we're asked to start in the presenter mode //determine the first available worksheet and show it in the presenter mode auto worksheets = m_project->children(); if (worksheets.size() > 0) { auto* view = static_cast(worksheets.constFirst()->view()); view->presenterMode(); } else { QMessageBox::information(this, i18n("Presenter Mode"), i18n("No worksheets are available in the project. The presenter mode will not be started.")); } } } AspectTreeModel* MainWin::model() const { return m_aspectTreeModel; } Project* MainWin::project() const { return m_project; } void MainWin::initGUI(const QString& fileName) { statusBar()->showMessage(i18nc("%1 is the LabPlot version", "Welcome to LabPlot %1", QLatin1String(LVERSION))); initActions(); #ifdef Q_OS_MAC setupGUI(Default, QLatin1String("/Applications/labplot2.app/Contents/Resources/labplot2ui.rc")); m_touchBar = new KDMacTouchBar(this); //m_touchBar->setTouchButtonStyle(KDMacTouchBar::IconOnly); #else setupGUI(Default, KXMLGUIClient::xmlFile()); // should be "labplot2ui.rc" #endif DEBUG("Component name: " << STDSTRING(KXMLGUIClient::componentName())); DEBUG("XML file: " << STDSTRING(KXMLGUIClient::xmlFile()) << " (should be \"labplot2ui.rc\")"); //all toolbars created via the KXMLGUI framework are locked on default: // * on the very first program start, unlock all toolbars // * on later program starts, set stored lock status //Furthermore, we want to show icons only after the first program start. KConfigGroup groupMain = KSharedConfig::openConfig()->group("MainWindow"); if (groupMain.exists()) { //KXMLGUI framework automatically stores "Disabled" for the key "ToolBarsMovable" //in case the toolbars are locked -> load this value const QString& str = groupMain.readEntry(QLatin1String("ToolBarsMovable"), ""); bool locked = (str == QLatin1String("Disabled")); KToolBar::setToolBarsLocked(locked); } else { //first start KToolBar::setToolBarsLocked(false); //show icons only for (auto* container : factory()->containers(QLatin1String("ToolBar"))) { auto* toolbar = dynamic_cast(container); if (toolbar) toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); } } initMenus(); auto* mainToolBar = qobject_cast(factory()->container("main_toolbar", this)); if (!mainToolBar) { QMessageBox::critical(this, i18n("GUI configuration file not found"), i18n("%1 file was not found. Please check your installation.", KXMLGUIClient::xmlFile())); //TODO: the application is not really usable if the rc file was not found. We should quit the application. The following line crashes //the application because of the splash screen. We need to find another solution. // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); //call close as soon as we enter the eventloop return; } auto* tbImport = new QToolButton(mainToolBar); tbImport->setPopupMode(QToolButton::MenuButtonPopup); tbImport->setMenu(m_importMenu); tbImport->setDefaultAction(m_importFileAction); mainToolBar->addWidget(tbImport); qobject_cast(factory()->container("import", this))->setIcon(QIcon::fromTheme("document-import")); setWindowIcon(QIcon::fromTheme("LabPlot2", QGuiApplication::windowIcon())); setAttribute( Qt::WA_DeleteOnClose ); //make the status bar of a fixed size in order to avoid height changes when placing a ProgressBar there. QFont font; font.setFamily(font.defaultFamily()); QFontMetrics fm(font); statusBar()->setFixedHeight(fm.height() + 5); //load recently used projects m_recentProjectsAction->loadEntries( KSharedConfig::openConfig()->group("Recent Files") ); //auto-save KConfigGroup group = KSharedConfig::openConfig()->group("Settings_General"); m_autoSaveActive = group.readEntry("AutoSave", false); int interval = group.readEntry("AutoSaveInterval", 1); interval = interval*60*1000; m_autoSaveTimer.setInterval(interval); connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWin::autoSaveProject); if (!fileName.isEmpty()) { createMdiArea(); setCentralWidget(m_mdiArea); #ifdef HAVE_LIBORIGIN if (Project::isLabPlotProject(fileName) || OriginProjectParser::isOriginProject(fileName)) { #else if (Project::isLabPlotProject(fileName)) { #endif QTimer::singleShot(0, this, [=] () { openProject(fileName); }); } else { newProject(); QTimer::singleShot(0, this, [=] () { importFileDialog(fileName); }); } } else { //There is no file to open. Depending on the settings do nothing, //create a new project or open the last used project. LoadOnStart load = (LoadOnStart)group.readEntry("LoadOnStart", (int)Nothing); if (load != WelcomeScreen) { createMdiArea(); setCentralWidget(m_mdiArea); if (load == NewProject) //create new project newProject(); else if (load == NewProjectWorksheet) { //create new project with a worksheet newProject(); newWorksheet(); } else if (load == LastProject) { //open last used project if (!m_recentProjectsAction->urls().isEmpty()) { QDEBUG("TO OPEN m_recentProjectsAction->urls() =" << m_recentProjectsAction->urls().constFirst()); openRecentProject( m_recentProjectsAction->urls().constFirst() ); } } updateGUIOnProjectChanges(); } else{ //welcome screen // m_showWelcomeScreen = true; // m_welcomeWidget = createWelcomeScreen(); // setCentralWidget(m_welcomeWidget); } } //show memory info const bool showMemoryInfo = group.readEntry(QLatin1String("ShowMemoryInfo"), true); if (showMemoryInfo) { m_memoryInfoWidget = new MemoryWidget(statusBar()); statusBar()->addPermanentWidget(m_memoryInfoWidget); } } /** * @brief Creates a new welcome screen to be set as central widget. */ /* QQuickWidget* MainWin::createWelcomeScreen() { QSize maxSize = qApp->primaryScreen()->availableSize(); resize(maxSize); setMinimumSize(700, 400); showMaximized(); KToolBar* toolbar = toolBar(); if(toolbar) toolbar->setVisible(false); QList recentList; for (QUrl& url : m_recentProjectsAction->urls()) recentList.append(QVariant(url)); //Set context property QQuickWidget* quickWidget = new QQuickWidget(this); QQmlContext* ctxt = quickWidget->rootContext(); QVariant variant(recentList); ctxt->setContextProperty("recentProjects", variant); //Create helper object if(m_welcomeScreenHelper) delete m_welcomeScreenHelper; m_welcomeScreenHelper = new WelcomeScreenHelper(); connect(m_welcomeScreenHelper, &WelcomeScreenHelper::openExampleProject, this, QOverload::of(&MainWin::openProject)); ctxt->setContextProperty("datasetModel", m_welcomeScreenHelper->getDatasetModel()); ctxt->setContextProperty("helper", m_welcomeScreenHelper); quickWidget->setSource(QUrl(QLatin1String("qrc:///main.qml"))); quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); QObject *item = quickWidget->rootObject(); //connect qml's signals QObject::connect(item, SIGNAL(recentProjectClicked(QUrl)), this, SLOT(openRecentProject(QUrl))); QObject::connect(item, SIGNAL(datasetClicked(QString,QString,QString)), m_welcomeScreenHelper, SLOT(datasetClicked(QString,QString,QString))); QObject::connect(item, SIGNAL(openDataset()), this, SLOT(openDatasetExample())); QObject::connect(item, SIGNAL(openExampleProject(QString)), m_welcomeScreenHelper, SLOT(exampleProjectClicked(QString))); emit m_welcomeScreenHelper->showFirstDataset(); return quickWidget; } */ /** * @brief Initiates resetting the layout of the welcome screen */ /* void MainWin::resetWelcomeScreen() { if(dynamic_cast(centralWidget())) QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "restoreOriginalLayout"); } */ /** * @brief Creates a new MDI area, to replace the Welcome Screen as central widget */ void MainWin::createMdiArea() { KToolBar* toolbar = toolBar(); if(toolbar) toolbar->setVisible(true); //Save welcome screen's dimensions. // if(m_showWelcomeScreen) // QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions"); m_mdiArea = new QMdiArea; setCentralWidget(m_mdiArea); connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &MainWin::handleCurrentSubWindowChanged); //set the view mode of the mdi area KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); int viewMode = group.readEntry("ViewMode", 0); if (viewMode == 1) { m_mdiArea->setViewMode(QMdiArea::TabbedView); int tabPosition = group.readEntry("TabPosition", 0); m_mdiArea->setTabPosition(QTabWidget::TabPosition(tabPosition)); m_mdiArea->setTabsClosable(true); m_mdiArea->setTabsMovable(true); m_tileWindowsAction->setVisible(false); m_cascadeWindowsAction->setVisible(false); } connect(m_closeWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::closeActiveSubWindow); connect(m_closeAllWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::closeAllSubWindows); connect(m_tileWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::tileSubWindows); connect(m_cascadeWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::cascadeSubWindows); connect(m_nextWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::activateNextSubWindow); connect(m_prevWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::activatePreviousSubWindow); } void MainWin::initActions() { // ******************** File-menu ******************************* //add some standard actions m_newProjectAction = KStandardAction::openNew(this, SLOT(newProject()),actionCollection()); m_openProjectAction = KStandardAction::open(this, SLOT(openProject()),actionCollection()); m_recentProjectsAction = KStandardAction::openRecent(this, SLOT(openRecentProject(QUrl)),actionCollection()); m_closeAction = KStandardAction::close(this, SLOT(closeProject()),actionCollection()); actionCollection()->setDefaultShortcut(m_closeAction, QKeySequence()); //remove the shortcut, QKeySequence::Close will be used for closing sub-windows m_saveAction = KStandardAction::save(this, SLOT(saveProject()),actionCollection()); m_saveAsAction = KStandardAction::saveAs(this, SLOT(saveProjectAs()),actionCollection()); m_printAction = KStandardAction::print(this, SLOT(print()),actionCollection()); m_printPreviewAction = KStandardAction::printPreview(this, SLOT(printPreview()),actionCollection()); //TODO: on Mac OS when going full-screen we get a crash because of an stack-overflow #ifndef Q_OS_MAC KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollection()); #endif //New Folder/Workbook/Spreadsheet/Matrix/Worksheet/Datasources m_newWorkbookAction = new QAction(QIcon::fromTheme("labplot-workbook-new"),i18n("Workbook"),this); actionCollection()->addAction("new_workbook", m_newWorkbookAction); m_newWorkbookAction->setWhatsThis(i18n("Creates a new workbook for collection spreadsheets, matrices and plots")); connect(m_newWorkbookAction, &QAction::triggered, this, &MainWin::newWorkbook); m_newDatapickerAction = new QAction(QIcon::fromTheme("color-picker-black"), i18n("Datapicker"), this); m_newDatapickerAction->setWhatsThis(i18n("Creates a data picker for getting data from a picture")); actionCollection()->addAction("new_datapicker", m_newDatapickerAction); connect(m_newDatapickerAction, &QAction::triggered, this, &MainWin::newDatapicker); m_newSpreadsheetAction = new QAction(QIcon::fromTheme("labplot-spreadsheet-new"),i18n("Spreadsheet"),this); // m_newSpreadsheetAction->setShortcut(Qt::CTRL+Qt::Key_Equal); m_newSpreadsheetAction->setWhatsThis(i18n("Creates a new spreadsheet for data editing")); actionCollection()->addAction("new_spreadsheet", m_newSpreadsheetAction); connect(m_newSpreadsheetAction, &QAction::triggered, this, &MainWin::newSpreadsheet); m_newMatrixAction = new QAction(QIcon::fromTheme("labplot-matrix-new"),i18n("Matrix"),this); // m_newMatrixAction->setShortcut(Qt::CTRL+Qt::Key_Equal); m_newMatrixAction->setWhatsThis(i18n("Creates a new matrix for data editing")); actionCollection()->addAction("new_matrix", m_newMatrixAction); connect(m_newMatrixAction, &QAction::triggered, this, &MainWin::newMatrix); m_newWorksheetAction = new QAction(QIcon::fromTheme("labplot-worksheet-new"),i18n("Worksheet"),this); // m_newWorksheetAction->setShortcut(Qt::ALT+Qt::Key_X); m_newWorksheetAction->setWhatsThis(i18n("Creates a new worksheet for data plotting")); actionCollection()->addAction("new_worksheet", m_newWorksheetAction); connect(m_newWorksheetAction, &QAction::triggered, this, &MainWin::newWorksheet); m_newNotesAction = new QAction(QIcon::fromTheme("document-new"),i18n("Note"),this); m_newNotesAction->setWhatsThis(i18n("Creates a new note for arbitrary text")); actionCollection()->addAction("new_notes", m_newNotesAction); connect(m_newNotesAction, &QAction::triggered, this, &MainWin::newNotes); // m_newScriptAction = new QAction(QIcon::fromTheme("insert-text"),i18n("Note/Script"),this); // actionCollection()->addAction("new_script", m_newScriptAction); // connect(m_newScriptAction, &QAction::triggered,SLOT(newScript())); m_newFolderAction = new QAction(QIcon::fromTheme("folder-new"),i18n("Folder"),this); m_newFolderAction->setWhatsThis(i18n("Creates a new folder to collect sheets and other elements")); actionCollection()->addAction("new_folder", m_newFolderAction); connect(m_newFolderAction, &QAction::triggered, this, &MainWin::newFolder); //"New file datasources" m_newLiveDataSourceAction = new QAction(QIcon::fromTheme("application-octet-stream"),i18n("Live Data Source"),this); m_newLiveDataSourceAction->setWhatsThis(i18n("Creates a live data source to read data from a real time device")); actionCollection()->addAction("new_live_datasource", m_newLiveDataSourceAction); connect(m_newLiveDataSourceAction, &QAction::triggered, this, &MainWin::newLiveDataSourceActionTriggered); //Import/Export m_importFileAction = new QAction(QIcon::fromTheme("document-import"), i18n("Import from File"), this); actionCollection()->setDefaultShortcut(m_importFileAction, Qt::CTRL+Qt::SHIFT+Qt::Key_I); m_importFileAction->setWhatsThis(i18n("Import data from a regular file")); actionCollection()->addAction("import_file", m_importFileAction); connect(m_importFileAction, &QAction::triggered, this, [=]() {importFileDialog();}); m_importSqlAction = new QAction(QIcon::fromTheme("network-server-database"), i18n("From SQL Database"), this); m_importSqlAction->setWhatsThis(i18n("Import data from a SQL database")); actionCollection()->addAction("import_sql", m_importSqlAction); connect(m_importSqlAction, &QAction::triggered, this, &MainWin::importSqlDialog); m_importDatasetAction = new QAction(QIcon::fromTheme(QLatin1String("database-index")), i18n("From Dataset Collection"), this); m_importDatasetAction->setWhatsThis(i18n("Imports data from an online dataset")); actionCollection()->addAction("import_dataset_datasource", m_importDatasetAction); connect(m_importDatasetAction, &QAction::triggered, this, &MainWin::importDatasetDialog); m_importLabPlotAction = new QAction(QIcon::fromTheme("project-open"), i18n("LabPlot Project"), this); m_importLabPlotAction->setWhatsThis(i18n("Import a project from a LabPlot project file (.lml)")); actionCollection()->addAction("import_labplot", m_importLabPlotAction); connect(m_importLabPlotAction, &QAction::triggered, this, &MainWin::importProjectDialog); #ifdef HAVE_LIBORIGIN m_importOpjAction = new QAction(QIcon::fromTheme("project-open"), i18n("Origin Project (OPJ)"), this); m_importOpjAction->setWhatsThis(i18n("Import a project from an OriginLab Origin project file (.opj)")); actionCollection()->addAction("import_opj", m_importOpjAction); connect(m_importOpjAction, &QAction::triggered, this, &MainWin::importProjectDialog); #endif m_exportAction = new QAction(QIcon::fromTheme("document-export"), i18n("Export"), this); m_exportAction->setWhatsThis(i18n("Export selected element")); actionCollection()->setDefaultShortcut(m_exportAction, Qt::CTRL+Qt::SHIFT+Qt::Key_E); actionCollection()->addAction("export", m_exportAction); connect(m_exportAction, &QAction::triggered, this, &MainWin::exportDialog); m_editFitsFileAction = new QAction(QIcon::fromTheme("editor"), i18n("FITS Metadata Editor"), this); m_editFitsFileAction->setWhatsThis(i18n("Open editor to edit FITS meta data")); actionCollection()->addAction("edit_fits", m_editFitsFileAction); connect(m_editFitsFileAction, &QAction::triggered, this, &MainWin::editFitsFileDialog); // Edit //Undo/Redo-stuff m_undoAction = KStandardAction::undo(this, SLOT(undo()), actionCollection()); m_redoAction = KStandardAction::redo(this, SLOT(redo()), actionCollection()); m_historyAction = new QAction(QIcon::fromTheme("view-history"), i18n("Undo/Redo History"),this); actionCollection()->addAction("history", m_historyAction); connect(m_historyAction, &QAction::triggered, this, &MainWin::historyDialog); #ifdef Q_OS_MAC m_undoIconOnlyAction = new QAction(m_undoAction->icon(), QString()); connect(m_undoIconOnlyAction, &QAction::triggered, this, &MainWin::undo); m_redoIconOnlyAction = new QAction(m_redoAction->icon(), QString()); connect(m_redoIconOnlyAction, &QAction::triggered, this, &MainWin::redo); #endif // TODO: more menus // Appearance // Analysis: see WorksheetView.cpp // Drawing // Script //Windows m_closeWindowAction = new QAction(i18n("&Close"), this); actionCollection()->setDefaultShortcut(m_closeWindowAction, QKeySequence::Close); m_closeWindowAction->setStatusTip(i18n("Close the active window")); actionCollection()->addAction("close window", m_closeWindowAction); m_closeAllWindowsAction = new QAction(i18n("Close &All"), this); m_closeAllWindowsAction->setStatusTip(i18n("Close all the windows")); actionCollection()->addAction("close all windows", m_closeAllWindowsAction); m_tileWindowsAction = new QAction(i18n("&Tile"), this); m_tileWindowsAction->setStatusTip(i18n("Tile the windows")); actionCollection()->addAction("tile windows", m_tileWindowsAction); m_cascadeWindowsAction = new QAction(i18n("&Cascade"), this); m_cascadeWindowsAction->setStatusTip(i18n("Cascade the windows")); actionCollection()->addAction("cascade windows", m_cascadeWindowsAction); m_nextWindowAction = new QAction(QIcon::fromTheme("go-next-view"), i18n("Ne&xt"), this); actionCollection()->setDefaultShortcut(m_nextWindowAction, QKeySequence::NextChild); m_nextWindowAction->setStatusTip(i18n("Move the focus to the next window")); actionCollection()->addAction("next window", m_nextWindowAction); m_prevWindowAction = new QAction(QIcon::fromTheme("go-previous-view"), i18n("Pre&vious"), this); actionCollection()->setDefaultShortcut(m_prevWindowAction, QKeySequence::PreviousChild); m_prevWindowAction->setStatusTip(i18n("Move the focus to the previous window")); actionCollection()->addAction("previous window", m_prevWindowAction); //"Standard actions" KStandardAction::preferences(this, SLOT(settingsDialog()), actionCollection()); KStandardAction::quit(this, SLOT(close()), actionCollection()); //Actions for window visibility auto* windowVisibilityActions = new QActionGroup(this); windowVisibilityActions->setExclusive(true); m_visibilityFolderAction = new QAction(QIcon::fromTheme("folder"), i18n("Current &Folder Only"), windowVisibilityActions); m_visibilityFolderAction->setCheckable(true); m_visibilityFolderAction->setData(static_cast(Project::MdiWindowVisibility::folderOnly)); m_visibilitySubfolderAction = new QAction(QIcon::fromTheme("folder-documents"), i18n("Current Folder and &Subfolders"), windowVisibilityActions); m_visibilitySubfolderAction->setCheckable(true); m_visibilitySubfolderAction->setData(static_cast(Project::MdiWindowVisibility::folderAndSubfolders)); m_visibilityAllAction = new QAction(i18n("&All"), windowVisibilityActions); m_visibilityAllAction->setCheckable(true); m_visibilityAllAction->setData(static_cast(Project::MdiWindowVisibility::allMdiWindows)); connect(windowVisibilityActions, &QActionGroup::triggered, this, &MainWin::setMdiWindowVisibility); //Actions for hiding/showing the dock widgets auto* docksActions = new QActionGroup(this); docksActions->setExclusive(false); m_toggleProjectExplorerDockAction = new QAction(QIcon::fromTheme("view-list-tree"), i18n("Project Explorer"), docksActions); m_toggleProjectExplorerDockAction->setCheckable(true); m_toggleProjectExplorerDockAction->setChecked(true); actionCollection()->addAction("toggle_project_explorer_dock", m_toggleProjectExplorerDockAction); m_togglePropertiesDockAction = new QAction(QIcon::fromTheme("view-list-details"), i18n("Properties Explorer"), docksActions); m_togglePropertiesDockAction->setCheckable(true); m_togglePropertiesDockAction->setChecked(true); actionCollection()->addAction("toggle_properties_explorer_dock", m_togglePropertiesDockAction); connect(docksActions, &QActionGroup::triggered, this, &MainWin::toggleDockWidget); //global search QAction* searchAction = new QAction(actionCollection()); searchAction->setShortcut(QKeySequence::Find); connect(searchAction, &QAction::triggered, this, [=]() { if (m_project) { if (!m_projectExplorerDock->isVisible()) { m_toggleProjectExplorerDockAction->setChecked(true); toggleDockWidget(m_toggleProjectExplorerDockAction); } m_projectExplorer->search(); } }); this->addAction(searchAction); } void MainWin::initMenus() { //menu in the main toolbar for adding new aspects auto* menu = dynamic_cast(factory()->container("new", this)); menu->setIcon(QIcon::fromTheme("window-new")); //menu in the project explorer and in the toolbar for adding new aspects m_newMenu = new QMenu(i18n("Add New"), this); m_newMenu->setIcon(QIcon::fromTheme("window-new")); m_newMenu->addAction(m_newFolderAction); m_newMenu->addAction(m_newWorkbookAction); m_newMenu->addAction(m_newSpreadsheetAction); m_newMenu->addAction(m_newMatrixAction); m_newMenu->addAction(m_newWorksheetAction); m_newMenu->addAction(m_newNotesAction); m_newMenu->addAction(m_newDatapickerAction); m_newMenu->addSeparator(); m_newMenu->addAction(m_newLiveDataSourceAction); //import menu m_importMenu = new QMenu(this); m_importMenu->setIcon(QIcon::fromTheme("document-import")); m_importMenu ->addAction(m_importFileAction); m_importMenu ->addAction(m_importSqlAction); m_importMenu->addAction(m_importDatasetAction); m_importMenu->addSeparator(); m_importMenu->addAction(m_importLabPlotAction); #ifdef HAVE_LIBORIGIN m_importMenu ->addAction(m_importOpjAction); #endif #ifdef HAVE_CANTOR_LIBS m_newMenu->addSeparator(); m_newCantorWorksheetMenu = new QMenu(i18n("CAS Worksheet"), this); m_newCantorWorksheetMenu->setIcon(QIcon::fromTheme("archive-insert")); //"Adding Cantor backends to menu and context menu" QStringList m_availableBackend = Cantor::Backend::listAvailableBackends(); if (m_availableBackend.count() > 0) { unplugActionList(QLatin1String("backends_list")); QList newBackendActions; for (auto* backend : Cantor::Backend::availableBackends()) { if (!backend->isEnabled()) continue; QAction* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(), this); action->setData(backend->name()); newBackendActions << action; m_newCantorWorksheetMenu->addAction(action); } connect(m_newCantorWorksheetMenu, &QMenu::triggered, this, &MainWin::newCantorWorksheet); plugActionList(QLatin1String("backends_list"), newBackendActions); } m_newMenu->addMenu(m_newCantorWorksheetMenu); #else delete this->guiFactory()->container("cas_worksheet", this); delete this->guiFactory()->container("new_cas_worksheet", this); delete this->guiFactory()->container("cas_worksheet_toolbar", this); #endif //menu subwindow visibility policy m_visibilityMenu = new QMenu(i18n("Window Visibility Policy"), this); m_visibilityMenu->setIcon(QIcon::fromTheme("window-duplicate")); m_visibilityMenu ->addAction(m_visibilityFolderAction); m_visibilityMenu ->addAction(m_visibilitySubfolderAction); m_visibilityMenu ->addAction(m_visibilityAllAction); //menu for editing files m_editMenu = new QMenu(i18n("Edit"), this); m_editMenu->addAction(m_editFitsFileAction); //set the action for the current color scheme checked KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); #if KCONFIGWIDGETS_VERSION > QT_VERSION_CHECK(5,67,0) // Since version 5.67 KColorSchemeManager has a system default option QString schemeName = group.readEntry("ColorScheme"); #else KConfigGroup generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General"); QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze")); QString schemeName = group.readEntry("ColorScheme", defaultSchemeName); #endif KActionMenu* schemesMenu = m_schemeManager->createSchemeSelectionMenu(i18n("Color Scheme"), schemeName, this); schemesMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color"))); QMenu* settingsMenu = dynamic_cast(factory()->container("settings", this)); if (settingsMenu) settingsMenu->insertMenu(settingsMenu->actions().constFirst(), schemesMenu->menu()); connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged); #ifdef HAVE_CANTOR_LIBS QAction* action = new QAction(QIcon::fromTheme(QLatin1String("cantor")), i18n("Configure CAS"), this); connect(action, &QAction::triggered, this, &MainWin::cantorSettingsDialog); if (settingsMenu) settingsMenu->addAction(action); #endif } void MainWin::colorSchemeChanged(QAction* action) { QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text()); //background of the mdi area is not updated on theme changes, do it here. QModelIndex index = m_schemeManager->indexForScheme(schemeName); const QPalette& palette = KColorScheme::createApplicationPalette( KSharedConfig::openConfig(index.data(Qt::UserRole).toString()) ); const QBrush& brush = palette.brush(QPalette::Dark); m_mdiArea->setBackground(brush); //save the selected color scheme KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); group.writeEntry("ColorScheme", schemeName); group.sync(); } /*! Asks to save the project if it was modified. \return \c true if the project still needs to be saved ("cancel" clicked), \c false otherwise. */ bool MainWin::warnModified() { if (m_project->hasChanged()) { int want_save = KMessageBox::warningYesNoCancel( this, i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()), i18n("Save Project")); switch (want_save) { case KMessageBox::Yes: return !saveProject(); case KMessageBox::No: break; case KMessageBox::Cancel: return true; } } return false; } /*! * updates the state of actions, menus and toolbars (enabled or disabled) * on project changes (project closes and opens) */ void MainWin::updateGUIOnProjectChanges() { if (m_closing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } //disable all menus if there is no project bool b = (m_project == nullptr); m_saveAction->setEnabled(!b); m_saveAsAction->setEnabled(!b); m_printAction->setEnabled(!b); m_printPreviewAction->setEnabled(!b); m_importFileAction->setEnabled(!b); m_importSqlAction->setEnabled(!b); #ifdef HAVE_LIBORIGIN m_importOpjAction->setEnabled(!b); #endif m_exportAction->setEnabled(!b); m_newWorkbookAction->setEnabled(!b); m_newSpreadsheetAction->setEnabled(!b); m_newMatrixAction->setEnabled(!b); m_newWorksheetAction->setEnabled(!b); m_newDatapickerAction->setEnabled(!b); m_closeAction->setEnabled(!b); m_toggleProjectExplorerDockAction->setEnabled(!b); m_togglePropertiesDockAction->setEnabled(!b); if (!m_mdiArea || !m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif } factory->container("new", this)->setEnabled(!b); factory->container("edit", this)->setEnabled(!b); factory->container("import", this)->setEnabled(!b); if (b) setCaption("LabPlot2"); else setCaption(m_project->name()); #ifdef Q_OS_MAC m_touchBar->clear(); if (b){ m_touchBar->addAction(m_newProjectAction); m_touchBar->addAction(m_openProjectAction); } else { m_touchBar->addAction(m_importFileAction); m_touchBar->addAction(m_newWorksheetAction); m_touchBar->addAction(m_newSpreadsheetAction); m_touchBar->addAction(m_newMatrixAction); } #endif // undo/redo actions are disabled in both cases - when the project is closed or opened m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } /* * updates the state of actions, menus and toolbars (enabled or disabled) * depending on the currently active window (worksheet or spreadsheet). */ void MainWin::updateGUI() { if (m_project == nullptr || m_project->isLoading()) return; if (m_closing || m_projectClosing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } //reset the touchbar #ifdef Q_OS_MAC m_touchBar->clear(); m_touchBar->addAction(m_undoIconOnlyAction); m_touchBar->addAction(m_redoIconOnlyAction); m_touchBar->addSeparator(); #endif if (!m_mdiArea || !m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif return; } #ifdef Q_OS_MAC if (dynamic_cast(m_currentAspect)) { m_touchBar->addAction(m_newWorksheetAction); m_touchBar->addAction(m_newSpreadsheetAction); m_touchBar->addAction(m_newMatrixAction); } #endif //Handle the Worksheet-object const Worksheet* w = dynamic_cast(m_currentAspect); if (!w) w = dynamic_cast(m_currentAspect->parent(AspectType::Worksheet)); if (w) { //populate worksheet menu auto* view = qobject_cast(w->view()); auto* menu = qobject_cast(factory->container("worksheet", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate analysis menu menu = qobject_cast(factory->container("analysis", this)); menu->clear(); view->createAnalysisMenu(menu); menu->setEnabled(true); //populate worksheet-toolbar auto* toolbar = qobject_cast(factory->container("worksheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the toolbar for cartesian plots toolbar = qobject_cast(factory->container("cartesian_plot_toolbar", this)); toolbar->clear(); view->fillCartesianPlotToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the touchbar on Mac #ifdef Q_OS_MAC view->fillTouchBar(m_touchBar); #endif //hide the spreadsheet toolbar factory->container("spreadsheet_toolbar", this)->setVisible(false); } else { factory->container("worksheet", this)->setEnabled(false); factory->container("worksheet_toolbar", this)->setVisible(false); factory->container("analysis", this)->setEnabled(false); // factory->container("drawing", this)->setEnabled(false); factory->container("worksheet_toolbar", this)->setEnabled(false); factory->container("cartesian_plot_toolbar", this)->setEnabled(false); } //Handle the Spreadsheet-object const auto* spreadsheet = this->activeSpreadsheet(); if (!spreadsheet) spreadsheet = dynamic_cast(m_currentAspect->parent(AspectType::Spreadsheet)); if (spreadsheet) { //populate spreadsheet-menu auto* view = qobject_cast(spreadsheet->view()); auto* menu = qobject_cast(factory->container("spreadsheet", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("spreadsheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the touchbar on Mac #ifdef Q_OS_MAC m_touchBar->addAction(m_importFileAction); view->fillTouchBar(m_touchBar); #endif } else { factory->container("spreadsheet", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->setVisible(false); } //Handle the Matrix-object const Matrix* matrix = dynamic_cast(m_currentAspect); if (!matrix) matrix = dynamic_cast(m_currentAspect->parent(AspectType::Matrix)); if (matrix) { //populate matrix-menu auto* view = qobject_cast(matrix->view()); auto* menu = qobject_cast(factory->container("matrix", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate the touchbar on Mac #ifdef Q_OS_MAC m_touchBar->addAction(m_importFileAction); //view->fillTouchBar(m_touchBar); #endif } else factory->container("matrix", this)->setEnabled(false); #ifdef HAVE_CANTOR_LIBS const CantorWorksheet* cantorworksheet = dynamic_cast(m_currentAspect); if (!cantorworksheet) cantorworksheet = dynamic_cast(m_currentAspect->parent(AspectType::CantorWorksheet)); if (cantorworksheet) { auto* view = qobject_cast(cantorworksheet->view()); auto* menu = qobject_cast(factory->container("cas_worksheet", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); auto* toolbar = qobject_cast(factory->container("cas_worksheet_toolbar", this)); toolbar->setVisible(true); toolbar->clear(); view->fillToolBar(toolbar); } else { //no Cantor worksheet selected -> deactivate Cantor worksheet related menu and toolbar factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->setVisible(false); } #endif const Datapicker* datapicker = dynamic_cast(m_currentAspect); if (!datapicker) datapicker = dynamic_cast(m_currentAspect->parent(AspectType::Datapicker)); if (!datapicker) { if (m_currentAspect->type() == AspectType::DatapickerCurve) datapicker = dynamic_cast(m_currentAspect->parentAspect()); } if (datapicker) { //populate datapicker-menu auto* view = qobject_cast(datapicker->view()); auto* menu = qobject_cast(factory->container("datapicker", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("datapicker_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); } else { factory->container("datapicker", this)->setEnabled(false); factory->container("datapicker_toolbar", this)->setVisible(false); } } /*! creates a new empty project. Returns \c true, if a new project was created. */ bool MainWin::newProject() { //close the current project, if available if (!closeProject()) return false; // if(dynamic_cast(centralWidget())) { // createMdiArea(); // setCentralWidget(m_mdiArea); // } QApplication::processEvents(QEventLoop::AllEvents, 100); if (m_project) delete m_project; if (m_aspectTreeModel) delete m_aspectTreeModel; m_project = new Project(); m_currentAspect = m_project; m_currentFolder = m_project; KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); Project::MdiWindowVisibility vis = Project::MdiWindowVisibility(group.readEntry("MdiWindowVisibility", 0)); m_project->setMdiWindowVisibility( vis ); if (vis == Project::MdiWindowVisibility::folderOnly) m_visibilityFolderAction->setChecked(true); else if (vis == Project::MdiWindowVisibility::folderAndSubfolders) m_visibilitySubfolderAction->setChecked(true); else m_visibilityAllAction->setChecked(true); m_aspectTreeModel = new AspectTreeModel(m_project, this); connect(m_aspectTreeModel, &AspectTreeModel::statusInfo, [=](const QString& text){ statusBar()->showMessage(text); }); //newProject is called for the first time, there is no project explorer yet //-> initialize the project explorer, the GUI-observer and the dock widgets. if (m_projectExplorer == nullptr) { m_projectExplorerDock = new QDockWidget(this); m_projectExplorerDock->setObjectName("projectexplorer"); m_projectExplorerDock->setWindowTitle(i18nc("@title:window", "Project Explorer")); addDockWidget(Qt::LeftDockWidgetArea, m_projectExplorerDock); m_projectExplorer = new ProjectExplorer(m_projectExplorerDock); m_projectExplorerDock->setWidget(m_projectExplorer); connect(m_projectExplorer, &ProjectExplorer::currentAspectChanged, this, &MainWin::handleCurrentAspectChanged); connect(m_projectExplorerDock, &QDockWidget::visibilityChanged, this, &MainWin::projectExplorerDockVisibilityChanged); //Properties dock m_propertiesDock = new QDockWidget(this); m_propertiesDock->setObjectName("aspect_properties_dock"); m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); addDockWidget(Qt::RightDockWidgetArea, m_propertiesDock); auto* sa = new QScrollArea(m_propertiesDock); stackedWidget = new QStackedWidget(sa); sa->setWidget(stackedWidget); sa->setWidgetResizable(true); m_propertiesDock->setWidget(sa); connect(m_propertiesDock, &QDockWidget::visibilityChanged, this, &MainWin::propertiesDockVisibilityChanged); //GUI-observer; m_guiObserver = new GuiObserver(this); } m_projectExplorer->setModel(m_aspectTreeModel); m_projectExplorer->setProject(m_project); m_projectExplorer->setCurrentAspect(m_project); m_projectExplorerDock->show(); m_propertiesDock->show(); updateGUIOnProjectChanges(); connect(m_project, &Project::aspectAdded, this, &MainWin::handleAspectAdded); connect(m_project, &Project::aspectRemoved, this, &MainWin::handleAspectRemoved); connect(m_project, &Project::aspectAboutToBeRemoved, this, &MainWin::handleAspectAboutToBeRemoved); connect(m_project, SIGNAL(statusInfo(QString)), statusBar(), SLOT(showMessage(QString))); connect(m_project, &Project::changed, this, &MainWin::projectChanged); connect(m_project, &Project::requestProjectContextMenu, this, &MainWin::createContextMenu); connect(m_project, &Project::requestFolderContextMenu, this, &MainWin::createFolderContextMenu); connect(m_project, &Project::mdiWindowVisibilityChanged, this, &MainWin::updateMdiWindowVisibility); connect(m_project, &Project::closeRequested, this, &MainWin::closeProject); m_undoViewEmptyLabel = i18n("%1: created", m_project->name()); setCaption(m_project->name()); return true; } void MainWin::openProject() { KConfigGroup group(KSharedConfig::openConfig(), "MainWin"); const QString& dir = group.readEntry("LastOpenDir", ""); bool supportOthers = false; QString allExtensions = Project::supportedExtensions(); QString extensions = i18n("LabPlot Projects (%1)", allExtensions); if (m_lastOpenFileFilter.isEmpty()) m_lastOpenFileFilter = extensions; #ifdef HAVE_LIBORIGIN extensions += i18n(";;Origin Projects (%1)", OriginProjectParser::supportedExtensions()); allExtensions += " " + OriginProjectParser::supportedExtensions(); supportOthers = true; #endif #ifdef HAVE_CANTOR_LIBS extensions += i18n(";;Cantor Projects (*.cws)"); extensions += i18n(";;Jupyter Notebooks (*.ipynb)"); allExtensions += QLatin1String(" *.cws *.ipynb"); supportOthers = true; #endif //add an entry for "All supported files" if we support more than labplot if (supportOthers) extensions = i18n("All supported files (%1)", allExtensions) + QLatin1String(";;") + extensions; const QString& path = QFileDialog::getOpenFileName(this,i18n("Open Project"), dir, extensions, &m_lastOpenFileFilter); if (path.isEmpty())// "Cancel" was clicked return; this->openProject(path); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) group.writeEntry("LastOpenDir", newDir); } } void MainWin::openProject(const QString& filename) { if (filename == m_currentFileName) { KMessageBox::information(this, i18n("The project file %1 is already opened.", filename), i18n("Open Project")); return; } // if(dynamic_cast(centralWidget())) { // createMdiArea(); // setCentralWidget(m_mdiArea); // } if (!newProject()) return; WAIT_CURSOR; QElapsedTimer timer; timer.start(); bool rc = false; if (Project::isLabPlotProject(filename)) { m_project->setFileName(filename); rc = m_project->load(filename); } #ifdef HAVE_LIBORIGIN else if (OriginProjectParser::isOriginProject(filename)) { OriginProjectParser parser; parser.setProjectFileName(filename); parser.importTo(m_project, QStringList()); //TODO: add return code rc = true; } #endif #ifdef HAVE_CANTOR_LIBS else if (QFileInfo(filename).completeSuffix() == QLatin1String("cws")) { QFile file(filename); KZip archive(&file); rc = archive.open(QIODevice::ReadOnly); if (rc) { const auto* contentEntry = archive.directory()->entry(QLatin1String("content.xml")); if (contentEntry && contentEntry->isFile()) { const auto* contentFile = static_cast(contentEntry); QByteArray data = contentFile->data(); archive.close(); //determine the name of the backend QDomDocument doc; doc.setContent(data); QString backendName = doc.documentElement().attribute(QLatin1String("backend")); if (!backendName.isEmpty()) { //create new Cantor worksheet and load the data auto* worksheet = new CantorWorksheet(backendName); worksheet->setName(QFileInfo(filename).fileName()); worksheet->setComment(filename); rc = file.open(QIODevice::ReadOnly); if (rc) { QByteArray content = file.readAll(); rc = worksheet->init(&content); if (rc) m_project->addChild(worksheet); else { delete worksheet; RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } }else { RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename)); } } else if (QFileInfo(filename).completeSuffix() == QLatin1String("ipynb")) { QFile file(filename); rc = file.open(QIODevice::ReadOnly); if (rc) { QByteArray content = file.readAll(); QJsonParseError error; // TODO: use QJsonDocument& doc = QJsonDocument::fromJson(content, &error); if minimum Qt version is at least 5.10 const QJsonDocument& jsonDoc = QJsonDocument::fromJson(content, &error); const QJsonObject& doc = jsonDoc.object(); if (error.error == QJsonParseError::NoError) { //determine the backend name QString backendName; // TODO: use doc["metadata"]["kernelspec"], etc. if minimum Qt version is at least 5.10 if ((doc["metadata"] != QJsonValue::Undefined && doc["metadata"].isObject()) && (doc["metadata"].toObject()["kernelspec"] != QJsonValue::Undefined && doc["metadata"].toObject()["kernelspec"].isObject()) ) { QString kernel; if (doc["metadata"].toObject()["kernelspec"].toObject()["name"] != QJsonValue::Undefined) kernel = doc["metadata"].toObject()["kernelspec"].toObject()["name"].toString(); if (!kernel.isEmpty()) { if (kernel.startsWith(QLatin1String("julia"))) backendName = QLatin1String("julia"); else if (kernel == QLatin1String("sagemath")) backendName = QLatin1String("sage"); else if (kernel == QLatin1String("ir")) backendName = QLatin1String("r"); else backendName = kernel; } else backendName = doc["metadata"].toObject()["kernelspec"].toObject()["language"].toString(); if (!backendName.isEmpty()) { //create new Cantor worksheet and load the data auto* worksheet = new CantorWorksheet(backendName); worksheet->setName(QFileInfo(filename).fileName()); worksheet->setComment(filename); rc = worksheet->init(&content); if (rc) m_project->addChild(worksheet); else { delete worksheet; RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename)); } } #endif m_project->setChanged(false); if (!rc) { closeProject(); RESET_CURSOR; return; } m_currentFileName = filename; m_project->undoStack()->clear(); m_undoViewEmptyLabel = i18n("%1: opened", m_project->name()); m_recentProjectsAction->addUrl( QUrl(filename) ); setCaption(m_project->name()); updateGUIOnProjectChanges(); updateGUI(); //there are most probably worksheets or spreadsheets in the open project -> update the GUI m_saveAction->setEnabled(false); statusBar()->showMessage( i18n("Project successfully opened (in %1 seconds).", (float)timer.elapsed()/1000) ); if (m_autoSaveActive) m_autoSaveTimer.start(); RESET_CURSOR; } void MainWin::openRecentProject(const QUrl& url) { // if(dynamic_cast(centralWidget())) { // createMdiArea(); // setCentralWidget(m_mdiArea); // } if (url.isLocalFile()) // fix for Windows this->openProject(url.toLocalFile()); else this->openProject(url.path()); } /*! Closes the current project, if available. Return \c true, if the project was closed. */ bool MainWin::closeProject() { if (m_project == nullptr) return true; //nothing to close if (warnModified()) return false; if(!m_closing) { // if(dynamic_cast(centralWidget()) && m_showWelcomeScreen) { // m_welcomeWidget = createWelcomeScreen(); // setCentralWidget(m_welcomeWidget); // } } m_projectClosing = true; statusBar()->clearMessage(); delete m_aspectTreeModel; m_aspectTreeModel = nullptr; delete m_project; m_project = nullptr; m_currentFileName.clear(); m_projectClosing = false; //update the UI if we're just closing a project //and not closing(quitting) the application if (!m_closing) { m_projectExplorerDock->hide(); m_propertiesDock->hide(); m_currentAspect = nullptr; m_currentFolder = nullptr; updateGUIOnProjectChanges(); if (m_autoSaveActive) m_autoSaveTimer.stop(); } removeDockWidget(cursorDock); delete cursorDock; cursorDock = nullptr; cursorWidget = nullptr; // is deleted, because it's the cild of cursorDock return true; } bool MainWin::saveProject() { const QString& fileName = m_project->fileName(); if (fileName.isEmpty()) return saveProjectAs(); else return save(fileName); } bool MainWin::saveProjectAs() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); QString path = QFileDialog::getSaveFileName(this, i18n("Save Project As"), dir, i18n("LabPlot Projects (*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ)")); if (path.isEmpty())// "Cancel" was clicked return false; if (path.contains(QLatin1String(".lml"), Qt::CaseInsensitive) == false) path.append(QLatin1String(".lml")); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } return save(path); } /*! * auxiliary function that does the actual saving of the project */ bool MainWin::save(const QString& fileName) { QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + QLatin1String("labplot_save_XXXXXX")); if (!tempFile.open()) { KMessageBox::error(this, i18n("Couldn't open the temporary file for writing.")); return false; } WAIT_CURSOR; const QString& tempFileName = tempFile.fileName(); DEBUG("Using temporary file " << STDSTRING(tempFileName)) tempFile.close(); // use file ending to find out how to compress file QIODevice* file; // if ending is .lml, do gzip compression anyway if (fileName.endsWith(QLatin1String(".lml"))) file = new KCompressionDevice(tempFileName, KCompressionDevice::GZip); else file = new KFilterDev(tempFileName); if (file == nullptr) file = new QFile(tempFileName); bool ok; if (file->open(QIODevice::WriteOnly)) { m_project->setFileName(fileName); QPixmap thumbnail = centralWidget()->grab(); QXmlStreamWriter writer(file); m_project->setFileName(fileName); m_project->save(thumbnail, &writer); m_project->undoStack()->clear(); m_project->setChanged(false); file->close(); // target file must not exist if (QFile::exists(fileName)) QFile::remove(fileName); // do not rename temp file. Qt still holds a handle (which fails renaming on Windows) and deletes it bool rc = QFile::copy(tempFileName, fileName); if (rc) { setCaption(m_project->name()); statusBar()->showMessage(i18n("Project saved")); m_saveAction->setEnabled(false); m_recentProjectsAction->addUrl( QUrl(fileName) ); ok = true; //if the project dock is visible, refresh the shown content //(version and modification time might have been changed) if (stackedWidget->currentWidget() == projectDock) projectDock->setProject(m_project); //we have a file name now // -> auto save can be activated now if not happened yet if (m_autoSaveActive && !m_autoSaveTimer.isActive()) m_autoSaveTimer.start(); } else { RESET_CURSOR; KMessageBox::error(this, i18n("Couldn't save the file '%1'.", fileName)); ok = false; } } else { RESET_CURSOR; KMessageBox::error(this, i18n("Couldn't open the file '%1' for writing.", fileName)); ok = false; } delete file; RESET_CURSOR; return ok; } /*! * automatically saves the project in the specified time interval. */ void MainWin::autoSaveProject() { //don't auto save when there are no changes or the file name //was not provided yet (the project was never explicitly saved yet). if ( !m_project->hasChanged() || m_project->fileName().isEmpty()) return; this->saveProject(); } /*! prints the current sheet (worksheet, spreadsheet or matrix) */ void MainWin::print() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = static_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printView()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->clearMessage(); } void MainWin::printPreview() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = static_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printPreview()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->clearMessage(); } /**************************************************************************************/ /*! adds a new Folder to the project. */ void MainWin::newFolder() { Folder* folder = new Folder(i18n("Folder")); this->addAspectToProject(folder); } /*! adds a new Workbook to the project. */ void MainWin::newWorkbook() { Workbook* workbook = new Workbook(i18n("Workbook")); this->addAspectToProject(workbook); } /*! adds a new Datapicker to the project. */ void MainWin::newDatapicker() { Datapicker* datapicker = new Datapicker(i18n("Datapicker")); this->addAspectToProject(datapicker); } /*! adds a new Spreadsheet to the project. */ void MainWin::newSpreadsheet() { Spreadsheet* spreadsheet = new Spreadsheet(i18n("Spreadsheet")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new spreadsheet to the workbook Workbook* workbook = dynamic_cast(m_currentAspect); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits(AspectType::Folder)) { workbook->addChild(spreadsheet); return; } } this->addAspectToProject(spreadsheet); } /*! adds a new Matrix to the project. */ void MainWin::newMatrix() { Matrix* matrix = new Matrix(i18n("Matrix")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new matrix to the workbook Workbook* workbook = dynamic_cast(m_currentAspect); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits(AspectType::Folder)) { workbook->addChild(matrix); return; } } this->addAspectToProject(matrix); } /*! adds a new Worksheet to the project. */ void MainWin::newWorksheet() { Worksheet* worksheet = new Worksheet(i18n("Worksheet")); this->addAspectToProject(worksheet); } /*! adds a new Note to the project. */ void MainWin::newNotes() { Note* notes = new Note(i18n("Note")); this->addAspectToProject(notes); } /*! returns a pointer to a \c Spreadsheet object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c SpreadsheetView Otherwise returns \c 0. */ Spreadsheet* MainWin::activeSpreadsheet() const { // if(dynamic_cast(centralWidget())) // return nullptr; if (!m_currentAspect) return nullptr; Spreadsheet* spreadsheet = nullptr; if (m_currentAspect->type() == AspectType::Spreadsheet) spreadsheet = dynamic_cast(m_currentAspect); else { //check whether one of spreadsheet columns is selected and determine the spreadsheet auto* parent = m_currentAspect->parentAspect(); if (parent && parent->type() == AspectType::Spreadsheet) spreadsheet = dynamic_cast(parent); } return spreadsheet; } #ifdef HAVE_CANTOR_LIBS /* adds a new Cantor Spreadsheet to the project. */ void MainWin::newCantorWorksheet(QAction* action) { CantorWorksheet* cantorworksheet = new CantorWorksheet(action->data().toString()); this->addAspectToProject(cantorworksheet); } /********************************************************************************/ #endif /*! called if there were changes in the project. Adds "changed" to the window caption and activates the save-Action. */ void MainWin::projectChanged() { setCaption(i18n("%1 [Changed]", m_project->name())); m_saveAction->setEnabled(true); m_undoAction->setEnabled(true); return; } void MainWin::handleCurrentSubWindowChanged(QMdiSubWindow* win) { if (!win) { updateGUI(); return; } auto* view = static_cast(win); if (view == m_currentSubWindow) { //do nothing, if the current sub-window gets selected again. //This event happens, when labplot loses the focus (modal window is opened or the user switches to another application) //and gets it back (modal window is closed or the user switches back to labplot). return; } else m_currentSubWindow = view; updateGUI(); if (!m_suppressCurrentSubWindowChangedEvent) m_projectExplorer->setCurrentAspect(view->part()); } void MainWin::handleAspectAdded(const AbstractAspect* aspect) { const auto* part = dynamic_cast(aspect); if (part) { // connect(part, &AbstractPart::importFromFileRequested, this, &MainWin::importFileDialog); connect(part, &AbstractPart::importFromFileRequested, this, [=]() {importFileDialog();}); connect(part, &AbstractPart::importFromSQLDatabaseRequested, this, &MainWin::importSqlDialog); //TODO: export, print and print preview should be handled in the views and not in MainWin. connect(part, &AbstractPart::exportRequested, this, &MainWin::exportDialog); connect(part, &AbstractPart::printRequested, this, &MainWin::print); connect(part, &AbstractPart::printPreviewRequested, this, &MainWin::printPreview); connect(part, &AbstractPart::showRequested, this, &MainWin::handleShowSubWindowRequested); const auto* worksheet = dynamic_cast(aspect); if (worksheet) connect(worksheet, &Worksheet::cartesianPlotMouseModeChanged, this, &MainWin::cartesianPlotMouseModeChanged); } } void MainWin::handleAspectRemoved(const AbstractAspect* parent,const AbstractAspect* before,const AbstractAspect* aspect) { Q_UNUSED(before); Q_UNUSED(aspect); //no need to react on removal of // - AbstractSimpleFilter // - columns in the data spreadsheet of a datapicker curve, // this can only happen when changing the error type and is done on the level of DatapickerImage if (!dynamic_cast(aspect) && !(parent->parentAspect() && parent->parentAspect()->type() == AspectType::DatapickerCurve) ) m_projectExplorer->setCurrentAspect(parent); } void MainWin::handleAspectAboutToBeRemoved(const AbstractAspect *aspect) { const auto* part = dynamic_cast(aspect); if (!part) return; const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (!workbook && !datapicker) { PartMdiView* win = part->mdiSubWindow(); if (win) m_mdiArea->removeSubWindow(win); } } /*! called when the current aspect in the tree of the project explorer was changed. Selects the new aspect. */ void MainWin::handleCurrentAspectChanged(AbstractAspect *aspect) { if (!aspect) aspect = m_project; // should never happen, just in case m_suppressCurrentSubWindowChangedEvent = true; if (aspect->folder() != m_currentFolder) { m_currentFolder = aspect->folder(); updateMdiWindowVisibility(); } m_currentAspect = aspect; //activate the corresponding MDI sub window for the current aspect activateSubWindowForAspect(aspect); m_suppressCurrentSubWindowChangedEvent = false; updateGUI(); } void MainWin::activateSubWindowForAspect(const AbstractAspect* aspect) const { const auto* part = dynamic_cast(aspect); if (part) { PartMdiView* win{nullptr}; //for aspects being children of a Workbook, we show workbook's window, otherwise the window of the selected part const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (workbook) win = workbook->mdiSubWindow(); else if (datapicker) win = datapicker->mdiSubWindow(); else win = part->mdiSubWindow(); if (m_mdiArea && m_mdiArea->subWindowList().indexOf(win) == -1) { if (dynamic_cast(part)) m_mdiArea->addSubWindow(win, Qt::Tool); else m_mdiArea->addSubWindow(win); win->show(); //Qt provides its own "system menu" for every sub-window. The shortcut for the close-action //in this menu collides with our global m_closeAction. //remove the shortcuts in the system menu to avoid this collision. QMenu* menu = win->systemMenu(); if (menu) { for (QAction* action : menu->actions()) action->setShortcut(QKeySequence()); } } if (m_mdiArea) m_mdiArea->setActiveSubWindow(win); } else { //activate the mdiView of the parent, if a child was selected const AbstractAspect* parent = aspect->parentAspect(); if (parent) { activateSubWindowForAspect(parent); //if the parent's parent is a Workbook (a column of a spreadsheet in workbook was selected), //we need to select the corresponding tab in WorkbookView too if (parent->parentAspect()) { auto* workbook = dynamic_cast(parent->parentAspect()); auto* datapicker = dynamic_cast(parent->parentAspect()); if (!datapicker) datapicker = dynamic_cast(parent->parentAspect()->parentAspect()); if (workbook) workbook->childSelected(parent); else if (datapicker) datapicker->childSelected(parent); } } } return; } void MainWin::setMdiWindowVisibility(QAction* action) { m_project->setMdiWindowVisibility((Project::MdiWindowVisibility)(action->data().toInt())); } /*! shows the sub window of a worksheet, matrix or a spreadsheet. Used if the window was closed before and the user asks to show the window again via the context menu in the project explorer. */ void MainWin::handleShowSubWindowRequested() { activateSubWindowForAspect(m_currentAspect); } /*! this is called on a right click on the root folder in the project explorer */ void MainWin::createContextMenu(QMenu* menu) const { 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_newMenu); //The tabbed view collides with the visibility policy for the subwindows. //Hide the menus for the visibility policy if the tabbed view is used. if (m_mdiArea->viewMode() != QMdiArea::TabbedView) { menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_visibilityMenu); menu->insertSeparator(firstAction); } } /*! this is called on a right click on a non-root folder in the project explorer */ void MainWin::createFolderContextMenu(const Folder* folder, QMenu* menu) const { Q_UNUSED(folder); //Folder provides it's own context menu. Add a separator before adding additional actions. menu->addSeparator(); this->createContextMenu(menu); } void MainWin::undo() { WAIT_CURSOR; m_project->undoStack()->undo(); if (m_project->undoStack()->index() == 0) { setCaption(m_project->name()); m_saveAction->setEnabled(false); m_undoAction->setEnabled(false); m_project->setChanged(false); } m_redoAction->setEnabled(true); RESET_CURSOR; } void MainWin::redo() { WAIT_CURSOR; m_project->undoStack()->redo(); projectChanged(); if (m_project->undoStack()->index() == m_project->undoStack()->count()) m_redoAction->setEnabled(false); RESET_CURSOR; } /*! Shows/hides mdi sub-windows depending on the current visibility policy. */ void MainWin::updateMdiWindowVisibility() const { QList windows = m_mdiArea->subWindowList(); PartMdiView* part_view; switch (m_project->mdiWindowVisibility()) { case Project::MdiWindowVisibility::allMdiWindows: for (auto* window : windows) window->show(); break; case Project::MdiWindowVisibility::folderOnly: for (auto* window : windows) { part_view = qobject_cast(window); Q_ASSERT(part_view); if (part_view->part()->folder() == m_currentFolder) part_view->show(); else part_view->hide(); } break; case Project::MdiWindowVisibility::folderAndSubfolders: for (auto* window : windows) { part_view = qobject_cast(window); if (part_view->part()->isDescendantOf(m_currentFolder)) part_view->show(); else part_view->hide(); } break; } } void MainWin::toggleDockWidget(QAction* action) { if (action->objectName() == "toggle_project_explorer_dock") { if (m_projectExplorerDock->isVisible()) m_projectExplorerDock->hide(); // toggleHideWidget(m_projectExplorerDock, true); else m_projectExplorerDock->show(); // toggleShowWidget(m_projectExplorerDock, true); } else if (action->objectName() == "toggle_properties_explorer_dock") { if (m_propertiesDock->isVisible()) m_propertiesDock->hide(); // toggleHideWidget(m_propertiesDock, false); else m_propertiesDock->show(); // toggleShowWidget(m_propertiesDock, false); } } /* void MainWin::toggleHideWidget(QWidget* widget, bool hideToLeft) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=] { const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (hideToLeft) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Backward); timeline->start(); connect(timeline, &QTimeLine::finished, [widget] {widget->hide();}); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } void MainWin::toggleShowWidget(QWidget* widget, bool showToRight) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=]() { if (widget->isHidden()) { widget->show(); } const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (showToRight) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Forward); timeline->start(); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } */ void MainWin::projectExplorerDockVisibilityChanged(bool visible) { m_toggleProjectExplorerDockAction->setChecked(visible); } void MainWin::propertiesDockVisibilityChanged(bool visible) { m_togglePropertiesDockAction->setChecked(visible); } void MainWin::cursorDockVisibilityChanged(bool visible) { //if the cursor dock was closed, switch to the "Select and Edit" mouse mode if (!visible) { // auto* worksheet = activeWorksheet(); //TODO: } } void MainWin::cartesianPlotMouseModeChanged(CartesianPlot::MouseMode mode) { if (mode != CartesianPlot::Cursor) { if (cursorDock) cursorDock->hide(); } else { if (!cursorDock) { cursorDock = new QDockWidget(i18n("Cursor"), this); cursorWidget = new CursorDock(cursorDock); cursorDock->setWidget(cursorWidget); connect(cursorDock, &QDockWidget::visibilityChanged, this, &MainWin::cursorDockVisibilityChanged); // cursorDock->setFloating(true); // does not work. Don't understand why // if (m_propertiesDock) // tabifyDockWidget(cursorDock, m_propertiesDock); // else addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, cursorDock); } auto* worksheet = static_cast(QObject::sender()); cursorWidget->setWorksheet(worksheet); cursorDock->show(); } } void MainWin::toggleFullScreen() { if (this->windowState() == Qt::WindowFullScreen) this->setWindowState(m_lastWindowState); else { m_lastWindowState = this->windowState(); this->showFullScreen(); } } void MainWin::closeEvent(QCloseEvent* event) { m_closing = true; if (!this->closeProject()) { m_closing = false; event->ignore(); } } void MainWin::dragEnterEvent(QDragEnterEvent* event) { event->accept(); } void MainWin::dropEvent(QDropEvent* event) { if (event->mimeData() && !event->mimeData()->urls().isEmpty()) { QUrl url = event->mimeData()->urls().at(0); const QString& f = url.toLocalFile(); bool open = Project::isLabPlotProject(f); #ifdef HAVE_LIBORIGIN if (!open) open = OriginProjectParser::isOriginProject(f); #endif #ifdef HAVE_CANTOR_LIBS if (!open) { QFileInfo fi(f); open = (fi.completeSuffix() == QLatin1String("cws")) || (fi.completeSuffix() == QLatin1String("ipynb")); } #endif if (open) openProject(f); else { if (!m_project) newProject(); importFileDialog(f); } event->accept(); } else event->ignore(); } void MainWin::handleSettingsChanges() { const KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); // if(dynamic_cast(centralWidget()) == nullptr) { // QMdiArea::ViewMode viewMode = QMdiArea::ViewMode(group.readEntry("ViewMode", 0)); // if (m_mdiArea->viewMode() != viewMode) { // m_mdiArea->setViewMode(viewMode); // if (viewMode == QMdiArea::SubWindowView) // this->updateMdiWindowVisibility(); // } // // if (m_mdiArea->viewMode() == QMdiArea::TabbedView) { // m_tileWindowsAction->setVisible(false); // m_cascadeWindowsAction->setVisible(false); // QTabWidget::TabPosition tabPosition = QTabWidget::TabPosition(group.readEntry("TabPosition", 0)); // if (m_mdiArea->tabPosition() != tabPosition) // m_mdiArea->setTabPosition(tabPosition); // } else { // m_tileWindowsAction->setVisible(true); // m_cascadeWindowsAction->setVisible(true); // } // } //autosave bool autoSave = group.readEntry("AutoSave", 0); if (m_autoSaveActive != autoSave) { m_autoSaveActive = autoSave; if (autoSave) m_autoSaveTimer.start(); else m_autoSaveTimer.stop(); } int interval = group.readEntry("AutoSaveInterval", 1); interval *= 60*1000; if (interval != m_autoSaveTimer.interval()) m_autoSaveTimer.setInterval(interval); //show memory info bool showMemoryInfo = group.readEntry(QLatin1String("ShowMemoryInfo"), true); if (m_showMemoryInfo != showMemoryInfo) { m_showMemoryInfo = showMemoryInfo; if (showMemoryInfo) { m_memoryInfoWidget = new MemoryWidget(statusBar()); statusBar()->addPermanentWidget(m_memoryInfoWidget); } else { if (m_memoryInfoWidget) { statusBar()->removeWidget(m_memoryInfoWidget); delete m_memoryInfoWidget; m_memoryInfoWidget = nullptr; } } } //update the units if (stackedWidget) { for (int i = 0; i < stackedWidget->count(); ++i) { auto* widget = stackedWidget->widget(i); BaseDock* dock = dynamic_cast(widget); if (dock) dock->updateUnits(); else { auto* labelWidget = dynamic_cast(widget); if (labelWidget) labelWidget->updateUnits(); } } } bool showWelcomeScreen = group.readEntry(QLatin1String("ShowWelcomeScreen"), true); if(m_showWelcomeScreen != showWelcomeScreen) m_showWelcomeScreen = showWelcomeScreen; } void MainWin::openDatasetExample() { newProject(); // addAspectToProject(m_welcomeScreenHelper->releaseConfiguredSpreadsheet()); } /***************************************************************************************/ /************************************** dialogs ***************************************/ /***************************************************************************************/ /*! shows the dialog with the Undo-history. */ void MainWin::historyDialog() { if (!m_project->undoStack()) return; auto* dialog = new HistoryDialog(this, m_project->undoStack(), m_undoViewEmptyLabel); int index = m_project->undoStack()->index(); if (dialog->exec() != QDialog::Accepted) { if (m_project->undoStack()->count() != 0) m_project->undoStack()->setIndex(index); } //disable undo/redo-actions if the history was cleared //(in both cases, when accepted or rejected in the dialog) if (m_project->undoStack()->count() == 0) { m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } } /*! Opens the dialog to import data to the selected workbook, spreadsheet or matrix */ void MainWin::importFileDialog(const QString& fileName) { DEBUG("MainWin::importFileDialog()"); auto* dlg = new ImportFileDialog(this, false, fileName); // select existing container if (m_currentAspect->type() == AspectType::Spreadsheet || m_currentAspect->type() == AspectType::Matrix || m_currentAspect->type() == AspectType::Workbook) dlg->setCurrentIndex(m_projectExplorer->currentIndex()); else if (m_currentAspect->type() == AspectType::Column && m_currentAspect->parentAspect()->type() == AspectType::Spreadsheet) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importFileDialog() DONE"); } void MainWin::importSqlDialog() { DEBUG("MainWin::importSqlDialog()"); auto* dlg = new ImportSQLDatabaseDialog(this); // select existing container if (m_currentAspect->type() == AspectType::Spreadsheet || m_currentAspect->type() == AspectType::Matrix || m_currentAspect->type() == AspectType::Workbook) dlg->setCurrentIndex(m_projectExplorer->currentIndex()); else if (m_currentAspect->type() == AspectType::Column && m_currentAspect->parentAspect()->type() == AspectType::Spreadsheet) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importSqlDialog() DONE"); } void MainWin::importProjectDialog() { DEBUG("MainWin::importProjectDialog()"); ImportProjectDialog::ProjectType type; if (QObject::sender() == m_importOpjAction) type = ImportProjectDialog::ProjectOrigin; else type = ImportProjectDialog::ProjectLabPlot; auto* dlg = new ImportProjectDialog(this, type); // set current folder dlg->setCurrentFolder(m_currentFolder); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importProjectDialog() DONE"); } /*! * \brief opens a dialog to import datasets */ void MainWin::importDatasetDialog() { ImportDatasetDialog* dlg = new ImportDatasetDialog(this); if (dlg->exec() == QDialog::Accepted) { Spreadsheet* spreadsheet = new Spreadsheet(i18n("Dataset%1", 1)); DatasetHandler* dataset = new DatasetHandler(spreadsheet); dlg->importToDataset(dataset, statusBar()); QTimer timer; timer.setSingleShot(true); QEventLoop loop; connect(dataset, &DatasetHandler::downloadCompleted, &loop, &QEventLoop::quit); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); timer.start(1500); loop.exec(); if(timer.isActive()){ timer.stop(); addAspectToProject(spreadsheet); delete dataset; } else delete dataset; } delete dlg; } /*! opens the dialog for the export of the currently active worksheet, spreadsheet or matrix. */ void MainWin::exportDialog() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = static_cast(win)->part(); if (part->exportView()) statusBar()->showMessage(i18n("%1 exported", part->name())); } void MainWin::editFitsFileDialog() { auto* editDialog = new FITSHeaderEditDialog(this); if (editDialog->exec() == QDialog::Accepted) { if (editDialog->saved()) statusBar()->showMessage(i18n("FITS files saved")); } } /*! adds a new file data source to the current project. */ void MainWin::newLiveDataSourceActionTriggered() { ImportFileDialog* dlg = new ImportFileDialog(this, true); if (dlg->exec() == QDialog::Accepted) { - if (static_cast(dlg->sourceType()) == LiveDataSource::MQTT) { + if (static_cast(dlg->sourceType()) == LiveDataSource::SourceType::MQTT) { #ifdef HAVE_MQTT MQTTClient* mqttClient = new MQTTClient(i18n("MQTT Client%1", 1)); dlg->importToMQTT(mqttClient); mqttClient->setName(mqttClient->clientHostName()); QVector existingClients = m_project->children(AbstractAspect::ChildIndexFlag::Recursive); //doesn't make sense to have more MQTTClients connected to the same broker bool found = false; for (const auto* client : existingClients) { if (client->clientHostName() == mqttClient->clientHostName() && client->clientPort() == mqttClient->clientPort()) { found = true; break; } } if (!found) addAspectToProject(mqttClient); else { delete mqttClient; QMessageBox::warning(this, "Warning", "There already is a MQTTClient with this host!"); } #endif } else { LiveDataSource* dataSource = new LiveDataSource(i18n("Live data source%1", 1), false); dlg->importToLiveDataSource(dataSource, statusBar()); addAspectToProject(dataSource); } } delete dlg; } void MainWin::addAspectToProject(AbstractAspect* aspect) { const QModelIndex& index = m_projectExplorer->currentIndex(); if (index.isValid()) { auto* parent = static_cast(index.internalPointer()); #ifdef HAVE_MQTT //doesn't make sense to add a new MQTTClient to an existing MQTTClient or to any of its successors QString className = parent->metaObject()->className(); MQTTClient* clientAncestor = parent->ancestor(); if (className == "MQTTClient") parent = parent->parentAspect(); else if (clientAncestor != nullptr) parent = clientAncestor->parentAspect(); #endif parent->folder()->addChild(aspect); } else m_project->addChild(aspect); } void MainWin::settingsDialog() { auto* dlg = new SettingsDialog(this); connect (dlg, &SettingsDialog::settingsChanged, this, &MainWin::handleSettingsChanges); // connect (dlg, &SettingsDialog::resetWelcomeScreen, this, &MainWin::resetWelcomeScreen); dlg->exec(); } #ifdef HAVE_CANTOR_LIBS void MainWin::cantorSettingsDialog() { static KCoreConfigSkeleton* emptyConfig = new KCoreConfigSkeleton(); KConfigDialog* cantorDialog = new KConfigDialog(this, QLatin1String("Cantor Settings"), emptyConfig); for (auto* backend : Cantor::Backend::availableBackends()) if (backend->config()) //It has something to configure, so add it to the dialog cantorDialog->addPage(backend->settingsWidget(cantorDialog), backend->config(), backend->name(), backend->icon()); cantorDialog->show(); } #endif diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index f6274085f..76d097064 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2195 +1,2195 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * 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 "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/filters.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include "JsonOptionsWidget.h" #include "ROOTOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include "MQTTConnectionManagerDialog.h" #include "MQTTSubscriptionWidget.h" #include #include #include #include #include #include #include #endif QString absolutePath(const QString& fileName) { #ifndef HAVE_WINDOWS // make absolute path // FIXME if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) return QDir::homePath() + QDir::separator() + fileName; #endif return fileName; } /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, bool liveDataSource, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_liveDataSource(liveDataSource) #ifdef HAVE_MQTT , m_connectTimeoutTimer(new QTimer(this)), m_subscriptionWidget(new MQTTSubscriptionWidget(this)) #endif { ui.setupUi(this); //add supported file types if (!liveDataSource) { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); ui.cbFileType->addItem(i18n("Image"), AbstractFileFilter::Image); #ifdef HAVE_HDF5 ui.cbFileType->addItem(i18n("Hierarchical Data Format 5 (HDF5)"), AbstractFileFilter::HDF5); #endif #ifdef HAVE_NETCDF ui.cbFileType->addItem(i18n("Network Common Data Format (NetCDF)"), AbstractFileFilter::NETCDF); #endif #ifdef HAVE_FITS ui.cbFileType->addItem(i18n("Flexible Image Transport System Data Format (FITS)"), AbstractFileFilter::FITS); #endif ui.cbFileType->addItem(i18n("JSON data"), AbstractFileFilter::JSON); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); //hide widgets relevant for live data reading only ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.gbUpdateOptions->hide(); } else { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); ui.chbLinkFile->setToolTip(i18n("If this option is checked, only the link to the file is stored in the project file but not its content.")); ui.chbRelativePath->setToolTip(i18n("If this option is checked, the relative path of the file (relative to project's folder) will be saved.")); #ifdef HAVE_MQTT m_connectTimeoutTimer->setInterval(6000); #endif } QStringList filterItems {i18n("Automatic"), i18n("Custom")}; ui.cbFilter->addItems(filterItems); //hide options that will be activated on demand ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); setMQTTVisible(false); - ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); + ui.cbReadingType->addItem(i18n("Whole file"), static_cast(LiveDataSource::ReadingType::WholeFile)); ui.bOpen->setIcon( QIcon::fromTheme(QLatin1String("document-open")) ); ui.bFileInfo->setIcon( QIcon::fromTheme(QLatin1String("help-about")) ); ui.bManageFilters->setIcon( QIcon::fromTheme(QLatin1String("configure")) ); ui.bSaveFilter->setIcon( QIcon::fromTheme(QLatin1String("document-save")) ); ui.bRefreshPreview->setIcon( QIcon::fromTheme(QLatin1String("view-refresh")) ); ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.tvJson->setAlternatingRowColors(true); showJsonModel(false); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); auto* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // the combobox for the import path m_cbFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, this); m_cbFileName->setMaxItems(7); auto* gridLayout = dynamic_cast(ui.gbDataSource->layout()); if (gridLayout) gridLayout->addWidget(m_cbFileName, 1, 2, 1, 3); //tooltips QString info = i18n("Specify how the data source has to be processed on every read:" "
    " "
  • Continuously fixed - fixed amount of samples is processed starting from the beginning of the newly received data.
  • " "
  • From End - fixed amount of samples is processed starting from the end of the newly received data.
  • " "
  • Till the End - all newly received data is processed.
  • " "
  • Whole file - on every read the whole file is re-read completely and processed. Only available for \"File Or Named Pipe\" data sources.
  • " "
"); ui.lReadingType->setToolTip(info); ui.cbReadingType->setToolTip(info); info = i18n("Number of samples (lines) to be processed on every read.\n" "Only needs to be specified for the reading mode \"Continuously Fixed\" and \"From End\"."); ui.lSampleSize->setToolTip(info); ui.sbSampleSize->setToolTip(info); info = i18n("Specify when and how frequently the data source needs to be read:" "
    " "
  • Periodically - the data source is read periodically with user specified time interval.
  • " "
  • On New Data - the data source is read when new data arrives.
  • " "
"); ui.lUpdateType->setToolTip(info); ui.cbUpdateType->setToolTip(info); info = i18n("Specify how frequently the data source has to be read."); ui.lUpdateInterval->setToolTip(info); ui.sbUpdateInterval->setToolTip(info); info = i18n("Specify how many samples need to be kept in memory after reading.\n" "Use \"All\" if all data has to be kept."); ui.lKeepLastValues->setToolTip(info); ui.sbKeepNValues->setToolTip(info); #ifdef HAVE_MQTT ui.cbSourceType->addItem(QLatin1String("MQTT")); m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QLatin1String("MQTT_connections"); //add subscriptions widget layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_subscriptionWidget); ui.frameSubscriptions->setLayout(layout); ui.bManageConnections->setIcon(QIcon::fromTheme(QLatin1String("network-server"))); ui.bManageConnections->setToolTip(i18n("Manage MQTT connections")); info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings AbstractFileFilter::FileType fileType = static_cast(conf.readEntry("Type", 0)); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } if (m_fileName.isEmpty()) { ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); m_cbFileName->setUrl(conf.readEntry("LastImportedFile", "")); QStringList urls = m_cbFileName->urls(); urls.append(conf.readXdgListEntry("LastImportedFiles")); m_cbFileName->setUrls(urls); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed } else m_cbFileName->setUrl(QUrl(m_fileName)); ui.sbPreviewLines->setValue(conf.readEntry("PreviewLines", 100)); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate", 13)); // index for bautrate 19200b/s - ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType", (int)LiveDataSource::WholeFile)); + ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType", static_cast(LiveDataSource::ReadingType::WholeFile))); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); - ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType", (int)LiveDataSource::NewData)); + ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType", static_cast(LiveDataSource::UpdateType::NewData))); updateTypeChanged(ui.cbUpdateType->currentIndex()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues", 0)); // keep all values ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleSize->setValue(conf.readEntry("SampleSize", 1)); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval", 1000)); ui.chbLinkFile->setCheckState((Qt::CheckState)conf.readEntry("LinkFile", (int)Qt::CheckState::Unchecked)); ui.chbRelativePath->setCheckState((Qt::CheckState)conf.readEntry("RelativePath", (int)Qt::CheckState::Unchecked)); #ifdef HAVE_MQTT //read available MQTT connections m_initialisingMQTT = true; readMQTTConnections(); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conf.readEntry("Connection", ""))); m_initialisingMQTT = false; m_willSettings.enabled = conf.readEntry("mqttWillEnabled", m_willSettings.enabled); m_willSettings.willRetain = conf.readEntry("mqttWillRetain", m_willSettings.willRetain); m_willSettings.willUpdateType = static_cast(conf.readEntry("mqttWillUpdateType", (int)m_willSettings.willUpdateType)); m_willSettings.willMessageType = static_cast(conf.readEntry("mqttWillMessageType", (int)m_willSettings.willMessageType)); m_willSettings.willQoS = conf.readEntry("mqttWillQoS", (int)m_willSettings.willQoS); m_willSettings.willOwnMessage = conf.readEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); m_willSettings.willTimeInterval = conf.readEntry("mqttWillUpdateInterval", m_willSettings.willTimeInterval); const QString& willStatistics = conf.readEntry("mqttWillStatistics",""); const QStringList& statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts); for (auto value : statisticsList) m_willSettings.willStatistics[value.toInt()] = true; #endif //initialize the slots after all settings were set in order to avoid unneeded refreshes initSlots(); //update the status of the widgets fileTypeChanged(fileType); - sourceTypeChanged(currentSourceType()); + sourceTypeChanged(static_cast(currentSourceType())); readingTypeChanged(ui.cbReadingType->currentIndex()); //all set now, refresh the content of the file and the preview for the selected dataset m_suppressRefresh = false; QTimer::singleShot(100, this, [=] () { WAIT_CURSOR; - if (currentSourceType() == LiveDataSource::FileOrPipe) { + if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) { QString tempFileName = fileName(); const QString& fileName = absolutePath(tempFileName); if (QFile::exists(fileName)) updateContent(fileName); } refreshPreview(); RESET_CURSOR; }); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", (int)currentFileType()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", m_cbFileName->currentText()); conf.writeXdgListEntry("LastImportedFiles", m_cbFileName->urls()); conf.writeEntry("PreviewLines", ui.sbPreviewLines->value()); //live data related settings conf.writeEntry("SourceType", (int)currentSourceType()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex()); conf.writeEntry("SampleSize", ui.sbSampleSize->value()); conf.writeEntry("KeepNValues", ui.sbKeepNValues->value()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); conf.writeEntry("LinkFile", (int)ui.chbLinkFile->checkState()); conf.writeEntry("RelativePath", (int)ui.chbRelativePath->checkState()); #ifdef HAVE_MQTT delete m_connectTimeoutTimer; delete m_subscriptionWidget; //MQTT related settings conf.writeEntry("Connection", ui.cbConnection->currentText()); conf.writeEntry("mqttWillMessageType", static_cast(m_willSettings.willMessageType)); conf.writeEntry("mqttWillUpdateType", static_cast(m_willSettings.willUpdateType)); conf.writeEntry("mqttWillQoS", QString::number(m_willSettings.willQoS)); conf.writeEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); conf.writeEntry("mqttWillUpdateInterval", QString::number(m_willSettings.willTimeInterval)); QString willStatistics; for (int i = 0; i < m_willSettings.willStatistics.size(); ++i) { if (m_willSettings.willStatistics[i]) willStatistics += QString::number(i)+ QLatin1Char('|'); } conf.writeEntry("mqttWillStatistics", willStatistics); conf.writeEntry("mqttWillRetain", static_cast(m_willSettings.willRetain)); conf.writeEntry("mqttWillUse", static_cast(m_willSettings.enabled)); #endif // data type specific settings if (m_asciiOptionsWidget) m_asciiOptionsWidget->saveSettings(); if (m_binaryOptionsWidget) m_binaryOptionsWidget->saveSettings(); if (m_imageOptionsWidget) m_imageOptionsWidget->saveSettings(); if (m_jsonOptionsWidget) m_jsonOptionsWidget->saveSettings(); } void ImportFileWidget::initSlots() { //SLOTs for the general part of the data source configuration connect(ui.cbSourceType, static_cast(&QComboBox::currentIndexChanged), this, static_cast(&ImportFileWidget::sourceTypeChanged)); connect(m_cbFileName, &KUrlComboBox::urlActivated, this, [=](const QUrl &url){fileNameChanged(url.path());}); connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::hostChanged); connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::portChanged); connect(ui.tvJson, &QTreeView::clicked, this, &ImportFileWidget::refreshPreview); connect(ui.bOpen, &QPushButton::clicked, this, &ImportFileWidget::selectFile); connect(ui.bFileInfo, &QPushButton::clicked, this, &ImportFileWidget::fileInfoDialog); connect(ui.bSaveFilter, &QPushButton::clicked, this, &ImportFileWidget::saveFilter); connect(ui.bManageFilters, &QPushButton::clicked, this, &ImportFileWidget::manageFilters); connect(ui.cbFileType, static_cast(&KComboBox::currentIndexChanged), this, &ImportFileWidget::fileTypeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::readingTypeChanged); connect(ui.cbFilter, static_cast(&KComboBox::activated), this, &ImportFileWidget::filterChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, this, &ImportFileWidget::refreshPreview); #ifdef HAVE_MQTT connect(ui.cbConnection, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::mqttConnectionChanged); connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() { emit checkFileType(); }); connect(ui.bManageConnections, &QPushButton::clicked, this, &ImportFileWidget::showMQTTConnectionManager); connect(ui.bLWT, &QPushButton::clicked, this, &ImportFileWidget::showWillSettings); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, this, &ImportFileWidget::mqttSubscribe); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::MQTTUnsubscribeFromTopic, this, &ImportFileWidget::unsubscribeFromTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &ImportFileWidget::enableWill); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::subscriptionChanged, this, &ImportFileWidget::refreshPreview); #endif } void ImportFileWidget::showAsciiHeaderOptions(bool b) { if (m_asciiOptionsWidget) m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showJsonModel(bool b) { ui.tvJson->setVisible(b); ui.lField->setVisible(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { DEBUG("ImportFileWidget::fileName() : " << STDSTRING(m_cbFileName->currentText())) return m_cbFileName->currentText(); } QString ImportFileWidget::selectedObject() const { const QString& path = fileName(); //determine the file name only QString name = path.right(path.length() - path.lastIndexOf('/') - 1); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); //for multi-dimensional formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedNames(); if (hdf5Names.size()) name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/' } else if (format == AbstractFileFilter::NETCDF) { const QStringList& names = m_netcdfOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } else if (format == AbstractFileFilter::FITS) { const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; } else if (format == AbstractFileFilter::ROOT) { const QStringList& names = m_rootOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } return name; } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { AbstractFileFilter::FileType fileType = currentFileType(); auto updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = currentSourceType(); auto readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( fileName() ); source->setFileType(fileType); currentFileFilter(); source->setFilter(m_currentFilter.release()); // pass ownership of the filter to the LiveDataSource source->setSourceType(sourceType); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName(fileName()); source->setFileLinked(ui.chbLinkFile->isChecked()); if (m_liveDataSource) source->setUseRelativePath(ui.chbRelativePath->isChecked()); break; case LiveDataSource::SourceType::LocalSocket: source->setFileName(fileName()); source->setLocalSocketName(fileName()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; case LiveDataSource::SourceType::MQTT: break; default: break; } //reading options source->setReadingType(readingType); source->setKeepNValues(ui.sbKeepNValues->value()); source->setUpdateType(updateType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); if (readingType != LiveDataSource::ReadingType::TillEnd) source->setSampleSize(ui.sbSampleSize->value()); } #ifdef HAVE_MQTT /*! saves the settings to the MQTTClient \c client. */ void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const { DEBUG("ImportFileWidget::saveMQTTSettings"); MQTTClient::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); MQTTClient::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); client->setComment(fileName()); currentFileFilter(); client->setFilter(static_cast(m_currentFilter.release())); // pass ownership of the filter to MQTTClient client->setReadingType(readingType); if (updateType == MQTTClient::UpdateType::TimeInterval) client->setUpdateInterval(ui.sbUpdateInterval->value()); client->setKeepNValues(ui.sbKeepNValues->value()); client->setUpdateType(updateType); if (readingType != MQTTClient::ReadingType::TillEnd) client->setSampleSize(ui.sbSampleSize->value()); client->setMQTTClientHostPort(m_client->hostname(), m_client->port()); KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); bool useID = group.readEntry("UseID").toUInt(); bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); client->setMQTTUseAuthentication(useAuthentication); if (useAuthentication) client->setMQTTClientAuthentication(m_client->username(), m_client->password()); client->setMQTTUseID(useID); if (useID) client->setMQTTClientId(m_client->clientId()); for (int i = 0; i < m_mqttSubscriptions.count(); ++i) client->addInitialMQTTSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); const bool retain = group.readEntry("Retain").toUInt(); client->setMQTTRetain(retain); if (m_willSettings.enabled) client->setWillSettings(m_willSettings); } #endif /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentData().toInt()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = currentFileType(); if (m_currentFilter && m_currentFilter->type() != fileType) m_currentFilter.reset(); switch (fileType) { case AbstractFileFilter::Ascii: { DEBUG(" ASCII"); if (!m_currentFilter) m_currentFilter.reset(new AsciiFilter); auto filter = static_cast(m_currentFilter.get()); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_asciiOptionsWidget) m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings(ui.cbFilter->currentText()); //save the data portion to import filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::Binary: { DEBUG(" Binary"); if (!m_currentFilter) m_currentFilter.reset(new BinaryFilter); auto filter = static_cast(m_currentFilter.get()); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_binaryOptionsWidget) m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::Image: { DEBUG(" Image"); if (!m_currentFilter) m_currentFilter.reset(new ImageFilter); auto filter = static_cast(m_currentFilter.get()); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::currentFileFilter(): HDF5"); if (!m_currentFilter) m_currentFilter.reset(new HDF5Filter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedHDF5Names(); QDEBUG("ImportFileWidget::currentFileFilter(): selected HDF5 names =" << names); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); DEBUG("ImportFileWidget::currentFileFilter(): OK"); break; } case AbstractFileFilter::NETCDF: { DEBUG(" NETCDF"); if (!m_currentFilter) m_currentFilter.reset(new NetCDFFilter); auto filter = static_cast(m_currentFilter.get()); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::FITS: { DEBUG(" FITS"); if (!m_currentFilter) m_currentFilter.reset(new FITSFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::JSON: { DEBUG(" JSON"); if (!m_currentFilter) m_currentFilter.reset(new JsonFilter); auto filter = static_cast(m_currentFilter.get()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::ROOT: { DEBUG(" ROOT"); if (!m_currentFilter) m_currentFilter.reset(new ROOTFilter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedROOTNames(); if (!names.isEmpty()) filter->setCurrentObject(names.first()); filter->setStartRow(m_rootOptionsWidget->startRow()); filter->setEndRow(m_rootOptionsWidget->endRow()); filter->setColumns(m_rootOptionsWidget->columns()); break; } case AbstractFileFilter::NgspiceRawAscii: { DEBUG(" NgspiceRawAscii"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawAsciiFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::NgspiceRawBinary: { DEBUG(" NgspiceRawBinary"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawBinaryFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } } return m_currentFilter.get(); } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { DEBUG("ImportFileWidget::selectFile()") KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("ImportFileWidget")); const QString& dir = conf.readEntry(QLatin1String("LastDir"), ""); const QString& path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); DEBUG(" dir = " << STDSTRING(dir)) DEBUG(" path = " << STDSTRING(path)) if (path.isEmpty()) //cancel was clicked in the file-dialog return; int pos = path.lastIndexOf('/'); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry(QLatin1String("LastDir"), newDir); } //process all events after the FileDialog was closed to repaint the widget //before we start calculating the preview QApplication::processEvents(QEventLoop::AllEvents, 0); QStringList urls = m_cbFileName->urls(); urls.insert(0, QUrl::fromLocalFile(path).url()); // add type of path m_cbFileName->setUrls(urls); m_cbFileName->setCurrentText(urls.first()); DEBUG(" combobox text = " << STDSTRING(m_cbFileName->currentText())) fileNameChanged(path); // why do I have to call this function separately } /*! hides the MQTT related items of the widget */ void ImportFileWidget::setMQTTVisible(bool visible) { ui.lConnections->setVisible(visible); ui.cbConnection->setVisible(visible); ui.bManageConnections->setVisible(visible); //topics if (ui.cbConnection->currentIndex() != -1 && visible) { ui.lTopics->setVisible(true); ui.frameSubscriptions->setVisible(true); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); #endif } else { ui.lTopics->setVisible(false); ui.frameSubscriptions->setVisible(false); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(false); m_subscriptionWidget->makeVisible(false); #endif } //will message ui.lLWT->setVisible(visible); ui.bLWT->setVisible(visible); } #ifdef HAVE_MQTT /*! * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic, * returns \c false otherwise. */ bool ImportFileWidget::isMqttValid() { if (!m_client) return false; bool connected = (m_client->state() == QMqttClient::ClientState::Connected); bool subscribed = (m_subscriptionWidget->subscriptionCount() > 0); bool fileTypeOk = false; if (this->currentFileType() == AbstractFileFilter::FileType::Ascii) fileTypeOk = true; return connected && subscribed && fileTypeOk; } /*! *\brief Unsubscribes from the given topic, and removes any data connected to it * * \param topicName the name of a topic we want to unsubscribe from */ void ImportFileWidget::unsubscribeFromTopic(const QString& topicName, QVector children) { if (topicName.isEmpty()) return; QMqttTopicFilter filter{topicName}; m_client->unsubscribe(filter); for (int i = 0; i< m_mqttSubscriptions.count(); ++i) if (m_mqttSubscriptions[i]->topic().filter() == topicName) { m_mqttSubscriptions.remove(i); break; } QMapIterator j(m_lastMessage); while (j.hasNext()) { j.next(); if (MQTTSubscriptionWidget::checkTopicContains(topicName, j.key().name())) m_lastMessage.remove(j.key()); } for (int i = 0; i < m_subscribedTopicNames.size(); ++i) { if (MQTTSubscriptionWidget::checkTopicContains(topicName, m_subscribedTopicNames[i])) { m_subscribedTopicNames.remove(i); i--; } } if (m_willSettings.willTopic == topicName) { if (m_subscriptionWidget->subscriptionCount() > 0) m_willSettings.willTopic = children[0]->text(0); else m_willSettings.willTopic.clear(); } //signals that there was a change among the subscribed topics emit subscriptionsChanged(); refreshPreview(); } #endif /************** SLOTS **************************************************************/ /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { DEBUG("ImportFileWidget::fileNameChanged() : " << STDSTRING(name)) const QString fileName = absolutePath(name); bool fileExists = QFile::exists(fileName); if (fileExists) m_cbFileName->setStyleSheet(QString()); else m_cbFileName->setStyleSheet("QComboBox{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.gbUpdateOptions->setEnabled(fileExists); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tePreview->clear(); m_twPreview->clear(); initOptionsWidget(); emit fileNameChanged(); return; } - if (currentSourceType() == LiveDataSource::FileOrPipe) { + if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) { const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { // automatically select a new file type if (ui.cbFileType->currentIndex() != i) { ui.cbFileType->setCurrentIndex(i); // will call the slot fileTypeChanged which updates content and preview //automatically set the comma separator if a csv file was selected if (fileType == AbstractFileFilter::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive)) m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(',')); emit fileNameChanged(); return; } else { initOptionsWidget(); //automatically set the comma separator if a csv file was selected if (fileType == AbstractFileFilter::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive)) m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(',')); updateContent(fileName); break; } } } } emit fileNameChanged(); refreshPreview(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined filter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int index) { Q_UNUSED(index); AbstractFileFilter::FileType fileType = currentFileType(); DEBUG("ImportFileWidget::fileTypeChanged " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); initOptionsWidget(); //default ui.lFilter->show(); ui.cbFilter->show(); //different file types show different number of tabs in ui.tabWidget. //when switching from the previous file type we re-set the tab widget to its original state //and remove/add the required tabs further below for (int i = 0; icount(); ++i) ui.tabWidget->removeTab(0); ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); if (!m_liveDataSource) ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); ui.lPreviewLines->show(); ui.sbPreviewLines->show(); ui.lStartColumn->show(); ui.sbStartColumn->show(); ui.lEndColumn->show(); ui.sbEndColumn->show(); showJsonModel(false); switch (fileType) { case AbstractFileFilter::Ascii: break; case AbstractFileFilter::Binary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; case AbstractFileFilter::ROOT: ui.tabWidget->removeTab(1); // falls through case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: ui.lFilter->hide(); ui.cbFilter->hide(); // hide global preview tab. we have our own ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Image: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lPreviewLines->hide(); ui.sbPreviewLines->hide(); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); ui.tabWidget->removeTab(0); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::JSON: ui.lFilter->hide(); ui.cbFilter->hide(); showJsonModel(true); break; default: DEBUG("unknown file type"); } int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); - if (currentSourceType() == LiveDataSource::FileOrPipe) { + if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) { QString tempFileName = fileName(); const QString& fileName = absolutePath(tempFileName); if (QFile::exists(fileName)) updateContent(fileName); } //for file types other than ASCII and binary we support re-reading the whole file only //select "read whole file" and deactivate the combobox if (m_liveDataSource && (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary)) { - ui.cbReadingType->setCurrentIndex(LiveDataSource::ReadingType::WholeFile); + ui.cbReadingType->setCurrentIndex(static_cast(LiveDataSource::ReadingType::WholeFile)); ui.cbReadingType->setEnabled(false); } else ui.cbReadingType->setEnabled(true); refreshPreview(); } // file type specific option widgets void ImportFileWidget::initOptionsWidget() { DEBUG("ImportFileWidget::initOptionsWidget for " << ENUM_TO_STRING(AbstractFileFilter, FileType, currentFileType())); switch (currentFileType()) { case AbstractFileFilter::Ascii: { if (!m_asciiOptionsWidget) { QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); m_asciiOptionsWidget->loadSettings(); ui.swOptions->addWidget(asciiw); } //for MQTT topics we don't allow to set the vector names since the different topics //can have different number of columns - bool isMQTT = (currentSourceType() == LiveDataSource::MQTT); + bool isMQTT = (currentSourceType() == LiveDataSource::SourceType::MQTT); m_asciiOptionsWidget->showAsciiHeaderOptions(!isMQTT); m_asciiOptionsWidget->showTimestampOptions(isMQTT); ui.swOptions->setCurrentWidget(m_asciiOptionsWidget->parentWidget()); break; } case AbstractFileFilter::Binary: if (!m_binaryOptionsWidget) { QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->addWidget(binaryw); m_binaryOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_binaryOptionsWidget->parentWidget()); break; case AbstractFileFilter::Image: if (!m_imageOptionsWidget) { QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->addWidget(imagew); m_imageOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_imageOptionsWidget->parentWidget()); break; case AbstractFileFilter::HDF5: if (!m_hdf5OptionsWidget) { QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->addWidget(hdf5w); } else m_hdf5OptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_hdf5OptionsWidget->parentWidget()); break; case AbstractFileFilter::NETCDF: if (!m_netcdfOptionsWidget) { QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); } else m_netcdfOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_netcdfOptionsWidget->parentWidget()); break; case AbstractFileFilter::FITS: if (!m_fitsOptionsWidget) { QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->addWidget(fitsw); } else m_fitsOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_fitsOptionsWidget->parentWidget()); break; case AbstractFileFilter::JSON: if (!m_jsonOptionsWidget) { QWidget* jsonw = new QWidget(); m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); ui.tvJson->setModel(m_jsonOptionsWidget->model()); ui.swOptions->addWidget(jsonw); m_jsonOptionsWidget->loadSettings(); } else m_jsonOptionsWidget->clearModel(); ui.swOptions->setCurrentWidget(m_jsonOptionsWidget->parentWidget()); showJsonModel(true); break; case AbstractFileFilter::ROOT: if (!m_rootOptionsWidget) { QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->addWidget(rootw); } else m_rootOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_rootOptionsWidget->parentWidget()); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedExtensions(); } const QStringList ImportFileWidget::selectedROOTNames() const { return m_rootOptionsWidget->selectedNames(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = fileName().split(';'); auto* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats AbstractFileFilter::FileType fileType = currentFileType(); if (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary) { ui.swOptions->setEnabled(true); return; } if (index == 0) { // "automatic" ui.swOptions->setEnabled(false); ui.bSaveFilter->setEnabled(false); } else if (index == 1) { //custom ui.swOptions->setEnabled(true); ui.bSaveFilter->setEnabled(true); } else { // predefined filter settings were selected. //load and show them in the GUI. //TODO } } void ImportFileWidget::refreshPreview() { if (m_suppressRefresh) return; DEBUG("ImportFileWidget::refreshPreview()"); WAIT_CURSOR; QString tempFileName = fileName(); QString fileName = absolutePath(tempFileName); AbstractFileFilter::FileType fileType = currentFileType(); LiveDataSource::SourceType sourceType = currentSourceType(); int lines = ui.sbPreviewLines->value(); if (sourceType == LiveDataSource::SourceType::FileOrPipe) DEBUG(" file name = " << STDSTRING(fileName)); // generic table widget if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary || fileType == AbstractFileFilter::JSON || fileType == AbstractFileFilter::NgspiceRawAscii || fileType == AbstractFileFilter::NgspiceRawBinary) m_twPreview->show(); else m_twPreview->hide(); bool ok = true; QTableWidget* tmpTableWidget = m_twPreview; QVector importedStrings; QStringList vectorNameList; QVector columnModes; DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); switch (fileType) { case AbstractFileFilter::Ascii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, sourceType)); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: { importedStrings = filter->preview(fileName, lines); break; } case LiveDataSource::SourceType::LocalSocket: { QLocalSocket lsocket{this}; DEBUG("Local socket: CONNECT PREVIEW"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { DEBUG("connected to local socket " << STDSTRING(fileName)); if (lsocket.waitForReadyRead()) importedStrings = filter->preview(lsocket); DEBUG("Local socket: DISCONNECT PREVIEW"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else DEBUG("failed connect to local socket " << STDSTRING(fileName) << " - " << STDSTRING(lsocket.errorString())); break; } case LiveDataSource::SourceType::NetworkTcpSocket: { QTcpSocket tcpSocket{this}; tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); if (tcpSocket.waitForConnected()) { DEBUG("connected to TCP socket"); if ( tcpSocket.waitForReadyRead() ) importedStrings = filter->preview(tcpSocket); tcpSocket.disconnectFromHost(); } else DEBUG("failed to connect to TCP socket " << " - " << STDSTRING(tcpSocket.errorString())); break; } case LiveDataSource::SourceType::NetworkUdpSocket: { QUdpSocket udpSocket{this}; DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.bind(QHostAddress(host()), port().toInt()); udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); if (udpSocket.waitForConnected()) { DEBUG(" connected to UDP socket " << STDSTRING(host()) << ':' << port().toInt()); if (!udpSocket.waitForReadyRead(2000) ) DEBUG(" ERROR: not ready for read after 2 sec"); if (udpSocket.hasPendingDatagrams()) { DEBUG(" has pending data"); } else { DEBUG(" has no pending data"); } importedStrings = filter->preview(udpSocket); DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.disconnectFromHost(); } else DEBUG("failed to connect to UDP socket " << " - " << STDSTRING(udpSocket.errorString())); break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; DEBUG(" Port: " << STDSTRING(serialPort()) << ", Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() << ',' << sPort.stopBits()); sPort.setPortName(serialPort()); sPort.setBaudRate(baudRate()); if (sPort.open(QIODevice::ReadOnly)) { if (sPort.waitForReadyRead(2000)) importedStrings = filter->preview(sPort); else DEBUG(" ERROR: not ready for read after 2 sec"); sPort.close(); } else DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT //show the preview for the currently selected topic auto* item = m_subscriptionWidget->currentItem(); if (item && item->childCount() == 0) { //only preview if the lowest level (i.e. a topic) is selected const QString& topicName = item->text(0); auto i = m_lastMessage.find(topicName); if (i != m_lastMessage.end()) importedStrings = filter->preview(i.value().payload().data()); else importedStrings << QStringList{i18n("No data arrived yet for the selected topic")}; } #endif break; } } vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::Binary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); break; } case AbstractFileFilter::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::refreshPreview: HDF5"); auto filter = static_cast(currentFileFilter()); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, nullptr, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case AbstractFileFilter::NETCDF: { auto filter = static_cast(currentFileFilter()); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, nullptr, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case AbstractFileFilter::FITS: { auto filter = static_cast(currentFileFilter()); lines = m_fitsOptionsWidget->lines(); QString extensionName = m_fitsOptionsWidget->extensionName(&ok); if (!extensionName.isEmpty()) { DEBUG(" extension name = " << STDSTRING(extensionName)); fileName = extensionName; } DEBUG(" file name = " << STDSTRING(fileName)); bool readFitsTableToMatrix; importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); emit checkedFitsTableToMatrix(readFitsTableToMatrix); tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } case AbstractFileFilter::JSON: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); importedStrings = filter->preview(fileName); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::ROOT: { auto filter = static_cast(currentFileFilter()); lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(fileName)); importedStrings = filter->previewCurrentObject( fileName, m_rootOptionsWidget->startRow(), qMin(m_rootOptionsWidget->startRow() + lines - 1, m_rootOptionsWidget->endRow()) ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names vectorNameList = importedStrings.last(); importedStrings.removeLast(); columnModes = QVector(vectorNameList.size(), AbstractColumn::ColumnMode::Numeric); break; } case AbstractFileFilter::NgspiceRawAscii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::NgspiceRawBinary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if ( !importedStrings.isEmpty() ) { if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); auto* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { const int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { auto* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else m_fileEmpty = true; emit previewRefreshed(); RESET_CURSOR; } void ImportFileWidget::updateContent(const QString& fileName) { if (m_suppressRefresh) return; QDEBUG("ImportFileWidget::updateContent(): file name = " << fileName); if (auto filter = currentFileFilter()) { switch (filter->type()) { case AbstractFileFilter::HDF5: m_hdf5OptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::NETCDF: m_netcdfOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::FITS: #ifdef HAVE_FITS m_fitsOptionsWidget->updateContent(static_cast(filter), fileName); #endif break; case AbstractFileFilter::ROOT: m_rootOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::JSON: m_jsonOptionsWidget->loadDocument(fileName); ui.tvJson->setExpanded( m_jsonOptionsWidget->model()->index(0, 0), true); //expand the root node break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } } void ImportFileWidget::updateTypeChanged(int idx) { const auto UpdateType = static_cast(idx); switch (UpdateType) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } } void ImportFileWidget::readingTypeChanged(int idx) { const auto readingType = static_cast(idx); const LiveDataSource::SourceType sourceType = currentSourceType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } if (readingType == LiveDataSource::ReadingType::WholeFile) { ui.lKeepLastValues->hide(); ui.sbKeepNValues->hide(); } else { ui.lKeepLastValues->show(); ui.sbKeepNValues->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { const auto sourceType = static_cast(idx); // enable/disable "on new data"-option const auto* model = qobject_cast(ui.cbUpdateType->model()); - QStandardItem* item = model->item(LiveDataSource::UpdateType::NewData); + QStandardItem* item = model->item(static_cast(LiveDataSource::UpdateType::NewData)); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); m_cbFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); if (m_liveDataSource) { ui.lRelativePath->show(); ui.chbRelativePath->show(); } ui.chbLinkFile->show(); //option for sample size are available for "continuously fixed" and "from end" reading options if (ui.cbReadingType->currentIndex() < 2) { ui.lSampleSize->show(); ui.sbSampleSize->show(); } else { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); fileNameChanged(fileName()); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); m_cbFileName->show(); ui.bFileInfo->hide(); ui.bOpen->show(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::MQTT: #ifdef HAVE_MQTT item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //for MQTT we read ascii data only, hide the file type options for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == AbstractFileFilter::Ascii) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } ui.cbFileType->hide(); ui.lFileType->hide(); ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); setMQTTVisible(true); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); //in case there are already connections defined, //show the available topics for the currently selected connection mqttConnectionChanged(); #endif break; } //deactivate/activate options that are specific to file of pipe sources only auto* typeModel = qobject_cast(ui.cbFileType->model()); - if (sourceType != LiveDataSource::FileOrPipe) { + if (sourceType != LiveDataSource::SourceType::FileOrPipe) { //deactivate file types other than ascii and binary for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (ui.cbFileType->currentIndex() > 1) ui.cbFileType->setCurrentIndex(1); //"whole file" read option is available for file or pipe only, disable it typeModel = qobject_cast(ui.cbReadingType->model()); - QStandardItem* item = typeModel->item(LiveDataSource::WholeFile); + QStandardItem* item = typeModel->item(static_cast(LiveDataSource::ReadingType::WholeFile)); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); - if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) - ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); + if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::ReadingType::WholeFile) + ui.cbReadingType->setCurrentIndex(static_cast(LiveDataSource::ReadingType::TillEnd)); //"update options" groupbox can be deactivated for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. ui.gbUpdateOptions->setEnabled(true); } else { for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //enable "whole file" item for file or pipe typeModel = qobject_cast(ui.cbReadingType->model()); - QStandardItem* item = typeModel->item(LiveDataSource::ReadingType::WholeFile); + QStandardItem* item = typeModel->item(static_cast(LiveDataSource::ReadingType::WholeFile)); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } emit sourceTypeChanged(); refreshPreview(); } #ifdef HAVE_MQTT /*! *\brief called when a different MQTT connection is selected in the connection ComboBox. * connects to the MQTT broker according to the connection settings. */ void ImportFileWidget::mqttConnectionChanged() { if (m_initialisingMQTT || ui.cbConnection->currentIndex() == -1) return; WAIT_CURSOR; //disconnected from the broker that was selected before, if this is the case if (m_client && m_client->state() == QMqttClient::ClientState::Connected) { emit MQTTClearTopics(); disconnect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); QDEBUG("Disconnecting from " << m_client->hostname()); m_client->disconnectFromHost(); delete m_client; } //determine the connection settings for the new broker and initialize the mqtt client KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); m_client = new QMqttClient; connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect); connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived); connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged); m_client->setHostname(group.readEntry("Host")); m_client->setPort(group.readEntry("Port").toUInt()); const bool useID = group.readEntry("UseID").toUInt(); if (useID) m_client->setClientId(group.readEntry("ClientID")); const bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); if (useAuthentication) { m_client->setUsername(group.readEntry("UserName")); m_client->setPassword(group.readEntry("Password")); } //connect to the selected broker QDEBUG("Connect to " << m_client->hostname() << ":" << m_client->port()); m_connectTimeoutTimer->start(); m_client->connectToHost(); } /*! *\brief called when the client connects to the broker successfully. * subscribes to every topic (# wildcard) in order to later list every available topic */ void ImportFileWidget::onMqttConnect() { if (m_client->error() == QMqttClient::NoError) { m_connectTimeoutTimer->stop(); ui.frameSubscriptions->setVisible(true); m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); if (!m_client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); } emit subscriptionsChanged(); RESET_CURSOR; } /*! *\brief called when the client disconnects from the broker successfully * removes every information about the former connection */ void ImportFileWidget::onMqttDisconnect() { DEBUG("Disconnected from " << STDSTRING(m_client->hostname())); m_connectTimeoutTimer->stop(); ui.lTopics->hide(); ui.frameSubscriptions->hide(); ui.lLWT->hide(); ui.bLWT->hide(); ui.cbConnection->setItemText(ui.cbConnection->currentIndex(), ui.cbConnection->currentText() + ' ' + i18n("(Disconnected)")); emit subscriptionsChanged(); RESET_CURSOR; QMessageBox::critical(this, i18n("Disconnected"), i18n("Disconnected from the broker '%1' before the connection was successful.", m_client->hostname())); } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics */ void ImportFileWidget::mqttSubscribe(const QString& name, uint QoS) { const QMqttTopicFilter filter {name}; QMqttSubscription* tempSubscription = m_client->subscribe(filter, static_cast(QoS) ); if (tempSubscription) { m_mqttSubscriptions.push_back(tempSubscription); connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void ImportFileWidget::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message); // qDebug()<<"received " << topic.name(); if (m_addedTopics.contains(topic.name())) return; m_addedTopics.push_back(topic.name()); m_subscriptionWidget->setTopicTreeText(i18n("Available (%1)", m_addedTopics.size())); QStringList name; QString rootName; const QChar sep = '/'; if (topic.name().contains(sep)) { const QStringList& list = topic.name().split(sep, QString::SkipEmptyParts); if (!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); int topItemIdx = -1; //check whether the first level of the topic can be found in twTopics for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if (topItemIdx < 0) { QTreeWidgetItem* currentItem = new QTreeWidgetItem(name); m_subscriptionWidget->addTopic(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { QTreeWidgetItem* currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for (int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if (childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if (!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for (; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topic.name(); name.append(topic.name()); m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); if (!subscriptionName.isEmpty()) { if (rootName == subscriptionName.first()) { QVector subscriptions; for(int i = 0; i < m_mqttSubscriptions.size(); ++i) subscriptions.push_back(m_mqttSubscriptions[i]->topic().filter()); emit updateSubscriptionTree(subscriptions); break; } } } //signals that a newTopic was added, in order to fill the completer of leTopics emit newTopic(rootName); } /*! *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) */ void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { QDEBUG("message received from: " << msg.topic().name()); if (!m_subscribedTopicNames.contains(msg.topic().name())) m_subscribedTopicNames.push_back(msg.topic().name()); //update the last message for the topic m_lastMessage[msg.topic()] = msg; } /*! *\brief called when the clientError of the MQTT client changes * * \param clientError the current error of the client */ void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { switch (clientError) { case QMqttClient::BadUsernameOrPassword: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("Wrong username or password")); break; case QMqttClient::IdRejected: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("The client ID wasn't accepted")); break; case QMqttClient::ServerUnavailable: QMessageBox::critical(this, i18n("Server unavailable"), i18n("The broker couldn't be reached.")); break; case QMqttClient::NotAuthorized: QMessageBox::critical(this, i18n("Not authorized"), i18n("The client is not authorized to connect.")); break; case QMqttClient::UnknownError: QMessageBox::critical(this, i18n("Unknown MQTT error"), i18n("An unknown error occurred.")); break; case QMqttClient::NoError: case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ProtocolViolation: case QMqttClient::Mqtt5SpecificError: break; default: break; } } /*! *\brief called when m_connectTimeoutTimer ticks, * meaning that the client couldn't connect to the broker in 5 seconds * disconnects the client, stops the timer, and warns the user */ void ImportFileWidget::mqttConnectTimeout() { m_connectionTimedOut = true; m_client->disconnectFromHost(); m_connectTimeoutTimer->stop(); RESET_CURSOR; QMessageBox::warning(this, i18n("Warning"), i18n("Connecting to the given broker timed out! Try changing the settings")); } /*! Shows the MQTT connection manager where the connections are created and edited. The selected connection is selected in the connection combo box in this widget. */ void ImportFileWidget::showMQTTConnectionManager() { bool previousConnectionChanged = false; MQTTConnectionManagerDialog* dlg = new MQTTConnectionManagerDialog(this, ui.cbConnection->currentText(), previousConnectionChanged); if (dlg->exec() == QDialog::Accepted) { //re-read the available connections to be in sync with the changes in MQTTConnectionManager m_initialisingMQTT = true; const QString& prevConn = ui.cbConnection->currentText(); ui.cbConnection->clear(); readMQTTConnections(); m_initialisingMQTT = false; //select the connection the user has selected in MQTTConnectionManager const QString& conn = dlg->connection(); int index = ui.cbConnection->findText(conn); if (conn != prevConn) {//Current connection isn't the previous one if (ui.cbConnection->currentIndex() != index) ui.cbConnection->setCurrentIndex(index); else mqttConnectionChanged(); } else if (dlg->initialConnectionChanged()) {//Current connection is the same with previous one but it changed if (ui.cbConnection->currentIndex() == index) mqttConnectionChanged(); else ui.cbConnection->setCurrentIndex(index); } else { //Previous connection wasn't changed m_initialisingMQTT = true; ui.cbConnection->setCurrentIndex(index); m_initialisingMQTT = false; } } delete dlg; } /*! loads all available saved MQTT nconnections */ void ImportFileWidget::readMQTTConnections() { DEBUG("ImportFileWidget: reading available MQTT connections"); KConfig config(m_configPath, KConfig::SimpleConfig); for (const auto& name : config.groupList()) ui.cbConnection->addItem(name); } /*! * \brief Shows the mqtt will settings widget, which allows the user to modify the will settings */ void ImportFileWidget::showWillSettings() { QMenu menu; QVector children; for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) MQTTSubscriptionWidget::findSubscriptionLeafChildren(children, m_subscriptionWidget->topLevelSubscription(i)); QVector topics; for (int i = 0; i < children.size(); ++i) topics.append(children[i]->text(0)); MQTTWillSettingsWidget willSettingsWidget(&menu, m_willSettings, topics); connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() { m_willSettings = willSettingsWidget.will(); menu.close(); }); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettingsWidget); menu.addAction(widgetAction); const QPoint pos(ui.bLWT->sizeHint().width(),ui.bLWT->sizeHint().height()); menu.exec(ui.bLWT->mapToGlobal(pos)); } void ImportFileWidget::enableWill(bool enable) { if(enable) { if(!ui.bLWT->isEnabled()) ui.bLWT->setEnabled(enable); } else ui.bLWT->setEnabled(enable); } #endif diff --git a/src/kdefrontend/datasources/ImportFileWidget.h b/src/kdefrontend/datasources/ImportFileWidget.h index 4a24786d4..a230e698a 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.h +++ b/src/kdefrontend/datasources/ImportFileWidget.h @@ -1,186 +1,186 @@ /*************************************************************************** File : ImportFileWidget.h Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2017 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ #ifndef IMPORTFILEWIDGET_H #define IMPORTFILEWIDGET_H #include "ui_importfilewidget.h" #include "backend/datasources/LiveDataSource.h" #include #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" class MQTTSubscriptionWidget; #endif #include class AbstractFileFilter; class AsciiOptionsWidget; class BinaryOptionsWidget; class HDF5OptionsWidget; class ImageOptionsWidget; class NetCDFOptionsWidget; class FITSOptionsWidget; class JsonOptionsWidget; class ROOTOptionsWidget; class QTableWidget; class QCompleter; class QTimer; class QTreeWidgetItem; class QStringList; class KUrlComboBox; class ImportFileWidget : public QWidget { Q_OBJECT public: explicit ImportFileWidget(QWidget*, bool liveDataSource, const QString& fileName = QString()); ~ImportFileWidget() override; void showOptions(bool); void saveSettings(LiveDataSource*) const; void loadSettings(); AbstractFileFilter::FileType currentFileType() const; LiveDataSource::SourceType currentSourceType() const; AbstractFileFilter* currentFileFilter() const; QString fileName() const; QString selectedObject() const; bool isFileEmpty() const; const QStringList selectedHDF5Names() const; const QStringList selectedNetCDFNames() const; const QStringList selectedFITSExtensions() const; const QStringList selectedROOTNames() const; void showAsciiHeaderOptions(bool); void showJsonModel(bool); QString host() const; QString port() const; QString serialPort() const; int baudRate() const; private: Ui::ImportFileWidget ui; void setMQTTVisible(bool); void updateContent(const QString&); void initOptionsWidget(); void initSlots(); std::unique_ptr m_asciiOptionsWidget; std::unique_ptr m_binaryOptionsWidget; std::unique_ptr m_hdf5OptionsWidget; std::unique_ptr m_imageOptionsWidget; std::unique_ptr m_netcdfOptionsWidget; std::unique_ptr m_fitsOptionsWidget; std::unique_ptr m_jsonOptionsWidget; std::unique_ptr m_rootOptionsWidget; mutable std::unique_ptr m_currentFilter; QTableWidget* m_twPreview{nullptr}; KUrlComboBox* m_cbFileName{nullptr}; const QString& m_fileName; bool m_fileEmpty{false}; bool m_liveDataSource; bool m_suppressRefresh{false}; private slots: void fileNameChanged(const QString&); void fileTypeChanged(int); - void updateTypeChanged(int); void sourceTypeChanged(int); + void updateTypeChanged(int); void readingTypeChanged(int); void saveFilter(); void manageFilters(); void filterChanged(int); void selectFile(); void fileInfoDialog(); void refreshPreview(); signals: void fileNameChanged(); void sourceTypeChanged(); void hostChanged(); void portChanged(); void previewRefreshed(); void checkedFitsTableToMatrix(const bool enable); friend class HDF5OptionsWidget; // to access refreshPreview() friend class NetCDFOptionsWidget; // to access refreshPreview() and others friend class FITSOptionsWidget; friend class JsonOptionsWidget; friend class ROOTOptionsWidget; // to access refreshPreview() and others #ifdef HAVE_MQTT private: void unsubscribeFromTopic(const QString&, QVector children); QMqttClient* m_client{nullptr}; QVector m_mqttSubscriptions; QTimer* m_connectTimeoutTimer; QMap m_lastMessage; QVector m_subscribedTopicNames; QVector m_addedTopics; QString m_configPath; bool m_initialisingMQTT{false}; bool m_connectionTimedOut{false}; MQTTClient::MQTTWill m_willSettings; MQTTSubscriptionWidget* m_subscriptionWidget; public: void saveMQTTSettings(MQTTClient*) const; bool isMqttValid(); signals: void newTopic(const QString&); void subscriptionsChanged(); void checkFileType(); void updateSubscriptionTree(const QVector&); void MQTTClearTopics(); private slots: void mqttConnectionChanged(); void onMqttConnect(); void mqttSubscribe(const QString&, uint); void mqttMessageReceived(const QByteArray&, const QMqttTopicName&); void mqttSubscriptionMessageReceived(const QMqttMessage& ); void onMqttDisconnect(); void mqttErrorChanged(QMqttClient::ClientError); void mqttConnectTimeout(); void showMQTTConnectionManager(); void readMQTTConnections(); void showWillSettings(); void enableWill(bool); #endif }; #endif diff --git a/src/kdefrontend/dockwidgets/AxisDock.cpp b/src/kdefrontend/dockwidgets/AxisDock.cpp index 7e6a245ba..3b9c5d419 100644 --- a/src/kdefrontend/dockwidgets/AxisDock.cpp +++ b/src/kdefrontend/dockwidgets/AxisDock.cpp @@ -1,2224 +1,2224 @@ /*************************************************************************** File : AxisDock.cpp Project : LabPlot Description : axes widget class -------------------------------------------------------------------- Copyright : (C) 2011-2020 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2020 Stefan Gerlach (stefan.gerlach@uni-konstanz.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 "AxisDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/widgets/LabelWidget.h" #include "commonfrontend/widgets/DateTimeSpinBox.h" #include #include #include #include #include extern "C" { #include #include "backend/nsl/nsl_math.h" } /*! \class AxisDock \brief Provides a widget for editing the properties of the axes currently selected in the project explorer. \ingroup kdefrontend */ AxisDock::AxisDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; //"Title"-tab auto* hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget = new LabelWidget(ui.tabTitle); labelWidget->setFixedLabelMode(true); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //"Ticks"-tab auto* layout = static_cast(ui.tabTicks->layout()); cbMajorTicksColumn = new TreeViewComboBox(ui.tabTicks); layout->addWidget(cbMajorTicksColumn, 7, 2); cbMinorTicksColumn = new TreeViewComboBox(ui.tabTicks); layout->addWidget(cbMinorTicksColumn, 21, 2); dtsbMajorTicksIncrement = new DateTimeSpinBox(ui.tabTicks); layout->addWidget(dtsbMajorTicksIncrement, 6, 2); dtsbMinorTicksIncrement = new DateTimeSpinBox(ui.tabTicks); layout->addWidget(dtsbMinorTicksIncrement, 20, 2); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //********************************** Slots ********************************************** //"General"-tab connect(ui.leName, &QLineEdit::textChanged, this, &AxisDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &AxisDock::commentChanged); connect(ui.chkVisible, &QCheckBox::clicked, this, &AxisDock::visibilityChanged); connect(ui.cbOrientation, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::orientationChanged); connect(ui.cbPosition, QOverload::of(&QComboBox::currentIndexChanged), this, QOverload::of(&AxisDock::positionChanged)); connect(ui.lePosition, &QLineEdit::textChanged, this, QOverload<>::of(&AxisDock::positionChanged)); connect(ui.cbScale, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::scaleChanged); connect(ui.chkAutoScale, &QCheckBox::stateChanged, this, &AxisDock::autoScaleChanged); connect(ui.leStart, &QLineEdit::textChanged, this, &AxisDock::startChanged); connect(ui.leEnd, &QLineEdit::textChanged, this, &AxisDock::endChanged); connect(ui.dateTimeEditStart, &QDateTimeEdit::dateTimeChanged, this, &AxisDock::startDateTimeChanged); connect(ui.dateTimeEditEnd, &QDateTimeEdit::dateTimeChanged, this, &AxisDock::endDateTimeChanged); connect(ui.leZeroOffset, &QLineEdit::textChanged, this, &AxisDock::zeroOffsetChanged); connect(ui.leScalingFactor, &QLineEdit::textChanged, this, &AxisDock::scalingFactorChanged); //"Line"-tab connect(ui.cbLineStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::lineStyleChanged); connect(ui.kcbLineColor, &KColorButton::changed, this, &AxisDock::lineColorChanged); connect(ui.sbLineWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::lineWidthChanged); connect(ui.sbLineOpacity, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::lineOpacityChanged); connect(ui.cbArrowPosition, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::arrowPositionChanged); connect(ui.cbArrowType, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::arrowTypeChanged); connect(ui.sbArrowSize, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::arrowSizeChanged); //"Major ticks"-tab connect(ui.cbMajorTicksDirection, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::majorTicksDirectionChanged); connect(ui.cbMajorTicksType, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::majorTicksTypeChanged); connect(ui.sbMajorTicksNumber, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::majorTicksNumberChanged); connect(ui.sbMajorTicksSpacingNumeric, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::majorTicksSpacingChanged); connect(dtsbMajorTicksIncrement, &DateTimeSpinBox::valueChanged, this, &AxisDock::majorTicksSpacingChanged); connect(cbMajorTicksColumn, &TreeViewComboBox::currentModelIndexChanged, this, &AxisDock::majorTicksColumnChanged); connect(ui.cbMajorTicksLineStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::majorTicksLineStyleChanged); connect(ui.kcbMajorTicksColor, &KColorButton::changed, this, &AxisDock::majorTicksColorChanged); connect(ui.sbMajorTicksWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::majorTicksWidthChanged); connect(ui.sbMajorTicksLength, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::majorTicksLengthChanged); connect(ui.sbMajorTicksOpacity, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::majorTicksOpacityChanged); //"Minor ticks"-tab connect(ui.cbMinorTicksDirection, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::minorTicksDirectionChanged); connect(ui.cbMinorTicksType, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::minorTicksTypeChanged); connect(ui.sbMinorTicksNumber, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::minorTicksNumberChanged); connect(ui.sbMinorTicksSpacingNumeric, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::minorTicksSpacingChanged); connect(dtsbMinorTicksIncrement, &DateTimeSpinBox::valueChanged, this, &AxisDock::minorTicksSpacingChanged); connect(cbMinorTicksColumn, &TreeViewComboBox::currentModelIndexChanged, this, &AxisDock::minorTicksColumnChanged); connect(ui.cbMinorTicksLineStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::minorTicksLineStyleChanged); connect(ui.kcbMinorTicksColor, &KColorButton::changed, this, &AxisDock::minorTicksColorChanged); connect(ui.sbMinorTicksWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::minorTicksWidthChanged); connect(ui.sbMinorTicksLength, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::minorTicksLengthChanged); connect(ui.sbMinorTicksOpacity, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::minorTicksOpacityChanged); //"Extra ticks"-tab //"Tick labels"-tab connect(ui.cbLabelsFormat, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::labelsFormatChanged); connect(ui.sbLabelsPrecision, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::labelsPrecisionChanged); connect(ui.chkLabelsAutoPrecision, &QCheckBox::stateChanged, this, &AxisDock::labelsAutoPrecisionChanged); connect(ui.cbLabelsDateTimeFormat, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::labelsDateTimeFormatChanged); connect(ui.cbLabelsPosition, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::labelsPositionChanged); connect(ui.sbLabelsOffset, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::labelsOffsetChanged); connect(ui.sbLabelsRotation, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::labelsRotationChanged); connect(ui.kfrLabelsFont, &KFontRequester::fontSelected, this, &AxisDock::labelsFontChanged); connect(ui.kcbLabelsFontColor, &KColorButton::changed, this, &AxisDock::labelsFontColorChanged); connect(ui.leLabelsPrefix, &QLineEdit::textChanged, this, &AxisDock::labelsPrefixChanged); connect(ui.leLabelsSuffix, &QLineEdit::textChanged, this, &AxisDock::labelsSuffixChanged); connect(ui.sbLabelsOpacity, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::labelsOpacityChanged); //"Grid"-tab connect(ui.cbMajorGridStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::majorGridStyleChanged); connect(ui.kcbMajorGridColor, &KColorButton::changed, this, &AxisDock::majorGridColorChanged); connect(ui.sbMajorGridWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::majorGridWidthChanged); connect(ui.sbMajorGridOpacity, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::majorGridOpacityChanged); connect(ui.cbMinorGridStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &AxisDock::minorGridStyleChanged); connect(ui.kcbMinorGridColor, &KColorButton::changed, this, &AxisDock::minorGridColorChanged); connect(ui.sbMinorGridWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &AxisDock::minorGridWidthChanged); connect(ui.sbMinorGridOpacity, QOverload::of(&QSpinBox::valueChanged), this, &AxisDock::minorGridOpacityChanged); //template handler auto* frame = new QFrame(this); auto* hlayout = new QHBoxLayout(frame); hlayout->setContentsMargins(0, 11, 0, 11); auto* templateHandler = new TemplateHandler(this, TemplateHandler::Axis); hlayout->addWidget(templateHandler); connect(templateHandler, &TemplateHandler::loadConfigRequested, this, &AxisDock::loadConfigFromTemplate); connect(templateHandler, &TemplateHandler::saveConfigRequested, this, &AxisDock::saveConfigAsTemplate); connect(templateHandler, &TemplateHandler::info, this, &AxisDock::info); ui.verticalLayout->addWidget(frame); init(); } AxisDock::~AxisDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } void AxisDock::init() { Lock lock(m_initializing); //Validators ui.lePosition->setValidator( new QDoubleValidator(ui.lePosition) ); ui.leStart->setValidator( new QDoubleValidator(ui.leStart) ); ui.leEnd->setValidator( new QDoubleValidator(ui.leEnd) ); ui.leZeroOffset->setValidator( new QDoubleValidator(ui.leZeroOffset) ); ui.leScalingFactor->setValidator( new QDoubleValidator(ui.leScalingFactor) ); //TODO move this stuff to retranslateUI() ui.cbPosition->addItem(i18n("Top")); ui.cbPosition->addItem(i18n("Bottom")); ui.cbPosition->addItem(i18n("Centered")); ui.cbPosition->addItem(i18n("Custom")); ui.cbScale->addItem( i18n("Linear") ); ui.cbScale->addItem( QLatin1String("log(x)") ); ui.cbScale->addItem( QLatin1String("log2(x)") ); ui.cbScale->addItem( QLatin1String("ln(x)") ); ui.cbScale->addItem( QLatin1String("sqrt(x)") ); ui.cbScale->addItem( QLatin1String("x^2") ); ui.cbOrientation->addItem( i18n("Horizontal") ); ui.cbOrientation->addItem( i18n("Vertical") ); //Arrows ui.cbArrowType->addItem( i18n("No arrow") ); ui.cbArrowType->addItem( i18n("Simple, Small") ); ui.cbArrowType->addItem( i18n("Simple, Big") ); ui.cbArrowType->addItem( i18n("Filled, Small") ); ui.cbArrowType->addItem( i18n("Filled, Big") ); ui.cbArrowType->addItem( i18n("Semi-filled, Small") ); ui.cbArrowType->addItem( i18n("Semi-filled, Big") ); QPainter pa; pa.setPen( QPen(Qt::SolidPattern, 0) ); QPixmap pm(20, 20); ui.cbArrowType->setIconSize( QSize(20,20) ); //no arrow pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3, 10, 17, 10); pa.end(); ui.cbArrowType->setItemIcon(0, pm); //simple, small float cos_phi = cos(3.14159/6); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3, 10, 17, 10); pa.drawLine(17,10, 10, 10-5*cos_phi); pa.drawLine(17,10, 10, 10+5*cos_phi); pa.end(); ui.cbArrowType->setItemIcon(1, pm); //simple, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3, 10, 17, 10); pa.drawLine(17,10, 10, 10-10*cos_phi); pa.drawLine(17,10, 10, 10+10*cos_phi); pa.end(); ui.cbArrowType->setItemIcon(2, pm); //filled, small pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3, 10, 17, 10); QPointF points3[3] = {QPointF(17, 10), QPointF(10, 10-4*cos_phi), QPointF(10, 10+4*cos_phi) }; pa.drawPolygon(points3, 3); pa.end(); ui.cbArrowType->setItemIcon(3, pm); //filled, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3, 10, 17, 10); QPointF points4[3] = {QPointF(17, 10), QPointF(10, 10-10*cos_phi), QPointF(10, 10+10*cos_phi) }; pa.drawPolygon(points4, 3); pa.end(); ui.cbArrowType->setItemIcon(4, pm); //semi-filled, small pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3, 10, 17, 10); QPointF points5[4] = {QPointF(17, 10), QPointF(10, 10-4*cos_phi), QPointF(13, 10), QPointF(10, 10+4*cos_phi) }; pa.drawPolygon(points5, 4); pa.end(); ui.cbArrowType->setItemIcon(5, pm); //semi-filled, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3, 10, 17, 10); QPointF points6[4] = {QPointF(17, 10), QPointF(10, 10-10*cos_phi), QPointF(13, 10), QPointF(10, 10+10*cos_phi) }; pa.drawPolygon(points6, 4); pa.end(); ui.cbArrowType->setItemIcon(6, pm); ui.cbArrowPosition->addItem( i18n("Left") ); ui.cbArrowPosition->addItem( i18n("Right") ); ui.cbArrowPosition->addItem( i18n("Both") ); ui.cbMajorTicksDirection->addItem( i18n("None") ); ui.cbMajorTicksDirection->addItem( i18n("In") ); ui.cbMajorTicksDirection->addItem( i18n("Out") ); ui.cbMajorTicksDirection->addItem( i18n("In and Out") ); ui.cbMajorTicksType->addItem( i18n("Number") ); ui.cbMajorTicksType->addItem( i18n("Spacing") ); ui.cbMajorTicksType->addItem( i18n("Custom column") ); ui.cbMinorTicksDirection->addItem( i18n("None") ); ui.cbMinorTicksDirection->addItem( i18n("In") ); ui.cbMinorTicksDirection->addItem( i18n("Out") ); ui.cbMinorTicksDirection->addItem( i18n("In and Out") ); ui.cbMinorTicksType->addItem( i18n("Number") ); ui.cbMinorTicksType->addItem( i18n("Spacing") ); ui.cbMinorTicksType->addItem( i18n("Custom column") ); GuiTools::updatePenStyles(ui.cbLineStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMajorGridStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMinorGridStyle, QColor(Qt::black)); //labels ui.cbLabelsPosition->addItem(i18n("No labels")); ui.cbLabelsPosition->addItem(i18n("Top")); ui.cbLabelsPosition->addItem(i18n("Bottom")); ui.cbLabelsFormat->addItem( i18n("Decimal notation") ); ui.cbLabelsFormat->addItem( i18n("Scientific notation") ); ui.cbLabelsFormat->addItem( i18n("Powers of 10") ); ui.cbLabelsFormat->addItem( i18n("Powers of 2") ); ui.cbLabelsFormat->addItem( i18n("Powers of e") ); ui.cbLabelsFormat->addItem( i18n("Multiples of π") ); ui.cbLabelsDateTimeFormat->addItems(AbstractColumn::dateTimeFormats()); } void AxisDock::setModel() { QList list{AspectType::Folder, AspectType::Spreadsheet, AspectType::Column}; cbMajorTicksColumn->setTopLevelClasses(list); cbMinorTicksColumn->setTopLevelClasses(list); list = {AspectType::Column}; m_aspectTreeModel->setSelectableAspects(list); cbMajorTicksColumn->setModel(m_aspectTreeModel); cbMinorTicksColumn->setModel(m_aspectTreeModel); } /*! sets the axes. The properties of the axes in the list \c list can be edited in this widget. */ void AxisDock::setAxes(QList list) { Lock lock(m_initializing); m_axesList = list; m_axis = list.first(); m_aspect = list.first(); Q_ASSERT(m_axis != nullptr); m_aspectTreeModel = new AspectTreeModel(m_axis->project()); this->setModel(); labelWidget->setAxes(list); //if there are more then one axis in the list, disable the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_axis->name()); ui.leComment->setText(m_axis->comment()); this->setModelIndexFromColumn(cbMajorTicksColumn, m_axis->majorTicksColumn()); this->setModelIndexFromColumn(cbMinorTicksColumn, m_axis->minorTicksColumn()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); cbMajorTicksColumn->setCurrentModelIndex(QModelIndex()); cbMinorTicksColumn->setCurrentModelIndex(QModelIndex()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first axis this->load(); // general connect(m_axis, &Axis::aspectDescriptionChanged, this, &AxisDock::axisDescriptionChanged); connect(m_axis, &Axis::orientationChanged, this, &AxisDock::axisOrientationChanged); connect(m_axis, QOverload::of(&Axis::positionChanged), this, QOverload::of(&AxisDock::axisPositionChanged)); connect(m_axis, &Axis::scaleChanged, this, &AxisDock::axisScaleChanged); connect(m_axis, &Axis::autoScaleChanged, this, &AxisDock::axisAutoScaleChanged); connect(m_axis, &Axis::startChanged, this, &AxisDock::axisStartChanged); connect(m_axis, &Axis::endChanged, this, &AxisDock::axisEndChanged); connect(m_axis, &Axis::zeroOffsetChanged, this, &AxisDock::axisZeroOffsetChanged); connect(m_axis, &Axis::scalingFactorChanged, this, &AxisDock::axisScalingFactorChanged); // line connect(m_axis, &Axis::linePenChanged, this, &AxisDock::axisLinePenChanged); connect(m_axis, &Axis::lineOpacityChanged, this, &AxisDock::axisLineOpacityChanged); connect(m_axis, &Axis::arrowTypeChanged, this, &AxisDock::axisArrowTypeChanged); connect(m_axis, &Axis::arrowPositionChanged, this, &AxisDock::axisArrowPositionChanged); connect(m_axis, &Axis::arrowSizeChanged, this, &AxisDock::axisArrowSizeChanged); // ticks connect(m_axis, &Axis::majorTicksDirectionChanged, this, &AxisDock::axisMajorTicksDirectionChanged); connect(m_axis, &Axis::majorTicksTypeChanged, this, &AxisDock::axisMajorTicksTypeChanged); connect(m_axis, &Axis::majorTicksNumberChanged, this, &AxisDock::axisMajorTicksNumberChanged); connect(m_axis, &Axis::majorTicksSpacingChanged, this, &AxisDock::axisMajorTicksSpacingChanged); connect(m_axis, &Axis::majorTicksPenChanged, this, &AxisDock::axisMajorTicksPenChanged); connect(m_axis, &Axis::majorTicksLengthChanged, this, &AxisDock::axisMajorTicksLengthChanged); connect(m_axis, &Axis::majorTicksOpacityChanged, this, &AxisDock::axisMajorTicksOpacityChanged); connect(m_axis, &Axis::minorTicksDirectionChanged, this, &AxisDock::axisMinorTicksDirectionChanged); connect(m_axis, &Axis::minorTicksTypeChanged, this, &AxisDock::axisMinorTicksTypeChanged); connect(m_axis, &Axis::minorTicksNumberChanged, this, &AxisDock::axisMinorTicksNumberChanged); connect(m_axis, &Axis::minorTicksIncrementChanged, this, &AxisDock::axisMinorTicksSpacingChanged); connect(m_axis, &Axis::minorTicksPenChanged, this, &AxisDock::axisMinorTicksPenChanged); connect(m_axis, &Axis::minorTicksLengthChanged, this, &AxisDock::axisMinorTicksLengthChanged); connect(m_axis, &Axis::minorTicksOpacityChanged, this, &AxisDock::axisMinorTicksOpacityChanged); // labels connect(m_axis, &Axis::labelsFormatChanged, this, &AxisDock::axisLabelsFormatChanged); connect(m_axis, &Axis::labelsAutoPrecisionChanged, this, &AxisDock::axisLabelsAutoPrecisionChanged); connect(m_axis, &Axis::labelsPrecisionChanged, this, &AxisDock::axisLabelsPrecisionChanged); connect(m_axis, &Axis::labelsDateTimeFormatChanged, this, &AxisDock::axisLabelsDateTimeFormatChanged); connect(m_axis, &Axis::labelsPositionChanged, this, &AxisDock::axisLabelsPositionChanged); connect(m_axis, &Axis::labelsOffsetChanged, this, &AxisDock::axisLabelsOffsetChanged); connect(m_axis, &Axis::labelsRotationAngleChanged, this, &AxisDock::axisLabelsRotationAngleChanged); connect(m_axis, &Axis::labelsFontChanged, this, &AxisDock::axisLabelsFontChanged); connect(m_axis, &Axis::labelsColorChanged, this, &AxisDock::axisLabelsFontColorChanged); connect(m_axis, &Axis::labelsPrefixChanged, this, &AxisDock::axisLabelsPrefixChanged); connect(m_axis, &Axis::labelsSuffixChanged, this, &AxisDock::axisLabelsSuffixChanged); connect(m_axis, &Axis::labelsOpacityChanged, this, &AxisDock::axisLabelsOpacityChanged); // grids connect(m_axis, &Axis::majorGridPenChanged, this, &AxisDock::axisMajorGridPenChanged); connect(m_axis, &Axis::majorGridOpacityChanged, this, &AxisDock::axisMajorGridOpacityChanged); connect(m_axis, &Axis::minorGridPenChanged, this, &AxisDock::axisMinorGridPenChanged); connect(m_axis, &Axis::minorGridOpacityChanged, this, &AxisDock::axisMinorGridOpacityChanged); connect(m_axis, &Axis::visibilityChanged, this, &AxisDock::axisVisibilityChanged); } void AxisDock::activateTitleTab() { ui.tabWidget->setCurrentWidget(ui.tabTitle); } void AxisDock::setModelIndexFromColumn(TreeViewComboBox* cb, const AbstractColumn* column) { if (column) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else cb->setCurrentModelIndex(QModelIndex()); } //************************************************************* //********** SLOTs for changes triggered in AxisDock ********** //************************************************************* //"General"-tab void AxisDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setVisible(state); } /*! called if the orientation (horizontal or vertical) of the current axis is changed. */ void AxisDock::orientationChanged(int index) { auto orientation = (Axis::AxisOrientation)index; if (orientation == Axis::AxisHorizontal) { ui.cbPosition->setItemText(0, i18n("Top") ); ui.cbPosition->setItemText(1, i18n("Bottom") ); ui.cbLabelsPosition->setItemText(1, i18n("Top") ); ui.cbLabelsPosition->setItemText(2, i18n("Bottom") ); ui.cbScale->setItemText(1, QLatin1String("log(x)") ); ui.cbScale->setItemText(2, QLatin1String("log2(x)") ); ui.cbScale->setItemText(3, QLatin1String("ln(x)") ); ui.cbScale->setItemText(4, QLatin1String("sqrt(x)") ); ui.cbScale->setItemText(5, QLatin1String("x^2") ); } else { //vertical ui.cbPosition->setItemText(0, i18n("Left") ); ui.cbPosition->setItemText(1, i18n("Right") ); ui.cbLabelsPosition->setItemText(1, i18n("Right") ); ui.cbLabelsPosition->setItemText(2, i18n("Left") ); ui.cbScale->setItemText(1, QLatin1String("log(y)") ); ui.cbScale->setItemText(2, QLatin1String("log2(y)") ); ui.cbScale->setItemText(3, QLatin1String("ln(y)") ); ui.cbScale->setItemText(4, QLatin1String("sqrt(y)") ); ui.cbScale->setItemText(5, QLatin1String("y^2") ); } if (m_initializing) return; //depending on the current orientation we need to update axis position and labels position //axis position, map from the current index in the combobox to the enum value in Axis::AxisPosition Axis::AxisPosition axisPosition; int posIndex = ui.cbPosition->currentIndex(); if (orientation == Axis::AxisHorizontal) { if (posIndex > 1) posIndex += 2; axisPosition = Axis::AxisPosition(posIndex); } else axisPosition = Axis::AxisPosition(posIndex+2); //labels position posIndex = ui.cbLabelsPosition->currentIndex(); auto labelsPosition = Axis::LabelsPosition(posIndex); for (auto* axis : m_axesList) { axis->beginMacro(i18n("%1: set axis orientation", axis->name())); axis->setOrientation(orientation); axis->setPosition(axisPosition); axis->setLabelsPosition(labelsPosition); axis->endMacro(); } } /*! called if one of the predefined axis positions (top, bottom, left, right, center or custom) was changed. */ void AxisDock::positionChanged(int index) { if (index == -1) return; //we occasionally get -1 here, nothing to do in this case if (index == 3) ui.lePosition->setVisible(true); else ui.lePosition->setVisible(false); if (m_initializing) return; //map from the current index in the combo box to the enum value in Axis::AxisPosition, //depends on the current orientation Axis::AxisPosition position; if ( ui.cbOrientation->currentIndex() == 0 ) { if (index>1) index += 2; position = Axis::AxisPosition(index); } else position = Axis::AxisPosition(index+2); for (auto* axis : m_axesList) axis->setPosition(position); } /*! called when the custom position of the axis in the corresponding LineEdit is changed. */ void AxisDock::positionChanged() { if (m_initializing) return; double offset = ui.lePosition->text().toDouble(); for (auto* axis : m_axesList) axis->setOffset(offset); } void AxisDock::scaleChanged(int index) { if (m_initializing) return; auto scale = (Axis::AxisScale)index; for (auto* axis : m_axesList) axis->setScale(scale); } void AxisDock::autoScaleChanged(int index) { bool autoScale = index == Qt::Checked; ui.leStart->setEnabled(!autoScale); ui.leEnd->setEnabled(!autoScale); ui.dateTimeEditStart->setEnabled(!autoScale); ui.dateTimeEditEnd->setEnabled(!autoScale); if (m_initializing) return; for (auto* axis : m_axesList) axis->setAutoScale(autoScale); } void AxisDock::startChanged() { if (m_initializing) return; double value = ui.leStart->text().toDouble(); //check first, whether the value for the lower limit is valid for the log- and square root scaling. If not, set the default values. auto scale = Axis::AxisScale(ui.cbScale->currentIndex()); if (scale == Axis::ScaleLog10 || scale == Axis::ScaleLog2 || scale == Axis::ScaleLn) { if (value <= 0) { KMessageBox::sorry(this, i18n("The axes lower limit has a non-positive value. Default minimal value will be used."), i18n("Wrong lower limit value") ); ui.leStart->setText( "0.01" ); value = 0.01; } } else if (scale == Axis::ScaleSqrt) { if (value < 0) { KMessageBox::sorry(this, i18n("The axes lower limit has a negative value. Default minimal value will be used."), i18n("Wrong lower limit value") ); ui.leStart->setText( "0" ); value = 0; } } const Lock lock(m_initializing); for (auto* axis : m_axesList) axis->setStart(value); } void AxisDock::endChanged() { if (m_initializing) return; double value = ui.leEnd->text().toDouble(); const Lock lock(m_initializing); for (auto* axis : m_axesList) axis->setEnd(value); } void AxisDock::startDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* axis : m_axesList) axis->setStart(value); } void AxisDock::endDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* axis : m_axesList) axis->setEnd(value); } void AxisDock::zeroOffsetChanged() { if (m_initializing) return; double offset = ui.leZeroOffset->text().toDouble(); const Lock lock(m_initializing); for (auto* axis : m_axesList) axis->setZeroOffset(offset); } void AxisDock::scalingFactorChanged() { if (m_initializing) return; double scalingFactor = ui.leScalingFactor->text().toDouble(); if (scalingFactor != 0.0) { const Lock lock(m_initializing); for (auto* axis : m_axesList) axis->setScalingFactor(scalingFactor); } } // "Line"-tab void AxisDock::lineStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lLineColor->setEnabled(b); ui.kcbLineColor->setEnabled(b); ui.lLineWidth->setEnabled(b); ui.sbLineWidth->setEnabled(b); ui.lLineOpacity->setEnabled(b); ui.sbLineOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->linePen(); pen.setStyle(penStyle); axis->setLinePen(pen); } } void AxisDock::lineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->linePen(); pen.setColor(color); axis->setLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing = false; } void AxisDock::lineWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->linePen(); - pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); + pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point)); axis->setLinePen(pen); } } void AxisDock::lineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setLineOpacity(opacity); } void AxisDock::arrowTypeChanged(int index) { auto type = (Axis::ArrowType)index; if (type == Axis::NoArrow) { ui.cbArrowPosition->setEnabled(false); ui.sbArrowSize->setEnabled(false); } else { ui.cbArrowPosition->setEnabled(true); ui.sbArrowSize->setEnabled(true); } if (m_initializing) return; for (auto* axis : m_axesList) axis->setArrowType(type); } void AxisDock::arrowPositionChanged(int index) { if (m_initializing) return; auto position = (Axis::ArrowPosition)index; for (auto* axis : m_axesList) axis->setArrowPosition(position); } void AxisDock::arrowSizeChanged(int value) { if (m_initializing) return; - float v = Worksheet::convertToSceneUnits(value, Worksheet::Point); + float v = Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point); for (auto* axis : m_axesList) axis->setArrowSize(v); } //"Major ticks" tab void AxisDock::majorTicksDirectionChanged(int index) { Axis::TicksDirection direction = Axis::TicksDirection(index); bool b = (direction != Axis::noTicks); ui.lMajorTicksType->setEnabled(b); ui.cbMajorTicksType->setEnabled(b); ui.lMajorTicksType->setEnabled(b); ui.cbMajorTicksType->setEnabled(b); ui.lMajorTicksNumber->setEnabled(b); ui.sbMajorTicksNumber->setEnabled(b); ui.lMajorTicksSpacingNumeric->setEnabled(b); ui.sbMajorTicksSpacingNumeric->setEnabled(b); ui.lMajorTicksIncrementDateTime->setEnabled(b); dtsbMajorTicksIncrement->setEnabled(b); ui.lMajorTicksLineStyle->setEnabled(b); ui.cbMajorTicksLineStyle->setEnabled(b); dtsbMinorTicksIncrement->setEnabled(b); if (b) { auto penStyle = Qt::PenStyle(ui.cbMajorTicksLineStyle->currentIndex()); b = (penStyle != Qt::NoPen); } ui.lMajorTicksColor->setEnabled(b); ui.kcbMajorTicksColor->setEnabled(b); ui.lMajorTicksWidth->setEnabled(b); ui.sbMajorTicksWidth->setEnabled(b); ui.lMajorTicksLength->setEnabled(b); ui.sbMajorTicksLength->setEnabled(b); ui.lMajorTicksOpacity->setEnabled(b); ui.sbMajorTicksOpacity->setEnabled(b); if (m_initializing) return; for (auto* axis : m_axesList) axis->setMajorTicksDirection(direction); } /*! called if the current style of the ticks (Number or Increment) is changed. Shows/hides the corresponding widgets. */ void AxisDock::majorTicksTypeChanged(int index) { if (!m_axis) // If elements are added to the combobox 'cbMajorTicksType' (at init of this class), then this function is called, which is a problem if no axis are available return; auto type = Axis::TicksType(index); if (type == Axis::TicksTotalNumber) { ui.lMajorTicksNumber->show(); ui.sbMajorTicksNumber->show(); ui.lMajorTicksSpacingNumeric->hide(); ui.sbMajorTicksSpacingNumeric->hide(); ui.lMajorTicksIncrementDateTime->hide(); dtsbMajorTicksIncrement->hide(); ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); } else if (type == Axis::TicksSpacing) { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); ui.lMajorTicksSpacingNumeric->show(); const auto* plot = static_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) { ui.lMajorTicksIncrementDateTime->hide(); dtsbMajorTicksIncrement->hide(); ui.lMajorTicksSpacingNumeric->show(); ui.sbMajorTicksSpacingNumeric->show(); } else { ui.lMajorTicksIncrementDateTime->show(); dtsbMajorTicksIncrement->show(); ui.lMajorTicksSpacingNumeric->hide(); ui.sbMajorTicksSpacingNumeric->hide(); } ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); // Check if spacing is not to small majorTicksSpacingChanged(); } else { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); ui.lMajorTicksSpacingNumeric->hide(); ui.sbMajorTicksSpacingNumeric->hide(); ui.lMajorTicksIncrementDateTime->hide(); dtsbMajorTicksIncrement->hide(); ui.lMajorTicksColumn->show(); cbMajorTicksColumn->show(); } if (m_initializing) return; for (auto* axis : m_axesList) axis->setMajorTicksType(type); } void AxisDock::majorTicksNumberChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setMajorTicksNumber(value); } void AxisDock::majorTicksSpacingChanged() { if (m_initializing) return; const auto* plot = static_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); double spacing = numeric ? ui.sbMajorTicksSpacingNumeric->value() : dtsbMajorTicksIncrement->value(); double range = fabs(m_axis->end() - m_axis->start()); DEBUG("major spacing = " << spacing << ", range = " << range) // fix spacing if incorrect (not set or > 100 ticks) if (spacing == 0. || range / spacing > 100.) { if (spacing == 0) spacing = range / (ui.sbMajorTicksNumber->value() - 1); if (range / spacing > 100.) spacing = range / 100.; DEBUG("new spacing = " << spacing) // determine stepsize and number of decimals m_initializing = true; if (numeric) { int decimals = nsl_math_rounded_decimals(spacing) + 1; DEBUG("decimals = " << decimals << ", step = " << gsl_pow_int(10., -decimals)) ui.sbMajorTicksSpacingNumeric->setDecimals(decimals); ui.sbMajorTicksSpacingNumeric->setSingleStep(gsl_pow_int(10., -decimals)); ui.sbMajorTicksSpacingNumeric->setMaximum(range); ui.sbMajorTicksSpacingNumeric->setValue(spacing); } else //TODO: check reversed axis dtsbMajorTicksIncrement->setValue(spacing); m_initializing = false; } for (auto* axis : m_axesList) axis->setMajorTicksSpacing(spacing); } void AxisDock::majorTicksLineStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMajorTicksColor->setEnabled(b); ui.kcbMajorTicksColor->setEnabled(b); ui.lMajorTicksWidth->setEnabled(b); ui.sbMajorTicksWidth->setEnabled(b); ui.lMajorTicksLength->setEnabled(b); ui.sbMajorTicksLength->setEnabled(b); ui.lMajorTicksOpacity->setEnabled(b); ui.sbMajorTicksOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorTicksPen(); pen.setStyle(penStyle); axis->setMajorTicksPen(pen); } } void AxisDock::majorTicksColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); AbstractColumn* column = nullptr; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column != nullptr); } for (auto* axis : m_axesList) axis->setMajorTicksColumn(column); } void AxisDock::majorTicksColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorTicksPen(); pen.setColor(color); axis->setMajorTicksPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, color); m_initializing = false; } void AxisDock::majorTicksWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorTicksPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); axis->setMajorTicksPen(pen); } } void AxisDock::majorTicksLengthChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) - axis->setMajorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + axis->setMajorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void AxisDock::majorTicksOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMajorTicksOpacity(opacity); } //"Minor ticks" tab void AxisDock::minorTicksDirectionChanged(int index) { Axis::TicksDirection direction = Axis::TicksDirection(index); bool b = (direction != Axis::noTicks); ui.lMinorTicksType->setEnabled(b); ui.cbMinorTicksType->setEnabled(b); ui.lMinorTicksType->setEnabled(b); ui.cbMinorTicksType->setEnabled(b); ui.lMinorTicksNumber->setEnabled(b); ui.sbMinorTicksNumber->setEnabled(b); ui.lMinorTicksSpacingNumeric->setEnabled(b); ui.sbMinorTicksSpacingNumeric->setEnabled(b); ui.lMinorTicksIncrementDateTime->setEnabled(b); dtsbMinorTicksIncrement->setEnabled(b); ui.lMinorTicksLineStyle->setEnabled(b); ui.cbMinorTicksLineStyle->setEnabled(b); if (b) { auto penStyle = Qt::PenStyle(ui.cbMinorTicksLineStyle->currentIndex()); b = (penStyle != Qt::NoPen); } ui.lMinorTicksColor->setEnabled(b); ui.kcbMinorTicksColor->setEnabled(b); ui.lMinorTicksWidth->setEnabled(b); ui.sbMinorTicksWidth->setEnabled(b); ui.lMinorTicksLength->setEnabled(b); ui.sbMinorTicksLength->setEnabled(b); ui.lMinorTicksOpacity->setEnabled(b); ui.sbMinorTicksOpacity->setEnabled(b); if (m_initializing) return; for (auto* axis : m_axesList) axis->setMinorTicksDirection(direction); } void AxisDock::minorTicksTypeChanged(int index) { if (!m_axis) // If elements are added to the combobox 'cbMajorTicksType' (at init of this class), then this function is called, which is a problem if no axis are available return; auto type = Axis::TicksType(index); if (type == Axis::TicksTotalNumber) { ui.lMinorTicksNumber->show(); ui.sbMinorTicksNumber->show(); ui.lMinorTicksSpacingNumeric->hide(); ui.sbMinorTicksSpacingNumeric->hide(); ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); ui.lMinorTicksIncrementDateTime->hide(); dtsbMinorTicksIncrement->hide(); } else if ( type == Axis::TicksSpacing) { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); const auto* plot = static_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) { ui.lMinorTicksSpacingNumeric->show(); ui.sbMinorTicksSpacingNumeric->show(); ui.lMinorTicksIncrementDateTime->hide(); dtsbMinorTicksIncrement->hide(); } else { ui.lMinorTicksSpacingNumeric->hide(); ui.sbMinorTicksSpacingNumeric->hide(); ui.lMinorTicksIncrementDateTime->show(); dtsbMinorTicksIncrement->show(); } ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); // Check if spacing is not to small minorTicksSpacingChanged(); } else { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); ui.lMinorTicksSpacingNumeric->hide(); ui.sbMinorTicksSpacingNumeric->hide(); ui.lMinorTicksIncrementDateTime->hide(); dtsbMinorTicksIncrement->hide(); ui.lMinorTicksColumn->show(); cbMinorTicksColumn->show(); } if (m_initializing) return; for (auto* axis : m_axesList) axis->setMinorTicksType(type); } void AxisDock::minorTicksNumberChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setMinorTicksNumber(value); } void AxisDock::minorTicksSpacingChanged() { if (m_initializing) return; const auto* plot = static_cast(m_axis->parentAspect()); bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); double spacing = numeric ? ui.sbMinorTicksSpacingNumeric->value() : dtsbMinorTicksIncrement->value(); double range = fabs(m_axis->end() - m_axis->start()); //DEBUG("minor spacing = " << spacing << ", range = " << range) int numberTicks = 0; int majorTicks = m_axis->majorTicksNumber(); if (spacing > 0.) numberTicks = range / (majorTicks - 1) / spacing - 1; // recalc //DEBUG(" nticks = " << numberTicks) // set if unset or > 100. if (spacing == 0. || numberTicks > 100) { if (spacing == 0.) spacing = range / (majorTicks - 1) / (ui.sbMinorTicksNumber->value() + 1); numberTicks = range / (majorTicks - 1) / spacing - 1; // recalculate number of ticks if (numberTicks > 100) // maximum 100 minor ticks spacing = range / (majorTicks - 1) / (100 + 1); DEBUG("new spacing = " << spacing) DEBUG("new nticks = " << numberTicks) // determine stepsize and number of decimals m_initializing = true; if (numeric) { int decimals = nsl_math_rounded_decimals(spacing) + 1; DEBUG("decimals = " << decimals << ", step = " << gsl_pow_int(10., -decimals)) ui.sbMinorTicksSpacingNumeric->setDecimals(decimals); ui.sbMinorTicksSpacingNumeric->setSingleStep(gsl_pow_int(10., -decimals)); ui.sbMinorTicksSpacingNumeric->setMaximum(range); ui.sbMinorTicksSpacingNumeric->setValue(spacing); } else dtsbMinorTicksIncrement->setValue(spacing); m_initializing = false; } for (auto* axis : m_axesList) axis->setMinorTicksSpacing(spacing); } void AxisDock::minorTicksColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column != nullptr); for (auto* axis : m_axesList) axis->setMinorTicksColumn(column); } void AxisDock::minorTicksLineStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMinorTicksColor->setEnabled(b); ui.kcbMinorTicksColor->setEnabled(b); ui.lMinorTicksWidth->setEnabled(b); ui.sbMinorTicksWidth->setEnabled(b); ui.lMinorTicksLength->setEnabled(b); ui.sbMinorTicksLength->setEnabled(b); ui.lMinorTicksOpacity->setEnabled(b); ui.sbMinorTicksOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorTicksPen(); pen.setStyle(penStyle); axis->setMinorTicksPen(pen); } } void AxisDock::minorTicksColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorTicksPen(); pen.setColor(color); axis->setMinorTicksPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, color); m_initializing = false; } void AxisDock::minorTicksWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorTicksPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); axis->setMinorTicksPen(pen); } } void AxisDock::minorTicksLengthChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) - axis->setMinorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + axis->setMinorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void AxisDock::minorTicksOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMinorTicksOpacity(opacity); } //"Tick labels"-tab void AxisDock::labelsFormatChanged(int index) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsFormat(Axis::LabelsFormat(index)); } void AxisDock::labelsPrecisionChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsPrecision(value); } void AxisDock::labelsAutoPrecisionChanged(int state) { bool checked = (state == Qt::Checked); ui.sbLabelsPrecision->setEnabled(!checked); if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsAutoPrecision(checked); } void AxisDock::labelsDateTimeFormatChanged(int) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsDateTimeFormat(ui.cbLabelsDateTimeFormat->currentText()); } void AxisDock::labelsPositionChanged(int index) { auto position = Axis::LabelsPosition(index); bool b = (position != Axis::NoLabels); ui.lLabelsOffset->setEnabled(b); ui.sbLabelsOffset->setEnabled(b); ui.lLabelsRotation->setEnabled(b); ui.sbLabelsRotation->setEnabled(b); ui.lLabelsFont->setEnabled(b); ui.kfrLabelsFont->setEnabled(b); ui.lLabelsColor->setEnabled(b); ui.kcbLabelsFontColor->setEnabled(b); ui.lLabelsPrefix->setEnabled(b); ui.leLabelsPrefix->setEnabled(b); ui.lLabelsSuffix->setEnabled(b); ui.leLabelsSuffix->setEnabled(b); ui.lLabelsOpacity->setEnabled(b); ui.sbLabelsOpacity->setEnabled(b); if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsPosition(position); } void AxisDock::labelsOffsetChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) - axis->setLabelsOffset( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + axis->setLabelsOffset( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void AxisDock::labelsRotationChanged(int value) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsRotationAngle(value); } void AxisDock::labelsPrefixChanged() { if (m_initializing) return; QString prefix = ui.leLabelsPrefix->text(); for (auto* axis : m_axesList) axis->setLabelsPrefix(prefix); } void AxisDock::labelsSuffixChanged() { if (m_initializing) return; QString suffix = ui.leLabelsSuffix->text(); for (auto* axis : m_axesList) axis->setLabelsSuffix(suffix); } void AxisDock::labelsFontChanged(const QFont& font) { if (m_initializing) return; QFont labelsFont = font; - labelsFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); + labelsFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Unit::Point) ); for (auto* axis : m_axesList) axis->setLabelsFont( labelsFont ); } void AxisDock::labelsFontColorChanged(const QColor& color) { if (m_initializing) return; for (auto* axis : m_axesList) axis->setLabelsColor(color); } void AxisDock::labelsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setLabelsOpacity(opacity); } // "Grid"-tab //major grid void AxisDock::majorGridStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMajorGridColor->setEnabled(b); ui.kcbMajorGridColor->setEnabled(b); ui.lMajorGridWidth->setEnabled(b); ui.sbMajorGridWidth->setEnabled(b); ui.lMajorGridOpacity->setEnabled(b); ui.sbMajorGridOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorGridPen(); pen.setStyle(penStyle); axis->setMajorGridPen(pen); } } void AxisDock::majorGridColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorGridPen(); pen.setColor(color); axis->setMajorGridPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMajorGridStyle, color); m_initializing = false; } void AxisDock::majorGridWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->majorGridPen(); - pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); + pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point)); axis->setMajorGridPen(pen); } } void AxisDock::majorGridOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMajorGridOpacity(opacity); } //minor grid void AxisDock::minorGridStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMinorGridColor->setEnabled(b); ui.kcbMinorGridColor->setEnabled(b); ui.lMinorGridWidth->setEnabled(b); ui.sbMinorGridWidth->setEnabled(b); ui.lMinorGridOpacity->setEnabled(b); ui.sbMinorGridOpacity->setEnabled(b); if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorGridPen(); pen.setStyle(penStyle); axis->setMinorGridPen(pen); } } void AxisDock::minorGridColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorGridPen(); pen.setColor(color); axis->setMinorGridPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbMinorGridStyle, color); m_initializing = false; } void AxisDock::minorGridWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* axis : m_axesList) { pen = axis->minorGridPen(); - pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); + pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point)); axis->setMinorGridPen(pen); } } void AxisDock::minorGridOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* axis : m_axesList) axis->setMinorGridOpacity(opacity); } //************************************************************* //************ SLOTs for changes triggered in Axis ************ //************************************************************* void AxisDock::axisDescriptionChanged(const AbstractAspect* aspect) { if (m_axis != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void AxisDock::axisOrientationChanged(Axis::AxisOrientation orientation) { m_initializing = true; ui.cbOrientation->setCurrentIndex( (int)orientation ); m_initializing = false; } void AxisDock::axisPositionChanged(Axis::AxisPosition position) { m_initializing = true; //map from the enum Axis::AxisOrientation to the index in the combo box int index(position); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); m_initializing = false; } void AxisDock::axisPositionChanged(float value) { m_initializing = true; ui.lePosition->setText( QString::number(value) ); m_initializing = false; } void AxisDock::axisScaleChanged(Axis::AxisScale scale) { m_initializing = true; ui.cbScale->setCurrentIndex( (int)scale ); m_initializing = false; } void AxisDock::axisAutoScaleChanged(bool on) { m_initializing = true; ui.chkAutoScale->setChecked(on); m_initializing = false; } void AxisDock::axisStartChanged(double value) { if (m_initializing) return; const Lock lock(m_initializing); ui.leStart->setText( QString::number(value) ); ui.dateTimeEditStart->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); // determine stepsize and number of decimals double range = fabs(m_axis->end() - m_axis->start()); int decimals = nsl_math_rounded_decimals(range) + 1; DEBUG("range = " << range << ", decimals = " << decimals) ui.sbMajorTicksSpacingNumeric->setDecimals(decimals); ui.sbMajorTicksSpacingNumeric->setSingleStep(gsl_pow_int(10., -decimals)); ui.sbMajorTicksSpacingNumeric->setMaximum(range); } void AxisDock::axisEndChanged(double value) { if (m_initializing) return; const Lock lock(m_initializing); ui.leEnd->setText( QString::number(value) ); ui.dateTimeEditEnd->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); // determine stepsize and number of decimals double range = fabs(m_axis->end() - m_axis->start()); int decimals = nsl_math_rounded_decimals(range) + 1; DEBUG("range = " << range << ", decimals = " << decimals) ui.sbMajorTicksSpacingNumeric->setDecimals(decimals); ui.sbMajorTicksSpacingNumeric->setSingleStep(gsl_pow_int(10., -decimals)); ui.sbMajorTicksSpacingNumeric->setMaximum(range); } void AxisDock::axisZeroOffsetChanged(qreal value) { if (m_initializing) return; const Lock lock(m_initializing); ui.leZeroOffset->setText( QString::number(value) ); } void AxisDock::axisScalingFactorChanged(qreal value) { if (m_initializing) return; const Lock lock(m_initializing); ui.leScalingFactor->setText( QString::number(value) ); } //line void AxisDock::axisLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( pen.style() ); ui.kcbLineColor->setColor( pen.color() ); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color() ); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void AxisDock::axisArrowTypeChanged(Axis::ArrowType type) { m_initializing = true; ui.cbArrowType->setCurrentIndex((int)type); m_initializing = false; } void AxisDock::axisLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisArrowPositionChanged(Axis::ArrowPosition position) { m_initializing = true; ui.cbArrowPosition->setCurrentIndex( (int)position ); m_initializing = false; } void AxisDock::axisArrowSizeChanged(qreal size) { m_initializing = true; - ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } //major ticks void AxisDock::axisMajorTicksDirectionChanged(Axis::TicksDirection direction) { m_initializing = true; ui.cbMajorTicksDirection->setCurrentIndex(direction); m_initializing = false; } void AxisDock::axisMajorTicksTypeChanged(Axis::TicksType type) { m_initializing = true; ui.cbMajorTicksType->setCurrentIndex(type); m_initializing = false; } void AxisDock::axisMajorTicksNumberChanged(int number) { m_initializing = true; ui.sbMajorTicksNumber->setValue(number); m_initializing = false; } void AxisDock::axisMajorTicksSpacingChanged(qreal increment) { m_initializing = true; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) ui.sbMajorTicksSpacingNumeric->setValue(increment); else { dtsbMajorTicksIncrement->setValue(increment); } } m_initializing = false; } void AxisDock::axisMajorTicksPenChanged(const QPen& pen) { m_initializing = true; ui.cbMajorTicksLineStyle->setCurrentIndex(pen.style()); ui.kcbMajorTicksColor->setColor(pen.color()); - ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); + ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void AxisDock::axisMajorTicksLengthChanged(qreal length) { m_initializing = true; - ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(length,Worksheet::Point) ); + ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(length, Worksheet::Unit::Point) ); m_initializing = false; } void AxisDock::axisMajorTicksOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMajorTicksOpacity->setValue( round(opacity*100.0)); m_initializing = false; } //minor ticks void AxisDock::axisMinorTicksDirectionChanged(Axis::TicksDirection direction) { m_initializing = true; ui.cbMinorTicksDirection->setCurrentIndex(direction); m_initializing = false; } void AxisDock::axisMinorTicksTypeChanged(Axis::TicksType type) { m_initializing = true; ui.cbMinorTicksType->setCurrentIndex(type); m_initializing = false; } void AxisDock::axisMinorTicksNumberChanged(int number) { m_initializing = true; ui.sbMinorTicksNumber->setValue(number); m_initializing = false; } void AxisDock::axisMinorTicksSpacingChanged(qreal increment) { m_initializing = true; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); if (numeric) ui.sbMinorTicksSpacingNumeric->setValue(increment); else { dtsbMinorTicksIncrement->setValue(increment); } } m_initializing = false; } void AxisDock::axisMinorTicksPenChanged(const QPen& pen) { m_initializing = true; ui.cbMinorTicksLineStyle->setCurrentIndex(pen.style()); ui.kcbMinorTicksColor->setColor(pen.color()); - ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); + ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void AxisDock::axisMinorTicksLengthChanged(qreal length) { m_initializing = true; - ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(length,Worksheet::Point) ); + ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(length, Worksheet::Unit::Point) ); m_initializing = false; } void AxisDock::axisMinorTicksOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMinorTicksOpacity->setValue(round(opacity*100.0)); m_initializing = false; } //labels void AxisDock::axisLabelsFormatChanged(Axis::LabelsFormat format) { m_initializing = true; ui.cbLabelsFormat->setCurrentIndex(format); m_initializing = false; } void AxisDock::axisLabelsAutoPrecisionChanged(bool on) { m_initializing = true; ui.chkLabelsAutoPrecision->setChecked((int) on); m_initializing = false; } void AxisDock::axisLabelsPrecisionChanged(int precision) { m_initializing = true; ui.sbLabelsPrecision->setValue(precision); m_initializing = false; } void AxisDock::axisLabelsDateTimeFormatChanged(const QString& format) { m_initializing = true; ui.cbLabelsDateTimeFormat->setCurrentText(format); m_initializing = false; } void AxisDock::axisLabelsPositionChanged(Axis::LabelsPosition position) { m_initializing = true; ui.cbLabelsPosition->setCurrentIndex(position); m_initializing = false; } void AxisDock::axisLabelsOffsetChanged(double offset) { m_initializing = true; - ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(offset, Worksheet::Point) ); + ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(offset, Worksheet::Unit::Point) ); m_initializing = false; } void AxisDock::axisLabelsRotationAngleChanged(qreal rotation) { m_initializing = true; ui.sbLabelsRotation->setValue(rotation); m_initializing = false; } void AxisDock::axisLabelsFontChanged(const QFont& font) { m_initializing = true; //we need to set the font size in points for KFontRequester QFont newFont(font); - newFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + newFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrLabelsFont->setFont(newFont); m_initializing = false; } void AxisDock::axisLabelsFontColorChanged(const QColor& color) { m_initializing = true; ui.kcbLabelsFontColor->setColor(color); m_initializing = false; } void AxisDock::axisLabelsPrefixChanged(const QString& prefix) { m_initializing = true; ui.leLabelsPrefix->setText(prefix); m_initializing = false; } void AxisDock::axisLabelsSuffixChanged(const QString& suffix) { m_initializing = true; ui.leLabelsSuffix->setText(suffix); m_initializing = false; } void AxisDock::axisLabelsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLabelsOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //grid void AxisDock::axisMajorGridPenChanged(const QPen& pen) { m_initializing = true; ui.cbMajorGridStyle->setCurrentIndex((int) pen.style()); ui.kcbMajorGridColor->setColor(pen.color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, pen.color()); - ui.sbMajorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); + ui.sbMajorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void AxisDock::axisMajorGridOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMajorGridOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisMinorGridPenChanged(const QPen& pen) { m_initializing = true; ui.cbMinorGridStyle->setCurrentIndex((int) pen.style()); ui.kcbMinorGridColor->setColor(pen.color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, pen.color()); - ui.sbMinorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); + ui.sbMinorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void AxisDock::axisMinorGridOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMinorGridOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //************************************************************* //************************* Settings ************************** //************************************************************* void AxisDock::load() { //General ui.chkVisible->setChecked( m_axis->isVisible() ); ui.cbOrientation->setCurrentIndex( (int) m_axis->orientation() ); int index = (int)m_axis->position(); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); ui.lePosition->setText( QString::number( m_axis->offset()) ); ui.cbScale->setCurrentIndex( (int) m_axis->scale() ); ui.chkAutoScale->setChecked( m_axis->autoScale() ); ui.leStart->setText( QString::number(m_axis->start()) ); ui.leEnd->setText( QString::number(m_axis->end()) ); ui.sbMajorTicksSpacingNumeric->setDecimals(0); ui.sbMajorTicksSpacingNumeric->setSingleStep(m_axis->majorTicksSpacing()); //depending on range format of the axis (numeric vs. datetime), show/hide the corresponding widgets const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { bool numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); //ranges ui.lStart->setVisible(numeric); ui.lEnd->setVisible(numeric); ui.leStart->setVisible(numeric); ui.leEnd->setVisible(numeric); ui.lStartDateTime->setVisible(!numeric); ui.dateTimeEditStart->setVisible(!numeric); ui.lEndDateTime->setVisible(!numeric); ui.dateTimeEditEnd->setVisible(!numeric); //tick labels format ui.lLabelsFormat->setVisible(numeric); ui.cbLabelsFormat->setVisible(numeric); ui.chkLabelsAutoPrecision->setVisible(numeric); ui.lLabelsPrecision->setVisible(numeric); ui.sbLabelsPrecision->setVisible(numeric); ui.cbLabelsDateTimeFormat->setVisible(numeric); ui.lLabelsDateTimeFormat->setVisible(!numeric); ui.cbLabelsDateTimeFormat->setVisible(!numeric); if (!numeric) { if (m_axis->orientation() == Axis::AxisHorizontal) { ui.dateTimeEditStart->setDisplayFormat(plot->xRangeDateTimeFormat()); ui.dateTimeEditEnd->setDisplayFormat(plot->xRangeDateTimeFormat()); } else { ui.dateTimeEditStart->setDisplayFormat(plot->yRangeDateTimeFormat()); ui.dateTimeEditEnd->setDisplayFormat(plot->yRangeDateTimeFormat()); } ui.dateTimeEditStart->setDateTime(QDateTime::fromMSecsSinceEpoch(m_axis->start())); ui.dateTimeEditEnd->setDateTime(QDateTime::fromMSecsSinceEpoch(m_axis->end())); } } ui.leZeroOffset->setText( QString::number(m_axis->zeroOffset()) ); ui.leScalingFactor->setText( QString::number(m_axis->scalingFactor()) ); //Line ui.cbLineStyle->setCurrentIndex( (int) m_axis->linePen().style() ); ui.kcbLineColor->setColor( m_axis->linePen().color() ); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->linePen().widthF(),Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->linePen().widthF(), Worksheet::Unit::Point) ); ui.sbLineOpacity->setValue( round(m_axis->lineOpacity()*100.0) ); ui.cbArrowType->setCurrentIndex( (int)m_axis->arrowType() ); ui.cbArrowPosition->setCurrentIndex( (int)m_axis->arrowPosition() ); - ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(m_axis->arrowSize(), Worksheet::Point) ); + ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(m_axis->arrowSize(), Worksheet::Unit::Point) ); //Major ticks ui.cbMajorTicksDirection->setCurrentIndex( (int) m_axis->majorTicksDirection() ); ui.cbMajorTicksType->setCurrentIndex( (int) m_axis->majorTicksType() ); ui.sbMajorTicksNumber->setValue( m_axis->majorTicksNumber() ); ui.cbMajorTicksLineStyle->setCurrentIndex( (int) m_axis->majorTicksPen().style() ); ui.kcbMajorTicksColor->setColor( m_axis->majorTicksPen().color() ); - ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksPen().widthF(),Worksheet::Point) ); - ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksLength(),Worksheet::Point) ); + ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksPen().widthF(), Worksheet::Unit::Point) ); + ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksLength(), Worksheet::Unit::Point) ); ui.sbMajorTicksOpacity->setValue( round(m_axis->majorTicksOpacity()*100.0) ); //Minor ticks ui.cbMinorTicksDirection->setCurrentIndex( (int) m_axis->minorTicksDirection() ); ui.cbMinorTicksType->setCurrentIndex( (int) m_axis->minorTicksType() ); ui.sbMinorTicksNumber->setValue( m_axis->minorTicksNumber() ); ui.cbMinorTicksLineStyle->setCurrentIndex( (int) m_axis->minorTicksPen().style() ); ui.kcbMinorTicksColor->setColor( m_axis->minorTicksPen().color() ); - ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksPen().widthF(),Worksheet::Point) ); - ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksLength(),Worksheet::Point) ); + ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksPen().widthF(), Worksheet::Unit::Point) ); + ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksLength(), Worksheet::Unit::Point) ); ui.sbMinorTicksOpacity->setValue( round(m_axis->minorTicksOpacity()*100.0) ); //Extra ticks //TODO // Tick label ui.cbLabelsPosition->setCurrentIndex( (int) m_axis->labelsPosition() ); - ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(m_axis->labelsOffset(),Worksheet::Point) ); + ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(m_axis->labelsOffset(), Worksheet::Unit::Point) ); ui.sbLabelsRotation->setValue( m_axis->labelsRotationAngle() ); ui.cbLabelsFormat->setCurrentIndex( (int) m_axis->labelsFormat() ); ui.chkLabelsAutoPrecision->setChecked( (int) m_axis->labelsAutoPrecision() ); ui.sbLabelsPrecision->setValue( (int)m_axis->labelsPrecision() ); ui.cbLabelsDateTimeFormat->setCurrentText(m_axis->labelsDateTimeFormat()); //we need to set the font size in points for KFontRequester QFont font = m_axis->labelsFont(); - font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrLabelsFont->setFont( font ); ui.kcbLabelsFontColor->setColor( m_axis->labelsColor() ); ui.leLabelsPrefix->setText( m_axis->labelsPrefix() ); ui.leLabelsSuffix->setText( m_axis->labelsSuffix() ); ui.sbLabelsOpacity->setValue( round(m_axis->labelsOpacity()*100.0) ); //Grid ui.cbMajorGridStyle->setCurrentIndex( (int) m_axis->majorGridPen().style() ); ui.kcbMajorGridColor->setColor( m_axis->majorGridPen().color() ); - ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->majorGridPen().widthF(),Worksheet::Point) ); + ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->majorGridPen().widthF(), Worksheet::Unit::Point) ); ui.sbMajorGridOpacity->setValue( round(m_axis->majorGridOpacity()*100.0) ); ui.cbMinorGridStyle->setCurrentIndex( (int) m_axis->minorGridPen().style() ); ui.kcbMinorGridColor->setColor( m_axis->minorGridPen().color() ); - ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorGridPen().widthF(),Worksheet::Point) ); + ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorGridPen().widthF(), Worksheet::Unit::Point) ); ui.sbMinorGridOpacity->setValue( round(m_axis->minorGridOpacity()*100.0) ); GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); this->majorTicksTypeChanged(ui.cbMajorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, ui.kcbMajorTicksColor->color()); this->minorTicksTypeChanged(ui.cbMinorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, ui.kcbMinorTicksColor->color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, ui.kcbMajorGridColor->color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, ui.kcbMinorGridColor->color()); } void AxisDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_axesList.size(); if (size > 1) m_axis->beginMacro(i18n("%1 axes: template \"%2\" loaded", size, name)); else m_axis->beginMacro(i18n("%1: template \"%2\" loaded", m_axis->name(), name)); this->loadConfig(config); m_axis->endMacro(); } void AxisDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "Axis" ); bool numeric = false; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); } //General ui.cbOrientation->setCurrentIndex( group.readEntry("Orientation", (int) m_axis->orientation()) ); int index = group.readEntry("Position", (int) m_axis->position()); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); ui.lePosition->setText( QString::number( group.readEntry("PositionOffset", m_axis->offset())) ); ui.cbScale->setCurrentIndex( group.readEntry("Scale", (int) m_axis->scale()) ); ui.chkAutoScale->setChecked(group.readEntry("AutoScale", m_axis->autoScale())); ui.leStart->setText( QString::number( group.readEntry("Start", m_axis->start())) ); ui.leEnd->setText( QString::number( group.readEntry("End", m_axis->end())) ); ui.leZeroOffset->setText( QString::number( group.readEntry("ZeroOffset", m_axis->zeroOffset())) ); ui.leScalingFactor->setText( QString::number( group.readEntry("ScalingFactor", m_axis->scalingFactor())) ); //Title KConfigGroup axisLabelGroup = config.group("AxisLabel"); labelWidget->loadConfig(axisLabelGroup); //Line ui.cbLineStyle->setCurrentIndex( group.readEntry("LineStyle", (int) m_axis->linePen().style()) ); ui.kcbLineColor->setColor( group.readEntry("LineColor", m_axis->linePen().color()) ); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_axis->linePen().widthF()),Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_axis->linePen().widthF()), Worksheet::Unit::Point) ); ui.sbLineOpacity->setValue( round(group.readEntry("LineOpacity", m_axis->lineOpacity())*100.0) ); ui.cbArrowType->setCurrentIndex( group.readEntry("ArrowType", (int) m_axis->arrowType()) ); ui.cbArrowPosition->setCurrentIndex( group.readEntry("ArrowPosition", (int) m_axis->arrowPosition()) ); - ui.sbArrowSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ArrowSize", m_axis->arrowSize()), Worksheet::Point) ); + ui.sbArrowSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ArrowSize", m_axis->arrowSize()), Worksheet::Unit::Point) ); //Major ticks ui.cbMajorTicksDirection->setCurrentIndex( group.readEntry("MajorTicksDirection", (int) m_axis->majorTicksDirection()) ); ui.cbMajorTicksType->setCurrentIndex( group.readEntry("MajorTicksType", (int) m_axis->majorTicksType()) ); ui.sbMajorTicksNumber->setValue( group.readEntry("MajorTicksNumber", m_axis->majorTicksNumber()) ); if (numeric) ui.sbMajorTicksSpacingNumeric->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksSpacing())); else dtsbMajorTicksIncrement->setValue(group.readEntry("MajorTicksIncrement", m_axis->majorTicksSpacing())); ui.cbMajorTicksLineStyle->setCurrentIndex( group.readEntry("MajorTicksLineStyle", (int) m_axis->majorTicksPen().style()) ); ui.kcbMajorTicksColor->setColor( group.readEntry("MajorTicksColor", m_axis->majorTicksPen().color()) ); - ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksWidth", m_axis->majorTicksPen().widthF()),Worksheet::Point) ); - ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksLength", m_axis->majorTicksLength()),Worksheet::Point) ); + ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksWidth", m_axis->majorTicksPen().widthF()), Worksheet::Unit::Point) ); + ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksLength", m_axis->majorTicksLength()), Worksheet::Unit::Point) ); ui.sbMajorTicksOpacity->setValue( round(group.readEntry("MajorTicksOpacity", m_axis->majorTicksOpacity())*100.0) ); //Minor ticks ui.cbMinorTicksDirection->setCurrentIndex( group.readEntry("MinorTicksDirection", (int) m_axis->minorTicksDirection()) ); ui.cbMinorTicksType->setCurrentIndex( group.readEntry("MinorTicksType", (int) m_axis->minorTicksType()) ); ui.sbMinorTicksNumber->setValue( group.readEntry("MinorTicksNumber", m_axis->minorTicksNumber()) ); if (numeric) ui.sbMinorTicksSpacingNumeric->setValue(group.readEntry("MinorTicksIncrement", m_axis->minorTicksSpacing())); else dtsbMinorTicksIncrement->setValue(group.readEntry("MinorTicksIncrement", m_axis->minorTicksSpacing())); ui.cbMinorTicksLineStyle->setCurrentIndex( group.readEntry("MinorTicksLineStyle", (int) m_axis->minorTicksPen().style()) ); ui.kcbMinorTicksColor->setColor( group.readEntry("MinorTicksColor", m_axis->minorTicksPen().color()) ); - ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksWidth", m_axis->minorTicksPen().widthF()),Worksheet::Point) ); - ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksLength", m_axis->minorTicksLength()),Worksheet::Point) ); + ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksWidth", m_axis->minorTicksPen().widthF()), Worksheet::Unit::Point) ); + ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksLength", m_axis->minorTicksLength()), Worksheet::Unit::Point) ); ui.sbMinorTicksOpacity->setValue( round(group.readEntry("MinorTicksOpacity", m_axis->minorTicksOpacity())*100.0) ); //Extra ticks //TODO // Tick label ui.cbLabelsFormat->setCurrentIndex( group.readEntry("LabelsFormat", (int) m_axis->labelsFormat()) ); ui.chkLabelsAutoPrecision->setChecked( group.readEntry("LabelsAutoPrecision", (int) m_axis->labelsAutoPrecision()) ); ui.sbLabelsPrecision->setValue( group.readEntry("LabelsPrecision", (int)m_axis->labelsPrecision()) ); ui.cbLabelsDateTimeFormat->setCurrentText( group.readEntry("LabelsDateTimeFormat", "yyyy-MM-dd hh:mm:ss") ); ui.cbLabelsPosition->setCurrentIndex( group.readEntry("LabelsPosition", (int) m_axis->labelsPosition()) ); - ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LabelsOffset", m_axis->labelsOffset()), Worksheet::Point) ); + ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LabelsOffset", m_axis->labelsOffset()), Worksheet::Unit::Point) ); ui.sbLabelsRotation->setValue( group.readEntry("LabelsRotation", m_axis->labelsRotationAngle()) ); //we need to set the font size in points for KFontRequester QFont font = m_axis->labelsFont(); - font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrLabelsFont->setFont( group.readEntry("LabelsFont", font) ); ui.kcbLabelsFontColor->setColor( group.readEntry("LabelsFontColor", m_axis->labelsColor()) ); ui.leLabelsPrefix->setText( group.readEntry("LabelsPrefix", m_axis->labelsPrefix()) ); ui.leLabelsSuffix->setText( group.readEntry("LabelsSuffix", m_axis->labelsSuffix()) ); ui.sbLabelsOpacity->setValue( round(group.readEntry("LabelsOpacity", m_axis->labelsOpacity())*100.0) ); //Grid ui.cbMajorGridStyle->setCurrentIndex( group.readEntry("MajorGridStyle", (int) m_axis->majorGridPen().style()) ); ui.kcbMajorGridColor->setColor( group.readEntry("MajorGridColor", m_axis->majorGridPen().color()) ); - ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorGridWidth", m_axis->majorGridPen().widthF()),Worksheet::Point) ); + ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorGridWidth", m_axis->majorGridPen().widthF()), Worksheet::Unit::Point) ); ui.sbMajorGridOpacity->setValue( round(group.readEntry("MajorGridOpacity", m_axis->majorGridOpacity())*100.0) ); ui.cbMinorGridStyle->setCurrentIndex( group.readEntry("MinorGridStyle", (int) m_axis->minorGridPen().style()) ); ui.kcbMinorGridColor->setColor( group.readEntry("MinorGridColor", m_axis->minorGridPen().color()) ); - ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorGridWidth", m_axis->minorGridPen().widthF()),Worksheet::Point) ); + ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorGridWidth", m_axis->minorGridPen().widthF()), Worksheet::Unit::Point) ); ui.sbMinorGridOpacity->setValue( round(group.readEntry("MinorGridOpacity", m_axis->minorGridOpacity())*100.0) ); m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); this->majorTicksTypeChanged(ui.cbMajorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, ui.kcbMajorTicksColor->color()); this->minorTicksTypeChanged(ui.cbMinorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, ui.kcbMinorTicksColor->color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, ui.kcbMajorGridColor->color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, ui.kcbMinorGridColor->color()); m_initializing = false; } void AxisDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Axis" ); bool numeric = false; const auto* plot = dynamic_cast(m_axis->parentAspect()); if (plot) { numeric = ( (m_axis->orientation() == Axis::AxisHorizontal && plot->xRangeFormat() == CartesianPlot::Numeric) || (m_axis->orientation() == Axis::AxisVertical && plot->yRangeFormat() == CartesianPlot::Numeric) ); } //General group.writeEntry("Orientation", ui.cbOrientation->currentIndex()); if (ui.cbPosition->currentIndex() == 2) { group.writeEntry("Position", (int)Axis::AxisCentered); } else if (ui.cbPosition->currentIndex() == 3) { group.writeEntry("Position", (int)Axis::AxisCustom); } else { if ( ui.cbOrientation->currentIndex() == Axis::AxisHorizontal ) group.writeEntry("Position", ui.cbPosition->currentIndex()); else group.writeEntry("Position", ui.cbPosition->currentIndex()+2); } group.writeEntry("PositionOffset", ui.lePosition->text()); group.writeEntry("Scale", ui.cbScale->currentIndex()); group.writeEntry("Start", ui.leStart->text()); group.writeEntry("End", ui.leEnd->text()); group.writeEntry("ZeroOffset", ui.leZeroOffset->text()); group.writeEntry("ScalingFactor", ui.leScalingFactor->text()); //Title KConfigGroup axisLabelGroup = config.group("AxisLabel"); labelWidget->saveConfig(axisLabelGroup); //Line group.writeEntry("LineStyle", ui.cbLineStyle->currentIndex()); group.writeEntry("LineColor", ui.kcbLineColor->color()); - group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(), Worksheet::Point)); + group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(), Worksheet::Unit::Point)); group.writeEntry("LineOpacity", ui.sbLineOpacity->value()/100.); //Major ticks group.writeEntry("MajorTicksDirection", ui.cbMajorTicksDirection->currentIndex()); group.writeEntry("MajorTicksType", ui.cbMajorTicksType->currentIndex()); group.writeEntry("MajorTicksNumber", ui.sbMajorTicksNumber->value()); if (numeric) group.writeEntry("MajorTicksIncrement", QString::number(ui.sbMajorTicksSpacingNumeric->value())); else group.writeEntry("MajorTicksIncrement", QString::number(dtsbMajorTicksIncrement->value())); group.writeEntry("MajorTicksLineStyle", ui.cbMajorTicksLineStyle->currentIndex()); group.writeEntry("MajorTicksColor", ui.kcbMajorTicksColor->color()); - group.writeEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(ui.sbMajorTicksWidth->value(),Worksheet::Point)); - group.writeEntry("MajorTicksLength", Worksheet::convertToSceneUnits(ui.sbMajorTicksLength->value(),Worksheet::Point)); + group.writeEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(ui.sbMajorTicksWidth->value(), Worksheet::Unit::Point)); + group.writeEntry("MajorTicksLength", Worksheet::convertToSceneUnits(ui.sbMajorTicksLength->value(), Worksheet::Unit::Point)); group.writeEntry("MajorTicksOpacity", ui.sbMajorTicksOpacity->value()/100.); //Minor ticks group.writeEntry("MinorTicksDirection", ui.cbMinorTicksDirection->currentIndex()); group.writeEntry("MinorTicksType", ui.cbMinorTicksType->currentIndex()); group.writeEntry("MinorTicksNumber", ui.sbMinorTicksNumber->value()); if (numeric) group.writeEntry("MinorTicksIncrement", QString::number(ui.sbMinorTicksSpacingNumeric->value())); else group.writeEntry("MinorTicksIncrement", QString::number(dtsbMinorTicksIncrement->value())); group.writeEntry("MinorTicksLineStyle", ui.cbMinorTicksLineStyle->currentIndex()); group.writeEntry("MinorTicksColor", ui.kcbMinorTicksColor->color()); - group.writeEntry("MinorTicksWidth", Worksheet::convertFromSceneUnits(ui.sbMinorTicksWidth->value(),Worksheet::Point)); - group.writeEntry("MinorTicksLength", Worksheet::convertFromSceneUnits(ui.sbMinorTicksLength->value(),Worksheet::Point)); + group.writeEntry("MinorTicksWidth", Worksheet::convertFromSceneUnits(ui.sbMinorTicksWidth->value(), Worksheet::Unit::Point)); + group.writeEntry("MinorTicksLength", Worksheet::convertFromSceneUnits(ui.sbMinorTicksLength->value(), Worksheet::Unit::Point)); group.writeEntry("MinorTicksOpacity", ui.sbMinorTicksOpacity->value()/100.); //Extra ticks // TODO // Tick label group.writeEntry("LabelsFormat", ui.cbLabelsFormat->currentIndex()); group.writeEntry("LabelsAutoPrecision", ui.chkLabelsAutoPrecision->isChecked()); group.writeEntry("LabelsPrecision", ui.sbLabelsPrecision->value()); group.writeEntry("LabelsPosition", ui.cbLabelsPosition->currentIndex()); - group.writeEntry("LabelsOffset", Worksheet::convertToSceneUnits(ui.sbLabelsOffset->value(), Worksheet::Point)); + group.writeEntry("LabelsOffset", Worksheet::convertToSceneUnits(ui.sbLabelsOffset->value(), Worksheet::Unit::Point)); group.writeEntry("LabelsRotation", ui.sbLabelsRotation->value()); group.writeEntry("LabelsFont", ui.kfrLabelsFont->font()); group.writeEntry("LabelsFontColor", ui.kcbLabelsFontColor->color()); group.writeEntry("LabelsPrefix", ui.leLabelsPrefix->text()); group.writeEntry("LabelsSuffix", ui.leLabelsSuffix->text()); group.writeEntry("LabelsOpacity", ui.sbLabelsOpacity->value()/100.); //Grid group.writeEntry("MajorGridStyle", ui.cbMajorGridStyle->currentIndex()); group.writeEntry("MajorGridColor", ui.kcbMajorGridColor->color()); - group.writeEntry("MajorGridWidth", Worksheet::convertToSceneUnits(ui.sbMajorGridWidth->value(), Worksheet::Point)); + group.writeEntry("MajorGridWidth", Worksheet::convertToSceneUnits(ui.sbMajorGridWidth->value(), Worksheet::Unit::Point)); group.writeEntry("MajorGridOpacity", ui.sbMajorGridOpacity->value()/100.); group.writeEntry("MinorGridStyle", ui.cbMinorGridStyle->currentIndex()); group.writeEntry("MinorGridColor", ui.kcbMinorGridColor->color()); - group.writeEntry("MinorGridWidth", Worksheet::convertToSceneUnits(ui.sbMinorGridWidth->value(), Worksheet::Point)); + group.writeEntry("MinorGridWidth", Worksheet::convertToSceneUnits(ui.sbMinorGridWidth->value(), Worksheet::Unit::Point)); group.writeEntry("MinorGridOpacity", ui.sbMinorGridOpacity->value()/100.); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/BaseDock.cpp b/src/kdefrontend/dockwidgets/BaseDock.cpp index 2186fe281..d1842c201 100644 --- a/src/kdefrontend/dockwidgets/BaseDock.cpp +++ b/src/kdefrontend/dockwidgets/BaseDock.cpp @@ -1,66 +1,66 @@ /*************************************************************************** File : BaseDock.cpp Project : LabPlot Description : Base Dock widget -------------------------------------------------------------------- Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@gmail.com) Copyright : (C) 2019-2020 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 "BaseDock.h" #include "backend/core/AbstractAspect.h" #include #include #include BaseDock::BaseDock(QWidget* parent) : QWidget(parent) { const KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); m_units = (Units)group.readEntry("Units", (int)MetricUnits); if (m_units == ImperialUnits) - m_worksheetUnit = Worksheet::Inch; + m_worksheetUnit = Worksheet::Unit::Inch; } BaseDock::~BaseDock() = default; void BaseDock::nameChanged() { if (m_initializing || !m_aspect) return; if (!m_aspect->setName(m_leName->text(), false)) { m_leName->setStyleSheet("background:red;"); m_leName->setToolTip(i18n("Please choose another name, because this is already in use.")); return; } m_leName->setStyleSheet(""); m_leName->setToolTip(""); } void BaseDock::commentChanged() { if (m_initializing || !m_aspect) return; m_aspect->setComment(m_leComment->text()); } diff --git a/src/kdefrontend/dockwidgets/BaseDock.h b/src/kdefrontend/dockwidgets/BaseDock.h index 0ddee74d0..13355c724 100644 --- a/src/kdefrontend/dockwidgets/BaseDock.h +++ b/src/kdefrontend/dockwidgets/BaseDock.h @@ -1,79 +1,79 @@ /*************************************************************************** File : BaseDock.h Project : LabPlot Description : Base dock widget -------------------------------------------------------------------- Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@gmail.com) Copyright : (C) 2019-2020 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 * * * ***************************************************************************/ #ifndef BASEDOCK #define BASEDOCK #include "backend/worksheet/Worksheet.h" #include #include class AbstractAspect; struct Lock { inline explicit Lock(bool& variable) : variable(variable = true){ } inline ~Lock(){ variable = false; } private: bool& variable; }; class BaseDock : public QWidget { Q_OBJECT public: explicit BaseDock(QWidget* parent); ~BaseDock(); enum Units {MetricUnits, ImperialUnits}; virtual void updateUnits() {}; protected: bool m_initializing{false}; QLineEdit* m_leName{nullptr}; QLineEdit* m_leComment{nullptr}; AbstractAspect* m_aspect{nullptr}; QList m_aspects; Units m_units{MetricUnits}; - Worksheet::Unit m_worksheetUnit{Worksheet::Centimeter}; + Worksheet::Unit m_worksheetUnit{Worksheet::Unit::Centimeter}; protected slots: void nameChanged(); void commentChanged(); }; #endif diff --git a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp index 16759914f..a16df8979 100644 --- a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp @@ -1,1826 +1,1826 @@ /*************************************************************************** File : CartesianPlotDock.cpp Project : LabPlot Description : widget for cartesian plot properties -------------------------------------------------------------------- Copyright : (C) 2011-2020 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2013 by Stefan Gerlach (stefan.gerlach@uni-konstanz.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 "CartesianPlotDock.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/core/column/Column.h" #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include #include /*! \class CartesianPlotDock \brief Provides a widget for editing the properties of the cartesian plot currently selected in the project explorer. \ingroup kdefrontend */ CartesianPlotDock::CartesianPlotDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; //"General"-tab auto* rangeButtonsGroup(new QButtonGroup); rangeButtonsGroup->addButton(ui.rbRangeFirst); rangeButtonsGroup->addButton(ui.rbRangeLast); rangeButtonsGroup->addButton(ui.rbRangeFree); //"Range breaks"-tab ui.bAddXBreak->setIcon( QIcon::fromTheme("list-add") ); ui.bRemoveXBreak->setIcon( QIcon::fromTheme("list-remove") ); ui.cbXBreak->addItem("1"); ui.bAddYBreak->setIcon( QIcon::fromTheme("list-add") ); ui.bRemoveYBreak->setIcon( QIcon::fromTheme("list-remove") ); ui.cbYBreak->addItem("1"); //"Background"-tab ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leBackgroundFileName->setCompleter(new QCompleter(new QDirModel, this)); //"Title"-tab auto* hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget = new LabelWidget(ui.tabTitle); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { auto* layout = qobject_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } // "Cursor"-tab QStringList list = {i18n("NoPen"), i18n("SolidLine"), i18n("DashLine"), i18n("DotLine"), i18n("DashDotLine"), i18n("DashDotDotLine")}; ui.cbCursorLineStyle->clear(); for (int i = 0; i < list.count(); i++) ui.cbCursorLineStyle->addItem(list[i], i); //Validators ui.leRangeFirst->setValidator( new QIntValidator(ui.leRangeFirst) ); ui.leRangeLast->setValidator( new QIntValidator(ui.leRangeLast) ); ui.leXBreakStart->setValidator( new QDoubleValidator(ui.leXBreakStart) ); ui.leXBreakEnd->setValidator( new QDoubleValidator(ui.leXBreakEnd) ); ui.leYBreakStart->setValidator( new QDoubleValidator(ui.leYBreakStart) ); ui.leYBreakEnd->setValidator( new QDoubleValidator(ui.leYBreakEnd) ); //SIGNAL/SLOT //General connect(ui.leName, &QLineEdit::textChanged, this, &CartesianPlotDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &CartesianPlotDock::commentChanged); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( ui.sbLeft, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbTop, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbWidth, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbHeight, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.leRangeFirst, SIGNAL(textChanged(QString)), this, SLOT(rangeFirstChanged(QString)) ); connect( ui.leRangeLast, SIGNAL(textChanged(QString)), this, SLOT(rangeLastChanged(QString)) ); connect( rangeButtonsGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(rangeTypeChanged()) ); connect(ui.chkAutoScaleX, &QCheckBox::stateChanged, this, &CartesianPlotDock::autoScaleXChanged); connect(ui.leXMin, &QLineEdit::textChanged, this, &CartesianPlotDock::xMinChanged); connect(ui.leXMax, &QLineEdit::textChanged, this, &CartesianPlotDock::xMaxChanged); connect(ui.dateTimeEditXMin, &QDateTimeEdit::dateTimeChanged, this, &CartesianPlotDock::xMinDateTimeChanged); connect(ui.dateTimeEditXMax, &QDateTimeEdit::dateTimeChanged, this, &CartesianPlotDock::xMaxDateTimeChanged); connect( ui.cbXScaling, SIGNAL(currentIndexChanged(int)), this, SLOT(xScaleChanged(int)) ); connect(ui.cbXRangeFormat, static_cast(&QComboBox::currentIndexChanged), this, &CartesianPlotDock::xRangeFormatChanged); connect(ui.chkAutoScaleY, &QCheckBox::stateChanged, this, &CartesianPlotDock::autoScaleYChanged); connect(ui.leYMin, &QLineEdit::textChanged, this, &CartesianPlotDock::yMinChanged); connect(ui.leYMax, &QLineEdit::textChanged, this, &CartesianPlotDock::yMaxChanged); connect(ui.dateTimeEditYMin, &QDateTimeEdit::dateTimeChanged, this, &CartesianPlotDock::yMinDateTimeChanged); connect(ui.dateTimeEditYMax, &QDateTimeEdit::dateTimeChanged, this, &CartesianPlotDock::yMaxDateTimeChanged); connect( ui.cbYScaling, SIGNAL(currentIndexChanged(int)), this, SLOT(yScaleChanged(int)) ); connect(ui.cbYRangeFormat, static_cast(&QComboBox::currentIndexChanged), this, &CartesianPlotDock::yRangeFormatChanged); //Range breaks connect( ui.chkXBreak, SIGNAL(toggled(bool)), this, SLOT(toggleXBreak(bool)) ); connect( ui.bAddXBreak, SIGNAL(clicked()), this, SLOT(addXBreak()) ); connect( ui.bRemoveXBreak, SIGNAL(clicked()), this, SLOT(removeXBreak()) ); connect( ui.cbXBreak, SIGNAL(currentIndexChanged(int)), this, SLOT(currentXBreakChanged(int)) ); connect( ui.leXBreakStart, SIGNAL(textChanged(QString)), this, SLOT(xBreakStartChanged()) ); connect( ui.leXBreakEnd, SIGNAL(textChanged(QString)), this, SLOT(xBreakEndChanged()) ); connect( ui.sbXBreakPosition, SIGNAL(valueChanged(int)), this, SLOT(xBreakPositionChanged(int)) ); connect( ui.cbXBreakStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(xBreakStyleChanged(int)) ); connect( ui.chkYBreak, SIGNAL(toggled(bool)), this, SLOT(toggleYBreak(bool)) ); connect( ui.bAddYBreak, SIGNAL(clicked()), this, SLOT(addYBreak()) ); connect( ui.bRemoveYBreak, SIGNAL(clicked()), this, SLOT(removeYBreak()) ); connect( ui.cbYBreak, SIGNAL(currentIndexChanged(int)), this, SLOT(currentYBreakChanged(int)) ); connect( ui.leYBreakStart, SIGNAL(textChanged(QString)), this, SLOT(yBreakStartChanged()) ); connect( ui.leYBreakEnd, SIGNAL(textChanged(QString)), this, SLOT(yBreakEndChanged()) ); connect( ui.sbYBreakPosition, SIGNAL(valueChanged(int)), this, SLOT(yBreakPositionChanged(int)) ); connect( ui.cbYBreakStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(yBreakStyleChanged(int)) ); //Background connect( ui.cbBackgroundType, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundTypeChanged(int)) ); connect( ui.cbBackgroundColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundColorStyleChanged(int)) ); connect( ui.cbBackgroundImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundImageStyleChanged(int)) ); connect( ui.cbBackgroundBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundBrushStyleChanged(int)) ); connect( ui.bOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile()) ); connect( ui.leBackgroundFileName, SIGNAL(textChanged(QString)), this, SLOT(fileNameChanged()) ); connect( ui.leBackgroundFileName, SIGNAL(textChanged(QString)), this, SLOT(fileNameChanged()) ); connect( ui.kcbBackgroundFirstColor, SIGNAL(changed(QColor)), this, SLOT(backgroundFirstColorChanged(QColor)) ); connect( ui.kcbBackgroundSecondColor, SIGNAL(changed(QColor)), this, SLOT(backgroundSecondColorChanged(QColor)) ); connect( ui.sbBackgroundOpacity, SIGNAL(valueChanged(int)), this, SLOT(backgroundOpacityChanged(int)) ); //Border connect( ui.cbBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(borderStyleChanged(int)) ); connect( ui.kcbBorderColor, SIGNAL(changed(QColor)), this, SLOT(borderColorChanged(QColor)) ); connect( ui.sbBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(borderWidthChanged(double)) ); connect( ui.sbBorderCornerRadius, SIGNAL(valueChanged(double)), this, SLOT(borderCornerRadiusChanged(double)) ); connect( ui.sbBorderOpacity, SIGNAL(valueChanged(int)), this, SLOT(borderOpacityChanged(int)) ); //Padding connect( ui.sbPaddingHorizontal, SIGNAL(valueChanged(double)), this, SLOT(horizontalPaddingChanged(double)) ); connect( ui.sbPaddingVertical, SIGNAL(valueChanged(double)), this, SLOT(verticalPaddingChanged(double)) ); connect( ui.sbPaddingRight, static_cast(&QDoubleSpinBox::valueChanged), this, &CartesianPlotDock::rightPaddingChanged); connect( ui.sbPaddingBottom, static_cast(&QDoubleSpinBox::valueChanged), this, &CartesianPlotDock::bottomPaddingChanged); connect( ui.cbPaddingSymmetric, &QCheckBox::toggled, this, &CartesianPlotDock::symmetricPaddingChanged); // Cursor connect(ui.sbCursorLineWidth, SIGNAL(valueChanged(int)), this, SLOT(cursorLineWidthChanged(int))); //connect(ui.sbCursorLineWidth, qOverload(&QDoubleSpinBox::valueChanged), this, &CartesianPlotDock::cursorLineWidthChanged); connect(ui.kcbCursorLineColor, &KColorButton::changed, this, &CartesianPlotDock::cursorLineColorChanged); //connect(ui.cbCursorLineStyle, qOverload(&QComboBox::currentIndexChanged), this, &CartesianPlotDock::cursorLineStyleChanged); connect(ui.cbCursorLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(cursorLineStyleChanged(int))); //theme and template handlers auto* frame = new QFrame(this); auto* layout = new QHBoxLayout(frame); layout->setContentsMargins(0, 11, 0, 11); m_themeHandler = new ThemeHandler(this); layout->addWidget(m_themeHandler); connect(m_themeHandler, SIGNAL(loadThemeRequested(QString)), this, SLOT(loadTheme(QString))); connect(m_themeHandler, SIGNAL(saveThemeRequested(KConfig&)), this, SLOT(saveTheme(KConfig&))); connect(m_themeHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); //connect(this, SIGNAL(saveThemeEnable(bool)), m_themeHandler, SLOT(saveThemeEnable(bool))); auto* templateHandler = new TemplateHandler(this, TemplateHandler::CartesianPlot); layout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); ui.verticalLayout->addWidget(frame); //TODO: activate the tab again once the functionality is implemented ui.tabWidget->removeTab(2); init(); } void CartesianPlotDock::init() { this->retranslateUi(); /* //TODO: activate later once range breaking is implemented //create icons for the different styles for scale breaking QPainter pa; pa.setPen( QPen(Qt::SolidPattern, 0) ); QPixmap pm(20, 20); ui.cbXBreakStyle->setIconSize( QSize(20,20) ); ui.cbYBreakStyle->setIconSize( QSize(20,20) ); //simple pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.end(); ui.cbXBreakStyle->setItemIcon(0, pm); ui.cbYBreakStyle->setItemIcon(0, pm); //vertical pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.drawLine(8,14,8,6); pa.drawLine(12,14,12,6); pa.end(); ui.cbXBreakStyle->setItemIcon(1, pm); ui.cbYBreakStyle->setItemIcon(1, pm); //sloped pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.drawLine(6,14,10,6); pa.drawLine(10,14,14,6); pa.end(); ui.cbXBreakStyle->setItemIcon(2, pm); ui.cbYBreakStyle->setItemIcon(2, pm); */ } void CartesianPlotDock::setPlots(QList list) { m_initializing = true; m_plotList = list; m_plot = list.first(); m_aspect = list.first(); QList labels; for (auto* plot : list) labels.append(plot->title()); labelWidget->setLabels(labels); //if there is more then one plot in the list, disable the name and comment fields in the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_plot->name()); ui.leComment->setText(m_plot->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } bool symmetric = m_plot->symmetricPadding(); ui.lPaddingHorizontalRight->setVisible(!symmetric); ui.sbPaddingRight->setVisible(!symmetric); ui.lPaddingVerticalDown->setVisible(!symmetric); ui.sbPaddingBottom->setVisible(!symmetric); if (symmetric) { ui.lPaddingHorizontal->setText(i18n("Horizontal")); ui.lPaddingVertical->setText(i18n("Vertical")); } else { ui.lPaddingHorizontal->setText(i18n("Left")); ui.lPaddingVertical->setText(i18n("Top")); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first plot this->load(); //update active widgets backgroundTypeChanged(ui.cbBackgroundType->currentIndex()); m_themeHandler->setCurrentTheme(m_plot->theme()); //Deactivate the geometry related widgets, if the worksheet layout is active. //Currently, a plot can only be a child of the worksheet itself, so we only need to ask the parent aspect (=worksheet). //TODO redesign this, if the hierarchy will be changend in future (a plot is a child of a new object group/container or so) auto* w = dynamic_cast(m_plot->parentAspect()); if (w) { - bool b = (w->layout() == Worksheet::NoLayout); + bool b = (w->layout() == Worksheet::Layout::NoLayout); ui.sbTop->setEnabled(b); ui.sbLeft->setEnabled(b); ui.sbWidth->setEnabled(b); ui.sbHeight->setEnabled(b); connect(w, SIGNAL(layoutChanged(Worksheet::Layout)), this, SLOT(layoutChanged(Worksheet::Layout))); } //SIGNALs/SLOTs connect( m_plot, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(plotDescriptionChanged(const AbstractAspect*)) ); connect( m_plot, SIGNAL(rectChanged(QRectF&)), this, SLOT(plotRectChanged(QRectF&)) ); connect( m_plot, SIGNAL(rangeTypeChanged(CartesianPlot::RangeType)), this, SLOT(plotRangeTypeChanged(CartesianPlot::RangeType)) ); connect( m_plot, SIGNAL(rangeFirstValuesChanged(int)), this, SLOT(plotRangeFirstValuesChanged(int)) ); connect( m_plot, SIGNAL(rangeLastValuesChanged(int)), this, SLOT(plotRangeLastValuesChanged(int)) ); connect( m_plot, SIGNAL(xAutoScaleChanged(bool)), this, SLOT(plotXAutoScaleChanged(bool)) ); connect( m_plot, SIGNAL(xMinChanged(double)), this, SLOT(plotXMinChanged(double)) ); connect( m_plot, SIGNAL(xMaxChanged(double)), this, SLOT(plotXMaxChanged(double)) ); connect( m_plot, SIGNAL(xScaleChanged(int)), this, SLOT(plotXScaleChanged(int)) ); connect(m_plot, &CartesianPlot::xRangeFormatChanged, this, &CartesianPlotDock::plotXRangeFormatChanged); connect( m_plot, SIGNAL(yAutoScaleChanged(bool)), this, SLOT(plotYAutoScaleChanged(bool)) ); connect( m_plot, SIGNAL(yMinChanged(double)), this, SLOT(plotYMinChanged(double)) ); connect( m_plot, SIGNAL(yMaxChanged(double)), this, SLOT(plotYMaxChanged(double)) ); connect( m_plot, SIGNAL(yScaleChanged(int)), this, SLOT(plotYScaleChanged(int)) ); connect(m_plot, &CartesianPlot::yRangeFormatChanged, this, &CartesianPlotDock::plotYRangeFormatChanged); connect( m_plot, SIGNAL(visibleChanged(bool)), this, SLOT(plotVisibleChanged(bool)) ); //range breaks connect( m_plot, SIGNAL(xRangeBreakingEnabledChanged(bool)), this, SLOT(plotXRangeBreakingEnabledChanged(bool)) ); connect( m_plot, SIGNAL(xRangeBreaksChanged(CartesianPlot::RangeBreaks)), this, SLOT(plotXRangeBreaksChanged(CartesianPlot::RangeBreaks)) ); connect( m_plot, SIGNAL(yRangeBreakingEnabledChanged(bool)), this, SLOT(plotYRangeBreakingEnabledChanged(bool)) ); connect( m_plot, SIGNAL(yRangeBreaksChanged(CartesianPlot::RangeBreaks)), this, SLOT(plotYRangeBreaksChanged(CartesianPlot::RangeBreaks)) ); // Plot Area connect( m_plot->plotArea(), SIGNAL(backgroundTypeChanged(PlotArea::BackgroundType)), this, SLOT(plotBackgroundTypeChanged(PlotArea::BackgroundType)) ); connect( m_plot->plotArea(), SIGNAL(backgroundColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(plotBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(plotBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundBrushStyleChanged(Qt::BrushStyle)), this, SLOT(plotBackgroundBrushStyleChanged(Qt::BrushStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundFirstColorChanged(QColor&)), this, SLOT(plotBackgroundFirstColorChanged(QColor&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundSecondColorChanged(QColor&)), this, SLOT(plotBackgroundSecondColorChanged(QColor&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundFileNameChanged(QString&)), this, SLOT(plotBackgroundFileNameChanged(QString&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundOpacityChanged(float)), this, SLOT(plotBackgroundOpacityChanged(float)) ); connect( m_plot->plotArea(), SIGNAL(borderPenChanged(QPen&)), this, SLOT(plotBorderPenChanged(QPen&)) ); connect( m_plot->plotArea(), SIGNAL(borderOpacityChanged(float)), this, SLOT(plotBorderOpacityChanged(float)) ); connect( m_plot, SIGNAL(horizontalPaddingChanged(float)), this, SLOT(plotHorizontalPaddingChanged(float)) ); connect( m_plot, SIGNAL(verticalPaddingChanged(float)), this, SLOT(plotVerticalPaddingChanged(float)) ); connect(m_plot, &CartesianPlot::rightPaddingChanged, this, &CartesianPlotDock::plotRightPaddingChanged); connect(m_plot, &CartesianPlot::bottomPaddingChanged, this, &CartesianPlotDock::plotBottomPaddingChanged); connect(m_plot, &CartesianPlot::symmetricPaddingChanged, this, &CartesianPlotDock::plotSymmetricPaddingChanged); m_initializing = false; } void CartesianPlotDock::activateTitleTab() { ui.tabWidget->setCurrentWidget(ui.tabTitle); } void CartesianPlotDock::updateUnits() { const KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); BaseDock::Units units = (BaseDock::Units)group.readEntry("Units", (int)MetricUnits); if (units == m_units) return; m_units = units; Lock lock(m_initializing); QString suffix; if (m_units == BaseDock::MetricUnits) { //convert from imperial to metric - m_worksheetUnit = Worksheet::Centimeter; + m_worksheetUnit = Worksheet::Unit::Centimeter; suffix = QLatin1String("cm"); ui.sbLeft->setValue(ui.sbLeft->value()*2.54); ui.sbTop->setValue(ui.sbTop->value()*2.54); ui.sbWidth->setValue(ui.sbWidth->value()*2.54); ui.sbHeight->setValue(ui.sbHeight->value()*2.54); ui.sbBorderCornerRadius->setValue(ui.sbBorderCornerRadius->value()*2.54); ui.sbPaddingHorizontal->setValue(ui.sbPaddingHorizontal->value()*2.54); ui.sbPaddingVertical->setValue(ui.sbPaddingVertical->value()*2.54); ui.sbPaddingRight->setValue(ui.sbPaddingRight->value()*2.54); ui.sbPaddingBottom->setValue(ui.sbPaddingBottom->value()*2.54); } else { //convert from metric to imperial - m_worksheetUnit = Worksheet::Inch; + m_worksheetUnit = Worksheet::Unit::Inch; suffix = QLatin1String("in"); ui.sbLeft->setValue(ui.sbLeft->value()/2.54); ui.sbTop->setValue(ui.sbTop->value()/2.54); ui.sbWidth->setValue(ui.sbWidth->value()/2.54); ui.sbHeight->setValue(ui.sbHeight->value()/2.54); ui.sbBorderCornerRadius->setValue(ui.sbBorderCornerRadius->value()/2.54); ui.sbPaddingHorizontal->setValue(ui.sbPaddingHorizontal->value()/2.54); ui.sbPaddingVertical->setValue(ui.sbPaddingVertical->value()/2.54); ui.sbPaddingRight->setValue(ui.sbPaddingRight->value()/2.54); ui.sbPaddingBottom->setValue(ui.sbPaddingBottom->value()/2.54); } ui.sbLeft->setSuffix(suffix); ui.sbTop->setSuffix(suffix); ui.sbWidth->setSuffix(suffix); ui.sbHeight->setSuffix(suffix); ui.sbBorderCornerRadius->setSuffix(suffix); ui.sbPaddingHorizontal->setSuffix(suffix); ui.sbPaddingVertical->setSuffix(suffix); ui.sbPaddingRight->setSuffix(suffix); ui.sbPaddingBottom->setSuffix(suffix); labelWidget->updateUnits(); } //************************************************************ //**** SLOTs for changes triggered in CartesianPlotDock ****** //************************************************************ void CartesianPlotDock::retranslateUi() { Lock lock(m_initializing); //general ui.cbXRangeFormat->addItem(i18n("numeric")); ui.cbXRangeFormat->addItem(i18n("datetime")); ui.cbYRangeFormat->addItem(i18n("numeric")); ui.cbYRangeFormat->addItem(i18n("datetime")); ui.cbXScaling->addItem( i18n("linear") ); ui.cbXScaling->addItem( i18n("log(x)") ); ui.cbXScaling->addItem( i18n("log2(x)") ); ui.cbXScaling->addItem( i18n("ln(x)") ); ui.cbXScaling->addItem( i18n("log(abs(x))") ); ui.cbXScaling->addItem( i18n("log2(abs(x))") ); ui.cbXScaling->addItem( i18n("ln(abs(x))") ); ui.cbYScaling->addItem( i18n("linear") ); ui.cbYScaling->addItem( i18n("log(y)") ); ui.cbYScaling->addItem( i18n("log2(y)") ); ui.cbYScaling->addItem( i18n("ln(y)") ); ui.cbYScaling->addItem( i18n("log(abs(y))") ); ui.cbYScaling->addItem( i18n("log2(abs(y))") ); ui.cbYScaling->addItem( i18n("ln(abs(y))") ); //scale breakings ui.cbXBreakStyle->addItem( i18n("Simple") ); ui.cbXBreakStyle->addItem( i18n("Vertical") ); ui.cbXBreakStyle->addItem( i18n("Sloped") ); ui.cbYBreakStyle->addItem( i18n("Simple") ); ui.cbYBreakStyle->addItem( i18n("Vertical") ); ui.cbYBreakStyle->addItem( i18n("Sloped") ); //plot area ui.cbBackgroundType->addItem(i18n("Color")); ui.cbBackgroundType->addItem(i18n("Image")); ui.cbBackgroundType->addItem(i18n("Pattern")); ui.cbBackgroundColorStyle->addItem(i18n("Single Color")); ui.cbBackgroundColorStyle->addItem(i18n("Horizontal Gradient")); ui.cbBackgroundColorStyle->addItem(i18n("Vertical Gradient")); ui.cbBackgroundColorStyle->addItem(i18n("Diag. Gradient (From Top Left)")); ui.cbBackgroundColorStyle->addItem(i18n("Diag. Gradient (From Bottom Left)")); ui.cbBackgroundColorStyle->addItem(i18n("Radial Gradient")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled and Cropped")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled, Keep Proportions")); ui.cbBackgroundImageStyle->addItem(i18n("Centered")); ui.cbBackgroundImageStyle->addItem(i18n("Tiled")); ui.cbBackgroundImageStyle->addItem(i18n("Center Tiled")); GuiTools::updatePenStyles(ui.cbBorderStyle, Qt::black); GuiTools::updateBrushStyles(ui.cbBackgroundBrushStyle, Qt::SolidPattern); QString suffix; if (m_units == BaseDock::MetricUnits) suffix = QLatin1String("cm"); else suffix = QLatin1String("in"); ui.sbLeft->setSuffix(suffix); ui.sbTop->setSuffix(suffix); ui.sbWidth->setSuffix(suffix); ui.sbHeight->setSuffix(suffix); ui.sbBorderCornerRadius->setSuffix(suffix); ui.sbPaddingHorizontal->setSuffix(suffix); ui.sbPaddingVertical->setSuffix(suffix); ui.sbPaddingRight->setSuffix(suffix); ui.sbPaddingBottom->setSuffix(suffix); } // "General"-tab void CartesianPlotDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* plot : m_plotList) plot->setVisible(state); } void CartesianPlotDock::geometryChanged() { if (m_initializing) return; float x = Worksheet::convertToSceneUnits(ui.sbLeft->value(), m_worksheetUnit); float y = Worksheet::convertToSceneUnits(ui.sbTop->value(), m_worksheetUnit); float w = Worksheet::convertToSceneUnits(ui.sbWidth->value(), m_worksheetUnit); float h = Worksheet::convertToSceneUnits(ui.sbHeight->value(), m_worksheetUnit); - QRectF rect(x,y,w,h); + QRectF rect(x, y, w, h); m_plot->setRect(rect); } /*! Called when the layout in the worksheet gets changed. Enables/disables the geometry widgets if the layout was deactivated/activated. Shows the new geometry values of the first plot if the layout was activated. */ void CartesianPlotDock::layoutChanged(Worksheet::Layout layout) { - bool b = (layout == Worksheet::NoLayout); + bool b = (layout == Worksheet::Layout::NoLayout); ui.sbTop->setEnabled(b); ui.sbLeft->setEnabled(b); ui.sbWidth->setEnabled(b); ui.sbHeight->setEnabled(b); } void CartesianPlotDock::rangeTypeChanged() { CartesianPlot::RangeType type; if (ui.rbRangeFirst->isChecked()) { ui.leRangeFirst->setEnabled(true); ui.leRangeLast->setEnabled(false); type = CartesianPlot::RangeFirst; } else if (ui.rbRangeLast->isChecked()) { ui.leRangeFirst->setEnabled(false); ui.leRangeLast->setEnabled(true); type = CartesianPlot::RangeLast; } else { ui.leRangeFirst->setEnabled(false); ui.leRangeLast->setEnabled(false); type = CartesianPlot::RangeFree; } if (m_initializing) return; for (auto* plot : m_plotList) plot->setRangeType(type); } void CartesianPlotDock::rangeFirstChanged(const QString& text) { if (m_initializing) return; const int value = text.toInt(); for (auto* plot : m_plotList) plot->setRangeFirstValues(value); } void CartesianPlotDock::rangeLastChanged(const QString& text) { if (m_initializing) return; const int value = text.toInt(); for (auto* plot : m_plotList) plot->setRangeLastValues(value); } void CartesianPlotDock::autoScaleXChanged(int state) { bool checked = (state == Qt::Checked); ui.cbXRangeFormat->setEnabled(!checked); ui.leXMin->setEnabled(!checked); ui.leXMax->setEnabled(!checked); ui.dateTimeEditXMin->setEnabled(!checked); ui.dateTimeEditXMax->setEnabled(!checked); if (m_initializing) return; for (auto* plot : m_plotList) plot->setAutoScaleX(checked); } void CartesianPlotDock::xMinChanged(const QString& value) { if (m_initializing) return; const Lock lock(m_initializing); const float min = value.toDouble(); for (auto* plot : m_plotList) plot->setXMin(min); } void CartesianPlotDock::xMaxChanged(const QString& value) { if (m_initializing) return; const Lock lock(m_initializing); const float max = value.toDouble(); for (auto* plot : m_plotList) plot->setXMax(max); } void CartesianPlotDock::xMinDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* plot : m_plotList) plot->setXMin(value); } void CartesianPlotDock::xMaxDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* plot : m_plotList) plot->setXMax(value); } /*! called on scale changes (linear, log) for the x-axis */ void CartesianPlotDock::xScaleChanged(int scale) { if (m_initializing) return; for (auto* plot : m_plotList) plot->setXScale((CartesianPlot::Scale) scale); } void CartesianPlotDock::xRangeFormatChanged(int index) { bool numeric = (index == 0); ui.lXMin->setVisible(numeric); ui.leXMin->setVisible(numeric); ui.lXMax->setVisible(numeric); ui.leXMax->setVisible(numeric); ui.lXMinDateTime->setVisible(!numeric); ui.dateTimeEditXMin->setVisible(!numeric); ui.lXMaxDateTime->setVisible(!numeric); ui.dateTimeEditXMax->setVisible(!numeric); if (m_initializing) return; auto format = (CartesianPlot::RangeFormat)index; for (auto* plot : m_plotList) plot->setXRangeFormat(format); } void CartesianPlotDock::autoScaleYChanged(int state) { bool checked = (state == Qt::Checked); ui.cbYRangeFormat->setEnabled(!checked); ui.leYMin->setEnabled(!checked); ui.leYMax->setEnabled(!checked); ui.dateTimeEditYMin->setEnabled(!checked); ui.dateTimeEditYMax->setEnabled(!checked); if (m_initializing) return; for (auto* plot : m_plotList) plot->setAutoScaleY(checked); } void CartesianPlotDock::yMinChanged(const QString& value) { if (m_initializing) return; const Lock lock(m_initializing); const float min = value.toDouble(); for (auto* plot : m_plotList) plot->setYMin(min); } void CartesianPlotDock::yMaxChanged(const QString& value) { if (m_initializing) return; const Lock lock(m_initializing); const float max = value.toDouble(); for (auto* plot : m_plotList) plot->setYMax(max); } void CartesianPlotDock::yMinDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* plot : m_plotList) plot->setXMin(value); } void CartesianPlotDock::yMaxDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; quint64 value = dateTime.toMSecsSinceEpoch(); for (auto* plot : m_plotList) plot->setXMax(value); } /*! called on scale changes (linear, log) for the y-axis */ void CartesianPlotDock::yScaleChanged(int index) { if (m_initializing) return; auto scale = (CartesianPlot::Scale)index; for (auto* plot : m_plotList) plot->setYScale(scale); } void CartesianPlotDock::yRangeFormatChanged(int index) { bool numeric = (index == 0); ui.lYMin->setVisible(numeric); ui.leYMin->setVisible(numeric); ui.lYMax->setVisible(numeric); ui.leYMax->setVisible(numeric); ui.lYMinDateTime->setVisible(!numeric); ui.dateTimeEditYMin->setVisible(!numeric); ui.lYMaxDateTime->setVisible(!numeric); ui.dateTimeEditYMax->setVisible(!numeric); if (m_initializing) return; auto format = (CartesianPlot::RangeFormat)index; for (auto* plot : m_plotList) plot->setYRangeFormat(format); } // "Range Breaks"-tab // x-range breaks void CartesianPlotDock::toggleXBreak(bool b) { ui.frameXBreakEdit->setEnabled(b); ui.leXBreakStart->setEnabled(b); ui.leXBreakEnd->setEnabled(b); ui.sbXBreakPosition->setEnabled(b); ui.cbXBreakStyle->setEnabled(b); if (m_initializing) return; for (auto* plot : m_plotList) plot->setXRangeBreakingEnabled(b); } void CartesianPlotDock::addXBreak() { ui.bRemoveXBreak->setVisible(true); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); CartesianPlot::RangeBreak b; breaks.list<setXRangeBreaks(breaks); ui.cbXBreak->addItem(QString::number(ui.cbXBreak->count()+1)); ui.cbXBreak->setCurrentIndex(ui.cbXBreak->count()-1); } void CartesianPlotDock::removeXBreak() { ui.bRemoveXBreak->setVisible(m_plot->xRangeBreaks().list.size()>1); int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list.takeAt(index); breaks.lastChanged = -1; for (auto* plot : m_plotList) plot->setXRangeBreaks(breaks); ui.cbXBreak->clear(); for (int i = 1; i <= breaks.list.size(); ++i) ui.cbXBreak->addItem(QString::number(i)); if (index < ui.cbXBreak->count()-1) ui.cbXBreak->setCurrentIndex(index); else ui.cbXBreak->setCurrentIndex(ui.cbXBreak->count()-1); ui.bRemoveXBreak->setVisible(ui.cbXBreak->count()!=1); } void CartesianPlotDock::currentXBreakChanged(int index) { if (m_initializing) return; if (index == -1) return; m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->xRangeBreaks().list.at(index); QString str = std::isnan(rangeBreak.start) ? QString() : QString::number(rangeBreak.start); ui.leXBreakStart->setText(str); str = std::isnan(rangeBreak.end) ? QString() : QString::number(rangeBreak.end); ui.leXBreakEnd->setText(str); ui.sbXBreakPosition->setValue(rangeBreak.position*100); ui.cbXBreakStyle->setCurrentIndex((int)rangeBreak.style); m_initializing = false; } void CartesianPlotDock::xBreakStartChanged() { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].start = ui.leXBreakStart->text().toDouble(); breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakEndChanged() { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].end = ui.leXBreakEnd->text().toDouble(); breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakPositionChanged(int value) { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].position = (float)value/100.; breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakStyleChanged(int styleIndex) { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); auto style = CartesianPlot::RangeBreakStyle(styleIndex); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].style = style; breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setXRangeBreaks(breaks); } // y-range breaks void CartesianPlotDock::toggleYBreak(bool b) { ui.frameYBreakEdit->setEnabled(b); ui.leYBreakStart->setEnabled(b); ui.leYBreakEnd->setEnabled(b); ui.sbYBreakPosition->setEnabled(b); ui.cbYBreakStyle->setEnabled(b); if (m_initializing) return; for (auto* plot : m_plotList) plot->setYRangeBreakingEnabled(b); } void CartesianPlotDock::addYBreak() { ui.bRemoveYBreak->setVisible(true); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); CartesianPlot::RangeBreak b; breaks.list << b; breaks.lastChanged = breaks.list.size() - 1; for (auto* plot : m_plotList) plot->setYRangeBreaks(breaks); ui.cbYBreak->addItem(QString::number(ui.cbYBreak->count()+1)); ui.cbYBreak->setCurrentIndex(ui.cbYBreak->count()-1); } void CartesianPlotDock::removeYBreak() { ui.bRemoveYBreak->setVisible(m_plot->yRangeBreaks().list.size()>1); int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list.takeAt(index); breaks.lastChanged = -1; for (auto* plot : m_plotList) plot->setYRangeBreaks(breaks); ui.cbYBreak->clear(); for (int i = 1; i <= breaks.list.size(); ++i) ui.cbYBreak->addItem(QString::number(i)); if (index < ui.cbYBreak->count()-1) ui.cbYBreak->setCurrentIndex(index); else ui.cbYBreak->setCurrentIndex(ui.cbYBreak->count()-1); ui.bRemoveYBreak->setVisible(ui.cbYBreak->count() != 1); } void CartesianPlotDock::currentYBreakChanged(int index) { if (m_initializing) return; if (index == -1) return; m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->yRangeBreaks().list.at(index); QString str = std::isnan(rangeBreak.start) ? QString() : QString::number(rangeBreak.start); ui.leYBreakStart->setText(str); str = std::isnan(rangeBreak.end) ? QString() : QString::number(rangeBreak.end); ui.leYBreakEnd->setText(str); ui.sbYBreakPosition->setValue(rangeBreak.position*100); ui.cbYBreakStyle->setCurrentIndex((int)rangeBreak.style); m_initializing = false; } void CartesianPlotDock::yBreakStartChanged() { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].start = ui.leYBreakStart->text().toDouble(); breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakEndChanged() { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].end = ui.leYBreakEnd->text().toDouble(); breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakPositionChanged(int value) { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].position = (float)value/100.; breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakStyleChanged(int styleIndex) { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); auto style = CartesianPlot::RangeBreakStyle(styleIndex); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].style = style; breaks.lastChanged = index; for (auto* plot : m_plotList) plot->setYRangeBreaks(breaks); } // "Plot area"-tab void CartesianPlotDock::backgroundTypeChanged(int index) { auto type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lBackgroundColorStyle->show(); ui.cbBackgroundColorStyle->show(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); auto style = (PlotArea::BackgroundColorStyle) ui.cbBackgroundColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color:")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->show(); ui.cbBackgroundImageStyle->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->show(); ui.leBackgroundFileName->show(); ui.bOpen->show(); ui.lBackgroundFirstColor->hide(); ui.kcbBackgroundFirstColor->hide(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->show(); ui.cbBackgroundBrushStyle->show(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } if (m_initializing) return; for (auto* plot : m_plotList) plot->plotArea()->setBackgroundType(type); } void CartesianPlotDock::backgroundColorStyleChanged(int index) { auto style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color:")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); } if (m_initializing) return; for (auto* plot : m_plotList) plot->plotArea()->setBackgroundColorStyle(style); } void CartesianPlotDock::backgroundImageStyleChanged(int index) { if (m_initializing) return; auto style = (PlotArea::BackgroundImageStyle)index; for (auto* plot : m_plotList) plot->plotArea()->setBackgroundImageStyle(style); } void CartesianPlotDock::backgroundBrushStyleChanged(int index) { if (m_initializing) return; auto style = (Qt::BrushStyle)index; for (auto* plot : m_plotList) plot->plotArea()->setBackgroundBrushStyle(style); } void CartesianPlotDock::backgroundFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* plot : m_plotList) plot->plotArea()->setBackgroundFirstColor(c); } void CartesianPlotDock::backgroundSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* plot : m_plotList) plot->plotArea()->setBackgroundSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void CartesianPlotDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "CartesianPlotDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const auto& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += ' ' + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leBackgroundFileName->setText( path ); for (auto* plot : m_plotList) plot->plotArea()->setBackgroundFileName(path); } void CartesianPlotDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leBackgroundFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(QString()); for (auto* plot : m_plotList) plot->plotArea()->setBackgroundFileName(fileName); } void CartesianPlotDock::backgroundOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* plot : m_plotList) plot->plotArea()->setBackgroundOpacity(opacity); } // "Border"-tab void CartesianPlotDock::borderStyleChanged(int index) { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* plot : m_plotList) { pen = plot->plotArea()->borderPen(); pen.setStyle(penStyle); plot->plotArea()->setBorderPen(pen); } } void CartesianPlotDock::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* plot : m_plotList) { pen = plot->plotArea()->borderPen(); pen.setColor(color); plot->plotArea()->setBorderPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void CartesianPlotDock::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* plot : m_plotList) { pen = plot->plotArea()->borderPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); plot->plotArea()->setBorderPen(pen); } } void CartesianPlotDock::borderCornerRadiusChanged(double value) { if (m_initializing) return; for (auto* plot : m_plotList) plot->plotArea()->setBorderCornerRadius(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); } void CartesianPlotDock::borderOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* plot : m_plotList) plot->plotArea()->setBorderOpacity(opacity); } void CartesianPlotDock::symmetricPaddingChanged(bool checked) { if (m_initializing) return; ui.lPaddingHorizontalRight->setVisible(!checked); ui.sbPaddingRight->setVisible(!checked); ui.lPaddingVerticalDown->setVisible(!checked); ui.sbPaddingBottom->setVisible(!checked); if (checked) { ui.lPaddingHorizontal->setText(i18n("Horizontal")); ui.lPaddingVertical->setText(i18n("Vertical")); } else { ui.lPaddingHorizontal->setText(i18n("Left")); ui.lPaddingVertical->setText(i18n("Top")); } for (auto* plot : m_plotList) plot->setSymmetricPadding(checked); if (checked) { rightPaddingChanged(ui.sbPaddingHorizontal->value()); bottomPaddingChanged(ui.sbPaddingVertical->value()); } } void CartesianPlotDock::horizontalPaddingChanged(double value) { if (m_initializing) return; double padding = Worksheet::convertToSceneUnits(value, m_worksheetUnit); for (auto* plot : m_plotList) plot->setHorizontalPadding(padding); if (m_plot->symmetricPadding()) { for (auto* plot: m_plotList) plot->setRightPadding(padding); } } void CartesianPlotDock::rightPaddingChanged(double value) { if (m_initializing) return; double padding = Worksheet::convertToSceneUnits(value, m_worksheetUnit); for (auto* plot : m_plotList) plot->setRightPadding(padding); } void CartesianPlotDock::verticalPaddingChanged(double value) { if (m_initializing) return; // TODO: find better solution (set spinbox range). When plot->rect().width() does change? double padding = Worksheet::convertToSceneUnits(value, m_worksheetUnit); for (auto* plot : m_plotList) plot->setVerticalPadding(padding); if (m_plot->symmetricPadding()) { for (auto* plot: m_plotList) plot->setBottomPadding(padding); } } void CartesianPlotDock::bottomPaddingChanged(double value) { if (m_initializing) return; double padding = Worksheet::convertToSceneUnits(value, m_worksheetUnit); for (auto* plot : m_plotList) plot->setBottomPadding(padding); } void CartesianPlotDock::cursorLineWidthChanged(int width) { if (m_initializing) return; for (auto* plot : m_plotList) { QPen pen = plot->cursorPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(width, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(width, Worksheet::Unit::Point) ); plot->setCursorPen(pen); } } void CartesianPlotDock::cursorLineColorChanged(QColor color) { if (m_initializing) return; for (auto* plot : m_plotList) { QPen pen = plot->cursorPen(); pen.setColor(color); plot->setCursorPen(pen); } } void CartesianPlotDock::cursorLineStyleChanged(int index) { if (m_initializing) return; if (index > 5) return; for (auto* plot : m_plotList) { QPen pen = plot->cursorPen(); pen.setStyle(static_cast(index)); plot->setCursorPen(pen); } } //************************************************************* //****** SLOTs for changes triggered in CartesianPlot ********* //************************************************************* //general void CartesianPlotDock::plotDescriptionChanged(const AbstractAspect* aspect) { if (m_plot != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void CartesianPlotDock::plotRectChanged(QRectF& rect) { m_initializing = true; ui.sbLeft->setValue(Worksheet::convertFromSceneUnits(rect.x(), m_worksheetUnit)); ui.sbTop->setValue(Worksheet::convertFromSceneUnits(rect.y(), m_worksheetUnit)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(rect.width(), m_worksheetUnit)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(rect.height(), m_worksheetUnit)); m_initializing = false; } void CartesianPlotDock::plotRangeTypeChanged(CartesianPlot::RangeType type) { m_initializing = true; switch (type) { case CartesianPlot::RangeFree: ui.rbRangeFree->setChecked(true); break; case CartesianPlot::RangeFirst: ui.rbRangeFirst->setChecked(true); break; case CartesianPlot::RangeLast: ui.rbRangeLast->setChecked(true); break; } m_initializing = false; } void CartesianPlotDock::plotRangeFirstValuesChanged(int value) { m_initializing = true; ui.leRangeFirst->setText(QString::number(value)); m_initializing = false; } void CartesianPlotDock::plotRangeLastValuesChanged(int value) { m_initializing = true; ui.leRangeLast->setText(QString::number(value)); m_initializing = false; } void CartesianPlotDock::plotXAutoScaleChanged(bool value) { m_initializing = true; ui.chkAutoScaleX->setChecked(value); m_initializing = false; } void CartesianPlotDock::plotXMinChanged(double value) { if (m_initializing)return; const Lock lock(m_initializing); ui.leXMin->setText( QString::number(value) ); ui.dateTimeEditXMin->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); } void CartesianPlotDock::plotXMaxChanged(double value) { if (m_initializing)return; const Lock lock(m_initializing); ui.leXMax->setText( QString::number(value) ); ui.dateTimeEditXMax->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); } void CartesianPlotDock::plotXScaleChanged(int scale) { m_initializing = true; ui.cbXScaling->setCurrentIndex( scale ); m_initializing = false; } void CartesianPlotDock::plotXRangeFormatChanged(CartesianPlot::RangeFormat format) { m_initializing = true; ui.cbXRangeFormat->setCurrentIndex(format); m_initializing = false; } void CartesianPlotDock::plotYAutoScaleChanged(bool value) { m_initializing = true; ui.chkAutoScaleY->setChecked(value); m_initializing = false; } void CartesianPlotDock::plotYMinChanged(double value) { if (m_initializing)return; const Lock lock(m_initializing); ui.leYMin->setText( QString::number(value) ); ui.dateTimeEditYMin->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); } void CartesianPlotDock::plotYMaxChanged(double value) { if (m_initializing)return; const Lock lock(m_initializing); ui.leYMax->setText( QString::number(value) ); ui.dateTimeEditYMax->setDateTime( QDateTime::fromMSecsSinceEpoch(value) ); } void CartesianPlotDock::plotYScaleChanged(int scale) { m_initializing = true; ui.cbYScaling->setCurrentIndex( scale ); m_initializing = false; } void CartesianPlotDock::plotYRangeFormatChanged(CartesianPlot::RangeFormat format) { m_initializing = true; ui.cbYRangeFormat->setCurrentIndex(format); m_initializing = false; } void CartesianPlotDock::plotVisibleChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //range breaks void CartesianPlotDock::plotXRangeBreakingEnabledChanged(bool on) { m_initializing = true; ui.chkXBreak->setChecked(on); m_initializing = false; } void CartesianPlotDock::plotXRangeBreaksChanged(const CartesianPlot::RangeBreaks& breaks) { Q_UNUSED(breaks); } void CartesianPlotDock::plotYRangeBreakingEnabledChanged(bool on) { m_initializing = true; ui.chkYBreak->setChecked(on); m_initializing = false; } void CartesianPlotDock::plotYRangeBreaksChanged(const CartesianPlot::RangeBreaks& breaks) { Q_UNUSED(breaks); } //background void CartesianPlotDock::plotBackgroundTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbBackgroundType->setCurrentIndex(type); m_initializing = false; } void CartesianPlotDock::plotBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbBackgroundColorStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbBackgroundImageStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbBackgroundBrushStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundFirstColor->setColor(color); m_initializing = false; } void CartesianPlotDock::plotBackgroundSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundSecondColor->setColor(color); m_initializing = false; } void CartesianPlotDock::plotBackgroundFileNameChanged(QString& filename) { m_initializing = true; ui.leBackgroundFileName->setText(filename); m_initializing = false; } void CartesianPlotDock::plotBackgroundOpacityChanged(float opacity) { m_initializing = true; ui.sbBackgroundOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void CartesianPlotDock::plotBorderPenChanged(QPen& pen) { m_initializing = true; if (ui.cbBorderStyle->currentIndex() != pen.style()) ui.cbBorderStyle->setCurrentIndex(pen.style()); if (ui.kcbBorderColor->color() != pen.color()) ui.kcbBorderColor->setColor(pen.color()); if (ui.sbBorderWidth->value() != pen.widthF()) - ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); + ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void CartesianPlotDock::plotBorderCornerRadiusChanged(float value) { m_initializing = true; ui.sbBorderCornerRadius->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotDock::plotBorderOpacityChanged(float value) { m_initializing = true; float v = (float)value*100.; ui.sbBorderOpacity->setValue(v); m_initializing = false; } void CartesianPlotDock::plotHorizontalPaddingChanged(float value) { m_initializing = true; ui.sbPaddingHorizontal->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotDock::plotVerticalPaddingChanged(float value) { m_initializing = true; ui.sbPaddingVertical->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotDock::plotRightPaddingChanged(double value) { m_initializing = true; ui.sbPaddingRight->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotDock::plotBottomPaddingChanged(double value) { m_initializing = true; ui.sbPaddingBottom->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotDock::plotSymmetricPaddingChanged(bool symmetric) { m_initializing = true; ui.cbPaddingSymmetric->setChecked(symmetric); m_initializing = false; } void CartesianPlotDock::plotCursorPenChanged(const QPen& pen) { m_initializing = true; - ui.sbCursorLineWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); + ui.sbCursorLineWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); ui.kcbCursorLineColor->setColor(pen.color()); ui.cbCursorLineStyle->setCurrentIndex(pen.style()); m_initializing = false; } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void CartesianPlotDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_plotList.size(); if (size > 1) m_plot->beginMacro(i18n("%1 cartesian plots: template \"%2\" loaded", size, name)); else m_plot->beginMacro(i18n("%1: template \"%2\" loaded", m_plot->name(), name)); this->loadConfig(config); m_plot->endMacro(); } void CartesianPlotDock::load() { //General-tab ui.chkVisible->setChecked(m_plot->isVisible()); ui.sbLeft->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().x(), m_worksheetUnit)); ui.sbTop->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().y(), m_worksheetUnit)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().width(), m_worksheetUnit)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().height(), m_worksheetUnit)); switch (m_plot->rangeType()) { case CartesianPlot::RangeFree: ui.rbRangeFree->setChecked(true); break; case CartesianPlot::RangeFirst: ui.rbRangeFirst->setChecked(true); break; case CartesianPlot::RangeLast: ui.rbRangeLast->setChecked(true); break; } rangeTypeChanged(); ui.leRangeFirst->setText( QString::number(m_plot->rangeFirstValues()) ); ui.leRangeLast->setText( QString::number(m_plot->rangeLastValues()) ); ui.chkAutoScaleX->setChecked(m_plot->autoScaleX()); ui.leXMin->setText( QString::number(m_plot->xMin()) ); ui.leXMax->setText( QString::number(m_plot->xMax()) ); ui.dateTimeEditXMin->setDisplayFormat(m_plot->xRangeDateTimeFormat()); ui.dateTimeEditXMax->setDisplayFormat(m_plot->xRangeDateTimeFormat()); ui.dateTimeEditXMin->setDateTime(QDateTime::fromMSecsSinceEpoch(m_plot->xMin())); ui.dateTimeEditXMax->setDateTime(QDateTime::fromMSecsSinceEpoch(m_plot->xMax())); ui.cbXScaling->setCurrentIndex( (int) m_plot->xScale() ); ui.cbXRangeFormat->setCurrentIndex( (int) m_plot->xRangeFormat() ); ui.chkAutoScaleY->setChecked(m_plot->autoScaleY()); ui.leYMin->setText( QString::number(m_plot->yMin()) ); ui.leYMax->setText( QString::number(m_plot->yMax()) ); ui.dateTimeEditYMin->setDisplayFormat(m_plot->yRangeDateTimeFormat()); ui.dateTimeEditYMax->setDisplayFormat(m_plot->yRangeDateTimeFormat()); ui.dateTimeEditYMin->setDateTime(QDateTime::fromMSecsSinceEpoch(m_plot->yMin())); ui.dateTimeEditYMax->setDateTime(QDateTime::fromMSecsSinceEpoch(m_plot->yMax())); ui.cbYScaling->setCurrentIndex( (int)m_plot->yScale() ); ui.cbYRangeFormat->setCurrentIndex( (int) m_plot->yRangeFormat() ); //Title labelWidget->load(); //x-range breaks, show the first break ui.chkXBreak->setChecked(m_plot->xRangeBreakingEnabled()); this->toggleXBreak(m_plot->xRangeBreakingEnabled()); ui.bRemoveXBreak->setVisible(m_plot->xRangeBreaks().list.size()>1); ui.cbXBreak->clear(); if (!m_plot->xRangeBreaks().list.isEmpty()) { for (int i = 1; i <= m_plot->xRangeBreaks().list.size(); ++i) ui.cbXBreak->addItem(QString::number(i)); } else ui.cbXBreak->addItem("1"); ui.cbXBreak->setCurrentIndex(0); //y-range breaks, show the first break ui.chkYBreak->setChecked(m_plot->yRangeBreakingEnabled()); this->toggleYBreak(m_plot->yRangeBreakingEnabled()); ui.bRemoveYBreak->setVisible(m_plot->yRangeBreaks().list.size()>1); ui.cbYBreak->clear(); if (!m_plot->yRangeBreaks().list.isEmpty()) { for (int i = 1; i <= m_plot->yRangeBreaks().list.size(); ++i) ui.cbYBreak->addItem(QString::number(i)); } else ui.cbYBreak->addItem("1"); ui.cbYBreak->setCurrentIndex(0); //"Plot Area"-tab //Background ui.cbBackgroundType->setCurrentIndex( (int)m_plot->plotArea()->backgroundType() ); ui.cbBackgroundColorStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundColorStyle() ); ui.cbBackgroundImageStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundImageStyle() ); ui.cbBackgroundBrushStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundBrushStyle() ); ui.leBackgroundFileName->setText( m_plot->plotArea()->backgroundFileName() ); ui.kcbBackgroundFirstColor->setColor( m_plot->plotArea()->backgroundFirstColor() ); ui.kcbBackgroundSecondColor->setColor( m_plot->plotArea()->backgroundSecondColor() ); ui.sbBackgroundOpacity->setValue( round(m_plot->plotArea()->backgroundOpacity()*100.0) ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_plot->plotArea()->backgroundFileName().isEmpty() && !QFile::exists(m_plot->plotArea()->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(QString()); //Padding ui.sbPaddingHorizontal->setValue( Worksheet::convertFromSceneUnits(m_plot->horizontalPadding(), m_worksheetUnit) ); ui.sbPaddingVertical->setValue( Worksheet::convertFromSceneUnits(m_plot->verticalPadding(), m_worksheetUnit) ); ui.sbPaddingRight->setValue(Worksheet::convertFromSceneUnits(m_plot->rightPadding(), m_worksheetUnit)); ui.sbPaddingBottom->setValue(Worksheet::convertFromSceneUnits(m_plot->bottomPadding(), m_worksheetUnit)); ui.cbPaddingSymmetric->setChecked(m_plot->symmetricPadding()); //Border ui.kcbBorderColor->setColor( m_plot->plotArea()->borderPen().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_plot->plotArea()->borderPen().style() ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderPen().widthF(), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderPen().widthF(), Worksheet::Unit::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderCornerRadius(), m_worksheetUnit) ); ui.sbBorderOpacity->setValue( round(m_plot->plotArea()->borderOpacity()*100) ); GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); // Cursor QPen pen = m_plot->cursorPen(); ui.cbCursorLineStyle->setCurrentIndex(pen.style()); ui.kcbCursorLineColor->setColor(pen.color()); ui.sbCursorLineWidth->setValue(pen.width()); } void CartesianPlotDock::loadConfig(KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in CartesianPlotDock::setPlots(). //Title KConfigGroup plotTitleGroup = config.group("CartesianPlotTitle"); labelWidget->loadConfig(plotTitleGroup); //Scale breakings //TODO //Background-tab ui.cbBackgroundType->setCurrentIndex( group.readEntry("BackgroundType", (int) m_plot->plotArea()->backgroundType()) ); ui.cbBackgroundColorStyle->setCurrentIndex( group.readEntry("BackgroundColorStyle", (int) m_plot->plotArea()->backgroundColorStyle()) ); ui.cbBackgroundImageStyle->setCurrentIndex( group.readEntry("BackgroundImageStyle", (int) m_plot->plotArea()->backgroundImageStyle()) ); ui.cbBackgroundBrushStyle->setCurrentIndex( group.readEntry("BackgroundBrushStyle", (int) m_plot->plotArea()->backgroundBrushStyle()) ); ui.leBackgroundFileName->setText( group.readEntry("BackgroundFileName", m_plot->plotArea()->backgroundFileName()) ); ui.kcbBackgroundFirstColor->setColor( group.readEntry("BackgroundFirstColor", m_plot->plotArea()->backgroundFirstColor()) ); ui.kcbBackgroundSecondColor->setColor( group.readEntry("BackgroundSecondColor", m_plot->plotArea()->backgroundSecondColor()) ); ui.sbBackgroundOpacity->setValue( round(group.readEntry("BackgroundOpacity", m_plot->plotArea()->backgroundOpacity())*100.0) ); ui.sbPaddingHorizontal->setValue(Worksheet::convertFromSceneUnits(group.readEntry("HorizontalPadding", m_plot->horizontalPadding()), m_worksheetUnit)); ui.sbPaddingVertical->setValue(Worksheet::convertFromSceneUnits(group.readEntry("VerticalPadding", m_plot->verticalPadding()), m_worksheetUnit)); ui.sbPaddingRight->setValue(Worksheet::convertFromSceneUnits(group.readEntry("RightPadding", m_plot->rightPadding()), m_worksheetUnit)); ui.sbPaddingBottom->setValue(Worksheet::convertFromSceneUnits(group.readEntry("BottomPadding", m_plot->bottomPadding()), m_worksheetUnit)); ui.cbPaddingSymmetric->setChecked(group.readEntry("SymmetricPadding", m_plot->symmetricPadding())); //Border-tab ui.kcbBorderColor->setColor( group.readEntry("BorderColor", m_plot->plotArea()->borderPen().color()) ); ui.cbBorderStyle->setCurrentIndex( group.readEntry("BorderStyle", (int) m_plot->plotArea()->borderPen().style()) ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_plot->plotArea()->borderPen().widthF()), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_plot->plotArea()->borderPen().widthF()), Worksheet::Unit::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderCornerRadius", m_plot->plotArea()->borderCornerRadius()), m_worksheetUnit) ); ui.sbBorderOpacity->setValue( group.readEntry("BorderOpacity", m_plot->plotArea()->borderOpacity())*100 ); m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing = false; } void CartesianPlotDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //Title KConfigGroup plotTitleGroup = config.group("CartesianPlotTitle"); labelWidget->saveConfig(plotTitleGroup); //Scale breakings //TODO //Background group.writeEntry("BackgroundType", ui.cbBackgroundType->currentIndex()); group.writeEntry("BackgroundColorStyle", ui.cbBackgroundColorStyle->currentIndex()); group.writeEntry("BackgroundImageStyle", ui.cbBackgroundImageStyle->currentIndex()); group.writeEntry("BackgroundBrushStyle", ui.cbBackgroundBrushStyle->currentIndex()); group.writeEntry("BackgroundFileName", ui.leBackgroundFileName->text()); group.writeEntry("BackgroundFirstColor", ui.kcbBackgroundFirstColor->color()); group.writeEntry("BackgroundSecondColor", ui.kcbBackgroundSecondColor->color()); group.writeEntry("BackgroundOpacity", ui.sbBackgroundOpacity->value()/100.0); group.writeEntry("HorizontalPadding", Worksheet::convertToSceneUnits(ui.sbPaddingHorizontal->value(), m_worksheetUnit)); group.writeEntry("VerticalPadding", Worksheet::convertToSceneUnits(ui.sbPaddingVertical->value(), m_worksheetUnit)); group.writeEntry("RightPadding", Worksheet::convertToSceneUnits(ui.sbPaddingRight->value(), m_worksheetUnit)); group.writeEntry("BottomPadding", Worksheet::convertToSceneUnits(ui.sbPaddingBottom->value(), m_worksheetUnit)); group.writeEntry("SymmetricPadding", ui.cbPaddingSymmetric->isChecked()); //Border group.writeEntry("BorderStyle", ui.cbBorderStyle->currentIndex()); group.writeEntry("BorderColor", ui.kcbBorderColor->color()); - group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Point)); + group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Unit::Point)); group.writeEntry("BorderCornerRadius", Worksheet::convertToSceneUnits(ui.sbBorderCornerRadius->value(), m_worksheetUnit)); group.writeEntry("BorderOpacity", ui.sbBorderOpacity->value()/100.0); config.sync(); } void CartesianPlotDock::loadTheme(const QString& theme) { for (auto* plot : m_plotList) plot->setTheme(theme); } void CartesianPlotDock::saveTheme(KConfig& config) const { if (!m_plotList.isEmpty()) m_plotList.at(0)->saveTheme(config); } diff --git a/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp index 09fe8cfa5..a5c7eb645 100644 --- a/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp @@ -1,1176 +1,1169 @@ /*************************************************************************** File : CartesianPlotLegendDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2013-2020 by Alexander Semke (alexander.semke@web.de) Description : widget for cartesian plot legend properties ***************************************************************************/ /*************************************************************************** * * * 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 "CartesianPlotLegendDock.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/Worksheet.h" #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include #include #include #include #include #include #include /*! \class CartesianPlotLegendDock \brief Provides a widget for editing the properties of the cartesian plot legend currently selected in the project explorer. \ingroup kdefrontend */ CartesianPlotLegendDock::CartesianPlotLegendDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; //"Title"-tab auto hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget = new LabelWidget(ui.tabTitle); labelWidget->setGeometryAvailable(false); labelWidget->setBorderAvailable(false); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //"Background"-tab ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leBackgroundFileName->setCompleter(new QCompleter(new QDirModel, this)); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { auto layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //SIGNAL/SLOT //General connect(ui.leName, &QLineEdit::textChanged, this, &CartesianPlotLegendDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &CartesianPlotLegendDock::commentChanged); connect(ui.chkVisible, &QCheckBox::clicked, this, &CartesianPlotLegendDock::visibilityChanged); connect(ui.kfrLabelFont, &KFontRequester::fontSelected, this, &CartesianPlotLegendDock::labelFontChanged); connect(ui.kcbLabelColor, &KColorButton::changed, this, &CartesianPlotLegendDock::labelColorChanged); connect(ui.cbOrder, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::labelOrderChanged); connect(ui.sbLineSymbolWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::lineSymbolWidthChanged); connect(ui.cbPositionX, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::positionXChanged); connect(ui.cbPositionY, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::positionYChanged); connect(ui.sbPositionX, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::customPositionXChanged); connect(ui.sbPositionY, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::customPositionYChanged); connect(ui.sbRotation, QOverload::of(&QSpinBox::valueChanged), this, &CartesianPlotLegendDock::rotationChanged); //Background connect(ui.cbBackgroundType, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::backgroundTypeChanged); connect(ui.cbBackgroundColorStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::backgroundColorStyleChanged); connect(ui.cbBackgroundImageStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::backgroundImageStyleChanged); connect(ui.cbBackgroundBrushStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::backgroundBrushStyleChanged); connect(ui.bOpen, &QPushButton::clicked, this, &CartesianPlotLegendDock::selectFile); connect(ui.leBackgroundFileName, &QLineEdit::returnPressed, this, &CartesianPlotLegendDock::fileNameChanged); connect(ui.leBackgroundFileName, &QLineEdit::textChanged, this, &CartesianPlotLegendDock::fileNameChanged); connect(ui.kcbBackgroundFirstColor, &KColorButton::changed, this, &CartesianPlotLegendDock::backgroundFirstColorChanged); connect(ui.kcbBackgroundSecondColor, &KColorButton::changed, this, &CartesianPlotLegendDock::backgroundSecondColorChanged); connect(ui.sbBackgroundOpacity, QOverload::of(&QSpinBox::valueChanged), this, &CartesianPlotLegendDock::backgroundOpacityChanged); //Border connect(ui.cbBorderStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &CartesianPlotLegendDock::borderStyleChanged); connect(ui.kcbBorderColor, &KColorButton::changed, this, &CartesianPlotLegendDock::borderColorChanged); connect(ui.sbBorderWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::borderWidthChanged); connect(ui.sbBorderCornerRadius, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::borderCornerRadiusChanged); connect(ui.sbBorderOpacity, QOverload::of(&QSpinBox::valueChanged), this, &CartesianPlotLegendDock::borderOpacityChanged); //Layout connect(ui.sbLayoutTopMargin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::layoutTopMarginChanged); connect(ui.sbLayoutBottomMargin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::layoutBottomMarginChanged); connect(ui.sbLayoutLeftMargin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::layoutLeftMarginChanged); connect(ui.sbLayoutRightMargin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::layoutRightMarginChanged); connect(ui.sbLayoutHorizontalSpacing, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::layoutHorizontalSpacingChanged); connect(ui.sbLayoutVerticalSpacing, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotLegendDock::layoutVerticalSpacingChanged); connect(ui.sbLayoutColumnCount, QOverload::of(&QSpinBox::valueChanged), this, &CartesianPlotLegendDock::layoutColumnCountChanged); //template handler auto* frame = new QFrame(this); auto* layout = new QHBoxLayout(frame); layout->setContentsMargins(0, 11, 0, 11); auto* templateHandler = new TemplateHandler(this, TemplateHandler::CartesianPlotLegend); layout->addWidget(templateHandler); connect(templateHandler, &TemplateHandler::loadConfigRequested, this, &CartesianPlotLegendDock::loadConfigFromTemplate); connect(templateHandler, &TemplateHandler::saveConfigRequested, this, &CartesianPlotLegendDock::saveConfigAsTemplate); connect(templateHandler, &TemplateHandler::info, this, &CartesianPlotLegendDock::info); ui.verticalLayout->addWidget(frame); init(); } void CartesianPlotLegendDock::init() { this->retranslateUi(); } void CartesianPlotLegendDock::setLegends(QList list) { Lock lock(m_initializing); m_legendList = list; m_legend = list.first(); m_aspect = list.first(); //if there is more then one legend in the list, disable the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_legend->name()); ui.leComment->setText(m_legend->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first curve this->load(); //on the very first start the column count shown in UI is 1. //if the this count for m_legend is also 1 then the slot layoutColumnCountChanged is not called //and we need to disable the "order" widgets here. ui.lOrder->setVisible(m_legend->layoutColumnCount()!=1); ui.cbOrder->setVisible(m_legend->layoutColumnCount()!=1); //legend title QList labels; for (auto* legend : list) labels.append(legend->title()); labelWidget->setLabels(labels); //update active widgets backgroundTypeChanged(ui.cbBackgroundType->currentIndex()); //SIGNALs/SLOTs //General connect(m_legend, &AbstractAspect::aspectDescriptionChanged, this, &CartesianPlotLegendDock::legendDescriptionChanged); connect(m_legend, &CartesianPlotLegend::labelFontChanged, this, &CartesianPlotLegendDock::legendLabelFontChanged); connect(m_legend, &CartesianPlotLegend::labelColorChanged, this, &CartesianPlotLegendDock::legendLabelColorChanged); connect(m_legend, &CartesianPlotLegend::labelColumnMajorChanged, this, &CartesianPlotLegendDock::legendLabelOrderChanged); connect(m_legend, QOverload::of(&CartesianPlotLegend::positionChanged), this, &CartesianPlotLegendDock::legendPositionChanged); connect(m_legend, &CartesianPlotLegend::rotationAngleChanged, this, &CartesianPlotLegendDock::legendRotationAngleChanged); connect(m_legend, &CartesianPlotLegend::lineSymbolWidthChanged, this, &CartesianPlotLegendDock::legendLineSymbolWidthChanged); connect(m_legend, &CartesianPlotLegend::visibilityChanged, this, &CartesianPlotLegendDock::legendVisibilityChanged); //background connect(m_legend, &CartesianPlotLegend::backgroundTypeChanged, this, &CartesianPlotLegendDock::legendBackgroundTypeChanged); connect(m_legend, &CartesianPlotLegend::backgroundColorStyleChanged, this, &CartesianPlotLegendDock::legendBackgroundColorStyleChanged); connect(m_legend, &CartesianPlotLegend::backgroundImageStyleChanged, this, &CartesianPlotLegendDock::legendBackgroundImageStyleChanged); connect(m_legend, &CartesianPlotLegend::backgroundBrushStyleChanged, this, &CartesianPlotLegendDock::legendBackgroundBrushStyleChanged); connect(m_legend, &CartesianPlotLegend::backgroundFirstColorChanged, this, &CartesianPlotLegendDock::legendBackgroundFirstColorChanged); connect(m_legend, &CartesianPlotLegend::backgroundSecondColorChanged, this, &CartesianPlotLegendDock::legendBackgroundSecondColorChanged); connect(m_legend, &CartesianPlotLegend::backgroundFileNameChanged, this, &CartesianPlotLegendDock::legendBackgroundFileNameChanged); connect(m_legend, &CartesianPlotLegend::backgroundOpacityChanged, this, &CartesianPlotLegendDock::legendBackgroundOpacityChanged); connect(m_legend, &CartesianPlotLegend::borderPenChanged, this, &CartesianPlotLegendDock::legendBorderPenChanged); connect(m_legend, &CartesianPlotLegend::borderCornerRadiusChanged, this, &CartesianPlotLegendDock::legendBorderCornerRadiusChanged); connect(m_legend, &CartesianPlotLegend::borderOpacityChanged, this, &CartesianPlotLegendDock::legendBorderOpacityChanged); //layout connect(m_legend, &CartesianPlotLegend::layoutTopMarginChanged, this, &CartesianPlotLegendDock::legendLayoutTopMarginChanged); connect(m_legend, &CartesianPlotLegend::layoutBottomMarginChanged, this, &CartesianPlotLegendDock::legendLayoutBottomMarginChanged); connect(m_legend, &CartesianPlotLegend::layoutLeftMarginChanged, this, &CartesianPlotLegendDock::legendLayoutLeftMarginChanged); connect(m_legend, &CartesianPlotLegend::layoutRightMarginChanged, this, &CartesianPlotLegendDock::legendLayoutRightMarginChanged); connect(m_legend, &CartesianPlotLegend::layoutVerticalSpacingChanged, this, &CartesianPlotLegendDock::legendLayoutVerticalSpacingChanged); connect(m_legend, &CartesianPlotLegend::layoutHorizontalSpacingChanged, this, &CartesianPlotLegendDock::legendLayoutHorizontalSpacingChanged); connect(m_legend, &CartesianPlotLegend::layoutColumnCountChanged, this, &CartesianPlotLegendDock::legendLayoutColumnCountChanged); } void CartesianPlotLegendDock::activateTitleTab() const{ ui.tabWidget->setCurrentWidget(ui.tabTitle); } void CartesianPlotLegendDock::updateUnits() { const KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); BaseDock::Units units = (BaseDock::Units)group.readEntry("Units", (int)MetricUnits); if (units == m_units) return; m_units = units; Lock lock(m_initializing); QString suffix; if (m_units == BaseDock::MetricUnits) { //convert from imperial to metric - m_worksheetUnit = Worksheet::Centimeter; + m_worksheetUnit = Worksheet::Unit::Centimeter; suffix = QLatin1String("cm"); ui.sbLineSymbolWidth->setValue(ui.sbLineSymbolWidth->value()*2.54); ui.sbPositionX->setValue(ui.sbPositionX->value()*2.54); ui.sbPositionY->setValue(ui.sbPositionY->value()*2.54); ui.sbBorderCornerRadius->setValue(ui.sbBorderCornerRadius->value()*2.54); ui.sbLayoutTopMargin->setValue(ui.sbLayoutTopMargin->value()*2.54); ui.sbLayoutBottomMargin->setValue(ui.sbLayoutBottomMargin->value()*2.54); ui.sbLayoutLeftMargin->setValue(ui.sbLayoutLeftMargin->value()*2.54); ui.sbLayoutRightMargin->setValue(ui.sbLayoutRightMargin->value()*2.54); ui.sbLayoutHorizontalSpacing->setValue(ui.sbLayoutHorizontalSpacing->value()*2.54); ui.sbLayoutVerticalSpacing->setValue(ui.sbLayoutVerticalSpacing->value()*2.54); } else { //convert from metric to imperial - m_worksheetUnit = Worksheet::Inch; + m_worksheetUnit = Worksheet::Unit::Inch; suffix = QLatin1String("in"); ui.sbLineSymbolWidth->setValue(ui.sbLineSymbolWidth->value()/2.54); ui.sbPositionX->setValue(ui.sbPositionX->value()/2.54); ui.sbPositionY->setValue(ui.sbPositionY->value()/2.54); ui.sbBorderCornerRadius->setValue(ui.sbBorderCornerRadius->value()/2.54); ui.sbLayoutTopMargin->setValue(ui.sbLayoutTopMargin->value()/2.54); ui.sbLayoutBottomMargin->setValue(ui.sbLayoutBottomMargin->value()/2.54); ui.sbLayoutLeftMargin->setValue(ui.sbLayoutLeftMargin->value()/2.54); ui.sbLayoutRightMargin->setValue(ui.sbLayoutRightMargin->value()/2.54); ui.sbLayoutHorizontalSpacing->setValue(ui.sbLayoutHorizontalSpacing->value()/2.54); ui.sbLayoutVerticalSpacing->setValue(ui.sbLayoutVerticalSpacing->value()/2.54); } ui.sbLineSymbolWidth->setSuffix(suffix); ui.sbPositionX->setSuffix(suffix); ui.sbPositionY->setSuffix(suffix); ui.sbBorderCornerRadius->setSuffix(suffix); ui.sbLayoutTopMargin->setSuffix(suffix); ui.sbLayoutBottomMargin->setSuffix(suffix); ui.sbLayoutLeftMargin->setSuffix(suffix); ui.sbLayoutRightMargin->setSuffix(suffix); ui.sbLayoutHorizontalSpacing->setSuffix(suffix); ui.sbLayoutVerticalSpacing->setSuffix(suffix); labelWidget->updateUnits(); } //************************************************************ //** SLOTs for changes triggered in CartesianPlotLegendDock ** //************************************************************ void CartesianPlotLegendDock::retranslateUi() { Lock lock(m_initializing); ui.cbBackgroundType->addItem(i18n("Color")); ui.cbBackgroundType->addItem(i18n("Image")); ui.cbBackgroundType->addItem(i18n("Pattern")); ui.cbBackgroundColorStyle->addItem(i18n("Single Color")); ui.cbBackgroundColorStyle->addItem(i18n("Horizontal Gradient")); ui.cbBackgroundColorStyle->addItem(i18n("Vertical Gradient")); ui.cbBackgroundColorStyle->addItem(i18n("Diag. Gradient (From Top Left)")); ui.cbBackgroundColorStyle->addItem(i18n("Diag. Gradient (From Bottom Left)")); ui.cbBackgroundColorStyle->addItem(i18n("Radial Gradient")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled and Cropped")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled, Keep Proportions")); ui.cbBackgroundImageStyle->addItem(i18n("Centered")); ui.cbBackgroundImageStyle->addItem(i18n("Tiled")); ui.cbBackgroundImageStyle->addItem(i18n("Center Tiled")); ui.cbOrder->addItem(i18n("Column Major")); ui.cbOrder->addItem(i18n("Row Major")); ui.cbPositionX->addItem(i18n("Left")); ui.cbPositionX->addItem(i18n("Center")); ui.cbPositionX->addItem(i18n("Right")); ui.cbPositionX->addItem(i18n("Custom")); ui.cbPositionY->addItem(i18n("Top")); ui.cbPositionY->addItem(i18n("Center")); ui.cbPositionY->addItem(i18n("Bottom")); ui.cbPositionY->addItem(i18n("Custom")); GuiTools::updatePenStyles(ui.cbBorderStyle, Qt::black); GuiTools::updateBrushStyles(ui.cbBackgroundBrushStyle, Qt::SolidPattern); QString suffix; if (m_units == BaseDock::MetricUnits) suffix = QLatin1String("cm"); else suffix = QLatin1String("in"); ui.sbLineSymbolWidth->setSuffix(suffix); ui.sbPositionX->setSuffix(suffix); ui.sbPositionY->setSuffix(suffix); ui.sbBorderCornerRadius->setSuffix(suffix); ui.sbLayoutTopMargin->setSuffix(suffix); ui.sbLayoutBottomMargin->setSuffix(suffix); ui.sbLayoutLeftMargin->setSuffix(suffix); ui.sbLayoutRightMargin->setSuffix(suffix); ui.sbLayoutHorizontalSpacing->setSuffix(suffix); ui.sbLayoutVerticalSpacing->setSuffix(suffix); } // "General"-tab void CartesianPlotLegendDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setVisible(state); } //General void CartesianPlotLegendDock::labelFontChanged(const QFont& font) { if (m_initializing) return; QFont labelsFont = font; - labelsFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); + labelsFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Unit::Point) ); for (auto* legend : m_legendList) legend->setLabelFont(labelsFont); } void CartesianPlotLegendDock::labelColorChanged(const QColor& color) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLabelColor(color); } void CartesianPlotLegendDock::labelOrderChanged(const int index) { if (m_initializing) return; bool columnMajor = (index == 0); for (auto* legend : m_legendList) legend->setLabelColumnMajor(columnMajor); } void CartesianPlotLegendDock::lineSymbolWidthChanged(double value) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLineSymbolWidth(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); } /*! called when legend's current horizontal position relative to its parent (left, center, right, custom ) is changed. */ void CartesianPlotLegendDock::positionXChanged(int index) { //Enable/disable the spinbox for the x- oordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionX->count()-1 ) { ui.sbPositionX->setEnabled(true); } else { ui.sbPositionX->setEnabled(false); } if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.horizontalPosition = CartesianPlotLegend::HorizontalPosition(index); for (auto* legend : m_legendList) legend->setPosition(position); } /*! called when legend's current horizontal position relative to its parent (top, center, bottom, custom ) is changed. */ void CartesianPlotLegendDock::positionYChanged(int index) { //Enable/disable the spinbox for the y- oordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionY->count()-1 ) { ui.sbPositionY->setEnabled(true); } else { ui.sbPositionY->setEnabled(false); } if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.verticalPosition = CartesianPlotLegend::VerticalPosition(index); for (auto* legend : m_legendList) legend->setPosition(position); } void CartesianPlotLegendDock::customPositionXChanged(double value) { if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.point.setX(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); for (auto* legend : m_legendList) legend->setPosition(position); } void CartesianPlotLegendDock::customPositionYChanged(double value) { if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.point.setY(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); for (auto* legend : m_legendList) legend->setPosition(position); } void CartesianPlotLegendDock::rotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_legendList) curve->setRotationAngle(value); } // "Background"-tab void CartesianPlotLegendDock::backgroundTypeChanged(int index) { const auto type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lBackgroundColorStyle->show(); ui.cbBackgroundColorStyle->show(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); auto style = (PlotArea::BackgroundColorStyle) ui.cbBackgroundColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color:")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->show(); ui.cbBackgroundImageStyle->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->show(); ui.leBackgroundFileName->show(); ui.bOpen->show(); ui.lBackgroundFirstColor->hide(); ui.kcbBackgroundFirstColor->hide(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->show(); ui.cbBackgroundBrushStyle->show(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } if (m_initializing) return; for (auto* legend : m_legendList) legend->setBackgroundType(type); } void CartesianPlotLegendDock::backgroundColorStyleChanged(int index) { auto style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color:")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); } if (m_initializing) return; for (auto* legend : m_legendList) legend->setBackgroundColorStyle(style); } void CartesianPlotLegendDock::backgroundImageStyleChanged(int index) { if (m_initializing) return; auto style = (PlotArea::BackgroundImageStyle)index; for (auto* legend : m_legendList) legend->setBackgroundImageStyle(style); } void CartesianPlotLegendDock::backgroundBrushStyleChanged(int index) { if (m_initializing) return; auto style = (Qt::BrushStyle)index; for (auto* legend : m_legendList) legend->setBackgroundBrushStyle(style); } void CartesianPlotLegendDock::backgroundFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setBackgroundFirstColor(c); } void CartesianPlotLegendDock::backgroundSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setBackgroundSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void CartesianPlotLegendDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "CartesianPlotLegendDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += ' ' + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leBackgroundFileName->setText( path ); for (auto* legend : m_legendList) legend->setBackgroundFileName(path); } void CartesianPlotLegendDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leBackgroundFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); for (auto* legend : m_legendList) legend->setBackgroundFileName(fileName); } void CartesianPlotLegendDock::backgroundOpacityChanged(int value) { if (m_initializing) return; float opacity = (float)value/100.; for (auto* legend : m_legendList) legend->setBackgroundOpacity(opacity); } // "Border"-tab void CartesianPlotLegendDock::borderStyleChanged(int index) { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* legend : m_legendList) { pen = legend->borderPen(); pen.setStyle(penStyle); legend->setBorderPen(pen); } } void CartesianPlotLegendDock::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* legend : m_legendList) { pen = legend->borderPen(); pen.setColor(color); legend->setBorderPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void CartesianPlotLegendDock::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* legend : m_legendList) { pen = legend->borderPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); legend->setBorderPen(pen); } } void CartesianPlotLegendDock::borderCornerRadiusChanged(double value) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setBorderCornerRadius(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); } void CartesianPlotLegendDock::borderOpacityChanged(int value) { if (m_initializing) return; float opacity = (float)value/100.; for (auto* legend : m_legendList) legend->setBorderOpacity(opacity); } //Layout void CartesianPlotLegendDock::layoutTopMarginChanged(double margin) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLayoutTopMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void CartesianPlotLegendDock::layoutBottomMarginChanged(double margin) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLayoutBottomMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void CartesianPlotLegendDock::layoutLeftMarginChanged(double margin) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLayoutLeftMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void CartesianPlotLegendDock::layoutRightMarginChanged(double margin) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLayoutRightMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void CartesianPlotLegendDock::layoutHorizontalSpacingChanged(double spacing) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLayoutHorizontalSpacing(Worksheet::convertToSceneUnits(spacing, m_worksheetUnit)); } void CartesianPlotLegendDock::layoutVerticalSpacingChanged(double spacing) { if (m_initializing) return; for (auto* legend : m_legendList) legend->setLayoutVerticalSpacing(Worksheet::convertToSceneUnits(spacing, m_worksheetUnit)); } void CartesianPlotLegendDock::layoutColumnCountChanged(int count) { ui.lOrder->setVisible(count!=1); ui.cbOrder->setVisible(count!=1); if (m_initializing) return; for (auto* legend : m_legendList) legend->setLayoutColumnCount(count); } //************************************************************* //**** SLOTs for changes triggered in CartesianPlotLegend ***** //************************************************************* //General void CartesianPlotLegendDock::legendDescriptionChanged(const AbstractAspect* aspect) { if (m_legend != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void CartesianPlotLegendDock::legendLabelFontChanged(QFont& font) { m_initializing = true; //we need to set the font size in points for KFontRequester QFont f(font); - f.setPointSizeF( Worksheet::convertFromSceneUnits(f.pixelSize(), Worksheet::Point) ); + f.setPointSizeF( Worksheet::convertFromSceneUnits(f.pixelSize(), Worksheet::Unit::Point) ); ui.kfrLabelFont->setFont(f); m_initializing = false; } void CartesianPlotLegendDock::legendLabelColorChanged(QColor& color) { m_initializing = true; ui.kcbLabelColor->setColor(color); m_initializing = false; } void CartesianPlotLegendDock::legendLabelOrderChanged(bool b) { m_initializing = true; if (b) ui.cbOrder->setCurrentIndex(0); //column major else ui.cbOrder->setCurrentIndex(1); //row major m_initializing = false; } void CartesianPlotLegendDock::legendLineSymbolWidthChanged(float value) { m_initializing = true; ui.sbLineSymbolWidth->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendPositionChanged(const CartesianPlotLegend::PositionWrapper& position) { m_initializing = true; ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(position.point.x(), m_worksheetUnit) ); ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(position.point.y(), m_worksheetUnit) ); - ui.cbPositionX->setCurrentIndex( position.horizontalPosition ); - ui.cbPositionY->setCurrentIndex( position.verticalPosition ); + ui.cbPositionX->setCurrentIndex( static_cast(position.horizontalPosition) ); + ui.cbPositionY->setCurrentIndex( static_cast(position.verticalPosition) ); m_initializing = false; } void CartesianPlotLegendDock::legendRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbRotation->setValue(angle); m_initializing = false; } void CartesianPlotLegendDock::legendVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //Background void CartesianPlotLegendDock::legendBackgroundTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbBackgroundType->setCurrentIndex(type); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbBackgroundColorStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbBackgroundImageStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbBackgroundBrushStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundFirstColor->setColor(color); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundSecondColor->setColor(color); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundFileNameChanged(QString& filename) { m_initializing = true; ui.leBackgroundFileName->setText(filename); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundOpacityChanged(float opacity) { m_initializing = true; ui.sbBackgroundOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } //Border void CartesianPlotLegendDock::legendBorderPenChanged(QPen& pen) { if (m_initializing) return; m_initializing = true; if (ui.cbBorderStyle->currentIndex() != pen.style()) ui.cbBorderStyle->setCurrentIndex(pen.style()); if (ui.kcbBorderColor->color() != pen.color()) ui.kcbBorderColor->setColor(pen.color()); if (ui.sbBorderWidth->value() != pen.widthF()) - ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); + ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void CartesianPlotLegendDock::legendBorderCornerRadiusChanged(float value) { m_initializing = true; ui.sbBorderCornerRadius->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendBorderOpacityChanged(float opacity) { m_initializing = true; ui.sbBorderOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } //Layout void CartesianPlotLegendDock::legendLayoutTopMarginChanged(float value) { m_initializing = true; ui.sbLayoutTopMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutBottomMarginChanged(float value) { m_initializing = true; ui.sbLayoutBottomMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutLeftMarginChanged(float value) { m_initializing = true; ui.sbLayoutLeftMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutRightMarginChanged(float value) { m_initializing = true; ui.sbLayoutRightMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutVerticalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutVerticalSpacing->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutHorizontalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutHorizontalSpacing->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutColumnCountChanged(int value) { m_initializing = true; ui.sbLayoutColumnCount->setValue(value); m_initializing = false; } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void CartesianPlotLegendDock::load() { //General-tab //Format //we need to set the font size in points for KFontRequester QFont font = m_legend->labelFont(); - font.setPointSizeF( qRound(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( qRound(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrLabelFont->setFont(font); ui.kcbLabelColor->setColor( m_legend->labelColor() ); bool columnMajor = m_legend->labelColumnMajor(); if (columnMajor) ui.cbOrder->setCurrentIndex(0); //column major else ui.cbOrder->setCurrentIndex(1); //row major ui.sbLineSymbolWidth->setValue( Worksheet::convertFromSceneUnits(m_legend->lineSymbolWidth(), m_worksheetUnit) ); //Geometry - ui.cbPositionX->setCurrentIndex(m_legend->position().horizontalPosition); + ui.cbPositionX->setCurrentIndex(static_cast(m_legend->position().horizontalPosition)); ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(m_legend->position().point.x(), m_worksheetUnit) ); - ui.cbPositionY->setCurrentIndex(m_legend->position().verticalPosition); + ui.cbPositionY->setCurrentIndex(static_cast(m_legend->position().verticalPosition)); ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(m_legend->position().point.y(), m_worksheetUnit) ); ui.sbRotation->setValue(m_legend->rotationAngle()); ui.chkVisible->setChecked( m_legend->isVisible() ); //Background-tab ui.cbBackgroundType->setCurrentIndex( (int) m_legend->backgroundType() ); ui.cbBackgroundColorStyle->setCurrentIndex( (int) m_legend->backgroundColorStyle() ); ui.cbBackgroundImageStyle->setCurrentIndex( (int) m_legend->backgroundImageStyle() ); ui.cbBackgroundBrushStyle->setCurrentIndex( (int) m_legend->backgroundBrushStyle() ); ui.leBackgroundFileName->setText( m_legend->backgroundFileName() ); ui.kcbBackgroundFirstColor->setColor( m_legend->backgroundFirstColor() ); ui.kcbBackgroundSecondColor->setColor( m_legend->backgroundSecondColor() ); ui.sbBackgroundOpacity->setValue( qRound(m_legend->backgroundOpacity()*100.0) ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_legend->backgroundFileName().isEmpty() && !QFile::exists(m_legend->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); //Border ui.kcbBorderColor->setColor( m_legend->borderPen().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_legend->borderPen().style() ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_legend->borderPen().widthF(), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_legend->borderPen().widthF(), Worksheet::Unit::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(m_legend->borderCornerRadius(), m_worksheetUnit) ); ui.sbBorderOpacity->setValue( qRound(m_legend->borderOpacity()*100.0) ); // Layout ui.sbLayoutTopMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutTopMargin(), m_worksheetUnit) ); ui.sbLayoutBottomMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutBottomMargin(), m_worksheetUnit) ); ui.sbLayoutLeftMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutLeftMargin(), m_worksheetUnit) ); ui.sbLayoutRightMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutRightMargin(), m_worksheetUnit) ); ui.sbLayoutHorizontalSpacing->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutHorizontalSpacing(), m_worksheetUnit) ); ui.sbLayoutVerticalSpacing->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutVerticalSpacing(), m_worksheetUnit) ); ui.sbLayoutColumnCount->setValue( m_legend->layoutColumnCount() ); m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing = false; } void CartesianPlotLegendDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_legendList.size(); if (size > 1) m_legend->beginMacro(i18n("%1 cartesian plot legends: template \"%2\" loaded", size, name)); else m_legend->beginMacro(i18n("%1: template \"%2\" loaded", m_legend->name(), name)); this->loadConfig(config); m_legend->endMacro(); } void CartesianPlotLegendDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "CartesianPlotLegend" ); //General-tab //Format //we need to set the font size in points for KFontRequester QFont font = m_legend->labelFont(); - font.setPointSizeF( qRound(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( qRound(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrLabelFont->setFont( group.readEntry("LabelFont", font) ); ui.kcbLabelColor->setColor( group.readEntry("LabelColor", m_legend->labelColor()) ); bool columnMajor = group.readEntry("LabelColumMajor", m_legend->labelColumnMajor()); if (columnMajor) ui.cbOrder->setCurrentIndex(0); //column major else ui.cbOrder->setCurrentIndex(1); //row major - ui.sbLineSymbolWidth->setValue(group.readEntry("LineSymbolWidth", - Worksheet::convertFromSceneUnits(m_legend->lineSymbolWidth(), m_worksheetUnit)) ); + ui.sbLineSymbolWidth->setValue(group.readEntry("LineSymbolWidth", Worksheet::convertFromSceneUnits(m_legend->lineSymbolWidth(), m_worksheetUnit)) ); // Geometry ui.cbPositionX->setCurrentIndex( group.readEntry("PositionX", (int) m_legend->position().horizontalPosition ) ); ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(group.readEntry("PositionXValue", m_legend->position().point.x()),m_worksheetUnit) ); ui.cbPositionY->setCurrentIndex( group.readEntry("PositionY", (int) m_legend->position().verticalPosition ) ); ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(group.readEntry("PositionYValue", m_legend->position().point.y()),m_worksheetUnit) ); ui.sbRotation->setValue( group.readEntry("Rotation", (int) m_legend->rotationAngle() ) ); ui.chkVisible->setChecked( group.readEntry("Visible", m_legend->isVisible()) ); //Background-tab ui.cbBackgroundType->setCurrentIndex( group.readEntry("BackgroundType", (int) m_legend->backgroundType()) ); ui.cbBackgroundColorStyle->setCurrentIndex( group.readEntry("BackgroundColorStyle", (int) m_legend->backgroundColorStyle()) ); ui.cbBackgroundImageStyle->setCurrentIndex( group.readEntry("BackgroundImageStyle", (int) m_legend->backgroundImageStyle()) ); ui.cbBackgroundBrushStyle->setCurrentIndex( group.readEntry("BackgroundBrushStyle", (int) m_legend->backgroundBrushStyle()) ); ui.leBackgroundFileName->setText( group.readEntry("BackgroundFileName", m_legend->backgroundFileName()) ); ui.kcbBackgroundFirstColor->setColor( group.readEntry("BackgroundFirstColor", m_legend->backgroundFirstColor()) ); ui.kcbBackgroundSecondColor->setColor( group.readEntry("BackgroundSecondColor", m_legend->backgroundSecondColor()) ); ui.sbBackgroundOpacity->setValue( qRound(group.readEntry("BackgroundOpacity", m_legend->backgroundOpacity())*100.0) ); //Border ui.kcbBorderColor->setColor( group.readEntry("BorderColor", m_legend->borderPen().color()) ); ui.cbBorderStyle->setCurrentIndex( group.readEntry("BorderStyle", (int) m_legend->borderPen().style()) ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_legend->borderPen().widthF()), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_legend->borderPen().widthF()), Worksheet::Unit::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderCornerRadius", m_legend->borderCornerRadius()), m_worksheetUnit) ); ui.sbBorderOpacity->setValue( qRound(group.readEntry("BorderOpacity", m_legend->borderOpacity())*100.0) ); // Layout - ui.sbLayoutTopMargin->setValue(group.readEntry("LayoutTopMargin", - Worksheet::convertFromSceneUnits(m_legend->layoutTopMargin(), m_worksheetUnit)) ); - ui.sbLayoutBottomMargin->setValue(group.readEntry("LayoutBottomMargin", - Worksheet::convertFromSceneUnits(m_legend->layoutBottomMargin(), m_worksheetUnit)) ); - ui.sbLayoutLeftMargin->setValue(group.readEntry("LayoutLeftMargin", - Worksheet::convertFromSceneUnits(m_legend->layoutLeftMargin(), m_worksheetUnit)) ); - ui.sbLayoutRightMargin->setValue(group.readEntry("LayoutRightMargin", - Worksheet::convertFromSceneUnits(m_legend->layoutRightMargin(), m_worksheetUnit)) ); - ui.sbLayoutHorizontalSpacing->setValue(group.readEntry("LayoutHorizontalSpacing", - Worksheet::convertFromSceneUnits(m_legend->layoutHorizontalSpacing(), m_worksheetUnit)) ); - ui.sbLayoutVerticalSpacing->setValue(group.readEntry("LayoutVerticalSpacing", - Worksheet::convertFromSceneUnits(m_legend->layoutVerticalSpacing(), m_worksheetUnit)) ); + ui.sbLayoutTopMargin->setValue(group.readEntry("LayoutTopMargin", Worksheet::convertFromSceneUnits(m_legend->layoutTopMargin(), m_worksheetUnit)) ); + ui.sbLayoutBottomMargin->setValue(group.readEntry("LayoutBottomMargin", Worksheet::convertFromSceneUnits(m_legend->layoutBottomMargin(), m_worksheetUnit)) ); + ui.sbLayoutLeftMargin->setValue(group.readEntry("LayoutLeftMargin", Worksheet::convertFromSceneUnits(m_legend->layoutLeftMargin(), m_worksheetUnit)) ); + ui.sbLayoutRightMargin->setValue(group.readEntry("LayoutRightMargin", Worksheet::convertFromSceneUnits(m_legend->layoutRightMargin(), m_worksheetUnit)) ); + ui.sbLayoutHorizontalSpacing->setValue(group.readEntry("LayoutHorizontalSpacing", Worksheet::convertFromSceneUnits(m_legend->layoutHorizontalSpacing(), m_worksheetUnit)) ); + ui.sbLayoutVerticalSpacing->setValue(group.readEntry("LayoutVerticalSpacing", Worksheet::convertFromSceneUnits(m_legend->layoutVerticalSpacing(), m_worksheetUnit)) ); ui.sbLayoutColumnCount->setValue(group.readEntry("LayoutColumnCount", m_legend->layoutColumnCount())); //Title group = config.group("PlotLegend"); labelWidget->loadConfig(group); m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing = false; } void CartesianPlotLegendDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "CartesianPlotLegend" ); //General-tab //Format QFont font = m_legend->labelFont(); - font.setPointSizeF( Worksheet::convertFromSceneUnits(font.pointSizeF(), Worksheet::Point) ); + font.setPointSizeF( Worksheet::convertFromSceneUnits(font.pointSizeF(), Worksheet::Unit::Point) ); group.writeEntry("LabelFont", font); group.writeEntry("LabelColor", ui.kcbLabelColor->color()); group.writeEntry("LabelColumMajorOrder", ui.cbOrder->currentIndex() == 0);// true for "column major", false for "row major" group.writeEntry("LineSymbolWidth", Worksheet::convertToSceneUnits(ui.sbLineSymbolWidth->value(), m_worksheetUnit)); //Geometry group.writeEntry("PositionX", ui.cbPositionX->currentIndex()); group.writeEntry("PositionXValue", Worksheet::convertToSceneUnits(ui.sbPositionX->value(),m_worksheetUnit) ); group.writeEntry("PositionY", ui.cbPositionY->currentIndex()); group.writeEntry("PositionYValue", Worksheet::convertToSceneUnits(ui.sbPositionY->value(),m_worksheetUnit) ); group.writeEntry("Rotation", ui.sbRotation->value()); group.writeEntry("Visible", ui.chkVisible->isChecked()); //Background group.writeEntry("BackgroundType", ui.cbBackgroundType->currentIndex()); group.writeEntry("BackgroundColorStyle", ui.cbBackgroundColorStyle->currentIndex()); group.writeEntry("BackgroundImageStyle", ui.cbBackgroundImageStyle->currentIndex()); group.writeEntry("BackgroundBrushStyle", ui.cbBackgroundBrushStyle->currentIndex()); group.writeEntry("BackgroundFileName", ui.leBackgroundFileName->text()); group.writeEntry("BackgroundFirstColor", ui.kcbBackgroundFirstColor->color()); group.writeEntry("BackgroundSecondColor", ui.kcbBackgroundSecondColor->color()); group.writeEntry("BackgroundOpacity", ui.sbBackgroundOpacity->value()/100.0); //Border group.writeEntry("BorderStyle", ui.cbBorderStyle->currentIndex()); group.writeEntry("BorderColor", ui.kcbBorderColor->color()); - group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Point)); + group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Unit::Point)); group.writeEntry("BorderCornerRadius", Worksheet::convertToSceneUnits(ui.sbBorderCornerRadius->value(), m_worksheetUnit)); group.writeEntry("BorderOpacity", ui.sbBorderOpacity->value()/100.0); //Layout group.writeEntry("LayoutTopMargin",Worksheet::convertToSceneUnits(ui.sbLayoutTopMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutBottomMargin",Worksheet::convertToSceneUnits(ui.sbLayoutBottomMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutLeftMargin",Worksheet::convertToSceneUnits(ui.sbLayoutLeftMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutRightMargin",Worksheet::convertToSceneUnits(ui.sbLayoutRightMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutVerticalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutVerticalSpacing->value(), m_worksheetUnit)); group.writeEntry("LayoutHorizontalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutHorizontalSpacing->value(), m_worksheetUnit)); group.writeEntry("LayoutColumnCount", ui.sbLayoutColumnCount->value()); //Title group = config.group("PlotLegend"); labelWidget->saveConfig(group); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/CustomPointDock.cpp b/src/kdefrontend/dockwidgets/CustomPointDock.cpp index 9865e95ef..68058bc1d 100644 --- a/src/kdefrontend/dockwidgets/CustomPointDock.cpp +++ b/src/kdefrontend/dockwidgets/CustomPointDock.cpp @@ -1,494 +1,494 @@ /*************************************************************************** File : CustomPointDock.cpp Project : LabPlot Description : widget for Datapicker-Point properties -------------------------------------------------------------------- Copyright : (C) 2015 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 "CustomPointDock.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include CustomPointDock::CustomPointDock(QWidget *parent): BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; //Validators ui.lePositionX->setValidator( new QDoubleValidator(ui.lePositionX) ); ui.lePositionY->setValidator( new QDoubleValidator(ui.lePositionY) ); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { auto* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //SLOTS //General connect(ui.leName, &QLineEdit::textChanged, this, &CustomPointDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &CustomPointDock::commentChanged); connect( ui.lePositionX, SIGNAL(returnPressed()), this, SLOT(positionXChanged()) ); connect( ui.lePositionY, SIGNAL(returnPressed()), this, SLOT(positionYChanged()) ); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); //Symbols connect( ui.cbSymbolStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolStyleChanged(int)) ); connect( ui.sbSymbolSize, SIGNAL(valueChanged(double)), this, SLOT(symbolSizeChanged(double)) ); connect( ui.sbSymbolRotation, SIGNAL(valueChanged(int)), this, SLOT(symbolRotationChanged(int)) ); connect( ui.sbSymbolOpacity, SIGNAL(valueChanged(int)), this, SLOT(symbolOpacityChanged(int)) ); connect( ui.cbSymbolFillingStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolFillingStyleChanged(int)) ); connect( ui.kcbSymbolFillingColor, SIGNAL(changed(QColor)), this, SLOT(symbolFillingColorChanged(QColor)) ); connect( ui.cbSymbolBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolBorderStyleChanged(int)) ); connect( ui.kcbSymbolBorderColor, SIGNAL(changed(QColor)), this, SLOT(symbolBorderColorChanged(QColor)) ); connect( ui.sbSymbolBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(symbolBorderWidthChanged(double)) ); //Template handler auto* templateHandler = new TemplateHandler(this, TemplateHandler::CustomPoint); ui.verticalLayout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); init(); } void CustomPointDock::init() { m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); QPainter pa; int iconSize = 20; QPixmap pm(iconSize, iconSize); QPen pen(Qt::SolidPattern, 0); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); for (int i = 0; i < 18; ++i) { auto style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen( pen ); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; } void CustomPointDock::setPoints(QList list) { m_initializing = true; m_pointsList = list; m_point = list.first(); m_aspect = list.first(); Q_ASSERT(m_point); //if there are more then one point in the list, disable the comment and name widgets in the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_point->name()); ui.leComment->setText(m_point->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first custom point this->load(); //SIGNALs/SLOTs // general connect(m_point, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(pointDescriptionChanged(const AbstractAspect*))); connect(m_point, SIGNAL(positionChanged(QPointF)), this, SLOT(pointPositionChanged(QPointF))); connect(m_point, SIGNAL(visibleChanged(bool)), this, SLOT(pointVisibilityChanged(bool))); //symbol connect(m_point, SIGNAL(symbolStyleChanged(Symbol::Style)), this, SLOT(pointSymbolStyleChanged(Symbol::Style))); connect(m_point, SIGNAL(symbolSizeChanged(qreal)), this, SLOT(pointSymbolSizeChanged(qreal))); connect(m_point, SIGNAL(symbolRotationAngleChanged(qreal)), this, SLOT(pointSymbolRotationAngleChanged(qreal))); connect(m_point, SIGNAL(symbolOpacityChanged(qreal)), this, SLOT(pointSymbolOpacityChanged(qreal))); connect(m_point, SIGNAL(symbolBrushChanged(QBrush)), this, SLOT(pointSymbolBrushChanged(QBrush))); connect(m_point, SIGNAL(symbolPenChanged(QPen)), this, SLOT(pointSymbolPenChanged(QPen))); } //********************************************************** //**** SLOTs for changes triggered in CustomPointDock ****** //********************************************************** //"General"-tab void CustomPointDock::positionXChanged() { if (m_initializing) return; QPointF pos = m_point->position(); float x = ui.lePositionX->text().toFloat(); pos.setX(x); for (auto* point : m_pointsList) point->setPosition(pos); } void CustomPointDock::positionYChanged() { if (m_initializing) return; QPointF pos = m_point->position(); float y = ui.lePositionY->text().toFloat(); pos.setY(y); for (auto* point : m_pointsList) point->setPosition(pos); } void CustomPointDock::visibilityChanged(bool state) { if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: visibility changed", m_pointsList.count())); for (auto* point : m_pointsList) point->setVisible(state); m_point->endMacro(); } void CustomPointDock::symbolStyleChanged(int index) { auto style = Symbol::Style(index); //enable/disable the filling options in the GUI depending on the currently selected points. if (style != Symbol::Line && style != Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: style changed", m_pointsList.count())); for (auto* point : m_pointsList) point->setSymbolStyle(style); m_point->endMacro(); } void CustomPointDock::symbolSizeChanged(double value) { if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: size changed", m_pointsList.count())); for (auto* point : m_pointsList) - point->setSymbolSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + point->setSymbolSize( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); m_point->endMacro(); } void CustomPointDock::symbolRotationChanged(int value) { if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: rotation changed", m_pointsList.count())); for (auto* point : m_pointsList) point->setSymbolRotationAngle(value); m_point->endMacro(); } void CustomPointDock::symbolOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; m_point->beginMacro(i18n("%1 CustomPoints: opacity changed", m_pointsList.count())); for (auto* point : m_pointsList) point->setSymbolOpacity(opacity); m_point->endMacro(); } void CustomPointDock::symbolFillingStyleChanged(int index) { auto brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; m_point->beginMacro(i18n("%1 CustomPoints: filling style changed", m_pointsList.count())); for (auto* point : m_pointsList) { brush = point->symbolBrush(); brush.setStyle(brushStyle); point->setSymbolBrush(brush); } m_point->endMacro(); } void CustomPointDock::symbolFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; m_point->beginMacro(i18n("%1 CustomPoints: filling color changed", m_pointsList.count())); for (auto* point : m_pointsList) { brush = point->symbolBrush(); brush.setColor(color); point->setSymbolBrush(brush); } m_point->endMacro(); m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void CustomPointDock::symbolBorderStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; m_point->beginMacro(i18n("%1 CustomPoints: border style changed", m_pointsList.count())); for (auto* point : m_pointsList) { pen = point->symbolPen(); pen.setStyle(penStyle); point->setSymbolPen(pen); } m_point->endMacro(); } void CustomPointDock::symbolBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; m_point->beginMacro(i18n("%1 CustomPoints: border color changed", m_pointsList.count())); for (auto* point : m_pointsList) { pen = point->symbolPen(); pen.setColor(color); point->setSymbolPen(pen); } m_point->endMacro(); m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void CustomPointDock::symbolBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; m_point->beginMacro(i18n("%1 CustomPoints: border width changed", m_pointsList.count())); for (auto* point : m_pointsList) { pen = point->symbolPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); point->setSymbolPen(pen); } m_point->endMacro(); } //********************************************************* //**** SLOTs for changes triggered in CustomPoint ********* //********************************************************* //"General"-tab void CustomPointDock::pointDescriptionChanged(const AbstractAspect* aspect) { if (m_point != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void CustomPointDock::pointPositionChanged(QPointF position) { m_initializing = true; ui.lePositionX->setText(QString::number(position.x())); ui.lePositionY->setText(QString::number(position.y())); m_initializing = false; } //"Symbol"-tab void CustomPointDock::pointSymbolStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style); m_initializing = false; } void CustomPointDock::pointSymbolSizeChanged(qreal size) { m_initializing = true; - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void CustomPointDock::pointSymbolRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbSymbolRotation->setValue(qRound(angle)); m_initializing = false; } void CustomPointDock::pointSymbolOpacityChanged(qreal opacity) { m_initializing = true; ui.sbSymbolOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } void CustomPointDock::pointSymbolBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void CustomPointDock::pointSymbolPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void CustomPointDock::pointVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //********************************************************** //******************** SETTINGS **************************** //********************************************************** void CustomPointDock::load() { if (!m_point) return; m_initializing = true; ui.lePositionX->setText(QString::number(m_point->position().x())); ui.lePositionY->setText(QString::number(m_point->position().y())); ui.cbSymbolStyle->setCurrentIndex( (int)m_point->symbolStyle() ); - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_point->symbolSize(), Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_point->symbolSize(), Worksheet::Unit::Point) ); ui.sbSymbolRotation->setValue( m_point->symbolRotationAngle() ); ui.sbSymbolOpacity->setValue( qRound(m_point->symbolOpacity()*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( (int) m_point->symbolBrush().style() ); ui.kcbSymbolFillingColor->setColor( m_point->symbolBrush().color() ); ui.cbSymbolBorderStyle->setCurrentIndex( (int) m_point->symbolPen().style() ); ui.kcbSymbolBorderColor->setColor( m_point->symbolPen().color() ); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_point->symbolPen().widthF(), Worksheet::Point) ); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_point->symbolPen().widthF(), Worksheet::Unit::Point) ); ui.chkVisible->setChecked( m_point->isVisible() ); m_initializing = false; } void CustomPointDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_pointsList.size(); if (size > 1) m_point->beginMacro(i18n("%1 custom points: template \"%2\" loaded", size, name)); else m_point->beginMacro(i18n("%1: template \"%2\" loaded", m_point->name(), name)); this->loadConfig(config); m_point->endMacro(); } void CustomPointDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "CustomPoint" ); ui.cbSymbolStyle->setCurrentIndex( group.readEntry("SymbolStyle", (int)m_point->symbolStyle()) ); - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_point->symbolSize()), Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_point->symbolSize()), Worksheet::Unit::Point) ); ui.sbSymbolRotation->setValue( group.readEntry("SymbolRotation", m_point->symbolRotationAngle()) ); ui.sbSymbolOpacity->setValue( qRound(group.readEntry("SymbolOpacity", m_point->symbolOpacity())*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( group.readEntry("SymbolFillingStyle", (int) m_point->symbolBrush().style()) ); ui.kcbSymbolFillingColor->setColor( group.readEntry("SymbolFillingColor", m_point->symbolBrush().color()) ); ui.cbSymbolBorderStyle->setCurrentIndex( group.readEntry("SymbolBorderStyle", (int) m_point->symbolPen().style()) ); ui.kcbSymbolBorderColor->setColor( group.readEntry("SymbolBorderColor", m_point->symbolPen().color()) ); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_point->symbolPen().widthF()), Worksheet::Point) ); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_point->symbolPen().widthF()), Worksheet::Unit::Point) ); m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); m_initializing = false; } void CustomPointDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "CustomPoint" ); group.writeEntry("SymbolStyle", ui.cbSymbolStyle->currentText()); - group.writeEntry("SymbolSize", Worksheet::convertToSceneUnits(ui.sbSymbolSize->value(),Worksheet::Point)); + group.writeEntry("SymbolSize", Worksheet::convertToSceneUnits(ui.sbSymbolSize->value(), Worksheet::Unit::Point)); group.writeEntry("SymbolRotation", ui.sbSymbolRotation->value()); group.writeEntry("SymbolOpacity", ui.sbSymbolOpacity->value()/100.0); group.writeEntry("SymbolFillingStyle", ui.cbSymbolFillingStyle->currentIndex()); group.writeEntry("SymbolFillingColor", ui.kcbSymbolFillingColor->color()); group.writeEntry("SymbolBorderStyle", ui.cbSymbolBorderStyle->currentIndex()); group.writeEntry("SymbolBorderColor", ui.kcbSymbolBorderColor->color()); - group.writeEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(ui.sbSymbolBorderWidth->value(),Worksheet::Point)); + group.writeEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(ui.sbSymbolBorderWidth->value(), Worksheet::Unit::Point)); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/HistogramDock.cpp b/src/kdefrontend/dockwidgets/HistogramDock.cpp index 1847825db..c9e7c3424 100644 --- a/src/kdefrontend/dockwidgets/HistogramDock.cpp +++ b/src/kdefrontend/dockwidgets/HistogramDock.cpp @@ -1,1716 +1,1716 @@ /*************************************************************************** File : HistogramDock.cpp Project : LabPlot Description : widget for Histogram properties -------------------------------------------------------------------- Copyright : (C) 2016 Anu Mittal (anu22mittal@gmail.com) Copyright : (C) 2018 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 "HistogramDock.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Symbol.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include #include #include #include #include /*! \class HistogramDock \brief Provides a widget for editing the properties of the Histograms (2D-curves) currently selected in the project explorer. If more than one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ HistogramDock::HistogramDock(QWidget* parent) : BaseDock(parent), cbDataColumn(new TreeViewComboBox) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; // Tab "General" auto* gridLayout = qobject_cast(ui.tabGeneral->layout()); gridLayout->addWidget(cbDataColumn, 3, 2, 1, 1); //Tab "Values" gridLayout = qobject_cast(ui.tabValues->layout()); cbValuesColumn = new TreeViewComboBox(ui.tabValues); gridLayout->addWidget(cbValuesColumn, 2, 2, 1, 1); //add formats for numeric values ui.cbValuesNumericFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbValuesNumericFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbValuesNumericFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbValuesNumericFormat->addItem(i18n("Automatic (e)"), QVariant('g')); ui.cbValuesNumericFormat->addItem(i18n("Automatic (E)"), QVariant('G')); //add format for date, time and datetime values for (const auto& s : AbstractColumn::dateFormats()) ui.cbValuesDateTimeFormat->addItem(s, QVariant(s)); for (const auto& s : AbstractColumn::timeFormats()) ui.cbValuesDateTimeFormat->addItem(s, QVariant(s)); for (const auto& s1 : AbstractColumn::dateFormats()) { for (const auto& s2 : AbstractColumn::timeFormats()) ui.cbValuesDateTimeFormat->addItem(s1 + ' ' + s2, QVariant(s1 + ' ' + s2)); } ui.cbValuesDateTimeFormat->setEditable(true); //Tab "Filling" ui.cbFillingColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bFillingOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leFillingFileName->setCompleter(new QCompleter(new QDirModel, this)); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { auto* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } ui.leBinWidth->setValidator(new QDoubleValidator(ui.leBinWidth)); ui.leBinRangesMin->setValidator(new QDoubleValidator(ui.leBinRangesMin)); ui.leBinRangesMax->setValidator(new QDoubleValidator(ui.leBinRangesMax)); //Slots //General connect(ui.leName, &QLineEdit::textChanged, this, &HistogramDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &HistogramDock::commentChanged); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( cbDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataColumnChanged(QModelIndex)) ); connect( ui.cbType, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int)) ); connect( ui.cbOrientation, SIGNAL(currentIndexChanged(int)), this, SLOT(orientationChanged(int))); connect( ui.cbBinningMethod, SIGNAL(currentIndexChanged(int)), this, SLOT(binningMethodChanged(int)) ); connect(ui.sbBinCount, static_cast(&QSpinBox::valueChanged), this, &HistogramDock::binCountChanged); connect(ui.leBinWidth, &QLineEdit::textChanged, this, &HistogramDock::binWidthChanged); connect( ui.chkAutoBinRanges, &QCheckBox::stateChanged, this, &HistogramDock::autoBinRangesChanged ); connect( ui.leBinRangesMin, &QLineEdit::textChanged, this, &HistogramDock::binRangesMinChanged ); connect( ui.leBinRangesMax, &QLineEdit::textChanged, this, &HistogramDock::binRangesMaxChanged ); //Line connect(ui.cbLineType, static_cast(&QComboBox::currentIndexChanged), this, &HistogramDock::lineTypeChanged); connect(ui.cbLineStyle, static_cast(&QComboBox::currentIndexChanged), this, &HistogramDock::lineStyleChanged); connect(ui.kcbLineColor, &KColorButton::changed, this, &HistogramDock::lineColorChanged); connect(ui.sbLineWidth, static_cast(&QDoubleSpinBox::valueChanged), this, &HistogramDock::lineWidthChanged); connect(ui.sbLineOpacity, static_cast(&QSpinBox::valueChanged), this, &HistogramDock::lineOpacityChanged); //Symbol connect( ui.cbSymbolStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsStyleChanged(int)) ); connect( ui.sbSymbolSize, SIGNAL(valueChanged(double)), this, SLOT(symbolsSizeChanged(double)) ); connect( ui.sbSymbolRotation, SIGNAL(valueChanged(int)), this, SLOT(symbolsRotationChanged(int)) ); connect( ui.sbSymbolOpacity, SIGNAL(valueChanged(int)), this, SLOT(symbolsOpacityChanged(int)) ); connect( ui.cbSymbolFillingStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsFillingStyleChanged(int)) ); connect( ui.kcbSymbolFillingColor, SIGNAL(changed(QColor)), this, SLOT(symbolsFillingColorChanged(QColor)) ); connect( ui.cbSymbolBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsBorderStyleChanged(int)) ); connect( ui.kcbSymbolBorderColor, SIGNAL(changed(QColor)), this, SLOT(symbolsBorderColorChanged(QColor)) ); connect( ui.sbSymbolBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(symbolsBorderWidthChanged(double)) ); //Values connect( ui.cbValuesType, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesTypeChanged(int)) ); connect( cbValuesColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(valuesColumnChanged(QModelIndex)) ); connect( ui.cbValuesPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesPositionChanged(int)) ); connect( ui.sbValuesDistance, SIGNAL(valueChanged(double)), this, SLOT(valuesDistanceChanged(double)) ); connect( ui.sbValuesRotation, SIGNAL(valueChanged(int)), this, SLOT(valuesRotationChanged(int)) ); connect( ui.sbValuesOpacity, SIGNAL(valueChanged(int)), this, SLOT(valuesOpacityChanged(int)) ); connect(ui.cbValuesNumericFormat, QOverload::of(&QComboBox::currentIndexChanged), this, &HistogramDock::valuesNumericFormatChanged); connect(ui.sbValuesPrecision, QOverload::of(&QSpinBox::valueChanged), this, &HistogramDock::valuesPrecisionChanged); connect(ui.cbValuesDateTimeFormat, &QComboBox::currentTextChanged, this, &HistogramDock::valuesDateTimeFormatChanged); connect( ui.leValuesPrefix, SIGNAL(returnPressed()), this, SLOT(valuesPrefixChanged()) ); connect( ui.leValuesSuffix, SIGNAL(returnPressed()), this, SLOT(valuesSuffixChanged()) ); connect( ui.kfrValuesFont, SIGNAL(fontSelected(QFont)), this, SLOT(valuesFontChanged(QFont)) ); connect( ui.kcbValuesColor, SIGNAL(changed(QColor)), this, SLOT(valuesColorChanged(QColor)) ); //Filling connect(ui.chkFillingEnabled, &QCheckBox::stateChanged, this, &HistogramDock::fillingEnabledChanged); connect( ui.cbFillingType, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingTypeChanged(int)) ); connect( ui.cbFillingColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorStyleChanged(int)) ); connect( ui.cbFillingImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingImageStyleChanged(int)) ); connect( ui.cbFillingBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingBrushStyleChanged(int)) ); connect( ui.bFillingOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile())); connect( ui.leFillingFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leFillingFileName, SIGNAL(textChanged(QString)), this, SLOT(fileNameChanged()) ); connect( ui.kcbFillingFirstColor, SIGNAL(changed(QColor)), this, SLOT(fillingFirstColorChanged(QColor)) ); connect( ui.kcbFillingSecondColor, SIGNAL(changed(QColor)), this, SLOT(fillingSecondColorChanged(QColor)) ); connect( ui.sbFillingOpacity, SIGNAL(valueChanged(int)), this, SLOT(fillingOpacityChanged(int)) ); //Error bars connect( ui.cbErrorType, SIGNAL(currentIndexChanged(int)), this, SLOT(errorTypeChanged(int)) ); connect( ui.cbErrorBarsType, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsTypeChanged(int)) ); connect( ui.sbErrorBarsCapSize, SIGNAL(valueChanged(double)), this, SLOT(errorBarsCapSizeChanged(double)) ); connect( ui.cbErrorBarsStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsStyleChanged(int)) ); connect( ui.kcbErrorBarsColor, SIGNAL(changed(QColor)), this, SLOT(errorBarsColorChanged(QColor)) ); connect( ui.sbErrorBarsWidth, SIGNAL(valueChanged(double)), this, SLOT(errorBarsWidthChanged(double)) ); connect( ui.sbErrorBarsOpacity, SIGNAL(valueChanged(int)), this, SLOT(errorBarsOpacityChanged(int)) ); //template handler auto* frame = new QFrame(this); auto* layout = new QHBoxLayout(frame); layout->setContentsMargins(0, 11, 0, 11); auto* templateHandler = new TemplateHandler(this, TemplateHandler::Histogram); layout->addWidget(templateHandler); connect(templateHandler, &TemplateHandler::loadConfigRequested, this, &HistogramDock::loadConfigFromTemplate); connect(templateHandler, &TemplateHandler::saveConfigRequested, this, &HistogramDock::saveConfigAsTemplate); connect(templateHandler, &TemplateHandler::info, this, &HistogramDock::info); ui.verticalLayout->addWidget(frame); retranslateUi(); init(); //TODO: activate the tab for error-bars again once the functionality is implemented ui.tabWidget->removeTab(5); } HistogramDock::~HistogramDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } void HistogramDock::init() { //General //bins option ui.cbBinningMethod->addItem(i18n("By Number")); ui.cbBinningMethod->addItem(i18n("By Width")); ui.cbBinningMethod->addItem(i18n("Square-root")); ui.cbBinningMethod->addItem(i18n("Rice")); ui.cbBinningMethod->addItem(i18n("Sturges")); ui.cbBinningMethod->addItem(i18n("Doane")); ui.cbBinningMethod->addItem(i18n("Scott")); //histogram type ui.cbType->addItem(i18n("Ordinary Histogram")); ui.cbType->addItem(i18n("Cumulative Histogram")); // ui.cbType->addItem(i18n("AvgShifted Histogram")); //Orientation ui.cbOrientation->addItem(i18n("Vertical")); ui.cbOrientation->addItem(i18n("Horizontal")); //Line ui.cbLineType->addItem(i18n("None")); ui.cbLineType->addItem(i18n("Bars")); ui.cbLineType->addItem(i18n("Envelope")); ui.cbLineType->addItem(i18n("Drop Lines")); GuiTools::updatePenStyles(ui.cbLineStyle, Qt::black); //Symbols GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); QPainter pa; //TODO size of the icon depending on the actual height of the combobox? int iconSize = 20; QPixmap pm(iconSize, iconSize); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); QPen pen(Qt::SolidPattern, 0); const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black; pen.setColor(color); pa.setPen( pen ); ui.cbSymbolStyle->addItem(i18n("None")); for (int i = 1; i < 19; ++i) { //TODO: use enum count auto style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; //Values ui.cbValuesType->addItem(i18n("No Values")); ui.cbValuesType->addItem(i18n("Frequency")); ui.cbValuesType->addItem(i18n("Custom Column")); ui.cbValuesPosition->addItem(i18n("Above")); ui.cbValuesPosition->addItem(i18n("Below")); ui.cbValuesPosition->addItem(i18n("Left")); ui.cbValuesPosition->addItem(i18n("Right")); //Filling ui.cbFillingType->clear(); ui.cbFillingType->addItem(i18n("Color")); ui.cbFillingType->addItem(i18n("Image")); ui.cbFillingType->addItem(i18n("Pattern")); ui.cbFillingColorStyle->clear(); ui.cbFillingColorStyle->addItem(i18n("Single Color")); ui.cbFillingColorStyle->addItem(i18n("Horizontal Linear Gradient")); ui.cbFillingColorStyle->addItem(i18n("Vertical Linear Gradient")); ui.cbFillingColorStyle->addItem(i18n("Diagonal Linear Gradient (Start From Top Left)")); ui.cbFillingColorStyle->addItem(i18n("Diagonal Linear Gradient (Start From Bottom Left)")); ui.cbFillingColorStyle->addItem(i18n("Radial Gradient")); ui.cbFillingImageStyle->clear(); ui.cbFillingImageStyle->addItem(i18n("Scaled and Cropped")); ui.cbFillingImageStyle->addItem(i18n("Scaled")); ui.cbFillingImageStyle->addItem(i18n("Scaled, Keep Proportions")); ui.cbFillingImageStyle->addItem(i18n("Centered")); ui.cbFillingImageStyle->addItem(i18n("Tiled")); ui.cbFillingImageStyle->addItem(i18n("Center Tiled")); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, Qt::SolidPattern); //Error-bars pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10);//vert. line pa.drawLine(10,3,10,17);//hor. line pa.end(); ui.cbErrorBarsType->addItem(i18n("Bars")); ui.cbErrorBarsType->setItemIcon(0, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); //vert. line pa.drawLine(10,3,10,17); //hor. line pa.drawLine(7,3,13,3); //upper cap pa.drawLine(7,17,13,17); //bottom cap pa.drawLine(3,7,3,13); //left cap pa.drawLine(17,7,17,13); //right cap pa.end(); ui.cbErrorBarsType->addItem(i18n("Bars with Ends")); ui.cbErrorBarsType->setItemIcon(1, pm); ui.cbErrorType->addItem(i18n("No Errors")); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, Qt::black); } void HistogramDock::setModel() { m_aspectTreeModel->enablePlottableColumnsOnly(true); m_aspectTreeModel->enableShowPlotDesignation(true); QList list{AspectType::Folder, AspectType::Workbook, AspectType::Datapicker, AspectType::DatapickerCurve, AspectType::Spreadsheet, AspectType::LiveDataSource, AspectType::Column, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::XYFitCurve, AspectType::XYSmoothCurve, AspectType::CantorWorksheet}; cbDataColumn->setTopLevelClasses(list); cbValuesColumn->setTopLevelClasses(list); list = {AspectType::Column}; m_aspectTreeModel->setSelectableAspects(list); cbDataColumn->setModel(m_aspectTreeModel); cbValuesColumn->setModel(m_aspectTreeModel); } void HistogramDock::setCurves(QList list) { m_initializing = true; m_curvesList = list; m_curve = list.first(); m_aspect = list.first(); Q_ASSERT(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); setModel(); //if there are more then one curve in the list, disable the content in the tab "general" if (m_curvesList.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.lXColumn->setEnabled(true); cbDataColumn->setEnabled(true); this->setModelIndexFromColumn(cbDataColumn, m_curve->dataColumn()); this->setModelIndexFromColumn(cbValuesColumn, m_curve->valuesColumn()); ui.leName->setText(m_curve->name()); ui.leComment->setText(m_curve->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.lXColumn->setEnabled(false); cbDataColumn->setEnabled(false); cbDataColumn->setCurrentModelIndex(QModelIndex()); cbValuesColumn->setCurrentModelIndex(QModelIndex()); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first curve ui.cbType->setCurrentIndex(m_curve->type()); ui.cbOrientation->setCurrentIndex(m_curve->orientation()); ui.cbBinningMethod->setCurrentIndex(m_curve->binningMethod()); ui.sbBinCount->setValue(m_curve->binCount()); ui.leBinWidth->setText(QString::number(m_curve->binWidth())); ui.chkAutoBinRanges->setChecked(m_curve->autoBinRanges()); ui.leBinRangesMin->setText( QString::number(m_curve->binRangesMin()) ); ui.leBinRangesMax->setText( QString::number(m_curve->binRangesMax()) ); ui.chkVisible->setChecked( m_curve->isVisible() ); KConfig config(QString(), KConfig::SimpleConfig); loadConfig(config); //Slots //General-tab connect(m_curve, &Histogram::aspectDescriptionChanged, this, &HistogramDock::curveDescriptionChanged); connect(m_curve, &Histogram::dataColumnChanged, this, &HistogramDock::curveDataColumnChanged); connect(m_curve, &Histogram::typeChanged, this, &HistogramDock::curveTypeChanged); connect(m_curve, &Histogram::orientationChanged, this, &HistogramDock::curveOrientationChanged); connect(m_curve, &Histogram::binningMethodChanged, this, &HistogramDock::curveBinningMethodChanged); connect(m_curve, &Histogram::binCountChanged, this, &HistogramDock::curveBinCountChanged); connect(m_curve, &Histogram::binWidthChanged, this, &HistogramDock::curveBinWidthChanged); connect(m_curve, &Histogram::autoBinRangesChanged, this, &HistogramDock::curveAutoBinRangesChanged); connect(m_curve, &Histogram::binRangesMinChanged, this, &HistogramDock::curveBinRangesMinChanged); connect(m_curve, &Histogram::binRangesMaxChanged, this, &HistogramDock::curveBinRangesMaxChanged); connect(m_curve, &Histogram::visibilityChanged, this, &HistogramDock::curveVisibilityChanged); //Line-tab connect(m_curve, &Histogram::linePenChanged, this, &HistogramDock::curveLinePenChanged); connect(m_curve, &Histogram::lineOpacityChanged, this, &HistogramDock::curveLineOpacityChanged); //Symbol-Tab connect(m_curve, &Histogram::symbolsStyleChanged, this, &HistogramDock::curveSymbolsStyleChanged); connect(m_curve, &Histogram::symbolsSizeChanged, this, &HistogramDock::curveSymbolsSizeChanged); connect(m_curve, &Histogram::symbolsRotationAngleChanged, this, &HistogramDock::curveSymbolsRotationAngleChanged); connect(m_curve, &Histogram::symbolsOpacityChanged, this, &HistogramDock::curveSymbolsOpacityChanged); connect(m_curve, &Histogram::symbolsBrushChanged, this, &HistogramDock::curveSymbolsBrushChanged); connect(m_curve, &Histogram::symbolsPenChanged, this, &HistogramDock::curveSymbolsPenChanged); //Values-Tab connect(m_curve, &Histogram::valuesTypeChanged, this, &HistogramDock::curveValuesTypeChanged); connect(m_curve, &Histogram::valuesColumnChanged, this, &HistogramDock::curveValuesColumnChanged); connect(m_curve, &Histogram::valuesPositionChanged, this, &HistogramDock::curveValuesPositionChanged); connect(m_curve, &Histogram::valuesDistanceChanged, this, &HistogramDock::curveValuesDistanceChanged); connect(m_curve, &Histogram::valuesOpacityChanged, this, &HistogramDock::curveValuesOpacityChanged); connect(m_curve, &Histogram::valuesRotationAngleChanged, this, &HistogramDock::curveValuesRotationAngleChanged); connect(m_curve, &Histogram::valuesNumericFormatChanged, this, &HistogramDock::curveValuesNumericFormatChanged); connect(m_curve, &Histogram::valuesPrecisionChanged, this, &HistogramDock::curveValuesPrecisionChanged); connect(m_curve, &Histogram::valuesDateTimeFormatChanged, this, &HistogramDock::curveValuesDateTimeFormatChanged); connect(m_curve, &Histogram::valuesPrefixChanged, this, &HistogramDock::curveValuesPrefixChanged); connect(m_curve, &Histogram::valuesSuffixChanged, this, &HistogramDock::curveValuesSuffixChanged); connect(m_curve, &Histogram::valuesFontChanged, this, &HistogramDock::curveValuesFontChanged); connect(m_curve, &Histogram::valuesColorChanged, this, &HistogramDock::curveValuesColorChanged); //Filling-Tab connect( m_curve, &Histogram::fillingTypeChanged, this, &HistogramDock::curveFillingTypeChanged); connect( m_curve, &Histogram::fillingColorStyleChanged, this, &HistogramDock::curveFillingColorStyleChanged); connect( m_curve, &Histogram::fillingImageStyleChanged, this, &HistogramDock::curveFillingImageStyleChanged); connect( m_curve, &Histogram::fillingBrushStyleChanged, this, &HistogramDock::curveFillingBrushStyleChanged); connect( m_curve, &Histogram::fillingFirstColorChanged, this, &HistogramDock::curveFillingFirstColorChanged); connect( m_curve, &Histogram::fillingSecondColorChanged, this, &HistogramDock::curveFillingSecondColorChanged); connect( m_curve, &Histogram::fillingFileNameChanged, this, &HistogramDock::curveFillingFileNameChanged); connect( m_curve, &Histogram::fillingOpacityChanged, this, &HistogramDock::curveFillingOpacityChanged); //"Error bars"-Tab connect(m_curve, &Histogram::errorTypeChanged, this, &HistogramDock::curveErrorTypeChanged); connect(m_curve, &Histogram::errorBarsCapSizeChanged, this, &HistogramDock::curveErrorBarsCapSizeChanged); connect(m_curve, &Histogram::errorBarsTypeChanged, this, &HistogramDock::curveErrorBarsTypeChanged); connect(m_curve, &Histogram::errorBarsPenChanged, this, &HistogramDock::curveErrorBarsPenChanged); connect(m_curve, &Histogram::errorBarsOpacityChanged, this, &HistogramDock::curveErrorBarsOpacityChanged); m_initializing = false; } void HistogramDock::setModelIndexFromColumn(TreeViewComboBox* cb, const AbstractColumn* column) { if (column) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else cb->setCurrentModelIndex(QModelIndex()); } void HistogramDock::retranslateUi() { //TODO: // ui.lName->setText(i18n("Name")); // ui.lComment->setText(i18n("Comment")); // ui.chkVisible->setText(i18n("Visible")); // ui.lXColumn->setText(i18n("x-data")); // ui.lYColumn->setText(i18n("y-data")); //TODO updatePenStyles, updateBrushStyles for all comboboxes } //************************************************************* //**** SLOTs for changes triggered in HistogramDock ***** //************************************************************* // "General"-tab void HistogramDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setVisible(state); } void HistogramDock::typeChanged(int index) { if (m_initializing) return; auto histogramType = Histogram::HistogramType(index); for (auto* curve : m_curvesList) curve->setType(histogramType); } void HistogramDock::dataColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto aspect = static_cast(index.internalPointer()); AbstractColumn* column(nullptr); if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve : m_curvesList) curve->setDataColumn(column); } void HistogramDock::orientationChanged(int index) { if (m_initializing) return; auto orientation = Histogram::HistogramOrientation(index); for (auto* curve : m_curvesList) curve->setOrientation(orientation); } void HistogramDock::binningMethodChanged(int index) { const auto binningMethod = Histogram::BinningMethod(index); if (binningMethod == Histogram::ByNumber) { ui.lBinCount->show(); ui.sbBinCount->show(); ui.lBinWidth->hide(); ui.leBinWidth->hide(); } else if (binningMethod == Histogram::ByWidth) { ui.lBinCount->hide(); ui.sbBinCount->hide(); ui.lBinWidth->show(); ui.leBinWidth->show(); } else { ui.lBinCount->hide(); ui.sbBinCount->hide(); ui.lBinWidth->hide(); ui.leBinWidth->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setBinningMethod(binningMethod); } void HistogramDock::binCountChanged(int value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setBinCount(value); } void HistogramDock::binWidthChanged() { if (m_initializing) return; float width = ui.leBinWidth->text().toDouble(); for (auto* curve : m_curvesList) curve->setBinWidth(width); } void HistogramDock::autoBinRangesChanged(int state) { bool checked = (state == Qt::Checked); ui.leBinRangesMin->setEnabled(!checked); ui.leBinRangesMax->setEnabled(!checked); if (m_initializing) return; for (auto* hist : m_curvesList) hist->setAutoBinRanges(checked); } void HistogramDock::binRangesMinChanged(const QString& value) { DEBUG("HistogramDock::binRangesMinChanged() value = " << value.toDouble()); if (m_initializing) return; DEBUG(" set value") const double min = value.toDouble(); for (auto* hist : m_curvesList) hist->setBinRangesMin(min); } void HistogramDock::binRangesMaxChanged(const QString& value) { if (m_initializing) return; const double max = value.toDouble(); for (auto* hist : m_curvesList) hist->setBinRangesMax(max); } //Line tab void HistogramDock::lineTypeChanged(int index) { auto lineType = Histogram::LineType(index); if ( lineType == Histogram::NoLine) { ui.cbLineStyle->setEnabled(false); ui.kcbLineColor->setEnabled(false); ui.sbLineWidth->setEnabled(false); ui.sbLineOpacity->setEnabled(false); } else { ui.cbLineStyle->setEnabled(true); ui.kcbLineColor->setEnabled(true); ui.sbLineWidth->setEnabled(true); ui.sbLineOpacity->setEnabled(true); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineType(lineType); } void HistogramDock::lineStyleChanged(int index) { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); pen.setStyle(penStyle); curve->setLinePen(pen); } } void HistogramDock::lineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); pen.setColor(color); curve->setLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing = false; } void HistogramDock::lineWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setLinePen(pen); } } void HistogramDock::lineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setLineOpacity(opacity); } //"Symbol"-tab void HistogramDock::symbolsStyleChanged(int index) { const auto style = Symbol::Style(index); if (style == Symbol::NoSymbols) { ui.sbSymbolSize->setEnabled(false); ui.sbSymbolRotation->setEnabled(false); ui.sbSymbolOpacity->setEnabled(false); ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); ui.cbSymbolBorderStyle->setEnabled(false); ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.sbSymbolSize->setEnabled(true); ui.sbSymbolRotation->setEnabled(true); ui.sbSymbolOpacity->setEnabled(true); //enable/disable the symbol filling options in the GUI depending on the currently selected symbol. if (style != Symbol::Line && style != Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } ui.cbSymbolBorderStyle->setEnabled(true); bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setSymbolsStyle(style); } void HistogramDock::symbolsSizeChanged(double value) { if (m_initializing) return; for (auto* curve : m_curvesList) - curve->setSymbolsSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + curve->setSymbolsSize( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void HistogramDock::symbolsRotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setSymbolsRotationAngle(value); } void HistogramDock::symbolsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setSymbolsOpacity(opacity); } void HistogramDock::symbolsFillingStyleChanged(int index) { auto brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; for (auto* curve : m_curvesList) { brush = curve->symbolsBrush(); brush.setStyle(brushStyle); curve->setSymbolsBrush(brush); } } void HistogramDock::symbolsFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; for (auto* curve : m_curvesList) { brush = curve->symbolsBrush(); brush.setColor(color); curve->setSymbolsBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void HistogramDock::symbolsBorderStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); pen.setStyle(penStyle); curve->setSymbolsPen(pen); } } void HistogramDock::symbolsBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); pen.setColor(color); curve->setSymbolsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void HistogramDock::symbolsBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setSymbolsPen(pen); } } //Values tab /*! called when the type of the values (none, x, y, (x,y) etc.) was changed. */ void HistogramDock::valuesTypeChanged(int index) { if (m_initializing) return; this->updateValuesWidgets(); auto valuesType = Histogram::ValuesType(index); for (auto* curve : m_curvesList) curve->setValuesType(valuesType); } /*! depending on the currently selected values column type (column mode) updates the widgets for the values column format, shows/hides the allowed widgets, fills the corresponding combobox with the possible entries. Called when the values column was changed. */ void HistogramDock::updateValuesWidgets() { const auto type = Histogram::ValuesType(ui.cbValuesType->currentIndex()); bool showValues = (type != Histogram::NoValues); ui.cbValuesPosition->setEnabled(showValues); ui.sbValuesDistance->setEnabled(showValues); ui.sbValuesRotation->setEnabled(showValues); ui.sbValuesOpacity->setEnabled(showValues); ui.kfrValuesFont->setEnabled(showValues); ui.kcbValuesColor->setEnabled(showValues); bool hasInteger = false; bool hasNumeric = false; bool hasDateTime = false; if (type == Histogram::ValuesCustomColumn) { ui.lValuesColumn->show(); cbValuesColumn->show(); auto* column = static_cast(cbValuesColumn->currentModelIndex().internalPointer()); if (column) { if (column->columnMode() == AbstractColumn::ColumnMode::Numeric) hasNumeric = true; else if (column->columnMode() == AbstractColumn::ColumnMode::Integer || column->columnMode() == AbstractColumn::ColumnMode::BigInt) hasInteger = true; else if (column->columnMode() == AbstractColumn::ColumnMode::DateTime) hasDateTime = true; } } else { ui.lValuesColumn->hide(); cbValuesColumn->hide(); if (type == Histogram::ValuesBinEntries) hasInteger = true; } //hide all the format related widgets first and //then show only what is required depending of the column mode(s) ui.lValuesFormat->hide(); ui.lValuesNumericFormat->hide(); ui.cbValuesNumericFormat->hide(); ui.lValuesPrecision->hide(); ui.sbValuesPrecision->hide(); ui.lValuesDateTimeFormat->hide(); ui.cbValuesDateTimeFormat->hide(); if (hasNumeric || hasInteger) { ui.lValuesFormat->show(); ui.lValuesNumericFormat->show(); ui.cbValuesNumericFormat->show(); } //precision is only available for Numeric if (hasNumeric) { ui.lValuesPrecision->show(); ui.sbValuesPrecision->show(); } if (hasDateTime) { ui.lValuesFormat->show(); ui.lValuesDateTimeFormat->show(); ui.cbValuesDateTimeFormat->show(); } } /*! called when the custom column for the values was changed. */ void HistogramDock::valuesColumnChanged(const QModelIndex& index) { if (m_initializing) return; this->updateValuesWidgets(); auto* column = static_cast(index.internalPointer()); for (auto* curve : m_curvesList) curve->setValuesColumn(column); } void HistogramDock::valuesPositionChanged(int index) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesPosition(Histogram::ValuesPosition(index)); } void HistogramDock::valuesDistanceChanged(double value) { if (m_initializing) return; for (auto* curve : m_curvesList) - curve->setValuesDistance( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + curve->setValuesDistance( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void HistogramDock::valuesRotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesRotationAngle(value); } void HistogramDock::valuesOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setValuesOpacity(opacity); } void HistogramDock::valuesNumericFormatChanged(int index) { if (m_initializing) return; char format = ui.cbValuesNumericFormat->itemData(index).toChar().toLatin1(); for (auto* curve : m_curvesList) curve->setValuesNumericFormat(format); } void HistogramDock::valuesPrecisionChanged(int precision) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesPrecision(precision); } void HistogramDock::valuesDateTimeFormatChanged(const QString& format) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesDateTimeFormat(format); } void HistogramDock::valuesPrefixChanged() { if (m_initializing) return; QString prefix = ui.leValuesPrefix->text(); for (auto* curve : m_curvesList) curve->setValuesPrefix(prefix); } void HistogramDock::valuesSuffixChanged() { if (m_initializing) return; QString suffix = ui.leValuesSuffix->text(); for (auto* curve : m_curvesList) curve->setValuesSuffix(suffix); } void HistogramDock::valuesFontChanged(const QFont& font) { if (m_initializing) return; QFont valuesFont = font; - valuesFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); + valuesFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Unit::Point) ); for (auto* curve : m_curvesList) curve->setValuesFont(valuesFont); } void HistogramDock::valuesColorChanged(const QColor& color) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesColor(color); } //Filling-tab void HistogramDock::fillingEnabledChanged(int state) { ui.cbFillingType->setEnabled(state); ui.cbFillingColorStyle->setEnabled(state); ui.cbFillingBrushStyle->setEnabled(state); ui.cbFillingImageStyle->setEnabled(state); ui.kcbFillingFirstColor->setEnabled(state); ui.kcbFillingSecondColor->setEnabled(state); ui.leFillingFileName->setEnabled(state); ui.bFillingOpen->setEnabled(state); ui.sbFillingOpacity->setEnabled(state); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingEnabled(state); } void HistogramDock::fillingTypeChanged(int index) { auto type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lFillingColorStyle->show(); ui.cbFillingColorStyle->show(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); auto style = (PlotArea::BackgroundColorStyle) ui.cbFillingColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color:")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->show(); ui.cbFillingImageStyle->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->show(); ui.leFillingFileName->show(); ui.bFillingOpen->show(); ui.lFillingFirstColor->hide(); ui.kcbFillingFirstColor->hide(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->show(); ui.cbFillingBrushStyle->show(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingType(type); } void HistogramDock::fillingColorStyleChanged(int index) { auto style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color:")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingColorStyle(style); } void HistogramDock::fillingImageStyleChanged(int index) { if (m_initializing) return; auto style = (PlotArea::BackgroundImageStyle)index; for (auto* curve : m_curvesList) curve->setFillingImageStyle(style); } void HistogramDock::fillingBrushStyleChanged(int index) { if (m_initializing) return; auto style = (Qt::BrushStyle)index; for (auto* curve : m_curvesList) curve->setFillingBrushStyle(style); } void HistogramDock::fillingFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingFirstColor(c); } void HistogramDock::fillingSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingSecondColor(c); } //"Error bars"-Tab void HistogramDock::errorTypeChanged(int index) const { bool b = (index != 0); ui.lErrorData->setVisible(b); ui.lErrorFormat->setVisible(b); ui.lErrorBarsType->setVisible(b); ui.cbErrorBarsType->setVisible(b); ui.lErrorBarsStyle->setVisible(b); ui.cbErrorBarsStyle->setVisible(b); ui.lErrorBarsColor->setVisible(b); ui.kcbErrorBarsColor->setVisible(b); ui.lErrorBarsWidth->setVisible(b); ui.sbErrorBarsWidth->setVisible(b); ui.lErrorBarsOpacity->setVisible(b); ui.sbErrorBarsOpacity->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setErrorType(Histogram::ErrorType(index)); } void HistogramDock::errorBarsTypeChanged(int index) const { auto type = XYCurve::ErrorBarsType(index); bool b = (type == XYCurve::ErrorBarsWithEnds); ui.lErrorBarsCapSize->setVisible(b); ui.sbErrorBarsCapSize->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setErrorBarsType(type); } void HistogramDock::errorBarsCapSizeChanged(double value) const { if (m_initializing) return; - float size = Worksheet::convertToSceneUnits(value, Worksheet::Point); + float size = Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point); for (auto* curve : m_curvesList) curve->setErrorBarsCapSize(size); } void HistogramDock::errorBarsStyleChanged(int index) const { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); pen.setStyle(penStyle); curve->setErrorBarsPen(pen); } } void HistogramDock::errorBarsColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); pen.setColor(color); curve->setErrorBarsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbErrorBarsStyle, color); m_initializing = false; } void HistogramDock::errorBarsWidthChanged(double value) const { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setErrorBarsPen(pen); } } void HistogramDock::errorBarsOpacityChanged(int value) const { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setErrorBarsOpacity(opacity); } /*! opens a file dialog and lets the user select the image file. */ void HistogramDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "HistogramDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += ' ' + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leFillingFileName->setText( path ); for (auto* curve : m_curvesList) curve->setFillingFileName(path); } void HistogramDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leFillingFileName->text(); for (auto* curve : m_curvesList) curve->setFillingFileName(fileName); } void HistogramDock::fillingOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setFillingOpacity(opacity); } //************************************************************* //*********** SLOTs for changes triggered in Histogram ******* //************************************************************* //General-Tab void HistogramDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void HistogramDock::curveDataColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromColumn(cbDataColumn, column); m_initializing = false; } void HistogramDock::curveTypeChanged(Histogram::HistogramType type) { m_initializing = true; ui.cbType->setCurrentIndex((int)type); m_initializing = false; } void HistogramDock::curveOrientationChanged(Histogram::HistogramOrientation orientation) { m_initializing = true; ui.cbOrientation->setCurrentIndex((int)orientation); m_initializing = false; } void HistogramDock::curveBinningMethodChanged(Histogram::BinningMethod method) { m_initializing = true; ui.cbBinningMethod->setCurrentIndex((int)method); m_initializing = false; } void HistogramDock::curveBinCountChanged(int count) { m_initializing = true; ui.sbBinCount->setValue(count); m_initializing = false; } void HistogramDock::curveBinWidthChanged(float width) { m_initializing = true; ui.leBinWidth->setText(QString::number(width)); m_initializing = false; } void HistogramDock::curveAutoBinRangesChanged(bool value) { m_initializing = true; ui.chkAutoBinRanges->setChecked(value); m_initializing = false; } void HistogramDock::curveBinRangesMinChanged(double value) { m_initializing = true; ui.leBinRangesMin->setText(QString::number(value)); m_initializing = false; } void HistogramDock::curveBinRangesMaxChanged(double value) { m_initializing = true; ui.leBinRangesMax->setText(QString::number(value)); m_initializing = false; } //Line-Tab void HistogramDock::curveLineTypeChanged(Histogram::LineType type) { m_initializing = true; ui.cbLineType->setCurrentIndex((int)type); m_initializing = false; } void HistogramDock::curveLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( (int)pen.style()); ui.kcbLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color()); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void HistogramDock::curveLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //Symbol-Tab void HistogramDock::curveSymbolsStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style); m_initializing = false; } void HistogramDock::curveSymbolsSizeChanged(qreal size) { m_initializing = true; - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void HistogramDock::curveSymbolsRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbSymbolRotation->setValue(angle); m_initializing = false; } void HistogramDock::curveSymbolsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbSymbolOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void HistogramDock::curveSymbolsBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void HistogramDock::curveSymbolsPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } //Values-Tab void HistogramDock::curveValuesTypeChanged(Histogram::ValuesType type) { m_initializing = true; ui.cbValuesType->setCurrentIndex((int) type); m_initializing = false; } void HistogramDock::curveValuesColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromColumn(cbValuesColumn, column); m_initializing = false; } void HistogramDock::curveValuesPositionChanged(Histogram::ValuesPosition position) { m_initializing = true; - ui.cbValuesPosition->setCurrentIndex((int) position); + ui.cbValuesPosition->setCurrentIndex((int) position); m_initializing = false; } void HistogramDock::curveValuesDistanceChanged(qreal distance) { m_initializing = true; - ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::Point) ); + ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::Unit::Point) ); m_initializing = false; } void HistogramDock::curveValuesRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbValuesRotation->setValue(angle); m_initializing = false; } void HistogramDock::curveValuesOpacityChanged(qreal opacity) { m_initializing = true; ui.sbValuesOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void HistogramDock::curveValuesNumericFormatChanged(char format) { m_initializing = true; ui.cbValuesNumericFormat->setCurrentIndex(ui.cbValuesNumericFormat->findData(format)); m_initializing = false; } void HistogramDock::curveValuesPrecisionChanged(int precision) { m_initializing = true; ui.sbValuesPrecision->setValue(precision); m_initializing = false; } void HistogramDock::curveValuesDateTimeFormatChanged(const QString& format) { m_initializing = true; ui.cbValuesDateTimeFormat->setCurrentText(format); m_initializing = false; } void HistogramDock::curveValuesPrefixChanged(const QString& prefix) { m_initializing = true; ui.leValuesPrefix->setText(prefix); m_initializing = false; } void HistogramDock::curveValuesSuffixChanged(const QString& suffix) { m_initializing = true; ui.leValuesSuffix->setText(suffix); m_initializing = false; } void HistogramDock::curveValuesFontChanged(QFont font) { m_initializing = true; - font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrValuesFont->setFont(font); m_initializing = false; } void HistogramDock::curveValuesColorChanged(QColor color) { m_initializing = true; ui.kcbValuesColor->setColor(color); m_initializing = false; } void HistogramDock::curveVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //Filling void HistogramDock::curveFillingEnabledChanged(bool status) { m_initializing = true; ui.chkFillingEnabled->setChecked(status); m_initializing = false; } void HistogramDock::curveFillingTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbFillingType->setCurrentIndex(type); m_initializing = false; } void HistogramDock::curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbFillingColorStyle->setCurrentIndex(style); m_initializing = false; } void HistogramDock::curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbFillingImageStyle->setCurrentIndex(style); m_initializing = false; } void HistogramDock::curveFillingBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbFillingBrushStyle->setCurrentIndex(style); m_initializing = false; } void HistogramDock::curveFillingFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingFirstColor->setColor(color); m_initializing = false; } void HistogramDock::curveFillingSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingSecondColor->setColor(color); m_initializing = false; } void HistogramDock::curveFillingFileNameChanged(QString& filename) { m_initializing = true; ui.leFillingFileName->setText(filename); m_initializing = false; } void HistogramDock::curveFillingOpacityChanged(float opacity) { m_initializing = true; ui.sbFillingOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //"Error bars"-Tab void HistogramDock::curveErrorTypeChanged(Histogram::ErrorType type) { m_initializing = true; ui.cbErrorType->setCurrentIndex((int)type); m_initializing = false; } void HistogramDock::curveErrorBarsCapSizeChanged(qreal size) { m_initializing = true; - ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void HistogramDock::curveErrorBarsTypeChanged(XYCurve::ErrorBarsType type) { m_initializing = true; ui.cbErrorBarsType->setCurrentIndex((int)type); m_initializing = false; } void HistogramDock::curveErrorBarsPenChanged(const QPen& pen) { m_initializing = true; ui.cbErrorBarsStyle->setCurrentIndex( (int) pen.style()); ui.kcbErrorBarsColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, pen.color()); - ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); + ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Unit::Point) ); m_initializing = false; } void HistogramDock::curveErrorBarsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbErrorBarsOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //************************************************************* //************************* Settings ************************** //************************************************************* void HistogramDock::loadConfig(KConfig& config) { KConfigGroup group = config.group(QLatin1String("Histogram")); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in HistogramDock::setCurves(). //Line ui.cbLineType->setCurrentIndex( group.readEntry("LineType", (int) m_curve->lineType()) ); ui.cbLineStyle->setCurrentIndex( group.readEntry("LineStyle", (int) m_curve->linePen().style()) ); ui.kcbLineColor->setColor( group.readEntry("LineColor", m_curve->linePen().color()) ); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_curve->linePen().widthF()), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_curve->linePen().widthF()), Worksheet::Unit::Point) ); ui.sbLineOpacity->setValue( round(group.readEntry("LineOpacity", m_curve->lineOpacity())*100.0) ); //Symbols ui.cbSymbolStyle->setCurrentIndex( group.readEntry("SymbolStyle", (int)m_curve->symbolsStyle()) ); - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_curve->symbolsSize()), Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_curve->symbolsSize()), Worksheet::Unit::Point) ); ui.sbSymbolRotation->setValue( group.readEntry("SymbolRotation", m_curve->symbolsRotationAngle()) ); ui.sbSymbolOpacity->setValue( round(group.readEntry("SymbolOpacity", m_curve->symbolsOpacity())*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( group.readEntry("SymbolFillingStyle", (int) m_curve->symbolsBrush().style()) ); ui.kcbSymbolFillingColor->setColor( group.readEntry("SymbolFillingColor", m_curve->symbolsBrush().color()) ); ui.cbSymbolBorderStyle->setCurrentIndex( group.readEntry("SymbolBorderStyle", (int) m_curve->symbolsPen().style()) ); ui.kcbSymbolBorderColor->setColor( group.readEntry("SymbolBorderColor", m_curve->symbolsPen().color()) ); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_curve->symbolsPen().widthF()), Worksheet::Point) ); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_curve->symbolsPen().widthF()), Worksheet::Unit::Point) ); //Values ui.cbValuesType->setCurrentIndex( group.readEntry("ValuesType", (int) m_curve->valuesType()) ); ui.cbValuesPosition->setCurrentIndex( group.readEntry("ValuesPosition", (int) m_curve->valuesPosition()) ); - ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ValuesDistance", m_curve->valuesDistance()), Worksheet::Point) ); + ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ValuesDistance", m_curve->valuesDistance()), Worksheet::Unit::Point) ); ui.sbValuesRotation->setValue( group.readEntry("ValuesRotation", m_curve->valuesRotationAngle()) ); ui.sbValuesOpacity->setValue( round(group.readEntry("ValuesOpacity",m_curve->valuesOpacity())*100.0) ); this->updateValuesWidgets(); ui.leValuesPrefix->setText( group.readEntry("ValuesPrefix", m_curve->valuesPrefix()) ); ui.leValuesSuffix->setText( group.readEntry("ValuesSuffix", m_curve->valuesSuffix()) ); QFont valuesFont = m_curve->valuesFont(); - valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); + valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrValuesFont->setFont( group.readEntry("ValuesFont", valuesFont) ); ui.kcbValuesColor->setColor( group.readEntry("ValuesColor", m_curve->valuesColor()) ); //Filling ui.chkFillingEnabled->setChecked( group.readEntry("FillingEnabled", m_curve->fillingEnabled()) ); ui.cbFillingType->setCurrentIndex( group.readEntry("FillingType", (int) m_curve->fillingType()) ); ui.cbFillingColorStyle->setCurrentIndex( group.readEntry("FillingColorStyle", (int) m_curve->fillingColorStyle()) ); ui.cbFillingImageStyle->setCurrentIndex( group.readEntry("FillingImageStyle", (int) m_curve->fillingImageStyle()) ); ui.cbFillingBrushStyle->setCurrentIndex( group.readEntry("FillingBrushStyle", (int) m_curve->fillingBrushStyle()) ); ui.leFillingFileName->setText( group.readEntry("FillingFileName", m_curve->fillingFileName()) ); ui.kcbFillingFirstColor->setColor( group.readEntry("FillingFirstColor", m_curve->fillingFirstColor()) ); ui.kcbFillingSecondColor->setColor( group.readEntry("FillingSecondColor", m_curve->fillingSecondColor()) ); ui.sbFillingOpacity->setValue( round(group.readEntry("FillingOpacity", m_curve->fillingOpacity())*100.0) ); //Error bars ui.cbErrorType->setCurrentIndex( group.readEntry("ErrorType", (int) m_curve->errorType()) ); ui.cbErrorBarsType->setCurrentIndex( group.readEntry("ErrorBarsType", (int) m_curve->errorBarsType()) ); - ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsCapSize", m_curve->errorBarsCapSize()), Worksheet::Point) ); + ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsCapSize", m_curve->errorBarsCapSize()), Worksheet::Unit::Point) ); ui.cbErrorBarsStyle->setCurrentIndex( group.readEntry("ErrorBarsStyle", (int) m_curve->errorBarsPen().style()) ); ui.kcbErrorBarsColor->setColor( group.readEntry("ErrorBarsColor", m_curve->errorBarsPen().color()) ); - ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsWidth", m_curve->errorBarsPen().widthF()),Worksheet::Point) ); + ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsWidth", m_curve->errorBarsPen().widthF()),Worksheet::Unit::Point) ); ui.sbErrorBarsOpacity->setValue( round(group.readEntry("ErrorBarsOpacity", m_curve->errorBarsOpacity())*100.0) ); } void HistogramDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_curvesList.size(); if (size > 1) m_curve->beginMacro(i18n("%1 xy-curves: template \"%2\" loaded", size, name)); else m_curve->beginMacro(i18n("%1: template \"%2\" loaded", m_curve->name(), name)); this->loadConfig(config); m_curve->endMacro(); } void HistogramDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Histogram" ); //Line group.writeEntry("LineType", ui.cbLineType->currentIndex()); group.writeEntry("LineStyle", ui.cbLineStyle->currentIndex()); group.writeEntry("LineColor", ui.kcbLineColor->color()); - group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(),Worksheet::Point)); + group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(), Worksheet::Unit::Point)); group.writeEntry("LineOpacity", ui.sbLineOpacity->value()/100.0); //Values group.writeEntry("ValuesType", ui.cbValuesType->currentIndex()); group.writeEntry("ValuesPosition", ui.cbValuesPosition->currentIndex()); - group.writeEntry("ValuesDistance", Worksheet::convertToSceneUnits(ui.sbValuesDistance->value(),Worksheet::Point)); + group.writeEntry("ValuesDistance", Worksheet::convertToSceneUnits(ui.sbValuesDistance->value(), Worksheet::Unit::Point)); group.writeEntry("ValuesRotation", ui.sbValuesRotation->value()); group.writeEntry("ValuesOpacity", ui.sbValuesOpacity->value()/100.0); group.writeEntry("ValuesPrefix", ui.leValuesPrefix->text()); group.writeEntry("ValuesSuffix", ui.leValuesSuffix->text()); group.writeEntry("ValuesFont", ui.kfrValuesFont->font()); group.writeEntry("ValuesColor", ui.kcbValuesColor->color()); //Filling group.writeEntry("FillingEnabled", ui.chkFillingEnabled->isChecked()); group.writeEntry("FillingType", ui.cbFillingType->currentIndex()); group.writeEntry("FillingColorStyle", ui.cbFillingColorStyle->currentIndex()); group.writeEntry("FillingImageStyle", ui.cbFillingImageStyle->currentIndex()); group.writeEntry("FillingBrushStyle", ui.cbFillingBrushStyle->currentIndex()); group.writeEntry("FillingFileName", ui.leFillingFileName->text()); group.writeEntry("FillingFirstColor", ui.kcbFillingFirstColor->color()); group.writeEntry("FillingSecondColor", ui.kcbFillingSecondColor->color()); group.writeEntry("FillingOpacity", ui.sbFillingOpacity->value()/100.0); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/ImageDock.cpp b/src/kdefrontend/dockwidgets/ImageDock.cpp index 249a4659e..69fed585b 100644 --- a/src/kdefrontend/dockwidgets/ImageDock.cpp +++ b/src/kdefrontend/dockwidgets/ImageDock.cpp @@ -1,586 +1,586 @@ /*************************************************************************** File : ImageDock.cpp Project : LabPlot Description : widget for image properties -------------------------------------------------------------------- Copyright : (C) 2019-2020 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 "ImageDock.h" #include "backend/worksheet/Image.h" #include "backend/worksheet/Worksheet.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/TemplateHandler.h" #include #include #include #include #include #include #include #include #include /*! \class ImageDock \brief Provides a widget for editing the properties of the worksheets image element. \ingroup kdefrontend */ ImageDock::ImageDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leFileName->setCompleter(new QCompleter(new QDirModel, this)); // ui.cbSize->addItem(i18n("Original")); // ui.cbSize->addItem(i18n("Custom")); //Positioning and alignment ui.cbPositionX->addItem(i18n("Left")); ui.cbPositionX->addItem(i18n("Center")); ui.cbPositionX->addItem(i18n("Right")); ui.cbPositionX->addItem(i18n("Custom")); ui.cbPositionY->addItem(i18n("Top")); ui.cbPositionY->addItem(i18n("Center")); ui.cbPositionY->addItem(i18n("Bottom")); ui.cbPositionY->addItem(i18n("Custom")); ui.cbHorizontalAlignment->addItem(i18n("Left")); ui.cbHorizontalAlignment->addItem(i18n("Center")); ui.cbHorizontalAlignment->addItem(i18n("Right")); ui.cbVerticalAlignment->addItem(i18n("Top")); ui.cbVerticalAlignment->addItem(i18n("Center")); ui.cbVerticalAlignment->addItem(i18n("Bottom")); QString suffix; if (m_units == BaseDock::MetricUnits) suffix = QLatin1String("cm"); else suffix = QLatin1String("in"); ui.sbWidth->setSuffix(suffix); ui.sbHeight->setSuffix(suffix); ui.sbPositionX->setSuffix(suffix); ui.sbPositionY->setSuffix(suffix); //border ui.cbBorderStyle->addItem(i18n("No line")); ui.cbBorderStyle->addItem(i18n("Solid line")); ui.cbBorderStyle->addItem(i18n("Dash line")); ui.cbBorderStyle->addItem(i18n("Dot line")); ui.cbBorderStyle->addItem(i18n("Dash dot line")); ui.cbBorderStyle->addItem(i18n("Dash dot dot line")); //SLOTs //General connect(ui.leName, &QLineEdit::textChanged, this, &ImageDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &ImageDock::commentChanged); connect(ui.bOpen, &QPushButton::clicked, this, &ImageDock::selectFile); connect(ui.leFileName, &QLineEdit::returnPressed, this, &ImageDock::fileNameChanged); connect(ui.leFileName, &QLineEdit::textChanged, this, &ImageDock::fileNameChanged); connect(ui.sbOpacity, static_cast(&QSpinBox::valueChanged), this, &ImageDock::opacityChanged); //Size connect(ui.sbWidth, static_cast(&QDoubleSpinBox::valueChanged), this, &ImageDock::widthChanged); connect(ui.sbHeight, static_cast(&QDoubleSpinBox::valueChanged), this, &ImageDock::heightChanged); connect(ui.chbKeepRatio, &QCheckBox::clicked, this, &ImageDock::keepRatioChanged); //Position connect(ui.cbPositionX, static_cast(&QComboBox::currentIndexChanged), this, &ImageDock::positionXChanged); connect(ui.cbPositionY, static_cast(&QComboBox::currentIndexChanged), this, &ImageDock::positionYChanged); connect(ui.sbPositionX, static_cast(&QDoubleSpinBox::valueChanged), this, &ImageDock::customPositionXChanged); connect(ui.sbPositionY, static_cast(&QDoubleSpinBox::valueChanged), this, &ImageDock::customPositionYChanged); connect(ui.cbHorizontalAlignment, static_cast(&QComboBox::currentIndexChanged), this, &ImageDock::horizontalAlignmentChanged); connect(ui.cbVerticalAlignment, static_cast(&QComboBox::currentIndexChanged), this, &ImageDock::verticalAlignmentChanged); connect(ui.sbRotation, static_cast(&QSpinBox::valueChanged), this, &ImageDock::rotationChanged); connect(ui.chbVisible, &QCheckBox::clicked, this, &ImageDock::visibilityChanged); //Border connect(ui.cbBorderStyle, static_cast(&QComboBox::currentIndexChanged), this, &ImageDock::borderStyleChanged); connect(ui.kcbBorderColor, &KColorButton::changed, this, &ImageDock::borderColorChanged); connect(ui.sbBorderWidth, static_cast(&QDoubleSpinBox::valueChanged), this, &ImageDock::borderWidthChanged); connect(ui.sbBorderOpacity, static_cast(&QSpinBox::valueChanged), this, &ImageDock::borderOpacityChanged); } void ImageDock::setImages(QList list) { Lock lock(m_initializing); m_imageList = list; m_image = list.first(); m_aspect = list.first(); //if there are more then one image in the list, disable the name and comment field in the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_image->name()); ui.leComment->setText(m_image->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first image this->load(); //init connections //General connect(m_image, &Image::fileNameChanged, this, &ImageDock::imageFileNameChanged); connect(m_image, &Image::opacityChanged, this, &ImageDock::imageOpacityChanged); connect(m_image, &Image::visibleChanged, this, &ImageDock::imageVisibleChanged); //Size connect(m_image, &Image::widthChanged, this, &ImageDock::imageWidthChanged); connect(m_image, &Image::heightChanged, this, &ImageDock::imageHeightChanged); connect(m_image, &Image::keepRatioChanged, this, &ImageDock::imageKeepRatioChanged); //Position connect(m_image, &Image::positionChanged, this, &ImageDock::imagePositionChanged); connect(m_image, &Image::horizontalAlignmentChanged, this, &ImageDock::imageHorizontalAlignmentChanged); connect(m_image, &Image::verticalAlignmentChanged, this, &ImageDock::imageVerticalAlignmentChanged); connect(m_image, &Image::rotationAngleChanged, this, &ImageDock::imageRotationAngleChanged); //Border connect(m_image, &Image::borderPenChanged, this, &ImageDock::imageBorderPenChanged); connect(m_image, &Image::borderOpacityChanged, this, &ImageDock::imageBorderOpacityChanged); } void ImageDock::updateUnits() { const KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); BaseDock::Units units = (BaseDock::Units)group.readEntry("Units", (int)MetricUnits); if (units == m_units) return; m_units = units; Lock lock(m_initializing); QString suffix; if (m_units == BaseDock::MetricUnits) { //convert from imperial to metric - m_worksheetUnit = Worksheet::Centimeter; + m_worksheetUnit = Worksheet::Unit::Centimeter; suffix = QLatin1String("cm"); ui.sbWidth->setValue(ui.sbWidth->value()*2.54); ui.sbHeight->setValue(ui.sbHeight->value()*2.54); ui.sbPositionX->setValue(ui.sbPositionX->value()*2.54); ui.sbPositionY->setValue(ui.sbPositionY->value()*2.54); } else { //convert from metric to imperial - m_worksheetUnit = Worksheet::Inch; + m_worksheetUnit = Worksheet::Unit::Inch; suffix = QLatin1String("in"); ui.sbWidth->setValue(ui.sbWidth->value()/2.54); ui.sbHeight->setValue(ui.sbHeight->value()/2.54); ui.sbPositionX->setValue(ui.sbPositionX->value()/2.54); ui.sbPositionY->setValue(ui.sbPositionY->value()/2.54); } ui.sbWidth->setSuffix(suffix); ui.sbHeight->setSuffix(suffix); ui.sbPositionX->setSuffix(suffix); ui.sbPositionY->setSuffix(suffix); } //************************************************************* //******** SLOTs for changes triggered in ImageDock *********** //************************************************************* /*! opens a file dialog and lets the user select the image file. */ void ImageDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImageDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += ' ' + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leFileName->setText(path); } void ImageDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leFileName->setStyleSheet(QString()); for (auto* image : m_imageList) image->setFileName(fileName); } void ImageDock::opacityChanged(int value) { if (m_initializing) return; float opacity = (float)value/100; for (auto* image : m_imageList) image->setOpacity(opacity); } //Size void ImageDock::sizeChanged(int index) { Q_UNUSED(index); } void ImageDock::widthChanged(double value) { if (m_initializing) return; int width = Worksheet::convertToSceneUnits(value, m_worksheetUnit); for (auto* image : m_imageList) image->setWidth(width); } void ImageDock::heightChanged(double value) { if (m_initializing) return; int height = Worksheet::convertToSceneUnits(value, m_worksheetUnit); for (auto* image : m_imageList) image->setHeight(height); } void ImageDock::keepRatioChanged(int state) { if (m_initializing) return; for (auto* image : m_imageList) image->setKeepRatio(state); } //Position /*! called when label's current horizontal position relative to its parent (left, center, right, custom ) is changed. */ void ImageDock::positionXChanged(int index) { //Enable/disable the spinbox for the x- oordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionX->count()-1 ) ui.sbPositionX->setEnabled(true); else ui.sbPositionX->setEnabled(false); if (m_initializing) return; WorksheetElement::PositionWrapper position = m_image->position(); position.horizontalPosition = WorksheetElement::HorizontalPosition(index); for (auto* image : m_imageList) image->setPosition(position); } /*! called when label's current horizontal position relative to its parent (top, center, bottom, custom ) is changed. */ void ImageDock::positionYChanged(int index) { //Enable/disable the spinbox for the y-coordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionY->count()-1 ) ui.sbPositionY->setEnabled(true); else ui.sbPositionY->setEnabled(false); if (m_initializing) return; WorksheetElement::PositionWrapper position = m_image->position(); position.verticalPosition = WorksheetElement::VerticalPosition(index); for (auto* image : m_imageList) image->setPosition(position); } void ImageDock::customPositionXChanged(double value) { if (m_initializing) return; WorksheetElement::PositionWrapper position = m_image->position(); position.point.setX(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); for (auto* image : m_imageList) image->setPosition(position); } void ImageDock::customPositionYChanged(double value) { if (m_initializing) return; WorksheetElement::PositionWrapper position = m_image->position(); position.point.setY(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); for (auto* image : m_imageList) image->setPosition(position); } void ImageDock::horizontalAlignmentChanged(int index) { if (m_initializing) return; for (auto* image : m_imageList) image->setHorizontalAlignment(WorksheetElement::HorizontalAlignment(index)); } void ImageDock::verticalAlignmentChanged(int index) { if (m_initializing) return; for (auto* image : m_imageList) image->setVerticalAlignment(WorksheetElement::VerticalAlignment(index)); } void ImageDock::rotationChanged(int value) { if (m_initializing) return; for (auto* image : m_imageList) image->setRotationAngle(value); } void ImageDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* image : m_imageList) image->setVisible(state); } //border void ImageDock::borderStyleChanged(int index) { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* image : m_imageList) { pen = image->borderPen(); pen.setStyle(penStyle); image->setBorderPen(pen); } } void ImageDock::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* image : m_imageList) { pen = image->borderPen(); pen.setColor(color); image->setBorderPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void ImageDock::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* image : m_imageList) { pen = image->borderPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); image->setBorderPen(pen); } } void ImageDock::borderOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* image : m_imageList) image->setBorderOpacity(opacity); } //************************************************************* //********** SLOTs for changes triggered in Image ************* //************************************************************* void ImageDock::imageDescriptionChanged(const AbstractAspect* aspect) { if (m_image != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void ImageDock::imageFileNameChanged(const QString& name) { m_initializing = true; ui.leFileName->setText(name); m_initializing = false; } void ImageDock::imageOpacityChanged(float opacity) { m_initializing = true; ui.sbOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } //Size void ImageDock::imageWidthChanged(int width) { m_initializing = true; ui.sbWidth->setValue( Worksheet::convertFromSceneUnits(width, m_worksheetUnit) ); m_initializing = false; } void ImageDock::imageHeightChanged(int height) { m_initializing = true; ui.sbHeight->setValue( Worksheet::convertFromSceneUnits(height, m_worksheetUnit) ); m_initializing = false; } void ImageDock::imageKeepRatioChanged(bool keep) { m_initializing = true; ui.chbKeepRatio->setChecked(keep); m_initializing = false; } //Position void ImageDock::imagePositionChanged(const WorksheetElement::PositionWrapper& position) { m_initializing = true; ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(position.point.x(), m_worksheetUnit) ); ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(position.point.y(), m_worksheetUnit) ); - ui.cbPositionX->setCurrentIndex( position.horizontalPosition ); - ui.cbPositionY->setCurrentIndex( position.verticalPosition ); + ui.cbPositionX->setCurrentIndex( static_cast(position.horizontalPosition) ); + ui.cbPositionY->setCurrentIndex( static_cast(position.verticalPosition) ); m_initializing = false; } void ImageDock::imageHorizontalAlignmentChanged(WorksheetElement::HorizontalAlignment index) { m_initializing = true; - ui.cbHorizontalAlignment->setCurrentIndex(index); + ui.cbHorizontalAlignment->setCurrentIndex(static_cast(index)); m_initializing = false; } void ImageDock::imageVerticalAlignmentChanged(WorksheetElement::VerticalAlignment index) { m_initializing = true; - ui.cbVerticalAlignment->setCurrentIndex(index); + ui.cbVerticalAlignment->setCurrentIndex(static_cast(index)); m_initializing = false; } void ImageDock::imageRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbRotation->setValue(angle); m_initializing = false; } void ImageDock::imageVisibleChanged(bool on) { m_initializing = true; ui.chbVisible->setChecked(on); m_initializing = false; } //Border void ImageDock::imageBorderPenChanged(const QPen& pen) { m_initializing = true; if (ui.cbBorderStyle->currentIndex() != pen.style()) ui.cbBorderStyle->setCurrentIndex(pen.style()); if (ui.kcbBorderColor->color() != pen.color()) ui.kcbBorderColor->setColor(pen.color()); if (ui.sbBorderWidth->value() != pen.widthF()) - ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); + ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void ImageDock::imageBorderOpacityChanged(float value) { m_initializing = true; float v = (float)value*100.; ui.sbBorderOpacity->setValue(v); m_initializing = false; } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void ImageDock::load() { if (!m_image) return; m_initializing = true; ui.leFileName->setText(m_image->fileName()); ui.chbVisible->setChecked(m_image->isVisible()); //Size ui.sbWidth->setValue( Worksheet::convertFromSceneUnits(m_image->width(), m_worksheetUnit) ); ui.sbHeight->setValue( Worksheet::convertFromSceneUnits(m_image->height(), m_worksheetUnit) ); ui.chbKeepRatio->setChecked(m_image->keepRatio()); //Position ui.cbPositionX->setCurrentIndex( (int) m_image->position().horizontalPosition ); positionXChanged(ui.cbPositionX->currentIndex()); ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(m_image->position().point.x(), m_worksheetUnit) ); ui.cbPositionY->setCurrentIndex( (int) m_image->position().verticalPosition ); positionYChanged(ui.cbPositionY->currentIndex()); ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(m_image->position().point.y(), m_worksheetUnit) ); ui.cbHorizontalAlignment->setCurrentIndex( (int) m_image->horizontalAlignment() ); ui.cbVerticalAlignment->setCurrentIndex( (int) m_image->verticalAlignment() ); ui.sbRotation->setValue( m_image->rotationAngle() ); //Border ui.kcbBorderColor->setColor( m_image->borderPen().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_image->borderPen().style() ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_image->borderPen().widthF(), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_image->borderPen().widthF(), Worksheet::Unit::Point) ); ui.sbBorderOpacity->setValue( round(m_image->borderOpacity()*100) ); GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing = false; } diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp index d00e50dfe..aef7c446c 100644 --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -1,948 +1,949 @@ /*************************************************************************** File : LiveDataDock.cpp Project : LabPlot Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) Copyright : (C) 2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017-2019 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 "LiveDataDock.h" #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include "kdefrontend/datasources/MQTTSubscriptionWidget.h" #include #include #include #endif LiveDataDock::LiveDataDock(QWidget* parent) : BaseDock(parent) #ifdef HAVE_MQTT , m_subscriptionWidget(new MQTTSubscriptionWidget(this)) #endif { ui.setupUi(this); m_leName = ui.leName; //leComment = // not available ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); connect(ui.leName, &QLineEdit::textChanged, this, &LiveDataDock::nameChanged); connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading); connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow); connect(ui.sbUpdateInterval, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged); connect(ui.sbKeepNValues, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::keepNValuesChanged); connect(ui.sbSampleSize, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::sampleSizeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged); #ifdef HAVE_MQTT connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); connect(ui.bLWT, &QPushButton::clicked, this, &LiveDataDock::showWillSettings); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &LiveDataDock::enableWill); ui.swSubscriptions->addWidget(m_subscriptionWidget); ui.swSubscriptions->setCurrentWidget(m_subscriptionWidget); ui.bLWT->setToolTip(i18n("Manage MQTT connection's will settings")); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); QString info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif } #ifdef HAVE_MQTT LiveDataDock::~LiveDataDock() { for (auto & host : m_hosts) delete host.client; delete m_subscriptionWidget; } #else LiveDataDock::~LiveDataDock() = default; #endif #ifdef HAVE_MQTT /*! * \brief Sets the MQTTClient of this dock widget * \param clients */ void LiveDataDock::setMQTTClient(MQTTClient* const client) { m_liveDataSource = nullptr; // prevent updates due to changes to input widgets if (m_mqttClient == client) return; auto oldclient = m_mqttClient; m_mqttClient = nullptr; // prevent updates due to changes to input widgets ui.leName->setText(client->name()); const QPair id(client->clientHostName(), client->clientPort()); ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(id.first).arg(id.second)); ui.sbUpdateInterval->setValue(client->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(client->updateType())); ui.cbReadingType->setCurrentIndex(static_cast(client->readingType())); if (client->updateType() == MQTTClient::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (client->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(client->keepNValues()); ui.sbKeepNValues->setEnabled(true); if (client->readingType() == MQTTClient::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else ui.sbSampleSize->setValue(client->sampleSize()); // disable "whole file" option const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); m_mqttClient = client; // updates may be applied from now on //show MQTT connected options ui.lTopics->show(); ui.swSubscriptions->setVisible(true); m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); ui.lLWT->show(); ui.bLWT->show(); m_previousHost = m_currentHost; //if there isn't a client with this hostname we instantiate a new one auto it = m_hosts.find(id); if (it == m_hosts.end()) { m_currentHost = &m_hosts[id]; m_currentHost->count = 1; m_currentHost->client = new QMqttClient; connect(client, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); connect(m_currentHost->client, &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription); m_currentHost->client->setHostname(id.first); m_currentHost->client->setPort(id.second); if (client->MQTTUseAuthentication()) { m_currentHost->client->setUsername(client->clientUserName()); m_currentHost->client->setPassword(client->clientPassword()); } if (client->MQTTUseID()) m_currentHost->client->setClientId(client->clientID()); m_currentHost->client->connectToHost(); } else { m_currentHost = &it.value(); ++m_currentHost->count; } if (m_previousMQTTClient == nullptr) { m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() { emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); }); //Fill the subscription tree(useful if the MQTTClient was loaded) QVector topics = client->topicNames(); for (const auto& topic : topics) addTopicToTree(topic); emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); } //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots //and clear the tree widgets else if (m_previousMQTTClient->clientHostName() != client->clientHostName()) { disconnect(m_updateSubscriptionConn); disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, m_previousMQTTClient, &MQTTClient::reparentTopic); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, m_previousMQTTClient, &MQTTClient::addBeforeRemoveSubscription); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, m_previousMQTTClient, &MQTTClient::removeMQTTSubscription); disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, m_previousMQTTClient, &MQTTClient::addMQTTSubscription); m_previousHost->topicList = m_subscriptionWidget->getTopicList(); m_subscriptionWidget->setTopicList(m_currentHost->topicList); emit MQTTClearTopics(); //repopulating the tree widget with the already known topics of the client for (int i = 0; i < m_currentHost->addedTopics.size(); ++i) addTopicToTree(m_currentHost->addedTopics.at(i)); //fill subscriptions tree widget emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() { emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); }); connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription); } if (client->willUpdateType() == MQTTClient::OnClick && client->MQTTWillUse()) ui.bWillUpdateNow->show(); m_previousMQTTClient = oldclient; } #endif /*! * \brief Sets the live data source of this dock widget * \param source */ void LiveDataDock::setLiveDataSource(LiveDataSource* const source) { #ifdef HAVE_MQTT m_mqttClient = nullptr; #endif // if (m_liveDataSource == source) // return; m_liveDataSource = nullptr; // prevent updates due to changes to input widgets ui.leName->setText(source->name()); ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); const LiveDataSource::SourceType sourceType = source->sourceType(); const LiveDataSource::ReadingType readingType = source->readingType(); const LiveDataSource::UpdateType updateType = source->updateType(); const AbstractFileFilter::FileType fileType = source->fileType(); ui.sbUpdateInterval->setValue(source->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(updateType)); ui.cbReadingType->setCurrentIndex(static_cast(readingType)); switch (sourceType) { - case LiveDataSource::FileOrPipe: + case LiveDataSource::SourceType::FileOrPipe: ui.leSourceInfo->setText(source->fileName()); if (QFile::exists(source->fileName())) ui.leSourceInfo->setStyleSheet(QString()); else ui.leSourceInfo->setStyleSheet("QLineEdit{background:red;}"); break; - case LiveDataSource::NetworkTcpSocket: - case LiveDataSource::NetworkUdpSocket: + case LiveDataSource::SourceType::NetworkTcpSocket: + case LiveDataSource::SourceType::NetworkUdpSocket: ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(source->host()).arg(source->port())); break; - case LiveDataSource::LocalSocket: + case LiveDataSource::SourceType::LocalSocket: ui.leSourceInfo->setText(source->localSocketName()); break; - case LiveDataSource::SerialPort: + case LiveDataSource::SourceType::SerialPort: ui.leSourceInfo->setText(source->serialPortName()); break; - case LiveDataSource::MQTT: + case LiveDataSource::SourceType::MQTT: break; } if (updateType == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (source->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(source->keepNValues()); // disable "whole file" when having no file (i.e. socket or port) auto* model = qobject_cast(ui.cbReadingType->model()); - QStandardItem* item = model->item(LiveDataSource::WholeFile); + QStandardItem* item = model->item(static_cast(LiveDataSource::ReadingType::WholeFile)); if (sourceType == LiveDataSource::SourceType::FileOrPipe) { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //for file types other than ASCII and binary we support re-reading the whole file only //select "read whole file" and deactivate the combobox if (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary) { - ui.cbReadingType->setCurrentIndex(LiveDataSource::WholeFile); + ui.cbReadingType->setCurrentIndex(static_cast(LiveDataSource::ReadingType::WholeFile)); ui.cbReadingType->setEnabled(false); } else ui.cbReadingType->setEnabled(true); } else { - if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) - ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); + if (ui.cbReadingType->currentIndex() == static_cast(LiveDataSource::ReadingType::WholeFile)) + ui.cbReadingType->setCurrentIndex(static_cast(LiveDataSource::ReadingType::TillEnd)); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); } - if (((sourceType == LiveDataSource::FileOrPipe || sourceType == LiveDataSource::NetworkUdpSocket) && - (readingType == LiveDataSource::ContinuousFixed || readingType == LiveDataSource::FromEnd))) + if (((sourceType == LiveDataSource::SourceType::FileOrPipe || sourceType == LiveDataSource::SourceType::NetworkUdpSocket) && + (readingType == LiveDataSource::ReadingType::ContinuousFixed || readingType == LiveDataSource::ReadingType::FromEnd))) ui.sbSampleSize->setValue(source->sampleSize()); else { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } // disable "on new data"-option if not available model = qobject_cast(ui.cbUpdateType->model()); - item = model->item(LiveDataSource::NewData); - if (sourceType == LiveDataSource::NetworkTcpSocket || sourceType == LiveDataSource::NetworkUdpSocket || - sourceType == LiveDataSource::SerialPort) + item = model->item(static_cast(LiveDataSource::UpdateType::NewData)); + if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::NetworkUdpSocket || + sourceType == LiveDataSource::SourceType::SerialPort) item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); else item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui.lTopics->hide(); ui.bLWT->hide(); ui.lLWT->hide(); ui.bWillUpdateNow->hide(); ui.swSubscriptions->hide(); #ifdef HAVE_MQTT m_subscriptionWidget->hide(); m_subscriptionWidget->hide(); #endif m_liveDataSource = source; // updates may be applied from now on } /*! * \brief Modifies the sample size of the live data source or MQTTClient object * \param sampleSize */ void LiveDataDock::sampleSizeChanged(int sampleSize) { if (m_liveDataSource) m_liveDataSource->setSampleSize(sampleSize); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->setSampleSize(sampleSize); #endif } /*! * \brief Updates the live data source now */ void LiveDataDock::updateNow() { if (m_liveDataSource) m_liveDataSource->updateNow(); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->updateNow(); #endif } void LiveDataDock::nameChanged(const QString& name) { if (m_liveDataSource) { if (!m_liveDataSource->setName(name, false)) { ui.leName->setStyleSheet("background:red;"); ui.leName->setToolTip(i18n("Please choose another name, because this is already in use.")); return; } } #ifdef HAVE_MQTT else if (m_mqttClient) { if (!m_mqttClient->setName(name, false)) { ui.leName->setStyleSheet("background:red;"); ui.leName->setToolTip(i18n("Please choose another name, because this is already in use.")); return; } } #endif ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); } /*! * \brief LiveDataDock::updateTypeChanged * \param idx */ void LiveDataDock::updateTypeChanged(int idx) { if (m_liveDataSource) { DEBUG("LiveDataDock::updateTypeChanged()"); const auto updateType = static_cast(idx); switch (updateType) { - case LiveDataSource::TimeInterval: { + case LiveDataSource::UpdateType::TimeInterval: { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); - const LiveDataSource::SourceType s = m_liveDataSource->sourceType(); - const LiveDataSource::ReadingType r = m_liveDataSource->readingType(); - const bool showSampleSize = ((s == LiveDataSource::FileOrPipe || s == LiveDataSource::NetworkUdpSocket) && - (r == LiveDataSource::ContinuousFixed || r == LiveDataSource::FromEnd)); + const auto s = m_liveDataSource->sourceType(); + const auto r = m_liveDataSource->readingType(); + const bool showSampleSize = ((s == LiveDataSource::SourceType::FileOrPipe || s == LiveDataSource::SourceType::NetworkUdpSocket) && + (r == LiveDataSource::ReadingType::ContinuousFixed || r == LiveDataSource::ReadingType::FromEnd)); ui.lSampleSize->setVisible(showSampleSize); ui.sbSampleSize->setVisible(showSampleSize); m_liveDataSource->setUpdateType(updateType); m_liveDataSource->setUpdateInterval(ui.sbUpdateInterval->value()); break; } - case LiveDataSource::NewData: + case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); m_liveDataSource->setUpdateType(updateType); } } #ifdef HAVE_MQTT else if (m_mqttClient) { DEBUG("LiveDataDock::updateTypeChanged()"); - const MQTTClient::UpdateType type = static_cast(idx); + const auto type = static_cast(idx); if (type == MQTTClient::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); m_mqttClient->setUpdateType(type); m_mqttClient->setUpdateInterval(ui.sbUpdateInterval->value()); } else if (type == MQTTClient::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); m_mqttClient->setUpdateType(type); } } #endif } /*! * \brief Handles the change of the reading type in the dock widget * \param idx */ void LiveDataDock::readingTypeChanged(int idx) { if (m_liveDataSource) { const auto type = static_cast(idx); - const LiveDataSource::SourceType sourceType = m_liveDataSource->sourceType(); - const LiveDataSource::UpdateType updateType = m_liveDataSource->updateType(); + const auto sourceType = m_liveDataSource->sourceType(); + const auto updateType = m_liveDataSource->updateType(); - if (sourceType == LiveDataSource::NetworkTcpSocket || sourceType == LiveDataSource::LocalSocket || sourceType == LiveDataSource::SerialPort - || type == LiveDataSource::TillEnd || type == LiveDataSource::WholeFile - || updateType == LiveDataSource::NewData) { + if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket + || sourceType == LiveDataSource::SourceType::SerialPort + || type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile + || updateType == LiveDataSource::UpdateType::NewData) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } m_liveDataSource->setReadingType(type); } #ifdef HAVE_MQTT else if (m_mqttClient) { MQTTClient::ReadingType type = static_cast(idx); if (type == MQTTClient::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } m_mqttClient->setReadingType(type); } #endif } /*! * \brief Modifies the update interval of the live data source * \param updateInterval */ void LiveDataDock::updateIntervalChanged(int updateInterval) { if (m_liveDataSource) m_liveDataSource->setUpdateInterval(updateInterval); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->setUpdateInterval(updateInterval); #endif } /*! * \brief Modifies the number of samples to keep in each of the live data source * \param keepNValues */ void LiveDataDock::keepNValuesChanged(const int keepNValues) { if (m_liveDataSource) m_liveDataSource->setKeepNValues(keepNValues); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->setKeepNValues(keepNValues); #endif } /*! * \brief Pauses the reading of the live data source */ void LiveDataDock::pauseReading() { if (m_liveDataSource) m_liveDataSource->pauseReading(); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->pauseReading(); #endif } /*! * \brief Continues the reading of the live data source */ void LiveDataDock::continueReading() { if (m_liveDataSource) m_liveDataSource->continueReading(); #ifdef HAVE_MQTT else if (m_mqttClient) m_mqttClient->continueReading(); #endif } /*! * \brief Handles the pausing/continuing of reading of the live data source */ void LiveDataDock::pauseContinueReading() { m_paused = !m_paused; if (m_paused) { pauseReading(); ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { continueReading(); ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } } #ifdef HAVE_MQTT /*! *\brief called when use will message checkbox's state is changed in the will settings widget, * Sets the mqttUseWill according to state for the m_mqttClient * * \param state the state of the checbox */ void LiveDataDock::useWillMessage(bool use) { qDebug()<<"Use will message: " << use; if (use) { m_mqttClient->setMQTTWillUse(true); if (m_mqttClient->willUpdateType() == MQTTClient::OnClick) ui.bWillUpdateNow->show(); } else { m_mqttClient->setMQTTWillUse(false); ui.bWillUpdateNow->hide(); } } /*! *\brief called when will message's QoS is changed in the will settings widget * sets the will QoS level for the m_mqttClient * * \param QoS the QoS level of the will message */ void LiveDataDock::willQoSChanged(int QoS) { m_mqttClient->setWillQoS(QoS); } /*! *\brief called when will message's retain flag is changed in the will settings widget * sets the retain flag for the will message in in m_mqttClient * * \param state the state of the will retain chechbox */ void LiveDataDock::willRetainChanged(bool useWillRetainMessages) { if (useWillRetainMessages) m_mqttClient->setWillRetain(true); else m_mqttClient->setWillRetain(false); } /*! *\brief called when will topic combobox's current item is changed in the will settings widget * sets the will topic for the m_mqttClient * * \param topic the current text of cbWillTopic */ void LiveDataDock::willTopicChanged(const QString& topic) { if (m_mqttClient->willTopic() != topic) m_mqttClient->clearLastMessage(); m_mqttClient->setWillTopic(topic); } /*! *\brief called when the selected will message type is changed in the will settings widget * sets the will message type for the m_mqttClient * * \param type the selected will message type */ void LiveDataDock::willMessageTypeChanged(MQTTClient::WillMessageType willMessageType) { m_mqttClient->setWillMessageType(willMessageType); } /*! *\brief called when the will own message is changed in the will settings widget * sets the will own message for the m_mqttClient * * \param message the will message given by the user */ void LiveDataDock::willOwnMessageChanged(const QString& message) { m_mqttClient->setWillOwnMessage(message); } /*! *\brief called when the selected update type for the will message is changed in the will settings widget * sets the will update type for the m_mqttClient * * \param type the selected will update type */ void LiveDataDock::willUpdateTypeChanged(int updateType) { m_mqttClient->setWillUpdateType(static_cast(updateType)); if (static_cast(updateType) == MQTTClient::TimePeriod) { ui.bWillUpdateNow->hide(); m_mqttClient->startWillTimer(); } else if (static_cast(updateType) == MQTTClient::OnClick) { ui.bWillUpdateNow->show(); //if update type is on click we stop the will timer m_mqttClient->stopWillTimer(); } } /*! *\brief called when the will update now button is pressed * updates the will message of m_mqttClient */ void LiveDataDock::willUpdateNow() { m_mqttClient->updateWillMessage(); } /*! *\brief called when the update interval for will message is changed in the will settings widget * sets the will update interval for the m_mqttClient, then starts the will timer for each one * * \param interval the new will update interval */ void LiveDataDock::willUpdateIntervalChanged(int interval) { m_mqttClient->setWillTimeInterval(interval); m_mqttClient->startWillTimer(); } /*! *\brief called when the will statistics are changed in the will settings widget * adds or removes the statistic represented by the index from m_mqttClient */ void LiveDataDock::statisticsChanged(MQTTClient::WillStatisticsType willStatisticsType) { if (willStatisticsType >= 0) { //if it's not already added and it's checked we add it if (!m_mqttClient->willStatistics().at(static_cast(willStatisticsType))) m_mqttClient->addWillStatistics(willStatisticsType); else //otherwise remove it m_mqttClient->removeWillStatistics(willStatisticsType); } } /*! *\brief called when the client connects to the broker successfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void LiveDataDock::onMQTTConnect() { if (!m_currentHost || !m_currentHost->client || !m_currentHost->client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) if (!m_currentHost->addedTopics.contains(topic.name())) { m_currentHost->addedTopics.push_back(topic.name()); addTopicToTree(topic.name()); } } /*! *\brief Adds topicName to twTopics * * \param topicName the name of the topic, which will be added to the tree widget */ void LiveDataDock::addTopicToTree(const QString &topicName) { QStringList name; QChar sep = '/'; QString rootName; if (topicName.contains(sep)) { QStringList list = topicName.split(sep, QString::SkipEmptyParts); if (!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); QTreeWidgetItem* currentItem; //check whether the first level of the topic can be found in twTopics int topItemIdx = -1; for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if ( topItemIdx < 0) { currentItem = new QTreeWidgetItem(name); m_subscriptionWidget->addTopic(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for (int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if (childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if (!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for (; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topicName; name.append(topicName); m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); if (rootName == subscriptionName[0]) { emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions()); break; } } //signals that a newTopic was added, in order to fill the completer of leTopics //we have to pass the whole topic name, not just the root name, for testing purposes emit newTopic(topicName); } /*! *\brief called when a client receives a message, if the clients hostname isn't identic with the host name of MQTTClient * if the message arrived from a new topic, the topic is added to the host data */ void LiveDataDock::mqttMessageReceivedInBackground(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) if (!m_currentHost->addedTopics.contains(topic.name())) m_currentHost->addedTopics.push_back(topic.name()); } /*! *\brief called when an MQTTClient is about to be deleted * removes every data connected to the MQTTClient, and disconnects the corresponding client from the host * * \param hostname the host name of the MQTTClient that will be deleted * \param name the host name of the MQTTClient that will be deleted */ void LiveDataDock::removeClient(const QString& hostname, quint16 port) { auto it = m_hosts.find(qMakePair(hostname, port)); if (it == m_hosts.end()) return; MQTTHost & host = it.value(); if (host.count > 1) { --host.count; return; } host.client->disconnectFromHost(); if (m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == hostname) { disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); m_previousMQTTClient = nullptr; } if (m_mqttClient->clientHostName() == hostname) { emit MQTTClearTopics(); m_mqttClient = nullptr; } delete host.client; m_hosts.erase(it); } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testSubscribe(const QString& topic) { QStringList topicList = topic.split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == topicList[0]) { currentItem = m_subscriptionWidget->topLevelTopic(i); break; } } if (currentItem) { for (int i = 1 ; i < topicList.size(); ++i) { if (topicList[i] == '#') break; for (int j = 0; j < currentItem->childCount(); ++j) { if (currentItem->child(j)->text(0) == topicList[i]) { currentItem = currentItem->child(j); break; } else if (j == currentItem->childCount() - 1) return false; } } } else return false; m_subscriptionWidget->testSubscribe(currentItem); return true; } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testUnsubscribe(const QString& topic) { QTreeWidgetItem* currentItem = nullptr; for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { if (MQTTSubscriptionWidget::checkTopicContains(m_subscriptionWidget->topLevelSubscription(i)->text(0), topic)) { currentItem = m_subscriptionWidget->topLevelSubscription(i); break; } } if (currentItem) { do { if (topic == currentItem->text(0)) { m_subscriptionWidget->testUnsubscribe(currentItem); return true; } else { for (int i = 0; i < currentItem->childCount(); ++i) { qDebug()<child(i)->text(0)<<" "<child(i)->text(0), topic)) { currentItem = currentItem->child(i); break; } else if (i == currentItem->childCount() - 1) return false; } } } while (currentItem); } else return false; return false; } void LiveDataDock::showWillSettings() { QMenu menu; const QVector& topics = m_mqttClient->topicNames(); MQTTWillSettingsWidget willSettingsWidget(&menu, m_mqttClient->willSettings(), topics); connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() { this->useWillMessage(willSettingsWidget.will().enabled); this->willMessageTypeChanged(willSettingsWidget.will().willMessageType); this->updateTypeChanged(willSettingsWidget.will().willUpdateType); this->willRetainChanged(willSettingsWidget.will().willRetain); this->willUpdateIntervalChanged(willSettingsWidget.will().willTimeInterval); this->willOwnMessageChanged(willSettingsWidget.will().willOwnMessage); this->willTopicChanged(willSettingsWidget.will().willTopic); this->statisticsChanged(willSettingsWidget.statisticsType()); menu.close(); }); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettingsWidget); menu.addAction(widgetAction); QPoint pos(ui.bLWT->sizeHint().width(), ui.bLWT->sizeHint().height()); menu.exec(ui.bLWT->mapToGlobal(pos)); } void LiveDataDock::enableWill(bool enable) { if(enable) { if(!ui.bLWT->isEnabled()) ui.bLWT->setEnabled(enable); } else ui.bLWT->setEnabled(enable); } #endif diff --git a/src/kdefrontend/dockwidgets/ReferenceLineDock.cpp b/src/kdefrontend/dockwidgets/ReferenceLineDock.cpp index c539581e6..fba80f131 100644 --- a/src/kdefrontend/dockwidgets/ReferenceLineDock.cpp +++ b/src/kdefrontend/dockwidgets/ReferenceLineDock.cpp @@ -1,263 +1,263 @@ /*************************************************************************** File : ReferenceLineDock.cpp Project : LabPlot Description : Dock widget for the reference line on the plot -------------------------------------------------------------------- Copyright : (C) 2020 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 "ReferenceLineDock.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/ReferenceLine.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include ReferenceLineDock::ReferenceLineDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; ui.cbOrientation->addItem(i18n("Horizontal")); ui.cbOrientation->addItem(i18n("Vertical")); ui.lePosition->setValidator( new QDoubleValidator(ui.lePosition) ); //SLOTS //General connect(ui.leName, &QLineEdit::textChanged, this, &ReferenceLineDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &ReferenceLineDock::commentChanged); connect(ui.cbOrientation, SIGNAL(currentIndexChanged(int)), this, SLOT(orientationChanged(int)) ); connect(ui.lePosition, &QLineEdit::returnPressed, this, &ReferenceLineDock::positionChanged); connect(ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool))); connect(ui.cbLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(styleChanged(int)) ); connect(ui.kcbLineColor, SIGNAL(changed(QColor)), this, SLOT(colorChanged(QColor)) ); connect(ui.sbLineWidth, SIGNAL(valueChanged(double)), this, SLOT(widthChanged(double)) ); connect(ui.sbLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(opacityChanged(int)) ); m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, QColor(Qt::black)); m_initializing = false; } void ReferenceLineDock::setReferenceLines(QList list) { m_initializing = true; m_linesList = list; m_line = list.first(); m_aspect = list.first(); Q_ASSERT(m_line); //if there are more then one point in the list, disable the comment and name widgets in the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_line->name()); ui.leComment->setText(m_line->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first reference line this->load(); //SIGNALs/SLOTs connect(m_line, &AbstractAspect::aspectDescriptionChanged,this, &ReferenceLineDock::lineDescriptionChanged); connect(m_line, &ReferenceLine::visibleChanged, this, &ReferenceLineDock::lineVisibilityChanged); //position connect(m_line, &ReferenceLine::orientationChanged, this, &ReferenceLineDock::lineOrientationChanged); connect(m_line, &ReferenceLine::positionChanged, this, &ReferenceLineDock::linePositionChanged); //line connect(m_line, &ReferenceLine::penChanged, this, &ReferenceLineDock::linePenChanged); connect(m_line, &ReferenceLine::opacityChanged, this, &ReferenceLineDock::lineOpacityChanged); } //********************************************************** //*** SLOTs for changes triggered in ReferenceLineDock ***** //********************************************************** //Position void ReferenceLineDock::orientationChanged(int index) { ReferenceLine::Orientation orientation = (ReferenceLine::Orientation)index; if (orientation == ReferenceLine::Horizontal) ui.lPosition->setText(QLatin1String("y:")); else ui.lPosition->setText(QLatin1String("x:")); if (m_initializing) return; for (auto* line : m_linesList) line->setOrientation(orientation); } void ReferenceLineDock::positionChanged() { if (m_initializing) return; double pos = ui.lePosition->text().toDouble(); for (auto* line : m_linesList) line->setPosition(pos); } //Line void ReferenceLineDock::styleChanged(int index) { if (m_initializing) return; const auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* line : m_linesList) { pen = line->pen(); pen.setStyle(penStyle); line->setPen(pen); } } void ReferenceLineDock::colorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* line : m_linesList) { pen = line->pen(); pen.setColor(color); line->setPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing = false; } void ReferenceLineDock::widthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* line : m_linesList) { pen = line->pen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); line->setPen(pen); } } void ReferenceLineDock::opacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* line : m_linesList) line->setOpacity(opacity); } void ReferenceLineDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* line : m_linesList) line->setVisible(state); } //************************************************************* //******* SLOTs for changes triggered in ReferenceLine ******** //************************************************************* void ReferenceLineDock::lineDescriptionChanged(const AbstractAspect* aspect) { if (m_line != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void ReferenceLineDock::linePositionChanged(double position) { m_initializing = true; ui.lePosition->setText(QString::number(position)); m_initializing = false; } void ReferenceLineDock::lineOrientationChanged(ReferenceLine::Orientation orientation) { m_initializing = true; ui.cbOrientation->setCurrentIndex((int)orientation); m_initializing = false; } void ReferenceLineDock::linePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( (int)pen.style()); ui.kcbLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color()); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void ReferenceLineDock::lineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void ReferenceLineDock::lineVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //********************************************************** //******************** SETTINGS **************************** //********************************************************** void ReferenceLineDock::load() { if (!m_line) return; m_initializing = true; ui.cbOrientation->setCurrentIndex((int)m_line->orientation()); ui.lePosition->setText(QString::number(m_line->position())); ui.cbLineStyle->setCurrentIndex( (int) m_line->pen().style() ); ui.kcbLineColor->setColor( m_line->pen().color() ); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_line->pen().widthF(), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_line->pen().widthF(), Worksheet::Unit::Point) ); ui.sbLineOpacity->setValue( round(m_line->opacity()*100.0) ); ui.chkVisible->setChecked( m_line->isVisible() ); m_initializing = false; } diff --git a/src/kdefrontend/dockwidgets/WorksheetDock.cpp b/src/kdefrontend/dockwidgets/WorksheetDock.cpp index 8e4dabd8d..f7c2cc9bc 100644 --- a/src/kdefrontend/dockwidgets/WorksheetDock.cpp +++ b/src/kdefrontend/dockwidgets/WorksheetDock.cpp @@ -1,1030 +1,1030 @@ /*************************************************************************** File : WorksheetDock.cpp Project : LabPlot Description : widget for worksheet properties -------------------------------------------------------------------- Copyright : (C) 2010-2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2013 by Stefan Gerlach (stefan.gerlach@uni-konstanz.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 "WorksheetDock.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/TemplateHandler.h" #include #include #include #include #include #include #include #include #include /*! \class WorksheetDock \brief Provides a widget for editing the properties of the worksheets currently selected in the project explorer. \ingroup kdefrontend */ WorksheetDock::WorksheetDock(QWidget *parent): BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; //Background-tab ui.cbBackgroundColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leBackgroundFileName->setCompleter(new QCompleter(new QDirModel, this)); //Layout-tab ui.chScaleContent->setToolTip(i18n("If checked, rescale the content of the worksheet on size changes. Otherwise resize the canvas only.")); ui.cbLayout->addItem(QIcon::fromTheme("labplot-editbreaklayout"), i18n("No Layout")); ui.cbLayout->addItem(QIcon::fromTheme("labplot-editvlayout"), i18n("Vertical Layout")); ui.cbLayout->addItem(QIcon::fromTheme("labplot-edithlayout"), i18n("Horizontal Layout")); ui.cbLayout->addItem(QIcon::fromTheme("labplot-editgrid"), i18n("Grid Layout")); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { auto* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2, 2, 2, 2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //SLOTs //General connect(ui.leName, &QLineEdit::textChanged, this, &WorksheetDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &WorksheetDock::commentChanged); connect(ui.cbSize, static_cast(&QComboBox::currentIndexChanged), this, static_cast(&WorksheetDock::sizeChanged)); connect(ui.sbWidth, static_cast(&QDoubleSpinBox::valueChanged), this, static_cast(&WorksheetDock::sizeChanged)); connect(ui.sbHeight, static_cast(&QDoubleSpinBox::valueChanged), this, static_cast(&WorksheetDock::sizeChanged)); connect(ui.cbOrientation, static_cast(&QComboBox::currentIndexChanged), this, &WorksheetDock::orientationChanged); //Background connect(ui.cbBackgroundType, static_cast(&QComboBox::currentIndexChanged), this, &WorksheetDock::backgroundTypeChanged); connect(ui.cbBackgroundColorStyle, static_cast(&QComboBox::currentIndexChanged), this, &WorksheetDock::backgroundColorStyleChanged); connect(ui.cbBackgroundImageStyle, static_cast(&QComboBox::currentIndexChanged), this, &WorksheetDock::backgroundImageStyleChanged); connect(ui.cbBackgroundBrushStyle, static_cast(&QComboBox::currentIndexChanged), this, &WorksheetDock::backgroundBrushStyleChanged); connect(ui.bOpen, &QPushButton::clicked, this, &WorksheetDock::selectFile); connect(ui.leBackgroundFileName, &QLineEdit::returnPressed, this, &WorksheetDock::fileNameChanged); connect(ui.leBackgroundFileName, &QLineEdit::textChanged, this, &WorksheetDock::fileNameChanged); connect(ui.kcbBackgroundFirstColor, &KColorButton::changed, this, &WorksheetDock::backgroundFirstColorChanged); connect(ui.kcbBackgroundSecondColor, &KColorButton::changed, this, &WorksheetDock::backgroundSecondColorChanged); connect(ui.sbBackgroundOpacity, static_cast(&QSpinBox::valueChanged), this, &WorksheetDock::backgroundOpacityChanged); //Layout connect(ui.cbLayout, static_cast(&QComboBox::currentIndexChanged), this, &WorksheetDock::layoutChanged); connect( ui.chScaleContent, &QCheckBox::clicked, this, &WorksheetDock::scaleContentChanged); connect( ui.sbLayoutTopMargin, static_cast(&QDoubleSpinBox::valueChanged), this, &WorksheetDock::layoutTopMarginChanged); connect( ui.sbLayoutBottomMargin, static_cast(&QDoubleSpinBox::valueChanged), this, &WorksheetDock::layoutBottomMarginChanged); connect( ui.sbLayoutLeftMargin, static_cast(&QDoubleSpinBox::valueChanged), this, &WorksheetDock::layoutLeftMarginChanged); connect( ui.sbLayoutRightMargin, static_cast(&QDoubleSpinBox::valueChanged), this, &WorksheetDock::layoutRightMarginChanged); connect( ui.sbLayoutHorizontalSpacing, static_cast(&QDoubleSpinBox::valueChanged), this, &WorksheetDock::layoutHorizontalSpacingChanged); connect( ui.sbLayoutVerticalSpacing, static_cast(&QDoubleSpinBox::valueChanged), this, &WorksheetDock::layoutVerticalSpacingChanged); connect( ui.sbLayoutRowCount, static_cast(&QSpinBox::valueChanged), this, &WorksheetDock::layoutRowCountChanged); connect( ui.sbLayoutColumnCount, static_cast(&QSpinBox::valueChanged), this, &WorksheetDock::layoutColumnCountChanged); //theme and template handlers auto* frame = new QFrame(this); auto* layout = new QHBoxLayout(frame); layout->setContentsMargins(0, 11, 0, 11); m_themeHandler = new ThemeHandler(this); layout->addWidget(m_themeHandler); connect(m_themeHandler, &ThemeHandler::loadThemeRequested, this, &WorksheetDock::loadTheme); connect(m_themeHandler, &ThemeHandler::info, this, &WorksheetDock::info); auto* templateHandler = new TemplateHandler(this, TemplateHandler::Worksheet); layout->addWidget(templateHandler); connect(templateHandler, &TemplateHandler::loadConfigRequested, this, &WorksheetDock::loadConfigFromTemplate); connect(templateHandler, &TemplateHandler::saveConfigRequested, this, &WorksheetDock::saveConfigAsTemplate); connect(templateHandler, &TemplateHandler::info, this, &WorksheetDock::info); ui.verticalLayout->addWidget(frame); this->retranslateUi(); } void WorksheetDock::setWorksheets(QList list) { m_initializing = true; m_worksheetList = list; m_worksheet = list.first(); m_aspect = list.first(); //if there are more then one worksheet in the list, disable the name and comment field in the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_worksheet->name()); ui.leComment->setText(m_worksheet->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first worksheet this->load(); this->worksheetLayoutChanged(m_worksheet->layout()); m_themeHandler->setCurrentTheme(m_worksheet->theme()); connect(m_worksheet, &Worksheet::aspectDescriptionChanged, this, &WorksheetDock::worksheetDescriptionChanged); connect(m_worksheet, &Worksheet::pageRectChanged, this, &WorksheetDock::worksheetPageRectChanged); connect(m_worksheet, &Worksheet::scaleContentChanged, this, &WorksheetDock::worksheetScaleContentChanged); connect(m_worksheet, &Worksheet::backgroundTypeChanged, this, &WorksheetDock::worksheetBackgroundTypeChanged); connect(m_worksheet, &Worksheet::backgroundColorStyleChanged, this, &WorksheetDock::worksheetBackgroundColorStyleChanged); connect(m_worksheet, &Worksheet::backgroundImageStyleChanged, this, &WorksheetDock::worksheetBackgroundImageStyleChanged); connect(m_worksheet, &Worksheet::backgroundBrushStyleChanged, this, &WorksheetDock::worksheetBackgroundBrushStyleChanged); connect(m_worksheet, &Worksheet::backgroundFirstColorChanged, this, &WorksheetDock::worksheetBackgroundFirstColorChanged); connect(m_worksheet, &Worksheet::backgroundSecondColorChanged, this, &WorksheetDock::worksheetBackgroundSecondColorChanged); connect(m_worksheet, &Worksheet::backgroundFileNameChanged, this, &WorksheetDock::worksheetBackgroundFileNameChanged); connect(m_worksheet, &Worksheet::backgroundOpacityChanged, this, &WorksheetDock::worksheetBackgroundOpacityChanged); connect(m_worksheet, &Worksheet::layoutChanged, this, &WorksheetDock::worksheetLayoutChanged); connect(m_worksheet, &Worksheet::layoutTopMarginChanged, this, &WorksheetDock::worksheetLayoutTopMarginChanged); connect(m_worksheet, &Worksheet::layoutBottomMarginChanged, this, &WorksheetDock::worksheetLayoutBottomMarginChanged); connect(m_worksheet, &Worksheet::layoutLeftMarginChanged, this, &WorksheetDock::worksheetLayoutLeftMarginChanged); connect(m_worksheet, &Worksheet::layoutRightMarginChanged, this, &WorksheetDock::worksheetLayoutRightMarginChanged); connect(m_worksheet, &Worksheet::layoutVerticalSpacingChanged, this, &WorksheetDock::worksheetLayoutVerticalSpacingChanged); connect(m_worksheet, &Worksheet::layoutHorizontalSpacingChanged, this, &WorksheetDock::worksheetLayoutHorizontalSpacingChanged); connect(m_worksheet, &Worksheet::layoutRowCountChanged, this, &WorksheetDock::worksheetLayoutRowCountChanged); connect(m_worksheet, &Worksheet::layoutColumnCountChanged, this, &WorksheetDock::worksheetLayoutColumnCountChanged); connect(m_worksheet, &Worksheet::themeChanged, m_themeHandler, &ThemeHandler::setCurrentTheme); m_initializing = false; } void WorksheetDock::updateUnits() { const KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); BaseDock::Units units = (BaseDock::Units)group.readEntry("Units", (int)MetricUnits); if (units == m_units) return; m_units = units; Lock lock(m_initializing); QString suffix; if (m_units == BaseDock::MetricUnits) { //convert from imperial to metric - m_worksheetUnit = Worksheet::Centimeter; + m_worksheetUnit = Worksheet::Unit::Centimeter; suffix = QLatin1String("cm"); ui.sbWidth->setValue(ui.sbWidth->value()*2.54); ui.sbHeight->setValue(ui.sbHeight->value()*2.54); ui.sbLayoutTopMargin->setValue(ui.sbLayoutTopMargin->value()*2.54); ui.sbLayoutBottomMargin->setValue(ui.sbLayoutBottomMargin->value()*2.54); ui.sbLayoutLeftMargin->setValue(ui.sbLayoutLeftMargin->value()*2.54); ui.sbLayoutRightMargin->setValue(ui.sbLayoutRightMargin->value()*2.54); ui.sbLayoutHorizontalSpacing->setValue(ui.sbLayoutHorizontalSpacing->value()*2.54); ui.sbLayoutVerticalSpacing->setValue(ui.sbLayoutVerticalSpacing->value()*2.54); } else { //convert from metric to imperial - m_worksheetUnit = Worksheet::Inch; + m_worksheetUnit = Worksheet::Unit::Inch; suffix = QLatin1String("in"); ui.sbWidth->setValue(ui.sbWidth->value()/2.54); ui.sbHeight->setValue(ui.sbHeight->value()/2.54); ui.sbLayoutTopMargin->setValue(ui.sbLayoutTopMargin->value()/2.54); ui.sbLayoutBottomMargin->setValue(ui.sbLayoutBottomMargin->value()/2.54); ui.sbLayoutLeftMargin->setValue(ui.sbLayoutLeftMargin->value()/2.54); ui.sbLayoutRightMargin->setValue(ui.sbLayoutRightMargin->value()/2.54); ui.sbLayoutHorizontalSpacing->setValue(ui.sbLayoutHorizontalSpacing->value()/2.54); ui.sbLayoutVerticalSpacing->setValue(ui.sbLayoutVerticalSpacing->value()/2.54); } ui.sbWidth->setSuffix(suffix); ui.sbHeight->setSuffix(suffix); ui.sbLayoutTopMargin->setSuffix(suffix); ui.sbLayoutBottomMargin->setSuffix(suffix); ui.sbLayoutLeftMargin->setSuffix(suffix); ui.sbLayoutRightMargin->setSuffix(suffix); ui.sbLayoutHorizontalSpacing->setSuffix(suffix); ui.sbLayoutVerticalSpacing->setSuffix(suffix); } /*! Checks whether the size is one of the QPageSize::PageSizeId and updates Size and Orientation checkbox when width/height changes. */ void WorksheetDock::updatePaperSize() { if (m_worksheet->useViewSize()) { ui.cbSize->setCurrentIndex(0); return; } double w = ui.sbWidth->value(); double h = ui.sbHeight->value(); if (m_units == BaseDock::MetricUnits) { //In UI we use cm, so we need to convert to mm first before we check with QPageSize w *= 10; h *= 10; } const QSizeF s = QSizeF(w, h); const QSizeF st = s.transposed(); //determine the position of the QPageSize::PageSizeId in the combobox bool found = false; for (int i = 0; i < ui.cbSize->count(); ++i) { const QVariant v = ui.cbSize->itemData(i); if (!v.isValid()) continue; const auto id = v.value(); QPageSize::Unit pageUnit = (m_units == BaseDock::MetricUnits) ? QPageSize::Millimeter : QPageSize::Inch; const QSizeF ps = QPageSize::size(id, pageUnit); if (s == ps) { //check the portrait-orientation first ui.cbSize->setCurrentIndex(i); ui.cbOrientation->setCurrentIndex(0); //a QPageSize::PaperSize in portrait-orientation was found found = true; break; } else if (st == ps) { //check for the landscape-orientation ui.cbSize->setCurrentIndex(i); ui.cbOrientation->setCurrentIndex(1); //a QPageSize::PaperSize in landscape-orientation was found found = true; break; } } if (!found) ui.cbSize->setCurrentIndex(ui.cbSize->count() - 1); //select "Custom" size } //************************************************************* //****** SLOTs for changes triggered in WorksheetDock ********* //************************************************************* void WorksheetDock::retranslateUi() { Lock lock(m_initializing); //Geometry ui.cbOrientation->clear(); ui.cbOrientation->addItem(i18n("Portrait")); ui.cbOrientation->addItem(i18n("Landscape")); QString suffix; if (m_units == BaseDock::MetricUnits) suffix = QLatin1String("cm"); else suffix = QLatin1String("in"); ui.sbWidth->setSuffix(suffix); ui.sbHeight->setSuffix(suffix); ui.sbLayoutTopMargin->setSuffix(suffix); ui.sbLayoutBottomMargin->setSuffix(suffix); ui.sbLayoutLeftMargin->setSuffix(suffix); ui.sbLayoutRightMargin->setSuffix(suffix); ui.sbLayoutHorizontalSpacing->setSuffix(suffix); ui.sbLayoutVerticalSpacing->setSuffix(suffix); const QVector pageSizeIds = { QPageSize::A0, QPageSize::A1, QPageSize::A2, QPageSize::A3, QPageSize::A4, QPageSize::A5, QPageSize::A6, QPageSize::A7, QPageSize::A8, QPageSize::A9, QPageSize::B0, QPageSize::B1, QPageSize::B2, QPageSize::B3, QPageSize::B4, QPageSize::B5, QPageSize::B6, QPageSize::B7, QPageSize::B8, QPageSize::B9, QPageSize::B10, QPageSize::C5E, QPageSize::DLE, QPageSize::Executive, QPageSize::Folio, QPageSize::Ledger, QPageSize::Legal, QPageSize::Letter, QPageSize::Tabloid, QPageSize::Comm10E, QPageSize::Custom, }; ui.cbSize->clear(); ui.cbSize->addItem(i18n("View Size")); for (auto id : pageSizeIds) ui.cbSize->addItem(QPageSize::name(id), id); ui.cbSize->insertSeparator(1); //Background ui.cbBackgroundType->clear(); ui.cbBackgroundType->addItem(i18n("Color")); ui.cbBackgroundType->addItem(i18n("Image")); ui.cbBackgroundType->addItem(i18n("Pattern")); ui.cbBackgroundColorStyle->clear(); ui.cbBackgroundColorStyle->addItem(i18n("Single Color")); ui.cbBackgroundColorStyle->addItem(i18n("Horizontal Gradient")); ui.cbBackgroundColorStyle->addItem(i18n("Vertical Gradient")); ui.cbBackgroundColorStyle->addItem(i18n("Diag. Gradient (From Top Left)")); ui.cbBackgroundColorStyle->addItem(i18n("Diag. Gradient (From Bottom Left)")); ui.cbBackgroundColorStyle->addItem(i18n("Radial Gradient")); ui.cbBackgroundImageStyle->clear(); ui.cbBackgroundImageStyle->addItem(i18n("Scaled and Cropped")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled")); ui.cbBackgroundImageStyle->addItem(i18n("Scaled, Keep Proportions")); ui.cbBackgroundImageStyle->addItem(i18n("Centered")); ui.cbBackgroundImageStyle->addItem(i18n("Tiled")); ui.cbBackgroundImageStyle->addItem(i18n("Center Tiled")); GuiTools::updateBrushStyles(ui.cbBackgroundBrushStyle, Qt::SolidPattern); } // "General"-tab void WorksheetDock::scaleContentChanged(bool scaled) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setScaleContent(scaled); } void WorksheetDock::sizeChanged(int i) { const auto index = ui.cbSize->itemData(i).value(); if (index == QPageSize::Custom) { ui.sbWidth->setEnabled(true); ui.sbHeight->setEnabled(true); ui.lOrientation->hide(); ui.cbOrientation->hide(); } else { ui.sbWidth->setEnabled(false); ui.sbHeight->setEnabled(false); if (i == 0) { //no orientation available when using the complete view size (first item in the combox is selected) ui.lOrientation->hide(); ui.cbOrientation->hide(); } else { ui.lOrientation->show(); ui.cbOrientation->show(); } } if (m_initializing) return; Lock lock(m_initializing); if (i == 0) { //use the complete view size (first item in the combox is selected) for (auto* worksheet : m_worksheetList) worksheet->setUseViewSize(true); } else if (index == QPageSize::Custom) { if (m_worksheet->useViewSize()) { for (auto* worksheet : m_worksheetList) worksheet->setUseViewSize(false); } } else { //determine the width and the height of the to be used predefined layout QSizeF s = QPageSize::size(index, QPageSize::Millimeter); if (ui.cbOrientation->currentIndex() == 1) s.transpose(); //s is in mm, in UI we show everything in cm/in if (m_units == BaseDock::MetricUnits) { ui.sbWidth->setValue(s.width()/10); ui.sbHeight->setValue(s.height()/10); } else { ui.sbWidth->setValue(s.width()/25.4); ui.sbHeight->setValue(s.height()/25.4); } - float w = Worksheet::convertToSceneUnits(s.width(), Worksheet::Millimeter); - float h = Worksheet::convertToSceneUnits(s.height(), Worksheet::Millimeter); + float w = Worksheet::convertToSceneUnits(s.width(), Worksheet::Unit::Millimeter); + float h = Worksheet::convertToSceneUnits(s.height(), Worksheet::Unit::Millimeter); for (auto* worksheet : m_worksheetList) { worksheet->setUseViewSize(false); worksheet->setPageRect(QRect(0,0,w,h)); } } } void WorksheetDock::sizeChanged() { if (m_initializing) return; int w = Worksheet::convertToSceneUnits(ui.sbWidth->value(), m_worksheetUnit); int h = Worksheet::convertToSceneUnits(ui.sbHeight->value(), m_worksheetUnit); for (auto* worksheet : m_worksheetList) worksheet->setPageRect(QRect(0,0,w,h)); } void WorksheetDock::orientationChanged(int index) { Q_UNUSED(index); if (m_initializing) return; this->sizeChanged(ui.cbSize->currentIndex()); } // "Background"-tab void WorksheetDock::backgroundTypeChanged(int index) { auto type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lBackgroundColorStyle->show(); ui.cbBackgroundColorStyle->show(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); auto style = (PlotArea::BackgroundColorStyle)ui.cbBackgroundColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color:")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lBackgroundFirstColor->hide(); ui.kcbBackgroundFirstColor->hide(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->show(); ui.cbBackgroundImageStyle->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->show(); ui.leBackgroundFileName->show(); ui.bOpen->show(); } else if (type == PlotArea::Pattern) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->show(); ui.cbBackgroundBrushStyle->show(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); } if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setBackgroundType(type); } void WorksheetDock::backgroundColorStyleChanged(int index) { auto style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color:")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color:")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } if (m_initializing) return; int size = m_worksheetList.size(); if (size>1) { m_worksheet->beginMacro(i18n("%1 worksheets: background color style changed", size)); for (auto* w : m_worksheetList) w->setBackgroundColorStyle(style); m_worksheet->endMacro(); } else m_worksheet->setBackgroundColorStyle(style); } void WorksheetDock::backgroundImageStyleChanged(int index) { if (m_initializing) return; auto style = (PlotArea::BackgroundImageStyle)index; for (auto* worksheet : m_worksheetList) worksheet->setBackgroundImageStyle(style); } void WorksheetDock::backgroundBrushStyleChanged(int index) { if (m_initializing) return; auto style = (Qt::BrushStyle)index; for (auto* worksheet : m_worksheetList) worksheet->setBackgroundBrushStyle(style); } void WorksheetDock::backgroundFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setBackgroundFirstColor(c); } void WorksheetDock::backgroundSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setBackgroundSecondColor(c); } void WorksheetDock::backgroundOpacityChanged(int value) { if (m_initializing) return; float opacity = (float)value/100; for (auto* worksheet : m_worksheetList) worksheet->setBackgroundOpacity(opacity); } //"Layout"-tab void WorksheetDock::layoutChanged(int index) { auto layout = (Worksheet::Layout)index; - bool b = (layout != Worksheet::NoLayout); + bool b = (layout != Worksheet::Layout::NoLayout); ui.sbLayoutTopMargin->setEnabled(b); ui.sbLayoutBottomMargin->setEnabled(b); ui.sbLayoutLeftMargin->setEnabled(b); ui.sbLayoutRightMargin->setEnabled(b); ui.sbLayoutHorizontalSpacing->setEnabled(b); ui.sbLayoutVerticalSpacing->setEnabled(b); ui.sbLayoutRowCount->setEnabled(b); ui.sbLayoutColumnCount->setEnabled(b); //show the "scale content" option if no layout active ui.lScaleContent->setVisible(!b); ui.chScaleContent->setVisible(!b); if (b) { //show grid specific settings if grid layout selected - bool grid = (layout == Worksheet::GridLayout); + bool grid = (layout == Worksheet::Layout::GridLayout); ui.lGrid->setVisible(grid); ui.lRowCount->setVisible(grid); ui.sbLayoutRowCount->setVisible(grid); ui.lColumnCount->setVisible(grid); ui.sbLayoutColumnCount->setVisible(grid); } else { //no layout selected, hide grid specific settings that were potentially shown before ui.lGrid->setVisible(false); ui.lRowCount->setVisible(false); ui.sbLayoutRowCount->setVisible(false); ui.lColumnCount->setVisible(false); ui.sbLayoutColumnCount->setVisible(false); } if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayout(layout); } void WorksheetDock::layoutTopMarginChanged(double margin) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutTopMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void WorksheetDock::layoutBottomMarginChanged(double margin) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutBottomMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void WorksheetDock::layoutLeftMarginChanged(double margin) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutLeftMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void WorksheetDock::layoutRightMarginChanged(double margin) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutRightMargin(Worksheet::convertToSceneUnits(margin, m_worksheetUnit)); } void WorksheetDock::layoutHorizontalSpacingChanged(double spacing) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutHorizontalSpacing(Worksheet::convertToSceneUnits(spacing, m_worksheetUnit)); } void WorksheetDock::layoutVerticalSpacingChanged(double spacing) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutVerticalSpacing(Worksheet::convertToSceneUnits(spacing, m_worksheetUnit)); } void WorksheetDock::layoutRowCountChanged(int count) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutRowCount(count); } void WorksheetDock::layoutColumnCountChanged(int count) { if (m_initializing) return; for (auto* worksheet : m_worksheetList) worksheet->setLayoutColumnCount(count); } /*! opens a file dialog and lets the user select the image file. */ void WorksheetDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "WorksheetDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += ' ' + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leBackgroundFileName->setText( path ); for (auto* worksheet : m_worksheetList) worksheet->setBackgroundFileName(path); } void WorksheetDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leBackgroundFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(QString()); for (auto* worksheet : m_worksheetList) worksheet->setBackgroundFileName(fileName); } //************************************************************* //******** SLOTs for changes triggered in Worksheet *********** //************************************************************* void WorksheetDock::worksheetDescriptionChanged(const AbstractAspect* aspect) { if (m_worksheet != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void WorksheetDock::worksheetScaleContentChanged(bool scaled) { m_initializing = true; ui.chScaleContent->setChecked(scaled); m_initializing = false; } void WorksheetDock::worksheetPageRectChanged(const QRectF& rect) { m_initializing = true; ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(rect.width(), m_worksheetUnit)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(rect.height(), m_worksheetUnit)); updatePaperSize(); m_initializing = false; } void WorksheetDock::worksheetBackgroundTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbBackgroundType->setCurrentIndex(type); m_initializing = false; } void WorksheetDock::worksheetBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbBackgroundColorStyle->setCurrentIndex(style); m_initializing = false; } void WorksheetDock::worksheetBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbBackgroundImageStyle->setCurrentIndex(style); m_initializing = false; } void WorksheetDock::worksheetBackgroundBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbBackgroundBrushStyle->setCurrentIndex(style); m_initializing = false; } void WorksheetDock::worksheetBackgroundFirstColorChanged(const QColor& color) { m_initializing = true; ui.kcbBackgroundFirstColor->setColor(color); m_initializing = false; } void WorksheetDock::worksheetBackgroundSecondColorChanged(const QColor& color) { m_initializing = true; ui.kcbBackgroundSecondColor->setColor(color); m_initializing = false; } void WorksheetDock::worksheetBackgroundFileNameChanged(const QString& name) { m_initializing = true; ui.leBackgroundFileName->setText(name); m_initializing = false; } void WorksheetDock::worksheetBackgroundOpacityChanged(float opacity) { m_initializing = true; ui.sbBackgroundOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } void WorksheetDock::worksheetLayoutChanged(Worksheet::Layout layout) { m_initializing = true; - ui.cbLayout->setCurrentIndex(layout); + ui.cbLayout->setCurrentIndex(static_cast(layout)); m_initializing = false; } void WorksheetDock::worksheetLayoutTopMarginChanged(float value) { m_initializing = true; ui.sbLayoutTopMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void WorksheetDock::worksheetLayoutBottomMarginChanged(float value) { m_initializing = true; ui.sbLayoutBottomMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void WorksheetDock::worksheetLayoutLeftMarginChanged(float value) { m_initializing = true; ui.sbLayoutLeftMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void WorksheetDock::worksheetLayoutRightMarginChanged(float value) { m_initializing = true; ui.sbLayoutRightMargin->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void WorksheetDock::worksheetLayoutVerticalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutVerticalSpacing->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void WorksheetDock::worksheetLayoutHorizontalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutHorizontalSpacing->setValue(Worksheet::convertFromSceneUnits(value, m_worksheetUnit)); m_initializing = false; } void WorksheetDock::worksheetLayoutRowCountChanged(int value) { m_initializing = true; ui.sbLayoutRowCount->setValue(value); m_initializing = false; } void WorksheetDock::worksheetLayoutColumnCountChanged(int value) { m_initializing = true; ui.sbLayoutColumnCount->setValue(value); m_initializing = false; } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void WorksheetDock::load() { // Geometry ui.chScaleContent->setChecked(m_worksheet->scaleContent()); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits( m_worksheet->pageRect().width(), m_worksheetUnit) ); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits( m_worksheet->pageRect().height(), m_worksheetUnit) ); updatePaperSize(); // Background-tab ui.cbBackgroundType->setCurrentIndex( (int) m_worksheet->backgroundType() ); ui.cbBackgroundColorStyle->setCurrentIndex( (int) m_worksheet->backgroundColorStyle() ); ui.cbBackgroundImageStyle->setCurrentIndex( (int) m_worksheet->backgroundImageStyle() ); ui.cbBackgroundBrushStyle->setCurrentIndex( (int) m_worksheet->backgroundBrushStyle() ); ui.leBackgroundFileName->setText( m_worksheet->backgroundFileName() ); ui.kcbBackgroundFirstColor->setColor( m_worksheet->backgroundFirstColor() ); ui.kcbBackgroundSecondColor->setColor( m_worksheet->backgroundSecondColor() ); ui.sbBackgroundOpacity->setValue( qRound(m_worksheet->backgroundOpacity()*100) ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_worksheet->backgroundFileName().isEmpty() && !QFile::exists(m_worksheet->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(QString()); // Layout ui.cbLayout->setCurrentIndex( (int) m_worksheet->layout() ); ui.sbLayoutTopMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutTopMargin(), m_worksheetUnit) ); ui.sbLayoutBottomMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutBottomMargin(), m_worksheetUnit) ); ui.sbLayoutLeftMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutLeftMargin(), m_worksheetUnit) ); ui.sbLayoutRightMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutRightMargin(), m_worksheetUnit) ); ui.sbLayoutHorizontalSpacing->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutHorizontalSpacing(), m_worksheetUnit) ); ui.sbLayoutVerticalSpacing->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutVerticalSpacing(), m_worksheetUnit) ); ui.sbLayoutRowCount->setValue( m_worksheet->layoutRowCount() ); ui.sbLayoutColumnCount->setValue( m_worksheet->layoutColumnCount() ); } void WorksheetDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_worksheetList.size(); if (size > 1) m_worksheet->beginMacro(i18n("%1 worksheets: template \"%2\" loaded", size, name)); else m_worksheet->beginMacro(i18n("%1: template \"%2\" loaded", m_worksheet->name(), name)); this->loadConfig(config); m_worksheet->endMacro(); } void WorksheetDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "Worksheet" ); // Geometry ui.chScaleContent->setChecked(group.readEntry("ScaleContent", false)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(group.readEntry("Width", m_worksheet->pageRect().width()), m_worksheetUnit)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(group.readEntry("Height", m_worksheet->pageRect().height()), m_worksheetUnit)); if (group.readEntry("UseViewSize", false)) ui.cbSize->setCurrentIndex(0); else updatePaperSize(); // Background-tab ui.cbBackgroundType->setCurrentIndex( group.readEntry("BackgroundType", (int) m_worksheet->backgroundType()) ); ui.cbBackgroundColorStyle->setCurrentIndex( group.readEntry("BackgroundColorStyle", (int) m_worksheet->backgroundColorStyle()) ); ui.cbBackgroundImageStyle->setCurrentIndex( group.readEntry("BackgroundImageStyle", (int) m_worksheet->backgroundImageStyle()) ); ui.cbBackgroundBrushStyle->setCurrentIndex( group.readEntry("BackgroundBrushStyle", (int) m_worksheet->backgroundBrushStyle()) ); ui.leBackgroundFileName->setText( group.readEntry("BackgroundFileName", m_worksheet->backgroundFileName()) ); ui.kcbBackgroundFirstColor->setColor( group.readEntry("BackgroundFirstColor", m_worksheet->backgroundFirstColor()) ); ui.kcbBackgroundSecondColor->setColor( group.readEntry("BackgroundSecondColor", m_worksheet->backgroundSecondColor()) ); ui.sbBackgroundOpacity->setValue( qRound(group.readEntry("BackgroundOpacity", m_worksheet->backgroundOpacity())*100) ); // Layout ui.sbLayoutTopMargin->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LayoutTopMargin", m_worksheet->layoutTopMargin()), m_worksheetUnit) ); ui.sbLayoutBottomMargin->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LayoutBottomMargin", m_worksheet->layoutBottomMargin()), m_worksheetUnit) ); ui.sbLayoutLeftMargin->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LayoutLeftMargin", m_worksheet->layoutLeftMargin()), m_worksheetUnit) ); ui.sbLayoutRightMargin->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LayoutRightMargin", m_worksheet->layoutRightMargin()), m_worksheetUnit) ); ui.sbLayoutHorizontalSpacing->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LayoutHorizontalSpacing", m_worksheet->layoutHorizontalSpacing()), m_worksheetUnit) ); ui.sbLayoutVerticalSpacing->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LayoutVerticalSpacing", m_worksheet->layoutVerticalSpacing()), m_worksheetUnit) ); ui.sbLayoutRowCount->setValue(group.readEntry("LayoutRowCount", m_worksheet->layoutRowCount())); ui.sbLayoutColumnCount->setValue(group.readEntry("LayoutColumnCount", m_worksheet->layoutColumnCount())); } void WorksheetDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Worksheet" ); //General group.writeEntry("ScaleContent",ui.chScaleContent->isChecked()); group.writeEntry("UseViewSize",ui.cbSize->currentIndex() == 0); group.writeEntry("Width",Worksheet::convertToSceneUnits(ui.sbWidth->value(), m_worksheetUnit)); group.writeEntry("Height",Worksheet::convertToSceneUnits(ui.sbHeight->value(), m_worksheetUnit)); //Background group.writeEntry("BackgroundType",ui.cbBackgroundType->currentIndex()); group.writeEntry("BackgroundColorStyle", ui.cbBackgroundColorStyle->currentIndex()); group.writeEntry("BackgroundImageStyle", ui.cbBackgroundImageStyle->currentIndex()); group.writeEntry("BackgroundBrushStyle", ui.cbBackgroundBrushStyle->currentIndex()); group.writeEntry("BackgroundFileName", ui.leBackgroundFileName->text()); group.writeEntry("BackgroundFirstColor", ui.kcbBackgroundFirstColor->color()); group.writeEntry("BackgroundSecondColor", ui.kcbBackgroundSecondColor->color()); group.writeEntry("BackgroundOpacity", ui.sbBackgroundOpacity->value()/100.0); //Layout group.writeEntry("LayoutTopMargin",Worksheet::convertToSceneUnits(ui.sbLayoutTopMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutBottomMargin",Worksheet::convertToSceneUnits(ui.sbLayoutBottomMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutLeftMargin",Worksheet::convertToSceneUnits(ui.sbLayoutLeftMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutRightMargin",Worksheet::convertToSceneUnits(ui.sbLayoutRightMargin->value(), m_worksheetUnit)); group.writeEntry("LayoutVerticalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutVerticalSpacing->value(), m_worksheetUnit)); group.writeEntry("LayoutHorizontalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutHorizontalSpacing->value(), m_worksheetUnit)); group.writeEntry("LayoutRowCount", ui.sbLayoutRowCount->value()); group.writeEntry("LayoutColumnCount", ui.sbLayoutColumnCount->value()); config.sync(); } void WorksheetDock::loadTheme(const QString& theme) { for (auto* worksheet : m_worksheetList) worksheet->setTheme(theme); } diff --git a/src/kdefrontend/dockwidgets/XYCurveDock.cpp b/src/kdefrontend/dockwidgets/XYCurveDock.cpp index e0eebe577..89245f992 100644 --- a/src/kdefrontend/dockwidgets/XYCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYCurveDock.cpp @@ -1,2317 +1,2317 @@ /*************************************************************************** File : XYCurveDock.cpp Project : LabPlot Description : widget for XYCurve properties -------------------------------------------------------------------- Copyright : (C) 2010-2020 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2017 Stefan Gerlach (stefan.gerlach@uni-konstanz.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 "XYCurveDock.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/Worksheet.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include #include #include #include #include /*! \class XYCurveDock \brief Provides a widget for editing the properties of the XYCurves (2D-curves) currently selected in the project explorer. If more than one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYCurveDock::XYCurveDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); //Tab "Values" auto* gridLayout = qobject_cast(ui.tabValues->layout()); cbValuesColumn = new TreeViewComboBox(ui.tabValues); gridLayout->addWidget(cbValuesColumn, 2, 2, 1, 1); //add formats for numeric values ui.cbValuesNumericFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbValuesNumericFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbValuesNumericFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbValuesNumericFormat->addItem(i18n("Automatic (e)"), QVariant('g')); ui.cbValuesNumericFormat->addItem(i18n("Automatic (E)"), QVariant('G')); //add format for date, time and datetime values for (const auto& s : AbstractColumn::dateFormats()) ui.cbValuesDateTimeFormat->addItem(s, QVariant(s)); for (const auto& s : AbstractColumn::timeFormats()) ui.cbValuesDateTimeFormat->addItem(s, QVariant(s)); for (const auto& s1 : AbstractColumn::dateFormats()) { for (const auto& s2 : AbstractColumn::timeFormats()) ui.cbValuesDateTimeFormat->addItem(s1 + ' ' + s2, QVariant(s1 + ' ' + s2)); } ui.cbValuesDateTimeFormat->setEditable(true); //Tab "Filling" ui.cbFillingColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bFillingOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leFillingFileName->setCompleter(new QCompleter(new QDirModel, this)); //Tab "Error bars" gridLayout = qobject_cast(ui.tabErrorBars->layout()); cbXErrorPlusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbXErrorPlusColumn, 2, 2, 1, 1); cbXErrorMinusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbXErrorMinusColumn, 3, 2, 1, 1); cbYErrorPlusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbYErrorPlusColumn, 7, 2, 1, 1); cbYErrorMinusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbYErrorMinusColumn, 8, 2, 1, 1); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { auto* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //Slots //Lines connect( ui.cbLineType, SIGNAL(currentIndexChanged(int)), this, SLOT(lineTypeChanged(int)) ); connect( ui.sbLineInterpolationPointsCount, SIGNAL(valueChanged(int)), this, SLOT(lineInterpolationPointsCountChanged(int)) ); connect( ui.chkLineSkipGaps, SIGNAL(clicked(bool)), this, SLOT(lineSkipGapsChanged(bool)) ); connect( ui.chkLineIncreasingXOnly, &QCheckBox::clicked, this, &XYCurveDock::lineIncreasingXOnlyChanged ); connect( ui.cbLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(lineStyleChanged(int)) ); connect( ui.kcbLineColor, SIGNAL(changed(QColor)), this, SLOT(lineColorChanged(QColor)) ); connect( ui.sbLineWidth, SIGNAL(valueChanged(double)), this, SLOT(lineWidthChanged(double)) ); connect( ui.sbLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(lineOpacityChanged(int)) ); connect( ui.cbDropLineType, SIGNAL(currentIndexChanged(int)), this, SLOT(dropLineTypeChanged(int)) ); connect( ui.cbDropLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(dropLineStyleChanged(int)) ); connect( ui.kcbDropLineColor, SIGNAL(changed(QColor)), this, SLOT(dropLineColorChanged(QColor)) ); connect( ui.sbDropLineWidth, SIGNAL(valueChanged(double)), this, SLOT(dropLineWidthChanged(double)) ); connect( ui.sbDropLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(dropLineOpacityChanged(int)) ); //Symbol connect( ui.cbSymbolStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsStyleChanged(int)) ); connect( ui.sbSymbolSize, SIGNAL(valueChanged(double)), this, SLOT(symbolsSizeChanged(double)) ); connect( ui.sbSymbolRotation, SIGNAL(valueChanged(int)), this, SLOT(symbolsRotationChanged(int)) ); connect( ui.sbSymbolOpacity, SIGNAL(valueChanged(int)), this, SLOT(symbolsOpacityChanged(int)) ); connect( ui.cbSymbolFillingStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsFillingStyleChanged(int)) ); connect( ui.kcbSymbolFillingColor, SIGNAL(changed(QColor)), this, SLOT(symbolsFillingColorChanged(QColor)) ); connect( ui.cbSymbolBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsBorderStyleChanged(int)) ); connect( ui.kcbSymbolBorderColor, SIGNAL(changed(QColor)), this, SLOT(symbolsBorderColorChanged(QColor)) ); connect( ui.sbSymbolBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(symbolsBorderWidthChanged(double)) ); //Values connect( ui.cbValuesType, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesTypeChanged(int)) ); connect( cbValuesColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(valuesColumnChanged(QModelIndex)) ); connect( ui.cbValuesPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesPositionChanged(int)) ); connect( ui.sbValuesDistance, SIGNAL(valueChanged(double)), this, SLOT(valuesDistanceChanged(double)) ); connect( ui.sbValuesRotation, SIGNAL(valueChanged(int)), this, SLOT(valuesRotationChanged(int)) ); connect( ui.sbValuesOpacity, SIGNAL(valueChanged(int)), this, SLOT(valuesOpacityChanged(int)) ); connect(ui.cbValuesNumericFormat, QOverload::of(&QComboBox::currentIndexChanged), this, &XYCurveDock::valuesNumericFormatChanged); connect(ui.sbValuesPrecision, QOverload::of(&QSpinBox::valueChanged), this, &XYCurveDock::valuesPrecisionChanged); connect(ui.cbValuesDateTimeFormat, &QComboBox::currentTextChanged, this, &XYCurveDock::valuesDateTimeFormatChanged); connect( ui.leValuesPrefix, SIGNAL(returnPressed()), this, SLOT(valuesPrefixChanged()) ); connect( ui.leValuesSuffix, SIGNAL(returnPressed()), this, SLOT(valuesSuffixChanged()) ); connect( ui.kfrValuesFont, SIGNAL(fontSelected(QFont)), this, SLOT(valuesFontChanged(QFont)) ); connect( ui.kcbValuesColor, SIGNAL(changed(QColor)), this, SLOT(valuesColorChanged(QColor)) ); //Filling connect( ui.cbFillingPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingPositionChanged(int)) ); connect( ui.cbFillingType, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingTypeChanged(int)) ); connect( ui.cbFillingColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorStyleChanged(int)) ); connect( ui.cbFillingImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingImageStyleChanged(int)) ); connect( ui.cbFillingBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingBrushStyleChanged(int)) ); connect(ui.bFillingOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile())); connect( ui.leFillingFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leFillingFileName, SIGNAL(textChanged(QString)), this, SLOT(fileNameChanged()) ); connect( ui.kcbFillingFirstColor, SIGNAL(changed(QColor)), this, SLOT(fillingFirstColorChanged(QColor)) ); connect( ui.kcbFillingSecondColor, SIGNAL(changed(QColor)), this, SLOT(fillingSecondColorChanged(QColor)) ); connect( ui.sbFillingOpacity, SIGNAL(valueChanged(int)), this, SLOT(fillingOpacityChanged(int)) ); //Error bars connect( ui.cbXErrorType, SIGNAL(currentIndexChanged(int)), this, SLOT(xErrorTypeChanged(int)) ); connect( cbXErrorPlusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorPlusColumnChanged(QModelIndex)) ); connect( cbXErrorMinusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorMinusColumnChanged(QModelIndex)) ); connect( ui.cbYErrorType, SIGNAL(currentIndexChanged(int)), this, SLOT(yErrorTypeChanged(int)) ); connect( cbYErrorPlusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorPlusColumnChanged(QModelIndex)) ); connect( cbYErrorMinusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorMinusColumnChanged(QModelIndex)) ); connect( ui.cbErrorBarsType, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsTypeChanged(int)) ); connect( ui.sbErrorBarsCapSize, SIGNAL(valueChanged(double)), this, SLOT(errorBarsCapSizeChanged(double)) ); connect( ui.cbErrorBarsStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsStyleChanged(int)) ); connect( ui.kcbErrorBarsColor, SIGNAL(changed(QColor)), this, SLOT(errorBarsColorChanged(QColor)) ); connect( ui.sbErrorBarsWidth, SIGNAL(valueChanged(double)), this, SLOT(errorBarsWidthChanged(double)) ); connect( ui.sbErrorBarsOpacity, SIGNAL(valueChanged(int)), this, SLOT(errorBarsOpacityChanged(int)) ); //template handler auto* frame = new QFrame(this); auto* layout = new QHBoxLayout(frame); layout->setContentsMargins(0, 11, 0, 11); auto* templateHandler = new TemplateHandler(this, TemplateHandler::XYCurve); layout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); ui.verticalLayout->addWidget(frame); retranslateUi(); init(); } XYCurveDock::~XYCurveDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } void XYCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); m_leName = uiGeneralTab.leName; m_leComment = uiGeneralTab.leComment; auto* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); // Tab "General" auto* gridLayout = qobject_cast(generalTab->layout()); cbXColumn = new TreeViewComboBox(generalTab); cbXColumn->useCurrentIndexText(false); gridLayout->addWidget(cbXColumn, 2, 2, 1, 1); cbYColumn = new TreeViewComboBox(generalTab); cbYColumn->useCurrentIndexText(false); gridLayout->addWidget(cbYColumn, 3, 2, 1, 1); //General connect(uiGeneralTab.leName, &QLineEdit::textChanged, this, &XYCurveDock::nameChanged); connect(uiGeneralTab.leComment, &QLineEdit::textChanged, this, &XYCurveDock::commentChanged); connect(uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool))); connect(cbXColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xColumnChanged(QModelIndex))); connect(cbYColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yColumnChanged(QModelIndex))); } void XYCurveDock::init() { m_initializing = true; //Line ui.cbLineType->addItem(i18n("None")); ui.cbLineType->addItem(i18n("Line")); ui.cbLineType->addItem(i18n("Horiz. Start")); ui.cbLineType->addItem(i18n("Vert. Start")); ui.cbLineType->addItem(i18n("Horiz. Midpoint")); ui.cbLineType->addItem(i18n("Vert. Midpoint")); ui.cbLineType->addItem(i18n("2-segments")); ui.cbLineType->addItem(i18n("3-segments")); ui.cbLineType->addItem(i18n("Cubic Spline (Natural)")); ui.cbLineType->addItem(i18n("Cubic Spline (Periodic)")); ui.cbLineType->addItem(i18n("Akima-spline (Natural)")); ui.cbLineType->addItem(i18n("Akima-spline (Periodic)")); QPainter pa; //TODO size of the icon depending on the actual height of the combobox? int iconSize = 20; QPixmap pm(iconSize, iconSize); ui.cbLineType->setIconSize(QSize(iconSize, iconSize)); QPen pen(Qt::SolidPattern, 0); const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black; pen.setColor(color); pa.setPen( pen ); //no line pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.end(); ui.cbLineType->setItemIcon(0, pm); //line pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,17); pa.end(); ui.cbLineType->setItemIcon(1, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,3); pa.drawLine(17,3,17,17); pa.end(); ui.cbLineType->setItemIcon(2, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,3,17); pa.drawLine(3,17,17,17); pa.end(); ui.cbLineType->setItemIcon(3, pm); //horizontal midpoint pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,10,3); pa.drawLine(10,3,10,17); pa.drawLine(10,17,17,17); pa.end(); ui.cbLineType->setItemIcon(4, pm); //vertical midpoint pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,3,10); pa.drawLine(3,10,17,10); pa.drawLine(17,10,17,17); pa.end(); ui.cbLineType->setItemIcon(5, pm); //2-segments pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 8,8,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,10,10); pa.end(); ui.cbLineType->setItemIcon(6, pm); //3-segments pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 8,8,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,17); pa.end(); ui.cbLineType->setItemIcon(7, pm); //natural spline pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.rotate(45); pa.drawArc(2*sqrt(2),-4,17*sqrt(2),20,30*16,120*16); pa.end(); ui.cbLineType->setItemIcon(8, pm); ui.cbLineType->setItemIcon(9, pm); ui.cbLineType->setItemIcon(10, pm); ui.cbLineType->setItemIcon(11, pm); GuiTools::updatePenStyles(ui.cbLineStyle, Qt::black); //Drop lines ui.cbDropLineType->addItem(i18n("No Drop Lines")); ui.cbDropLineType->addItem(i18n("Drop Lines, X")); ui.cbDropLineType->addItem(i18n("Drop Lines, Y")); ui.cbDropLineType->addItem(i18n("Drop Lines, XY")); ui.cbDropLineType->addItem(i18n("Drop Lines, X, Zero Baseline")); ui.cbDropLineType->addItem(i18n("Drop Lines, X, Min Baseline")); ui.cbDropLineType->addItem(i18n("Drop Lines, X, Max Baseline")); GuiTools::updatePenStyles(ui.cbDropLineStyle, Qt::black); //Symbols GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); ui.cbSymbolStyle->addItem(i18n("None")); for (int i = 1; i < 19; ++i) { //TODO: use enum count const auto style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; //Values ui.cbValuesType->addItem(i18n("No Values")); ui.cbValuesType->addItem("x"); ui.cbValuesType->addItem("y"); ui.cbValuesType->addItem("x, y"); ui.cbValuesType->addItem("(x, y)"); ui.cbValuesType->addItem(i18n("Custom Column")); ui.cbValuesPosition->addItem(i18n("Above")); ui.cbValuesPosition->addItem(i18n("Below")); ui.cbValuesPosition->addItem(i18n("Left")); ui.cbValuesPosition->addItem(i18n("Right")); //Filling ui.cbFillingPosition->clear(); ui.cbFillingPosition->addItem(i18n("None")); ui.cbFillingPosition->addItem(i18n("Above")); ui.cbFillingPosition->addItem(i18n("Below")); ui.cbFillingPosition->addItem(i18n("Zero Baseline")); ui.cbFillingPosition->addItem(i18n("Left")); ui.cbFillingPosition->addItem(i18n("Right")); ui.cbFillingType->clear(); ui.cbFillingType->addItem(i18n("Color")); ui.cbFillingType->addItem(i18n("Image")); ui.cbFillingType->addItem(i18n("Pattern")); ui.cbFillingColorStyle->clear(); ui.cbFillingColorStyle->addItem(i18n("Single Color")); ui.cbFillingColorStyle->addItem(i18n("Horizontal Gradient")); ui.cbFillingColorStyle->addItem(i18n("Vertical Gradient")); ui.cbFillingColorStyle->addItem(i18n("Diag. Gradient (From Top Left)")); ui.cbFillingColorStyle->addItem(i18n("Diag. Gradient (From Bottom Left)")); ui.cbFillingColorStyle->addItem(i18n("Radial Gradient")); ui.cbFillingImageStyle->clear(); ui.cbFillingImageStyle->addItem(i18n("Scaled and Cropped")); ui.cbFillingImageStyle->addItem(i18n("Scaled")); ui.cbFillingImageStyle->addItem(i18n("Scaled, Keep Proportions")); ui.cbFillingImageStyle->addItem(i18n("Centered")); ui.cbFillingImageStyle->addItem(i18n("Tiled")); ui.cbFillingImageStyle->addItem(i18n("Center Tiled")); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, Qt::SolidPattern); //Error-bars pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10);//vert. line pa.drawLine(10,3,10,17);//hor. line pa.end(); ui.cbErrorBarsType->addItem(i18n("Bars")); ui.cbErrorBarsType->setItemIcon(0, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); //vert. line pa.drawLine(10,3,10,17); //hor. line pa.drawLine(7,3,13,3); //upper cap pa.drawLine(7,17,13,17); //bottom cap pa.drawLine(3,7,3,13); //left cap pa.drawLine(17,7,17,13); //right cap pa.end(); ui.cbErrorBarsType->addItem(i18n("Bars with Ends")); ui.cbErrorBarsType->setItemIcon(1, pm); ui.cbXErrorType->addItem(i18n("No")); ui.cbXErrorType->addItem(i18n("Symmetric")); ui.cbXErrorType->addItem(i18n("Asymmetric")); ui.cbYErrorType->addItem(i18n("No")); ui.cbYErrorType->addItem(i18n("Symmetric")); ui.cbYErrorType->addItem(i18n("Asymmetric")); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, Qt::black); } void XYCurveDock::setModel() { m_aspectTreeModel->enablePlottableColumnsOnly(true); m_aspectTreeModel->enableShowPlotDesignation(true); QList list{AspectType::Folder, AspectType::Workbook, AspectType::Datapicker, AspectType::DatapickerCurve, AspectType::Spreadsheet, AspectType::LiveDataSource, AspectType::Column, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::XYFitCurve, AspectType::XYSmoothCurve, AspectType::CantorWorksheet}; if (cbXColumn) { cbXColumn->setTopLevelClasses(list); cbYColumn->setTopLevelClasses(list); } cbValuesColumn->setTopLevelClasses(list); cbXErrorMinusColumn->setTopLevelClasses(list); cbXErrorPlusColumn->setTopLevelClasses(list); cbYErrorMinusColumn->setTopLevelClasses(list); cbYErrorPlusColumn->setTopLevelClasses(list); list = {AspectType::Column, AspectType::XYCurve}; m_aspectTreeModel->setSelectableAspects(list); if (cbXColumn) { cbXColumn->setModel(m_aspectTreeModel); cbYColumn->setModel(m_aspectTreeModel); } cbValuesColumn->setModel(m_aspectTreeModel); cbXErrorMinusColumn->setModel(m_aspectTreeModel); cbXErrorPlusColumn->setModel(m_aspectTreeModel); cbYErrorMinusColumn->setModel(m_aspectTreeModel); cbYErrorPlusColumn->setModel(m_aspectTreeModel); if (cbXColumn) { QString msg = i18n("The column \"%1\" is not available. If a new column at this path is created, it will automatically be used again by this curve."); QString path = m_curve->xColumnPath().split('/').last(); if (m_curve->xColumn()) { path += QString("\t ") + m_curve->xColumn()->plotDesignationString(); cbXColumn->setInvalid(false); } else cbXColumn->setInvalid(true, msg.arg(m_curve->xColumnPath())); cbXColumn->setText(path); path = m_curve->yColumnPath().split('/').last(); if (m_curve->yColumn()) { path += QString("\t ") + m_curve->yColumn()->plotDesignationString(); cbYColumn->setInvalid(false); } else cbYColumn->setInvalid(true, msg.arg(m_curve->yColumnPath())); cbYColumn->setText(path); } } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYCurveDock::setCurves(QList list) { m_initializing = true; m_curvesList = list; m_curve = list.first(); m_aspect = m_curve; Q_ASSERT(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); setModel(); initGeneralTab(); initTabs(); m_initializing = false; } void XYCurveDock::initGeneralTab() { DEBUG("XYCurveDock::initGeneralTab()"); //if there are more than one curve in the list, disable the content in the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.lXColumn->setEnabled(true); cbXColumn->setEnabled(true); uiGeneralTab.lYColumn->setEnabled(true); cbYColumn->setEnabled(true); DEBUG("setModelIndexFromAspect()"); this->setModelIndexFromAspect(cbXColumn, m_curve->xColumn()); this->setModelIndexFromAspect(cbYColumn, m_curve->yColumn()); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.lXColumn->setEnabled(false); cbXColumn->setEnabled(false); uiGeneralTab.lYColumn->setEnabled(false); cbYColumn->setEnabled(false); cbXColumn->setCurrentModelIndex(QModelIndex()); cbYColumn->setCurrentModelIndex(QModelIndex()); uiGeneralTab.leName->setText(QString()); uiGeneralTab.leComment->setText(QString()); } checkColumnAvailability(cbXColumn, m_curve->xColumn(), m_curve->xColumnPath()); checkColumnAvailability(cbYColumn, m_curve->yColumn(), m_curve->yColumnPath()); checkColumnAvailability(cbValuesColumn, m_curve->valuesColumn(), m_curve->valuesColumnPath()); checkColumnAvailability(cbXErrorPlusColumn, m_curve->xErrorPlusColumn(), m_curve->xErrorPlusColumnPath()); checkColumnAvailability(cbXErrorMinusColumn, m_curve->xErrorMinusColumn(), m_curve->xErrorMinusColumnPath()); checkColumnAvailability(cbYErrorPlusColumn, m_curve->yErrorPlusColumn(), m_curve->yErrorPlusColumnPath()); checkColumnAvailability(cbYErrorMinusColumn, m_curve->yErrorMinusColumn(), m_curve->yErrorMinusColumnPath()); //show the properties of the first curve uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_curve, &XYCurve::aspectDescriptionChanged, this, &XYCurveDock::curveDescriptionChanged); connect(m_curve, &XYCurve::xColumnChanged, this, &XYCurveDock::curveXColumnChanged); connect(m_curve, &XYCurve::yColumnChanged, this, &XYCurveDock::curveYColumnChanged); connect(m_curve, QOverload::of(&XYCurve::visibilityChanged), this, &XYCurveDock::curveVisibilityChanged); DEBUG("XYCurveDock::initGeneralTab() DONE"); } void XYCurveDock::initTabs() { //if there are more than one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { this->setModelIndexFromAspect(cbValuesColumn, m_curve->valuesColumn()); this->setModelIndexFromAspect(cbXErrorPlusColumn, m_curve->xErrorPlusColumn()); this->setModelIndexFromAspect(cbXErrorMinusColumn, m_curve->xErrorMinusColumn()); this->setModelIndexFromAspect(cbYErrorPlusColumn, m_curve->yErrorPlusColumn()); this->setModelIndexFromAspect(cbYErrorMinusColumn, m_curve->yErrorMinusColumn()); } else { cbValuesColumn->setCurrentModelIndex(QModelIndex()); cbXErrorPlusColumn->setCurrentModelIndex(QModelIndex()); cbXErrorMinusColumn->setCurrentModelIndex(QModelIndex()); cbYErrorPlusColumn->setCurrentModelIndex(QModelIndex()); cbYErrorMinusColumn->setCurrentModelIndex(QModelIndex()); } //show the properties of the first curve load(); //Slots //Line-Tab connect(m_curve, SIGNAL(lineTypeChanged(XYCurve::LineType)), this, SLOT(curveLineTypeChanged(XYCurve::LineType))); connect(m_curve, SIGNAL(lineSkipGapsChanged(bool)), this, SLOT(curveLineSkipGapsChanged(bool))); connect(m_curve, &XYCurve::lineIncreasingXOnlyChanged, this, &XYCurveDock::curveLineIncreasingXOnlyChanged); connect(m_curve, SIGNAL(lineInterpolationPointsCountChanged(int)), this, SLOT(curveLineInterpolationPointsCountChanged(int))); connect(m_curve, SIGNAL(linePenChanged(QPen)), this, SLOT(curveLinePenChanged(QPen))); connect(m_curve, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(curveLineOpacityChanged(qreal))); connect(m_curve, SIGNAL(dropLineTypeChanged(XYCurve::DropLineType)), this, SLOT(curveDropLineTypeChanged(XYCurve::DropLineType))); connect(m_curve, SIGNAL(dropLinePenChanged(QPen)), this, SLOT(curveDropLinePenChanged(QPen))); connect(m_curve, SIGNAL(dropLineOpacityChanged(qreal)), this, SLOT(curveDropLineOpacityChanged(qreal))); //Symbol-Tab connect(m_curve, SIGNAL(symbolsStyleChanged(Symbol::Style)), this, SLOT(curveSymbolsStyleChanged(Symbol::Style))); connect(m_curve, SIGNAL(symbolsSizeChanged(qreal)), this, SLOT(curveSymbolsSizeChanged(qreal))); connect(m_curve, SIGNAL(symbolsRotationAngleChanged(qreal)), this, SLOT(curveSymbolsRotationAngleChanged(qreal))); connect(m_curve, SIGNAL(symbolsOpacityChanged(qreal)), this, SLOT(curveSymbolsOpacityChanged(qreal))); connect(m_curve, SIGNAL(symbolsBrushChanged(QBrush)), this, SLOT(curveSymbolsBrushChanged(QBrush))); connect(m_curve, SIGNAL(symbolsPenChanged(QPen)), this, SLOT(curveSymbolsPenChanged(QPen))); //Values-Tab connect(m_curve, SIGNAL(valuesTypeChanged(XYCurve::ValuesType)), this, SLOT(curveValuesTypeChanged(XYCurve::ValuesType))); connect(m_curve, SIGNAL(valuesColumnChanged(const AbstractColumn*)), this, SLOT(curveValuesColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(valuesPositionChanged(XYCurve::ValuesPosition)), this, SLOT(curveValuesPositionChanged(XYCurve::ValuesPosition))); connect(m_curve, SIGNAL(valuesDistanceChanged(qreal)), this, SLOT(curveValuesDistanceChanged(qreal))); connect(m_curve, SIGNAL(valuesOpacityChanged(qreal)), this, SLOT(curveValuesOpacityChanged(qreal))); connect(m_curve, SIGNAL(valuesRotationAngleChanged(qreal)), this, SLOT(curveValuesRotationAngleChanged(qreal))); connect(m_curve, &XYCurve::valuesNumericFormatChanged, this, &XYCurveDock::curveValuesNumericFormatChanged); connect(m_curve, &XYCurve::valuesPrecisionChanged, this, &XYCurveDock::curveValuesPrecisionChanged); connect(m_curve, &XYCurve::valuesDateTimeFormatChanged, this, &XYCurveDock::curveValuesDateTimeFormatChanged); connect(m_curve, SIGNAL(valuesPrefixChanged(QString)), this, SLOT(curveValuesPrefixChanged(QString))); connect(m_curve, SIGNAL(valuesSuffixChanged(QString)), this, SLOT(curveValuesSuffixChanged(QString))); connect(m_curve, SIGNAL(valuesFontChanged(QFont)), this, SLOT(curveValuesFontChanged(QFont))); connect(m_curve, SIGNAL(valuesColorChanged(QColor)), this, SLOT(curveValuesColorChanged(QColor))); //Filling-Tab connect( m_curve, SIGNAL(fillingPositionChanged(XYCurve::FillingPosition)), this, SLOT(curveFillingPositionChanged(XYCurve::FillingPosition)) ); connect( m_curve, SIGNAL(fillingTypeChanged(PlotArea::BackgroundType)), this, SLOT(curveFillingTypeChanged(PlotArea::BackgroundType)) ); connect( m_curve, SIGNAL(fillingColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_curve, SIGNAL(fillingImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_curve, SIGNAL(fillingBrushStyleChanged(Qt::BrushStyle)), this, SLOT(curveFillingBrushStyleChanged(Qt::BrushStyle)) ); connect( m_curve, SIGNAL(fillingFirstColorChanged(QColor&)), this, SLOT(curveFillingFirstColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingSecondColorChanged(QColor&)), this, SLOT(curveFillingSecondColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingFileNameChanged(QString&)), this, SLOT(curveFillingFileNameChanged(QString&)) ); connect( m_curve, SIGNAL(fillingOpacityChanged(float)), this, SLOT(curveFillingOpacityChanged(float)) ); //"Error bars"-Tab connect(m_curve, SIGNAL(xErrorTypeChanged(XYCurve::ErrorType)), this, SLOT(curveXErrorTypeChanged(XYCurve::ErrorType))); connect(m_curve, SIGNAL(xErrorPlusColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorPlusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(xErrorMinusColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorMinusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yErrorTypeChanged(XYCurve::ErrorType)), this, SLOT(curveYErrorTypeChanged(XYCurve::ErrorType))); connect(m_curve, SIGNAL(yErrorPlusColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorPlusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yErrorMinusColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorMinusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(errorBarsCapSizeChanged(qreal)), this, SLOT(curveErrorBarsCapSizeChanged(qreal))); connect(m_curve, SIGNAL(errorBarsTypeChanged(XYCurve::ErrorBarsType)), this, SLOT(curveErrorBarsTypeChanged(XYCurve::ErrorBarsType))); connect(m_curve, SIGNAL(errorBarsPenChanged(QPen)), this, SLOT(curveErrorBarsPenChanged(QPen))); connect(m_curve, SIGNAL(errorBarsOpacityChanged(qreal)), this, SLOT(curveErrorBarsOpacityChanged(qreal))); } void XYCurveDock::checkColumnAvailability(TreeViewComboBox* cb, const AbstractColumn* column, const QString& columnPath) { if (!cb) return;// normally it shouldn't be called // don't make the comboboxes red for initially created curves if (!column && columnPath.isEmpty()) { cb->setText(""); cb->setInvalid(false); return; } if (column) { // current index text should be used cb->useCurrentIndexText(true); cb->setInvalid(false); } else { cb->useCurrentIndexText(false); cb->setInvalid(true, i18n("The column \"%1\"\nis not available anymore. It will be automatically used once it is created again.", columnPath)); } cb->setText(columnPath.split('/').last()); } void XYCurveDock::setModelIndexFromAspect(TreeViewComboBox* cb, const AbstractAspect* aspect) { if (aspect) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(aspect)); else cb->setCurrentModelIndex(QModelIndex()); } //************************************************************* //********** SLOTs for changes triggered in XYCurveDock ******** //************************************************************* void XYCurveDock::retranslateUi() { ui.lLineSkipGaps->setToolTip(i18n("If checked, connect neighbour points with lines even if there are gaps (invalid or masked values) between them")); ui.chkLineSkipGaps->setToolTip(i18n("If checked, connect neighbour points with lines even if there are gaps (invalid or masked values) between them")); ui.lLineIncreasingXOnly->setToolTip(i18n("If checked, connect data points only for strictly increasing values of X")); ui.chkLineIncreasingXOnly->setToolTip(i18n("If checked, connect data points only for strictly increasing values of X")); //TODO: // uiGeneralTab.lName->setText(i18n("Name")); // uiGeneralTab.lComment->setText(i18n("Comment")); // uiGeneralTab.chkVisible->setText(i18n("Visible")); // uiGeneralTab.lXColumn->setText(i18n("x-data")); // uiGeneralTab.lYColumn->setText(i18n("y-data")); //TODO updatePenStyles, updateBrushStyles for all comboboxes } void XYCurveDock::xColumnChanged(const QModelIndex& index) { updateValuesWidgets(); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); AbstractColumn* column = nullptr; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve : m_curvesList) curve->setXColumn(column); } void XYCurveDock::yColumnChanged(const QModelIndex& index) { updateValuesWidgets(); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); AbstractColumn* column = nullptr; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve : m_curvesList) curve->setYColumn(column); } void XYCurveDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setVisible(state); } // "Line"-tab void XYCurveDock::lineTypeChanged(int index) { const auto lineType = XYCurve::LineType(index); if ( lineType == XYCurve::NoLine) { ui.chkLineSkipGaps->setEnabled(false); ui.cbLineStyle->setEnabled(false); ui.kcbLineColor->setEnabled(false); ui.sbLineWidth->setEnabled(false); ui.sbLineOpacity->setEnabled(false); ui.lLineInterpolationPointsCount->hide(); ui.sbLineInterpolationPointsCount->hide(); } else { ui.chkLineSkipGaps->setEnabled(true); ui.cbLineStyle->setEnabled(true); ui.kcbLineColor->setEnabled(true); ui.sbLineWidth->setEnabled(true); ui.sbLineOpacity->setEnabled(true); if (lineType == XYCurve::SplineCubicNatural || lineType == XYCurve::SplineCubicPeriodic || lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) { ui.lLineInterpolationPointsCount->show(); ui.sbLineInterpolationPointsCount->show(); ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } else { ui.lLineInterpolationPointsCount->hide(); ui.sbLineInterpolationPointsCount->hide(); ui.lLineSkipGaps->show(); ui.chkLineSkipGaps->show(); } } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineType(lineType); } void XYCurveDock::lineSkipGapsChanged(bool skip) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineSkipGaps(skip); } void XYCurveDock::lineIncreasingXOnlyChanged(bool incr) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineIncreasingXOnly(incr); } void XYCurveDock::lineInterpolationPointsCountChanged(int count) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineInterpolationPointsCount(count); } void XYCurveDock::lineStyleChanged(int index) { if (m_initializing) return; const auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); pen.setStyle(penStyle); curve->setLinePen(pen); } } void XYCurveDock::lineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); pen.setColor(color); curve->setLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing = false; } void XYCurveDock::lineWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setLinePen(pen); } } void XYCurveDock::lineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setLineOpacity(opacity); } void XYCurveDock::dropLineTypeChanged(int index) { const auto dropLineType = XYCurve::DropLineType(index); if ( dropLineType == XYCurve::NoDropLine) { ui.cbDropLineStyle->setEnabled(false); ui.kcbDropLineColor->setEnabled(false); ui.sbDropLineWidth->setEnabled(false); ui.sbDropLineOpacity->setEnabled(false); } else { ui.cbDropLineStyle->setEnabled(true); ui.kcbDropLineColor->setEnabled(true); ui.sbDropLineWidth->setEnabled(true); ui.sbDropLineOpacity->setEnabled(true); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setDropLineType(dropLineType); } void XYCurveDock::dropLineStyleChanged(int index) { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->dropLinePen(); pen.setStyle(penStyle); curve->setDropLinePen(pen); } } void XYCurveDock::dropLineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->dropLinePen(); pen.setColor(color); curve->setDropLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbDropLineStyle, color); m_initializing = false; } void XYCurveDock::dropLineWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->dropLinePen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setDropLinePen(pen); } } void XYCurveDock::dropLineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setDropLineOpacity(opacity); } //"Symbol"-tab void XYCurveDock::symbolsStyleChanged(int index) { const auto style = Symbol::Style(index); if (style == Symbol::NoSymbols) { ui.sbSymbolSize->setEnabled(false); ui.sbSymbolRotation->setEnabled(false); ui.sbSymbolOpacity->setEnabled(false); ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); ui.cbSymbolBorderStyle->setEnabled(false); ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.sbSymbolSize->setEnabled(true); ui.sbSymbolRotation->setEnabled(true); ui.sbSymbolOpacity->setEnabled(true); //enable/disable the symbol filling options in the GUI depending on the currently selected symbol. if (style != Symbol::Line && style != Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } ui.cbSymbolBorderStyle->setEnabled(true); bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setSymbolsStyle(style); } void XYCurveDock::symbolsSizeChanged(double value) { if (m_initializing) return; for (auto* curve : m_curvesList) - curve->setSymbolsSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + curve->setSymbolsSize( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void XYCurveDock::symbolsRotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setSymbolsRotationAngle(value); } void XYCurveDock::symbolsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setSymbolsOpacity(opacity); } void XYCurveDock::symbolsFillingStyleChanged(int index) { const auto brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; for (auto* curve : m_curvesList) { brush = curve->symbolsBrush(); brush.setStyle(brushStyle); curve->setSymbolsBrush(brush); } } void XYCurveDock::symbolsFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; for (auto* curve : m_curvesList) { brush = curve->symbolsBrush(); brush.setColor(color); curve->setSymbolsBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void XYCurveDock::symbolsBorderStyleChanged(int index) { const auto penStyle = Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); pen.setStyle(penStyle); curve->setSymbolsPen(pen); } } void XYCurveDock::symbolsBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); pen.setColor(color); curve->setSymbolsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void XYCurveDock::symbolsBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setSymbolsPen(pen); } } //Values-tab /*! called when the type of the values (none, x, y, (x,y) etc.) was changed. */ void XYCurveDock::valuesTypeChanged(int index) { if (m_initializing) return; this->updateValuesWidgets(); const auto type = XYCurve::ValuesType(index); for (auto* curve : m_curvesList) curve->setValuesType(type); } /*! called when the custom column for the values was changed. */ void XYCurveDock::valuesColumnChanged(const QModelIndex& index) { if (m_initializing) return; this->updateValuesWidgets(); auto* column = static_cast(index.internalPointer()); for (auto* curve : m_curvesList) curve->setValuesColumn(column); } /*! shows the formatting properties of the column \c column. Called, when a new column for the values was selected - either by changing the type of the values (none, x, y, etc.) or by selecting a new custom column for the values. */ /*! depending on the currently selected values column type (column mode) updates the widgets for the values column format, shows/hides the allowed widgets, fills the corresponding combobox with the possible entries. Called when the values column was changed. */ void XYCurveDock::updateValuesWidgets() { const auto type = XYCurve::ValuesType(ui.cbValuesType->currentIndex()); bool showValues = (type != XYCurve::NoValues); ui.cbValuesPosition->setEnabled(showValues); ui.sbValuesDistance->setEnabled(showValues); ui.sbValuesRotation->setEnabled(showValues); ui.sbValuesOpacity->setEnabled(showValues); ui.kfrValuesFont->setEnabled(showValues); ui.kcbValuesColor->setEnabled(showValues); bool hasInteger = false; bool hasNumeric = false; bool hasDateTime = false; if (type == XYCurve::ValuesCustomColumn) { ui.lValuesColumn->show(); cbValuesColumn->show(); auto* column = static_cast(cbValuesColumn->currentModelIndex().internalPointer()); if (column) { if (column->columnMode() == AbstractColumn::ColumnMode::Numeric) hasNumeric = true; else if (column->columnMode() == AbstractColumn::ColumnMode::Integer || column->columnMode() == AbstractColumn::ColumnMode::BigInt) hasInteger = true; else if (column->columnMode() == AbstractColumn::ColumnMode::DateTime) hasDateTime = true; } } else { ui.lValuesColumn->hide(); cbValuesColumn->hide(); const AbstractColumn* xColumn = nullptr; const AbstractColumn* yColumn = nullptr; switch (type) { case XYCurve::NoValues: break; case XYCurve::ValuesX: xColumn = m_curve->xColumn(); break; case XYCurve::ValuesY: yColumn = m_curve->yColumn(); break; case XYCurve::ValuesXY: case XYCurve::ValuesXYBracketed: xColumn = m_curve->xColumn(); yColumn = m_curve->yColumn(); break; case XYCurve::ValuesCustomColumn: break; } hasInteger = (xColumn && (xColumn->columnMode() == AbstractColumn::ColumnMode::Integer || xColumn->columnMode() == AbstractColumn::ColumnMode::Integer)) || (yColumn && (yColumn->columnMode() == AbstractColumn::ColumnMode::Integer || yColumn->columnMode() == AbstractColumn::ColumnMode::Integer)); hasNumeric = (xColumn && xColumn->columnMode() == AbstractColumn::ColumnMode::Numeric) || (yColumn && yColumn->columnMode() == AbstractColumn::ColumnMode::Numeric); hasDateTime = (xColumn && xColumn->columnMode() == AbstractColumn::ColumnMode::DateTime) || (yColumn && yColumn->columnMode() == AbstractColumn::ColumnMode::DateTime); } //hide all the format related widgets first and //then show only what is required depending of the column mode(s) ui.lValuesFormat->hide(); ui.lValuesNumericFormat->hide(); ui.cbValuesNumericFormat->hide(); ui.lValuesPrecision->hide(); ui.sbValuesPrecision->hide(); ui.lValuesDateTimeFormat->hide(); ui.cbValuesDateTimeFormat->hide(); if (hasNumeric || hasInteger) { ui.lValuesFormat->show(); ui.lValuesNumericFormat->show(); ui.cbValuesNumericFormat->show(); } //precision is only available for Numeric if (hasNumeric) { ui.lValuesPrecision->show(); ui.sbValuesPrecision->show(); } if (hasDateTime) { ui.lValuesFormat->show(); ui.lValuesDateTimeFormat->show(); ui.cbValuesDateTimeFormat->show(); } } void XYCurveDock::valuesPositionChanged(int index) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesPosition(XYCurve::ValuesPosition(index)); } void XYCurveDock::valuesDistanceChanged(double value) { if (m_initializing) return; for (auto* curve : m_curvesList) - curve->setValuesDistance( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + curve->setValuesDistance( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void XYCurveDock::valuesRotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesRotationAngle(value); } void XYCurveDock::valuesOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setValuesOpacity(opacity); } void XYCurveDock::valuesNumericFormatChanged(int index) { if (m_initializing) return; char format = ui.cbValuesNumericFormat->itemData(index).toChar().toLatin1(); for (auto* curve : m_curvesList) curve->setValuesNumericFormat(format); } void XYCurveDock::valuesDateTimeFormatChanged(const QString& format) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesDateTimeFormat(format); } void XYCurveDock::valuesPrecisionChanged(int precision) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesPrecision(precision); } void XYCurveDock::valuesPrefixChanged() { if (m_initializing) return; QString prefix = ui.leValuesPrefix->text(); for (auto* curve : m_curvesList) curve->setValuesPrefix(prefix); } void XYCurveDock::valuesSuffixChanged() { if (m_initializing) return; QString suffix = ui.leValuesSuffix->text(); for (auto* curve : m_curvesList) curve->setValuesSuffix(suffix); } void XYCurveDock::valuesFontChanged(const QFont& font) { if (m_initializing) return; QFont valuesFont = font; - valuesFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); + valuesFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Unit::Point) ); for (auto* curve : m_curvesList) curve->setValuesFont(valuesFont); } void XYCurveDock::valuesColorChanged(const QColor& color) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesColor(color); } //Filling-tab void XYCurveDock::fillingPositionChanged(int index) { const auto fillingPosition = XYCurve::FillingPosition(index); bool b = (fillingPosition != XYCurve::NoFilling); ui.cbFillingType->setEnabled(b); ui.cbFillingColorStyle->setEnabled(b); ui.cbFillingBrushStyle->setEnabled(b); ui.cbFillingImageStyle->setEnabled(b); ui.kcbFillingFirstColor->setEnabled(b); ui.kcbFillingSecondColor->setEnabled(b); ui.leFillingFileName->setEnabled(b); ui.bFillingOpen->setEnabled(b); ui.sbFillingOpacity->setEnabled(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingPosition(fillingPosition); } void XYCurveDock::fillingTypeChanged(int index) { const auto type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lFillingColorStyle->show(); ui.cbFillingColorStyle->show(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); auto style = (PlotArea::BackgroundColorStyle) ui.cbFillingColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color:")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->show(); ui.cbFillingImageStyle->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->show(); ui.leFillingFileName->show(); ui.bFillingOpen->show(); ui.lFillingFirstColor->hide(); ui.kcbFillingFirstColor->hide(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->show(); ui.cbFillingBrushStyle->show(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingType(type); } void XYCurveDock::fillingColorStyleChanged(int index) { const auto style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color:")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingColorStyle(style); } void XYCurveDock::fillingImageStyleChanged(int index) { if (m_initializing) return; auto style = (PlotArea::BackgroundImageStyle)index; for (auto* curve : m_curvesList) curve->setFillingImageStyle(style); } void XYCurveDock::fillingBrushStyleChanged(int index) { if (m_initializing) return; auto style = (Qt::BrushStyle)index; for (auto* curve : m_curvesList) curve->setFillingBrushStyle(style); } void XYCurveDock::fillingFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingFirstColor(c); m_initializing = true; GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, c); m_initializing = false; } void XYCurveDock::fillingSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void XYCurveDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "XYCurveDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += ' ' + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leFillingFileName->setText( path ); for (auto* curve : m_curvesList) curve->setFillingFileName(path); } void XYCurveDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leFillingFileName->text(); for (auto* curve : m_curvesList) curve->setFillingFileName(fileName); } void XYCurveDock::fillingOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setFillingOpacity(opacity); } //"Error bars"-Tab void XYCurveDock::xErrorTypeChanged(int index) const { if (index == 0) { //no error ui.lXErrorDataPlus->setVisible(false); cbXErrorPlusColumn->setVisible(false); ui.lXErrorDataMinus->setVisible(false); cbXErrorMinusColumn->setVisible(false); } else if (index == 1) { //symmetric error ui.lXErrorDataPlus->setVisible(true); cbXErrorPlusColumn->setVisible(true); ui.lXErrorDataMinus->setVisible(false); cbXErrorMinusColumn->setVisible(false); ui.lXErrorDataPlus->setText(i18n("Data, +-")); } else if (index == 2) { //asymmetric error ui.lXErrorDataPlus->setVisible(true); cbXErrorPlusColumn->setVisible(true); ui.lXErrorDataMinus->setVisible(true); cbXErrorMinusColumn->setVisible(true); ui.lXErrorDataPlus->setText(i18n("Data, +")); } bool b = (index!=0 || ui.cbYErrorType->currentIndex()!=0); ui.lErrorFormat->setVisible(b); ui.lErrorBarsType->setVisible(b); ui.cbErrorBarsType->setVisible(b); ui.lErrorBarsStyle->setVisible(b); ui.cbErrorBarsStyle->setVisible(b); ui.lErrorBarsColor->setVisible(b); ui.kcbErrorBarsColor->setVisible(b); ui.lErrorBarsWidth->setVisible(b); ui.sbErrorBarsWidth->setVisible(b); ui.lErrorBarsOpacity->setVisible(b); ui.sbErrorBarsOpacity->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setXErrorType(XYCurve::ErrorType(index)); } void XYCurveDock::xErrorPlusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setXErrorPlusColumn(column); } void XYCurveDock::xErrorMinusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setXErrorMinusColumn(column); } void XYCurveDock::yErrorTypeChanged(int index) const { if (index == 0) { //no error ui.lYErrorDataPlus->setVisible(false); cbYErrorPlusColumn->setVisible(false); ui.lYErrorDataMinus->setVisible(false); cbYErrorMinusColumn->setVisible(false); } else if (index == 1) { //symmetric error ui.lYErrorDataPlus->setVisible(true); cbYErrorPlusColumn->setVisible(true); ui.lYErrorDataMinus->setVisible(false); cbYErrorMinusColumn->setVisible(false); ui.lYErrorDataPlus->setText(i18n("Data, +-")); } else if (index == 2) { //asymmetric error ui.lYErrorDataPlus->setVisible(true); cbYErrorPlusColumn->setVisible(true); ui.lYErrorDataMinus->setVisible(true); cbYErrorMinusColumn->setVisible(true); ui.lYErrorDataPlus->setText(i18n("Data, +")); } bool b = (index!=0 || ui.cbXErrorType->currentIndex()!=0); ui.lErrorFormat->setVisible(b); ui.lErrorBarsType->setVisible(b); ui.cbErrorBarsType->setVisible(b); ui.lErrorBarsStyle->setVisible(b); ui.cbErrorBarsStyle->setVisible(b); ui.lErrorBarsColor->setVisible(b); ui.kcbErrorBarsColor->setVisible(b); ui.lErrorBarsWidth->setVisible(b); ui.sbErrorBarsWidth->setVisible(b); ui.lErrorBarsOpacity->setVisible(b); ui.sbErrorBarsOpacity->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setYErrorType(XYCurve::ErrorType(index)); } void XYCurveDock::yErrorPlusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setYErrorPlusColumn(column); } void XYCurveDock::yErrorMinusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setYErrorMinusColumn(column); } void XYCurveDock::errorBarsTypeChanged(int index) const { auto type = XYCurve::ErrorBarsType(index); bool b = (type == XYCurve::ErrorBarsWithEnds); ui.lErrorBarsCapSize->setVisible(b); ui.sbErrorBarsCapSize->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setErrorBarsType(type); } void XYCurveDock::errorBarsCapSizeChanged(double value) const { if (m_initializing) return; - float size = Worksheet::convertToSceneUnits(value, Worksheet::Point); + float size = Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point); for (auto* curve : m_curvesList) curve->setErrorBarsCapSize(size); } void XYCurveDock::errorBarsStyleChanged(int index) const { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); pen.setStyle(penStyle); curve->setErrorBarsPen(pen); } } void XYCurveDock::errorBarsColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); pen.setColor(color); curve->setErrorBarsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbErrorBarsStyle, color); m_initializing = false; } void XYCurveDock::errorBarsWidthChanged(double value) const { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setErrorBarsPen(pen); } } void XYCurveDock::errorBarsOpacityChanged(int value) const { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setErrorBarsOpacity(opacity); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) uiGeneralTab.leName->setText(aspect->name()); else if (aspect->comment() != uiGeneralTab.leComment->text()) uiGeneralTab.leComment->setText(aspect->comment()); m_initializing = false; } void XYCurveDock::curveXColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXColumn, column); cbXColumn->useCurrentIndexText(true); cbXColumn->setInvalid(false); updateValuesWidgets(); m_initializing = false; } void XYCurveDock::curveYColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYColumn, column); cbYColumn->useCurrentIndexText(true); cbYColumn->setInvalid(false); updateValuesWidgets(); m_initializing = false; } void XYCurveDock::curveVisibilityChanged(bool on) { m_initializing = true; uiGeneralTab.chkVisible->setChecked(on); m_initializing = false; } //Line-Tab void XYCurveDock::curveLineTypeChanged(XYCurve::LineType type) { m_initializing = true; ui.cbLineType->setCurrentIndex( (int) type); m_initializing = false; } void XYCurveDock::curveLineSkipGapsChanged(bool skip) { m_initializing = true; ui.chkLineSkipGaps->setChecked(skip); m_initializing = false; } void XYCurveDock::curveLineIncreasingXOnlyChanged(bool incr) { m_initializing = true; ui.chkLineIncreasingXOnly->setChecked(incr); m_initializing = false; } void XYCurveDock::curveLineInterpolationPointsCountChanged(int count) { m_initializing = true; ui.sbLineInterpolationPointsCount->setValue(count); m_initializing = false; } void XYCurveDock::curveLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( (int)pen.style()); ui.kcbLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color()); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void XYCurveDock::curveLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveDropLineTypeChanged(XYCurve::DropLineType type) { m_initializing = true; ui.cbDropLineType->setCurrentIndex( (int)type ); m_initializing = false; } void XYCurveDock::curveDropLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbDropLineStyle->setCurrentIndex( (int) pen.style()); ui.kcbDropLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, pen.color()); - ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); + ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void XYCurveDock::curveDropLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbDropLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //Symbol-Tab void XYCurveDock::curveSymbolsStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style); m_initializing = false; } void XYCurveDock::curveSymbolsSizeChanged(qreal size) { m_initializing = true; - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void XYCurveDock::curveSymbolsRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbSymbolRotation->setValue(angle); m_initializing = false; } void XYCurveDock::curveSymbolsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbSymbolOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveSymbolsBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void XYCurveDock::curveSymbolsPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } //Values-Tab void XYCurveDock::curveValuesTypeChanged(XYCurve::ValuesType type) { m_initializing = true; ui.cbValuesType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveValuesColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbValuesColumn, column); m_initializing = false; } void XYCurveDock::curveValuesPositionChanged(XYCurve::ValuesPosition position) { m_initializing = true; ui.cbValuesPosition->setCurrentIndex((int) position); m_initializing = false; } void XYCurveDock::curveValuesDistanceChanged(qreal distance) { m_initializing = true; - ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::Point) ); + ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::Unit::Point) ); m_initializing = false; } void XYCurveDock::curveValuesRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbValuesRotation->setValue(angle); m_initializing = false; } void XYCurveDock::curveValuesNumericFormatChanged(char format) { m_initializing = true; ui.cbValuesNumericFormat->setCurrentIndex(ui.cbValuesNumericFormat->findData(format)); m_initializing = false; } void XYCurveDock::curveValuesPrecisionChanged(int precision) { m_initializing = true; ui.sbValuesPrecision->setValue(precision); m_initializing = false; } void XYCurveDock::curveValuesDateTimeFormatChanged(const QString& format) { m_initializing = true; ui.cbValuesDateTimeFormat->setCurrentText(format); m_initializing = false; } void XYCurveDock::curveValuesOpacityChanged(qreal opacity) { m_initializing = true; ui.sbValuesOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveValuesPrefixChanged(const QString& prefix) { m_initializing = true; ui.leValuesPrefix->setText(prefix); m_initializing = false; } void XYCurveDock::curveValuesSuffixChanged(const QString& suffix) { m_initializing = true; ui.leValuesSuffix->setText(suffix); m_initializing = false; } void XYCurveDock::curveValuesFontChanged(QFont font) { m_initializing = true; - font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrValuesFont->setFont(font); m_initializing = false; } void XYCurveDock::curveValuesColorChanged(QColor color) { m_initializing = true; ui.kcbValuesColor->setColor(color); m_initializing = false; } //Filling void XYCurveDock::curveFillingPositionChanged(XYCurve::FillingPosition position) { m_initializing = true; ui.cbFillingPosition->setCurrentIndex((int)position); m_initializing = false; } void XYCurveDock::curveFillingTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbFillingType->setCurrentIndex(type); m_initializing = false; } void XYCurveDock::curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbFillingColorStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbFillingImageStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbFillingBrushStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingFirstColor->setColor(color); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, color); m_initializing = false; } void XYCurveDock::curveFillingSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingSecondColor->setColor(color); m_initializing = false; } void XYCurveDock::curveFillingFileNameChanged(QString& filename) { m_initializing = true; ui.leFillingFileName->setText(filename); m_initializing = false; } void XYCurveDock::curveFillingOpacityChanged(float opacity) { m_initializing = true; ui.sbFillingOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //"Error bars"-Tab void XYCurveDock::curveXErrorTypeChanged(XYCurve::ErrorType type) { m_initializing = true; ui.cbXErrorType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveXErrorPlusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXErrorPlusColumn, column); m_initializing = false; } void XYCurveDock::curveXErrorMinusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXErrorMinusColumn, column); m_initializing = false; } void XYCurveDock::curveYErrorTypeChanged(XYCurve::ErrorType type) { m_initializing = true; ui.cbYErrorType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveYErrorPlusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYErrorPlusColumn, column); m_initializing = false; } void XYCurveDock::curveYErrorMinusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYErrorMinusColumn, column); m_initializing = false; } void XYCurveDock::curveErrorBarsCapSizeChanged(qreal size) { m_initializing = true; - ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void XYCurveDock::curveErrorBarsTypeChanged(XYCurve::ErrorBarsType type) { m_initializing = true; ui.cbErrorBarsType->setCurrentIndex( (int) type); m_initializing = false; } void XYCurveDock::curveErrorBarsPenChanged(const QPen& pen) { m_initializing = true; ui.cbErrorBarsStyle->setCurrentIndex( (int) pen.style()); ui.kcbErrorBarsColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, pen.color()); - ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); + ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point) ); m_initializing = false; } void XYCurveDock::curveErrorBarsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbErrorBarsOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //************************************************************* //************************* Settings ************************** //************************************************************* void XYCurveDock::load() { //General //This data is read in XYCurveDock::setCurves(). //Line ui.cbLineType->setCurrentIndex( (int) m_curve->lineType() ); ui.chkLineSkipGaps->setChecked( m_curve->lineSkipGaps() ); ui.sbLineInterpolationPointsCount->setValue( m_curve->lineInterpolationPointsCount() ); ui.cbLineStyle->setCurrentIndex( (int) m_curve->linePen().style() ); ui.kcbLineColor->setColor( m_curve->linePen().color() ); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->linePen().widthF(), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->linePen().widthF(), Worksheet::Unit::Point) ); ui.sbLineOpacity->setValue( round(m_curve->lineOpacity()*100.0) ); //Drop lines ui.cbDropLineType->setCurrentIndex( (int) m_curve->dropLineType() ); ui.cbDropLineStyle->setCurrentIndex( (int) m_curve->dropLinePen().style() ); ui.kcbDropLineColor->setColor( m_curve->dropLinePen().color() ); - ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->dropLinePen().widthF(),Worksheet::Point) ); + ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->dropLinePen().widthF(), Worksheet::Unit::Point) ); ui.sbDropLineOpacity->setValue( round(m_curve->dropLineOpacity()*100.0) ); //Symbols ui.cbSymbolStyle->setCurrentIndex( (int)m_curve->symbolsStyle() ); - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsSize(), Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsSize(), Worksheet::Unit::Point) ); ui.sbSymbolRotation->setValue( m_curve->symbolsRotationAngle() ); ui.sbSymbolOpacity->setValue( round(m_curve->symbolsOpacity()*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( (int) m_curve->symbolsBrush().style() ); ui.kcbSymbolFillingColor->setColor( m_curve->symbolsBrush().color() ); ui.cbSymbolBorderStyle->setCurrentIndex( (int) m_curve->symbolsPen().style() ); ui.kcbSymbolBorderColor->setColor( m_curve->symbolsPen().color() ); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsPen().widthF(), Worksheet::Point) ); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsPen().widthF(), Worksheet::Unit::Point) ); //Values ui.cbValuesType->setCurrentIndex( (int) m_curve->valuesType() ); ui.cbValuesPosition->setCurrentIndex( (int) m_curve->valuesPosition() ); - ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(m_curve->valuesDistance(), Worksheet::Point) ); + ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(m_curve->valuesDistance(), Worksheet::Unit::Point) ); ui.sbValuesRotation->setValue( m_curve->valuesRotationAngle() ); ui.sbValuesOpacity->setValue( round(m_curve->valuesOpacity()*100.0) ); ui.sbValuesPrecision->setValue(m_curve->valuesPrecision()); ui.cbValuesNumericFormat->setCurrentIndex(ui.cbValuesNumericFormat->findData(m_curve->valuesNumericFormat())); ui.cbValuesDateTimeFormat->setCurrentText(m_curve->valuesDateTimeFormat()); ui.leValuesPrefix->setText( m_curve->valuesPrefix() ); ui.leValuesSuffix->setText( m_curve->valuesSuffix() ); QFont valuesFont = m_curve->valuesFont(); - valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); + valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrValuesFont->setFont(valuesFont); ui.kcbValuesColor->setColor( m_curve->valuesColor() ); this->updateValuesWidgets(); //Filling ui.cbFillingPosition->setCurrentIndex( (int) m_curve->fillingPosition() ); ui.cbFillingType->setCurrentIndex( (int)m_curve->fillingType() ); ui.cbFillingColorStyle->setCurrentIndex( (int) m_curve->fillingColorStyle() ); ui.cbFillingImageStyle->setCurrentIndex( (int) m_curve->fillingImageStyle() ); ui.cbFillingBrushStyle->setCurrentIndex( (int) m_curve->fillingBrushStyle() ); ui.leFillingFileName->setText( m_curve->fillingFileName() ); ui.kcbFillingFirstColor->setColor( m_curve->fillingFirstColor() ); ui.kcbFillingSecondColor->setColor( m_curve->fillingSecondColor() ); ui.sbFillingOpacity->setValue( round(m_curve->fillingOpacity()*100.0) ); //Error bars ui.cbXErrorType->setCurrentIndex( (int) m_curve->xErrorType() ); ui.cbYErrorType->setCurrentIndex( (int) m_curve->yErrorType() ); ui.cbErrorBarsType->setCurrentIndex( (int) m_curve->errorBarsType() ); - ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsCapSize(), Worksheet::Point) ); + ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsCapSize(), Worksheet::Unit::Point) ); ui.cbErrorBarsStyle->setCurrentIndex( (int) m_curve->errorBarsPen().style() ); ui.kcbErrorBarsColor->setColor( m_curve->errorBarsPen().color() ); - ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsPen().widthF(),Worksheet::Point) ); + ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsPen().widthF(), Worksheet::Unit::Point) ); ui.sbErrorBarsOpacity->setValue( round(m_curve->errorBarsOpacity()*100.0) ); m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, ui.kcbDropLineColor->color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, ui.kcbErrorBarsColor->color()); m_initializing = false; } void XYCurveDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_curvesList.size(); if (size > 1) m_curve->beginMacro(i18n("%1 xy-curves: template \"%2\" loaded", size, name)); else m_curve->beginMacro(i18n("%1: template \"%2\" loaded", m_curve->name(), name)); this->loadConfig(config); m_curve->endMacro(); } void XYCurveDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "XYCurve" ); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in XYCurveDock::setCurves(). //Line ui.cbLineType->setCurrentIndex( group.readEntry("LineType", (int) m_curve->lineType()) ); ui.chkLineSkipGaps->setChecked( group.readEntry("LineSkipGaps", m_curve->lineSkipGaps()) ); ui.sbLineInterpolationPointsCount->setValue( group.readEntry("LineInterpolationPointsCount", m_curve->lineInterpolationPointsCount()) ); ui.cbLineStyle->setCurrentIndex( group.readEntry("LineStyle", (int) m_curve->linePen().style()) ); ui.kcbLineColor->setColor( group.readEntry("LineColor", m_curve->linePen().color()) ); - ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_curve->linePen().widthF()), Worksheet::Point) ); + ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_curve->linePen().widthF()), Worksheet::Unit::Point) ); ui.sbLineOpacity->setValue( round(group.readEntry("LineOpacity", m_curve->lineOpacity())*100.0) ); //Drop lines ui.cbDropLineType->setCurrentIndex( group.readEntry("DropLineType", (int) m_curve->dropLineType()) ); ui.cbDropLineStyle->setCurrentIndex( group.readEntry("DropLineStyle", (int) m_curve->dropLinePen().style()) ); ui.kcbDropLineColor->setColor( group.readEntry("DropLineColor", m_curve->dropLinePen().color()) ); - ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("DropLineWidth", m_curve->dropLinePen().widthF()),Worksheet::Point) ); + ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("DropLineWidth", m_curve->dropLinePen().widthF()), Worksheet::Unit::Point) ); ui.sbDropLineOpacity->setValue( round(group.readEntry("DropLineOpacity", m_curve->dropLineOpacity())*100.0) ); //Symbols ui.cbSymbolStyle->setCurrentIndex( group.readEntry("SymbolStyle", (int)m_curve->symbolsStyle()) ); - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_curve->symbolsSize()), Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_curve->symbolsSize()), Worksheet::Unit::Point) ); ui.sbSymbolRotation->setValue( group.readEntry("SymbolRotation", m_curve->symbolsRotationAngle()) ); ui.sbSymbolOpacity->setValue( round(group.readEntry("SymbolOpacity", m_curve->symbolsOpacity())*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( group.readEntry("SymbolFillingStyle", (int) m_curve->symbolsBrush().style()) ); ui.kcbSymbolFillingColor->setColor( group.readEntry("SymbolFillingColor", m_curve->symbolsBrush().color()) ); ui.cbSymbolBorderStyle->setCurrentIndex( group.readEntry("SymbolBorderStyle", (int) m_curve->symbolsPen().style()) ); ui.kcbSymbolBorderColor->setColor( group.readEntry("SymbolBorderColor", m_curve->symbolsPen().color()) ); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_curve->symbolsPen().widthF()), Worksheet::Point) ); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_curve->symbolsPen().widthF()), Worksheet::Unit::Point) ); //Values ui.cbValuesType->setCurrentIndex( group.readEntry("ValuesType", (int) m_curve->valuesType()) ); ui.cbValuesPosition->setCurrentIndex( group.readEntry("ValuesPosition", (int) m_curve->valuesPosition()) ); - ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ValuesDistance", m_curve->valuesDistance()), Worksheet::Point) ); + ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ValuesDistance", m_curve->valuesDistance()), Worksheet::Unit::Point) ); ui.sbValuesRotation->setValue( group.readEntry("ValuesRotation", m_curve->valuesRotationAngle()) ); ui.sbValuesOpacity->setValue( round(group.readEntry("ValuesOpacity",m_curve->valuesOpacity())*100.0) ); ui.leValuesPrefix->setText( group.readEntry("ValuesPrefix", m_curve->valuesPrefix()) ); ui.leValuesSuffix->setText( group.readEntry("ValuesSuffix", m_curve->valuesSuffix()) ); QFont valuesFont = m_curve->valuesFont(); - valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); + valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Unit::Point)) ); ui.kfrValuesFont->setFont( group.readEntry("ValuesFont", valuesFont) ); ui.kcbValuesColor->setColor( group.readEntry("ValuesColor", m_curve->valuesColor()) ); //Filling ui.cbFillingPosition->setCurrentIndex( group.readEntry("FillingPosition", (int) m_curve->fillingPosition()) ); ui.cbFillingType->setCurrentIndex( group.readEntry("FillingType", (int) m_curve->fillingType()) ); ui.cbFillingColorStyle->setCurrentIndex( group.readEntry("FillingColorStyle", (int) m_curve->fillingColorStyle()) ); ui.cbFillingImageStyle->setCurrentIndex( group.readEntry("FillingImageStyle", (int) m_curve->fillingImageStyle()) ); ui.cbFillingBrushStyle->setCurrentIndex( group.readEntry("FillingBrushStyle", (int) m_curve->fillingBrushStyle()) ); ui.leFillingFileName->setText( group.readEntry("FillingFileName", m_curve->fillingFileName()) ); ui.kcbFillingFirstColor->setColor( group.readEntry("FillingFirstColor", m_curve->fillingFirstColor()) ); ui.kcbFillingSecondColor->setColor( group.readEntry("FillingSecondColor", m_curve->fillingSecondColor()) ); ui.sbFillingOpacity->setValue( round(group.readEntry("FillingOpacity", m_curve->fillingOpacity())*100.0) ); //Error bars ui.cbXErrorType->setCurrentIndex( group.readEntry("XErrorType", (int) m_curve->xErrorType()) ); ui.cbYErrorType->setCurrentIndex( group.readEntry("YErrorType", (int) m_curve->yErrorType()) ); ui.cbErrorBarsType->setCurrentIndex( group.readEntry("ErrorBarsType", (int) m_curve->errorBarsType()) ); - ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsCapSize", m_curve->errorBarsCapSize()), Worksheet::Point) ); + ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsCapSize", m_curve->errorBarsCapSize()), Worksheet::Unit::Point) ); ui.cbErrorBarsStyle->setCurrentIndex( group.readEntry("ErrorBarsStyle", (int) m_curve->errorBarsPen().style()) ); ui.kcbErrorBarsColor->setColor( group.readEntry("ErrorBarsColor", m_curve->errorBarsPen().color()) ); - ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsWidth", m_curve->errorBarsPen().widthF()),Worksheet::Point) ); + ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsWidth", m_curve->errorBarsPen().widthF()), Worksheet::Unit::Point) ); ui.sbErrorBarsOpacity->setValue( round(group.readEntry("ErrorBarsOpacity", m_curve->errorBarsOpacity())*100.0) ); m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, ui.kcbDropLineColor->color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, ui.kcbErrorBarsColor->color()); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, ui.kcbFillingFirstColor->color()); m_initializing = false; } void XYCurveDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "XYCurve" ); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. group.writeEntry("LineType", ui.cbLineType->currentIndex()); group.writeEntry("LineSkipGaps", ui.chkLineSkipGaps->isChecked()); group.writeEntry("LineInterpolationPointsCount", ui.sbLineInterpolationPointsCount->value() ); group.writeEntry("LineStyle", ui.cbLineStyle->currentIndex()); group.writeEntry("LineColor", ui.kcbLineColor->color()); - group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(),Worksheet::Point) ); + group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(), Worksheet::Unit::Point) ); group.writeEntry("LineOpacity", ui.sbLineOpacity->value()/100.0); //Drop Line group.writeEntry("DropLineType", ui.cbDropLineType->currentIndex()); group.writeEntry("DropLineStyle", ui.cbDropLineStyle->currentIndex()); group.writeEntry("DropLineColor", ui.kcbDropLineColor->color()); - group.writeEntry("DropLineWidth", Worksheet::convertToSceneUnits(ui.sbDropLineWidth->value(),Worksheet::Point) ); + group.writeEntry("DropLineWidth", Worksheet::convertToSceneUnits(ui.sbDropLineWidth->value(), Worksheet::Unit::Point) ); group.writeEntry("DropLineOpacity", ui.sbDropLineOpacity->value()/100.0); //Symbol (TODO: character) group.writeEntry("SymbolStyle", ui.cbSymbolStyle->currentIndex()); - group.writeEntry("SymbolSize", Worksheet::convertToSceneUnits(ui.sbSymbolSize->value(),Worksheet::Point)); + group.writeEntry("SymbolSize", Worksheet::convertToSceneUnits(ui.sbSymbolSize->value(),Worksheet::Unit::Point)); group.writeEntry("SymbolRotation", ui.sbSymbolRotation->value()); group.writeEntry("SymbolOpacity", ui.sbSymbolOpacity->value()/100.0); group.writeEntry("SymbolFillingStyle", ui.cbSymbolFillingStyle->currentIndex()); group.writeEntry("SymbolFillingColor", ui.kcbSymbolFillingColor->color()); group.writeEntry("SymbolBorderStyle", ui.cbSymbolBorderStyle->currentIndex()); group.writeEntry("SymbolBorderColor", ui.kcbSymbolBorderColor->color()); - group.writeEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(ui.sbSymbolBorderWidth->value(),Worksheet::Point)); + group.writeEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(ui.sbSymbolBorderWidth->value(), Worksheet::Unit::Point)); //Values group.writeEntry("ValuesType", ui.cbValuesType->currentIndex()); group.writeEntry("ValuesPosition", ui.cbValuesPosition->currentIndex()); - group.writeEntry("ValuesDistance", Worksheet::convertToSceneUnits(ui.sbValuesDistance->value(),Worksheet::Point)); + group.writeEntry("ValuesDistance", Worksheet::convertToSceneUnits(ui.sbValuesDistance->value(), Worksheet::Unit::Point)); group.writeEntry("ValuesRotation", ui.sbValuesRotation->value()); group.writeEntry("ValuesOpacity", ui.sbValuesOpacity->value()/100.0); group.writeEntry("valuesNumericFormat", ui.cbValuesNumericFormat->currentText()); group.writeEntry("valuesPrecision", ui.sbValuesPrecision->value()); group.writeEntry("valuesDateTimeFormat", ui.cbValuesDateTimeFormat->currentText()); group.writeEntry("ValuesPrefix", ui.leValuesPrefix->text()); group.writeEntry("ValuesSuffix", ui.leValuesSuffix->text()); group.writeEntry("ValuesFont", ui.kfrValuesFont->font()); group.writeEntry("ValuesColor", ui.kcbValuesColor->color()); //Filling group.writeEntry("FillingPosition", ui.cbFillingPosition->currentIndex()); group.writeEntry("FillingType", ui.cbFillingType->currentIndex()); group.writeEntry("FillingColorStyle", ui.cbFillingColorStyle->currentIndex()); group.writeEntry("FillingImageStyle", ui.cbFillingImageStyle->currentIndex()); group.writeEntry("FillingBrushStyle", ui.cbFillingBrushStyle->currentIndex()); group.writeEntry("FillingFileName", ui.leFillingFileName->text()); group.writeEntry("FillingFirstColor", ui.kcbFillingFirstColor->color()); group.writeEntry("FillingSecondColor", ui.kcbFillingSecondColor->color()); group.writeEntry("FillingOpacity", ui.sbFillingOpacity->value()/100.0); //Error bars group.writeEntry("XErrorType", ui.cbXErrorType->currentIndex()); group.writeEntry("YErrorType", ui.cbYErrorType->currentIndex()); group.writeEntry("ErrorBarsType", ui.cbErrorBarsType->currentIndex()); - group.writeEntry("ErrorBarsCapSize", Worksheet::convertToSceneUnits(ui.sbErrorBarsCapSize->value(),Worksheet::Point) ); + group.writeEntry("ErrorBarsCapSize", Worksheet::convertToSceneUnits(ui.sbErrorBarsCapSize->value(), Worksheet::Unit::Point) ); group.writeEntry("ErrorBarsStyle", ui.cbErrorBarsStyle->currentIndex()); group.writeEntry("ErrorBarsColor", ui.kcbErrorBarsColor->color()); - group.writeEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(ui.sbErrorBarsWidth->value(),Worksheet::Point) ); + group.writeEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(ui.sbErrorBarsWidth->value(), Worksheet::Unit::Point) ); group.writeEntry("ErrorBarsOpacity", ui.sbErrorBarsOpacity->value()/100.0); config.sync(); } diff --git a/src/kdefrontend/widgets/DatapickerCurveWidget.cpp b/src/kdefrontend/widgets/DatapickerCurveWidget.cpp index cef98aba6..3c8e2bb44 100644 --- a/src/kdefrontend/widgets/DatapickerCurveWidget.cpp +++ b/src/kdefrontend/widgets/DatapickerCurveWidget.cpp @@ -1,519 +1,519 @@ /*************************************************************************** File : ImageWidget.cpp Project : LabPlot Description : widget for datapicker properties -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2019 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 "DatapickerCurveWidget.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/worksheet/Worksheet.h" #include "kdefrontend/GuiTools.h" #include #include #include DatapickerCurveWidget::DatapickerCurveWidget(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; ui.cbXErrorType->addItem(i18n("No Error")); ui.cbXErrorType->addItem(i18n("Symmetric")); ui.cbXErrorType->addItem(i18n("Asymmetric")); ui.cbYErrorType->addItem(i18n("No Error")); ui.cbYErrorType->addItem(i18n("Symmetric")); ui.cbYErrorType->addItem(i18n("Asymmetric")); QString info = i18n("Specify whether the data points have errors and of which type.\n" "Note, changing this type is not possible once at least one point was read."); ui.lXErrorType->setToolTip(info); ui.cbXErrorType->setToolTip(info); ui.lYErrorType->setToolTip(info); ui.cbYErrorType->setToolTip(info); connect(ui.leName, &QLineEdit::textChanged, this, &DatapickerCurveWidget::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &DatapickerCurveWidget::commentChanged); connect(ui.cbXErrorType, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerCurveWidget::xErrorTypeChanged); connect(ui.cbYErrorType, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerCurveWidget::yErrorTypeChanged); //symbol connect(ui.cbStyle, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerCurveWidget::styleChanged); connect(ui.sbSize, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerCurveWidget::sizeChanged); connect(ui.sbRotation, static_cast(&QSpinBox::valueChanged), this, &DatapickerCurveWidget::rotationChanged); connect(ui.sbOpacity, static_cast(&QSpinBox::valueChanged), this, &DatapickerCurveWidget::opacityChanged); //Filling connect(ui.cbFillingStyle, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerCurveWidget::fillingStyleChanged); connect(ui.kcbFillingColor, &KColorButton::changed, this, &DatapickerCurveWidget::fillingColorChanged); //border connect(ui.cbBorderStyle, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerCurveWidget::borderStyleChanged); connect(ui.kcbBorderColor, &KColorButton::changed, this, &DatapickerCurveWidget::borderColorChanged); connect(ui.sbBorderWidth, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerCurveWidget::borderWidthChanged); connect(ui.chbVisible, &QCheckBox::clicked, this, &DatapickerCurveWidget::visibilityChanged); //error bar connect(ui.cbErrorBarFillingStyle, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerCurveWidget::errorBarFillingStyleChanged); connect(ui.kcbErrorBarFillingColor, &KColorButton::changed, this, &DatapickerCurveWidget::errorBarFillingColorChanged); connect(ui.sbErrorBarSize, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerCurveWidget::errorBarSizeChanged); init(); hideErrorBarWidgets(true); } DatapickerCurveWidget::~DatapickerCurveWidget() = default; void DatapickerCurveWidget::init() { m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, Qt::black); QPainter pa; int iconSize = 20; QPixmap pm(iconSize, iconSize); QPen pen(Qt::SolidPattern, 0); ui.cbStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); //TODO: constant? for (int i = 1; i < 19; ++i) { auto style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbFillingStyle, Qt::black); GuiTools::updateBrushStyles(ui.cbErrorBarFillingStyle, Qt::black); m_initializing = false; } void DatapickerCurveWidget::setCurves(QList list) { if (list.isEmpty()) return; m_curveList = list; m_curve = list.first(); m_aspect = list.first(); if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_curve->name()); ui.leComment->setText(m_curve->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } load(); updateSymbolWidgets(); connect(m_curve, &AbstractAspect::aspectDescriptionChanged,this, &DatapickerCurveWidget::curveDescriptionChanged); connect(m_curve, &AbstractAspect::aspectRemoved,this, &DatapickerCurveWidget::updateSymbolWidgets); connect(m_curve, &AbstractAspect::aspectAdded, this, &DatapickerCurveWidget::updateSymbolWidgets); connect(m_curve, &DatapickerCurve::curveErrorTypesChanged, this, &DatapickerCurveWidget::curveErrorsChanged); connect(m_curve, &DatapickerCurve::pointStyleChanged, this, &DatapickerCurveWidget::symbolStyleChanged); connect(m_curve, &DatapickerCurve::pointSizeChanged, this, &DatapickerCurveWidget::symbolSizeChanged); connect(m_curve, &DatapickerCurve::pointRotationAngleChanged, this, &DatapickerCurveWidget::symbolRotationAngleChanged); connect(m_curve, &DatapickerCurve::pointOpacityChanged, this, &DatapickerCurveWidget::symbolOpacityChanged); connect(m_curve, &DatapickerCurve::pointBrushChanged, this, &DatapickerCurveWidget::symbolBrushChanged); connect(m_curve, &DatapickerCurve::pointPenChanged, this, &DatapickerCurveWidget::symbolPenChanged); connect(m_curve, &DatapickerCurve::pointVisibilityChanged, this, &DatapickerCurveWidget::symbolVisibleChanged); connect(m_curve, &DatapickerCurve::pointErrorBarBrushChanged, this, &DatapickerCurveWidget::symbolErrorBarBrushChanged); connect(m_curve, &DatapickerCurve::pointErrorBarSizeChanged, this, &DatapickerCurveWidget::symbolErrorBarSizeChanged); } void DatapickerCurveWidget::hideErrorBarWidgets(bool on) { ui.lErrorBar->setVisible(!on); ui.cbErrorBarFillingStyle->setVisible(!on); ui.kcbErrorBarFillingColor->setVisible(!on); ui.lErrorBarFillingColor->setVisible(!on); ui.lErrorBarFillingStyle->setVisible(!on); ui.sbErrorBarSize->setVisible(!on); ui.lErrorBarSize->setVisible(!on); } //************************************************************* //**** SLOTs for changes triggered in DatapickerCurveWidget *** //************************************************************* //"General"-tab void DatapickerCurveWidget::xErrorTypeChanged(int index) { - if ( DatapickerCurve::ErrorType(index) != DatapickerCurve::NoError - || m_curve->curveErrorTypes().y != DatapickerCurve::NoError ) + if ( DatapickerCurve::ErrorType(index) != DatapickerCurve::ErrorType::NoError + || m_curve->curveErrorTypes().y != DatapickerCurve::ErrorType::NoError ) hideErrorBarWidgets(false); else hideErrorBarWidgets(true); if (m_initializing || m_suppressTypeChange) return; DatapickerCurve::Errors errors = m_curve->curveErrorTypes(); errors.x = DatapickerCurve::ErrorType(index); for (auto* curve : m_curveList) curve->setCurveErrorTypes(errors); } void DatapickerCurveWidget::yErrorTypeChanged(int index) { - if ( DatapickerCurve::ErrorType(index) != DatapickerCurve::NoError - || m_curve->curveErrorTypes().x != DatapickerCurve::NoError ) + if ( DatapickerCurve::ErrorType(index) != DatapickerCurve::ErrorType::NoError + || m_curve->curveErrorTypes().x != DatapickerCurve::ErrorType::NoError ) hideErrorBarWidgets(false); else hideErrorBarWidgets(true); if (m_initializing || m_suppressTypeChange) return; DatapickerCurve::Errors errors = m_curve->curveErrorTypes(); errors.y = DatapickerCurve::ErrorType(index); for (auto* curve : m_curveList) curve->setCurveErrorTypes(errors); } void DatapickerCurveWidget::styleChanged(int index) { auto style = Symbol::Style(index + 1); //enable/disable the filling options in the GUI depending on the currently selected points. if (style != Symbol::Line && style != Symbol::Cross) { ui.cbFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbFillingColor->setEnabled(!noBrush); } else { ui.kcbFillingColor->setEnabled(false); ui.cbFillingStyle->setEnabled(false); } bool noLine = (Qt::PenStyle(ui.cbBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbBorderColor->setEnabled(!noLine); ui.sbBorderWidth->setEnabled(!noLine); if (m_initializing) return; for (auto* curve : m_curveList) curve->setPointStyle(style); } void DatapickerCurveWidget::sizeChanged(double value) { if (m_initializing) return; for (auto* curve : m_curveList) - curve->setPointSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + curve->setPointSize( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void DatapickerCurveWidget::rotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_curveList) curve->setPointRotationAngle(value); } void DatapickerCurveWidget::opacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curveList) curve->setPointOpacity(opacity); } void DatapickerCurveWidget::errorBarSizeChanged(double value) { if (m_initializing) return; for (auto* curve : m_curveList) - curve->setPointErrorBarSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + curve->setPointErrorBarSize( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void DatapickerCurveWidget::fillingStyleChanged(int index) { auto brushStyle = Qt::BrushStyle(index); ui.kcbFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; for (auto* curve : m_curveList) { brush = curve->pointBrush(); brush.setStyle(brushStyle); curve->setPointBrush(brush); } } void DatapickerCurveWidget::errorBarFillingStyleChanged(int index) { auto brushStyle = Qt::BrushStyle(index); ui.kcbErrorBarFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; for (auto* curve : m_curveList) { brush = curve->pointBrush(); brush.setStyle(brushStyle); curve->setPointErrorBarBrush(brush); } } void DatapickerCurveWidget::fillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; for (auto* curve : m_curveList) { brush = curve->pointBrush(); brush.setColor(color); curve->setPointBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbFillingStyle, color ); m_initializing = false; } void DatapickerCurveWidget::errorBarFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; for (auto* curve : m_curveList) { brush = curve->pointErrorBarBrush(); brush.setColor(color); curve->setPointErrorBarBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbErrorBarFillingStyle, color ); m_initializing = false; } void DatapickerCurveWidget::borderStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbBorderColor->setEnabled(false); ui.sbBorderWidth->setEnabled(false); } else { ui.kcbBorderColor->setEnabled(true); ui.sbBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; for (auto* curve : m_curveList) { pen = curve->pointPen(); pen.setStyle(penStyle); curve->setPointPen(pen); } } void DatapickerCurveWidget::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curveList) { pen = curve->pointPen(); pen.setColor(color); curve->setPointPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void DatapickerCurveWidget::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curveList) { pen = curve->pointPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); curve->setPointPen(pen); } } void DatapickerCurveWidget::visibilityChanged(bool state) { if (m_initializing) return; for (auto* curve : m_curveList) curve->setPointVisibility(state); } void DatapickerCurveWidget::updateSymbolWidgets() { auto list = m_curve->children(AbstractAspect::ChildIndexFlag::IncludeHidden); if (list.isEmpty()) { ui.cbXErrorType->setEnabled(true); ui.cbYErrorType->setEnabled(true); m_suppressTypeChange = false; } else { ui.cbXErrorType->setEnabled(false); ui.cbYErrorType->setEnabled(false); m_suppressTypeChange = true; } } //************************************************************* //******** SLOTs for changes triggered in DatapickerCurve ***** //************************************************************* void DatapickerCurveWidget::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void DatapickerCurveWidget::curveErrorsChanged(DatapickerCurve::Errors errors) { m_initializing = true; ui.cbXErrorType->setCurrentIndex((int) errors.x); ui.cbYErrorType->setCurrentIndex((int) errors.y); m_initializing = false; } void DatapickerCurveWidget::symbolStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbStyle->setCurrentIndex((int)style - 1); m_initializing = false; } void DatapickerCurveWidget::symbolSizeChanged(qreal size) { m_initializing = true; - ui.sbSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void DatapickerCurveWidget::symbolErrorBarSizeChanged(qreal size) { m_initializing = true; - ui.sbErrorBarSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbErrorBarSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void DatapickerCurveWidget::symbolRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbRotation->setValue(round(angle)); m_initializing = false; } void DatapickerCurveWidget::symbolOpacityChanged(qreal opacity) { m_initializing = true; ui.sbOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void DatapickerCurveWidget::symbolBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbFillingStyle, brush.color()); m_initializing = false; } void DatapickerCurveWidget::symbolErrorBarBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbErrorBarFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbErrorBarFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbErrorBarFillingStyle, brush.color()); m_initializing = false; } void DatapickerCurveWidget::symbolPenChanged(const QPen& pen) { m_initializing = true; ui.cbBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbBorderStyle, pen.color()); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void DatapickerCurveWidget::symbolVisibleChanged(bool on) { m_initializing = true; ui.chbVisible->setChecked(on); m_initializing = false; } //********************************************************** //******************** SETTINGS **************************** //********************************************************** void DatapickerCurveWidget::load() { if (!m_curve) return; m_initializing = true; ui.cbXErrorType->setCurrentIndex((int) m_curve->curveErrorTypes().x); ui.cbYErrorType->setCurrentIndex((int) m_curve->curveErrorTypes().y); ui.cbStyle->setCurrentIndex( (int)m_curve->pointStyle() - 1 ); - ui.sbSize->setValue( Worksheet::convertFromSceneUnits(m_curve->pointSize(), Worksheet::Point) ); + ui.sbSize->setValue( Worksheet::convertFromSceneUnits(m_curve->pointSize(), Worksheet::Unit::Point) ); ui.sbRotation->setValue( m_curve->pointRotationAngle() ); ui.sbOpacity->setValue( round(m_curve->pointOpacity()*100.0) ); ui.cbFillingStyle->setCurrentIndex( (int) m_curve->pointBrush().style() ); ui.kcbFillingColor->setColor( m_curve->pointBrush().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_curve->pointPen().style() ); ui.kcbBorderColor->setColor( m_curve->pointPen().color() ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->pointPen().widthF(), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->pointPen().widthF(), Worksheet::Unit::Point) ); ui.chbVisible->setChecked( m_curve->pointVisibility() ); ui.cbErrorBarFillingStyle->setCurrentIndex( (int) m_curve->pointErrorBarBrush().style() ); ui.kcbErrorBarFillingColor->setColor( m_curve->pointErrorBarBrush().color() ); - ui.sbErrorBarSize->setValue( Worksheet::convertFromSceneUnits(m_curve->pointErrorBarSize(), Worksheet::Point) ); + ui.sbErrorBarSize->setValue( Worksheet::convertFromSceneUnits(m_curve->pointErrorBarSize(), Worksheet::Unit::Point) ); m_initializing = false; } diff --git a/src/kdefrontend/widgets/DatapickerImageWidget.cpp b/src/kdefrontend/widgets/DatapickerImageWidget.cpp index ecb30b6ab..4686697f9 100644 --- a/src/kdefrontend/widgets/DatapickerImageWidget.cpp +++ b/src/kdefrontend/widgets/DatapickerImageWidget.cpp @@ -1,902 +1,902 @@ /*************************************************************************** File : DatapickerImageWidget.cpp Project : LabPlot Description : widget for datapicker properties -------------------------------------------------------------------- Copyright : (C) 2015-2016 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 "DatapickerImageWidget.h" #include "backend/datapicker/DatapickerPoint.h" #include "commonfrontend/widgets/qxtspanslider.h" #include "kdefrontend/GuiTools.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Symbol.h" #include "backend/datapicker/ImageEditor.h" #include #include #include #include #include #include #include #include #include #include #include HistogramView::HistogramView(QWidget* parent, int range) : QGraphicsView(parent), m_scene(new QGraphicsScene()), m_range(range) { setTransform(QTransform()); QRectF pageRect( 0, 0, 1000, 100 ); m_scene->setSceneRect(pageRect); setScene(m_scene); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_lowerSlider = new QGraphicsRectItem(pageRect, nullptr); m_lowerSlider->setPen(QPen(Qt::black, 0.5)); m_lowerSlider->setBrush(Qt::blue); m_lowerSlider->setOpacity(0.2); m_scene->addItem(m_lowerSlider); m_upperSlider = new QGraphicsRectItem(pageRect, nullptr); m_upperSlider->setPen(QPen(Qt::black, 0.5)); m_upperSlider->setBrush(Qt::blue); m_upperSlider->setOpacity(0.2); m_scene->addItem(m_upperSlider); } void HistogramView::setScalePixmap(const QString& file) { // scene rect is 1000*100 where upper 1000*80 is for histogram graph // and lower 1000*20 is for histogram scale QGraphicsPixmapItem* pixmap = new QGraphicsPixmapItem(QPixmap(file).scaled( 1000, 20, Qt::IgnoreAspectRatio), nullptr); pixmap->setZValue(-1); pixmap->setPos(0, 90); m_scene->addItem(pixmap); } void HistogramView::setSpan(int l, int h) { l = l*1000/m_range; h = h*1000/m_range; m_lowerSlider->setPos(QPointF(l - 1000, 0)); m_upperSlider->setPos(QPointF(h, 0)); invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } void HistogramView::resizeEvent(QResizeEvent *event) { fitInView(m_scene->sceneRect(), Qt::IgnoreAspectRatio); QGraphicsView::resizeEvent(event); } void HistogramView::drawBackground(QPainter* painter, const QRectF& rect) { if (!bins) return; painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); int max = 1; for (int i = 0; i <= m_range; i++) if (bins [i] > max) max = bins [i]; // convert y-scale count to log scale so small counts are still visible // scene rect is 1000*100 where upper 1000*80 is for histogram graph // and lower 1000*20 is for histogram scale QPainterPath path(QPointF(0, (log(bins[0])*100/log(max)))); for (int i = 1; i <= m_range; i++) { int x = i*1000/m_range; int y = 80; if ( bins[i] > 1 ) y = 80 - (log(bins[i])*80/log(max)); path.lineTo(QPointF(x, y)); } painter->drawPath(path); invalidateScene(rect, QGraphicsScene::BackgroundLayer); painter->restore(); } DatapickerImageWidget::DatapickerImageWidget(QWidget* parent) : BaseDock(parent), m_image(nullptr) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; ui.leFileName->setClearButtonEnabled(true); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leFileName->setCompleter(new QCompleter(new QDirModel, this)); auto* editTabLayout = static_cast(ui.tEdit->layout()); editTabLayout->setContentsMargins(2,2,2,2); editTabLayout->setHorizontalSpacing(2); editTabLayout->setVerticalSpacing(4); ssHue = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssHue->setToolTip(i18n("Select the range for the hue.\nEverything outside of this range will be set to white.")); ssHue->setRange(0, 360); editTabLayout->addWidget(ssHue, 3, 2); ssSaturation = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssSaturation->setToolTip(i18n("Select the range for the saturation.\nEverything outside of this range will be set to white.")); ssSaturation->setRange(0,100); editTabLayout->addWidget(ssSaturation, 5, 2); ssValue = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssValue->setToolTip(i18n("Select the range for the value, the degree of lightness of the color.\nEverything outside of this range will be set to white.")); ssValue->setRange(0,100); editTabLayout->addWidget(ssValue, 7, 2); ssIntensity = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssIntensity->setToolTip(i18n("Select the range for the intensity.\nEverything outside of this range will be set to white.")); ssIntensity->setRange(0, 100); editTabLayout->addWidget(ssIntensity, 9, 2); ssForeground = new QxtSpanSlider(Qt::Horizontal, ui.tEdit); ssForeground->setToolTip(i18n("Select the range for the colors that are not part of the background color.\nEverything outside of this range will be set to white.")); ssForeground->setRange(0, 100); editTabLayout->addWidget(ssForeground, 11, 2); ui.cbGraphType->addItem(i18n("Cartesian (x, y)")); ui.cbGraphType->addItem(i18n("Polar (x, y°)")); ui.cbGraphType->addItem(i18n("Polar (x, y(rad))")); ui.cbGraphType->addItem(i18n("Logarithmic (ln(x), y)")); ui.cbGraphType->addItem(i18n("Logarithmic (x, ln(y))")); ui.cbGraphType->addItem(i18n("Ternary (x, y, z)")); ui.lTernaryScale->setHidden(true); ui.sbTernaryScale->setHidden(true); ui.lPositionZ1->setHidden(true); ui.lPositionZ2->setHidden(true); ui.lPositionZ3->setHidden(true); ui.sbPositionZ1->setHidden(true); ui.sbPositionZ2->setHidden(true); ui.sbPositionZ3->setHidden(true); ui.cbPlotImageType->addItem(i18n("No Image")); ui.cbPlotImageType->addItem(i18n("Original Image")); ui.cbPlotImageType->addItem(i18n("Processed Image")); QString valueFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/colorchooser/colorchooser_value.xpm"); QString hueFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/colorchooser/colorchooser_hue.xpm"); QString saturationFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/colorchooser/colorchooser_saturation.xpm"); - gvHue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Hue)); + gvHue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Hue)); gvHue->setToolTip(i18n("Select the range for the hue.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvHue, 2, 2); gvHue->setScalePixmap(hueFile); - gvSaturation = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Saturation)); + gvSaturation = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Saturation)); gvSaturation->setToolTip(i18n("Select the range for the saturation.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvSaturation, 4, 2); gvSaturation->setScalePixmap(saturationFile); - gvValue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Value)); + gvValue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Value)); gvValue->setToolTip(i18n("Select the range for the value, the degree of lightness of the color.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvValue, 6,2); gvValue->setScalePixmap(valueFile); - gvIntensity = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Intensity)); + gvIntensity = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Intensity)); gvIntensity->setToolTip(i18n("Select the range for the intensity.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvIntensity, 8, 2); gvIntensity->setScalePixmap(valueFile); - gvForeground = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::Foreground)); + gvForeground = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Foreground)); gvForeground->setToolTip(i18n("Select the range for the colors that are not part of the background color.\nEverything outside of this range will be set to white.")); editTabLayout->addWidget(gvForeground, 10, 2); gvForeground->setScalePixmap(valueFile); //SLOTS //general connect(ui.leName, &QLineEdit::textChanged, this, &DatapickerImageWidget::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &DatapickerImageWidget::commentChanged); connect(ui.bOpen, &QPushButton::clicked, this, &DatapickerImageWidget::selectFile); connect(ui.leFileName, &QLineEdit::returnPressed, this, &DatapickerImageWidget::fileNameChanged); connect(ui.leFileName, &QLineEdit::textChanged, this, &DatapickerImageWidget::fileNameChanged); // edit image connect(ui.cbPlotImageType, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerImageWidget::plotImageTypeChanged); connect(ui.sbRotation, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::rotationChanged); connect(ssIntensity, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::intensitySpanChanged); connect(ssIntensity, &QxtSpanSlider::spanChanged, gvIntensity, &HistogramView::setSpan); connect(ssForeground, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::foregroundSpanChanged); connect(ssForeground, &QxtSpanSlider::spanChanged, gvForeground, &HistogramView::setSpan ); connect(ssHue, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::hueSpanChanged); connect(ssHue, &QxtSpanSlider::spanChanged, gvHue, &HistogramView::setSpan ); connect(ssSaturation, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::saturationSpanChanged); connect(ssSaturation, &QxtSpanSlider::spanChanged, gvSaturation, &HistogramView::setSpan ); connect(ssValue, &QxtSpanSlider::spanChanged, this, &DatapickerImageWidget::valueSpanChanged); connect(ssValue, &QxtSpanSlider::spanChanged, gvValue, &HistogramView::setSpan ); connect(ui.sbMinSegmentLength, static_cast(&QSpinBox::valueChanged), this, &DatapickerImageWidget::minSegmentLengthChanged); connect(ui.sbPointSeparation, static_cast(&QSpinBox::valueChanged), this, &DatapickerImageWidget::pointSeparationChanged); //axis point connect(ui.cbGraphType, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerImageWidget::graphTypeChanged); connect(ui.sbTernaryScale, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::ternaryScaleChanged); connect(ui.sbPositionX1, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionY1, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionX2, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionY2, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionX3, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionY3, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionZ1, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionZ2, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); connect(ui.sbPositionZ3, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged); //SYMBOL connect(ui.cbSymbolStyle, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerImageWidget::pointsStyleChanged); connect(ui.sbSymbolSize, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::pointsSizeChanged); connect(ui.sbSymbolRotation,static_cast(&QSpinBox::valueChanged), this, &DatapickerImageWidget::pointsRotationChanged); connect(ui.sbSymbolOpacity, static_cast(&QSpinBox::valueChanged), this, &DatapickerImageWidget::pointsOpacityChanged); //Filling connect(ui.cbSymbolFillingStyle, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerImageWidget::pointsFillingStyleChanged); connect(ui.kcbSymbolFillingColor, &KColorButton::changed, this, &DatapickerImageWidget::pointsFillingColorChanged); //border connect(ui.cbSymbolBorderStyle, static_cast(&QComboBox::currentIndexChanged), this, &DatapickerImageWidget::pointsBorderStyleChanged); connect(ui.kcbSymbolBorderColor, &KColorButton::changed, this, &DatapickerImageWidget::pointsBorderColorChanged); connect(ui.sbSymbolBorderWidth, static_cast(&QDoubleSpinBox::valueChanged), this, &DatapickerImageWidget::pointsBorderWidthChanged); connect(ui.chbSymbolVisible, &QCheckBox::clicked, this, &DatapickerImageWidget::pointsVisibilityChanged); init(); } void DatapickerImageWidget::init() { m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); QPainter pa; int iconSize = 20; QPixmap pm(iconSize, iconSize); QPen pen(Qt::SolidPattern, 0); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); //TODO: constant? for (int i = 1; i < 19; ++i) { auto style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen( pen ); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; } void DatapickerImageWidget::setImages(QList list) { m_initializing = true; m_imagesList = list; m_image = list.first(); m_aspect = list.first()->parentAspect(); if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_image->parentAspect()->name()); ui.leComment->setText(m_image->parentAspect()->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } this->load(); initConnections(); handleWidgetActions(); updateSymbolWidgets(); m_initializing = false; } void DatapickerImageWidget::initConnections() { connect(m_image->parentAspect(), &AbstractAspect::aspectDescriptionChanged, this, &DatapickerImageWidget::imageDescriptionChanged); connect(m_image, &DatapickerImage::fileNameChanged, this, &DatapickerImageWidget::imageFileNameChanged); connect(m_image, &DatapickerImage::rotationAngleChanged, this, &DatapickerImageWidget::imageRotationAngleChanged); connect(m_image, &AbstractAspect::aspectRemoved, this, &DatapickerImageWidget::updateSymbolWidgets); connect(m_image, &AbstractAspect::aspectAdded, this, &DatapickerImageWidget::updateSymbolWidgets); connect(m_image, &DatapickerImage::axisPointsChanged, this, &DatapickerImageWidget::imageAxisPointsChanged); connect(m_image, &DatapickerImage::settingsChanged, this, &DatapickerImageWidget::imageEditorSettingsChanged); connect(m_image, &DatapickerImage::minSegmentLengthChanged, this, &DatapickerImageWidget::imageMinSegmentLengthChanged); connect(m_image, &DatapickerImage::pointStyleChanged, this, &DatapickerImageWidget::symbolStyleChanged); connect(m_image, &DatapickerImage::pointSizeChanged, this, &DatapickerImageWidget::symbolSizeChanged); connect(m_image, &DatapickerImage::pointRotationAngleChanged, this, &DatapickerImageWidget::symbolRotationAngleChanged); connect(m_image, &DatapickerImage::pointOpacityChanged, this, &DatapickerImageWidget::symbolOpacityChanged); connect(m_image, &DatapickerImage::pointBrushChanged, this, &DatapickerImageWidget::symbolBrushChanged); connect(m_image, &DatapickerImage::pointPenChanged, this, &DatapickerImageWidget::symbolPenChanged); connect(m_image, &DatapickerImage::pointVisibilityChanged, this, &DatapickerImageWidget::symbolVisibleChanged); } void DatapickerImageWidget::handleWidgetActions() { QString fileName = ui.leFileName->text().trimmed(); bool b = !fileName.isEmpty(); ui.tEdit->setEnabled(b); ui.cbGraphType->setEnabled(b); ui.sbRotation->setEnabled(b); ui.sbPositionX1->setEnabled(b); ui.sbPositionX2->setEnabled(b); ui.sbPositionX3->setEnabled(b); ui.sbPositionY1->setEnabled(b); ui.sbPositionY2->setEnabled(b); ui.sbPositionY3->setEnabled(b); ui.sbMinSegmentLength->setEnabled(b); ui.sbPointSeparation->setEnabled(b); if (b) { //upload histogram to view gvIntensity->bins = m_image->intensityBins; gvForeground->bins = m_image->foregroundBins; gvHue->bins = m_image->hueBins; gvSaturation->bins = m_image->saturationBins; gvValue->bins = m_image->valueBins; } } //********************************************************** //****** SLOTs for changes triggered in DatapickerImageWidget ******** //********************************************************** //"General"-tab void DatapickerImageWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "DatapickerImageWidget"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += " " + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leFileName->setText( path ); handleWidgetActions(); for (auto* image : m_imagesList) image->setFileName(path); } void DatapickerImageWidget::fileNameChanged() { if (m_initializing) return; handleWidgetActions(); QString fileName = ui.leFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leFileName->setStyleSheet(QString()); for (auto* image : m_imagesList) image->setFileName(fileName); } void DatapickerImageWidget::graphTypeChanged() { if (m_initializing) return; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.type = DatapickerImage::GraphType(ui.cbGraphType->currentIndex()); - if (points.type != DatapickerImage::Ternary) { + if (points.type != DatapickerImage::GraphType::Ternary) { ui.lTernaryScale->setHidden(true); ui.sbTernaryScale->setHidden(true); ui.lPositionZ1->setHidden(true); ui.lPositionZ2->setHidden(true); ui.lPositionZ3->setHidden(true); ui.sbPositionZ1->setHidden(true); ui.sbPositionZ2->setHidden(true); ui.sbPositionZ3->setHidden(true); } else { ui.lTernaryScale->setHidden(false); ui.sbTernaryScale->setHidden(false); ui.lPositionZ1->setHidden(false); ui.lPositionZ2->setHidden(false); ui.lPositionZ3->setHidden(false); ui.sbPositionZ1->setHidden(false); ui.sbPositionZ2->setHidden(false); ui.sbPositionZ3->setHidden(false); } for (auto* image : m_imagesList) image->setAxisPoints(points); } void DatapickerImageWidget::ternaryScaleChanged(double value) { if (m_initializing) return; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.ternaryScale = value; for (auto* image : m_imagesList) image->setAxisPoints(points); } void DatapickerImageWidget::logicalPositionChanged() { if (m_initializing) return; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.logicalPos[0].setX(ui.sbPositionX1->value()); points.logicalPos[0].setY(ui.sbPositionY1->value()); points.logicalPos[1].setX(ui.sbPositionX2->value()); points.logicalPos[1].setY(ui.sbPositionY2->value()); points.logicalPos[2].setX(ui.sbPositionX3->value()); points.logicalPos[2].setY(ui.sbPositionY3->value()); points.logicalPos[0].setZ(ui.sbPositionZ1->value()); points.logicalPos[1].setZ(ui.sbPositionZ2->value()); points.logicalPos[2].setZ(ui.sbPositionZ3->value()); const Lock lock(m_initializing); for (auto* image : m_imagesList) image->setAxisPoints(points); } void DatapickerImageWidget::pointsStyleChanged(int index) { auto style = Symbol::Style(index + 1); //enable/disable the filling options in the GUI depending on the currently selected points. if (style != Symbol::Line && style != Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); if (m_initializing) return; for (auto* image : m_imagesList) image->setPointStyle(style); } void DatapickerImageWidget::pointsSizeChanged(double value) { if (m_initializing) return; for (auto* image : m_imagesList) - image->setPointSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + image->setPointSize( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void DatapickerImageWidget::pointsRotationChanged(int value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPointRotationAngle(value); } void DatapickerImageWidget::pointsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* image : m_imagesList) image->setPointOpacity(opacity); } void DatapickerImageWidget::pointsFillingStyleChanged(int index) { auto brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; for (auto* image : m_imagesList) { brush = image->pointBrush(); brush.setStyle(brushStyle); image->setPointBrush(brush); } } void DatapickerImageWidget::pointsFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; for (auto* image : m_imagesList) { brush = image->pointBrush(); brush.setColor(color); image->setPointBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void DatapickerImageWidget::pointsBorderStyleChanged(int index) { auto penStyle = Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; for (auto* image : m_imagesList) { pen = image->pointPen(); pen.setStyle(penStyle); image->setPointPen(pen); } } void DatapickerImageWidget::pointsBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* image : m_imagesList) { pen = image->pointPen(); pen.setColor(color); image->setPointPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void DatapickerImageWidget::pointsBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* image : m_imagesList) { pen = image->pointPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); image->setPointPen(pen); } } void DatapickerImageWidget::pointsVisibilityChanged(bool state) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPointVisibility(state); } void DatapickerImageWidget::intensitySpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.intensityThresholdHigh = upperLimit; settings.intensityThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::foregroundSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.foregroundThresholdHigh = upperLimit; settings.foregroundThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::hueSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.hueThresholdHigh = upperLimit; settings.hueThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::saturationSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.saturationThresholdHigh = upperLimit; settings.saturationThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::valueSpanChanged(int lowerLimit, int upperLimit) { if (m_initializing) return; DatapickerImage::EditorSettings settings = m_image->settings(); settings.valueThresholdHigh = upperLimit; settings.valueThresholdLow = lowerLimit; for (auto* image : m_imagesList) image->setSettings(settings); } void DatapickerImageWidget::plotImageTypeChanged(int index) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPlotImageType(DatapickerImage::PlotImageType(index)); } void DatapickerImageWidget::rotationChanged(double value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setRotationAngle(value); } void DatapickerImageWidget::minSegmentLengthChanged(int value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setminSegmentLength(value); } void DatapickerImageWidget::pointSeparationChanged(int value) { if (m_initializing) return; for (auto* image : m_imagesList) image->setPointSeparation(value); } //******************************************************************* //******** SLOTs for changes triggered in DatapickerImage *********** //******************************************************************* /*! * called when the name or comment of image's parent (datapicker) was changed. */ void DatapickerImageWidget::imageDescriptionChanged(const AbstractAspect* aspect) { if (m_image->parentAspect() != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void DatapickerImageWidget::imageFileNameChanged(const QString& name) { m_initializing = true; ui.leFileName->setText(name); m_initializing = false; } void DatapickerImageWidget::imageRotationAngleChanged(float angle) { m_initializing = true; ui.sbRotation->setValue(angle); m_initializing = false; } void DatapickerImageWidget::imageAxisPointsChanged(const DatapickerImage::ReferencePoints& axisPoints) { if (m_initializing)return; const Lock lock(m_initializing); m_initializing = true; ui.cbGraphType->setCurrentIndex((int) axisPoints.type); ui.sbTernaryScale->setValue(axisPoints.ternaryScale); ui.sbPositionX1->setValue(axisPoints.logicalPos[0].x()); ui.sbPositionY1->setValue(axisPoints.logicalPos[0].y()); ui.sbPositionX2->setValue(axisPoints.logicalPos[1].x()); ui.sbPositionY2->setValue(axisPoints.logicalPos[1].y()); ui.sbPositionX3->setValue(axisPoints.logicalPos[2].x()); ui.sbPositionY3->setValue(axisPoints.logicalPos[2].y()); ui.sbPositionZ1->setValue(axisPoints.logicalPos[0].z()); ui.sbPositionZ2->setValue(axisPoints.logicalPos[1].z()); ui.sbPositionZ3->setValue(axisPoints.logicalPos[2].z()); m_initializing = false; } void DatapickerImageWidget::imageEditorSettingsChanged(const DatapickerImage::EditorSettings& settings) { m_initializing = true; ssIntensity->setSpan(settings.intensityThresholdLow, settings.intensityThresholdHigh); ssForeground->setSpan(settings.foregroundThresholdLow, settings.foregroundThresholdHigh); ssHue->setSpan(settings.hueThresholdLow, settings.hueThresholdHigh); ssSaturation->setSpan(settings.saturationThresholdLow, settings.saturationThresholdHigh); ssValue->setSpan(settings.valueThresholdLow, settings.valueThresholdHigh); gvIntensity->setSpan(settings.intensityThresholdLow, settings.intensityThresholdHigh); gvForeground->setSpan(settings.foregroundThresholdLow, settings.foregroundThresholdHigh); gvHue->setSpan(settings.hueThresholdLow, settings.hueThresholdHigh); gvSaturation->setSpan(settings.saturationThresholdLow, settings.saturationThresholdHigh); gvValue->setSpan(settings.valueThresholdLow, settings.valueThresholdHigh); m_initializing = false; } void DatapickerImageWidget::imageMinSegmentLengthChanged(const int value) { m_initializing = true; ui.sbMinSegmentLength->setValue(value); m_initializing = false; } void DatapickerImageWidget::updateSymbolWidgets() { int pointCount = m_image->childCount(AbstractAspect::ChildIndexFlag::IncludeHidden); if (pointCount) ui.tSymbol->setEnabled(true); else ui.tSymbol->setEnabled(false); } void DatapickerImageWidget::symbolStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style - 1); m_initializing = false; } void DatapickerImageWidget::symbolSizeChanged(qreal size) { m_initializing = true; - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point) ); m_initializing = false; } void DatapickerImageWidget::symbolRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbSymbolRotation->setValue(round(angle)); m_initializing = false; } void DatapickerImageWidget::symbolOpacityChanged(qreal opacity) { m_initializing = true; ui.sbSymbolOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void DatapickerImageWidget::symbolBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void DatapickerImageWidget::symbolPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void DatapickerImageWidget::symbolVisibleChanged(bool on) { m_initializing = true; ui.chbSymbolVisible->setChecked(on); m_initializing = false; } //********************************************************** //******************** SETTINGS **************************** //********************************************************** void DatapickerImageWidget::load() { if (!m_image) return; m_initializing = true; ui.leFileName->setText( m_image->fileName() ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_image->fileName().isEmpty() && !QFile::exists(m_image->fileName())) ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leFileName->setStyleSheet(QString()); ui.cbGraphType->setCurrentIndex((int) m_image->axisPoints().type); ui.sbTernaryScale->setValue(m_image->axisPoints().ternaryScale); ui.sbPositionX1->setValue(m_image->axisPoints().logicalPos[0].x()); ui.sbPositionY1->setValue(m_image->axisPoints().logicalPos[0].y()); ui.sbPositionX2->setValue(m_image->axisPoints().logicalPos[1].x()); ui.sbPositionY2->setValue(m_image->axisPoints().logicalPos[1].y()); ui.sbPositionX3->setValue(m_image->axisPoints().logicalPos[2].x()); ui.sbPositionY3->setValue(m_image->axisPoints().logicalPos[2].y()); ui.sbPositionZ1->setValue(m_image->axisPoints().logicalPos[0].z()); ui.sbPositionZ2->setValue(m_image->axisPoints().logicalPos[1].z()); ui.sbPositionZ3->setValue(m_image->axisPoints().logicalPos[2].z()); ui.cbPlotImageType->setCurrentIndex((int) m_image->plotImageType()); ssIntensity->setSpan(m_image->settings().intensityThresholdLow, m_image->settings().intensityThresholdHigh); ssForeground->setSpan(m_image->settings().foregroundThresholdLow, m_image->settings().foregroundThresholdHigh); ssHue->setSpan(m_image->settings().hueThresholdLow, m_image->settings().hueThresholdHigh); ssSaturation->setSpan(m_image->settings().saturationThresholdLow, m_image->settings().saturationThresholdHigh); ssValue->setSpan(m_image->settings().valueThresholdLow, m_image->settings().valueThresholdHigh); gvIntensity->setSpan(m_image->settings().intensityThresholdLow, m_image->settings().intensityThresholdHigh); gvForeground->setSpan(m_image->settings().foregroundThresholdLow, m_image->settings().foregroundThresholdHigh); gvHue->setSpan(m_image->settings().hueThresholdLow, m_image->settings().hueThresholdHigh); gvSaturation->setSpan(m_image->settings().saturationThresholdLow, m_image->settings().saturationThresholdHigh); gvValue->setSpan(m_image->settings().valueThresholdLow, m_image->settings().valueThresholdHigh); ui.sbPointSeparation->setValue(m_image->pointSeparation()); ui.sbMinSegmentLength->setValue(m_image->minSegmentLength()); ui.cbSymbolStyle->setCurrentIndex( (int)m_image->pointStyle() - 1 ); - ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_image->pointSize(), Worksheet::Point) ); + ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_image->pointSize(), Worksheet::Unit::Point) ); ui.sbSymbolRotation->setValue( m_image->pointRotationAngle() ); ui.sbSymbolOpacity->setValue( round(m_image->pointOpacity()*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( (int) m_image->pointBrush().style() ); ui.kcbSymbolFillingColor->setColor( m_image->pointBrush().color() ); ui.cbSymbolBorderStyle->setCurrentIndex( (int) m_image->pointPen().style() ); ui.kcbSymbolBorderColor->setColor( m_image->pointPen().color() ); - ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_image->pointPen().widthF(), Worksheet::Point) ); + ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_image->pointPen().widthF(), Worksheet::Unit::Point) ); ui.chbSymbolVisible->setChecked( m_image->pointVisibility() ); m_initializing = false; } diff --git a/src/kdefrontend/widgets/LabelWidget.cpp b/src/kdefrontend/widgets/LabelWidget.cpp index a7b6a2749..1b896560e 100644 --- a/src/kdefrontend/widgets/LabelWidget.cpp +++ b/src/kdefrontend/widgets/LabelWidget.cpp @@ -1,1232 +1,1232 @@ /*************************************************************************** File : LabelWidget.cc Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2008-2020 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2017 Stefan Gerlach (stefan.gerlach@uni-konstanz.de) Description : label settings widget ***************************************************************************/ /*************************************************************************** * * * 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 "LabelWidget.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/dockwidgets/BaseDock.h" #include "tools/TeXRenderer.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING #include #include #include #endif #include /*! \class LabelWidget \brief Widget for editing the properties of a TextLabel object, mostly used in an appropriate dock widget. In order the properties of the label to be shown, \c loadConfig() has to be called with the corresponding KConfigGroup (settings for a label in *Plot, Axis etc. or for an independent label on the worksheet). \ingroup kdefrontend */ LabelWidget::LabelWidget(QWidget* parent) : QWidget(parent), m_dateTimeMenu(new QMenu(this)) { ui.setupUi(this); const KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); m_units = (BaseDock::Units)group.readEntry("Units", (int)BaseDock::MetricUnits); if (m_units == BaseDock::ImperialUnits) - m_worksheetUnit = Worksheet::Inch; + m_worksheetUnit = Worksheet::Unit::Inch; m_dateTimeMenu->setSeparatorsCollapsible(false); //we don't want the first separator to be removed ui.kcbFontColor->setColor(Qt::black); // default color //Icons ui.tbFontBold->setIcon( QIcon::fromTheme(QLatin1String("format-text-bold")) ); ui.tbFontItalic->setIcon( QIcon::fromTheme(QLatin1String("format-text-italic")) ); ui.tbFontUnderline->setIcon( QIcon::fromTheme(QLatin1String("format-text-underline")) ); ui.tbFontStrikeOut->setIcon( QIcon::fromTheme(QLatin1String("format-text-strikethrough")) ); ui.tbFontSuperScript->setIcon( QIcon::fromTheme(QLatin1String("format-text-superscript")) ); ui.tbFontSubScript->setIcon( QIcon::fromTheme(QLatin1String("format-text-subscript")) ); ui.tbSymbols->setIcon( QIcon::fromTheme(QLatin1String("labplot-format-text-symbol")) ); ui.tbDateTime->setIcon( QIcon::fromTheme(QLatin1String("chronometer")) ); ui.tbTexUsed->setIcon( QIcon::fromTheme(QLatin1String("labplot-TeX-logo")) ); ui.tbFontBold->setToolTip(i18n("Bold")); ui.tbFontItalic->setToolTip(i18n("Italic")); ui.tbFontUnderline->setToolTip(i18n("Underline")); ui.tbFontStrikeOut->setToolTip(i18n("Strike Out")); ui.tbFontSuperScript->setToolTip(i18n("Super Script")); ui.tbFontSubScript->setToolTip(i18n("Sub-Script")); ui.tbSymbols->setToolTip(i18n("Insert Symbol")); ui.tbDateTime->setToolTip(i18n("Insert Date/Time")); ui.tbTexUsed->setToolTip(i18n("Switch to TeX mode")); //Positioning and alignment ui.cbPositionX->addItem(i18n("Left")); ui.cbPositionX->addItem(i18n("Center")); ui.cbPositionX->addItem(i18n("Right")); ui.cbPositionX->addItem(i18n("Custom")); ui.cbPositionY->addItem(i18n("Top")); ui.cbPositionY->addItem(i18n("Center")); ui.cbPositionY->addItem(i18n("Bottom")); ui.cbPositionY->addItem(i18n("Custom")); QString suffix; if (m_units == BaseDock::MetricUnits) suffix = QLatin1String("cm"); else suffix = QLatin1String("in"); ui.sbPositionX->setSuffix(suffix); ui.sbPositionY->setSuffix(suffix); ui.cbHorizontalAlignment->addItem(i18n("Left")); ui.cbHorizontalAlignment->addItem(i18n("Center")); ui.cbHorizontalAlignment->addItem(i18n("Right")); ui.cbVerticalAlignment->addItem(i18n("Top")); ui.cbVerticalAlignment->addItem(i18n("Center")); ui.cbVerticalAlignment->addItem(i18n("Bottom")); ui.cbBorderShape->addItem(i18n("No Border")); ui.cbBorderShape->addItem(i18n("Rectangle")); ui.cbBorderShape->addItem(i18n("Ellipse")); ui.cbBorderShape->addItem(i18n("Round sided rectangle")); ui.cbBorderShape->addItem(i18n("Round corner rectangle")); ui.cbBorderShape->addItem(i18n("Inwards round corner rectangle")); ui.cbBorderShape->addItem(i18n("Dented border rectangle")); ui.cbBorderShape->addItem(i18n("Cuboid")); ui.cbBorderShape->addItem(i18n("Up Pointing rectangle")); ui.cbBorderShape->addItem(i18n("Down Pointing rectangle")); ui.cbBorderShape->addItem(i18n("Left Pointing rectangle")); ui.cbBorderShape->addItem(i18n("Right Pointing rectangle")); ui.cbBorderStyle->addItem(i18n("No line")); ui.cbBorderStyle->addItem(i18n("Solid line")); ui.cbBorderStyle->addItem(i18n("Dash line")); ui.cbBorderStyle->addItem(i18n("Dot line")); ui.cbBorderStyle->addItem(i18n("Dash dot line")); ui.cbBorderStyle->addItem(i18n("Dash dot dot line")); ui.kcbBackgroundColor->setAlphaChannelEnabled(true); ui.kcbBackgroundColor->setColor(QColor(0,0,0, 0)); // transparent ui.kcbFontColor->setAlphaChannelEnabled(true); ui.kcbFontColor->setColor(QColor(255,255,255, 255)); // black ui.kcbBorderColor->setAlphaChannelEnabled(true); ui.kcbBorderColor->setColor(QColor(255,255,255, 255)); // black //check whether the used latex compiler is available. //Following logic is implemented (s.a. LabelWidget::teXUsedChanged()): //1. in case latex was used to generate the text label in the stored project //and no latex is available on the target system, latex button is toggled and //the user still can switch to the non-latex mode. //2. in case the label was in the non-latex mode and no latex is available, //deactivate the latex button so the user cannot switch to this mode. m_teXEnabled = TeXRenderer::enabled(); #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING m_highlighter = new KSyntaxHighlighting::SyntaxHighlighter(ui.teLabel->document()); m_highlighter->setDefinition(m_repository.definitionForName(QLatin1String("LaTeX"))); m_highlighter->setTheme( (palette().color(QPalette::Base).lightness() < 128) ? m_repository.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme) : m_repository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme) ); #endif //SLOTS // text properties connect(ui.tbTexUsed, &QToolButton::clicked, this, &LabelWidget::teXUsedChanged ); connect(ui.teLabel, &ResizableTextEdit::textChanged, this, &LabelWidget::textChanged); connect(ui.teLabel, &ResizableTextEdit::currentCharFormatChanged, this, &LabelWidget::charFormatChanged); connect(ui.kcbFontColor, &KColorButton::changed, this, &LabelWidget::fontColorChanged); connect(ui.kcbBackgroundColor, &KColorButton::changed, this, &LabelWidget::backgroundColorChanged); connect(ui.tbFontBold, &QToolButton::clicked, this, &LabelWidget::fontBoldChanged); connect(ui.tbFontItalic, &QToolButton::clicked, this, &LabelWidget::fontItalicChanged); connect(ui.tbFontUnderline, &QToolButton::clicked, this, &LabelWidget::fontUnderlineChanged); connect(ui.tbFontStrikeOut, &QToolButton::clicked, this, &LabelWidget::fontStrikeOutChanged); connect(ui.tbFontSuperScript, &QToolButton::clicked, this, &LabelWidget::fontSuperScriptChanged); connect(ui.tbFontSubScript, &QToolButton::clicked, this, &LabelWidget::fontSubScriptChanged); connect(ui.tbSymbols, &QToolButton::clicked, this, &LabelWidget::charMenu); connect(ui.tbDateTime, &QToolButton::clicked, this, &LabelWidget::dateTimeMenu); connect(m_dateTimeMenu, &QMenu::triggered, this, &LabelWidget::insertDateTime ); connect(ui.kfontRequester, &KFontRequester::fontSelected, this, &LabelWidget::fontChanged); connect(ui.kfontRequesterTeX, &KFontRequester::fontSelected, this, &LabelWidget::teXFontChanged); connect(ui.sbFontSize, QOverload::of(&QSpinBox::valueChanged), this, &LabelWidget::fontSizeChanged); // geometry connect( ui.cbPositionX, QOverload::of(&KComboBox::currentIndexChanged), this, &LabelWidget::positionXChanged); connect( ui.cbPositionY, QOverload::of(&KComboBox::currentIndexChanged), this, &LabelWidget::positionYChanged); connect( ui.sbPositionX, QOverload::of(&QDoubleSpinBox::valueChanged), this, &LabelWidget::customPositionXChanged); connect( ui.sbPositionY, QOverload::of(&QDoubleSpinBox::valueChanged), this, &LabelWidget::customPositionYChanged); connect( ui.cbHorizontalAlignment, QOverload::of(&KComboBox::currentIndexChanged), this, &LabelWidget::horizontalAlignmentChanged); connect( ui.cbVerticalAlignment, QOverload::of(&KComboBox::currentIndexChanged), this, &LabelWidget::verticalAlignmentChanged); connect( ui.sbRotation, QOverload::of(&QSpinBox::valueChanged), this, &LabelWidget::rotationChanged); connect( ui.sbOffsetX, QOverload::of(&QDoubleSpinBox::valueChanged), this, &LabelWidget::offsetXChanged); connect( ui.sbOffsetY, QOverload::of(&QDoubleSpinBox::valueChanged), this, &LabelWidget::offsetYChanged); connect( ui.chbVisible, &QCheckBox::clicked, this, &LabelWidget::visibilityChanged); //Border connect(ui.cbBorderShape, QOverload::of(&QComboBox::currentIndexChanged), this, &LabelWidget::borderShapeChanged); connect(ui.cbBorderStyle, QOverload::of(&QComboBox::currentIndexChanged), this, &LabelWidget::borderStyleChanged); connect(ui.kcbBorderColor, &KColorButton::changed, this, &LabelWidget::borderColorChanged); connect(ui.sbBorderWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, &LabelWidget::borderWidthChanged); connect(ui.sbBorderOpacity, QOverload::of(&QSpinBox::valueChanged), this, &LabelWidget::borderOpacityChanged); //TODO: https://bugreports.qt.io/browse/QTBUG-25420 ui.tbFontUnderline->hide(); ui.tbFontStrikeOut->hide(); } void LabelWidget::setLabels(QList labels) { m_labelsList = labels; m_label = labels.first(); ui.lOffsetX->hide(); ui.lOffsetY->hide(); ui.sbOffsetX->hide(); ui.sbOffsetY->hide(); this->load(); initConnections(); updateBackground(); } void LabelWidget::setAxes(QList axes) { m_labelsList.clear(); for (auto* axis : axes) { m_labelsList.append(axis->title()); connect(axis, &Axis::titleOffsetXChanged, this, &LabelWidget::labelOffsetxChanged); connect(axis, &Axis::titleOffsetYChanged, this, &LabelWidget::labelOffsetyChanged ); connect(axis->title(), &TextLabel::rotationAngleChanged, this, &LabelWidget::labelRotationAngleChanged ); } m_axesList = axes; m_label = m_labelsList.first(); this->load(); initConnections(); updateBackground(); } /*! * this function keeps the background color of the TextEdit in LabelWidget in sync with * the background color of the parent aspect. This is to avoid the situations where the * text wouldn't be readable anymore if the foreground color is close or equal to the * background color of TextEdit - e.g., a label with white foreground color on a dark worksheet * and with white background color in TextEdit (desktop default color). * * Called if the background color of the parent aspect has changed. */ void LabelWidget::updateBackground() const { if (ui.tbTexUsed->isChecked()) return; QColor color; AspectType type = m_label->parentAspect()->type(); if (type == AspectType::Worksheet) color = static_cast(m_label->parentAspect())->backgroundFirstColor(); else if (type == AspectType::CartesianPlot) color = static_cast(m_label->parentAspect())->plotArea()->backgroundFirstColor(); else if (type == AspectType::CartesianPlotLegend) color = static_cast(m_label->parentAspect())->backgroundFirstColor(); if (type == AspectType::Axis) color = static_cast(m_label->parentAspect()->parentAspect())->plotArea()->backgroundFirstColor(); QPalette p = ui.teLabel->palette(); p.setColor(QPalette::Base, color); ui.teLabel->setPalette(p); } void LabelWidget::initConnections() const { connect(m_label, &TextLabel::textWrapperChanged, this, &LabelWidget::labelTextWrapperChanged); connect(m_label, &TextLabel::teXImageUpdated, this, &LabelWidget::labelTeXImageUpdated); connect(m_label, &TextLabel::teXFontChanged, this, &LabelWidget::labelTeXFontChanged); connect(m_label, &TextLabel::fontColorChanged, this, &LabelWidget::labelFontColorChanged); connect(m_label, &TextLabel::backgroundColorChanged, this, &LabelWidget::labelBackgroundColorChanged); connect(m_label, &TextLabel::positionChanged, this, &LabelWidget::labelPositionChanged); connect(m_label, &TextLabel::horizontalAlignmentChanged, this, &LabelWidget::labelHorizontalAlignmentChanged); connect(m_label, &TextLabel::verticalAlignmentChanged, this, &LabelWidget::labelVerticalAlignmentChanged); connect(m_label, &TextLabel::rotationAngleChanged, this, &LabelWidget::labelRotationAngleChanged); connect(m_label, &TextLabel::borderShapeChanged, this, &LabelWidget::labelBorderShapeChanged); connect(m_label, &TextLabel::borderPenChanged, this, &LabelWidget::labelBorderPenChanged); connect(m_label, &TextLabel::borderOpacityChanged, this, &LabelWidget::labelBorderOpacityChanged); connect(m_label, &TextLabel::visibleChanged, this, &LabelWidget::labelVisibleChanged); AspectType type = m_label->parentAspect()->type(); if (type == AspectType::Worksheet) { auto* worksheet = static_cast(m_label->parentAspect()); connect(worksheet, &Worksheet::backgroundFirstColorChanged, this, &LabelWidget::updateBackground); }else if (type == AspectType::CartesianPlot) { auto* plotArea = static_cast(m_label->parentAspect())->plotArea(); connect(plotArea, &PlotArea::backgroundFirstColorChanged, this, &LabelWidget::updateBackground); } else if (type == AspectType::CartesianPlotLegend) { auto* legend = static_cast(m_label->parentAspect()); connect(legend, &CartesianPlotLegend::backgroundFirstColorChanged, this, &LabelWidget::updateBackground); } else if (type == AspectType::Axis) { auto* plotArea = static_cast(m_label->parentAspect()->parentAspect())->plotArea(); connect(plotArea, &PlotArea::backgroundFirstColorChanged, this, &LabelWidget::updateBackground); } } /*! * enables/disables the "fixed label"-mode, used when displaying * the properties of axis' title label. * In this mode, in the "geometry"-part only the offset (offset to the axis) * and the rotation of the label are available. */ void LabelWidget::setFixedLabelMode(const bool b) { ui.lPositionX->setVisible(!b); ui.cbPositionX->setVisible(!b); ui.sbPositionX->setVisible(!b); ui.lPositionY->setVisible(!b); ui.cbPositionY->setVisible(!b); ui.sbPositionY->setVisible(!b); ui.lHorizontalAlignment->setVisible(!b); ui.cbHorizontalAlignment->setVisible(!b); ui.lVerticalAlignment->setVisible(!b); ui.cbVerticalAlignment->setVisible(!b); ui.lOffsetX->setVisible(b); ui.lOffsetY->setVisible(b); ui.sbOffsetX->setVisible(b); ui.sbOffsetY->setVisible(b); } /*! * enables/disables all geometry relevant widgets. * Used when displaying legend's title label. */ void LabelWidget::setGeometryAvailable(const bool b) { ui.lGeometry->setVisible(b); ui.lPositionX->setVisible(b); ui.cbPositionX->setVisible(b); ui.sbPositionX->setVisible(b); ui.lPositionY->setVisible(b); ui.cbPositionY->setVisible(b); ui.sbPositionY->setVisible(b); ui.lHorizontalAlignment->setVisible(b); ui.cbHorizontalAlignment->setVisible(b); ui.lVerticalAlignment->setVisible(b); ui.cbVerticalAlignment->setVisible(b); ui.lOffsetX->setVisible(b); ui.lOffsetY->setVisible(b); ui.sbOffsetX->setVisible(b); ui.sbOffsetY->setVisible(b); ui.lRotation->setVisible(b); ui.sbRotation->setVisible(b); } /*! * enables/disables all border relevant widgets. * Used when displaying legend's title label. */ void LabelWidget::setBorderAvailable(bool b) { ui.lBorder->setVisible(b); ui.lBorderShape->setVisible(b); ui.cbBorderShape->setVisible(b); ui.lBorderStyle->setVisible(b); ui.cbBorderStyle->setVisible(b); ui.lBorderColor->setVisible(b); ui.kcbBorderColor->setVisible(b); ui.lBorderWidth->setVisible(b); ui.sbBorderWidth->setVisible(b); ui.lBorderOpacity->setVisible(b); ui.sbBorderOpacity->setVisible(b); } void LabelWidget::updateUnits() { const KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); BaseDock::Units units = (BaseDock::Units)group.readEntry("Units", (int)BaseDock::MetricUnits); if (units == m_units) return; m_units = units; Lock lock(m_initializing); QString suffix; if (m_units == BaseDock::MetricUnits) { //convert from imperial to metric - m_worksheetUnit = Worksheet::Centimeter; + m_worksheetUnit = Worksheet::Unit::Centimeter; suffix = QLatin1String("cm"); ui.sbPositionX->setValue(ui.sbPositionX->value()*2.54); ui.sbPositionY->setValue(ui.sbPositionX->value()*2.54); } else { //convert from metric to imperial - m_worksheetUnit = Worksheet::Inch; + m_worksheetUnit = Worksheet::Unit::Inch; suffix = QLatin1String("in"); ui.sbPositionX->setValue(ui.sbPositionX->value()/2.54); ui.sbPositionY->setValue(ui.sbPositionY->value()/2.54); } ui.sbPositionX->setSuffix(suffix); ui.sbPositionY->setSuffix(suffix); } //********************************************************** //****** SLOTs for changes triggered in LabelWidget ******** //********************************************************** // text formatting slots void LabelWidget::textChanged() { if (m_initializing) return; const Lock lock(m_initializing); if (ui.tbTexUsed->isChecked()) { QString text = ui.teLabel->toPlainText(); TextLabel::TextWrapper wrapper(text, true); for (auto* label : m_labelsList) label->setText(wrapper); } else { //save an empty string instead of a html-string with empty body, //if no text available in QTextEdit QString text; if (!ui.teLabel->toPlainText().isEmpty()) { //if the current label text is empty, set the color first if (m_label->text().text.isEmpty()) { ui.teLabel->selectAll(); ui.teLabel->setTextColor(m_label->fontColor()); } text = ui.teLabel->toHtml(); } TextLabel::TextWrapper wrapper(text, false, true); for (auto* label : m_labelsList) label->setText(wrapper); } //background color gets lost on every text change... updateBackground(); } /*! * \brief LabelWidget::charFormatChanged * \param format * Used to update the colors, font,... in the color font widgets to show the style of the selected text */ void LabelWidget::charFormatChanged(const QTextCharFormat& format) { if (m_initializing) return; if (ui.tbTexUsed->isChecked()) return; const Lock lock(m_initializing); // update button state ui.tbFontBold->setChecked(ui.teLabel->fontWeight() == QFont::Bold); ui.tbFontItalic->setChecked(ui.teLabel->fontItalic()); ui.tbFontUnderline->setChecked(ui.teLabel->fontUnderline()); ui.tbFontStrikeOut->setChecked(format.fontStrikeOut()); ui.tbFontSuperScript->setChecked(format.verticalAlignment() == QTextCharFormat::AlignSuperScript); ui.tbFontSubScript->setChecked(format.verticalAlignment() == QTextCharFormat::AlignSubScript); //font and colors if (format.foreground().color().isValid()) ui.kcbFontColor->setColor(format.foreground().color()); else ui.kcbFontColor->setColor(m_label->fontColor()); if (format.background().color().isValid()) ui.kcbBackgroundColor->setColor(format.background().color()); else ui.kcbBackgroundColor->setColor(m_label->backgroundColor()); ui.kfontRequester->setFont(format.font()); } void LabelWidget::teXUsedChanged(bool checked) { //hide text editing elements if TeX-option is used ui.tbFontBold->setVisible(!checked); ui.tbFontItalic->setVisible(!checked); //TODO: https://bugreports.qt.io/browse/QTBUG-25420 // ui.tbFontUnderline->setVisible(!checked); // ui.tbFontStrikeOut->setVisible(!checked); ui.tbFontSubScript->setVisible(!checked); ui.tbFontSuperScript->setVisible(!checked); ui.tbSymbols->setVisible(!checked); ui.lFont->setVisible(!checked); ui.kfontRequester->setVisible(!checked); //TODO: //for normal text we need to hide the background color because of QTBUG-25420 ui.kcbBackgroundColor->setVisible(checked); ui.lBackgroundColor->setVisible(checked); if (checked) { ui.tbTexUsed->setToolTip(i18n("Switch to TeX mode")); //reset all applied formattings when switching from html to tex mode QTextCursor cursor = ui.teLabel->textCursor(); int position = cursor.position(); ui.teLabel->selectAll(); QTextCharFormat format; ui.teLabel->setCurrentCharFormat(format); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, position); ui.teLabel->setTextCursor(cursor); #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING m_highlighter->setDocument(ui.teLabel->document()); #endif KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("Settings_Worksheet")); QString engine = conf.readEntry(QLatin1String("LaTeXEngine"), ""); if (engine == QLatin1String("xelatex") || engine == QLatin1String("lualatex")) { ui.lFontTeX->setVisible(true); ui.kfontRequesterTeX->setVisible(true); ui.lFontSize->setVisible(false); ui.sbFontSize->setVisible(false); } else { ui.lFontTeX->setVisible(false); ui.kfontRequesterTeX->setVisible(false); ui.lFontSize->setVisible(true); ui.sbFontSize->setVisible(true); } //update TeX colors ui.kcbFontColor->setColor(m_label->fontColor()); ui.kcbBackgroundColor->setColor(m_label->backgroundColor()); } else { ui.tbTexUsed->setToolTip(i18n("Switch to text mode")); #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING m_highlighter->setDocument(nullptr); #endif ui.lFontTeX->setVisible(false); ui.kfontRequesterTeX->setVisible(false); ui.lFontSize->setVisible(false); ui.sbFontSize->setVisible(false); //when switching to the text mode, set the background color to white just for the case the latex code provided by the user //in the TeX-mode is not valid and the background was set to red (s.a. LabelWidget::labelTeXImageUpdated()) ui.teLabel->setStyleSheet(QString()); } //no latex is available and the user switched to the text mode, //deactivate the button since it shouldn't be possible anymore to switch to the TeX-mode if (!m_teXEnabled && !checked) { ui.tbTexUsed->setEnabled(false); ui.tbTexUsed->setToolTip(i18n("LaTeX typesetting not possible. Please check the settings.")); } else ui.tbTexUsed->setEnabled(true); if (m_initializing) return; QString text = checked ? ui.teLabel->toPlainText() : ui.teLabel->toHtml(); TextLabel::TextWrapper wrapper(text, checked); for (auto* label : m_labelsList) label->setText(wrapper); } void LabelWidget::fontColorChanged(const QColor& color) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); ui.teLabel->setTextColor(color); } void LabelWidget::backgroundColorChanged(const QColor& color) { if (m_initializing) return; if (!m_teXEnabled && m_label->text().teXUsed) { QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); ui.teLabel->setTextBackgroundColor(color); } else { // Latex text does not support html code. For this the backgroundColor variable is used // Only single color background is supported for (auto* label : m_labelsList) label->setBackgroundColor(color); } } void LabelWidget::fontSizeChanged(int value) { if (m_initializing) return; QFont font = m_label->teXFont(); font.setPointSize(value); for (auto* label : m_labelsList) label->setTeXFont(font); } void LabelWidget::fontBoldChanged(bool checked) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); if (checked) ui.teLabel->setFontWeight(QFont::Bold); else ui.teLabel->setFontWeight(QFont::Normal); } void LabelWidget::fontItalicChanged(bool checked) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); ui.teLabel->setFontItalic(checked); } void LabelWidget::fontUnderlineChanged(bool checked) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); ui.teLabel->setFontUnderline(checked); } void LabelWidget::fontStrikeOutChanged(bool checked) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); QTextCharFormat format = ui.teLabel->currentCharFormat(); format.setFontStrikeOut(checked); ui.teLabel->setCurrentCharFormat(format); } void LabelWidget::fontSuperScriptChanged(bool checked) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); QTextCharFormat format = ui.teLabel->currentCharFormat(); if (checked) format.setVerticalAlignment(QTextCharFormat::AlignSuperScript); else format.setVerticalAlignment(QTextCharFormat::AlignNormal); ui.teLabel->setCurrentCharFormat(format); } void LabelWidget::fontSubScriptChanged(bool checked) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); QTextCharFormat format = ui.teLabel->currentCharFormat(); if (checked) format.setVerticalAlignment(QTextCharFormat::AlignSubScript); else format.setVerticalAlignment(QTextCharFormat::AlignNormal); ui.teLabel->setCurrentCharFormat(format); } void LabelWidget::fontChanged(const QFont& font) { if (m_initializing) return; QTextCursor c = ui.teLabel->textCursor(); if (c.selectedText().isEmpty()) ui.teLabel->selectAll(); // use format instead of using ui.teLabel->setFontFamily(font.family()); // because this calls after every command textChanged() which is inefficient QTextCharFormat format; format.setFontFamily(font.family()); format.setFontPointSize(font.pointSize()); format.setFontItalic(font.italic()); format.setFontWeight(font.weight()); if (font.underline()) format.setUnderlineStyle(QTextCharFormat::UnderlineStyle::SingleUnderline); if (font.strikeOut()) // anytime true. don't know why format.setFontStrikeOut(font.strikeOut()); ui.teLabel->mergeCurrentCharFormat(format); } void LabelWidget::teXFontChanged(const QFont& font) { if (m_initializing) return; for (auto* label : m_labelsList) label->setTeXFont(font); } void LabelWidget::charMenu() { QMenu menu; KCharSelect selection(this, nullptr, KCharSelect::SearchLine | KCharSelect::CharacterTable | KCharSelect::BlockCombos | KCharSelect::HistoryButtons); QFont font = ui.teLabel->currentFont(); // use the system default size, otherwise the symbols might be hard to read // if the current label font size is too small font.setPointSize(QFont().pointSize()); selection.setCurrentFont(font); connect(&selection, &KCharSelect::charSelected, this, &LabelWidget::insertChar); connect(&selection, &KCharSelect::charSelected, &menu, &LabelWidget::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&selection); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width()+ui.tbSymbols->width(),-menu.sizeHint().height()); menu.exec(ui.tbSymbols->mapToGlobal(pos)); } void LabelWidget::insertChar(QChar c) { ui.teLabel->insertPlainText(QString(c)); } void LabelWidget::dateTimeMenu() { m_dateTimeMenu->clear(); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); const QString configFile = configPath + QLatin1String("/klanguageoverridesrc"); if (!QFile::exists(configFile)) { QDate date = QDate::currentDate(); m_dateTimeMenu->addSeparator()->setText(i18n("Date")); m_dateTimeMenu->addAction( date.toString(Qt::TextDate) ); m_dateTimeMenu->addAction( date.toString(Qt::ISODate) ); m_dateTimeMenu->addAction( date.toString(Qt::SystemLocaleShortDate) ); m_dateTimeMenu->addAction( date.toString(Qt::SystemLocaleLongDate) ); m_dateTimeMenu->addAction( date.toString(Qt::RFC2822Date) ); QDateTime time = QDateTime::currentDateTime(); m_dateTimeMenu->addSeparator()->setText(i18n("Date and Time")); m_dateTimeMenu->addAction( time.toString(Qt::TextDate) ); m_dateTimeMenu->addAction( time.toString(Qt::ISODate) ); m_dateTimeMenu->addAction( time.toString(Qt::SystemLocaleShortDate) ); m_dateTimeMenu->addAction( time.toString(Qt::SystemLocaleLongDate) ); m_dateTimeMenu->addAction( time.toString(Qt::RFC2822Date) ); } else { //application language was changed: //determine the currently used language and use QLocale::toString() //to get the strings translated into the currently used language QSettings settings (configFile, QSettings::IniFormat); settings.beginGroup(QLatin1String("Language")); QByteArray languageCode; languageCode = settings.value(qAppName(), languageCode).toByteArray(); QLocale locale(QString::fromLatin1(languageCode.data())); QDate date = QDate::currentDate(); m_dateTimeMenu->addSeparator()->setText(i18n("Date")); m_dateTimeMenu->addAction( locale.toString(date, QLatin1String("ddd MMM d yyyy")) ); //Qt::TextDate m_dateTimeMenu->addAction( locale.toString(date, QLatin1String("yyyy-MM-dd")) ); //Qt::ISODate m_dateTimeMenu->addAction( locale.system().toString(date, QLocale::ShortFormat) ); //Qt::SystemLocaleShortDate //no LongFormat here since it would contain strings in system's language which (potentially) is not the current application language m_dateTimeMenu->addAction( locale.toString(date, QLatin1String("dd MMM yyyy")) ); //Qt::RFC2822Date QDateTime time = QDateTime::currentDateTime(); m_dateTimeMenu->addSeparator()->setText(i18n("Date and Time")); m_dateTimeMenu->addAction( locale.toString(time, QLatin1String("ddd MMM d hh:mm:ss yyyy")) ); //Qt::TextDate m_dateTimeMenu->addAction( locale.toString(time, QLatin1String("yyyy-MM-ddTHH:mm:ss")) ); //Qt::ISODate m_dateTimeMenu->addAction( locale.system().toString(time, QLocale::ShortFormat) ); //Qt::SystemLocaleShortDate //no LongFormat here since it would contain strings in system's language which (potentially) is not the current application language //TODO: RFC2822 requires time zone but Qt QLocale::toString() seems to ignore TZD (time zone designator) completely, //which works correctly with QDateTime::toString() m_dateTimeMenu->addAction( locale.toString(time, QLatin1String("dd MMM yyyy hh:mm:ss")) ); //Qt::RFC2822Date } m_dateTimeMenu->exec( mapToGlobal(ui.tbDateTime->rect().bottomLeft())); } void LabelWidget::insertDateTime(QAction* action) { ui.teLabel->insertPlainText( action->text().remove('&') ); } // geometry slots /*! called when label's current horizontal position relative to its parent (left, center, right, custom ) is changed. */ void LabelWidget::positionXChanged(int index) { //Enable/disable the spinbox for the x- oordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionX->count()-1 ) ui.sbPositionX->setEnabled(true); else ui.sbPositionX->setEnabled(false); if (m_initializing) return; TextLabel::PositionWrapper position = m_label->position(); position.horizontalPosition = TextLabel::HorizontalPosition(index); for (auto* label : m_labelsList) label->setPosition(position); } /*! called when label's current horizontal position relative to its parent (top, center, bottom, custom ) is changed. */ void LabelWidget::positionYChanged(int index) { //Enable/disable the spinbox for the y-coordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionY->count()-1 ) ui.sbPositionY->setEnabled(true); else ui.sbPositionY->setEnabled(false); if (m_initializing) return; TextLabel::PositionWrapper position = m_label->position(); position.verticalPosition = TextLabel::VerticalPosition(index); for (auto* label : m_labelsList) label->setPosition(position); } void LabelWidget::customPositionXChanged(double value) { if (m_initializing) return; TextLabel::PositionWrapper position = m_label->position(); position.point.setX(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); for (auto* label : m_labelsList) label->setPosition(position); } void LabelWidget::customPositionYChanged(double value) { if (m_initializing) return; TextLabel::PositionWrapper position = m_label->position(); position.point.setY(Worksheet::convertToSceneUnits(value, m_worksheetUnit)); for (auto* label : m_labelsList) label->setPosition(position); } void LabelWidget::horizontalAlignmentChanged(int index) { if (m_initializing) return; for (auto* label : m_labelsList) label->setHorizontalAlignment(TextLabel::HorizontalAlignment(index)); } void LabelWidget::verticalAlignmentChanged(int index) { if (m_initializing) return; for (auto* label : m_labelsList) label->setVerticalAlignment(TextLabel::VerticalAlignment(index)); } void LabelWidget::rotationChanged(int value) { if (m_initializing) return; for (auto* label : m_labelsList) label->setRotationAngle(value); } void LabelWidget::offsetXChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) - axis->setTitleOffsetX( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + axis->setTitleOffsetX( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void LabelWidget::offsetYChanged(double value) { if (m_initializing) return; for (auto* axis : m_axesList) - axis->setTitleOffsetY( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + axis->setTitleOffsetY( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); } void LabelWidget::visibilityChanged(bool state) { if (m_initializing) return; for (auto* label : m_labelsList) label->setVisible(state); } //border void LabelWidget::borderShapeChanged(int index) { auto shape = (TextLabel::BorderShape)index; bool b = (shape != TextLabel::BorderShape::NoBorder); ui.lBorderStyle->setVisible(b); ui.cbBorderStyle->setVisible(b); ui.lBorderWidth->setVisible(b); ui.sbBorderWidth->setVisible(b); ui.lBorderColor->setVisible(b); ui.kcbBorderColor->setVisible(b); ui.lBorderOpacity->setVisible(b); ui.sbBorderOpacity->setVisible(b); if (m_initializing) return; for (auto* label : m_labelsList) label->setBorderShape(shape); } void LabelWidget::borderStyleChanged(int index) { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* label : m_labelsList) { pen = label->borderPen(); pen.setStyle(penStyle); label->setBorderPen(pen); } } void LabelWidget::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* label : m_labelsList) { pen = label->borderPen(); pen.setColor(color); label->setBorderPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void LabelWidget::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* label : m_labelsList) { pen = label->borderPen(); - pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); + pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point) ); label->setBorderPen(pen); } } void LabelWidget::borderOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* label : m_labelsList) label->setBorderOpacity(opacity); } //********************************************************* //****** SLOTs for changes triggered in TextLabel ********* //********************************************************* void LabelWidget::labelTextWrapperChanged(const TextLabel::TextWrapper& text) { if (m_initializing)return; const Lock lock(m_initializing); //save and restore the current cursor position after changing the text QTextCursor cursor = ui.teLabel->textCursor(); int position = cursor.position(); if (text.teXUsed) ui.teLabel->setText(text.text); else ui.teLabel->setHtml(text.text); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, position); ui.teLabel->setTextCursor(cursor); ui.tbTexUsed->setChecked(text.teXUsed); this->teXUsedChanged(text.teXUsed); } /*! * \brief Highlights the text field red if wrong latex syntax was used (null image was produced) * or something else went wrong during rendering (\sa ExpressionTextEdit::validateExpression()) */ void LabelWidget::labelTeXImageUpdated(bool valid) { if (!valid) { if (ui.teLabel->styleSheet().isEmpty()) ui.teLabel->setStyleSheet(QLatin1String("QTextEdit{background: red;}")); } else ui.teLabel->setStyleSheet(QString()); } void LabelWidget::labelTeXFontChanged(const QFont& font) { m_initializing = true; ui.kfontRequesterTeX->setFont(font); ui.sbFontSize->setValue(font.pointSize()); m_initializing = false; } void LabelWidget::labelFontColorChanged(const QColor color) { // this function is only called when the theme is changed. Otherwise the color // is directly in the html text. // when the theme changes, the hole text should change color regardless of the color it has m_initializing = true; ui.kcbFontColor->setColor(color); ui.teLabel->selectAll(); ui.teLabel->setTextColor(color); m_initializing = false; } void LabelWidget::labelPositionChanged(const TextLabel::PositionWrapper& position) { m_initializing = true; ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(position.point.x(), m_worksheetUnit) ); ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(position.point.y(), m_worksheetUnit) ); - ui.cbPositionX->setCurrentIndex( position.horizontalPosition ); - ui.cbPositionY->setCurrentIndex( position.verticalPosition ); + ui.cbPositionX->setCurrentIndex( static_cast(position.horizontalPosition) ); + ui.cbPositionY->setCurrentIndex( static_cast(position.verticalPosition) ); m_initializing = false; } void LabelWidget::labelBackgroundColorChanged(const QColor color) { m_initializing = true; ui.kcbBackgroundColor->setColor(color); m_initializing = false; } void LabelWidget::labelHorizontalAlignmentChanged(TextLabel::HorizontalAlignment index) { m_initializing = true; - ui.cbHorizontalAlignment->setCurrentIndex(index); + ui.cbHorizontalAlignment->setCurrentIndex(static_cast(index)); m_initializing = false; } void LabelWidget::labelVerticalAlignmentChanged(TextLabel::VerticalAlignment index) { m_initializing = true; - ui.cbVerticalAlignment->setCurrentIndex(index); + ui.cbVerticalAlignment->setCurrentIndex(static_cast(index)); m_initializing = false; } void LabelWidget::labelOffsetxChanged(qreal offset) { m_initializing = true; - ui.sbOffsetX->setValue(Worksheet::convertFromSceneUnits(offset, Worksheet::Point)); + ui.sbOffsetX->setValue(Worksheet::convertFromSceneUnits(offset, Worksheet::Unit::Point)); m_initializing = false; } void LabelWidget::labelOffsetyChanged(qreal offset) { m_initializing = true; - ui.sbOffsetY->setValue(Worksheet::convertFromSceneUnits(offset, Worksheet::Point)); + ui.sbOffsetY->setValue(Worksheet::convertFromSceneUnits(offset, Worksheet::Unit::Point)); m_initializing = false; } void LabelWidget::labelRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbRotation->setValue(angle); m_initializing = false; } void LabelWidget::labelVisibleChanged(bool on) { m_initializing = true; ui.chbVisible->setChecked(on); m_initializing = false; } //border void LabelWidget::labelBorderShapeChanged(TextLabel::BorderShape shape) { m_initializing = true; ui.cbBorderShape->setCurrentIndex(static_cast(shape)); m_initializing = false; } void LabelWidget::labelBorderPenChanged(const QPen& pen) { m_initializing = true; if (ui.cbBorderStyle->currentIndex() != pen.style()) ui.cbBorderStyle->setCurrentIndex(pen.style()); if (ui.kcbBorderColor->color() != pen.color()) ui.kcbBorderColor->setColor(pen.color()); if (ui.sbBorderWidth->value() != pen.widthF()) - ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); + ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Unit::Point)); m_initializing = false; } void LabelWidget::labelBorderOpacityChanged(float value) { m_initializing = true; float v = (float)value*100.; ui.sbBorderOpacity->setValue(v); m_initializing = false; } //********************************************************** //******************** SETTINGS **************************** //********************************************************** void LabelWidget::load() { if (!m_label) return; m_initializing = true; ui.chbVisible->setChecked(m_label->isVisible()); //Text/TeX ui.tbTexUsed->setChecked( (bool) m_label->text().teXUsed ); if (m_label->text().teXUsed) ui.teLabel->setText(m_label->text().text); else { ui.teLabel->setHtml(m_label->text().text); ui.teLabel->selectAll(); // must be done to retrieve font ui.kfontRequester->setFont(ui.teLabel->currentFont()); } // if the text is empty yet, user LabelWidget::fontColor(), //extract the color from the html formatted text otherwise if (!m_label->text().text.isEmpty()) { QTextCharFormat format = ui.teLabel->currentCharFormat(); ui.kcbFontColor->setColor(format.foreground().color()); //ui.kcbBackgroundColor->setColor(format.background().color()); } else ui.kcbFontColor->setColor(m_label->fontColor()); //used for latex text only ui.kcbBackgroundColor->setColor(m_label->backgroundColor()); this->teXUsedChanged(m_label->text().teXUsed); ui.kfontRequesterTeX->setFont(m_label->teXFont()); ui.sbFontSize->setValue( m_label->teXFont().pointSize() ); //move the cursor to the end and set the focus to the text editor QTextCursor cursor = ui.teLabel->textCursor(); cursor.movePosition(QTextCursor::End); ui.teLabel->setTextCursor(cursor); ui.teLabel->setFocus(); // Geometry ui.cbPositionX->setCurrentIndex( (int)m_label->position().horizontalPosition ); positionXChanged(ui.cbPositionX->currentIndex()); - ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(m_label->position().point.x(),m_worksheetUnit) ); + ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(m_label->position().point.x(), m_worksheetUnit) ); ui.cbPositionY->setCurrentIndex( (int)m_label->position().verticalPosition ); positionYChanged(ui.cbPositionY->currentIndex()); - ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(m_label->position().point.y(),m_worksheetUnit) ); + ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(m_label->position().point.y(), m_worksheetUnit) ); if (!m_axesList.isEmpty()) { - ui.sbOffsetX->setValue( Worksheet::convertFromSceneUnits(m_axesList.first()->titleOffsetX(), Worksheet::Point) ); - ui.sbOffsetY->setValue( Worksheet::convertFromSceneUnits(m_axesList.first()->titleOffsetY(), Worksheet::Point) ); + ui.sbOffsetX->setValue( Worksheet::convertFromSceneUnits(m_axesList.first()->titleOffsetX(), Worksheet::Unit::Point) ); + ui.sbOffsetY->setValue( Worksheet::convertFromSceneUnits(m_axesList.first()->titleOffsetY(), Worksheet::Unit::Point) ); } ui.cbHorizontalAlignment->setCurrentIndex( (int) m_label->horizontalAlignment() ); ui.cbVerticalAlignment->setCurrentIndex( (int) m_label->verticalAlignment() ); ui.sbRotation->setValue( m_label->rotationAngle() ); //Border ui.cbBorderShape->setCurrentIndex(static_cast(m_label->borderShape())); ui.kcbBorderColor->setColor( m_label->borderPen().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_label->borderPen().style() ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_label->borderPen().widthF(), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_label->borderPen().widthF(), Worksheet::Unit::Point) ); ui.sbBorderOpacity->setValue( round(m_label->borderOpacity()*100) ); GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing = false; } void LabelWidget::loadConfig(KConfigGroup& group) { if (!m_label) return; m_initializing = true; //TeX ui.tbTexUsed->setChecked(group.readEntry("TeXUsed", (bool) m_label->text().teXUsed)); this->teXUsedChanged(m_label->text().teXUsed); ui.sbFontSize->setValue( group.readEntry("TeXFontSize", m_label->teXFont().pointSize()) ); ui.kfontRequesterTeX->setFont(group.readEntry("TeXFont", m_label->teXFont())); // Geometry ui.cbPositionX->setCurrentIndex( group.readEntry("PositionX", (int) m_label->position().horizontalPosition ) ); - ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(group.readEntry("PositionXValue", m_label->position().point.x()),m_worksheetUnit) ); + ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(group.readEntry("PositionXValue", m_label->position().point.x()), m_worksheetUnit) ); ui.cbPositionY->setCurrentIndex( group.readEntry("PositionY", (int) m_label->position().verticalPosition ) ); - ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(group.readEntry("PositionYValue", m_label->position().point.y()),m_worksheetUnit) ); + ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(group.readEntry("PositionYValue", m_label->position().point.y()), m_worksheetUnit) ); if (!m_axesList.isEmpty()) { - ui.sbOffsetX->setValue( Worksheet::convertFromSceneUnits(group.readEntry("OffsetX", m_axesList.first()->titleOffsetX()), Worksheet::Point) ); - ui.sbOffsetY->setValue( Worksheet::convertFromSceneUnits(group.readEntry("OffsetY", m_axesList.first()->titleOffsetY()), Worksheet::Point) ); + ui.sbOffsetX->setValue( Worksheet::convertFromSceneUnits(group.readEntry("OffsetX", m_axesList.first()->titleOffsetX()), Worksheet::Unit::Point) ); + ui.sbOffsetY->setValue( Worksheet::convertFromSceneUnits(group.readEntry("OffsetY", m_axesList.first()->titleOffsetY()), Worksheet::Unit::Point) ); } ui.cbHorizontalAlignment->setCurrentIndex( group.readEntry("HorizontalAlignment", (int) m_label->horizontalAlignment()) ); ui.cbVerticalAlignment->setCurrentIndex( group.readEntry("VerticalAlignment", (int) m_label->verticalAlignment()) ); ui.sbRotation->setValue( group.readEntry("Rotation", m_label->rotationAngle()) ); //Border ui.cbBorderShape->setCurrentIndex(group.readEntry("BorderShape").toInt()); ui.kcbBorderColor->setColor( group.readEntry("BorderColor", m_label->borderPen().color()) ); ui.cbBorderStyle->setCurrentIndex( group.readEntry("BorderStyle", (int)m_label->borderPen().style()) ); - ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_label->borderPen().widthF()), Worksheet::Point) ); + ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_label->borderPen().widthF()), Worksheet::Unit::Point) ); ui.sbBorderOpacity->setValue( group.readEntry("BorderOpacity", m_label->borderOpacity())*100 ); m_initializing = false; } void LabelWidget::saveConfig(KConfigGroup& group) { //TeX group.writeEntry("TeXUsed", ui.tbTexUsed->isChecked()); group.writeEntry("TeXFontColor", ui.kcbFontColor->color()); group.writeEntry("TeXBackgroundColor", ui.kcbBackgroundColor->color()); group.writeEntry("TeXFont", ui.kfontRequesterTeX->font()); // Geometry group.writeEntry("PositionX", ui.cbPositionX->currentIndex()); group.writeEntry("PositionXValue", Worksheet::convertToSceneUnits(ui.sbPositionX->value(),m_worksheetUnit) ); group.writeEntry("PositionY", ui.cbPositionY->currentIndex()); group.writeEntry("PositionYValue", Worksheet::convertToSceneUnits(ui.sbPositionY->value(),m_worksheetUnit) ); if (!m_axesList.isEmpty()) { - group.writeEntry("OffsetX", Worksheet::convertToSceneUnits(ui.sbOffsetX->value(), Worksheet::Point) ); - group.writeEntry("OffsetY", Worksheet::convertToSceneUnits(ui.sbOffsetY->value(), Worksheet::Point) ); + group.writeEntry("OffsetX", Worksheet::convertToSceneUnits(ui.sbOffsetX->value(), Worksheet::Unit::Point) ); + group.writeEntry("OffsetY", Worksheet::convertToSceneUnits(ui.sbOffsetY->value(), Worksheet::Unit::Point) ); } group.writeEntry("HorizontalAlignment", ui.cbHorizontalAlignment->currentIndex()); group.writeEntry("VerticalAlignment", ui.cbVerticalAlignment->currentIndex()); group.writeEntry("Rotation", ui.sbRotation->value()); //Border group.writeEntry("BorderShape", ui.cbBorderShape->currentIndex()); group.writeEntry("BorderStyle", ui.cbBorderStyle->currentIndex()); group.writeEntry("BorderColor", ui.kcbBorderColor->color()); - group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Point)); + group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Unit::Point)); group.writeEntry("BorderOpacity", ui.sbBorderOpacity->value()/100.0); } diff --git a/src/kdefrontend/widgets/LabelWidget.h b/src/kdefrontend/widgets/LabelWidget.h index f8e7c2cb9..876993fba 100644 --- a/src/kdefrontend/widgets/LabelWidget.h +++ b/src/kdefrontend/widgets/LabelWidget.h @@ -1,146 +1,146 @@ /*************************************************************************** File : LabelWidget.h Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2008-2020 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2014 Stefan Gerlach (stefan.gerlach@uni-konstanz.de) Description : label settings widget ***************************************************************************/ /*************************************************************************** * * * 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 * * * ***************************************************************************/ #ifndef LABELWIDGET_H #define LABELWIDGET_H #include "ui_labelwidget.h" #include "backend/worksheet/TextLabel.h" #include "kdefrontend/dockwidgets/BaseDock.h" #include #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING #include namespace KSyntaxHighlighting { class SyntaxHighlighter; } #endif class Label; class Axis; class QMenu; class LabelWidget : public QWidget { Q_OBJECT public: explicit LabelWidget(QWidget*); void setLabels(QList); void setAxes(QList); void updateUnits(); void load(); void loadConfig(KConfigGroup&); void saveConfig(KConfigGroup&); void setGeometryAvailable(bool); void setFixedLabelMode(bool); void setBorderAvailable(bool); private: Ui::LabelWidget ui; TextLabel* m_label{nullptr}; QList m_labelsList; QList m_axesList; bool m_initializing{false}; QMenu* m_dateTimeMenu; bool m_teXEnabled{false}; BaseDock::Units m_units{BaseDock::MetricUnits}; - Worksheet::Unit m_worksheetUnit{Worksheet::Centimeter}; + Worksheet::Unit m_worksheetUnit{Worksheet::Unit::Centimeter}; #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING KSyntaxHighlighting::SyntaxHighlighter* m_highlighter; KSyntaxHighlighting::Repository m_repository; #endif void initConnections() const; signals: void dataChanged(bool); private slots: //SLOTs for changes triggered in LabelWidget void textChanged(); void charFormatChanged(const QTextCharFormat&); void teXUsedChanged(bool); void fontColorChanged(const QColor&); void updateBackground() const; void backgroundColorChanged(const QColor&); void fontBoldChanged(bool); void fontItalicChanged(bool); void fontUnderlineChanged(bool); void fontStrikeOutChanged(bool); void fontSuperScriptChanged(bool); void fontSubScriptChanged(bool); void charMenu(); void insertChar(QChar); void fontChanged(const QFont&); void teXFontChanged(const QFont&); void fontSizeChanged(int); void dateTimeMenu(); void insertDateTime(QAction*); void positionXChanged(int); void positionYChanged(int); void customPositionXChanged(double); void customPositionYChanged(double); void horizontalAlignmentChanged(int); void verticalAlignmentChanged(int); void rotationChanged(int); void offsetXChanged(double); void offsetYChanged(double); void borderShapeChanged(int); void borderStyleChanged(int); void borderColorChanged(const QColor&); void borderWidthChanged(double); void borderOpacityChanged(int); void visibilityChanged(bool); //SLOTs for changes triggered in TextLabel void labelTextWrapperChanged(const TextLabel::TextWrapper&); void labelTeXImageUpdated(bool); void labelTeXFontChanged(const QFont&); void labelFontColorChanged(const QColor); void labelBackgroundColorChanged(const QColor); void labelPositionChanged(const TextLabel::PositionWrapper&); void labelHorizontalAlignmentChanged(TextLabel::HorizontalAlignment); void labelVerticalAlignmentChanged(TextLabel::VerticalAlignment); void labelOffsetxChanged(qreal); void labelOffsetyChanged(qreal); void labelRotationAngleChanged(qreal); void labelBorderShapeChanged(TextLabel::BorderShape); void labelBorderPenChanged(const QPen&); void labelBorderOpacityChanged(float); void labelVisibleChanged(bool); }; #endif //LABELWIDGET_H